修复 AGC 最近项目「检查失败」被钉死:失败退避重试 + 按轮重查(#490) #492

Merged
suzmii merged 10 commits from fix/agc-recent-project-status-retry into master 2026-09-23 17:30:59 +08:00
Member

关联 issue:#490

问题

AGC 卡住一次之后,项目列表所有行变成「检查失败 + 待识别」,首页「最近项目」变成「暂无最近项目」,后端恢复也不会自愈,只能重启客户端。

根因

前端把一次性的目录检查失败当成了终态,且没有任何重试入口:

  1. useRecentProjects.ts 每个最近项目的检查只有 5s 预算,失败被 catch 吞成 null
  2. 增量投影里 next[workspace] = current[workspace] ?? null 把上一轮的 null 原样搬进新一轮
  3. model.tsnull 渲染成「检查失败」,projectKind 落成 unknown → 「待识别」。
  4. 首页只收 canOpen 为真的行,而 canOpen 依赖 Boolean(directoryStatus) → 同一批 null 让首页变空态。
  5. effect 只依赖 [recentWorkspaces, recentWorkspaceRefreshKey],而 refreshKey 只有「打开/新建项目」才会 +1;没有重试、没有 focus 重查。

⇒ 一次 5s 量级的抖动就能把整个会话的列表与首页钉死。

现场证据(2026-09-23 本机 dev 客户端):11:24:12 两个平台接口 15s 超时、11:24:26 关闭项目回启动页后 Rust 日志静默 32 分钟;11:56 在同一进程里逐条复跑 inspect_local_project_directory(8 个项目)全部 OK、0ms,而界面仍是全「检查失败」。

改动前后对照

场景 改前 改后
单次目录检查失败 立刻落成「检查失败」,本会话不再重查 先就地重试一次(300ms);成功则整行直接正常
持续失败 「检查失败」永久保留,后续刷新把 null 原样搬过去 失败项不进新投影:刷新时回到「检查中」并重新检查
一轮结束仍有可重试失败 无任何后续动作 按 15s / 45s / 120s 退避重跑整张列表(最多 3 次),失败集合变化或整轮无失败即重置预算
重命名后的单条刷新 单次检查,失败即终态 复用同一套就地重试 + 有界重查
提权/权限类失败 与瞬时抖动同样对待,300ms 后再试一次 判定为不可重试:不重试、不驱动整表重查,在用户主动打开/新建或重命名刷新前跳过
已有正常行 保留 保留(增量投影只保留成功结果)
invoke 类型 3 处 NonNullable<ReturnType<typeof resolveTauriInvoke>> 复用 app/types.ts 既有 TauriInvoke,不再新增同名类型

不改的东西:5s 单次超时口径、Rust 侧命令、行状态文案、公开契约与持久化格式。

验证证据

  • npx vitest run apps/ai-game-creator-shell/tests/recentProjectsHook.test.tsx:4 passed(新增 3 个用例)
    • 「单次失败就地重试」「提权类失败不重试」在改前代码上必挂git stash 对照:1 failed)。
  • npx vitest run apps/ai-game-creator-shell/tests/appSurface.test.ts apps/admin-web/src/pages/AdminAgcTemplatesPage.test.tsx apps/ai-game-creator-shell/tests/recentProjectsModel.test.ts:合计 233 passed / 9 skipped(home.suite 里「检查失败」断言改为等待最终状态,因为失败状态现在要等重试结束才落成)。
  • 退避重查路径:一次性脚本(已删除,不入库)证明持续失败后 15.0s 自动重查并恢复
  • npm run agc:typechecknpm run check:encodinggit diff --checkeslintprettier --check 全部通过。
  • 文档同步:decision-log.md(新增 2026-09-23 条目 + 修正 09-20 条目措辞)、pitfalls.md(新增条目)、【技术方案】AGC客户端稳定版生命周期大切换-2026-09-14.md【技术方案】AGC异步操作可恢复闭环-2026-09-14.md(行为契约 + 验收矩阵)。

4 片评审发现与处置

本 PR 用 4 个只读 agent 分片评审(合并完整性 / 主逻辑 / 测试与文档 / 资源与安全),结论与处置:

严重度 发现 处置
提权被拒会被自动重试,等于自己驱动 UAC:前端只按「失败」重试且吞掉错误文本;提权闸门 attempted_targets 是每次 invoke 新建的局部 Vec(config.rs:2330-2352),Start-Process -Verb RunAs -Wait 无超时(config.rs:2824),Managed 作用域无需 file-picker 授权(config.rs:910-1041)→ 用户点「否」(exit 1223)正好命中重试条件。最坏 4 轮 × 8 项目 × 2 次 = 64 次 invoke(改前 8 次) 已修:按 config.rs 同口径判据区分不可重试失败并跳过
重命名路径 refreshRecentWorkspace 不重试、不排重查,该行仍会被钉成终态 已修
退避预算挂在会话上,长期坏目录吃满 3 次额度后新的瞬时失败也不再自愈 已修(预算绑定失败集合)
docs/共享记忆未同步(仓库规范要求) 已修
新增的 TauriInvokeapp/types.ts:1052 既有同名类型重复,注释却写「唯一入口类型」 已修
「不沿用失败结果」用例标题与断言不符(改前也能通过) 已修(改为断言重查在途时该行是「检查中」)
无发现 合并完整性:无回退、无冲突残留、chat/generated/* 与 master 一致、3 个 Rust 测试文件逐字节等于 master、platform-agent/Cargo.lock 未入库

未做(记录待评):「Rust 侧 per-target 提权冷却」是更根本的闸门(进程级记忆,避免任何调用方重复触发提权),但要改 ACL/提权行为,需单独评审 + 真机验证,不塞进本 PR。

CI 过程与当前状态

  1. run 2807(首个提交 04b738755):7 个 job 成功;Backend testsRepository checks 因 base-commit gate(pull request head does not contain the latest base commit)失败——是分支基线落后 master,与本 PR 改动无关。
  2. run 2808(首次合入 master 后):AI game creator shell Rust lane 1/2lane 2/2 变红。核对确认是 master 自身的问题(run 2806 / 2809 同样只有这两条红),首个红 commit = f789db7f2(#491 移除提示词里的 runId/instanceId/sessionId 时漏同步 6 处断言)。
  3. run 2811(推了断言同步 25d331ca0):两条 Rust lane 转绿;同一 base-commit gate 再次把 Repository checks / Backend tests 判红。
  4. 随后发现 master 已用 #494(6b4c6b8a6 修了同一批断言(把 contains(run_id) 换成「请求包含该 run 的任务文本」)→ 冲突按 master 版本解决,25d331ca0 的内容由合并结果覆盖,不重复引入。
  5. run 2819:全绿。此后 master 又多次前进(016356e5f42694470e8d987c205bc5328c0…),每次推送前都重新合入,当前 head 已包含最新 master。
  6. 本轮评审收口后再次合入 master 并推送(b48953f87)。

未覆盖 / 风险

  • 「AGC 卡住」本身的停顿层(远端网络 / Rust IPC 回传 / WebView 主线程)尚未定位,本 PR 只保证一次抖动不再永久污染界面;采证探针(invoke 拦截 + IPC 心跳 + 界面状态计数)在 issue #490 里继续跟踪。
  • 提权类失败目前只跳过、不显示专门状态词:这类项目在用户主动操作前停留在「检查失败」。是否新增「需要提权」状态与一键重试入口,值得单独决策。
  • 与本次无关但已在 master 上出现的红(供参考):master 的 Rust lane 2/2 偶发 command_exec::tests::project_command_runs_project_build_and_git_read_inside_sandbox 失败(Linux 沙箱进程组回收,间歇性);本地 Windows 上 response_stream::provider_retry_waiting_final_reply_compaction_resumes_without_new_tool_planresponse_stream.rs:3419-3421 的 100ms 负向窗口断言,2026-07-21 引入)稳定失败、Linux CI 通过。
关联 issue:#490 ## 问题 AGC 卡住一次之后,项目列表所有行变成「检查失败 + 待识别」,首页「最近项目」变成「暂无最近项目」,**后端恢复也不会自愈**,只能重启客户端。 ## 根因 前端把一次性的目录检查失败当成了终态,且没有任何重试入口: 1. `useRecentProjects.ts` 每个最近项目的检查只有 5s 预算,失败被 `catch` 吞成 `null`。 2. 增量投影里 `next[workspace] = current[workspace] ?? null` 把上一轮的 `null` **原样搬进新一轮**。 3. `model.ts` 把 `null` 渲染成「检查失败」,`projectKind` 落成 `unknown` → 「待识别」。 4. 首页只收 `canOpen` 为真的行,而 `canOpen` 依赖 `Boolean(directoryStatus)` → 同一批 `null` 让首页变空态。 5. effect 只依赖 `[recentWorkspaces, recentWorkspaceRefreshKey]`,而 `refreshKey` 只有「打开/新建项目」才会 +1;没有重试、没有 focus 重查。 ⇒ 一次 5s 量级的抖动就能把整个会话的列表与首页钉死。 现场证据(2026-09-23 本机 dev 客户端):11:24:12 两个平台接口 15s 超时、11:24:26 关闭项目回启动页后 Rust 日志静默 32 分钟;11:56 在同一进程里逐条复跑 `inspect_local_project_directory`(8 个项目)**全部 OK、0ms**,而界面仍是全「检查失败」。 ## 改动前后对照 | 场景 | 改前 | 改后 | | --- | --- | --- | | 单次目录检查失败 | 立刻落成「检查失败」,本会话不再重查 | 先就地重试一次(300ms);成功则整行直接正常 | | 持续失败 | 「检查失败」永久保留,后续刷新把 `null` 原样搬过去 | 失败项不进新投影:刷新时回到「检查中」并重新检查 | | 一轮结束仍有可重试失败 | 无任何后续动作 | 按 15s / 45s / 120s 退避重跑整张列表(最多 3 次),失败集合变化或整轮无失败即重置预算 | | 重命名后的单条刷新 | 单次检查,失败即终态 | 复用同一套就地重试 + 有界重查 | | 提权/权限类失败 | 与瞬时抖动同样对待,300ms 后再试一次 | 判定为不可重试:不重试、不驱动整表重查,在用户主动打开/新建或重命名刷新前跳过 | | 已有正常行 | 保留 | 保留(增量投影只保留成功结果) | | `invoke` 类型 | 3 处 `NonNullable<ReturnType<typeof resolveTauriInvoke>>` | 复用 `app/types.ts` 既有 `TauriInvoke`,不再新增同名类型 | 不改的东西:5s 单次超时口径、Rust 侧命令、行状态文案、公开契约与持久化格式。 ## 验证证据 - `npx vitest run apps/ai-game-creator-shell/tests/recentProjectsHook.test.tsx`:4 passed(新增 3 个用例) - 「单次失败就地重试」「提权类失败不重试」在**改前代码上必挂**(`git stash` 对照:`1 failed`)。 - `npx vitest run apps/ai-game-creator-shell/tests/appSurface.test.ts apps/admin-web/src/pages/AdminAgcTemplatesPage.test.tsx apps/ai-game-creator-shell/tests/recentProjectsModel.test.ts`:合计 233 passed / 9 skipped(`home.suite` 里「检查失败」断言改为等待最终状态,因为失败状态现在要等重试结束才落成)。 - 退避重查路径:一次性脚本(已删除,不入库)证明持续失败后 **15.0s 自动重查并恢复**。 - `npm run agc:typecheck`、`npm run check:encoding`、`git diff --check`、`eslint`、`prettier --check` 全部通过。 - 文档同步:`decision-log.md`(新增 2026-09-23 条目 + 修正 09-20 条目措辞)、`pitfalls.md`(新增条目)、`【技术方案】AGC客户端稳定版生命周期大切换-2026-09-14.md`、`【技术方案】AGC异步操作可恢复闭环-2026-09-14.md`(行为契约 + 验收矩阵)。 ## 4 片评审发现与处置 本 PR 用 4 个只读 agent 分片评审(合并完整性 / 主逻辑 / 测试与文档 / 资源与安全),结论与处置: | 严重度 | 发现 | 处置 | | --- | --- | --- | | 高 | **提权被拒会被自动重试,等于自己驱动 UAC**:前端只按「失败」重试且吞掉错误文本;提权闸门 `attempted_targets` 是每次 invoke 新建的局部 Vec(`config.rs:2330-2352`),`Start-Process -Verb RunAs -Wait` 无超时(`config.rs:2824`),Managed 作用域无需 file-picker 授权(`config.rs:910-1041`)→ 用户点「否」(exit 1223)正好命中重试条件。最坏 `4 轮 × 8 项目 × 2 次 = 64` 次 invoke(改前 8 次) | 已修:按 `config.rs` 同口径判据区分不可重试失败并跳过 | | 中 | 重命名路径 `refreshRecentWorkspace` 不重试、不排重查,该行仍会被钉成终态 | 已修 | | 中 | 退避预算挂在会话上,长期坏目录吃满 3 次额度后新的瞬时失败也不再自愈 | 已修(预算绑定失败集合) | | 中 | docs/共享记忆未同步(仓库规范要求) | 已修 | | 低 | 新增的 `TauriInvoke` 与 `app/types.ts:1052` 既有同名类型重复,注释却写「唯一入口类型」 | 已修 | | 低 | 「不沿用失败结果」用例标题与断言不符(改前也能通过) | 已修(改为断言重查在途时该行是「检查中」) | | 无发现 | 合并完整性:无回退、无冲突残留、`chat/generated/*` 与 master 一致、3 个 Rust 测试文件逐字节等于 master、`platform-agent/Cargo.lock` 未入库 | — | 未做(记录待评):「Rust 侧 per-target 提权冷却」是更根本的闸门(进程级记忆,避免任何调用方重复触发提权),但要改 ACL/提权行为,需单独评审 + 真机验证,不塞进本 PR。 ## CI 过程与当前状态 1. run 2807(首个提交 `04b738755`):7 个 job 成功;`Backend tests`、`Repository checks` 因 base-commit gate(`pull request head does not contain the latest base commit`)失败——是**分支基线落后 master**,与本 PR 改动无关。 2. run 2808(首次合入 master 后):`AI game creator shell Rust lane 1/2`、`lane 2/2` 变红。核对确认是 **master 自身的问题**(run 2806 / 2809 同样只有这两条红),首个红 commit = `f789db7f2`(#491 移除提示词里的 `runId/instanceId/sessionId` 时漏同步 6 处断言)。 3. run 2811(推了断言同步 `25d331ca0`):两条 Rust lane 转绿;同一 base-commit gate 再次把 `Repository checks` / `Backend tests` 判红。 4. 随后发现 master 已用 **#494(`6b4c6b8a6`)** 修了同一批断言(把 `contains(run_id)` 换成「请求包含该 run 的任务文本」)→ 冲突按 master 版本解决,`25d331ca0` 的内容由合并结果覆盖,不重复引入。 5. run 2819:全绿。此后 master 又多次前进(`016356e5`、`f42694470`、`e8d987c20`、`5bc5328c0`…),每次推送前都重新合入,当前 head 已包含最新 master。 6. 本轮评审收口后再次合入 master 并推送(`b48953f87`)。 ## 未覆盖 / 风险 - 「AGC 卡住」本身的停顿层(远端网络 / Rust IPC 回传 / WebView 主线程)尚未定位,本 PR 只保证**一次抖动不再永久污染界面**;采证探针(invoke 拦截 + IPC 心跳 + 界面状态计数)在 issue #490 里继续跟踪。 - 提权类失败目前只跳过、不显示专门状态词:这类项目在用户主动操作前停留在「检查失败」。是否新增「需要提权」状态与一键重试入口,值得单独决策。 - 与本次无关但已在 master 上出现的红(供参考):master 的 Rust lane 2/2 偶发 `command_exec::tests::project_command_runs_project_build_and_git_read_inside_sandbox` 失败(Linux 沙箱进程组回收,间歇性);本地 Windows 上 `response_stream::provider_retry_waiting_final_reply_compaction_resumes_without_new_tool_plan`(`response_stream.rs:3419-3421` 的 100ms 负向窗口断言,2026-07-21 引入)稳定失败、Linux CI 通过。
suzmii added the Kind/Bug
Priority
High
2
labels 2026-09-23 12:39:43 +08:00
suzmii added 1 commit 2026-09-23 12:39:43 +08:00
修复 AGC 最近项目「检查失败」被钉死:失败退避重试 + 按轮重查
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m20s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 57s
Project CI / Backend tests (pull_request) Failing after 19s
Project CI / Frontend tests (pull_request) Successful in 2m3s
Project CI / Repository checks (pull_request) Failing after 14s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m20s
Project CI / Native shell tests (pull_request) Successful in 5m31s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 7m28s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 8m11s
04b738755f
- useRecentProjects:单次目录检查失败先就地重试一次(300ms),不再立刻落成失败状态
- useRecentProjects:增量投影只保留成功结果,上一轮的失败项回到「检查中」并重新检查
- useRecentProjects:一轮结束仍有失败行时按 15s/45s/120s 退避重跑整张列表,连续无失败即清零
- app/tauri:导出命名类型 TauriInvoke,替换 3 处 ReturnType<typeof resolveTauriInvoke> 写法
- tests/recentProjectsHook:新增「单次失败会重试」「刷新时重查失败项」两个用例
- tests/appSurface/home.suite:失败状态改为异步等待(重试后才会落成)
suzmii requested review from k88936 2026-09-23 12:53:04 +08:00
suzmii requested review from lhk229 2026-09-23 12:53:06 +08:00
suzmii added 1 commit 2026-09-23 12:53:18 +08:00
Merge branch 'master' into fix/agc-recent-project-status-retry
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m22s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m51s
Project CI / Backend tests (pull_request) Successful in 3m40s
Project CI / Frontend tests (pull_request) Failing after 45s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 6m23s
Project CI / Native shell tests (pull_request) Successful in 5m57s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 7m57s
Project CI / Repository checks (pull_request) Successful in 2m16s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m42s
ceb6418877
suzmii added 1 commit 2026-09-23 13:29:03 +08:00
同步 #491 漏改的提示词断言,修 master 上两条 AGC Rust lane 的 CI 红
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m20s
Project CI / Backend tests (pull_request) Failing after 12s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m6s
Project CI / Frontend tests (pull_request) Successful in 2m3s
Project CI / Repository checks (pull_request) Failing after 12s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m23s
Project CI / Native shell tests (pull_request) Successful in 5m45s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 8m8s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 8m52s
25d331ca0a
- tests/provider:工具计划与修复请求的 run_id 断言反转为「不得携带」(#491 已把 runId 移出提示词)
- tests/response_stream:4 个 finalization/replan 用例同步 run_id 与 session_id 断言(同一 run 改由 runtime/DB 断言承担)
- tests/project_tools:删除确认后的重规划请求同样反转为「不得携带 run_id」
- admin-web:AGC 模板 409 写入错误用例把聚焦断言套进 waitFor,消除 CI 抢跑 flake
suzmii added 1 commit 2026-09-23 14:48:16 +08:00
合并 master(含 #494):AGC 提示词断言同步改采 master 版本
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m22s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m52s
Project CI / Backend tests (pull_request) Successful in 3m54s
Project CI / Frontend tests (pull_request) Successful in 1m55s
Project CI / Native shell tests (pull_request) Successful in 5m58s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 8m13s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 8m47s
Project CI / Repository checks (pull_request) Successful in 2m1s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m26s
7157a90d31
- 合并 origin/master(6b4c6b8a6 修复策划 Agent 提示词契约误删 #494)
- Rust 断言同步与本分支 25d331ca0 重复,冲突按 master 版本解决(#494 用「请求含该 run 任务文本」替代 run_id 断言)
- 保留本分支独有改动:最近项目检查失败退避重试、TauriInvoke 命名类型、admin-web 409 聚焦断言等待
- 丢弃本地 cargo 运行重新生成的 chat/generated/*.ts,按 master 版本还原
lhk229 added 1 commit 2026-09-23 15:22:33 +08:00
Merge branch 'master' into fix/agc-recent-project-status-retry
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m15s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m22s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 4m35s
Project CI / Backend tests (pull_request) Successful in 3m56s
Project CI / Frontend tests (pull_request) Successful in 1m41s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 8m40s
Project CI / Native shell tests (pull_request) Successful in 6m17s
Project CI / Repository checks (pull_request) Successful in 2m28s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m57s
a1dd382489
lhk229 approved these changes 2026-09-23 15:28:17 +08:00
lhk229 left a comment
Member

审查结论:未发现阻断合并的问题,通过审查,建议合并。

审查范围:当前 PR 提交 a1dd382489 相对 master 的全部 6 个变更文件。

非阻断建议(P3):apps/ai-game-creator-shell/src/features/app-shell/useRecentProjects.ts 第 120–122 行的 300ms 退避不会随旧检查轮次失效而取消。首次检查失败后,若用户在退避期间移除项目、触发新轮次或组件卸载,旧循环仍会再发出一次目录检查。现有 disposed/generation 保护会丢弃结果,不会恢复已删除项目或污染列表;影响仅为额外 IPC 和磁盘访问。建议后续在重试前检查轮次是否仍有效,此项不阻断合并。

实际验证:

  • recentProjectsHook.test.tsx:3 个测试通过。
  • AdminAgcTemplatesPage.test.tsx:20 个测试通过。
  • appSurface.test.ts 最近项目相关定向测试:9 个通过,212 个未选中。
  • npx tsc -p apps/ai-game-creator-shell/tsconfig.json --noEmit:通过。
  • git diff --check master...HEAD:通过。

验证边界:未运行全量测试或真实 Tauri 联调。测试输出有 DOM 嵌套、act 和 mock 警告,未导致失败。15/45/120 秒自动重查链路缺少直接测试覆盖,建议后续补充。

审查结论:未发现阻断合并的问题,通过审查,建议合并。 审查范围:当前 PR 提交 a1dd38248915070d6ee6aeaed1796597f70f0c4a 相对 master 的全部 6 个变更文件。 非阻断建议(P3):apps/ai-game-creator-shell/src/features/app-shell/useRecentProjects.ts 第 120–122 行的 300ms 退避不会随旧检查轮次失效而取消。首次检查失败后,若用户在退避期间移除项目、触发新轮次或组件卸载,旧循环仍会再发出一次目录检查。现有 disposed/generation 保护会丢弃结果,不会恢复已删除项目或污染列表;影响仅为额外 IPC 和磁盘访问。建议后续在重试前检查轮次是否仍有效,此项不阻断合并。 实际验证: - recentProjectsHook.test.tsx:3 个测试通过。 - AdminAgcTemplatesPage.test.tsx:20 个测试通过。 - appSurface.test.ts 最近项目相关定向测试:9 个通过,212 个未选中。 - npx tsc -p apps/ai-game-creator-shell/tsconfig.json --noEmit:通过。 - git diff --check master...HEAD:通过。 验证边界:未运行全量测试或真实 Tauri 联调。测试输出有 DOM 嵌套、act 和 mock 警告,未导致失败。15/45/120 秒自动重查链路缺少直接测试覆盖,建议后续补充。
suzmii added 5 commits 2026-09-23 17:14:47 +08:00
- useRecentProjects:区分可重试失败与提权/权限类失败,后者不再就地重试、也不驱动整表重查,避免自己驱动 UAC 反复弹窗
- useRecentProjects:提权类失败的项目在用户主动打开/新建项目或重命名刷新前跳过检查
- useRecentProjects:退避重查预算绑定到失败集合,集合变化或整轮无失败即重置,长期坏目录不再吃满额度
- useRecentProjects:重命名后的单条刷新复用同一套就地重试与有界重查
- app/tauri:删除重复的 TauriInvoke 类型,统一引用 app/types 的既有定义
- tests/recentProjectsHook:失败不跨轮保留改为断言在途「检查中」,新增提权类失败不重试用例
- docs:decision-log、pitfalls、生命周期方案与异步闭环方案同步失败自愈口径与提权边界
# Conflicts:
#	docs/project-memory/shared-memory/decision-log.md
# Conflicts:
#	docs/project-memory/shared-memory/decision-log.md
Merge remote-tracking branch 'origin/fix/agc-recent-project-status-retry' into fix/agc-recent-project-status-retry
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m30s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m9s
Project CI / Backend tests (pull_request) Successful in 5m2s
Project CI / Native shell tests (pull_request) Successful in 6m57s
Project CI / Frontend tests (pull_request) Successful in 2m35s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 9m19s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 9m23s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m23s
Project CI / Repository checks (pull_request) Successful in 1m48s
b48953f87c
Author
Member

评审报告(4 片只读 agent + 本地复现)

评审对象:head 7157a90d3(人工 review 的那版),已在 b48953f87 上完成收口。评审切分:合并完整性 / 主逻辑 / 测试与文档 / 资源与安全。

高(本 PR 自己的重试设计引入,值得单独看一眼)

提权被拒 → 自动重试 → 自己驱动 UAC

  • 前端只按「失败」重试:catch 吞掉错误文本,300ms 后再发一次(useRecentProjects.ts:70-71,113-128)。
  • 提权闸门只在单次 invoke 内:attempted_targets 是每次调用新建的局部 Vec(config.rs:2330-2352);Start-Process -Verb RunAs -Wait 无超时(config.rs:2824)。
  • Managed 作用域(.agent/manifest.json 祖先 / AGC AppData)无需 file-picker 授权、无 TTLconfig.rs:910-1041)→ 纯首页列表检查即可弹 UAC;用户点「否」(exit 1223)恰好命中重试条件。
  • 放大:单次失败片段最坏 4 轮 × 8 项目 × 2 次 = 64 次 invoke(改前 8 次)。

处置:按 config.rswindows_acl_error_may_need_elevation 同口径识别不可重试失败(DACL / 权限 / error 5 / 安全对象不属于当前用户 / 特权 / 1300 / AGC ACL 提权修复未成功),不重试、不驱动整表重查,并在用户主动打开/新建项目或重命名刷新前跳过该项目。

未做:「Rust 侧 per-target 提权冷却」(进程级记忆)是更根本的闸门,会动 ACL/提权行为,建议单独评审+真机验证后再做。另:是否给这类项目一个「需要提权」状态词与一键重试入口,也值得单独决策。

中 / 低(均已收口)

严重度 发现 处置
重命名后的 refreshRecentWorkspace 不走重试也不排重查 → 该行仍会被钉成终态(唯一调用点 ProjectCreation.tsx:237-239 复用同一套重试 + 有界重查
退避预算挂在会话上:一个长期坏目录吃满 3 次额度后,新的瞬时失败也不再自愈(failureStreakRef 只在整轮零失败时清零) 预算绑定失败集合,集合变化即重置
docs/共享记忆未同步(AGENTS.md 明确要求) 已补 decision-log(新条目 + 修正 09-20 条目措辞)、pitfalls,以及两份技术方案的行为契约与验收矩阵
本 PR 新增的 TauriInvokeapp/types.ts:1052 既有同名类型重复,注释还写「唯一入口类型」(两处签名可互相赋值,编译器不会报错) 删除新增定义,统一引用既有类型
「刷新时…不沿用失败结果」用例标题与断言不符(该断言在改前也能通过) 改为断言重查在途时该行是「检查中」,直接钉住投影语义
无发现 合并完整性:无回退、无冲突残留、chat/generated/* 与 master 一致、3 个 Rust 测试文件逐字节等于 master、platform-agent/Cargo.lock 未入库

验证

  • 新增/改动用例在改前代码上必挂(git stash 对照:提权用例 1 failed);改后 recentProjectsHook 4 + appSurface 217(9 skipped)+ admin-web 20 + recentProjectsModel 1 = 233 passed。
  • npm run agc:typechecknpm run check:encodinggit diff --checkeslintprettier --check 全绿。
  • 退避重查:一次性脚本证明持续失败后 15.0s 自动恢复(脚本未入库)。

结论

主修复方向评审无异议(主逻辑切片结论亦为「方向正确」);上面 6 条是收口问题,已在 b48953f87 落地。可以 pass,建议带上收口提交(特别是「高」那条,否则会把「被拒提权自动重试」带进 master)。停顿层根因仍未定位,继续在 issue #490 用采证探针跟踪。

## 评审报告(4 片只读 agent + 本地复现) 评审对象:head `7157a90d3`(人工 review 的那版),已在 `b48953f87` 上完成收口。评审切分:合并完整性 / 主逻辑 / 测试与文档 / 资源与安全。 ### 高(本 PR 自己的重试设计引入,值得单独看一眼) **提权被拒 → 自动重试 → 自己驱动 UAC** - 前端只按「失败」重试:`catch` 吞掉错误文本,300ms 后再发一次(`useRecentProjects.ts:70-71,113-128`)。 - 提权闸门只在单次 invoke 内:`attempted_targets` 是每次调用新建的局部 Vec(`config.rs:2330-2352`);`Start-Process -Verb RunAs -Wait` 无超时(`config.rs:2824`)。 - Managed 作用域(`.agent/manifest.json` 祖先 / AGC AppData)**无需 file-picker 授权、无 TTL**(`config.rs:910-1041`)→ 纯首页列表检查即可弹 UAC;用户点「否」(exit 1223)恰好命中重试条件。 - 放大:单次失败片段最坏 `4 轮 × 8 项目 × 2 次 = 64` 次 invoke(改前 8 次)。 处置:按 `config.rs` 的 `windows_acl_error_may_need_elevation` 同口径识别不可重试失败(`DACL / 权限 / error 5 / 安全对象不属于当前用户 / 特权 / 1300 / AGC ACL 提权修复未成功`),不重试、不驱动整表重查,并在用户主动打开/新建项目或重命名刷新前跳过该项目。 未做:「Rust 侧 per-target 提权冷却」(进程级记忆)是更根本的闸门,会动 ACL/提权行为,建议单独评审+真机验证后再做。另:是否给这类项目一个「需要提权」状态词与一键重试入口,也值得单独决策。 ### 中 / 低(均已收口) | 严重度 | 发现 | 处置 | | --- | --- | --- | | 中 | 重命名后的 `refreshRecentWorkspace` 不走重试也不排重查 → 该行仍会被钉成终态(唯一调用点 `ProjectCreation.tsx:237-239`) | 复用同一套重试 + 有界重查 | | 中 | 退避预算挂在会话上:一个长期坏目录吃满 3 次额度后,新的瞬时失败也不再自愈(`failureStreakRef` 只在整轮零失败时清零) | 预算绑定失败集合,集合变化即重置 | | 中 | docs/共享记忆未同步(`AGENTS.md` 明确要求) | 已补 `decision-log`(新条目 + 修正 09-20 条目措辞)、`pitfalls`,以及两份技术方案的行为契约与验收矩阵 | | 低 | 本 PR 新增的 `TauriInvoke` 与 `app/types.ts:1052` 既有同名类型重复,注释还写「唯一入口类型」(两处签名可互相赋值,编译器不会报错) | 删除新增定义,统一引用既有类型 | | 低 | 「刷新时…不沿用失败结果」用例标题与断言不符(该断言在改前也能通过) | 改为断言重查在途时该行是「检查中」,直接钉住投影语义 | | 无发现 | 合并完整性:无回退、无冲突残留、`chat/generated/*` 与 master 一致、3 个 Rust 测试文件逐字节等于 master、`platform-agent/Cargo.lock` 未入库 | — | ### 验证 - 新增/改动用例在改前代码上必挂(`git stash` 对照:提权用例 `1 failed`);改后 `recentProjectsHook` 4 + `appSurface` 217(9 skipped)+ `admin-web` 20 + `recentProjectsModel` 1 = 233 passed。 - `npm run agc:typecheck`、`npm run check:encoding`、`git diff --check`、`eslint`、`prettier --check` 全绿。 - 退避重查:一次性脚本证明持续失败后 15.0s 自动恢复(脚本未入库)。 ### 结论 主修复方向评审无异议(主逻辑切片结论亦为「方向正确」);上面 6 条是收口问题,已在 `b48953f87` 落地。**可以 pass,建议带上收口提交**(特别是「高」那条,否则会把「被拒提权自动重试」带进 master)。停顿层根因仍未定位,继续在 issue #490 用采证探针跟踪。
suzmii merged commit 64018f9661 into master 2026-09-23 17:30:59 +08:00
suzmii deleted branch fix/agc-recent-project-status-retry 2026-09-23 17:30:59 +08:00
Sign in to join this conversation.