Files
my-pi/pi-hermes-memory/tests/memory-initialization.test.ts
云服务部-叶林立 312bb2ba5a feat(pi-hermes-memory): 导入上游 v0.9.10 源码快照
从官方 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。
2026-10-13 23:19:35 -07:00

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;
});
});