feat(agent): align investigation reports and selected context
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const text = z.string().trim().min(1);
|
||||
export const investigationDiagramSchema = z.object({
|
||||
title: text,
|
||||
mermaid: text.max(20000).refine((source) =>
|
||||
/^\s*(?:flowchart|graph)\s+(?:TD|TB|BT|LR|RL)\b/.test(source)
|
||||
&& !source.includes("```") && !source.includes("%%{") && !/(?:^|[;\n])\s*click\b/.test(source),
|
||||
"调查图只接受不含指令或回调的 Mermaid 流程描述"),
|
||||
node_refs: z.array(z.object({
|
||||
node_id: z.string().regex(/^[A-Za-z_][A-Za-z0-9_-]{0,79}$/),
|
||||
label: text,
|
||||
evidence_refs: z.array(text).default([]),
|
||||
action_refs: z.array(z.string().uuid()).default([]),
|
||||
}).strict()).max(100).default([]),
|
||||
}).strict().superRefine((diagram, ctx) => {
|
||||
if (new Set(diagram.node_refs.map((node) => node.node_id)).size !== diagram.node_refs.length) {
|
||||
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "调查图节点引用重复" });
|
||||
}
|
||||
});
|
||||
const dataGapSchema = z.object({
|
||||
gap_id: text,
|
||||
node_id: text.nullish(),
|
||||
missing_data: text,
|
||||
scope: text,
|
||||
impact: text,
|
||||
required_data: text,
|
||||
evidence_refs: z.array(text).min(1),
|
||||
blocking: z.boolean()
|
||||
}).strict();
|
||||
|
||||
/** New report writes require explicit business outcomes; storage reads remain compatible. */
|
||||
export const taskReportSchema = z.object({
|
||||
title: text,
|
||||
current_conclusion: text,
|
||||
conclusion_boundary: text,
|
||||
evidence_refs: z.array(text).default([]),
|
||||
recommendations: z.array(text).default([]),
|
||||
pending_items: z.array(text).default([]),
|
||||
stage: z.enum(["interim", "final"]),
|
||||
outcome: z.enum(["concluded", "insufficient_data"]),
|
||||
stop_reason: text.nullable().default(null),
|
||||
data_gaps: z.array(dataGapSchema),
|
||||
diagram: investigationDiagramSchema.nullish(),
|
||||
}).strict().superRefine((report, ctx) => {
|
||||
const invalid = (message: string) => ctx.addIssue({ code: z.ZodIssueCode.custom, message });
|
||||
if (new Set(report.data_gaps.map((gap) => gap.gap_id)).size !== report.data_gaps.length) invalid("数据缺口编号重复");
|
||||
const blocking = report.data_gaps.some((gap) => gap.blocking);
|
||||
if (report.outcome === "insufficient_data") {
|
||||
if (report.stage !== "final" || !report.stop_reason || !blocking) invalid("证据不足必须提供最终报告、结束原因和关键缺口");
|
||||
if (report.recommendations.length || report.pending_items.length) invalid("证据不足退出后不再生成后续处置建议或待办");
|
||||
} else if (report.stop_reason != null || blocking) invalid("已形成结论不能包含结束原因或阻塞缺口");
|
||||
});
|
||||
|
||||
export type TaskReport = z.infer<typeof taskReportSchema>;
|
||||
@@ -1,4 +1,6 @@
|
||||
import { bindSessionCredential, releaseSessionCredential, requestWithSessionCredential } from "./credential-refresh.js";
|
||||
import { taskReportSchema } from "./investigation-report.js";
|
||||
export { taskReportSchema } from "./investigation-report.js";
|
||||
import { z } from "zod";
|
||||
|
||||
import { config } from "../config.js";
|
||||
@@ -63,6 +65,10 @@ const agentTaskContextSchema = z.object({
|
||||
permissions: z.record(z.boolean()),
|
||||
}).passthrough(),
|
||||
approved_data_scopes: z.array(taskDataSourceSchema),
|
||||
selected_report: z.object({
|
||||
revision: z.number().int().positive(), execution_generation: z.number().int().positive().nullish(),
|
||||
report: z.unknown(),
|
||||
}).passthrough().nullish(),
|
||||
});
|
||||
|
||||
const receivedEvidenceSchema = z.object({
|
||||
@@ -103,15 +109,6 @@ export const taskEvidenceSchema = z.object({
|
||||
observed_at: z.string().datetime({ offset: true }),
|
||||
});
|
||||
|
||||
export const taskReportSchema = z.object({
|
||||
title: z.string().trim().min(1).max(160),
|
||||
current_conclusion: z.string().trim().min(1).max(4000),
|
||||
conclusion_boundary: z.string().trim().min(1).max(2000),
|
||||
evidence_refs: z.array(z.string().trim().min(1).max(128)).max(100).default([]),
|
||||
recommendations: z.array(z.string().trim().min(1).max(1000)).max(30).default([]),
|
||||
pending_items: z.array(z.string().trim().min(1).max(500)).max(20).default([]),
|
||||
});
|
||||
|
||||
export const operationalTaskDataQuerySchema = z.object({
|
||||
source: taskDataSourceSchema,
|
||||
device_ids: z.array(z.string().trim().min(1).max(160)).max(20).optional(),
|
||||
@@ -203,8 +200,9 @@ export const clearOperationalTaskLease = (runtimeSessionId: string) => {
|
||||
export class OperationalTaskClient {
|
||||
constructor(private readonly baseUrl = config.TJWATER_SERVER_BASE_URL) {}
|
||||
|
||||
async getAgentContext(runtimeSessionId: string, taskId: string) {
|
||||
const raw = await this.request(runtimeSessionId, `/api/v1/operational-tasks/${encodeURIComponent(taskId)}/agent-context`, {
|
||||
async getAgentContext(runtimeSessionId: string, taskId: string, reportRevision?: number) {
|
||||
const query = reportRevision == null ? "" : `?report_revision=${reportRevision}`;
|
||||
const raw = await this.request(runtimeSessionId, `/api/v1/operational-tasks/${encodeURIComponent(taskId)}/agent-context${query}`, {
|
||||
method: "GET",
|
||||
});
|
||||
const result = agentTaskContextSchema.parse(raw);
|
||||
|
||||
Reference in New Issue
Block a user