refactor: consolidate chat state and task bridges

Centralize session scope, chat projections and typed task bridges.
Keep historical interaction replies authoritative and publish terminal state to subscribers. Validate Server contracts before replacing generated artifacts.
This commit is contained in:
2026-11-09 17:55:00 +08:00
parent addc81e106
commit 6279a4b555
31 changed files with 849 additions and 522 deletions
+45
View File
@@ -0,0 +1,45 @@
import { expect, it } from "bun:test";
import express from "express";
import type { OperationalTaskClient } from "../../src/product/operational-task-client.js";
import { registerTaskCommandBridge } from "../../src/routes/task-command-bridge.js";
import { removeRuntimeSessionContext, setRuntimeSessionContext } from "../../src/runtime/sessionContext.js";
it("preserves command-specific session purposes and trusted bridge authentication", async () => {
const calls: unknown[][] = [];
const client = {
requestDataAccess: async (...args: unknown[]) => { calls.push(args); return {}; },
proposeAction: async (...args: unknown[]) => { calls.push(args); throw Object.assign(new Error("conflict"), { status: 409 }); },
addHumanEvidence: async (...args: unknown[]) => { calls.push(args); return {}; },
} as unknown as Pick<OperationalTaskClient, "requestDataAccess" | "proposeAction" | "addHumanEvidence">;
for (const purpose of ["task_execution", "task_collaboration"] as const) setRuntimeSessionContext({
sessionId: purpose, clientSessionId: `client-${purpose}`, taskId: "task", purpose,
actorKey: "actor", projectKey: "project", traceId: "test",
});
const app = express(); app.use(express.json());
registerTaskCommandBridge(app, { internalToken: "test-token", client });
const server = app.listen(0, "127.0.0.1");
await new Promise<void>((resolve) => server.once("listening", resolve));
const post = (kind: string, session: string, data: object, headers = {}) => fetch(`http://127.0.0.1:${(server.address() as { port: number }).port}/internal/operational-task/${kind}`, {
method: "POST", headers: { "Content-Type": "application/json", "x-agent-internal-token": "test-token", ...headers }, body: JSON.stringify({ session_id: session, ...data }),
});
const requestId = "60000000-0000-4000-8000-000000000001";
const access = { request: { requestId, nodeId: "read", scopes: ["scada_devices"], reason: "查询" } };
const evidence = { command: { request_id: requestId, evidence: { evidence_id: "e1", kind: "human_input", summary: "已复核", source_label: "用户", observed_at: "2026-11-09T00:00:00Z" } } };
try {
expect((await post("access-requests", "task_execution", access, { Origin: "http://browser.test" })).status).toBe(403);
expect((await post("access-requests", "task_execution", access, { "x-agent-internal-token": "wrong" })).status).toBe(403);
expect((await post("access-requests", "task_collaboration", access)).status).toBe(403);
expect((await post("human-evidence", "task_execution", evidence)).status).toBe(403);
expect(calls).toHaveLength(0);
expect((await post("access-requests", "task_execution", access)).status).toBe(200);
expect(calls[0]).toEqual(["task_execution", "task", { ...access.request, sourceSessionId: "client-task_execution" }]);
expect((await post("human-evidence", "task_collaboration", evidence)).status).toBe(200);
const action = { action: { request_id: requestId, reason: "核验", kind: "create_work_order", payload: { title: "采样", description: "核验", work_order_type: "general" } } };
expect((await post("actions", "task_execution", action)).status).toBe(409);
expect((await post("actions", "task_collaboration", action)).status).toBe(409);
expect((await post("actions", "unknown", action)).status).toBe(403);
} finally {
removeRuntimeSessionContext("task_execution"); removeRuntimeSessionContext("task_collaboration");
await new Promise<void>((resolve) => server.close(() => resolve()));
}
});