Publish graph deltas using the Server-owned cumulative graph contract. Validate historical comparison chart publications and consolidate Agent documentation.
88 lines
5.0 KiB
TypeScript
88 lines
5.0 KiB
TypeScript
import { describe, expect, it } from "bun:test";
|
|
import express from "express";
|
|
|
|
import { tool } from "../../.opencode/tool.js";
|
|
import publishProgress from "../../.opencode/tools/publish_task_progress.js";
|
|
import type { OperationalTaskClient } from "../../src/product/operational-task-client.js";
|
|
import { investigationNodeAnnotations } from "../../src/product/investigation-node-state.js";
|
|
import { registerTaskPublicationBridge } from "../../src/routes/task-publication-bridge.js";
|
|
import { removeRuntimeSessionContext, setRuntimeSessionContext } from "../../src/runtime/sessionContext.js";
|
|
|
|
const native = tool.schema.object(publishProgress.args);
|
|
|
|
describe("investigation reference contract", () => {
|
|
it("advertises five optional annotations to the model without execution identities or Mermaid", () => {
|
|
const schema = tool.schema.toJSONSchema(native, { io: "input" });
|
|
const progress = schema.properties!.progress as {
|
|
properties: Record<string, {
|
|
properties: { nodes: { items: { properties: { status: { anyOf: Array<{ enum?: unknown[] }> } }; required?: string[] } } };
|
|
}>;
|
|
};
|
|
const node = progress.properties.graph_update.properties.nodes.items;
|
|
const statuses = node.properties.status.anyOf.flatMap((variant) => variant.enum ?? []);
|
|
expect(statuses).toEqual([...investigationNodeAnnotations]);
|
|
expect(node.required).not.toContain("status");
|
|
expect(Object.keys(schema.properties!)).toEqual(["request_id", "progress"]);
|
|
expect(Object.keys(progress.properties)).toEqual(["summary", "graph_update"]);
|
|
});
|
|
|
|
it("transports a native graph update and text-only update through the trusted task bridge", async () => {
|
|
const runtimeSessionId = "reference-contract-native-session";
|
|
const taskId = "50000000-0000-4000-8000-000000000031";
|
|
const token = "reference-contract-test-token";
|
|
const published: unknown[][] = [];
|
|
setRuntimeSessionContext({
|
|
sessionId: runtimeSessionId, clientSessionId: "reference-contract-client", actorKey: "test-actor",
|
|
projectKey: "test-project", traceId: "test", taskId, purpose: "task_execution",
|
|
});
|
|
const app = express();
|
|
app.use(express.json());
|
|
registerTaskPublicationBridge(app, { internalToken: token, client: {
|
|
publishArtifacts: async (...args: unknown[]) => {
|
|
published.push(args);
|
|
return { progress_revision: published.length };
|
|
},
|
|
} 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 baseUrl = `http://127.0.0.1:${(server.address() as { port: number }).port}`;
|
|
const oldToken = process.env.AGENT_INTERNAL_TOKEN;
|
|
const oldBaseUrl = process.env.TJWATER_AGENT_INTERNAL_BASE_URL;
|
|
process.env.AGENT_INTERNAL_TOKEN = token;
|
|
process.env.TJWATER_AGENT_INTERNAL_BASE_URL = baseUrl;
|
|
try {
|
|
const requestId = "60000000-0000-4000-8000-000000000031";
|
|
const progress = { summary: "整理已有路线与结果", graph_update: {
|
|
nodes: investigationNodeAnnotations.map((status, index) => ({
|
|
node_id: `step_${index}`, label: `参考步骤 ${index}`, status,
|
|
...(status === "failed" ? { detail: "此前查询超时,保留失败路径" } : {}),
|
|
})), edges: [{ source: "step_3", target: "step_0", label: "替代路线" }],
|
|
} };
|
|
const args = native.parse({ request_id: requestId, progress });
|
|
expect(JSON.parse(await publishProgress.execute(args, { sessionID: runtimeSessionId }))).toEqual({ progress_revision: 1 });
|
|
expect(published[0]?.slice(0, 2)).toEqual([runtimeSessionId, taskId]);
|
|
expect(published[0]?.[2]).toMatchObject({ requestId, sourceSessionId: "reference-contract-client",
|
|
progress: { ...progress, diagram: null },
|
|
});
|
|
await publishProgress.execute(native.parse({ request_id: "60000000-0000-4000-8000-000000000032", progress: { summary: "补充本轮说明" } }),
|
|
{ sessionID: runtimeSessionId });
|
|
expect(published[1]?.[2]).toMatchObject({ progress: { summary: "补充本轮说明", diagram: null } });
|
|
const invalid = await fetch(`${baseUrl}/internal/operational-task/artifacts`, {
|
|
method: "POST", headers: { "Content-Type": "application/json", "x-agent-internal-token": token },
|
|
body: JSON.stringify({ session_id: runtimeSessionId, request_id: requestId, progress: {
|
|
summary: "旧执行状态不进入新图", graph_update: { nodes: [{ node_id: "old", label: "核查", status: "running" }] },
|
|
} }),
|
|
});
|
|
expect(invalid.status).toBe(400);
|
|
expect(published).toHaveLength(2);
|
|
} finally {
|
|
if (oldToken === undefined) delete process.env.AGENT_INTERNAL_TOKEN;
|
|
else process.env.AGENT_INTERNAL_TOKEN = oldToken;
|
|
if (oldBaseUrl === undefined) delete process.env.TJWATER_AGENT_INTERNAL_BASE_URL;
|
|
else process.env.TJWATER_AGENT_INTERNAL_BASE_URL = oldBaseUrl;
|
|
removeRuntimeSessionContext(runtimeSessionId);
|
|
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
}
|
|
});
|
|
});
|