import assert from "node:assert/strict"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import test from "node:test"; import { AcpBackendRegistry } from "../src/acp/backend-registry.js"; import { AcpSessionManager } from "../src/acp/session-manager.js"; import { parseConfig } from "../src/config.js"; import { DurableSessionStore } from "../src/core/durable-session-store.js"; import { RoleRegistry } from "../src/roles/role-registry.js"; const fixture = path.resolve("test/fixtures/fake-acp-agent.mjs"); function config(stateFile: string, logFile: string) { return parseConfig({ configVersion: 2, acp: { stateFile, promptTimeoutMs: 2_000, cancelGraceMs: 100, idleTimeoutMs: 100, sweepIntervalMs: 20, maxProcesses: 2 }, backends: [{ id: "kimi", command: process.execPath, args: [fixture], env: { FAKE_ACP_LOG: logFile } }], skills: [], defaultRole: "assistant", roles: [{ id: "assistant", backend: "kimi", workspace: path.resolve("."), persona: "test", skills: [], policy: { permissionMode: "deny" } }], platforms: {} }); } async function runtime(stateFile: string, logFile: string) { const cfg = config(stateFile, logFile); const store = new DurableSessionStore(stateFile); await store.open(); return { cfg, store, manager: new AcpSessionManager(cfg.acp, new AcpBackendRegistry(cfg.backends), new RoleRegistry(cfg), store) }; } test("creates, persists, idles, and resumes the same native session", async () => { const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "gori-acp-")); const state = path.join(dir, "state.json"); const log = path.join(dir, "fake.log"); const first = await runtime(state, log); const response = await first.manager.prompt({ platform: "qq", chatId: "chat", userId: "user", text: "one" }); const sessionId = response.text.split(":")[1]; assert.match(response.text, /reply:fake-/); await new Promise((resolve) => setTimeout(resolve, 180)); await first.manager.prompt({ platform: "qq", chatId: "chat", userId: "user", text: "two" }); await first.manager.shutdown(); await first.store.close(); const second = await runtime(state, log); const resumed = await second.manager.prompt({ platform: "qq", chatId: "chat", userId: "user", text: "three" }); assert.equal(resumed.text, `reply:${sessionId}:three`); const entries = (await fs.promises.readFile(log, "utf8")).trim().split("\n").map(JSON.parse); assert.ok(entries.some((entry) => entry.method === "session/resume" && entry.sessionId === sessionId)); await second.manager.shutdown(); await second.store.close(); }); test("cancel reaches a hanging ACP prompt and new unbinds", async () => { const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "gori-acp-cancel-")); const state = path.join(dir, "state.json"); const log = path.join(dir, "fake.log"); const current = await runtime(state, log); await current.manager.prompt({ platform: "qq", chatId: "chat", userId: "user", text: "ready" }); const hanging = current.manager.prompt({ platform: "qq", chatId: "chat", userId: "user", text: "hang" }); await new Promise((resolve) => setTimeout(resolve, 50)); assert.equal(await current.manager.cancel("qq", "chat"), true); await hanging; await current.manager.reset("qq", "chat"); assert.equal(current.store.stats().bindings, 0); await current.manager.shutdown(); await current.store.close(); });