内置工具失败改成 typed 错误穿出派发边界,事件带原始 error,软失败清零

- dispatch 返回 Result<Value, ToolCallError>:不再在工具内部吞掉错误再补一个失败 Value,handle_direct_tool_bridge 在唯一一处 match 里同时组装发给模型的内容与诊断
- 新增 ToolCallError { message, redact_limit, error, images },error 直接存序列化后的 typed 错误;每个内置工具在自己的 agent/tool/<tool>/error.rs 定义 typed enum,共用 case(参数不是对象 / 未审核字段等)抽到 agent/tool/error.rs 的 ToolFailure
- AgentRuntimeErrorEvent 增加 error 字段携带原始 typed 错误,schema 升到 agent-runtime-error.v3;应用日志详情行扩成 message=… error=… metadata=…,各带独立长度预算
- 软失败清零:import_account_assets、run_validation、browser_playtest、environment_check、apply_patch、editor_execute、cocos_execute 原先返回 Ok(失败载荷) 的分支改为 typed 错误,并把 status / dispatched / retryAllowed 等载荷带进错误,模型仍能看到
- 失败证据截图走 ToolFailure::attached_images 与 ToolCallError.images,截图放 #[serde(skip)] 字段,不混进诊断里的 error
- bridge_tool_result 去掉 is_error 入参,isError 只在组装处按 Ok/Err 分支写一次;MCP 线上 isError 字段与校验不变
- 修正上一提交遗留的两处 rustfmt:direct_tools_mcp.rs、runtime_tools/context.rs(同一 workspace 整体格式门禁要求)
- 同步更新技术方案文档字段表与共享记忆决策记录

验证:cargo check --bin genarrative-ai-game-creator-shell --tests 通过;cargo test --bin genarrative-ai-game-creator-shell -- agent:: 951 passed / 0 failed;npm run check:encoding、git diff --check 通过;Windows 专属 cocos/unity/godot 分支在 Linux 下临时去 cfg 交叉编译通过(无真实 Windows 构建)
This commit is contained in:
2026-10-01 13:30:32 +08:00
parent 4999653a5e
commit 9372845c35
31 changed files with 1045 additions and 795 deletions
@@ -1647,7 +1647,7 @@ DirectProject 在收到完整游戏策划或游戏制作请求后,必须把视
## 2026-09-15 AGC 统一错误事件、诊断落库与验收反馈
DirectProject、Agent Runtime、Provider、app-server、内置 MCP、命令执行、构建和浏览器试玩的失败必须先转换为统一的 `AgentRuntimeErrorEvent`,再分别投影到用户消息、运行面板和项目诊断文件;业务模块不得自行拼接只有一句“执行失败”的终态文案。统一事件至少包含 `schemaVersion / eventId / clientTurnId / source / stage / code / occurredAt / elapsedMs / message / detailRef`,其中 `message` 是产生失败的 typed 错误在失败现场写好、脱敏后的人类可读文案,`detailRef` 指向项目内有界诊断记录;Token、Cookie、URL/query、私钥、宿主绝对路径、原始请求正文和未脱敏 stderr 不得进入对话或用户可见文本。
DirectProject、Agent Runtime、Provider、app-server、内置 MCP、命令执行、构建和浏览器试玩的失败必须先转换为统一的 `AgentRuntimeErrorEvent`,再分别投影到用户消息、运行面板和项目诊断文件;业务模块不得自行拼接只有一句“执行失败”的终态文案。统一事件至少包含 `schemaVersion / eventId / clientTurnId / source / stage / code / occurredAt / elapsedMs / message / error / detailRef`,其中 `message` 是产生失败的 typed 错误在失败现场写好、脱敏后的人类可读文案,`error` 是同一个 typed 错误 enum 的原样序列化(一个 case 一个变体,供开发者按变体与字段定位;没有 typed 错误的调用方写 `null`),`detailRef` 指向项目内有界诊断记录;Token、Cookie、URL/query、私钥、宿主绝对路径、原始请求正文和未脱敏 stderr 不得进入对话或用户可见文本。
项目内统一落库目录为 `.agent/runtime/errors/`,事件记录采用幂等 JSONL 或 JSON sidecar;写入失败不能覆盖原始业务错误,但必须在事件中标记 `persistenceFailed`。DirectProject 对话历史必须持久化本轮用户消息、终态错误的安全 assistant 投影和诊断引用,使下一轮能够读取上一轮失败证据。前端只展示宿主给的安全文案;诊断正文只留在 `detailRef` 指向的有界、脱敏记录里,不进入用户可见文本。
@@ -1797,9 +1797,9 @@ Direct 回合的所有权属于进程内项目身份锁,不属于当前页面
## 2026-09-21 统一错误事件同时落到 AppData 应用日志
`AgentRuntimeErrorEvent` 把失败投影到用户消息、运行面板和项目内 `.agent/runtime/errors/<eventId>.json` 时,同一份已脱敏诊断还要投影成 AppData `diagnostics/application.log` 的两行:`agent.runtime.error`(身份行:`eventId / source / stage / code / clientTurnId / elapsedMs / detailRef`)与 `agent.runtime.error.detail`(详情行:`message / metadata`)。原因是项目内 sidecar 只在项目目录可见,而“报告问题”只上传应用级日志:没有这两行时,用户提交的失败消息里只剩一个 `详情:.agent/runtime/errors/...json` 路径,团队拿不到诊断正文。
`AgentRuntimeErrorEvent` 把失败投影到用户消息、运行面板和项目内 `.agent/runtime/errors/<eventId>.json` 时,同一份已脱敏诊断还要投影成 AppData `diagnostics/application.log` 的两行:`agent.runtime.error`(身份行:`eventId / source / stage / code / clientTurnId / elapsedMs / detailRef`)与 `agent.runtime.error.detail`(详情行:`message / error / metadata`)。原因是项目内 sidecar 只在项目目录可见,而“报告问题”只上传应用级日志:没有这两行时,用户提交的失败消息里只剩一个 `详情:.agent/runtime/errors/...json` 路径,团队拿不到诊断正文。
口径:两行都由 `agent/runtime_error.rs` 从同一份 diagnosis 生成,字段不退化成第二份来源;sidecar 里的 `message` 按 8 KiB 上限、应用日志的 `message` 与 `metadata` 按(1200 / 200 字符)预算先脱敏再截断,落盘前还会被 `sanitize_diagnostic_message` 二次脱敏并按行截断,因此自由文本字段在行内先压平换行。拆两行是因为整行一旦出现凭据标记会被整体替换成脱敏占位:所以**自由文本(message)只放详情行**,身份行只留程序生成与调用方常量字段,详情行被整体脱敏时事件仍能按 eventId / detailRef 定位。写日志先于写 sidecar:sidecar 失败不能连日志一起丢。
口径:两行都由 `agent/runtime_error.rs` 从同一份 diagnosis 生成,字段不退化成第二份来源;sidecar 里的 `message` 按 8 KiB 上限、应用日志的 `message` / `error` / `metadata` 按(1200 / 400 / 200 字符)预算先脱敏再截断,落盘前还会被 `sanitize_diagnostic_message` 二次脱敏并按行截断,因此自由文本字段在行内先压平换行。拆两行是因为整行一旦出现凭据标记会被整体替换成脱敏占位:所以**自由文本(message)只放详情行**,身份行只留程序生成与调用方常量字段,详情行被整体脱敏时事件仍能按 eventId / detailRef 定位。写日志先于写 sidecar:sidecar 失败不能连日志一起丢。
## 2026-09-23 AGC UI 设计文档 Agent 工具化重写