mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-10-26 10:45:22 +00:00
从官方 Git 仓库导入 chandra447/pi-hermes-memory 在 tag v0.9.10 (df624056a2e8c68bc89ba0a81e1c120856ec9665,2026-10-02)的源码快照。 本次仅导入源码,尚未接入组合包:package.json、加载顺序、依赖与权限策略 均未改动。 - 原样导入 src/、tests/、docs/、scripts/、README、CHANGELOG、PLAN、 AGENTS 与 LICENSE;该快照共 156 个上游文件。 - 剔除嵌套 .git、上游 .github 工作流、.ralph/、.claude/、node_modules、 覆盖率与构建产物。 - 新增中文 UPSTREAM.md,记录来源,以及启用前必须完成的待决事项: 在 pi-compaction 之前注册加载顺序、better-sqlite3 版本与根 allowScripts 对齐、strip-ansi 解析确认。 - 说明选择 tag 而非 main 的原因:导入时上游 main 已领先至 24fcd8ad26fc27badb8d7dbaf8034645de07b893(2026-10-11),选 tag 是为 对应一个已发布、可复现的版本。 尚未接线。启用前还需反转 tests/extension-load.test.ts 中"不得加载 Hermes"的断言,并同步 pi-tool-search 分组、权限策略与文档。 验证:快照 156 个上游文件全部导入,目录内仅多出 UPSTREAM.md 一份; 已确认与上游 main 内容存在差异,证明取的是 tag 而非 main。
183 lines
7.3 KiB
TypeScript
183 lines
7.3 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { describe, it } from "node:test";
|
|
import { createMemoryInitializer, withMemoryInitialization } from "../src/memory-initialization.js";
|
|
|
|
describe("memory initialization", () => {
|
|
it("does no work until used and shares initialization across callers", async () => {
|
|
let calls = 0;
|
|
const gate = Promise.withResolvers<void>();
|
|
const init = createMemoryInitializer(async () => { calls++; await gate.promise; });
|
|
assert.equal(calls, 0);
|
|
assert.equal(init.isReady(), false);
|
|
const first = init.ensure();
|
|
const second = init.ensure();
|
|
await Promise.resolve();
|
|
assert.equal(calls, 1);
|
|
gate.resolve();
|
|
await Promise.all([first, second]);
|
|
await init.ensure();
|
|
assert.equal(calls, 1);
|
|
assert.equal(init.isReady(), true);
|
|
await init.close();
|
|
});
|
|
|
|
it("retries after failure instead of caching a rejected promise", async () => {
|
|
let calls = 0;
|
|
const init = createMemoryInitializer(async () => {
|
|
if (++calls === 1) throw new Error("load failed");
|
|
});
|
|
await assert.rejects(init.ensure(), /load failed/);
|
|
assert.equal(init.isReady(), false);
|
|
await init.ensure();
|
|
assert.equal(calls, 2);
|
|
assert.equal(init.isReady(), true);
|
|
await init.close();
|
|
});
|
|
|
|
it("closing an unused session does not initialize it", async () => {
|
|
let calls = 0;
|
|
const init = createMemoryInitializer(async () => { calls++; });
|
|
await init.close();
|
|
await init.close();
|
|
await assert.rejects(init.ensure(), /shut down/);
|
|
assert.equal(calls, 0);
|
|
});
|
|
|
|
it("joins an in-flight load on shutdown and rejects its waiting operation", async () => {
|
|
const gate = Promise.withResolvers<void>();
|
|
const init = createMemoryInitializer(() => gate.promise);
|
|
const first = assert.rejects(init.ensure(), /shut down/);
|
|
let closed = false;
|
|
const closing = init.close().then(() => { closed = true; });
|
|
await Promise.resolve();
|
|
assert.equal(closed, false);
|
|
gate.resolve();
|
|
await Promise.all([first, closing]);
|
|
assert.equal(closed, true);
|
|
});
|
|
});
|
|
|
|
describe("guarded memory entry points", () => {
|
|
it("preserves metadata and waits for initialization before tools and commands", async () => {
|
|
const tools: any[] = [];
|
|
const commands: any[] = [];
|
|
const events: string[] = [];
|
|
const contexts: unknown[] = [];
|
|
const init = createMemoryInitializer(async () => {}, async (ctx) => { contexts.push(ctx); events.push("ready"); });
|
|
const pi = withMemoryInitialization({
|
|
registerTool: (tool: any) => tools.push(tool),
|
|
registerCommand: (name: string, options: any) => commands.push({ name, ...options }),
|
|
} as any, init);
|
|
const renderResult = () => undefined;
|
|
const parameters = {} as any;
|
|
const getArgumentCompletions = () => null;
|
|
pi.registerTool({
|
|
name: "memory_test", label: "Test", description: "Test", parameters, renderResult: renderResult as any,
|
|
execute: async (...args) => { events.push("tool"); return { content: [], details: args }; },
|
|
});
|
|
pi.registerCommand("memory-test", {
|
|
description: "Test", getArgumentCompletions,
|
|
handler: async () => { events.push("command"); },
|
|
});
|
|
assert.deepEqual(events, []);
|
|
assert.equal(tools[0].renderResult, renderResult);
|
|
assert.equal(tools[0].parameters, parameters);
|
|
assert.equal(commands[0].getArgumentCompletions, getArgumentCompletions);
|
|
const ctx = { cwd: "/project" };
|
|
const args = ["id", { query: "test" }, undefined, undefined, ctx];
|
|
assert.deepEqual((await tools[0].execute(...args)).details, args);
|
|
await commands[0].handler("test", ctx);
|
|
assert.deepEqual(events, ["ready", "tool", "ready", "command"]);
|
|
assert.deepEqual(contexts, [ctx, ctx]);
|
|
});
|
|
|
|
it("does not execute a cancelled tool or cancel initialization for sibling calls", async () => {
|
|
let tool: any;
|
|
let loads = 0;
|
|
let executions = 0;
|
|
const gate = Promise.withResolvers<void>();
|
|
const init = createMemoryInitializer(async () => { loads++; await gate.promise; });
|
|
withMemoryInitialization({ registerTool: (def: any) => { tool = def; } } as any, init)
|
|
.registerTool({ name: "test", label: "Test", description: "Test", parameters: {} as any,
|
|
execute: async () => { executions++; return { content: [], details: {} }; } });
|
|
const aborted = AbortSignal.abort();
|
|
await assert.rejects(tool.execute("id", {}, aborted, undefined, { cwd: "/project" }));
|
|
assert.equal(loads, 0);
|
|
const controller = new AbortController();
|
|
const first = assert.rejects(tool.execute("id", {}, controller.signal, undefined, { cwd: "/project" }));
|
|
const second = tool.execute("id2", {}, undefined, undefined, { cwd: "/project" });
|
|
controller.abort();
|
|
gate.resolve();
|
|
await Promise.all([first, second]);
|
|
assert.equal(loads, 1);
|
|
assert.equal(executions, 1);
|
|
await init.close();
|
|
});
|
|
|
|
it("keeps first use unready and shutdown waiting through project binding and backfill", async () => {
|
|
const bind = Promise.withResolvers<void>();
|
|
const backfill = Promise.withResolvers<void>();
|
|
const started = Promise.withResolvers<void>();
|
|
const init = createMemoryInitializer(async () => {}, async () => {
|
|
started.resolve();
|
|
await bind.promise;
|
|
await backfill.promise;
|
|
});
|
|
const first = init.ensure({ cwd: "/project" });
|
|
await started.promise;
|
|
assert.equal(init.isReady(), false);
|
|
const rejected = assert.rejects(first, /shut down/);
|
|
let closed = false;
|
|
const closing = init.close().then(() => { closed = true; });
|
|
bind.resolve();
|
|
await new Promise(setImmediate);
|
|
assert.equal(init.isReady(), false);
|
|
assert.equal(closed, false);
|
|
backfill.resolve();
|
|
await Promise.all([rejected, closing]);
|
|
assert.equal(closed, true);
|
|
});
|
|
|
|
it("waits for tool execution, not just its preparation, before closing", async () => {
|
|
const execution = Promise.withResolvers<void>();
|
|
const started = Promise.withResolvers<void>();
|
|
const init = createMemoryInitializer(async () => {});
|
|
const running = init.run({ cwd: "/project" }, async () => {
|
|
started.resolve();
|
|
await execution.promise;
|
|
return "written";
|
|
});
|
|
await started.promise;
|
|
let closed = false;
|
|
const closing = init.close().then(() => { closed = true; });
|
|
await new Promise(setImmediate);
|
|
assert.equal(closed, false);
|
|
await assert.rejects(init.run({ cwd: "/project" }, async () => "late"), /shut down/);
|
|
execution.resolve();
|
|
assert.equal(await running, "written");
|
|
await closing;
|
|
});
|
|
|
|
it("cancels a waiting tool immediately while retaining shared preparation for shutdown", { timeout: 2000 }, async () => {
|
|
const preparation = Promise.withResolvers<void>();
|
|
const started = Promise.withResolvers<void>();
|
|
const init = createMemoryInitializer(async () => {}, async () => {
|
|
started.resolve();
|
|
await preparation.promise;
|
|
});
|
|
const controller = new AbortController();
|
|
const cancelled = assert.rejects(init.run({ cwd: "/project" }, async () => {
|
|
assert.fail("cancelled tool must not execute");
|
|
}, controller.signal), /cancelled/);
|
|
await started.promise;
|
|
controller.abort(new Error("cancelled"));
|
|
await cancelled;
|
|
let closed = false;
|
|
const closing = init.close().then(() => { closed = true; });
|
|
await new Promise(setImmediate);
|
|
assert.equal(closed, false);
|
|
preparation.resolve();
|
|
await closing;
|
|
});
|
|
});
|