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
+21
View File
@@ -0,0 +1,21 @@
import { tool } from "../tool.js";
import askUser from "./ask_user.js";
import { interactionQuestionsSchema } from "../../src/chat/interaction-request-contract.js";
import { getInternalBridgeConfig } from "../internalBridge.js";
export default tool({
description: "向当前 event_investigation 任务的用户提出最多三个问题,支持单选、多选和自定义回答。先完成可独立推进的查询与计算,仅对需人工补充的关键事实、参数或选择提问;不代替审批,也不把接口故障当作缺数据。问题由 Server 持久化;任务通知打开右侧调查报告,用户在报告内选择或填写答案并提交。左侧 Agent 保留自由讨论,不承接此工具的固定问答。宿主直接展示问题,无需先发布报告或生成 ui_blocks 表单。返回 question_request_id 且 status=pending 后停止本轮工具调用,无需发布 final;宿主将任务转为等待人工,用户回答后接续新一轮调查。相同内容重试复用 request_id;若重试返回 answered 或 cancelled,按实际状态核对上下文,不重复等待旧问题。",
args: { request_id: tool.schema.string().uuid(), ...askUser.args },
async execute(args, context) {
const { baseUrl, internalToken } = getInternalBridgeConfig();
const response = await fetch(`${baseUrl}/internal/operational-task/questions`, {
method: "POST",
headers: { "content-type": "application/json", "x-agent-internal-token": internalToken },
body: JSON.stringify({ session_id: context.sessionID, request_id: args.request_id,
questions: interactionQuestionsSchema.parse(args.questions) }),
signal: context.signal,
});
if (!response.ok) throw new Error(`Task question was not saved (${response.status}): ${await response.text()}`);
return response.text();
},
});
-14
View File
@@ -1,14 +0,0 @@
import { optionalToolReason } from "../tool-reason.js";
import { tool } from "../tool.js";
import { executeFrontendAction } from "./frontend_action.js";
export default tool({
description: "清除当前浏览器地图上的 Agent SCADA 分析结果覆盖层。",
args: {
reason: optionalToolReason,
},
async execute(args, context) {
return executeFrontendAction("clear_scada_analysis", args, context);
},
});
+10 -2
View File
@@ -1,10 +1,18 @@
import { tool } from "../tool.js";
import { answerUiAnchors } from "../../src/chat/answer-ui-publication.js";
export default tool({
description: "提交本轮完整的最终答复。全部工具结果与需要的业务写回已核实后调用;需要浏览器动作回执时先核实回执,本地回答界面发布不等待浏览器回执;阶段说明留在过程记录。answer 是最后报告的完整文字;ui_block_ids 只列已经用 present_answer 发布且应保留在最终报告中的 block_id,按报告顺序排列。收到最终报告后,其他过程文字、界面与轨迹自动折叠。已发布本地交互界面的聊天会在此工具完成后结束本轮,之后不能继续更新界面;需要补充内容应先更新再提交最终答复。后台任务仍以 Server 的完成/失败接口为准,此工具不代表任务、工单或运行已完成。",
description: "提交本轮完整的最终答复。收尾前按阅读任务检查表达:多对象比较和数值趋势优先已用 present_answer 发布 Table/Chart,简单解释、单个事实或用户要求纯文字可直接回答;不要遗漏报告需要的交互内容。全部工具结果与需要的业务写回已核实后调用;需要浏览器动作回执时先核实回执,本地回答界面发布不等待浏览器回执;阶段说明留在过程记录。answer 保留必要正文、结论和关键边界,不重复整张表;在 answer 中用独立一行 [[ui:block_id]] 指定界面位置,代码块、缩进代码和引用中的标记不生效。ui_block_ids 是允许引用列表,必须与锚点去重后的顺序一致,只列本轮已经用 present_answer phase=final 发布的 block_id;process 界面不得引用。收到最终报告后,其他过程文字、界面与轨迹自动折叠。普通聊天与任务协作在此工具完成、当前工具和追加输入均已处理后结束本轮,之后不能继续更新界面;需要补充内容应先更新再提交最终答复。后台任务仍以 Server 的完成/失败接口为准,此工具不代表任务、工单或运行已完成。",
args: {
answer: tool.schema.string().min(1).max(30000),
ui_block_ids: tool.schema.array(tool.schema.string().regex(/^[a-zA-Z0-9_-]{1,80}$/)).max(48).optional(),
},
async execute({ answer, ui_block_ids }) { return JSON.stringify({ answer, ui_block_ids: ui_block_ids ?? [] }); },
async execute({ answer, ui_block_ids }) {
const refs = ui_block_ids ?? [];
const anchors = [...new Set(answerUiAnchors(answer))];
if (JSON.stringify(anchors) !== JSON.stringify(refs)) {
throw new Error("ui_block_ids 必须与 answer 中独立一行的 [[ui:block_id]] 锚点按去重后顺序一致");
}
return JSON.stringify({ answer, ui_block_ids: refs });
},
});
+4 -6
View File
@@ -6,14 +6,13 @@ type FrontendActionArgs = Record<string, unknown> & {
};
type FrontendActionBridgeResult = {
version: "frontend-action-result@1";
version: "frontend-action-result@2";
status: "succeeded" | "failed" | "rejected" | "cancelled" | "expired";
output?: unknown;
error?: { code?: string; message?: string };
};
export const executeFrontendAction = async (
name: "render_scada_analysis" | "clear_scada_analysis" | "locate_features" | "zoom_to_map" | "show_chart" | "view_history",
args: FrontendActionArgs,
context: { sessionID: string; callID?: string },
) => {
@@ -22,7 +21,7 @@ export const executeFrontendAction = async (
if (!callId) {
throw new Error("frontend action bridge did not inject the OpenCode call ID");
}
const { __frontend_action_call_id: _callId, reason, ...params } = args;
const { __frontend_action_call_id: _callId, reason, command } = args;
let response: Response;
try {
response = await fetch(`${internalBaseUrl}/internal/frontend-actions/request`, {
@@ -34,8 +33,7 @@ export const executeFrontendAction = async (
body: JSON.stringify({
session_id: context.sessionID,
call_id: callId,
name,
params,
command,
fallback_text: reason,
}),
});
@@ -60,7 +58,7 @@ export const unwrapFrontendActionResult = (text: string) => {
} catch {
throw new Error("frontend action bridge returned invalid JSON");
}
if (result.version !== "frontend-action-result@1" || typeof result.status !== "string") {
if (result.version !== "frontend-action-result@2" || typeof result.status !== "string") {
throw new Error("frontend action bridge returned an invalid result");
}
if (result.status !== "succeeded") {
-8
View File
@@ -1,8 +0,0 @@
import { optionalToolReason } from "../tool-reason.js";
import { tool } from "../tool.js";
import { executeFrontendAction } from "./frontend_action.js";
export default tool({
description: "在当前供水地图定位资产并等待浏览器执行回执。返回已定位和未找到的 ID,失败时不得宣称成功。",
args: { layer: tool.schema.enum(["junctions", "pipes", "valves", "pumps", "tanks", "reservoirs", "scada"]), feature_ids: tool.schema.array(tool.schema.string().min(1).max(128)).min(1).max(100), reason: optionalToolReason },
async execute(args, context) { return executeFrontendAction("locate_features", args, context); },
});
+28 -2
View File
@@ -2,9 +2,35 @@ import { tool } from "../tool.js";
import { answerUiInputSchema } from "../../src/chat/answer-ui.js";
import catalog from "../../src/chat/answer-ui-catalog.json" with { type: "json" };
// These complete call pairs also serve as executable contract examples in tests.
export const answerUiExamples: Array<{
present_answer: ReturnType<typeof answerUiInputSchema.parse>;
final_answer: { answer: string; ui_block_ids: string[] };
}> = [
{
present_answer: {
phase: "final" as const, block_id: "demo_comparison", fallback_text: "演练资料:甲管径 300 mm,乙管径 200 mm。",
spec: { root: "table", elements: { table: {
type: "Table", props: { title: "管径比较(用户提供的演练资料)", columns: ["对象", "管径(mm)"], rows: [["甲", "300"], ["乙", "200"]] }, children: [],
} } },
},
final_answer: { answer: "甲的管径较大,不能仅凭管径判断实际供水能力。\n\n[[ui:demo_comparison]]", ui_block_ids: ["demo_comparison"] },
},
{
present_answer: {
phase: "final" as const, block_id: "demo_trend", fallback_text: "演练资料:08:00、09:00、10:00 压力依次为 0.30、0.28、0.18 MPa。",
spec: { root: "chart", elements: { chart: {
type: "Chart", props: { title: "压力变化(MPa)", chart_type: "line", x_data: ["08:00", "09:00", "10:00"], series: [{ name: "压力(MPa)", data: [0.30, 0.28, 0.18] }], source: "用户提供的演练资料,非现场实测" }, children: [],
} } },
},
final_answer: { answer: "演练序列中后一个时段下降更明显,原因仍需其他证据判断。\n\n[[ui:demo_trend]]", ui_block_ids: ["demo_trend"] },
},
];
export default tool({
description: "把回答的一段内容发布为本地交互界面。spec 是 root + elements 的扁平树,每个元素包括 type、props、children。只组织真实查询资料或用户事实;无脚本、URL 或任意代码。不执行地图或业务动作,不等待浏览器,不代表业务完成。相同 block_id 更新本轮同一内容块。组件参数的 JSON Schema:" + JSON.stringify(catalog.props),
description: "把已有资料发布为保留在聊天中的交互内容。多对象同类字段比较优先用 Table,数值时间序列分析优先用 Chart,不等用户指定组件;简单解释、单个事实和简短确认直接用文字。用户明确要求纯文字时遵从。工作台窗口与地图动作另用 present_view。spec 是 root + elements 的扁平树,每个元素包括 type、props、children;单个组件可以直接作为 root。关键指标用 Metric,复杂结果用 Result(status 必须有真实依据),可复核依据用 Evidence,反复定位入口用 MapLocation,最终答复后的可选补充用 FollowUpForm。只组织查询结果或用户提供的资料,保留来源、单位、范围及演练标识;缺数不补造、不补零,不为布局凑卡片。无脚本、URL、CSS 或任意代码。Result 不执行动作,演练或记录成功不等于现场成功;发布不执行地图或业务动作,也不等待浏览器。Stack 的 columns/gap 仍必填,可选 intent=response|comparison|report、density=compact|comfortable;Section 可选 role=result|analysis|evidence|follow_up,Text 可选 title。需要保留的内容直接显式 phase=final,无须先发布 process;仅临时过程材料用 phase=process,省略仍默认 process。相同 block_id 只能同 phase 更新;过程材料转为最终内容须另用 block_id。final_answer 前完成所有更新,并通过独立正文锚点和 ui_block_ids 引用 final 块,正文不抄写整表或重复全文。下列是两组独立的最小调用示例,实际调用必须替换为本轮资料和 ID,不能复用演练数值:" + JSON.stringify(answerUiExamples) + "。组件参数的 JSON Schema:" + JSON.stringify(catalog.props),
args: {
phase: tool.schema.enum(["process", "final"]).optional().describe("保留在最终答复中的内容显式填 final;临时核查材料填 process。省略默认 process。"),
block_id: tool.schema.string().regex(/^[a-zA-Z0-9_-]{1,80}$/),
fallback_text: tool.schema.string().min(1).max(4000),
spec: tool.schema.object({ root: tool.schema.string(), elements: tool.schema.record(tool.schema.string(), tool.schema.object({
@@ -13,6 +39,6 @@ export default tool({
},
async execute(args) {
const value = answerUiInputSchema.parse(args);
return JSON.stringify({ version: "answer-ui@1", ...value, recorded: true });
return JSON.stringify({ version: "answer-ui@1", ...value, phase: value.phase ?? "process", recorded: true });
},
});
+15
View File
@@ -0,0 +1,15 @@
import { optionalToolReason } from "../tool-reason.js";
import { tool } from "../tool.js";
import { createViewSchemas } from "../../src/frontendAction/command.js";
import { executeFrontendAction } from "./frontend_action.js";
export default tool({
description: "操作当前工作台的地图、独立图表窗口、证据图层或已完成的 SCADA 分析展示,等待浏览器回执。分析趋势并保留在聊天中时优先用 present_answer 的 Chart;用户要求打开工作台图表或操作已有窗口时才用 open_chart/close_chart,先查询数据,不隐式查询,也不默认重复展示同一趋势。地图定位、高亮和图层操作使用本工具;聊天中的可重复定位入口使用 MapLocation。不得编造坐标或分析等级。",
args: {
command: createViewSchemas(tool.schema as unknown as Parameters<typeof createViewSchemas>[0]).command as unknown as ReturnType<typeof tool.schema.object>,
reason: optionalToolReason,
},
async execute(args, context) {
return executeFrontendAction(args, context);
},
});
+1 -1
View File
@@ -4,7 +4,7 @@ import { getInternalBridgeConfig } from "../internalBridge.js";
import { proposedTaskActionSchema } from "../../src/product/operational-task-contract.js";
export default tool({
description: "Propose a concrete work-order creation or command draft for human approval after saving an interim concluded report for the current run. This tool only creates a pending approval record and can never execute the action. No execution plan is required; node_id is an optional display reference. After success, publish a final report and optionally link the returned action_id in diagram node_refs. create_work_order requires title, description and work_order_type; its priority is normal or urgent. issue_command requires command_type, target, parameters, impact_summary, non-empty preconditions and expires_at.",
description: "Propose a concrete work-order creation or command draft for human approval after saving an interim concluded report for the current run. This tool only creates a pending approval record and can never execute the action. No execution plan is required; node_id is an optional display reference. After success, continue executable necessary analysis. Publish a final report only when the investigation stopping conditions are met, referencing the returned action_id; a field-verification proposal does not itself complete the investigation. create_work_order requires title, description and work_order_type; its priority is normal or urgent. issue_command requires command_type, target, parameters, impact_summary, non-empty preconditions and expires_at.",
args: {
request_id: tool.schema.string().describe("Stable UUID idempotency key for this proposal"),
kind: tool.schema.enum(["create_work_order", "issue_command"]),
+1 -1
View File
@@ -4,7 +4,7 @@ import { getInternalBridgeConfig } from "../internalBridge.js";
import { newExecutionPlanSchema } from "../../src/routes/executionPlan.js";
export default tool({
description: "Publish an optional legacy investigation description to TJWater Server. This display snapshot never schedules the Agent or controls event-investigation completion. Prefer the optional Mermaid diagram in publish_task_report for a human-facing investigation narrative. Existing plan validation still requires stable node IDs, factual evidence and explicit edges. Do not invent dependencies from list order or infer human completion. Reports and action proposals do not require this plan. Missing critical data ends the round with a final insufficient_data report. This tool only records explanatory information, it does not execute actions.",
description: "Publish an optional legacy investigation description to TJWater Server. This display snapshot never schedules the Agent or controls event-investigation completion. Prefer the optional Mermaid diagram in publish_task_report for a human-facing investigation narrative. Existing plan validation still requires stable node IDs, factual evidence and explicit edges. Do not invent dependencies from list order or infer human completion. Reports and action proposals do not require this plan. Follow the investigation instructions for report outcomes and stopping conditions; missing fields or planned field work do not themselves end the investigation. This tool only records explanatory information, it does not execute actions.",
args: {
request_id: tool.schema.string().describe("Stable UUID idempotency key for this plan revision"),
schema: tool.schema.enum(["execution-plan@2"]),
+1 -1
View File
@@ -4,7 +4,7 @@ import { taskPresentationCommandSchema } from "../../src/product/task-presentati
import { taskPublicationCommandSchema } from "./task-publication-schema.js";
export default tool({
description: "Record a bounded map, chart or evidence-backed overlay presentation command for the current task execution. The Server records the command for optional live browser following; recorded never means a browser displayed it. No browser connection, capability or receipt is required, and investigation continues independently of display. Chart data must reflect the referenced persisted evidence. Overlay coordinates and values are extracted by the Server from the evidence snapshot; never supply invented geometry or arbitrary code. Use a stable UUID request_id and reuse it when retrying the same command.",
description: "Record a bounded map, chart or evidence-backed overlay presentation command for the current task execution. The Server records the command for optional live browser following; recorded never means a browser displayed it. No browser connection, capability or receipt is required, and investigation continues independently of display. Chart data must reflect the referenced persisted evidence. Overlay coordinates and values are extracted by the Server from the evidence snapshot; data may be omitted; any supplied data is validated by the Server against the evidence snapshot. Never supply invented geometry or arbitrary code. Use a stable UUID request_id and reuse it when retrying the same command.",
args: {
request_id: tool.schema.string().uuid().describe("Stable UUID idempotency key; reuse for retries of this command"),
command: taskPublicationCommandSchema,
+1 -1
View File
@@ -4,7 +4,7 @@ import { taskProgressSchema } from "../../src/product/investigation-report.js";
import { taskPublicationProgressSchema } from "./task-publication-schema.js";
export default tool({
description: "Publish the complete current investigation progress from a task execution session. Publish before starting a real investigation step and when changing direction. The summary and optional Mermaid diagram describe actual activity for people; they never schedule the Agent or derive status from todo. The entire diagram may be redrawn, and only explicitly running nodes indicate current activity. Map targets reference known supply features. Use a stable UUID request_id and reuse it on retries. Publishing progress does not complete the run or confirm any human work.",
description: "Publish the complete current investigation progress from a task execution session. Publish before starting a real investigation step and when changing direction. The summary and optional Mermaid diagram describe actual activity for people; they never schedule the Agent or derive status from todo. Every node_refs node_id must exactly reuse an id declared in the Mermaid source; do not use A/B nodes in Mermaid and separate semantic ids in node_refs. The entire diagram may be redrawn, and only explicitly running nodes indicate current activity. Map targets reference known supply features. Use a stable UUID request_id and reuse it on retries. Publishing progress does not complete the run or confirm any human work.",
args: {
request_id: tool.schema.string().uuid().describe("Stable UUID idempotency key; reuse for retries of this progress publication"),
progress: taskPublicationProgressSchema,
+1 -1
View File
@@ -4,7 +4,7 @@ import { taskPublicationDiagramSchema, taskReportUiBlocksSchema } from "./task-p
import catalog from "../../src/chat/answer-ui-catalog.json" with { type: "json" };
export default tool({
description: "Publish an evidence-backed investigation report. No todo or execution plan is required. An optional Mermaid diagram explains the investigation to people; it never schedules or completes work. Before proposing actions publish an interim concluded report, then publish a final report referencing the actual actions. Missing critical data ends the round with final insufficient_data and blocking gaps, no recommendations or pending items. Query failures are execution errors, not proof that data is absent. The run lifecycle endpoint settles execution independently of the diagram. Optional ui_blocks express evidence as interactive metrics, charts, tables and forms. Each block references saved report evidence, has a unique block_id and a text fallback. Maximum 8 blocks / 400 KB total. Do not duplicate the conclusion or invent data to fill a layout. Component property schemas: " + JSON.stringify(catalog.props),
description: "Publish an evidence-backed investigation report. No todo or execution plan is required. An optional Mermaid diagram explains the investigation to people; it never schedules or completes work. Every node_refs node_id must exactly reuse an id declared in the Mermaid source; never create separate reference ids for the same nodes. Before proposing actions publish an interim concluded report; continue executable necessary analysis before finalizing with actual action references. Follow the investigation instructions for stopping: concluded classifies a supported finding, not completion of all analysis. Field-verification proposals and queued calculations do not justify early finalization. If a user can supply the remaining critical input, use ask_task_user before finalizing; the host renders saved questions in the right investigation report even before any report is published. A saved pending question needs no final report. Use final insufficient_data only when queries and applicable user clarification cannot fill critical gaps and there is no supported current judgment or concrete recommendation; include blocking gaps, no recommendations or pending items. Query failures are execution errors, not proof that data is absent. The run lifecycle endpoint settles execution independently of the diagram. Optional ui_blocks express existing results with Result, object comparisons with Table, real series with Chart, location links with MapLocation. Use ask_task_user for fixed task questions; do not generate FollowUpForm to collect investigation answers, because it routes input to left-side discussion rather than the task answer endpoint. Result status must reflect actual evidence; saving a synthetic record does not prove field action completion. Result never executes actions. Stack intent/density and Section role express presentation semantics; original required props remain required. Do not supply CSS or executable props. Report blocks are final results: set phase to final or omit it for legacy compatibility; process blocks cannot be published in reports. Each block references saved report evidence, has a unique block_id and a text fallback. Maximum 8 blocks / 400 KB total. Do not duplicate the conclusion or invent data to fill a layout. Component property schemas: " + JSON.stringify(catalog.props),
args: {
request_id: tool.schema.string().describe("Stable UUID idempotency key for this report revision"),
report: tool.schema.object({
-33
View File
@@ -1,33 +0,0 @@
import { optionalToolReason } from "../tool-reason.js";
import { tool } from "../tool.js";
import { executeFrontendAction } from "./frontend_action.js";
const deviceId = tool.schema.string().trim().min(1).max(128);
export default tool({
description:
"将已经完成分析并确定等级的 SCADA 点位结果高亮、编号并定位到前端地图。等级只能来自已完成的分析结论。",
args: {
reason: optionalToolReason,
items: tool.schema
.array(
tool.schema.object({
device_id: deviceId.describe("Trusted SCADA device identifier from the backend API."),
level: tool.schema
.enum(["high", "medium", "low", "unrated"])
.describe("Completed analysis level; use unrated only when no level was concluded."),
}),
)
.min(1)
.max(100)
.refine(
(items) => new Set(items.map((item) => item.device_id)).size === items.length,
"device_id values must be unique",
)
.describe("One to one hundred unique SCADA analysis results."),
},
async execute(args, context) {
return executeFrontendAction("render_scada_analysis", args, context);
},
});
-8
View File
@@ -1,8 +0,0 @@
import { optionalToolReason } from "../tool-reason.js";
import { tool } from "../tool.js";
import { executeFrontendAction } from "./frontend_action.js";
export default tool({
description: "显示受控折线或柱状图并等待浏览器回执。不接收 ECharts 脚本或任意 option;数据须来自可信查询或用户输入。",
args: { title: tool.schema.string().max(200), chart_type: tool.schema.enum(["line", "bar"]), x_data: tool.schema.array(tool.schema.string().max(128)).min(1).max(100), series: tool.schema.array(tool.schema.object({ name: tool.schema.string().max(128), data: tool.schema.array(tool.schema.number().finite()).min(1).max(100) })).min(1).max(8), reason: optionalToolReason },
async execute(args, context) { return executeFrontendAction("show_chart", args, context); },
});
+11 -27
View File
@@ -1,4 +1,5 @@
import { tool } from "../tool.js";
import { mermaidDeclaresNodeId } from "../../src/product/investigation-report.js";
// OpenCode owns a separate Zod version. Native parameters are described here;
// product schemas revalidate progress and presentation at execution boundaries.
@@ -10,6 +11,7 @@ export const taskReportUiBlocksSchema = tool.schema.array(tool.schema.object({
block: tool.schema.object({
version: tool.schema.literal("answer-ui@1"),
block_id: tool.schema.string().regex(/^[a-zA-Z0-9_-]{1,80}$/),
phase: tool.schema.literal("final").optional(),
fallback_text: text.max(4000),
spec: tool.schema.object({
root: tool.schema.string(),
@@ -25,42 +27,24 @@ export const taskReportUiBlocksSchema = tool.schema.array(tool.schema.object({
export const taskPublicationDiagramSchema = tool.schema.object({
title: text,
mermaid: text.max(20000).describe("Mermaid flowchart source; no Markdown fences, initialization directives or click callbacks"),
mermaid: text.max(20000).describe("Mermaid flowchart source; no Markdown fences, initialization directives or click callbacks. Reuse the exact Mermaid node ids in node_refs"),
node_refs: tool.schema.array(tool.schema.object({
node_id: tool.schema.string().regex(/^[A-Za-z_][A-Za-z0-9_-]{0,79}$/),
node_id: tool.schema.string().regex(/^[A-Za-z_][A-Za-z0-9_-]{0,79}$/).describe("Exact id of a node declared in the Mermaid source; do not create a separate reference id"),
label: text,
evidence_refs: tool.schema.array(text).default([]),
action_refs: tool.schema.array(tool.schema.string().uuid()).default([]),
status: tool.schema.enum(["pending", "running", "completed", "waiting_human", "failed", "skipped", "cancelled"]).nullish(),
status: tool.schema.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: tool.schema.array(tool.schema.object({ source_id: layer, feature_id: identifier }).strict()).max(100).default([]),
}).strict()).max(100).default([]),
}).strict();
}).strict().refine(
(diagram) => diagram.node_refs.every((node) => mermaidDeclaresNodeId(diagram.mermaid, node.node_id)),
"Every node_refs node_id must be declared with the same id in the Mermaid source",
);
export const taskPublicationProgressSchema = tool.schema.object({
summary: text.max(20000),
diagram: taskPublicationDiagramSchema.nullable().default(null),
}).strict();
const chart = tool.schema.object({
title: tool.schema.string().max(200),
chart_type: tool.schema.enum(["line", "bar"]),
x_data: tool.schema.array(tool.schema.string().max(128)).min(1).max(100),
series: tool.schema.array(tool.schema.object({
name: tool.schema.string().max(128), data: tool.schema.array(tool.schema.number().finite()).min(1).max(100),
}).strict()).min(1).max(8),
}).strict();
export const taskPublicationCommandSchema = tool.schema.discriminatedUnion("action", [
tool.schema.object({
action: tool.schema.literal("highlight_features"), layer,
feature_ids: tool.schema.array(identifier).min(1).max(100),
effect: tool.schema.enum(["steady", "pulse"]).default("steady"),
}).strict(),
tool.schema.object({ action: tool.schema.literal("clear_highlights") }).strict(),
tool.schema.object({ action: tool.schema.literal("open_chart"), chart_id: identifier, chart,
evidence_refs: tool.schema.array(text).min(1).max(100) }).strict(),
tool.schema.object({ action: tool.schema.literal("close_chart"), chart_id: identifier }).strict(),
tool.schema.object({ action: tool.schema.literal("set_overlay"), overlay_id: identifier,
title: text.max(200), mode: tool.schema.enum(["geometry", "heatmap"]), evidence_id: text }).strict(),
tool.schema.object({ action: tool.schema.literal("clear_overlay"), overlay_id: identifier }).strict(),
]);
import { createViewSchemas } from "../../src/frontendAction/command.js";
export const taskPublicationCommandSchema = createViewSchemas(tool.schema as unknown as Parameters<typeof createViewSchemas>[0], true).command as unknown as ReturnType<typeof tool.schema.object>;
-8
View File
@@ -1,8 +0,0 @@
import { optionalToolReason } from "../tool-reason.js";
import { tool } from "../tool.js";
import { executeFrontendAction } from "./frontend_action.js";
export default tool({
description: "查询当前项目资产最多24小时的监测历史并显示曲线面板,等待浏览器回执。使用服务返回的单位和数据,缺数时明确说明。仅普通会话可用。",
args: { element_type: tool.schema.enum(["junction", "pipe"]), element_id: tool.schema.string().min(1).max(128), start: tool.schema.string(), end: tool.schema.string(), reason: optionalToolReason },
async execute(args, context) { return executeFrontendAction("view_history", args, context); },
});
-8
View File
@@ -1,8 +0,0 @@
import { optionalToolReason } from "../tool-reason.js";
import { tool } from "../tool.js";
import { executeFrontendAction } from "./frontend_action.js";
export default tool({
description: "将当前地图缩放到 WGS84 经纬度并等待浏览器回执。",
args: { center: tool.schema.tuple([tool.schema.number().min(-180).max(180), tool.schema.number().min(-90).max(90)]), zoom: tool.schema.number().min(0).max(24), reason: optionalToolReason },
async execute(args, context) { return executeFrontendAction("zoom_to_map", args, context); },
});