feat: upgrade investigation and presentation runtime
Generic Container CI/CD / test-build-publish (push) Successful in 1m6s
Agent CI/CD / build-test-publish-and-deploy (push) Successful in 1m7s

This commit is contained in:
2026-10-30 17:30:19 +08:00
parent 3b52a0ddab
commit 6336157ff4
104 changed files with 6541 additions and 961 deletions
+71
View File
@@ -17,6 +17,7 @@
"Stack",
"Section",
"Text",
"Result",
"Metric",
"Table",
"Chart",
@@ -66,6 +67,7 @@
"Stack",
"Section",
"Text",
"Result",
"Metric",
"Table",
"Chart",
@@ -90,6 +92,21 @@
"compact",
"normal"
]
},
"intent": {
"type": "string",
"enum": [
"response",
"comparison",
"report"
]
},
"density": {
"type": "string",
"enum": [
"compact",
"comfortable"
]
}
},
"required": [
@@ -116,6 +133,15 @@
"type": "null"
}
]
},
"role": {
"type": "string",
"enum": [
"result",
"analysis",
"evidence",
"follow_up"
]
}
},
"required": [
@@ -139,6 +165,10 @@
"info",
"warning"
]
},
"title": {
"type": "string",
"maxLength": 200
}
},
"required": [
@@ -148,6 +178,47 @@
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
},
"Result": {
"type": "object",
"properties": {
"title": {
"type": "string",
"maxLength": 200
},
"summary": {
"type": "string",
"maxLength": 4000
},
"status": {
"type": "string",
"enum": [
"success",
"partial",
"unconfirmed",
"error"
]
},
"source": {
"anyOf": [
{
"type": "string",
"maxLength": 200
},
{
"type": "null"
}
]
}
},
"required": [
"title",
"summary",
"status",
"source"
],
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
},
"Metric": {
"type": "object",
"properties": {
+47
View File
@@ -0,0 +1,47 @@
import type { z } from "zod";
import type { answerUiBlockSchema } from "./answer-ui.js";
type AnswerUiBlock = z.infer<typeof answerUiBlockSchema>;
/** Round-local publication state. Missing phase is reserved for legacy blocks. */
export class AnswerUiPublication {
private readonly blocks = new Map<string, AnswerUiBlock>();
private closed = false;
get size(): number { return this.blocks.size; }
publish(block: AnswerUiBlock): string | undefined {
if (this.closed) return "最终答复已提交,本轮不能继续更新界面";
const previous = this.blocks.get(block.block_id);
if (previous && previous.phase !== block.phase) return "相同 block_id 不能变更 phase;请使用独立 block_id 发布最终界面";
this.blocks.set(block.block_id, block);
return undefined;
}
finalize(refs: unknown): string[] {
this.closed = true;
return Array.isArray(refs) ? [...new Set(refs.filter((id): id is string => {
if (typeof id !== "string") return false;
const block = this.blocks.get(id);
return !!block && block.phase !== "process";
}))].slice(0, 48) : [];
}
}
/** Only standalone marker lines outside Markdown fenced code are references. */
export function answerUiAnchors(answer: string): string[] {
const ids: string[] = [];
let fence: { char: string; length: number } | undefined;
for (const line of answer.split(/\r?\n/)) {
const marker = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line);
if (marker) {
if (!fence) fence = { char: marker[1][0], length: marker[1].length };
else if (marker[1][0] === fence.char && marker[1].length >= fence.length && !marker[2].trim()) fence = undefined;
continue;
}
if (fence) continue;
const anchor = /^ {0,3}\[\[ui:([a-zA-Z0-9_-]{1,80})\]\][ \t]*$/.exec(line);
if (anchor) ids.push(anchor[1]);
}
return ids;
}
+2 -2
View File
@@ -36,10 +36,10 @@ export const answerUiSpecSchema = z.object({ root: key, elements: z.record(z.obj
if (!visit(spec.root, 0) || visited.size !== names.length) fail();
});
export const answerUiInputSchema = z.object({ block_id: key, fallback_text: z.string().min(1).max(4000), spec: answerUiSpecSchema }).strict();
export const answerUiInputSchema = z.object({ block_id: key, phase: z.enum(["process", "final"]).optional(), fallback_text: z.string().min(1).max(4000), spec: answerUiSpecSchema }).strict();
export const answerUiBlockSchema = answerUiInputSchema.extend({ version: z.literal("answer-ui@1") })
.refine((block) => JSON.stringify(block).length <= 100000, "界面块过大");
export const parseAnswerUiInput = (value: unknown) => {
const parsed = answerUiInputSchema.safeParse(value);
return parsed.success ? { version: "answer-ui@1" as const, ...parsed.data } : null;
return parsed.success ? { version: "answer-ui@1" as const, ...parsed.data, phase: parsed.data.phase ?? "process" } : null;
};