Move project and host queries into native MCP with host-owned OAuth/context, while retaining memory, skills, actions and publication plugins. Add nearby-model queries and explicit chat/task/learning model defaults. Adopt Responses HTTP/SSE and native steer/queue inbox delivery, preserving scoped session ownership, pending input edits and investigation communication. Disable local-provider discovery: Server /health allowed native vLLM to probe /v1/models on port 8000. A native before/after regression prevents recurrence. Validation: 170 focused Bun tests; service/plugin typechecks; native discovery, provider-discovery and input-inbox smoke checks; no paid model inference.
72 lines
4.6 KiB
TypeScript
72 lines
4.6 KiB
TypeScript
import { describe, expect, it } from "bun:test";
|
|
import express from "express";
|
|
|
|
import type { OperationalTaskClient } from "../../src/product/operational-task-client.js";
|
|
import { registerTaskPublicationBridge } from "../../src/routes/task-publication-bridge.js";
|
|
import { removeRuntimeSessionContext, setRuntimeSessionContext } from "../../src/runtime/sessionContext.js";
|
|
|
|
describe("task publication bridge", () => {
|
|
it("requires a trusted execution context and injects task identity without browser capabilities", async () => {
|
|
const taskId = "50000000-0000-4000-8000-000000000001";
|
|
const requestId = "60000000-0000-4000-8000-000000000001";
|
|
const contexts = ["task-publication-execution", "task-publication-collaboration", "task-publication-ordinary"];
|
|
const published: Array<{ kind: string; args: unknown[] }> = [];
|
|
contexts.forEach((sessionId, index) => setRuntimeSessionContext({
|
|
sessionId, clientSessionId: `client-${index}`, actorKey: "actor-1", projectKey: "project-1", traceId: "test",
|
|
taskId, purpose: ["task_execution", "task_collaboration", "ordinary_chat"][index] as "task_execution",
|
|
}));
|
|
const app = express();
|
|
app.use(express.json());
|
|
registerTaskPublicationBridge(app, { internalToken: "internal-test", client: {
|
|
publishArtifacts: async (...args: unknown[]) => {
|
|
published.push({ kind: "artifacts", args });
|
|
return { progress_revision: 1 };
|
|
},
|
|
publishPresentation: async (...args: unknown[]) => {
|
|
published.push({ kind: "presentation", args });
|
|
return { recorded: true, event_seq: 8, run_id: "55000000-0000-4000-8000-000000000001", execution_generation: 1 };
|
|
},
|
|
} as unknown as Pick<OperationalTaskClient, "publishArtifacts" | "publishPresentation"> });
|
|
const server = app.listen(0, "127.0.0.1");
|
|
await new Promise<void>((resolve) => server.once("listening", resolve));
|
|
const base = `http://127.0.0.1:${(server.address() as { port: number }).port}`;
|
|
const post = (kind: string, body: unknown, extraHeaders: Record<string, string> = {}) => fetch(`${base}/internal/operational-task/${kind}`, {
|
|
method: "POST", headers: { "Content-Type": "application/json", "x-agent-internal-token": "internal-test", ...extraHeaders },
|
|
body: JSON.stringify(body),
|
|
});
|
|
const progress = { summary: "核对实际资料", diagram: null };
|
|
const command = { action: "highlight_features", layer: "pipes", feature_ids: ["P-1"], effect: "pulse" };
|
|
try {
|
|
const body = { session_id: contexts[0], request_id: requestId, command };
|
|
expect((await post("presentation", body, { "x-agent-internal-token": "wrong" })).status).toBe(403);
|
|
expect((await post("presentation", body, { Origin: "http://browser.test" })).status).toBe(403);
|
|
expect((await post("presentation", { ...body, session_id: "unknown" })).status).toBe(403);
|
|
for (const sessionId of contexts.slice(1)) {
|
|
expect((await post("presentation", { ...body, session_id: sessionId })).status).toBe(403);
|
|
expect((await post("artifacts", { session_id: sessionId, request_id: requestId, progress })).status).toBe(403);
|
|
}
|
|
expect((await post("presentation", { ...body, run_id: "model-supplied-run" })).status).toBe(400);
|
|
expect((await post("presentation", { ...body, command: {
|
|
action: "set_overlay", overlay_id: "o1", title: "位置", mode: "geometry", evidence_id: "e1", data: { coordinates: [1, 2] },
|
|
} })).status).toBe(400);
|
|
expect(published).toHaveLength(0);
|
|
const result = await post("presentation", body);
|
|
expect(result.status).toBe(200);
|
|
expect(await result.json()).toMatchObject({ recorded: true, event_seq: 8 });
|
|
expect(published[0]).toEqual({ kind: "presentation", args: [contexts[0], taskId, {
|
|
requestId, sourceSessionId: "client-0", command,
|
|
}] });
|
|
expect((await post("artifacts", { session_id: contexts[0], request_id: requestId, progress })).status).toBe(200);
|
|
expect(published[1]?.args).toEqual([contexts[0], taskId, {
|
|
requestId, sourceSessionId: "client-0", evidence: undefined, executionPlan: undefined, report: undefined, progress,
|
|
}]);
|
|
const report = { title: "报告", current_conclusion: "核验结束", conclusion_boundary: "授权数据范围",
|
|
stage: "final", outcome: "concluded", stop_reason: null, data_gaps: [] };
|
|
expect((await post("artifacts", { session_id: contexts[1], request_id: requestId, report })).status).toBe(200);
|
|
} finally {
|
|
contexts.forEach(removeRuntimeSessionContext);
|
|
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
}
|
|
});
|
|
});
|