feat(agent): align investigation reports and selected context
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { taskReportSchema } from "../../src/product/investigation-report.js";
|
||||
|
||||
const report = { title: "调查报告", current_conclusion: "无法确定原因", conclusion_boundary: "仅有上游数据",
|
||||
evidence_refs: ["query-1"], recommendations: [], pending_items: [], stage: "final", outcome: "insufficient_data",
|
||||
stop_reason: "缺少关键采样", data_gaps: [{ gap_id: "g1", node_id: "read", missing_data: "末梢采样",
|
||||
scope: "投诉时段", impact: "无法判断异常来源", required_data: "该时段采样结果", evidence_refs: ["query-1"], blocking: true }] };
|
||||
|
||||
describe("new investigation report writes", () => {
|
||||
it("requires explicit outcome and gap assessment", () => {
|
||||
expect(taskReportSchema.parse(report).outcome).toBe("insufficient_data");
|
||||
for (const field of ["stage", "outcome", "data_gaps"]) {
|
||||
const incomplete = { ...report };
|
||||
Reflect.deleteProperty(incomplete, field);
|
||||
expect(() => taskReportSchema.parse(incomplete)).toThrow();
|
||||
}
|
||||
});
|
||||
it("accepts an evidence-backed gap without a plan or diagram node", () => {
|
||||
const { node_id: _nodeId, ...gap } = report.data_gaps[0];
|
||||
expect(taskReportSchema.parse({ ...report, data_gaps: [gap] }).data_gaps[0]?.node_id).toBeUndefined();
|
||||
});
|
||||
it("accepts an optional narrative diagram and rejects interactive directives", () => {
|
||||
const diagram = { title: "核查关系", mermaid: 'flowchart TD\nA["查询"] --> B["判断"]',
|
||||
node_refs: [{ node_id: "A", label: "查询", evidence_refs: ["query-1"] }] };
|
||||
expect(taskReportSchema.parse({ ...report, diagram }).diagram?.node_refs[0]?.action_refs).toEqual([]);
|
||||
for (const mermaid of ['%%{init: {}}%%\nflowchart TD\nA-->B', 'flowchart TD\nclick A callback', 'flowchart TD; A-->B; click A callback', 'sequenceDiagram\nA->>B: 查询']) {
|
||||
expect(() => taskReportSchema.parse({ ...report, diagram: { ...diagram, mermaid } })).toThrow();
|
||||
}
|
||||
});
|
||||
it("rejects downstream work or unsupported claims on insufficient-data exit", () => {
|
||||
for (const changes of [{ recommendations: ["派采样工单"] }, { pending_items: ["等待人工"] },
|
||||
{ stop_reason: null }, { data_gaps: [] }, { stage: "interim" }, { outcome: "concluded" },
|
||||
{ data_gaps: [{ ...report.data_gaps[0], evidence_refs: [] }] }, { schema: "unexpected" }]) {
|
||||
expect(() => taskReportSchema.parse({ ...report, ...changes })).toThrow();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -51,6 +51,21 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe("OperationalTaskClient", () => {
|
||||
it("requests a selected report while retaining the current task snapshot", async () => {
|
||||
let requestedUrl = "";
|
||||
globalThis.fetch = (async (input: Parameters<typeof fetch>[0]) => {
|
||||
requestedUrl = String(input);
|
||||
return Response.json({ task: { ...task, execution_generation: 2, latest_plan: null,
|
||||
latest_report: { revision: 8, execution_generation: 2 }, evidence: [], actions: [], permissions: {} },
|
||||
approved_data_scopes: [], selected_report: { revision: 3, execution_generation: 1, report: { title: "上一轮报告" } } });
|
||||
}) as unknown as typeof fetch;
|
||||
bindOperationalTaskCredential("runtime-1", { authorization: "Bearer transient-secret", projectId: "project-1" });
|
||||
const client = new OperationalTaskClient("http://server.test");
|
||||
const context = await client.getAgentContext("runtime-1", taskId, 3);
|
||||
expect(new URL(requestedUrl).searchParams.get("report_revision")).toBe("3");
|
||||
expect(context.selected_report?.execution_generation).toBe(1);
|
||||
expect(context.task.execution_generation).toBe(2);
|
||||
});
|
||||
it("matches proposed action payloads to the generated Server contract", () => {
|
||||
const common = {
|
||||
request_id: "60000000-0000-4000-8000-000000000001",
|
||||
@@ -187,6 +202,7 @@ describe("OperationalTaskClient", () => {
|
||||
sourceSessionId: "session-1",
|
||||
report: {
|
||||
title: "失败",
|
||||
stage: "final", outcome: "concluded", stop_reason: null, data_gaps: [],
|
||||
current_conclusion: "租约已失效",
|
||||
conclusion_boundary: "未继续写回",
|
||||
evidence_refs: [],
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import proposeTaskAction from "../../.opencode/tools/propose_task_action.js";
|
||||
|
||||
import {
|
||||
agentTriggerContextSchema,
|
||||
@@ -37,6 +38,22 @@ describe("agent event trigger", () => {
|
||||
expect(prompt).toContain(trigger.trigger_id);
|
||||
});
|
||||
|
||||
it("keeps event entry and action-tool instructions consistent with report-driven completion", () => {
|
||||
const prompt = buildAgentTriggerPrompt(agentTriggerContextSchema.parse(trigger));
|
||||
expect(prompt).toContain("get_operational_task_context");
|
||||
expect(prompt).toContain("final/insufficient_data");
|
||||
expect(prompt).toContain("正常结束本轮");
|
||||
expect(prompt).toContain("权限拒绝和接口错误不能证明数据缺失");
|
||||
expect(prompt).toContain("interim/concluded");
|
||||
expect(prompt).not.toContain("开始调查前调用 publish_execution_plan");
|
||||
expect(prompt).not.toContain("人工工作保持 waiting");
|
||||
expect(proposeTaskAction.description).toContain("interim concluded report");
|
||||
expect(proposeTaskAction.description).toContain("No execution plan is required");
|
||||
expect(proposeTaskAction.description).toContain("node_id is an optional display reference");
|
||||
expect(proposeTaskAction.description).not.toContain("published current plan");
|
||||
expect(proposeTaskAction.description).not.toContain("republish the plan");
|
||||
});
|
||||
|
||||
it("rejects a different scenario instead of routing arbitrary work orders", () => {
|
||||
expect(agentTriggerContextSchema.safeParse({
|
||||
...trigger,
|
||||
|
||||
Reference in New Issue
Block a user