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
+48 -87
View File
@@ -1,106 +1,67 @@
# Agent UI Protocol
## Summary
## Controlled content and display commands
`agent-ui@1` is the experimental frontend protocol for Agent-driven UI. The
Agent never returns executable frontend code. It invokes trusted opencode tools;
the service maps those tool calls to `UIEnvelope` payloads; the frontend validates
and renders known components, charts, and map actions.
`answer-ui@1` describes retained answer content rendered by the frontend component catalog. Stable block_id, process/final phase and standalone `[[ui:block_id]]` anchors govern placement. Publishing or reading answer content never replays a map action.
## SSE Event
Map, chart, overlay and SCADA display use a single flat command object: `{action, ...typed fields}`. Chat calls `present_view({command, reason?})` with an optional concise purpose; tasks call `publish_task_presentation({request_id, command})`; Dev JSONL wraps the same command in `{record_type:"presentation", command}`. No executable JS, JSX, HTML, CSS, URL or arbitrary request is accepted. The former chat name/params action shape and frontend-action@1 are rejected; no migration adapter is provided.
生成接口为 `POST /api/v1/responses`。下方示例是会话状态订阅中的 UIEnvelope;Responses 生成流将同一业务数据放入 `tjwater.ui_envelope` 事件的 `data` 中,并携带 `response_id` 和 `sequence_number`。
The ten commands are locate_features, zoom_to_map, highlight_features, clear_highlights, open_chart, close_chart, set_overlay, clear_overlay, render_scada_analysis and clear_scada_analysis. Historical data must first be queried through an authorized MCP tool and then supplied to open_chart. Command definitions, including bounded GeoJSON, charts and target lists, are generated from the supply frontend's `src/features/agent/presentation/command-schema.ts`; the exported `src/frontendAction/command.ts` is also used with OpenCode's native Zod instance. Regenerate using `node scripts/export-view-command-contract.mjs` in the supply frontend; add `--check` to verify copies.
运行中追加文本使用同一生成地址,`stream=false`,携带 `conversation`、`tjwater.delivery="steer" | "queue"` 和稳定的 `tjwater.input_id`。HTTP 202 的 `tjwater.response_input` 收件回执不是新的 Response 或处理成功证明。当前 Responses SSE 保持连接,用 `tjwater.user_input` 的 `pending` / `delivered` 状态同步输入,`delivered` 表示原生收件箱已取走而非任务完成。`GET /api/v1/session/:session_id/inputs` 返回待处理消息;PATCH 其 `/:input_id` 的 `delivery` 可在处理前切换模式,处理后返回409。恢复会话流使用同样的 `user_input` 业务事件和原生待处理列表。
## Chat transport and receipts
```text
event: ui_envelope
data: {
"session_id": "session-id",
"envelope_id": "env-id",
"created_at": 1710000000000,
"envelope": {}
The frontend advertises `frontend-action@2`, and discovers supported actions through `GET /api/v1/frontend-action-registry`, schema_version `frontend-action-registry@2`.
Generation uses `POST /api/v1/responses`. A `tjwater.frontend_action` SSE event carries response_id, sequence_number and data containing:
```json
{
"version": "frontend-action@2",
"actionId": "host-action-id",
"toolCallId": "native-call-id",
"sessionId": "session-id",
"command": {"action":"highlight_features","layer":"pipes","feature_ids":["533793"],"effect":"pulse"},
"risk": "view",
"replayPolicy": "never",
"issuedAt": 1710000000000,
"expiresAt": 1710000015000
}
```
## Envelope Types
Session subscriptions carry the same data in a `frontend_action` event. The browser validates the active session and command, executes in stream order, and submits `frontend-action-result@2` through `POST /api/v1/frontend-actions/:action_id/result`, with the scoped session header. Results contain actionId, status, output or error, and completedAt. Status is succeeded, failed, rejected, cancelled or expired. Locate/highlight output reports actual matched and missing IDs; publishing a tool call is not proof of display success. Duplicate delivery reuses the terminal receipt, not the map side effect. Session switches cancel running and queued work; expiry prevents later execution.
Current `agent-ui@1` support is intentionally limited to three trusted shapes:
## Task and preview lifetimes
- `registered_component` for frontend-registered business panels.
- `chart` for the `echarts-safe-subset` chart grammar.
- `map_action` for the WebGIS map adapter.
Task display is asynchronous: Server verifies the task/run/generation, active lease, source session, evidence ownership and idempotency key before recording `operational.presentation.requested`. A recorded result does not mean a browser displayed it; tasks progress without any connected browser. New subscriptions take a fresh event baseline and do not replay historical display commands.
The Agent must not return executable JS, JSX, HTML, CSS, or arbitrary component
source. The frontend renders only allowlisted components, chart specs, and map
actions.
Task open_chart requires persisted evidence references. Task set_overlay can omit data; Server resolves immutable evidence into bounded GeoJSON. Supplied data must match that resolution and never overrides the stored snapshot. Chat and Dev supply resolved data for structural validation before rendering.
```ts
type UISurface = "chat_inline" | "side_panel" | "canvas" | "map_overlay";
Chat, task following and Dev previews share the frontend command dispatcher but own independent map sources, SCADA layers and chart state. Clearing or switching one owner does not clear another. Dev JSONL is an explicit local preview/import/export container, not an alternative online business protocol. Plain data records and ui_block records do not execute embedded actions. The earlier agent-ui@1 registry has no active tool mappings and is not an action entry point.
type UIEnvelope =
| {
kind: "registered_component";
schemaVersion: "agent-ui@1";
component: string;
surface: UISurface;
props: unknown;
data?: unknown;
fallbackText?: string;
}
| {
kind: "chart";
schemaVersion: "agent-ui@1";
grammar: "echarts-safe-subset";
surface: "chat_inline" | "side_panel" | "canvas";
spec: unknown;
data: unknown;
fallbackText?: string;
}
| {
kind: "map_action";
schemaVersion: "agent-ui@1";
action: string;
surface: "map_overlay";
params: unknown;
fallbackText?: string;
};
```
## Running and waiting input delivery
## Registry
Text output has separate append, flush and close operations. Smoothing releases
English words and bounded Chinese chunks at the configured interval (20 ms by
default). A short tail that cannot form a regular chunk waits at most 100 ms
after input starts or the last visible release; this deadline never dumps a
whole queued paragraph. Text segment completion, tool transitions and
permission/question waits drain preceding text at the same pace before the
next event. Publishing `present_answer` also drains preceding text but leaves
output open for later narration. Only terminal cleanup closes output.
Cancellation immediately drains already-received text before native interruption;
it does not manufacture a final answer. The first-token log records emission
from this buffer, rather than input admission into it.
```text
GET /api/v1/ui-registry
```
Ordinary chat and task collaboration share one final-answer handoff. A completed
`final_answer` is held until all observed tools are terminal and the inbox close
guard permits completion. The host then emits `tjwater.final_answer` exactly
once before interrupting further native rewrites and waiting for native idle.
Plain-text and component answers follow the same rule. Tool arguments are not
published as partial JSON; answer content and component references must pass
their existing validation before publication. Background `task_execution` keeps
its native idle and Server completion checks; a chat answer never completes a
business task.
The registry returns the allowed chart grammar, registered components, and map
actions. Unknown components/actions should be rejected by the frontend.
Running text additions use `POST /api/v1/responses` with stream=false, conversation, `tjwater.delivery="steer" | "queue"` and a stable tjwater.input_id. HTTP 202/tjwater.response_input is an inbox receipt, not execution completion. The existing SSE connection carries tjwater.user_input pending/delivered states; delivered means the native inbox took the input. `GET /api/v1/session/:session_id/inputs` lists pending input; PATCH of its /:input_id delivery can change its mode before processing and returns 409 afterward. Restored session streams retain these semantics.
`map_action` is a WebGIS-specific extension, not a general command channel. It
is valid only on `map_overlay`; every action and parameter set must be validated
by the frontend before it can affect MapLibre state. Invalid map actions should
fall back to `fallbackText`.
There are currently no Agent tool mappings for charts, components, or map
actions. The protocol types remain documented for compatibility and future
allowlisted actions.
Analysis evidence must come from validated request context or allowlisted
tools. This service does not bundle a local historical business dataset.
## State Persistence
Assistant messages may contain:
```ts
uiEnvelopes?: Array<{
envelopeId: string;
createdAt: number;
envelope: UIEnvelope;
renderStatus: "pending" | "rendered" | "fallback" | "error";
error?: string;
}>;
```
`tool_call` and `artifacts` remain deprecated compatibility/debug surfaces. New
frontends should consume `ui_envelope` and `uiEnvelopes`.
A pending host question or failed continuation does not disable input admission. With no active host turn, steer/queue messages enter the native inbox with resume=false and remain available for the next owned continuation. This does not answer a host question or launch inference. A lost delivery acknowledgement can be reconciled only after verifying both native consumption and completed related output. HTTP 409 with code RESPONSE_FINISHED means the input was rejected before admission because the turn has ended; the client may refresh state and send it as a new response. Other failures do not authorize automatic resubmission.
+3 -2
View File
@@ -66,6 +66,7 @@ Agent:`bun run check`,`bun test tests/routes/executionPlan.test.ts tests/rou
报告采用固定字段、增量扩展,不增加格式版本标签。完整规则见
[Server 调查报告契约](../../next-tjwater-server/docs/investigation-report-contract.md)。
数据缺失不再保持 waiting:关键缺口导致 final/insufficient_data 正常退出;
动作提议前先保存本轮 interim/concluded 报告,动作提议后再保存 final 报告。
事件调查中可由用户补齐的关键输入通过 `ask_task_user` 保存问题,右侧调查报告直接展示固定问答,无需预先发布报告或生成表单。问题为 pending 时当前 run 正常结束、任务进入 waiting_human;用户在报告内提交后原任务排队新 generation,继续读取 questions 中的回答。
只有查询与适用的人工补充均无法解决关键缺口、且无法形成必要判断或具体建议时,才以 final/insufficient_data 正常退出。计算等待、人工等待与执行故障按调查指令分别处理。
动作提议前先保存本轮 interim/concluded 报告,动作提议后继续必要分析,满足停止条件再保存 final 报告。
数据授权由系统配置,本场景不请求人工数据授权。