import { describe, expect, it, mock } from "bun:test"; import express from "express"; import { createAgentMessageQuery, registerAgentMessageBridge } from "../../src/routes/agent-message-routes.js"; import { setRuntimeSessionContext, removeRuntimeSessionContext } from "../../src/runtime/sessionContext.js"; import type { SessionMetadataStore, SessionRecord } from "../../src/sessions/metadataStore.js"; import type { AgentMessageCoordinator } from "../../src/chat/agent-message-coordinator.js"; const task = "81000000-0000-4000-8000-000000000001"; const run = "81000000-0000-4000-8000-000000000002"; const request = "81000000-0000-4000-8000-000000000003"; describe("scoped Agent message bridge", () => { it("derives sender from runtime, validates arguments, and denies child/review/cross-run writes", async () => { const records = new Map(); for (const [id, purpose] of [["main", "ordinary_chat"], ["investigator", "task_execution"], ["review", "ordinary_chat"]] as const) { const record: SessionRecord = { sessionId: id, actorKey: "owner", projectKey: "project", projectId: task, createdAt: "now", updatedAt: "now", status: "active", purpose, ...(purpose === "task_execution" ? { taskId: task, runId: run } : {}) }; records.set(id, record); setRuntimeSessionContext({ ...record, clientSessionId: id, traceId: "test", learningMode: id === "review" ? "review" : "interactive" }); } const send = mock(async (_session: string, input: unknown) => ({ input, target_session_id: null })); const reply = mock(async () => ({})); const read = mock(async () => ({ items: [], next_cursor: null })); const coordinator = { client: { send, reply, list: read, get: async () => ({ task_id: task, run_id: request, direction: "to_task" }) }, isActive: () => true, notify: () => {}, deliver: async () => {}, } as unknown as AgentMessageCoordinator; const app = express(); app.use(express.json()); registerAgentMessageBridge(app, { internalToken: "test-only", messages: coordinator, sessions: { get: async (_context: unknown, id: string) => records.get(id) ?? null } as unknown as SessionMetadataStore }); const server = app.listen(0, "127.0.0.1"); await new Promise((resolve) => server.once("listening", resolve)); const url = `http://127.0.0.1:${(server.address() as { port: number }).port}/internal/agent-messages`; const call = (operation: string, session: string, input: unknown, token = "test-only") => fetch(`${url}/${operation}`, { method: "POST", headers: { "Content-Type": "application/json", "x-agent-internal-token": token }, body: JSON.stringify({ session_id: session, input }), }); const input = { task_id: task, run_id: run, request_id: request, message: "核对压力记录" }; try { expect((await call("send", "main", input, "wrong")).status).toBe(403); expect((await call("send", "child", input)).status).toBe(403); expect((await call("send", "review", input)).status).toBe(403); expect((await call("send", "investigator", input)).status).toBe(403); expect((await call("send", "main", { ...input, source_session_id: "someone-else" })).status).toBe(400); expect((await call("send", "main", input)).status).toBe(200); expect(send).toHaveBeenCalledTimes(1); expect(send.mock.calls[0]).toEqual(["main", input]); expect((await call("reply", "investigator", { message_id: request, request_id: request, message: "回复" })).status).toBe(403); expect(reply).not.toHaveBeenCalled(); expect((await call("read", "main", {})).status).toBe(404); const query = createAgentMessageQuery({ sessions: { get: async (_context: unknown, id: string) => records.get(id) ?? null } as unknown as SessionMetadataStore, messages: coordinator }); await query("main", "read", {}); expect(read).toHaveBeenCalledWith("main", "main", undefined, 20); await expect(query("investigator", "list", {})).rejects.toThrow(); await expect(query("child", "read", {})).rejects.toThrow(); } finally { for (const id of records.keys()) removeRuntimeSessionContext(id); await new Promise((resolve) => server.close(() => resolve())); } }); });