fix: harden report tools and guide investigation presentation
Generic Container CI/CD / test-build-publish (push) Successful in 56s
Agent CI/CD / build-test-publish-and-deploy (push) Successful in 56s

This commit is contained in:
2026-11-02 15:23:34 +08:00
parent 6336157ff4
commit 874bfba907
16 changed files with 264 additions and 49 deletions
+21 -7
View File
@@ -6,7 +6,7 @@ const text = z.string().trim().min(1);
export function mermaidDeclaresNodeId(source: string, nodeId: string): boolean {
const escaped = nodeId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
return new RegExp(
`(?:^|[^A-Za-z0-9_-])${escaped}(?=\\s*(?:[\\[({@;]|--|==|-\\.|$))`,
`(?:^|[^A-Za-z0-9_-])${escaped}(?=\\s*(?:[\\[({@;]|:::|--|==|-\\.|$))`,
"m",
).test(source);
}
@@ -59,6 +59,19 @@ const dataGapSchema = z.object({
blocking: z.boolean()
}).strict();
const reportUiBlockSchema = z.preprocess((value) => {
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
const item = value as Record<string, unknown>;
if (item.version !== undefined && item.version !== "answer-ui@1") return value;
if (!item.block || typeof item.block !== "object" || Array.isArray(item.block)) return value;
const { version: _legacyVersion, ...canonical } = item;
const block = item.block as Record<string, unknown>;
return { ...canonical, block: { ...block, version: block.version === undefined ? "answer-ui@1" : block.version } };
}, z.object({
block: answerUiBlockSchema,
evidence_refs: z.array(text.max(128)).min(1).max(100),
}).strict());
/** New report writes require explicit business outcomes; storage reads remain compatible. */
export const taskReportSchema = z.object({
title: text,
@@ -72,12 +85,10 @@ export const taskReportSchema = z.object({
stop_reason: text.nullable().default(null),
data_gaps: z.array(dataGapSchema),
diagram: investigationDiagramSchema.nullish(),
ui_blocks: z.array(z.object({
block: answerUiBlockSchema,
evidence_refs: z.array(text.max(128)).min(1).max(100),
}).strict()).max(8).optional(),
ui_blocks: z.array(reportUiBlockSchema).max(8).optional(),
}).strict().superRefine((report, ctx) => {
const invalid = (message: string) => ctx.addIssue({ code: z.ZodIssueCode.custom, message });
const invalid = (message: string, path: (string | number)[] = []) =>
ctx.addIssue({ code: z.ZodIssueCode.custom, message, path });
const blocks = report.ui_blocks ?? [];
if (Buffer.byteLength(JSON.stringify(blocks), "utf8") > 400000) invalid("报告交互内容超过 400 KB");
if (new Set(blocks.map((item) => item.block.block_id)).size !== blocks.length) invalid("报告界面块编号重复");
@@ -94,7 +105,10 @@ export const taskReportSchema = z.object({
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("已形成结论不能包含结束原因或阻塞缺口");
} else {
if (report.stop_reason != null) invalid("已形成结论不能包含结束原因;outcome=concluded 时 stop_reason 必须省略或为 null,后续事项写入 pending_items", ["stop_reason"]);
if (blocking) invalid("已形成结论不能包含阻塞缺口;outcome=concluded 时 data_gaps.blocking 必须为 false", ["data_gaps"]);
}
});
export type TaskReport = z.infer<typeof taskReportSchema>;