feat(chat): stream and persist local answer UI with final report references
This commit is contained in:
@@ -99,3 +99,10 @@ bun test tests/frontendAction/callIdPlugin.test.ts tests/runtime/opencode.test.t
|
||||
MCP 策略变更使用 MCP 相关测试,实际连接联调可选择根目录 README 列出的本地 smoke 脚本。模型配置修改使用 `bun run check:models`。这些命令从仓库根目录运行;仅修改描述或说明时检查路径、JSON 和 `git diff --check` 即可。
|
||||
|
||||
运行时确认以 Agent 的启动预热和 `/health` 为准。client 模式要同时更新外部 OpenCode 使用的资产。服务启动、工作区保留和容器挂载参见 [根目录 README](../README.md),代码修改约束见 [AGENTS.md](../AGENTS.md)。
|
||||
|
||||
## 本地交互式回答
|
||||
|
||||
`tools/present_answer.ts` 注册本地交互式回答工具。组件契约来自前端导出的
|
||||
`../src/chat/answer-ui-catalog.json`,由服务与前端同时校验;注册入口仍是
|
||||
`plugins/tjwater-tools.ts`。更新后需要重新加载 Agent/OpenCode 的可信插件资产。
|
||||
发现验证使用 `bun run tests/integration/answer-ui-smoke.ts`,从 Agent 仓库根目录运行。
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"plugins": [
|
||||
{
|
||||
"id": "tjwater.tools",
|
||||
"description": "注册业务工具和待办工具,安装会话 Shell 沙箱与 MCP 权限策略,并提供模型校验和工具发现 RPC。",
|
||||
"description": "注册业务工具、本地交互式回答 present_answer 和待办工具,安装会话 Shell 沙箱与 MCP 权限策略,并提供模型校验和工具发现 RPC。",
|
||||
"package": "tjwater-plugin",
|
||||
"entrypoint": "tjwater-plugin/index.ts",
|
||||
"implementation": "plugins/tjwater-tools.ts"
|
||||
|
||||
@@ -34,9 +34,21 @@ Agent 负责:
|
||||
| 发布精简调查报告和关键证据 | `publish_task_report` |
|
||||
| 提议需人工审批的工单或命令 | `propose_task_action` |
|
||||
| 记录用户明确提供的任务证据 | `add_task_human_evidence` |
|
||||
| 发布真实调查进度与当前路线 | `publish_task_progress` |
|
||||
| 发布任务地图、图表和证据图层展示请求 | `publish_task_presentation` |
|
||||
|
||||
## UI 约束
|
||||
|
||||
回答可通过 `present_answer` 组合本地交互界面,与简短文字交错呈现。按问题选择组织方式:比较资料用 Stack 双列或 Table,真实监测数据用 Chart,证据依据用 Evidence,补充资料用 FollowUpForm;普通短问答可只用文字。组件参数以工具描述中的 schema 为准,不输出 HTML、JSX、脚本或 JSONL 到聊天正文。
|
||||
|
||||
一个界面块发布后可以继续查询或解释,再发布下一块;相同 block_id 更新本轮同一块。不要重复在文字中抄写整张表或图。每个 Chart 和 Metric 保留单位或来源;查询事实、用户事实、推断和演练数据分别标注。MapLocation 仅提供用户主动点击后的定位入口。FollowUpForm 只收集输入,用户提交后形成新的对话消息,不能在未提交时当作已确认事实。后台 task_execution 使用原任务进度与展示渠道,不调用 present_answer。
|
||||
|
||||
已发布本地交互界面的普通/协作聊天在 `final_answer` 完成后结束本轮,不能在最终答复之后继续调用 `present_answer`。需要补充界面应先更新完再提交最终答复。后台调查执行仍按原有任务运行和完成接口收尾。
|
||||
|
||||
`final_answer.answer` 是完整的最后报告,保留结论和必要边界;在 ui_block_ids 中明确引用应保留在报告内的已发布 block_id(按报告顺序)。其余流式文字、界面与轨迹在最终报告到达后统一折叠为处理过程,不能把报告必需的图表、证据或资料表单遗漏在折叠过程里。
|
||||
|
||||
`final_answer` 保留完整报告和必要边界,不抹掉已发布界面,也不把资料记录成功说成浏览器已展示、业务处置完成或现场核验完成。
|
||||
|
||||
1. `render_scada_analysis` 只能在分析结论已经完成后调用,不得让前端计算、猜测或修改高中低等级。
|
||||
2. 每次普通工具调用必须填写具体 `reason`。
|
||||
3. 不生成 JS、JSX、HTML、CSS 或可执行前端代码。
|
||||
|
||||
@@ -2,6 +2,7 @@ import query_project_data from "../tools/query_project_data.js";
|
||||
import web_search from "../tools/web_search.js";
|
||||
import geocode from "../tools/geocode.js";
|
||||
import final_answer from "../tools/final_answer.js";
|
||||
import present_answer from "../tools/present_answer.js";
|
||||
import locate_features from "../tools/locate_features.js";
|
||||
import zoom_to_map from "../tools/zoom_to_map.js";
|
||||
import show_chart from "../tools/show_chart.js";
|
||||
@@ -31,6 +32,7 @@ const businessTools = {
|
||||
geocode,
|
||||
query_project_data,
|
||||
final_answer,
|
||||
present_answer,
|
||||
locate_features,
|
||||
zoom_to_map,
|
||||
show_chart,
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { tool } from "../tool.js";
|
||||
|
||||
export default tool({
|
||||
description: "提交本轮完整的最终答复。只在全部工具结果、浏览器回执和业务写回已核实后调用;阶段说明留在过程记录。此工具不停止执行,也不代表任务、工单或运行已完成。",
|
||||
args: { answer: tool.schema.string().min(1).max(30000) },
|
||||
async execute({ answer }) { return JSON.stringify({ answer }); },
|
||||
description: "提交本轮完整的最终答复。全部工具结果与需要的业务写回已核实后调用;需要浏览器动作回执时先核实回执,本地回答界面发布不等待浏览器回执;阶段说明留在过程记录。answer 是最后报告的完整文字;ui_block_ids 只列已经用 present_answer 发布且应保留在最终报告中的 block_id,按报告顺序排列。收到最终报告后,其他过程文字、界面与轨迹自动折叠。已发布本地交互界面的聊天会在此工具完成后结束本轮,之后不能继续更新界面;需要补充内容应先更新再提交最终答复。后台任务仍以 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 ?? [] }); },
|
||||
});
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
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" };
|
||||
|
||||
export default tool({
|
||||
description: "把回答的一段内容发布为本地交互界面。spec 是 root + elements 的扁平树,每个元素包括 type、props、children。只组织真实查询资料或用户事实;无脚本、URL 或任意代码。不执行地图或业务动作,不等待浏览器,不代表业务完成。相同 block_id 更新本轮同一内容块。组件参数的 JSON Schema:" + JSON.stringify(catalog.props),
|
||||
args: {
|
||||
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({
|
||||
type: tool.schema.string(), props: tool.schema.record(tool.schema.string(), tool.schema.unknown()), children: tool.schema.array(tool.schema.string()).max(48),
|
||||
}).strict()) }).strict(),
|
||||
},
|
||||
async execute(args) {
|
||||
const value = answerUiInputSchema.parse(args);
|
||||
return JSON.stringify({ version: "answer-ui@1", ...value, recorded: true });
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user