feat(investigation): publish incremental reference graphs
Publish graph deltas using the Server-owned cumulative graph contract. Validate historical comparison chart publications and consolidate Agent documentation.
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
export const investigationNodeAnnotations = ["reference", "recorded", "needs_input", "failed", "not_taken"] as const;
|
||||
export type InvestigationNodeAnnotation = (typeof investigationNodeAnnotations)[number];
|
||||
export const legacyInvestigationNodeAnnotations = ["pending", "running", "completed", "waiting", "waiting_human", "skipped", "cancelled"] as const;
|
||||
const legacyAnnotations = {
|
||||
pending: "reference", running: "reference", completed: "recorded",
|
||||
waiting: "needs_input", waiting_human: "needs_input", skipped: "not_taken", cancelled: "not_taken",
|
||||
} as const satisfies Record<string, InvestigationNodeAnnotation>;
|
||||
|
||||
export function readInvestigationNodeAnnotation(status: string | null | undefined): InvestigationNodeAnnotation | null | undefined {
|
||||
if (status == null) return status;
|
||||
if (status in legacyAnnotations) return legacyAnnotations[status as keyof typeof legacyAnnotations];
|
||||
return status as InvestigationNodeAnnotation;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { z } from "zod";
|
||||
import { supplyLayerSchema } from "./task-presentation.js";
|
||||
import { answerUiBlockSchema } from "../chat/answer-ui.js";
|
||||
import { investigationNodeAnnotations, legacyInvestigationNodeAnnotations, readInvestigationNodeAnnotation } from "./investigation-node-state.js";
|
||||
|
||||
const text = z.string().trim().min(1);
|
||||
export function mermaidDeclaresNodeId(source: string, nodeId: string): boolean {
|
||||
@@ -11,23 +12,41 @@ export function mermaidDeclaresNodeId(source: string, nodeId: string): boolean {
|
||||
).test(source);
|
||||
}
|
||||
|
||||
export const investigationGraphNodeSchema = z.object({
|
||||
node_id: z.string().regex(/^[A-Za-z_][A-Za-z0-9_-]{0,79}$/),
|
||||
label: text,
|
||||
detail: text.max(2000).nullish(),
|
||||
evidence_refs: z.array(text).default([]),
|
||||
action_refs: z.array(z.string().uuid()).default([]),
|
||||
status: z.enum(investigationNodeAnnotations).nullish(),
|
||||
map_targets: z.array(z.object({
|
||||
source_id: supplyLayerSchema,
|
||||
feature_id: text.max(128),
|
||||
}).strict()).max(100).default([]),
|
||||
}).strict();
|
||||
const graphEdgeSchema = z.object({
|
||||
source: z.string().regex(/^[A-Za-z_][A-Za-z0-9_-]{0,79}$/),
|
||||
target: z.string().regex(/^[A-Za-z_][A-Za-z0-9_-]{0,79}$/),
|
||||
label: text.max(200).nullish(),
|
||||
}).strict();
|
||||
export const investigationGraphUpdateSchema = z.object({
|
||||
title: text.max(200).nullish(),
|
||||
nodes: z.array(investigationGraphNodeSchema).max(100).default([]),
|
||||
edges: z.array(graphEdgeSchema).max(200).default([]),
|
||||
}).strict();
|
||||
|
||||
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([]),
|
||||
status: z.enum(["pending", "running", "completed", "waiting", "waiting_human", "failed", "skipped", "cancelled"]).nullish().describe("Evidence-confirmed node state; waiting means prerequisites/external/system result, waiting_human requires manual authorization/approval/feedback. Unknown stays omitted/null; host controls colors."),
|
||||
map_targets: z.array(z.object({
|
||||
source_id: supplyLayerSchema,
|
||||
feature_id: text.max(128),
|
||||
}).strict()).max(100).default([]),
|
||||
}).strict()).max(100).default([]),
|
||||
node_refs: z.array(investigationGraphNodeSchema.extend({
|
||||
status: z.enum([...investigationNodeAnnotations, ...legacyInvestigationNodeAnnotations]).nullish()
|
||||
.transform(readInvestigationNodeAnnotation),
|
||||
})).max(100).default([]),
|
||||
edges: z.array(graphEdgeSchema).max(200).nullish(),
|
||||
source_mermaid: text.max(20000).nullish(),
|
||||
}).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: "调查图节点引用重复" });
|
||||
@@ -45,7 +64,9 @@ export const investigationDiagramSchema = z.object({
|
||||
export const taskProgressSchema = z.object({
|
||||
summary: text.max(20000),
|
||||
diagram: investigationDiagramSchema.nullable().default(null),
|
||||
}).strict();
|
||||
graph_update: investigationGraphUpdateSchema.nullish(),
|
||||
}).strict().refine((progress) => !(progress.diagram && progress.graph_update),
|
||||
"Use graph_update without a generated Mermaid diagram");
|
||||
|
||||
export type TaskProgress = z.infer<typeof taskProgressSchema>;
|
||||
const dataGapSchema = z.object({
|
||||
|
||||
Reference in New Issue
Block a user