fix: harden report tools and guide investigation presentation
This commit is contained in:
Vendored
+1
-1
@@ -8705,7 +8705,7 @@ export interface components {
|
||||
*/
|
||||
queried_at: string;
|
||||
/** Source */
|
||||
source: ("workbench_runtime" | "scada_devices" | "scada_readings" | "peripheral_points" | "peripheral_work_orders" | "peripheral_observations" | "peripheral_test_data") | ("get_mutation_status" | "query_nearby_elements" | "list_regions" | "get_region" | "get_region_nodes" | "get_node_connections" | "query_network_extent" | "list_major_nodes" | "get_model_configuration" | "list_model_configurations" | "search_project_places" | "reverse_geocode" | "get_project_info" | "export_project_model" | "get_sensor_placement_candidate" | "export_sensor_placement" | "list_task_schedules" | "get_task_run_history" | "calculate_demand_allocation" | "run_burst_simulation" | "edit_model_element" | "edit_model_configuration" | "clean_scada_readings" | "write_scada_readings" | "delete_scada_readings" | "save_simulation_scenario" | "create_sensor_placement" | "update_sensor_placement" | "create_operational_task" | "create_task_schedule" | "update_task_schedule" | "retry_operational_task" | "cancel_task_run" | "run_simulation" | "run_contaminant_simulation" | "analyze_valve_closure" | "analyze_flushing" | "analyze_pressure_regulation" | "optimize_sensor_placement" | "run_burst_detection" | "run_burst_location" | "analyze_valve_isolation" | "run_leakage_identification" | "get_element_properties" | "list_elements" | "get_network_options" | "get_network_schema" | "list_scada_devices" | "get_scada_device" | "query_scada_readings" | "query_model_baseline_series" | "get_model_baseline_at_time" | "query_model_baseline_field" | "query_analysis_series" | "query_analysis_values" | "list_analysis_runs" | "get_analysis_run" | "list_analysis_results" | "list_sensor_placement_runs" | "get_sensor_placement_run" | "query_element_history" | "get_element_gis_properties" | "get_source_gis_properties" | "get_workbench_runtime" | "get_task_context" | "query_peripheral_data" | "get_task_evidence");
|
||||
source: ("workbench_runtime" | "scada_devices" | "scada_readings" | "peripheral_points" | "peripheral_work_orders" | "peripheral_observations" | "peripheral_test_data") | ("get_mutation_status" | "query_nearby_elements" | "list_regions" | "get_region" | "get_region_nodes" | "get_node_connections" | "query_network_extent" | "list_major_nodes" | "get_model_configuration" | "list_model_configurations" | "search_project_places" | "reverse_geocode" | "get_project_info" | "export_project_model" | "get_sensor_placement_candidate" | "export_sensor_placement" | "list_task_schedules" | "get_task_run_history" | "calculate_demand_allocation" | "run_burst_simulation" | "edit_model_element" | "edit_model_configuration" | "clean_scada_readings" | "write_scada_readings" | "delete_scada_readings" | "save_simulation_scenario" | "create_sensor_placement" | "update_sensor_placement" | "create_operational_task" | "create_task_schedule" | "update_task_schedule" | "retry_operational_task" | "cancel_task_run" | "run_simulation" | "run_contaminant_simulation" | "analyze_valve_closure" | "analyze_flushing" | "analyze_pressure_regulation" | "optimize_sensor_placement" | "run_burst_detection" | "run_burst_location" | "analyze_valve_isolation" | "generate_node_coverage" | "run_leakage_identification" | "get_element_properties" | "list_elements" | "get_network_options" | "get_network_schema" | "list_scada_devices" | "get_scada_device" | "query_scada_readings" | "query_model_baseline_series" | "get_model_baseline_at_time" | "query_model_baseline_field" | "query_analysis_series" | "query_analysis_values" | "list_analysis_runs" | "get_analysis_run" | "list_analysis_results" | "list_sensor_placement_runs" | "get_sensor_placement_run" | "query_element_history" | "get_element_gis_properties" | "get_source_gis_properties" | "get_workbench_runtime" | "get_task_context" | "query_peripheral_data" | "get_task_evidence");
|
||||
/** Source Ref */
|
||||
source_ref: string;
|
||||
/** Truncated */
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -8,6 +8,10 @@ import { TASK_AGENT, TASK_READER_AGENT, TASK_READER_TOOLS } from "./task-agent-p
|
||||
|
||||
const agentMessageTools = [queryMcpToolId("list_investigation_tasks"), queryMcpToolId("get_investigation_task"), "send_task_message", queryMcpToolId("read_agent_messages"), "reply_agent_message"];
|
||||
|
||||
const readAgentInstructions = (name: "instruction" | "investigation" | "prompt-suggestions") =>
|
||||
readFileSync(resolve(`.opencode/agents/${name}.md`), "utf8")
|
||||
.replace(/^---\s*\n[\s\S]*?\n---\s*\n/, "");
|
||||
|
||||
type PermissionRule = { action: string; resource: string; effect: "allow" | "ask" | "deny" };
|
||||
const deniedProductQuery = (name: string, rules: PermissionRule[]) => rules.some((rule) => {
|
||||
if (rule.effect !== "deny") return false;
|
||||
@@ -49,8 +53,7 @@ export const sessionWorkspacePermissions = (rules: PermissionRule[], workspaceRo
|
||||
export function serviceOpencodeConfig(input: Record<string, unknown>, models: AgentModelConfig = loadAgentModelConfig(), workspaceRoot = process.env.TJWATER_AGENT_WORKSPACE_ROOT ?? process.env.CONVERSATION_WORKSPACE_ROOT ?? DEFAULT_SESSION_WORKSPACE_ROOT) {
|
||||
const agents = (input.agents ?? {}) as Record<string, unknown>;
|
||||
const instruction = (agents.instruction ?? {}) as Record<string, unknown>;
|
||||
const system = readFileSync(resolve(".opencode/agents/instruction.md"), "utf8")
|
||||
.replace(/^---\s*\n[\s\S]*?\n---\s*\n/, "");
|
||||
const system = readAgentInstructions("instruction");
|
||||
return {
|
||||
...input,
|
||||
...compileOpencodeModels(models),
|
||||
@@ -77,13 +80,13 @@ export function serviceOpencodeConfig(input: Record<string, unknown>, models: Ag
|
||||
"prompt-suggestions": {
|
||||
description: "仅根据已加载活动生成推荐问题 JSON", mode: "primary",
|
||||
model: models.defaults.chat,
|
||||
system: readFileSync(resolve(".opencode/agents/prompt-suggestions.md"), "utf8"),
|
||||
system: readAgentInstructions("prompt-suggestions"),
|
||||
permissions: [{ action: "*", resource: "*", effect: "deny" }],
|
||||
},
|
||||
[TASK_AGENT]: {
|
||||
description: "后台工况调查,协调只读子任务并统一发布进展与报告", mode: "primary",
|
||||
model: models.defaults.task,
|
||||
system: `${system}\n\n${readFileSync(resolve(".opencode/agents/investigation.md"), "utf8")}`,
|
||||
system: `${system}\n\n${readAgentInstructions("investigation")}`,
|
||||
permissions: [
|
||||
{ action: "ask_task_user", resource: "*", effect: "allow" },
|
||||
{ action: "question", resource: "*", effect: "deny" },
|
||||
|
||||
@@ -51,6 +51,7 @@ export const PRODUCT_MCP_ANALYSIS_TOOLS = [
|
||||
"analyze_valve_closure",
|
||||
"analyze_valve_isolation",
|
||||
"calculate_demand_allocation",
|
||||
"generate_node_coverage",
|
||||
"optimize_sensor_placement",
|
||||
"run_burst_detection",
|
||||
"run_burst_location",
|
||||
|
||||
Reference in New Issue
Block a user