Refactor runtime around isolated bot instances

This commit is contained in:
zenord
2026-08-16 23:45:09 +08:00
parent 914579a94c
commit f9fb4ef776
51 changed files with 2355 additions and 1459 deletions
+12 -16
View File
@@ -1,32 +1,26 @@
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<void>((resolve) => { this.release = resolve; }); return { text: "done", roleId: this.role, backendId: "kimi" }; }
prompts = 0; cancelled = 0; resets = 0; release?: () => void;
async prompt() { this.prompts++; await new Promise<void>((resolve) => { this.release = resolve; }); return { text: "done", botId: "test-bot", agentId: "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) }; }
status() { return { bot: "test-bot", agent: "kimi", workspace: "/tmp", 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 policy = { allowedUsers: [] as string[], allowedChats: [] as string[], requireMentionInGroup: false };
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 runtime = new FakeRuntime(); const gateway = new Gateway(policy, runtime);
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 });
@@ -37,9 +31,11 @@ test("cancel bypasses the chat lock and new resets", async () => {
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/);
test("fixed Bot status and retired role commands never reach ACP", async () => {
const runtime = new FakeRuntime(); const gateway = new Gateway(policy, runtime);
for (const command of ["/roles", "/role ops", "/agents", "/agent ops"]) {
assert.match((await gateway.receive(message(command), adapter, { synchronous: true })).reply || "", /removed in Config v3/);
}
assert.match((await gateway.receive(message("/status"), adapter, { synchronous: true })).reply || "", /bot=test-bot/);
assert.equal(runtime.prompts, 0);
});