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:
2026-11-11 19:10:41 +08:00
parent 5ee93329a4
commit db87323b69
28 changed files with 2329 additions and 90 deletions
+2 -2
View File
@@ -4,9 +4,9 @@ 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。不得编造坐标或分析等级。",
description: "操作当前工作台的地图、独立图表窗口、证据图层或已完成的 SCADA 分析展示,等待浏览器回执。分析趋势并保留在聊天中时优先用 present_answer 的 Chart;用户要求打开工作台图表或操作已有窗口时才用 open_chart/close_chart,先查询数据,不隐式查询,也不默认重复展示同一趋势。地图定位、高亮和图层操作使用本工具;聊天中的可重复定位入口使用 MapLocation。同指标同期复合图按对象分别提供稳定 chart_id,标题为“对象ID 共同标题”,每条序列名以“对象ID ”开头,同组轴名、槽位与当前窗口一致,前端在右侧提供对象切换、下方显示图例。time_comparison 必须保留真实 sample_times 并按共享时刻槽对齐;普通折线和柱状保持独立。不得编造坐标或分析等级。",
args: {
command: createViewSchemas(tool.schema as unknown as Parameters<typeof createViewSchemas>[0]).command as unknown as ReturnType<typeof tool.schema.object>,
command: createViewSchemas(tool.schema as unknown as Parameters<typeof createViewSchemas>[0], false, true).command as unknown as ReturnType<typeof tool.schema.object>,
reason: optionalToolReason,
},
async execute(args, context) {
+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. 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.",
description: "Publish an optional legacy investigation description to TJWater Server. This display snapshot never schedules the Agent or controls event-investigation completion. Prefer incremental graph_update in publish_task_progress for the human-facing investigation narrative; the Server generates Mermaid and archives it in reports. 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: "Publish the current investigation step as a bounded map, chart or evidence-backed overlay command. Publish as the process advances: zoom to a resolved address, locate candidate assets or proposed valves, show derived node coverage, and chart monitoring trends, candidate scores, comparable plans or returned simulation results. Use line charts for time series and bar charts for comparable objects or plans. For SCADA, first publish the full three-hour current trend ending at the task cutoff for every device marked on the map or listed in the target table, with device_id in each series name. Publish pressure and flow separately. Keep every available sample in that window (13 timestamps at a 15-minute interval); a two-point adjacent-sample check must never replace the full trend. Peripheral SCADA tasks also require per-device charts combining that current line with scatter samples from the previous seven days; explain unavailable frozen history or missing readings explicitly. Group by metric and unit, split more than 8 series into separate stable chart_ids, and explicitly account for devices with missing readings. Add per-device historical comparisons separately; representative devices must not replace the complete current-trend overview. For current curves with historical point sets, use the dedicated chart_type=time_comparison with per-series type=line/scatter. Ordinary charts use line or bar. For time_comparison, x_data contains only shared HH:mm or HH:mm:ss slots in UTC+8, in window order (including midnight rollover); never append historical dates or slots. Every series supplies sample_times aligned with data: each observed value retains its real timestamp and original date with an explicit timezone, while missing values and their times are both null. Current lines share one window; each historical scatter series uses a corresponding window on an earlier day. Place each historical value at the matching time-of-day slot. Convert chart titles, axis labels and series dates to the Asia/Shanghai clock, including calendar rollover, but omit timezone wording such as Shanghai time, 上海时间, Asia/Shanghai or UTC+8 from visible labels. Name the time axis 时间. Keep explicit offsets in sample_times. Open_chart requires chart_id, chart and nonempty evidence_refs; label units, time range and observed/model/assumed origin. Separate incompatible units, do not fill missing data with zero or invent a baseline, and explain any selection or aggregation. Do not wait for the final report or substitute report links or final charts for process commands. 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; omit data when referencing saved geometry, and do not publish an empty coverage. Any supplied data is validated 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; do not republish an unchanged step merely to create activity.",
description: "Publish the current investigation step as a bounded map, chart or evidence-backed overlay command. Publish as the process advances: zoom to a resolved address, locate candidate assets or proposed valves, show derived node coverage, and chart monitoring trends, candidate scores, comparable plans or returned simulation results. Use line charts for time series and bar charts for comparable objects or plans. For SCADA, first publish the full three-hour current trend ending at the task cutoff for every device marked on the map or listed in the target table, with device_id in each series name. Publish pressure and flow separately. Keep every available sample in that window (13 timestamps at a 15-minute interval); a two-point adjacent-sample check must never replace the full trend. Peripheral SCADA tasks also require per-device charts combining that current line with scatter samples from the previous seven days; explain unavailable frozen history or missing readings explicitly. Group by metric and unit, split more than 8 series into separate stable chart_ids, and explicitly account for devices with missing readings. Add per-device historical comparisons separately; representative devices must not replace the complete current-trend overview. For object switching within one metric/unit/window, keep one stable chart_id per device, title each chart as <device_id> <shared comparison title>, and start every series name with <device_id> followed by a space. Use identical x/y axis names, x_data and current sample_times across the group, with matching current-series names after removing the device prefix. The browser groups these object datasets into one child chart, with object buttons on the right and series legends below the plot; ordinary overview and bar charts stay separate. The browser manages all charts in one parent window and preserves manual child enlargement. Only new live commands may open the parent; task loading, switching, refresh, completed results and history restoration never replay popup intent. Do not republish charts to force windows open. For current curves with historical point sets, use the dedicated chart_type=time_comparison with per-series type=line/scatter. Ordinary charts use line or bar. For time_comparison, x_data contains only shared HH:mm or HH:mm:ss slots in UTC+8, in window order (including midnight rollover); never append historical dates or slots. Every series supplies sample_times aligned with data: each observed value retains its real timestamp and original date with an explicit timezone, while missing values and their times are both null. Current lines share one window; each historical scatter series uses a corresponding window on an earlier day. Place each historical value at the matching time-of-day slot. Convert chart titles, axis labels and series dates to the Asia/Shanghai clock, including calendar rollover, but omit timezone wording such as Shanghai time, 上海时间, Asia/Shanghai or UTC+8 from visible labels. Name the time axis 时间. Keep explicit offsets in sample_times. Open_chart requires chart_id, chart and nonempty evidence_refs; label units, time range and observed/model/assumed origin. Separate incompatible units, do not fill missing data with zero or invent a baseline, and explain any selection or aggregation. Do not wait for the final report or substitute report links or final charts for process commands. 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; omit data when referencing saved geometry, and do not publish an empty coverage. Any supplied data is validated 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; do not republish an unchanged step merely to create activity.",
args: {
request_id: tool.schema.string().uuid().describe("Stable UUID idempotency key; reuse for retries of this command"),
command: taskPublicationCommandSchema,
+4 -4
View File
@@ -1,16 +1,16 @@
import { tool } from "../tool.js";
import { getInternalBridgeConfig } from "../internalBridge.js";
import { taskProgressSchema } from "../../src/product/investigation-report.js";
import { taskPublicationProgressSchema } from "./task-publication-schema.js";
import { taskPublicationGraphProgressSchema } 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. 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.",
description: "Save an optional investigation process reference with summary and optional graph_update. Update when the route materially changes, an important finding or failed attempt should be recorded, or a report is being organized. No per-step publication, frequent refresh, or live synchronization is required. The Server retains prior nodes and paths and generates Mermaid. Use stable node_id values; only submit changes. Before extending a saved graph, read get_task_context section=progress when needed. Optional status annotations are reference, recorded, needs_input, failed, and not_taken. They describe the saved explanation, not execution lifecycle: recorded means a finding exists, not that a task, calculation, work order, or field action completed. Retain failed attempts with the actual reason in detail; append a new node and edge for a retry or alternative. Text-only updates preserve the graph. Graphs never schedule execution, settle runs, or infer human completion. Use known supply feature ids and saved evidence/action references. Reuse request_id on retries.",
args: {
request_id: tool.schema.string().uuid().describe("Stable UUID idempotency key; reuse for retries of this progress publication"),
progress: taskPublicationProgressSchema,
progress: taskPublicationGraphProgressSchema,
},
async execute(args, context) {
const progress = taskProgressSchema.parse(args.progress);
const progress = taskProgressSchema.parse(taskPublicationGraphProgressSchema.parse(args.progress));
const { baseUrl, internalToken } = getInternalBridgeConfig();
const response = await fetch(`${baseUrl}/internal/operational-task/artifacts`, {
method: "POST",
+1 -1
View File
@@ -5,7 +5,7 @@ import { taskReportSchema } from "../../src/product/investigation-report.js";
import catalog from "../../src/chat/answer-ui-catalog.json" with { type: "json" };
export default tool({
description: "Publish an evidence-backed investigation report. Write all reader-facing times in Asia/Shanghai (UTC+8), including prose, tables, chart labels and historical series dates. Tool display timestamps with +08:00 already use this zone; keep the same instant and offset. Format UTC source timestamps into UTC+8 once, including date rollover. The host supplies fixed UI protocol versions and the default null stop_reason. For outcome=concluded omit stop_reason or use null, and keep every data_gaps.blocking false; use pending_items for external follow-up. For outcome=insufficient_data use stage=final, a nonempty stop_reason and an evidence-backed blocking gap, with no recommendations or pending_items. 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 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),
description: "Publish an evidence-backed investigation report. Write all reader-facing times in Asia/Shanghai (UTC+8), including prose, tables, chart labels and historical series dates. Tool display timestamps with +08:00 already use this zone; keep the same instant and offset. Format UTC source timestamps into UTC+8 once, including date rollover. The host supplies fixed UI protocol versions and the default null stop_reason. For outcome=concluded omit stop_reason or use null, and keep every data_gaps.blocking false; use pending_items for external follow-up. For outcome=insufficient_data use stage=final, a nonempty stop_reason and an evidence-backed blocking gap, with no recommendations or pending_items. No todo or execution plan is required. The Server archives the accumulated investigation graph from publish_task_progress; do not regenerate or supply Mermaid in a report. Update graph nodes and failure states through publish_task_progress before publishing. Graphs explain activity and never schedule or complete work. 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 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().uuid().describe("Stable UUID idempotency key for this report revision"),
report: taskPublicationReportSchema,
+19 -20
View File
@@ -1,5 +1,5 @@
import { tool } from "../tool.js";
import { mermaidDeclaresNodeId } from "../../src/product/investigation-report.js";
import { investigationNodeAnnotations } from "../../src/product/investigation-node-state.js";
// OpenCode owns a separate Zod version. Native parameters are described here;
// product schemas revalidate progress and presentation at execution boundaries.
@@ -36,28 +36,11 @@ const gapFields = {
evidence_refs: tool.schema.array(text).min(1),
};
export const taskPublicationDiagramSchema = tool.schema.object({
title: text,
mermaid: text.max(20000).describe("Mermaid flowchart structure; no Markdown fences, initialization directives or click callbacks. Reuse exact Mermaid node ids in node_refs, including class-attached declarations such as A:::stage. Supply execution states through node_refs.status; the host supplies execution colors, so no style/classDef is needed for state coloring."),
node_refs: tool.schema.array(tool.schema.object({
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", "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().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",
);
const reportFields = {
title: text,
current_conclusion: text,
conclusion_boundary: text,
evidence_refs: tool.schema.array(text).default([]),
diagram: taskPublicationDiagramSchema.nullish(),
ui_blocks: taskReportUiBlocksSchema,
};
@@ -83,9 +66,25 @@ export const taskPublicationReportSchema = tool.schema.discriminatedUnion("outco
}).strict(),
]);
export const taskPublicationProgressSchema = tool.schema.object({
export const taskPublicationGraphProgressSchema = tool.schema.object({
summary: text.max(20000),
diagram: taskPublicationDiagramSchema.nullable().default(null),
graph_update: tool.schema.object({
title: text.max(200).nullish(),
nodes: tool.schema.array(tool.schema.object({
node_id: tool.schema.string().regex(/^[A-Za-z_][A-Za-z0-9_-]{0,79}$/).describe("Stable task-wide id. Keep this id when updating a step; use a new id for a retry of a failed attempt."),
label: text,
detail: text.max(2000).nullish().describe("Actual result or failure reason; preserve the failed attempt."),
status: tool.schema.enum(investigationNodeAnnotations).nullish().describe("Optional saved reference annotation. recorded means a finding exists, not business completion; reference is neutral. No running state or live task synchronization."),
evidence_refs: tool.schema.array(text).default([]),
action_refs: tool.schema.array(tool.schema.string().uuid()).default([]),
map_targets: tool.schema.array(tool.schema.object({ source_id: layer, feature_id: identifier }).strict()).max(100).default([]),
}).strict()).max(100).default([]),
edges: tool.schema.array(tool.schema.object({
source: tool.schema.string().regex(/^[A-Za-z_][A-Za-z0-9_-]{0,79}$/),
target: tool.schema.string().regex(/^[A-Za-z_][A-Za-z0-9_-]{0,79}$/),
label: text.max(200).nullish(),
}).strict()).max(200).default([]),
}).strict().optional().describe("Incremental nodes and explicit relationships. The Server retains prior paths and generates Mermaid; omit for a text-only update."),
}).strict();
import { createViewSchemas } from "../../src/frontendAction/command.js";