import assert from "node:assert/strict"; import test from "node:test"; import type { ConversationRuntime } from "../src/acp/types.js"; import { parseConfig } from "../src/config.js"; import type { PlatformAdapter } from "../src/core/adapter.js"; import { Gateway } from "../src/core/gateway.js"; import type { IncomingMessage } from "../src/core/types.js"; import { RoleRegistry } from "../src/roles/role-registry.js"; class FakeRuntime implements ConversationRuntime { role = "assistant"; prompts = 0; cancelled = 0; resets = 0; release?: () => void; async prompt() { this.prompts++; await new Promise((resolve) => { this.release = resolve; }); return { text: "done", roleId: this.role, backendId: "kimi" }; } async cancel() { this.cancelled++; this.release?.(); return true; } async reset() { this.resets++; } async selectRole(_p: string, _c: string, role: string) { this.role = role; } selectedRole() { return this.role; } status() { return { role: this.role, running: Boolean(this.release) }; } stats() { return { activeWorkers: 0, inFlight: 0, crashes: 0, persistedBindings: 0 }; } async shutdown() {} } const cfg = parseConfig({ configVersion: 2, backends: [{ id: "kimi", command: "kimi", args: ["acp"] }], defaultRole: "assistant", roles: [ { id: "assistant", backend: "kimi", workspace: "/tmp" }, { id: "ops", backend: "kimi", workspace: "/tmp" } ], platforms: {} }); const adapter: PlatformAdapter = { name: "test", async handleWebhook() { return {}; }, async sendMessage() {} }; const message = (text: string): IncomingMessage => ({ platform: "qq", chatId: "chat", userId: "user", text }); test("cancel bypasses the chat lock and new resets", async () => { const runtime = new FakeRuntime(); const gateway = new Gateway(cfg.policy, runtime, new RoleRegistry(cfg)); const turn = gateway.receive(message("work"), adapter, { synchronous: true }); await new Promise((resolve) => setTimeout(resolve, 10)); const cancelled = await gateway.receive(message("/cancel"), adapter, { synchronous: true }); assert.equal(cancelled.reply, "Cancellation requested."); await turn; const reset = await gateway.receive(message("/new"), adapter, { synchronous: true }); assert.match(reset.reply || "", /new native ACP session/); assert.equal(runtime.resets, 1); }); test("role aliases, role selection, and status are routed", async () => { const runtime = new FakeRuntime(); const gateway = new Gateway(cfg.policy, runtime, new RoleRegistry(cfg)); assert.match((await gateway.receive(message("/agents"), adapter, { synchronous: true })).reply || "", /Deprecated alias/); assert.equal((await gateway.receive(message("/role ops"), adapter, { synchronous: true })).reply, "Selected role: ops"); assert.match((await gateway.receive(message("/status"), adapter, { synchronous: true })).reply || "", /role=ops/); });