Refactor/整理项目对话错误相关代码 #593

Merged
k88936 merged 40 commits from refactor/turn-error-to-explicit-struct into master 2026-10-03 15:58:25 +08:00
Member
  • 移动,拆分代码
  • 重命名
* 移动,拆分代码 * 重命名
k88936 added 20 commits 2026-10-03 11:41:38 +08:00
- check-generated-bindings 同时覆盖 shared-contracts 与 AGC 的 chat/generated、services/generated、ui-editor/types
- ts-rs 重新生成后先按仓库格式跑 prettier 再逐字节比较,消除"格式不同"的假漂移
- 校验失败时按目标给出对应的重新生成命令
- 三条错误表去冗余前缀:DirectTurnError→TurnError、DirectTurnEnqueueError→EnqueueError、DirectTurnFailure→TurnFailure、DirectTurnDeadline→Deadline、DirectCodexFailureStage→FailureStage、DirectCodexNativeKind→NativeKind、DirectModelCallKind→ModelCallKind
- 用户条目去前缀:DirectCodexUserItem→UserItem 等六个类型;agent/direct_turn_error.rs 移到 codex_app_server/turn_error.rs
- thread_manager/wire.rs 拆为 wire/{mod,clock,items,turn,failure,tests}.rs;direct_turn_failure.rs 的失败载荷并入 wire/failure.rs
- 宿主侧回合终态从 struct TurnTerminal 改为 enum TurnCompletion{Completed,Interrupted,Aborted,Failed},从 wire 移到 thread_manager/turn_completion.rs,status 由变体反推
- 过期回合释放兜底改走 TurnCompletion::Aborted.event
- 同步生成绑定、前端调用点与测试;修正终态单测里过时的 TransportClosed 断言
- 对 src-tauri 全量 cargo fmt,满足 pre-commit 的 rustfmt 门禁
- turn_error.rs 把 DomainFact 改名为故意扎眼的 SuperErrorEnumFromStringTyped,标记"从字符串读出的 typed 失败事实表"这笔待清债
- TODO 从"产生层还没有 typed 出口"改为明确指向上游改造:让产生层直接产出 typed 变体/结构化子错误、判定层改 match,之后整张表与 classify_direct_codex_error 一起删
- enum 注释写明这个名字是债标记:禁止把它改漂亮,只允许按 TODO 删掉整张表
- 只改名与注释,不改任何判定逻辑
- AGC 命令错误 ADR:三条错误表补 DirectProject 现有命名(TurnError / TurnFailure / EnqueueError),并写明"回合终态 TurnCompletion 是宿主内部判别联合,不导出、不进 wire"
- 决策记录新增 2026-10-02 条目:命名去冗余前缀、wire.rs 按职责拆分、宿主终态改判别联合与 TurnCompletion 的落点
- TurnFailed 更名为 SuperErrorFromStringPlusStage:只有 stage 是 typed,detail 仍是产生层字符串
- TurnFailedUnclassified 更名为 Unclassified:深层字符串兜底,连交付阶段都没有
- 拆掉 direct_runtime 与 thread_manager 两处收口重包,typed 失败原样投影成失败载荷
- 审计写盘保留,只是不再把 direct-codex-failure:v2 收口文案当失败载荷的 detail
- 生成绑定重导出(旧 TurnFailed/TurnFailedUnclassified 生成文件删除),前端分支与 directThreadChat 用例同步改名
- 命名债注释补记:这两个载荷臂与 SuperErrorEnumFromStringTyped 属同一笔待清债
- 修复 direct_tools_mcp 容量用例:handler 已改为返回 Response,改为取出 body 后再断言 JSON-RPC id
- 修复 direct_runtime 命令边界诊断用例:改为断言结构化 sidecar(schemaVersion/source/stage/code/clientTurnId/detail),不再断言早已不落盘的 direct-codex-failure:v2 文案
- AGC 命令错误 ADR 补记 TurnFailure 两个字符串债臂的命名与“terminal_failure 是唯一投影点、不许重包”口径
- 决策记录新增 2026-10-02 条目:两臂改名(SuperErrorFromStringPlusStage / Unclassified)与 typed 失败原样下发
- 排查记录补充:cargo test 过滤是子串匹配,会连带命中 export_bindings 并重写生成目录,用 --skip export_bindings 避开
- resourceReferences.ts:chatComposerDraftToDirectCodexUserItem → chatComposerDraftToUserItem、directCodexUserItemFromContent → userItemFromContent,与已改名的 UserItem 生成类型对齐
- 同步 DirectProjectChatView、useDirectProjectChatController 与三处用例的调用与 import
- items.rs:item_text 先判空/全空白再回落 content/summary;此前 Some("") 会短路兜底,末尾非空过滤把整条 message/reasoning 丢掉
- wire/tests.rs:补空 text + 有 content 的回归用例
- codex_app_server/mod.rs:Self::Failed(error) 用 write!(formatter, "{error}") 直接走 Display,不再先 error.to_string() 再 write_str
- check-generated-bindings.mjs:prettier 归一化改用平台命令名 npx.cmd / npx,spawnSync 无 shell 时 Windows 解析不到裸 npx
- shared-contracts 提示补上 --manifest-path server-rs/Cargo.toml:仓库根没有 Cargo.toml,照抄提示会直接失败
- 两条提示都补上 --locked,与实际执行的 cargo 命令保持一致
- spawnSync 连命令都拉不起来(如 ENOENT)时 status 为 null、error 非空,原来只报一句"未通过",看不到原因
- run() 改为在 result.error 时先打印"无法启动 <命令>:<原因>",spawn 失败与测试失败可区分
- snapshot() 遇到配置目录不存在时原来直接返回空表,空表比空表会被当成"通过(0 个文件)"
- 目录写错或已删除时改为打印"目录不存在,跳过校验",避免整组绑定被悄悄跳过
- snapshot() 原来只列目录下一层文件,绑定若放进子目录就两边快照都看不见,陈旧绑定会被漏掉
- 改用 readdirSync 的 recursive 列出全部条目,再按文件过滤并归一到 POSIX 分隔符,键形态与原来保持兼容
- 原来每个目标里直接 process.exit,第一个目标(shared-contracts)失败时 AGC 目标根本不会被检查,得反复重跑才知道还有哪些绑定陈旧
- 改为记录 failed 后 continue,跑完全部目标再统一退出 1,两个绑定集的错误一次报齐
- FunctionCall.name 原来既不脱敏也不截断,改为走 detail_text(脱敏 + THREAD_DETAIL_MAX_CHARS)
- McpToolCall.tool 原来只脱敏不截断,改为 detail_text
- CommandExecution.status、McpToolCall.status、ThreadItem::Other.raw_type、ThreadFileChange.kind 原来原样拷贝,统一按 THREAD_DETAIL_MAX_CHARS 限长,避免单条事件突破声明的事件通道预算
- turn.completed.failure 跨 IPC 没有运行时校验,modelCallFailed 载荷缺 kind 时 directModelCallText 会解引用 undefined 抛错,整个事件订阅被打断
- directModelCallText 参数放宽为 ModelCallKind | null | undefined,缺分类时回落到通用句"智能服务执行失败,请稍后重试"
- turn.completed.failure 跨 IPC 没有运行时校验,superErrorFromStringPlusStage 的 stage 缺失或为更新后端新增的取值时,查表得到 undefined 会被拼进用户可见文案
- 查不到标签时回落到通用句"执行失败,请稍后重试",只有已知阶段才带"请检查项目诊断"
- 新增 TurnCompletedStatus(completed / interrupted / aborted / failed),作为 turn.rs 里 status 字段的类型
- 新增只作构造参数的 TurnOutcome(completed / interrupted / aborted):turn_completed 拿不到 Failed,"failed 但没有失败载荷"在构造层就写不出来
- turn_completed_failed 固定写 TurnCompletedStatus::Failed,载荷与状态在同一条构造路径上
- 内部状态比较与用例断言改为比较枚举取值;线上 JSON 形状、字段名与前端契约不变
- 重新生成绑定:新增 TurnCompletedStatus.ts,ThreadEvent.ts 的 status 由 string 收成字面量联合
修复:模型调用失败改按 typed kind 判定通道断开
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m39s
Project CI / Backend tests (pull_request) Failing after 16s
Project CI / AI game creator shell Rust crates (pull_request) Failing after 3m14s
Project CI / Frontend tests (pull_request) Successful in 2m4s
Project CI / Repository checks (pull_request) Failing after 22s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m46s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 9m14s
Project CI / Native shell tests (pull_request) Successful in 6m20s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 12m24s
2dbbf72450
- direct_codex_error_code 原来对 ModelCallFailed 一律按宿主原文子串分类,ModelCallKind::TransportBroken 只有在原文恰好含 transport closed / 连接已关闭 时才会标成 transport-closed
- 改为先看 payload.kind:TransportBroken 直接给 transport-closed,其余 kind 仍按原文兜底(TODO 文本判据)
- 补用例 model_call_transport_broken_is_classified_from_the_typed_kind:原文不含任何分类标记,也只可能由 typed kind 得出该 code
Author
Member
  • 1. resourceReferences.ts 残留 DirectCodex 函数名 —— 已修(cb13ecf87)。
    当前实现:chatComposerDraftToDirectCodexUserItem / directCodexUserItemFromContent 已改名为 chatComposerDraftToUserItem / userItemFromContent,调用方同步。
    说明:文件里 DirectCodexLegacyContentDto、hasMeaningfulDirectCodexContent 描述的是 legacy content 形态,不属于这次改名范围。

  • 2. wire/turn.rs 的 status 是自由字符串 —— 已实现(40b79bb1e)。
    当前实现:字段类型改为 TurnCompletedStatus(completed / interrupted / aborted / failed,serde 序列化到同样的 JSON 字符串);turn_completed 的参数类型是只有三档的 TurnOutcome,函数内部 Into 成字段类型。
    解决了什么:turn_completed("failed".into(), at) 这种"failed 但没有失败载荷"在构造层已经写不出来;turn_completed_failed 固定写 Failed + 载荷。
    线上形状没变:JSON 仍是 {"type":"turn.completed","status":"completed",...};TS 侧 status 从 string 收成 'completed' | 'interrupted' | 'aborted' | 'failed'(新增 TurnCompletedStatus.ts),前端 reducer 只读 failure,不受影响。
    仍留的口子(评审提过、这次没动):status: "failed" 但没有 failure 的 JSON 仍能反序列化(wire/tests.rs 有用例专门盯这个宽松行为,是给重放兼容留的);把 failure 并进 Failed 变体做成"内部 tagged 的扁平枚举"能彻底关掉,但会改前端判别联合的形状,需要你点头再动。

  • 3. wire/items.rs:426-432 等上游字符串绕过限长/脱敏 —— 已修(fc10b2359)。
    当前实现:FunctionCall.name → detail_text(脱敏 + 4000 字);McpToolCall.tool 由 sanitize_detail_text 改成 detail_text;CommandExecution.status、McpToolCall.status、ThreadItem::Other.raw_type、ThreadFileChange.kind 统一 bounded(..., THREAD_DETAIL_MAX_CHARS)。
    说明:机器判别字段只限长、不脱敏改写,避免破坏枚举语义。

  • 4. wire/items.rs:244 空 text 短路 content/summary 兜底 —— 已修(25213c9ff)。
    当前实现:先做"非空/非纯空白"过滤,再回落 content/summary。
    问题(原):过滤在兜底之后,"text": "" 会产出 Some("") 短路兜底,末尾再返回 None,整条 message/reasoning 被丢掉。

  • 5. detail_text 先脱敏后截断的成本 —— 已答复,代码不动。
    结论:脱敏有两条链路,你问的那处只影响前端,不落盘。
    · 前端这条:wire/items.rs::detail_text / sanitize_detail_text 只被 thread_item_from_value / thread_items_from_history 调用,产物只进下发给前端的 ThreadEvent 与历史切片,没有任何写盘操作。它的口径:项目内绝对路径换成项目相对路径(<root>/game/src/x.ts → game/src/x.ts),其余绝对路径 <absolute-path>、密钥 [redacted-secret]。
    · 落盘这条:runtime_state.rs::redact_agent_runtime_error,负责 .agent/runtime/errors/*.json、.agent/runtime/direct-codex-diagnostics/**/failure.json、AppData 的 application.log,以及 turn.completed.failure 载荷里的 detail / cause / diagnostic。它的口径:项目根换成 $PROJECT_ROOT,另有 <redacted-url>。它同样在最后一步才截断(truncate_agent_runtime_text)。
    · 所以"先脱敏后截断"两条都存在,理由也相同(先截断会把 token 切断、漏半个密钥);前端这条只是每次事件的 CPU 成本,没有磁盘副作用。真要治只能在入口限制原始 payload 大小,建议保持现状。

  • 6. check-generated-bindings.mjs Windows 上的 npx —— 已修(09f007d51):模块级 npxCommand = win32 ? 'npx.cmd' : 'npx'。

  • 7. check-generated-bindings.mjs:114-115 逐目标 process.exit —— 已修(80afa2a13):失败改为 failed = true + continue,跑完全部目标再统一 exit 1。

  • 8. check-generated-bindings.mjs:52 hint 缺参数 —— 已修(bfbcb1dd0):shared-contracts 补 --locked --manifest-path server-rs/Cargo.toml,AGC 补 --locked。

  • 9. check-generated-bindings.mjs:76-77 snapshot() 不递归 —— 已修(fff4e8ff6):readdirSync(recursive) + 文件过滤 + POSIX 键名。

  • 10. codex_app_server/mod.rs:4265 多余的一次 String 分配 —— 已修(e9ad1f3dd):改回 write!(formatter, "{error}")。

  • 11. directTurnEnqueueFailure.ts:50 无条件包 wrapper —— 按你的结论不改:信任 Tauri,invoke 的 rejection 形状就是 EnqueueError。
    评审提的两个反例(JSON.stringify(new Error('boom')) === '{}'、半结构化 payload 让 .trim() 再抛)都建立在"rejection 可能是任意 JS 值"上;在这个前提下不成立。若哪天发现不成立,再回来加形状判据。

  • 12. directTurnFailure.ts:167 缺 kind 时抛错 —— 已修(1ba9ac848):directModelCallText 参数放宽为 ModelCallKind | null | undefined,缺分类时回落通用句。

  • 13. dispatch.rs 的 Option<TurnFailure> —— 已改(11e98a3ca)。
    当前实现:TurnError::terminal_failure -> Option<TurnFailure> 换成
    TurnError::classify -> TurnErrorClassified{ShouldStop(TurnFailure), ShouldContinue { detail }}。
    dispatch.rs 两处 .expect("回合失败必可投影成失败载荷") 改成按分类 match:ShouldStop 写终态,
    ShouldContinue 不写终态、不伪造失败;turn_terminal 里 ShouldContinue 显式认账(debug 构建
    断言,release 落到账本正常终态)。
    说明:控制流不再借 None 表达"要继续跑";TurnFailure 上加了 // TODO badnaming。

  • 14. check-generated-bindings.mjs run() 吞掉 spawn 失败原因 —— 已修(6f2eed0dc):result.error 时打印 无法启动 <命令>:<原因>。

  • 15. check-generated-bindings.mjs:73 目录不存在静默通过 —— 已修(f593bd4bf):目录不存在时 console.warn。

  • 16. direct_runtime/mod.rs:7310 断言 direct-codex-failure:v2 —— 已在更早的提交(9a75ea57f)修掉,当前 HEAD 不成立。
    当前实现:该用例断言结构化 sidecar 的 schemaVersion / source / stage / code / clientTurnId 与脱敏结果;direct-codex-failure:v2 ... 只在 record_direct_codex_failure 的返回值和错误上报池上断言。

  • 17. direct_runtime/mod.rs:2017 ModelCallFailed 走文本分类 —— 已实现(2dbbf7245)。
    当前实现(原):direct_codex_error_code 对 ModelCallFailed 一律拿宿主原文跑子串分类。结果是 ModelCallKind::TransportBroken(来自 LlmError::Transport)只有在原文恰好含 transport closed / 连接已关闭 时才会被标成 transport-closed;宿主换一句 Display 就会掉进 runtime-unclassified,同样的失败给出不同的诊断 code。
    改成:先看 payload.kind,TransportBroken 直接给 transport-closed;其余 kind 还没有专属 code,仍按原文兜底(保留 TODO(文本判据))。
    新增用例 model_call_transport_broken_is_classified_from_the_typed_kind:原文不含任何分类标记,也只可能由 typed kind 得出 transport-closed。

  • 18. turn_completion.rs 的 and_then 与 SessionOutcome —— 已改(11e98a3ca)。
    当前实现:删掉 private SessionOutcome,收尾 status → 终态的解析并成
    session_completion(status) -> Option<TurnCompletion>;turn_terminal 不再用 and_then 压平:
    先按 host_failure > collect_outcome > 账本 failed 兜底 选出错误,再走 classify,ShouldStop
    直接成失败终态,ShouldContinue 不伪造失败。
    说明:TurnCompletion 仍是"这一轮怎么收场"的唯一定义,少了一个同义类型。

  • 19. ThreadManager 脱敏 / 限长搬前端 —— 已改(68cb41e33 Rust 侧,549915a2b 前端侧)。
    · Rust 侧:删掉 wire/items.rs 的 bounded / detail_text / sanitize_detail_text /
    relativize_project_root_paths / thread_delta_text 与 THREAD_TEXT_MAX_CHARS /
    THREAD_DETAIL_MAX_CHARS / THREAD_PATH_MAX_CHARS;thread_item_from_value /
    thread_items_from_history / 运行态投影 / 流式增量签名去掉 root,字段原样透传。dispatch.rs
    写历史失败不再预脱敏——错误文案的脱敏保留在 classify 投影成失败载荷那一步(脱敏 + 截断)。
    · 前端侧:新增 directThreadSanitize,在 bootstrap / consume / 历史切片进入聊天状态之前统一过
    一遍;用 hook 已有的 projectPath 把项目内绝对路径归一成相对路径,其余绝对路径 / 密钥 /
    敏感赋值 / Bearer / 配置文件 / 私钥块 / 敏感开关换成占位符。占位符沿用
    <absolute-path> / [redacted-secret] / [redacted-sensitive-field] / [redacted-config] /
    [redacted sensitive context],model.ts 的中文显示映射不用改;不做任何截断。配 12 条单测。
    · 说明:ThreadManager 每条线程的 8 MiB / 8192 事件订阅缓冲上限没动——那是订阅背压,不是
    字段限长;字段限长已按"no bounded at all"全删。

  • 20. directTurnFailure.ts:175 未知 stage 渲染 undefined —— 已修(adcf3b973):查不到标签时回落"执行失败,请稍后重试"。(原评审的 patch 用的是旧分支名 turnFailed,现名是 superErrorFromStringPlusStage。)

  • 21. direct_runtime/mod.rs 入队失败硬写 stage=code-generation —— 已答复:这是重构前就有的行为,不是本次引入。
    证据:重构前的 direct_turn_error.rs 里,turn_failure_stage() 的注释与实现是"已记录失败的诊断阶段;拿不到阶段的错误归到回合主体的代码生成段",即 TurnFailed { stage, .. } => *stage,其余全部 CodeGeneration。入队失败是 EnvironmentNotReady / HostStateUnavailable,本来就落在 CodeGeneration。这次重构只是把同一行为原样搬成 record_direct_codex_enqueue_failure 里的常量,没有改变结果。
    结论:评审"重构把 stage 记错了"的表述不成立。若你认为入队失败不该记成"代码生成段",那是新的语义需求(另立一个 enqueue / unknown 阶段值),可以单开一项——本轮按不改处理。

  • 1.(转后续 issue,本分支不做)AGC 的 tests/ 不在任何 tsconfig、也没有 type-aware lint,satisfies TurnFailure 这类 mock 类型断言是空写;
    需要单独立项:建 tsconfig.tests.json 把 tests 挂进类型门禁。实测并入后暴露 640 个既有错误,须按文件分批清零,清零前不能挂进默认 typecheck。

  • 2. apps/ai-game-creator-shell/tests/appSurface/home.suite.ts:2540(test · low)—— 入队被拒后没有任何"不重放"断言
    已修(6550e9a4b):恢复 persistedMessages 桩并让历史切片返回它;新增断言"被拒消息不落历史、不被乐观渲染、不写本地历史",
    并在 cleanup() + 重挂载后断言被拒消息与拒绝文案都不重放、也不自动重试。appSurface 全套 195 passed / 9 skipped。
    名词澄清:"被拒回合" = enqueue_direct_codex_turn 命令返回结构化 EnqueueError(本例 permissionRejected)被拒,
    消息没进待发队列、这一轮从未成立——就是 msg enqueue error,不是回合失败。
    更正 review 原始描述(已核对 git 历史,见 394b8de9d):原用例锁的其实不是"被拒回合不落盘",而是
    "已跑起来的回合以 unauthorized 失败后,用户消息 + 脱敏失败文案会落盘并在重挂载时重放"。

  • 3. apps/ai-game-creator-shell/src-tauri/src/agent/thread_manager/dispatch.rs:205(bug · low)—— 泄漏的控制流仍会被 Drop 兜底收成 HostDropped
    已修(84cbe9b1a、47da90ab8)。根因不是 dispatch,而是 direct_runtime 的返修循环:它只接住了 direct_delivery::review_reply
    返回的控制流,漏掉了 chat 调用自身返回的 TurnError::RepairRequired(app-server 封口复核,HostOutcome::RepairRequired)。
    这条控制流从 chat 的 Err 漏出循环 → dispatch 的 ShouldContinue => {} 静默吞掉 → TurnReservation::drop 兜成 HostDropped。
    修法:① 循环的流式 / 非流式两条分支都补上 Err(TurnError::RepairRequired { detail }),与 review_reply 的返修要求同一处理,写回 prompt 继续一轮;
    ② 删掉 finish_turn_failure,两处收口就地对 classify 显式分流:ShouldStop 写失败终态,ShouldContinue 直接 unreachable!——不再有静默接受控制流的出口。
    ③ 顺手清掉 turn_completion.rs::turn_terminal 里同一口径的两处 ShouldContinue 分支(debug_assert! + 回落 / host_dropped)→ unreachable!(47da90ab8)。
    说明:修复前控制流是真漏,用户会看到一条说不出原因的失败;修完后它已被返修循环与终态投影挡在门外,unreachable! 只是把不变式写死在收口处。
    验证:cargo test --bin genarrative-ai-game-creator-shell -- agent:: --skip export_bindings -- --test-threads=1 → 689 passed / 0 failed / 5 ignored。

  • 4. apps/ai-game-creator-shell/src-tauri/src/agent/thread_manager/turn_completion.rs:87(maintainability · low)—— 多余的 clone
    已修(bc115002d):(None, Err(error)) => Some(error)。collect_outcome 按值匹配,Err(error) 已经是 owned;
    host_failure 那支仍是 &TurnError,clone 保留。行为不变。

  • 5. scripts/check-generated-bindings.mjs:76-79(bug · high)—— 目录缺失时静默通过
    已修(7fb4dabb5):snapshot() 遇到目录不存在改为 throw new Error('生成绑定目录不存在,无法校验:<dir>')。
    原实现 console.warn + 返回空 Map:若 export_to 路径被改名、或生成文件被整体删除,前后快照都是空 Map,
    diff 报不出任何问题,这个 target 会以"0 个文件"通过,门禁形同虚设。

  • 6. scripts/check-generated-bindings.mjs:92(bug · medium)—— Windows 上无法执行 npx.cmd
    已修(3ab313a11):run() 的 spawnSync 增加 shell: process.platform === 'win32'(与 scripts/database-backup-to-oss.mjs、
    scripts/lint-staged-rustfmt.mjs 等既有写法一致)。不带 shell 时 Node 修完 CVE-2024-27980 直接 EINVAL,
    把命令换成 npx.cmd 只是把 ENOENT 换成了 EINVAL,prettier 这步在 Windows 上仍然失败。
    说明:Linux 侧已跑 check:generated-bindings 验证;Windows 路径本机实测不了,麻烦你在 Windows 上确认一次。

  • 7. scripts/check-generated-bindings.mjs:80(maintainability · low)—— readdirSync 的 recursive 依赖 Node 版本
    已修(6a4bcf4f3):改回普通 fs.readdirSync(absolute)。recursive 要 Node 18.17 / 20.1 才有,仓库没有 engines 约束,
    旧版本会静默退化成只读顶层、反而削弱覆盖;四个目标目录实测都是平铺的(无子目录、无深度 ≥2 的文件),去掉后行为不变。

  • 8. apps/ai-game-creator-shell/src/view/project-development/chat/conversation/directThreadSanitize.ts:434(security · medium)—— Bearer 引号值只遮到第一个空格
    已修(d07f894bf):\\.?["'] → \\?["'],与同文件 SENSITIVE_ASSIGNMENT 的写法一致。
    原写法要求引号前必须有一个字面反斜杠,Bearer "abc def" 落不到引号分支,退化到裸 token 分支后实测输出
    Bearer [redacted-secret] def"(即 def" 漏出)。已在 directThreadSanitize.test.ts 补用例:修复前失败,修复后 12/12 通过。

    你的决定(2026-10-03):wontfix(维持宿主侧不做条目脱敏的有意设计)。下面保留现状与风险备查。

  • 9. apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs:1184-1190(security · medium)—— 流式 delta 不再在宿主侧脱敏
    现状:这是本分支的有意设计(68cb41e33 / 549915a2b)——线程条目(流式 delta、direct_thread_event_item 的 arguments/result)
    由 Rust 原样透传,统一交给前端 directThreadSanitize;只有错误文案的脱敏保留在宿主侧(TurnError::classify 投影点)。
    问题:review 的风险成立——事件到达 webview 之前是明文,任何在 TS 脱敏之前读流的订阅方(dev 日志、持久化、错误捕获、其它窗口)
    都能看到 sk-... 与 tool-call 里的 token;渲染层一旦 XSS 也会暴露本来不必离开后端的数据。
    建议:你当时明确拍板"宿主侧不做条目脱敏",所以我没动。若要收口,最小折中是只在宿主侧保留凭据级脱敏
    (redact_secret_tokens,现在在 agent/generation/prompt_context.rs),不恢复整套路径/换行敏感的 sanitizer——
    既避开当初促使退役的那些 bug,又不让凭据明文跨进程边界。收哪些字段、要不要收由你定。

  • 10. apps/ai-game-creator-shell/src/view/project-development/chat/conversation/directTurnEnqueueFailure.ts:50-52(bug · medium)—— 真 Error 被包装后文案变成 {}
    已修(0a16d5d57):catch 里先 if (error instanceof Error) throw error; 再包装。
    JSON.stringify(new Error(...)) 得到 {}(message/stack 不可枚举),包进 EnqueueFailureWrapper 后控制器读 error.message
    只能拿到 {},横幅只剩 fallback。Tauri 的结构化拒绝是普通对象,不受影响;真 Error 交回控制器既有的 instanceof 分界,
    与类注释的口径一致。

  • 11. apps/ai-game-creator-shell/src/view/project-development/chat/conversation/directTurnEnqueueFailure.ts:75-78(bug · low)—— policyDetail / detail 直接 trim
    已修(2fe651c84):改用 ?.trim()。跨 IPC 载荷没有运行时校验,字段为 null/undefined 时原实现会在 runTurn 的 catch 里抛裸
    TypeError、草稿还被清掉;现在与空串同档:返回 null,按"没有提示"处理,保留草稿并走上报通道。

- [x] 1. `resourceReferences.ts` 残留 `DirectCodex` 函数名 —— 已修(cb13ecf87)。 当前实现:`chatComposerDraftToDirectCodexUserItem` / `directCodexUserItemFromContent` 已改名为 `chatComposerDraftToUserItem` / `userItemFromContent`,调用方同步。 说明:文件里 `DirectCodexLegacyContentDto`、`hasMeaningfulDirectCodexContent` 描述的是 legacy content 形态,不属于这次改名范围。 - [x] 2. `wire/turn.rs` 的 `status` 是自由字符串 —— 已实现(40b79bb1e)。 当前实现:字段类型改为 `TurnCompletedStatus`(`completed` / `interrupted` / `aborted` / `failed`,serde 序列化到同样的 JSON 字符串);`turn_completed` 的参数类型是只有三档的 `TurnOutcome`,函数内部 `Into` 成字段类型。 解决了什么:`turn_completed("failed".into(), at)` 这种"failed 但没有失败载荷"在**构造层**已经写不出来;`turn_completed_failed` 固定写 `Failed` + 载荷。 线上形状没变:JSON 仍是 `{"type":"turn.completed","status":"completed",...}`;TS 侧 `status` 从 `string` 收成 `'completed' | 'interrupted' | 'aborted' | 'failed'`(新增 `TurnCompletedStatus.ts`),前端 reducer 只读 `failure`,不受影响。 仍留的口子(评审提过、这次没动):`status: "failed"` 但没有 `failure` 的 JSON 仍能反序列化(wire/tests.rs 有用例专门盯这个宽松行为,是给重放兼容留的);把 `failure` 并进 `Failed` 变体做成"内部 tagged 的扁平枚举"能彻底关掉,但会改前端判别联合的形状,需要你点头再动。 - [x] 3. `wire/items.rs:426-432` 等上游字符串绕过限长/脱敏 —— 已修(fc10b2359)。 当前实现:`FunctionCall.name` → `detail_text`(脱敏 + 4000 字);`McpToolCall.tool` 由 `sanitize_detail_text` 改成 `detail_text`;`CommandExecution.status`、`McpToolCall.status`、`ThreadItem::Other.raw_type`、`ThreadFileChange.kind` 统一 `bounded(..., THREAD_DETAIL_MAX_CHARS)`。 说明:机器判别字段只限长、不脱敏改写,避免破坏枚举语义。 - [x] 4. `wire/items.rs:244` 空 `text` 短路 `content`/`summary` 兜底 —— 已修(25213c9ff)。 当前实现:先做"非空/非纯空白"过滤,再回落 `content`/`summary`。 问题(原):过滤在兜底之后,`"text": ""` 会产出 `Some("")` 短路兜底,末尾再返回 `None`,整条 message/reasoning 被丢掉。 - [x] 5. `detail_text` 先脱敏后截断的成本 —— 已答复,代码不动。 **结论:脱敏有两条链路,你问的那处只影响前端,不落盘。** · 前端这条:`wire/items.rs::detail_text` / `sanitize_detail_text` 只被 `thread_item_from_value` / `thread_items_from_history` 调用,产物只进下发给前端的 ThreadEvent 与历史切片,没有任何写盘操作。它的口径:项目内绝对路径换成项目相对路径(`<root>/game/src/x.ts` → `game/src/x.ts`),其余绝对路径 `<absolute-path>`、密钥 `[redacted-secret]`。 · 落盘这条:`runtime_state.rs::redact_agent_runtime_error`,负责 `.agent/runtime/errors/*.json`、`.agent/runtime/direct-codex-diagnostics/**/failure.json`、AppData 的 application.log,以及 `turn.completed.failure` 载荷里的 `detail` / `cause` / `diagnostic`。它的口径:项目根换成 `$PROJECT_ROOT`,另有 `<redacted-url>`。它同样在最后一步才截断(`truncate_agent_runtime_text`)。 · 所以"先脱敏后截断"两条都存在,理由也相同(先截断会把 token 切断、漏半个密钥);前端这条只是每次事件的 CPU 成本,没有磁盘副作用。真要治只能在**入口**限制原始 payload 大小,建议保持现状。 - [x] 6. `check-generated-bindings.mjs` Windows 上的 `npx` —— 已修(09f007d51):模块级 `npxCommand = win32 ? 'npx.cmd' : 'npx'`。 - [x] 7. `check-generated-bindings.mjs:114-115` 逐目标 `process.exit` —— 已修(80afa2a13):失败改为 `failed = true` + `continue`,跑完全部目标再统一 `exit 1`。 - [x] 8. `check-generated-bindings.mjs:52` hint 缺参数 —— 已修(bfbcb1dd0):shared-contracts 补 `--locked --manifest-path server-rs/Cargo.toml`,AGC 补 `--locked`。 - [x] 9. `check-generated-bindings.mjs:76-77` `snapshot()` 不递归 —— 已修(fff4e8ff6):`readdirSync(recursive)` + 文件过滤 + POSIX 键名。 - [x] 10. `codex_app_server/mod.rs:4265` 多余的一次 `String` 分配 —— 已修(e9ad1f3dd):改回 `write!(formatter, "{error}")`。 - [x] 11. `directTurnEnqueueFailure.ts:50` 无条件包 wrapper —— 按你的结论**不改**:信任 Tauri,`invoke` 的 rejection 形状就是 `EnqueueError`。 评审提的两个反例(`JSON.stringify(new Error('boom')) === '{}'`、半结构化 payload 让 `.trim()` 再抛)都建立在"rejection 可能是任意 JS 值"上;在这个前提下不成立。若哪天发现不成立,再回来加形状判据。 - [x] 12. `directTurnFailure.ts:167` 缺 `kind` 时抛错 —— 已修(1ba9ac848):`directModelCallText` 参数放宽为 `ModelCallKind | null | undefined`,缺分类时回落通用句。 - [x] 13. `dispatch.rs` 的 `Option<TurnFailure>` —— 已改(11e98a3ca)。 当前实现:`TurnError::terminal_failure -> Option<TurnFailure>` 换成 `TurnError::classify -> TurnErrorClassified{ShouldStop(TurnFailure), ShouldContinue { detail }}`。 `dispatch.rs` 两处 `.expect("回合失败必可投影成失败载荷")` 改成按分类 match:`ShouldStop` 写终态, `ShouldContinue` 不写终态、不伪造失败;`turn_terminal` 里 `ShouldContinue` 显式认账(debug 构建 断言,release 落到账本正常终态)。 说明:控制流不再借 `None` 表达"要继续跑";`TurnFailure` 上加了 `// TODO badnaming`。 - [x] 14. `check-generated-bindings.mjs` `run()` 吞掉 spawn 失败原因 —— 已修(6f2eed0dc):`result.error` 时打印 `无法启动 <命令>:<原因>`。 - [x] 15. `check-generated-bindings.mjs:73` 目录不存在静默通过 —— 已修(f593bd4bf):目录不存在时 `console.warn`。 - [x] 16. `direct_runtime/mod.rs:7310` 断言 `direct-codex-failure:v2` —— 已在更早的提交(9a75ea57f)修掉,当前 HEAD 不成立。 当前实现:该用例断言结构化 sidecar 的 `schemaVersion` / `source` / `stage` / `code` / `clientTurnId` 与脱敏结果;`direct-codex-failure:v2 ...` 只在 `record_direct_codex_failure` 的返回值和错误上报池上断言。 - [x] 17. `direct_runtime/mod.rs:2017` `ModelCallFailed` 走文本分类 —— 已实现(2dbbf7245)。 当前实现(原):`direct_codex_error_code` 对 `ModelCallFailed` 一律拿宿主原文跑子串分类。结果是 `ModelCallKind::TransportBroken`(来自 `LlmError::Transport`)只有在原文恰好含 `transport closed` / `连接已关闭` 时才会被标成 `transport-closed`;宿主换一句 Display 就会掉进 `runtime-unclassified`,同样的失败给出不同的诊断 code。 改成:先看 `payload.kind`,`TransportBroken` 直接给 `transport-closed`;其余 kind 还没有专属 code,仍按原文兜底(保留 `TODO(文本判据)`)。 新增用例 `model_call_transport_broken_is_classified_from_the_typed_kind`:原文不含任何分类标记,也只可能由 typed kind 得出 `transport-closed`。 - [x] 18. `turn_completion.rs` 的 `and_then` 与 `SessionOutcome` —— 已改(11e98a3ca)。 当前实现:删掉 private `SessionOutcome`,收尾 status → 终态的解析并成 `session_completion(status) -> Option<TurnCompletion>`;`turn_terminal` 不再用 `and_then` 压平: 先按 `host_failure > collect_outcome > 账本 failed 兜底` 选出错误,再走 `classify`,`ShouldStop` 直接成失败终态,`ShouldContinue` 不伪造失败。 说明:`TurnCompletion` 仍是"这一轮怎么收场"的唯一定义,少了一个同义类型。 - [x] 19. ThreadManager 脱敏 / 限长搬前端 —— 已改(68cb41e33 Rust 侧,549915a2b 前端侧)。 · Rust 侧:删掉 `wire/items.rs` 的 `bounded` / `detail_text` / `sanitize_detail_text` / `relativize_project_root_paths` / `thread_delta_text` 与 `THREAD_TEXT_MAX_CHARS` / `THREAD_DETAIL_MAX_CHARS` / `THREAD_PATH_MAX_CHARS`;`thread_item_from_value` / `thread_items_from_history` / 运行态投影 / 流式增量签名去掉 root,字段原样透传。`dispatch.rs` 写历史失败不再预脱敏——错误文案的脱敏保留在 `classify` 投影成失败载荷那一步(脱敏 + 截断)。 · 前端侧:新增 `directThreadSanitize`,在 bootstrap / consume / 历史切片进入聊天状态之前统一过 一遍;用 hook 已有的 `projectPath` 把项目内绝对路径归一成相对路径,其余绝对路径 / 密钥 / 敏感赋值 / Bearer / 配置文件 / 私钥块 / 敏感开关换成占位符。占位符沿用 `<absolute-path>` / `[redacted-secret]` / `[redacted-sensitive-field]` / `[redacted-config]` / `[redacted sensitive context]`,`model.ts` 的中文显示映射不用改;不做任何截断。配 12 条单测。 · 说明:ThreadManager 每条线程的 8 MiB / 8192 事件订阅缓冲上限**没动**——那是订阅背压,不是 字段限长;字段限长已按"no bounded at all"全删。 - [x] 20. `directTurnFailure.ts:175` 未知 `stage` 渲染 `undefined` —— 已修(adcf3b973):查不到标签时回落"执行失败,请稍后重试"。(原评审的 patch 用的是旧分支名 `turnFailed`,现名是 `superErrorFromStringPlusStage`。) - [x] 21. `direct_runtime/mod.rs` 入队失败硬写 `stage=code-generation` —— 已答复:**这是重构前就有的行为,不是本次引入。** 证据:重构前的 `direct_turn_error.rs` 里,`turn_failure_stage()` 的注释与实现是"已记录失败的诊断阶段;拿不到阶段的错误归到回合主体的代码生成段",即 `TurnFailed { stage, .. } => *stage`,其余全部 `CodeGeneration`。入队失败是 `EnvironmentNotReady` / `HostStateUnavailable`,本来就落在 `CodeGeneration`。这次重构只是把同一行为原样搬成 `record_direct_codex_enqueue_failure` 里的常量,没有改变结果。 结论:评审"重构把 stage 记错了"的表述不成立。若你认为入队失败不该记成"代码生成段",那是新的语义需求(另立一个 enqueue / unknown 阶段值),可以单开一项——本轮按不改处理。 - [x] 1.(转后续 issue,本分支不做)AGC 的 `tests/` 不在任何 tsconfig、也没有 type-aware lint,`satisfies TurnFailure` 这类 mock 类型断言是空写; 需要单独立项:建 `tsconfig.tests.json` 把 tests 挂进类型门禁。实测并入后暴露 **640 个既有错误**,须按文件分批清零,清零前不能挂进默认 `typecheck`。 - [x] 2. apps/ai-game-creator-shell/tests/appSurface/home.suite.ts:2540(test · low)—— 入队被拒后没有任何"不重放"断言 已修(6550e9a4b):恢复 `persistedMessages` 桩并让历史切片返回它;新增断言"被拒消息不落历史、不被乐观渲染、不写本地历史", 并在 `cleanup()` + 重挂载后断言被拒消息与拒绝文案都不重放、也不自动重试。appSurface 全套 195 passed / 9 skipped。 名词澄清:"被拒回合" = `enqueue_direct_codex_turn` 命令返回结构化 `EnqueueError`(本例 `permissionRejected`)被拒, 消息没进待发队列、这一轮从未成立——就是 msg enqueue error,不是回合失败。 更正 review 原始描述(已核对 git 历史,见 394b8de9d):原用例锁的其实不是"被拒回合不落盘",而是 "已跑起来的回合以 `unauthorized` 失败后,用户消息 + 脱敏失败文案会落盘并在重挂载时重放"。 - [x] 3. apps/ai-game-creator-shell/src-tauri/src/agent/thread_manager/dispatch.rs:205(bug · low)—— 泄漏的控制流仍会被 Drop 兜底收成 HostDropped 已修(84cbe9b1a、47da90ab8)。根因不是 dispatch,而是 direct_runtime 的返修循环:它只接住了 `direct_delivery::review_reply` 返回的控制流,漏掉了 chat 调用自身返回的 `TurnError::RepairRequired`(app-server 封口复核,`HostOutcome::RepairRequired`)。 这条控制流从 chat 的 `Err` 漏出循环 → dispatch 的 `ShouldContinue => {}` 静默吞掉 → `TurnReservation::drop` 兜成 `HostDropped`。 修法:① 循环的流式 / 非流式两条分支都补上 `Err(TurnError::RepairRequired { detail })`,与 review_reply 的返修要求同一处理,写回 prompt 继续一轮; ② 删掉 `finish_turn_failure`,两处收口就地对 `classify` 显式分流:`ShouldStop` 写失败终态,`ShouldContinue` 直接 `unreachable!`——不再有静默接受控制流的出口。 ③ 顺手清掉 `turn_completion.rs::turn_terminal` 里同一口径的两处 ShouldContinue 分支(`debug_assert!` + 回落 / `host_dropped`)→ `unreachable!`(47da90ab8)。 说明:修复前控制流是**真漏**,用户会看到一条说不出原因的失败;修完后它已被返修循环与终态投影挡在门外,`unreachable!` 只是把不变式写死在收口处。 验证:`cargo test --bin genarrative-ai-game-creator-shell -- agent:: --skip export_bindings -- --test-threads=1` → 689 passed / 0 failed / 5 ignored。 - [x] 4. apps/ai-game-creator-shell/src-tauri/src/agent/thread_manager/turn_completion.rs:87(maintainability · low)—— 多余的 clone 已修(bc115002d):`(None, Err(error)) => Some(error)`。`collect_outcome` 按值匹配,`Err(error)` 已经是 owned; `host_failure` 那支仍是 `&TurnError`,clone 保留。行为不变。 - [x] 5. scripts/check-generated-bindings.mjs:76-79(bug · high)—— 目录缺失时静默通过 已修(7fb4dabb5):`snapshot()` 遇到目录不存在改为 `throw new Error('生成绑定目录不存在,无法校验:<dir>')`。 原实现 `console.warn` + 返回空 Map:若 `export_to` 路径被改名、或生成文件被整体删除,前后快照都是空 Map, diff 报不出任何问题,这个 target 会以"0 个文件"通过,门禁形同虚设。 - [x] 6. scripts/check-generated-bindings.mjs:92(bug · medium)—— Windows 上无法执行 npx.cmd 已修(3ab313a11):`run()` 的 `spawnSync` 增加 `shell: process.platform === 'win32'`(与 scripts/database-backup-to-oss.mjs、 scripts/lint-staged-rustfmt.mjs 等既有写法一致)。不带 shell 时 Node 修完 CVE-2024-27980 直接 EINVAL, 把命令换成 npx.cmd 只是把 ENOENT 换成了 EINVAL,prettier 这步在 Windows 上仍然失败。 说明:Linux 侧已跑 check:generated-bindings 验证;Windows 路径本机实测不了,麻烦你在 Windows 上确认一次。 - [x] 7. scripts/check-generated-bindings.mjs:80(maintainability · low)—— readdirSync 的 recursive 依赖 Node 版本 已修(6a4bcf4f3):改回普通 `fs.readdirSync(absolute)`。`recursive` 要 Node 18.17 / 20.1 才有,仓库没有 engines 约束, 旧版本会静默退化成只读顶层、反而削弱覆盖;四个目标目录实测都是平铺的(无子目录、无深度 ≥2 的文件),去掉后行为不变。 - [x] 8. apps/ai-game-creator-shell/src/view/project-development/chat/conversation/directThreadSanitize.ts:434(security · medium)—— Bearer 引号值只遮到第一个空格 已修(d07f894bf):`\\.?["']` → `\\?["']`,与同文件 SENSITIVE_ASSIGNMENT 的写法一致。 原写法要求引号前必须有一个字面反斜杠,`Bearer "abc def"` 落不到引号分支,退化到裸 token 分支后实测输出 `Bearer [redacted-secret] def"`(即 `def"` 漏出)。已在 directThreadSanitize.test.ts 补用例:修复前失败,修复后 12/12 通过。 你的决定(2026-10-03):**wontfix**(维持宿主侧不做条目脱敏的有意设计)。下面保留现状与风险备查。 - [x] 9. apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs:1184-1190(security · medium)—— 流式 delta 不再在宿主侧脱敏 现状:这是本分支的有意设计(`68cb41e33` / `549915a2b`)——线程条目(流式 delta、`direct_thread_event_item` 的 arguments/result) 由 Rust 原样透传,统一交给前端 `directThreadSanitize`;只有错误文案的脱敏保留在宿主侧(`TurnError::classify` 投影点)。 问题:review 的风险成立——事件到达 webview 之前是明文,任何在 TS 脱敏之前读流的订阅方(dev 日志、持久化、错误捕获、其它窗口) 都能看到 `sk-...` 与 tool-call 里的 token;渲染层一旦 XSS 也会暴露本来不必离开后端的数据。 建议:你当时明确拍板"宿主侧不做条目脱敏",所以我没动。若要收口,最小折中是只在宿主侧保留凭据级脱敏 (`redact_secret_tokens`,现在在 agent/generation/prompt_context.rs),不恢复整套路径/换行敏感的 sanitizer—— 既避开当初促使退役的那些 bug,又不让凭据明文跨进程边界。收哪些字段、要不要收由你定。 - [x] 10. apps/ai-game-creator-shell/src/view/project-development/chat/conversation/directTurnEnqueueFailure.ts:50-52(bug · medium)—— 真 Error 被包装后文案变成 {} 已修(0a16d5d57):catch 里先 `if (error instanceof Error) throw error;` 再包装。 `JSON.stringify(new Error(...))` 得到 `{}`(message/stack 不可枚举),包进 `EnqueueFailureWrapper` 后控制器读 `error.message` 只能拿到 `{}`,横幅只剩 fallback。Tauri 的结构化拒绝是普通对象,不受影响;真 Error 交回控制器既有的 `instanceof` 分界, 与类注释的口径一致。 - [x] 11. apps/ai-game-creator-shell/src/view/project-development/chat/conversation/directTurnEnqueueFailure.ts:75-78(bug · low)—— policyDetail / detail 直接 trim 已修(2fe651c84):改用 `?.trim()`。跨 IPC 载荷没有运行时校验,字段为 null/undefined 时原实现会在 runTurn 的 catch 里抛裸 TypeError、草稿还被清掉;现在与空串同档:返回 null,按"没有提示"处理,保留草稿并走上报通道。
k88936 added 19 commits 2026-10-03 14:50:37 +08:00
- TurnError::terminal_failure 的 Option<TurnFailure> 换成 TurnError::classify -> TurnErrorClassified{ShouldStop, ShouldContinue},控制流不再借 None 表达
- dispatch.rs 两处 .expect("回合失败必可投影成失败载荷") 改为按分类层 match,收到 ShouldContinue 不写终态、不伪造失败
- turn_terminal 不再用 and_then 压平;ShouldContinue 显式认账并落到账本正常终态
- 并掉 private SessionOutcome,收尾 status 解析直接产出 TurnCompletion
- TurnFailure 补 // TODO badnaming,同步更新相关文档注释
- 删除 wire/items.rs 的 bounded / detail_text / sanitize_detail_text / relativize_project_root_paths / thread_delta_text 与三个字符上限常量
- thread_item_from_value / thread_items_from_history / 运行态条目 / 流式增量签名去掉 root,字段原样透传,脱敏交给事件进前端状态时统一做
- dispatch.rs 写历史失败不再预脱敏,原文交给 classify 投影成失败载荷时统一脱敏 + 截断
- 用例改为"原样透传"口径,删除已无对象的流式脱敏用例
- 失败载荷(错误文案)的脱敏保留
- ThreadItem 注记改为"原样投影、脱敏在前端"
- TurnFailure 注记改为 TurnError::classify / TurnErrorClassified::ShouldContinue
- 新增 directThreadSanitize:项目内路径归一成相对路径,绝对路径 / 密钥 / 敏感赋值 / Bearer / 配置文件 / 私钥块 / 敏感开关统一换成占位符,只遮挡不截断
- 订阅 hook 在 bootstrap / consume / 历史切片进入聊天状态之前统一过脱敏,复用已有的 projectPath
- 新增 12 条单测:项目内外路径、密钥前缀与 JWT、Bearer、.env、私钥块、多行换行保留、条目与增量事件
- ADR 与 decision-log 里的 TurnError::terminal_failure 改成 TurnError::classify
- pitfalls 的「逐 delta 脱敏吃掉段尾换行」补一条现状:Rust 逐 delta 脱敏已退役,脱敏搬到前端 directThreadSanitize
- decision-log 增加 2026-10-03 条目:TurnError::classify / TurnErrorClassified 取代 terminal_failure
- 记录 ThreadManager 不再做字段级脱敏与限长、脱敏搬到前端 directThreadSanitize,失败载荷脱敏保留在 classify
- 解决 direct_runtime/mod.rs 冲突:保留本分支 typed 回合错误重构(TurnError/EnqueueError 与 record_direct_codex_failure_facts 拆分),接入 master 的诊断 v3 口径(persist_agent_runtime_error 新签名 + typed error 字段)、prompt 反馈改截断不脱敏、项目写锁改名
- 解决 thread_manager/dispatch.rs 冲突:保留本分支 TurnError/TurnCompletion 换代与 finish_turn_failure 分类层,接住 master 的 tt= 日志改名与 cc 执行器整轮成功后补写 completed 终态
- 解决 direct_tools_mcp.rs 冲突:采用 master 版(本分支唯一改动是 external MCP 容量测试读 body,master 已有等价实现)
- 处理 direct_turn_failure.rs 删除/修改冲突:维持本分支删除,把 completed() 语义迁进 TurnCompletion::Completed 并补回对应测试
- 处理 runtime_driver/entrypoints.rs 修改/删除冲突:接受 master 退役自建 Agent Runtime 的删除(本分支只在该文件做 direct_now_ms 改名)
- 合并 decision-log.md:双方条目都保留
- 修复 auto-merge 漏掉的语义冲突:claude_code_cli.rs 的 direct_now_ms() 改名 now_ms();诊断测试改为断言 AGENT_RUNTIME_ERROR_SCHEMA_VERSION、detail 字段改 message;record_direct_codex_failure_facts 新增 typed_error 入参(TurnError 不序列化,落盘用 classify 投影出的 TurnFailure)
- turn_completion.rs:collect_outcome 按值匹配,Err(error) 已是 owned,Some(error.clone()) 改为 Some(error),省掉一次整份错误载荷的克隆
- 行为不变:host_failure 那一支仍是 &TurnError,需要 clone 的保留
- directTurnEnqueueFailure.ts:permissionRejected.policyDetail / inputRejected.detail 改用可选链再 trim
- 跨 IPC 载荷没有运行时校验,字段理论上可能是 null/undefined;原实现会在 runTurn 的 catch 里抛错,用户看到的是裸 TypeError、草稿还被清掉
- 缺失字段现在与空串同档:返回 null,按"没有提示"处理,保留草稿并走上报通道
- directTurnEnqueueFailure.ts:invokeTurnEnqueue 的 catch 先判断 error instanceof Error,是则原样抛出
- JSON.stringify(new Error(...)) 得到 {}(message/stack 不可枚举),包进 EnqueueFailureWrapper 后控制器读 error.message 只能拿到 {},真实失败文案丢失
- 结构化拒绝是普通对象,不受影响;真 Error 交回调用方既有的 instanceof 分界,与类注释口径一致
- directThreadSanitize.ts:BEARER_VALUE 的引号分支由 \\.?["'] 改为 \\?["'],与同文件 SENSITIVE_ASSIGNMENT 的写法一致
- 原写法要求引号前必须有一个字面反斜杠,导致 Bearer "abc def" 走不到引号分支,退化到裸 token 分支后只遮到第一个空格,示例会输出 Bearer [redacted-secret] def"
- 修正后引号内的整段值都被遮掉;JSON 转义的 \"abc def\" 仍然匹配
- 同步在 directThreadSanitize.test.ts 增加带引号 Bearer 的用例(修复前该用例失败)
- check-generated-bindings.mjs:snapshot() 遇到目标目录不存在时由 console.warn + 返回空 Map 改为抛出错误
- 旧实现下,若 export_to 路径被改名或生成文件被整体删除,前后快照都是空 Map,diff 报不出任何问题,校验以"0 个文件"静默通过
- 现在目录缺失会立即失败并指出是哪个目录,不再给假绿
- check-generated-bindings.mjs:snapshot() 改用普通 fs.readdirSync(absolute)
- recursive 选项要 Node 18.17 / 20.1 才有;仓库没有 engines 约束,旧版本会静默退化成只读顶层,反而削弱嵌套目录的覆盖
- 四个生成目录都是平铺的(ts-rs 的 export_to 直接落文件),去掉后行为不变,已跑 check:generated-bindings 验证
- check-generated-bindings.mjs:run() 的 spawnSync 增加 shell: process.platform === 'win32'
- Windows 上不带 shell 无法执行 .cmd/.bat(Node 修 CVE-2024-27980 后报 EINVAL),npxCommand 特意换成 npx.cmd 只把 ENOENT 换成了 EINVAL,prettier 这一步仍然跑不起来
- 与 scripts/database-backup-to-oss.mjs、lint-staged-rustfmt.mjs 等既有脚本的写法保持一致
- Linux 侧已跑 check:generated-bindings 验证;Windows 路径本机无法实测
- home.suite.ts 的入队被拒用例恢复 persistedMessages 桩,并让历史切片返回它
- 增加断言:被拒消息不落历史条目、不被乐观渲染成用户气泡、不写本地历史
- 增加 cleanup + 重挂载断言:重进项目不重放被拒消息与拒绝文案,且不自动重试
- direct_runtime 返修循环接住 chat 侧返回的 TurnError::RepairRequired(app-server 封口复核),写回 prompt 继续一轮,流式与非流式两条分支都补上
- 删除 thread_manager/dispatch.rs 的 finish_turn_failure:它的 ShouldContinue 静默分支正是控制流漏到收口后、被 Drop 兜成 HostDropped 的入口
- 两处回合失败收口改为就地对 classify 结果显式分流:ShouldStop 写失败终态,ShouldContinue 视为上游缺陷直接 unreachable
- turn_completion.rs 的 turn_terminal 两处 ShouldContinue 分支由 debug_assert + 回落账本终态 / host_dropped 改为 unreachable:控制流在返修循环与终态投影处已被过滤
- 同步收紧 turn_terminal 的文档说明,明确控制流不该也不能进入终态判定
Merge remote-tracking branch 'origin/master' into refactor/turn-error-to-explicit-struct
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 4m0s
Project CI / AI game creator shell Rust crates (pull_request) Failing after 2m45s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 4m52s
Project CI / Frontend tests (pull_request) Successful in 2m30s
Project CI / Backend tests (pull_request) Successful in 4m54s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m0s
Project CI / Native shell tests (pull_request) Successful in 7m38s
Project CI / Repository checks (pull_request) Successful in 6m34s
141e59c971
k88936 added 1 commit 2026-10-03 15:45:43 +08:00
修复:生成绑定校验不再用 prettier 改写生成文件
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m54s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 4m36s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 4m44s
Project CI / Backend tests (pull_request) Successful in 5m46s
Project CI / Frontend tests (pull_request) Successful in 2m50s
Project CI / AI game creator shell web tests (pull_request) Successful in 2m41s
Project CI / Native shell tests (pull_request) Successful in 6m39s
Project CI / Repository checks (pull_request) Successful in 6m12s
0e313405d6
- scripts/check-generated-bindings.mjs:删掉生成后的 prettier 归一化,直接逐字节比较 ts-rs 原始输出
- .prettierignore 与 .eslintrc.cjs:四个 ts-rs 生成目录统一排除,禁止格式化工具二次改写
- .prettierrc.json:删除 contracts/generated 的 printWidth / singleQuote 覆盖
- .gitattributes:生成目录标 linguist-generated 并豁免 trailing-space,保证 git diff --check 通过
- 重新生成 AGC chat/generated 与 services/generated 的 51 个绑定,改为提交 ts-rs 原始输出(纯格式,语义不变)
- docs/project-memory:记录生成绑定不再经 prettier 的口径,修正旧流程说明
k88936 merged commit 633355952b into master 2026-10-03 15:58:25 +08:00
k88936 deleted branch refactor/turn-error-to-explicit-struct 2026-10-03 15:58:26 +08:00
Sign in to join this conversation.