import { describe, expect, it, spyOn } from "bun:test"; import { tool } from "../../.opencode/tool.js"; import publishReport from "../../.opencode/tools/publish_task_report.js"; import { taskReportSchema } from "../../src/product/investigation-report.js"; const native = tool.schema.object(publishReport.args); const requestId = "60000000-0000-4000-8000-000000000001"; const report = { title: "候选隔离方案", current_conclusion: "已取得候选与隔离分析", conclusion_boundary: "候选尚待现场核验", stage: "final", outcome: "concluded", evidence_refs: ["query-1"], recommendations: ["核验阀门可用性"], pending_items: ["等待现场回执"], data_gaps: [], }; const block = { block_id: "summary", fallback_text: "已取得有边界的方案。", spec: { root: "text", elements: { text: { type: "Text", children: [], props: { text: "已取得有边界的方案。", tone: "neutral" } } }, } }; const gap = { gap_id: "sample", scope: "投诉时段", missing_data: "实际采样", required_data: "现场采样结果", impact: "无法形成必要判断", evidence_refs: ["query-1"], blocking: true }; describe("report publication contract", () => { it("advertises outcome-specific constraints without requiring host-owned fields", () => { const schema = tool.schema.toJSONSchema(native, { io: "input" }); const reportSchema = schema.properties!.report as { anyOf: Array<{ properties: Record; required?: string[] }> }; const concluded = reportSchema.anyOf.find((variant) => variant.properties.outcome.const === "concluded")!; const insufficient = reportSchema.anyOf.find((variant) => variant.properties.outcome.const === "insufficient_data")!; expect(concluded.required).not.toContain("stop_reason"); expect(insufficient.required).toContain("stop_reason"); expect(insufficient.properties.stage.const).toBe("final"); }); it("supplies fixed protocol fields and accepts the known misplaced version tag without forwarding it", () => { const input = { ...report, ui_blocks: [{ version: "answer-ui@1", block, evidence_refs: ["query-1"] }] }; const args = native.parse({ request_id: requestId, report: input, evidence: [] }); const canonical = taskReportSchema.parse(args.report); expect(canonical.stop_reason).toBeNull(); expect(canonical.ui_blocks?.[0].block.version).toBe("answer-ui@1"); expect(canonical.ui_blocks?.[0]).not.toHaveProperty("version"); expect(taskReportSchema.parse(input)).toEqual(canonical); for (const ui_blocks of [ [{ block: { ...block, version: "unsupported" }, evidence_refs: ["query-1"] }], [{ version: "unsupported", block, evidence_refs: ["query-1"] }], ]) { expect(native.safeParse({ request_id: requestId, report: { ...report, ui_blocks }, evidence: [] }).success).toBe(false); expect(taskReportSchema.safeParse({ ...report, ui_blocks }).success).toBe(false); } }); it("exposes the same outcome conditions at native and host boundaries", () => { const insufficient = { ...report, outcome: "insufficient_data", recommendations: [], pending_items: [], stop_reason: "无法取得关键采样", data_gaps: [gap] }; for (const [input, accepted] of [ [report, true], [insufficient, true], [{ ...report, stop_reason: "本轮已结束" }, false], [{ ...report, data_gaps: [gap] }, false], [{ ...insufficient, stage: "interim" }, false], [{ ...insufficient, stop_reason: null }, false], [{ ...insufficient, recommendations: ["继续采样"] }, false], [{ ...insufficient, pending_items: ["等人"] }, false], [{ ...insufficient, data_gaps: [{ ...gap, blocking: false }] }, false], [{ ...insufficient, data_gaps: [{ ...gap, evidence_refs: [] }] }, false], ] as const) { expect(native.safeParse({ request_id: requestId, report: input, evidence: [] }).success).toBe(accepted); expect(taskReportSchema.safeParse(input).success).toBe(accepted); } }); it("rejects a contradictory report before issuing any HTTP request", async () => { const originalToken = process.env.AGENT_INTERNAL_TOKEN; process.env.AGENT_INTERNAL_TOKEN = "test-tool-token"; const fetch = spyOn(globalThis, "fetch").mockRejectedValue(new Error("Unexpected HTTP request")); try { await expect(publishReport.execute({ request_id: requestId, evidence: [], report: { ...report, stop_reason: "本轮已结束" }, } as never, { sessionID: "test" })).rejects.toThrow("已形成结论"); expect(fetch).not.toHaveBeenCalled(); } finally { fetch.mockRestore(); if (originalToken === undefined) delete process.env.AGENT_INTERNAL_TOKEN; else process.env.AGENT_INTERNAL_TOKEN = originalToken; } }); it("sends only canonical report fields to the bridge", async () => { const originalToken = process.env.AGENT_INTERNAL_TOKEN; process.env.AGENT_INTERNAL_TOKEN = "test-tool-token"; const fetch = spyOn(globalThis, "fetch").mockResolvedValue(new Response('{"report_revision":1}')); try { const input = { request_id: requestId, evidence: [], report: { ...report, ui_blocks: [{ version: "answer-ui@1", block, evidence_refs: ["query-1"] }], } }; await publishReport.execute(input as never, { sessionID: "test" }); const body = JSON.parse(String(fetch.mock.calls[0]?.[1]?.body)); expect(body.report.stop_reason).toBeNull(); expect(body.report.ui_blocks[0].block.version).toBe("answer-ui@1"); expect(body.report.ui_blocks[0]).not.toHaveProperty("version"); expect(body.report.current_conclusion).toBe(report.current_conclusion); } finally { fetch.mockRestore(); if (originalToken === undefined) delete process.env.AGENT_INTERNAL_TOKEN; else process.env.AGENT_INTERNAL_TOKEN = originalToken; } }); });