@ghyper9023/pi-dev-workflow 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/.pi-dev-output/pi-grill/answers/answer-mpfe77f1-20260521-1913.md +58 -0
  2. package/.pi-dev-output/pi-grill/answers/answer-mpfh37wu-20260521-2034.md +13 -0
  3. package/.pi-dev-output/pi-grill/answers/answer-mpfi5q4c-20260521-2104.md +13 -0
  4. package/.pi-dev-output/pi-grill/answers/answer-mpfizccb-20260521-2127.md +13 -0
  5. package/.pi-dev-output/pi-grill/answers/answer-mpfjk78k-20260521-2143.md +13 -0
  6. package/.pi-dev-output/pi-grill/answers/answer-mpfttme1-20260522-0230.md +13 -0
  7. package/.pi-dev-output/pi-grill/questions/questions-mpfdz1tz-20260521-1907.json +94 -0
  8. package/.pi-dev-output/pi-plans/20260521-113000-fix-loopcount-timeout.md +215 -0
  9. package/.pi-dev-output/pi-plans/20260521-1730-grill-input-wrap-back-fix.md +240 -0
  10. package/.pi-dev-output/pi-plans/20260521-230000-fix-timeout-display-loopcount-gitdiff.md +253 -0
  11. package/.pi-dev-output/pi-plans/20260521-230500-esc-double-press-confirm-workflow.md +137 -0
  12. package/.pi-dev-output/pi-plans/20260521-235000-fix-gitdiff-loopcount.md +258 -0
  13. package/.pi-dev-output/pi-plans/20260522-113000-grill-left-arrow-fix.md +274 -0
  14. package/.pi-dev-output/pi-review/html/20260521-2305-review-workflow-index.html +196 -0
  15. package/.pi-dev-output/pi-review/md/review-20260520-100000.md +91 -0
  16. package/.pi-dev-output/pi-review/md/review-20260521-140000.md +191 -0
  17. package/.pi-dev-output/pi-review/md/review-20260521-190000.md +189 -0
  18. package/.pi-dev-output/pi-review/md/review-20260521-204500.md +241 -0
  19. package/.pi-dev-output/pi-review/md/review-20260521-214500.md +270 -0
  20. package/.pi-dev-output/pi-review/md/review-20260521-215158.md +214 -0
  21. package/.pi-dev-output/pi-review/md/review-20260521-234500.md +201 -0
  22. package/.pi-dev-output/pi-review/md/review-20260521-235500.md +422 -0
  23. package/.pi-dev-output/pi-review/md/review-20260522-000000.md +212 -0
  24. package/.pi-dev-output/pi-review/md/review-20260522-003000.md +377 -0
  25. package/.pi-dev-output/pi-review/md/review-20260522-003500.md +296 -0
  26. package/.pi-dev-output/pi-review/md/review-20260522-105000.md +166 -0
  27. package/.pi-dev-output/pi-workflow/checkpoint-20260521-113000-fix-loopcount-timeout.json +402 -0
  28. package/.pi-dev-output/pi-workflow/checkpoint-20260521-1730-grill-input-wrap-back-fix.json +447 -0
  29. package/.pi-dev-output/pi-workflow/checkpoint-20260521-230000-fix-timeout-display-loopcount-gitdiff.json +708 -0
  30. package/.pi-dev-output/pi-workflow/checkpoint-20260521-230500-esc-double-press-confirm-workflow.json +365 -0
  31. package/.pi-dev-output/pi-workflow/checkpoint-20260521-235000-fix-gitdiff-loopcount.json +395 -0
  32. package/.pi-dev-output/pi-workflow/checkpoint-20260522-113000-grill-left-arrow-fix.json +473 -0
  33. package/.pi-dev-output/pi-workflow/checkpoint-archive-mpfhyxc5.json +30 -0
  34. package/.pi-dev-output/pi-workflow/checkpoint-archive-mpfi2unc.json +49 -0
  35. package/.pi-dev-output/pi-workflow/checkpoint-archive-mpfi382e.json +59 -0
  36. package/.pi-dev-output/pi-workflow/checkpoint-archive-mpfi5r22.json +76 -0
  37. package/.version/RELEASE-v0.4.2.md +31 -0
  38. package/.version/RELEASE-v0.4.3.md +42 -0
  39. package/README.md +21 -3
  40. package/extensions/dev-prompts.ts +16 -8
  41. package/extensions/grill-me-agent.ts +74 -8
  42. package/extensions/ui-helpers.ts +59 -7
  43. package/extensions/workflow-engine.ts +80 -32
  44. package/package.json +1 -1
  45. package/tests/test-loopcount-timeout-fix.mjs +336 -0
  46. package/themes/oh-my-pi-titanium.json +90 -0
@@ -0,0 +1,214 @@
1
+ # 代码审查报告 — git diff 解析与循环计数 Bug
2
+
3
+ **审查日期**:2026-05-21 21:51
4
+ **审查范围**:`extensions/workflow-engine.ts`, `extensions/ui-helpers.ts`
5
+ **审查者**:代码审查专家
6
+
7
+ ---
8
+
9
+ ## 严重性问题
10
+
11
+ ### C1. [critical] 文本刮取过滤器不完整:`filePatterns` pattern 4 仍可能匹配包含 `{}` 或 `[]` 的脏数据
12
+
13
+ **文件**:`extensions/workflow-engine.ts`
14
+ **位置**:`runAgentWithProgress` 函数,约第 1036~1044 行
15
+
16
+ **问题描述**:
17
+ `filePatterns` 中的最后一个 pattern:
18
+ ```typescript
19
+ /(?:^|\n)\s*(?:edit|new|delete|read|modify|create|update|add|remove)\s*[::]\s*([^\n]+\.[a-zA-Z0-9_]+)/gim
20
+ ```
21
+ 要求匹配的文件路径**必须包含 `.`**(`\.[a-zA-Z0-9_]+`),这排除了大部分纯符号文本。但当前已有的三个额外过滤器:
22
+ ```typescript
23
+ if (filePath.includes("${") || filePath.includes("\\n") || filePath.includes("\\t")) continue;
24
+ if (filePath.includes("[]") || filePath.includes("{}")) continue;
25
+ if (filePath.match(/^[\s,;)\]}]+$/)) continue;
26
+ ```
27
+ 存在一个盲区:如果 agent 输出中包含类似 `modify: some.text.[]` 或 `update: file.name.{key}` 这样的文本(即包含 `.` 且包含 `{}` 或 `[]`),`filePath.includes("{}")` 和 `filePath.includes("[]")` 可以拦截。但如果文本是 `modify: arrays.0.name`(没有 `[]` 但语义上不是文件路径),过滤器无法拦截。
28
+
29
+ **根因**:
30
+ 文本刮取始终是脆弱的 —— 正则无法完全区分自然语言中的"看起来像文件路径"的文本和真正的文件路径。当前代码虽然添加了多层过滤器,但 agent 输出千变万化,总会有漏网之鱼。
31
+
32
+ **影响**:
33
+ 在 agent 输出包含包含 `.` 的代码片段或 JSON 路径(如 `config.data.0.name`、`output.path` 等)时,widget 中会显示这些虚假的 "M config.data.0.name" 条目,造成 UI 混乱。
34
+
35
+ **建议修复**:
36
+ 1. 最彻底的方式:完全依赖 `updateToolsFromGit`(git diff)来做文件变更检测,将文本刮取改为纯提示性(不影响 `_workflowFileChanges`)。
37
+ 2. 或者添加更严格的路径验证:验证文件是否真实存在于磁盘上(`fs.existsSync`),虽然会影响性能但准确率最高。
38
+ 3. 至少添加对数字索引路径(如 `data.0.name`、`list.1.title`)的过滤。
39
+
40
+ **严重性**:critical — 直接导致 UI 显示脏数据,影响用户对工作流状态的判断。
41
+
42
+ ---
43
+
44
+ ### C2. [critical] 循环计数偏移:`loopCount` 在 while 循环开头递增导致 checkpoint 恢复时语义不一致
45
+
46
+ **文件**:`extensions/workflow-engine.ts`
47
+ **位置**:`executeLoopGroup` 函数
48
+
49
+ **问题描述**:
50
+ 当前(未提交的)修改将 `loopCount++` 从 reviewer 完成之后**移到了 while 循环最开头**。逻辑是:
51
+ ```
52
+ while (loopCount < maxLoops) {
53
+ loopCount++; // 现在 loopCount 从 0 → 1
54
+ // ... 执行 worker ...
55
+ // ... 执行 reviewer ...
56
+ // 如果 need_continue,continue → 再次 loopCount++ → 2
57
+ }
58
+ ```
59
+
60
+ 这个修改解决了 UI 显示问题(不再显示两次 "第 1 次循环"),但引入了新的不一致性:
61
+
62
+ 1. **checkpoint 恢复时的语义偏移**:
63
+ - 旧语义(f98799d commit):`loopCount` = "已完成次数",`loopCounts[step.id]` 保存在 checkpoint 中
64
+ - 新语义:`loopCount` = "当前正在进行的轮次"(从 1 开始)
65
+ - 从 checkpoint 恢复时:`let loopCount = loopCounts[step.id] ?? 0;` — 如果之前完成了 1 次循环但被中断,`loopCounts[step.id] = 1`(旧语义的"已完成 1 次"),恢复后进入 while 时 `loopCount < maxLoops`,然后 `loopCount++` 变成 2。但恢复时应该**从第 2 次循环开始**(第 1 次已完成),所以行为是正确的。✅
66
+
67
+ 2. **初始 pending 状态显示问题**:
68
+ - `buildWidgetLines` 中 `isRunning` 的 fallback 检查 `if (s.loopCount == null || s.loopCount === 0)` 会 fallback 到"第 1 次循环"
69
+ - 但 `executeLoopGroup` 一开始就设置了 `state.loopCount = loopCount`(=1),所以 UI 永远不会走到这个 fallback
70
+ - 然而,如果 widget 的动画渲染在 `executeLoopGroup` 设置 loopCount 之前就触发,就可能看到短暂的"第 1 次循环"fallback
71
+
72
+ 3. **最大的问题**:`buildWidgetLines` 中 `isRunning` 时的 fallback 逻辑现在**永远不会被执行**,因为 `executeLoopGroup` 在 while 循环开头立即设置了 `loopCount = 1`。但这段代码仍然存在,是个死代码。
73
+
74
+ **影响**:
75
+ 主要风险是 checkpoint 恢复场景下如果 loopCounts 的存储格式在旧版和新版之间切换,可能产生计数偏移。但当前分析显示实际行为可能是正确的。代码可读性降低,有死代码残留。
76
+
77
+ **建议修复**:
78
+ 清理 `buildWidgetLines` 中 `isRunning` 分支下无用的 fallback 代码,改为直接依赖 `s.loopCount`。
79
+
80
+ **严重性**:critical — 循环计数偏移是用户明确报告的 bug,虽然当前修改在"正常流程"下修复了它,但有死代码残留和 checkpoint 兼容性风险。
81
+
82
+ ---
83
+
84
+ ## 中等问题
85
+
86
+ ### M1. [medium] `buildWidgetLines` 中 `isRunning` 的 fallback 逻辑是死代码
87
+
88
+ **文件**:`extensions/ui-helpers.ts`
89
+ **位置**:`buildWidgetLines` 函数
90
+ ```typescript
91
+ if (isRunning) {
92
+ if (s.loopCount == null || s.loopCount === 0) {
93
+ // 安全 fallback(理论上不会走到这里)
94
+ loopStr = dim(theme, ` · 第 1 次循环`);
95
+ }
96
+ }
97
+ ```
98
+
99
+ **问题描述**:
100
+ 当前 `executeLoopGroup` 在 while 循环的第一行就设置了 `state.loopCount = loopCount`(=1),所以 widget 状态中的 `s.loopCount` 在 `isRunning` 状态下永远不会为 `null` 或 `0`。这段 fallback 代码永远无法被执行。
101
+
102
+ 实际上,这个 fallback 的存在表明原本的设计存在缺陷 —— 在 `f98799d` commit 中,`loopCount++` 被放在 reviewer 完成之后,所以 UI 在 worker 运行时 loopCount 还没更新,才需要这个 fallback 在 UI 层面补显示"第 1 次循环"。
103
+
104
+ **建议修复**:
105
+ 移除 `isRunning` 分支下的 fallback 代码,因为这个场景现在不会发生,而且如果发生了(因某个竞态条件),显示错误的"第 1 次循环"比不显示更糟糕。
106
+
107
+ **严重性**:medium — 死代码不影响功能,但降低可维护性,且未来如果代码重构可能造成误导。
108
+
109
+ ---
110
+
111
+ ### M2. [medium] `getGitDiffChanges` 的 `git status --porcelain` 解析中 `statusPrefix.startsWith("A")` 过于宽松
112
+
113
+ **文件**:`extensions/workflow-engine.ts`
114
+ **位置**:`getGitDiffChanges` 函数
115
+ ```typescript
116
+ if (filePath && !seen.has(filePath) && (statusPrefix === "??" || statusPrefix === "A " || statusPrefix.startsWith("A"))) {
117
+ ```
118
+
119
+ **问题描述**:
120
+ `statusPrefix.startsWith("A")` 会匹配所有以 `A` 开头的状态码,包括:
121
+ - `"A "` — 已暂存的新增文件(正确)
122
+ - `"AM"` — 已暂存新增但又有修改(正确但不常见)
123
+ - `"AD"` — 已暂存新增但又有删除(罕见)
124
+ - 但不应该匹配 `"??"`(未追踪,已有专用处理)
125
+
126
+ 更严重的是,`statusPrefix` 是 `trimmed.match(/^(..)\s+(.+)$/)` 的第一个捕获组,它捕获了**两个字符**。但 `trimmed` 是 `line.trim()` 后的结果,对于 `?? somefile`,`statusPrefix` 是 `"??"`,对于 `"?? somefile"` 的 `trimmed` = `"?? somefile"`,`match` 给出 `statusPrefix = "??"`,正确。对于 `"A somefile"`(两个空格),`statusPrefix = "A "`(A + 空格),正确。
127
+
128
+ 但如果有一行是 `"A somefile"`(一个空格),这是不合法的 porcelain 格式,但 trim 后是 `"A somefile"`,match 给出 `statusPrefix = "A "`(因为 `(..)` 匹配第一个和第二个字符,第二个字符是空格)。
129
+
130
+ 实际上这个逻辑是正确的,只是看起来有点让人担心。风险很低。
131
+
132
+ **建议修复**:
133
+ 明确只匹配已知的 porcelain 状态码:
134
+ ```typescript
135
+ const ADD_CODES = new Set(["A ", "AM", "AD"]);
136
+ if (filePath && !seen.has(filePath) && (statusPrefix === "??" || ADD_CODES.has(statusPrefix))) {
137
+ ```
138
+
139
+ **严重性**:medium — 当前逻辑实际正确但不够严谨,可能在未来 git 版本输出变化时出现误匹配。
140
+
141
+ ---
142
+
143
+ ### M3. [medium] `addWidgetSubStepTool` 中 `gitMatch` 正则过于宽松
144
+
145
+ **文件**:`extensions/workflow-engine.ts`
146
+ **位置**:`addWidgetSubStepTool` 函数
147
+ ```typescript
148
+ const gitMatch = !oldMatch ? tool.match(/^([MAD])\s{2,}(.+)$/) : null;
149
+ ```
150
+
151
+ **问题描述**:
152
+ 这个正则 `^([MAD])\s{2,}(.+)$` 匹配以 `M`、`A` 或 `D` 开头,后跟至少 2 个空格,然后任意内容。这意味着如果任何字符串以 `M ` 开头(比如 agent 输出中的 "More interesting..." 或 "Add new feature..."),它都可能被匹配。
153
+
154
+ 但实际上这个函数只被 `addWidgetSubStepTool` 调用,而该函数的调用者已经确保了 `tool` 参数要么来自 progress 回调(格式化的 `M path`),要么来自 `updateToolsFromGit`(格式化的 `M path`),要么来自文本刮取(格式化的 `M path`)。所以误匹配的**源头**在于文本刮取,而不是这里。
155
+
156
+ 不过,多做一层防御总是好的。可以加强验证:确保捕获的 `filePath` 确实是一个真实存在的文件路径(或至少看起来像路径)。
157
+
158
+ **严重性**:medium — 依赖上游调用的正确性,自身防御不足。
159
+
160
+ ---
161
+
162
+ ## 低优先级问题
163
+
164
+ ### L1. [low] `extractFileChanges` 函数中 `sendWorkflowResult` 使用 `sendMessage` 可能产生冗余消息
165
+
166
+ **文件**:`extensions/ui-helpers.ts`
167
+ **位置**:`sendWorkflowResult` 函数
168
+
169
+ **问题描述**:
170
+ 工作流完成时,`executeWorkflowBackground` 调用 `sendWorkflowResult` 发送完成消息。同时,在工作流取消时,cancel callback 中也调用了 `sendWorkflowResult`。如果工作流在自然完成后 cancel callback 也被触发(竞态条件),可能发送两条重复的完成消息。
171
+
172
+ **严重性**:low — 仅在极罕见的竞态条件下可能发生。
173
+
174
+ ---
175
+
176
+ ### L2. [low] `populatePredefinedSubSteps` 在 checkpoint 恢复时可能跳过 sub-step 初始化
177
+
178
+ **文件**:`extensions/workflow-engine.ts`
179
+ **位置**:`populatePredefinedSubSteps` 函数
180
+
181
+ **问题描述**:
182
+ 函数开头的 `if (step.subSteps && step.subSteps.length > 0) return;` 意味着如果 widget step 已经包含 sub-steps(从 checkpoint 恢复时),就不会重新初始化。但 checkpoint 中的 sub-steps 可能没有 `detail` 字段(如 `超时时间60m`),导致恢复后的 sub-step 缺少超时时间显示。
183
+
184
+ **严重性**:low — 仅影响 checkpoint 恢复后的展示细节。
185
+
186
+ ---
187
+
188
+ ### L3. [low] 重复的 `extractTaskSummary` 函数定义
189
+
190
+ **文件**:`extensions/workflow-engine.ts` 和 `extensions/ui-helpers.ts`
191
+
192
+ **问题描述**:
193
+ 两个文件各自定义了 `extractTaskSummary` 函数,功能完全一致。这是代码重复,如果未来需要修改需要同步修改两处。
194
+
195
+ **严重性**:low — 不影响功能,单是代码风格问题。
196
+
197
+ ---
198
+
199
+ ## 总结
200
+
201
+ 当前代码已经针对 `git diff 解析` 和 `循环计数偏移` 两个 bug 做了本地修复(未提交):
202
+
203
+ 1. **git diff 解析**:在本地已从 `split("\t")` 改回正则解析,并保留了 tab split 作为后备。✅
204
+ 2. **文本刮取脏数据**:已添加三层额外过滤器(`${}`, `[]`/`{}`, 纯符号)来拦截脏数据。✅
205
+ 3. **循环计数偏移**:已将 `loopCount++` 从 reviewer 完成后移到 while 循环开头。✅
206
+
207
+ 但存在以下**残留问题**:
208
+
209
+ - **C1**: 文本刮取方案仍然脆弱,建议完全依赖 git diff 而非文本刮取
210
+ - **C2 + M1**: 循环计数 fallback 是死代码,需要清理
211
+ - **M2**: `statusPrefix.startsWith("A")` 过于宽松
212
+ - **M3**: `addWidgetSubStepTool` 的 gitMatch 正则防御不足
213
+
214
+ 建议优先解决 C1 和 C2+M1,然后处理 M2-M3,最后清理 L1-L3 的代码风格问题。
@@ -0,0 +1,201 @@
1
+ # 审查报告 — 超时时间/循环次数/git diff 解析修复
2
+
3
+ **审查时间**: 2026-05-21 23:45
4
+ **审查范围**: `extensions/ui-helpers.ts`, `extensions/workflow-engine.ts`, `extensions/dev-prompts.ts`, `tests/test-loopcount-timeout-fix.mjs`
5
+ **审查提交**: 当前 HEAD (`01413c9`) + 未暂存修改
6
+
7
+ ---
8
+
9
+ ## 严重(Critical)
10
+
11
+ ### C1. loopCount 显示逻辑在跨循环迭代期间显示错误的循环编号
12
+
13
+ **位置**: `extensions/workflow-engine.ts` `executeLoopGroup()` (~L1190-1265) & `extensions/ui-helpers.ts` `buildWidgetLines()` (~L485-494)
14
+
15
+ **根因**: `loopCount` 变量代表的是 **已完成的循环次数**,而非当前进行的循环次数。在 `executeLoopGroup` 中:
16
+
17
+ 1. `loopCount++` 在每次循环的 **末尾**(reviewer 完成后)执行
18
+ 2. 进入下一次循环时,`setWidgetSubStepStatus` 重置 sub-step 为 pending 并触发 `refreshWidget()`,此时 widget 中 step 的 `loopCount` 仍是上一次循环完成后的值
19
+ 3. 因此在 worker/reviewer 运行期间(可能需要几分钟),UI 显示的是上一次已完成循环的次数,而非当前循环的次数
20
+
21
+ **具体场景**:
22
+ - 第 1 次循环 worker/reviewer 运行 → `loopCount=0` → `isRunning` fallback 显示"第 1 次循环" ✅
23
+ - 第 1 次循环完成 → `loopCount++` → `updateWidgetStep` → 显示"第 1 次循环" ✅
24
+ - 第 2 次循环 worker 运行 → `loopCount=1`(仍为第 1 次完成值) → 显示仍是"第 1 次循环" ❌(应为"第 2 次循环")
25
+ - 第 2 次循环完成 → `loopCount=2` → 显示"第 2 次循环" ✅
26
+ - 第 3 次循环 worker 运行 → `loopCount=2`(仍为第 2 次完成值) → 显示"第 2 次循环" ❌
27
+
28
+ **修复建议**: 在 `buildWidgetLines` 中,当 `isRunning` 状态且 `s.maxLoops != null` 时,显示的循环次数应为 `s.loopCount + 1`(当前正在进行的循环)而非 `s.loopCount`。具体来说:
29
+
30
+ ```typescript
31
+ // 当前逻辑(错误):
32
+ if (s.loopCount != null && s.loopCount > 0) {
33
+ loopStr = dim(theme, ` · 第 ${s.loopCount} 次循环`);
34
+ } else if (s.maxLoops != null) {
35
+ if (isRunning) {
36
+ loopStr = dim(theme, ` · 第 1 次循环`); // 硬编码 1
37
+ }
38
+ }
39
+
40
+ // 修复后:
41
+ if (s.maxLoops != null) {
42
+ const displayCount = (s.loopCount ?? 0) + (isRunning && !isDone ? 1 : 0);
43
+ // 如果 loopCount=0(pending),显示第 0 次
44
+ // 如果 isRunning 且 loopCount=1,显示第 2 次
45
+ // 如果 isDone,显示 loopCount
46
+ if (displayCount > 0 || isPending) {
47
+ loopStr = dim(theme, ` · 第 ${displayCount} 次循环`);
48
+ }
49
+ }
50
+ ```
51
+
52
+ ### C2. executeLoopGroup 重置 sub-step 状态时丢失 worker 的 tools/outputs 历史
53
+
54
+ **位置**: `extensions/workflow-engine.ts` `executeLoopGroup()` L1194-1195
55
+
56
+ ```typescript
57
+ // 每次循环开始时重置 sub-step 状态
58
+ setWidgetSubStepStatus(stepIndex, step.loopAgentName!, "pending");
59
+ setWidgetSubStepStatus(stepIndex, step.reviewAgentName!, "pending");
60
+ ```
61
+
62
+ **根因**: `setWidgetSubStepStatus` 只将 sub-step 的 `status` 改为 `"pending"`,并没有重置 `tools`、`outputs`、`startedAt` 等字段。当进入新一轮循环时,旧的 tool 记录仍会显示在 UI 上,导致新 worker 的结果和旧 worker 的结果混合显示。
63
+
64
+ 然而,更严重的问题是 **`runAgentWithProgress` 在创建新的 sub-step 时检查 `existing = step.subSteps?.find(s => s.agent === agentName)`**,它找到旧 sub-step 后**不会清除旧数据**,而是直接复用:
65
+
66
+ ```typescript
67
+ const existing = step.subSteps.find(s => s.agent === agentName);
68
+ if (!existing) {
69
+ // 创建新的...
70
+ } else {
71
+ // Update existing sub-step status, startedAt, and detail
72
+ existing.status = "running";
73
+ existing.startedAt = agentStartTime;
74
+ existing.detail = `超时时间${formatTimeout(timeoutMs)}`;
75
+ refreshWidget();
76
+ }
77
+ ```
78
+
79
+ 注意:`tools` 和 `outputs` 没有被清空!所以在第 2 次循环的 worker 运行时,旧的 tools 和 outputs 仍然会显示在 UI 上。
80
+
81
+ **修复建议**: 在 `executeLoopGroup` 重置 sub-step 时,同时清空 tools 和 outputs:
82
+
83
+ ```typescript
84
+ // 每次循环开始时重置 sub-step 状态并清空历史
85
+ const loopSub = _widgetSteps[stepIndex]?.subSteps?.find(s => s.agent === step.loopAgentName);
86
+ const reviewSub = _widgetSteps[stepIndex]?.subSteps?.find(s => s.agent === step.reviewAgentName);
87
+ if (loopSub) { loopSub.status = "pending"; loopSub.tools = []; loopSub.outputs = []; loopSub.startedAt = undefined; }
88
+ if (reviewSub) { reviewSub.status = "pending"; reviewSub.tools = []; reviewSub.outputs = []; reviewSub.startedAt = undefined; }
89
+ refreshWidget();
90
+ ```
91
+
92
+ 或者,在 `runAgentWithProgress` 中复用 sub-step 时清空 tools/outputs。
93
+
94
+ ---
95
+
96
+ ## 中等(Medium)
97
+
98
+ ### M1. 超时时间格式化不一致
99
+
100
+ **位置**: `extensions/ui-helpers.ts` `formatTimeout()` (~L368-371)
101
+
102
+ ```typescript
103
+ export function formatTimeout(ms: number): string {
104
+ const m = Math.floor(ms / 60000);
105
+ const s = Math.floor((ms % 60000) / 1000);
106
+ return s > 0 ? `${m}m${s}s` : `${m}m`;
107
+ }
108
+ ```
109
+
110
+ **问题**: 当超时时间恰好是整分钟(如 15min=900000ms)时,`formatTimeout` 返回 `15m`(不带秒)。这与 `formatDurationFull` 的行为不一致——`formatDurationFull` 在 `ms >= 60000` 时总是显示 `m` 和 `s` 两段(即使秒数为 0 也会显示 `0s`)。
111
+
112
+ 在 UI 上会看到 `(52.6s/超时时间15m)`,而预期应为 `(52.6s/超时时间15m0s)` 或至少格式一致。不过这个差异很细微,且现有的测试用例验证了 `15m` 格式是可接受的。
113
+
114
+ **建议**: 如果希望一致性,可以改为 `return s > 0 ? `${m}m${s}s` : `${m}m0s`;`。
115
+
116
+ ### M2. 测试覆盖不完整
117
+
118
+ **位置**: `tests/test-loopcount-timeout-fix.mjs`
119
+
120
+ **问题**:
121
+ 1. 测试用例仅通过静态代码分析(`string.includes()`)验证代码存在性,没有模拟执行验证运行时行为
122
+ 2. 没有测试验证跨多个循环迭代的 loopCount 正确性(如第 2 次循环显示"第 2 次循环")
123
+ 3. 没有测试验证 `getGitDiffChanges` 的 git diff 解析正确处理制表符分隔的格式
124
+
125
+ **建议**:
126
+ - 添加针对 `getGitDiffChanges` 的单元测试,模拟 `git diff --name-status` 输出并验证解析结果
127
+ - 添加对 `buildWidgetLines` 中 loopCount 在 `isRunning` 状态下的测试
128
+
129
+ ### M3. 子代理行超时显示可能存在重复/格式问题
130
+
131
+ **位置**: `extensions/ui-helpers.ts` `buildWidgetLines()` ~L560-565
132
+
133
+ ```typescript
134
+ if (sub.detail && sub.detail.includes("超时时间")) {
135
+ subTimeoutStr = dim(theme, `/${sub.detail}`);
136
+ }
137
+ ```
138
+
139
+ **问题**: `sub.detail` 的值是 `"超时时间60m"`(来自 `runAgentWithProgress`),当拼接到 UI 行时,会变成 `(52.6s/超时时间60m)`。但 UI 示例中期望的格式是 `(52.6s/超时时间60m)`,这没问题。
140
+
141
+ 但如果 `sub.detail` 因某种原因包含多余字符(如空格或换行),或者被后续代码修改,可能导致格式异常。且 `sub.detail.includes("超时时间")` 检查是字符串包含匹配,过于宽松,应改为更精确匹配,如 `sub.detail.startsWith("超时时间")`。
142
+
143
+ ---
144
+
145
+ ## 低优先级(Low)
146
+
147
+ ### L1. setWidgetSubStepStatus 缩进不一致
148
+
149
+ **位置**: `extensions/workflow-engine.ts` `setWidgetSubStepStatus()` ~L781-791
150
+
151
+ ```typescript
152
+ function setWidgetSubStepStatus(stepIndex: number, agentName: string, status: WorkflowSubStepWidgetState["status"]): void {
153
+ const step = _widgetSteps[stepIndex]; // Tabs → spaces
154
+ if (!step) return;
155
+ const sub = step.subSteps?.find(s => s.agent === agentName);
156
+ if (sub) {
157
+ sub.status = status; // Spaces → tabs
158
+ refreshWidget();
159
+ }
160
+ }
161
+ ```
162
+
163
+ 这个函数存在混用 tab 和 space 缩进的问题(`step` 行用空格,`sub` 行用 tab),这在 commit 01413c9 中引入。
164
+
165
+ ### L2. 注释中示例 UI 未更新
166
+
167
+ **位置**: `extensions/ui-helpers.ts` ~L399-415
168
+
169
+ `buildWidgetLines` 上方的 JSDoc 注释中的 UI 渲染示例仍然显示旧格式:
170
+ ```
171
+ ▶ ⠋ 🔧实施代码 → 审查 · 第 1 次循环 (1s/超时时间15m)
172
+ |__ worker ·
173
+ ```
174
+
175
+ 没有显示子代理行中应有的超时信息:
176
+ ```
177
+ |__ worker · (1s/超时时间15m)
178
+ ```
179
+
180
+ ### L3. git status --porcelain 解析边界情况
181
+
182
+ **位置**: `extensions/workflow-engine.ts` `getGitDiffChanges()` ~L326-330
183
+
184
+ ```typescript
185
+ const statusPrefix = trimmed.slice(0, 2);
186
+ const filePath = trimmed.slice(3).trim();
187
+ ```
188
+
189
+ 当 `trimmed` 长度小于 3 时(如空行或长度异常的输入),`trimmed.slice(3)` 可能返回空字符串,然后 `trim()` 返回 `""`,导致 `if (filePath && ...)` 条件失败。这实际上是安全的(不会产生假阳性),但代码缺乏显式的长度检查,可读性略差。
190
+
191
+ ---
192
+
193
+ ## 总结
194
+
195
+ 本次审查发现 2 个严重问题、3 个中等问题和 3 个低优先级问题。
196
+
197
+ **严重问题 C1** 是最关键的用户可见 bug:跨循环迭代期间,UI 显示的循环编号滞后。例如,当第 2 次循环的 worker 运行时,UI 仍显示"第 1 次循环"。需要修改 `buildWidgetLines` 中的 `loopCount` 显示逻辑,在运行时使用 `loopCount + 1` 作为当前循环编号。
198
+
199
+ **严重问题 C2** 是另一个用户可见问题:跨循环迭代时,旧的 tools/outputs 数据未被清空,导致新 worker 的结果与旧数据混合显示。
200
+
201
+ 当前未暂存的 diff 已正确修复了超时时间显示位置(子代理行)和 git diff 解析(string 拆分替代正则),但循环次数计数问题(C1)和 sub-step 数据重置问题(C2)仍需要进一步修复。