@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,76 @@
1
+ {
2
+ "version": 2,
3
+ "createdAt": "2026-05-21T12:59:04.215Z",
4
+ "updatedAt": "2026-05-21T13:02:24.734Z",
5
+ "prompt": "[fix] 修复 当前目录 中的 问题: 1.commit提交哈希:01413c9edb1dee3af525bfabdb4f55de7f0a3b4b里面的改动, 现在的超时时间显示位置: ``` ▶ ⠏ 🔧 修复代码 → 审查 (52.6s) |__ ⠏ worker · |__ 超时时间60m ``` 2.问题2,`第 0 次循环`只在排队时候显示了,等loop组开始工作连`第 1 次循环`的提示都不见了 2.根据git diff --name-status获取的文件变动信息,是通过正则筛选的,但很明显git diff --name-status给的结果如下: ```git diff --name-statusM .gitignoreM Cargo.lock ``` 非常的规整,正则现在会识别出来一些无关东西,判断不出来是哪里来的,git diff --name-status直接拿到的信息`X 空格 filepath 换行`只需要简单的string处理即可,无需复杂正则,而且正则效果不好。\n\n**背景**:\n- 输入:见代码上下文\n- 预期行为:预期: 1.超时时间显示位置: ``` ▶ ⠏ 🔧 修复代码 → 审查 (52.6s/超时时间60m ) |__ ⠏ worker · ``` 超时时间应该跟在计时的后面(当前计时/超时时间xm) 2.恢复`第 x 次循环`的显示,上次fix的模板是x计数不更新,但是修改后直接导致`第 x 次循环`不见了,恢复显示并修复x计数和更新ui逻辑。 3.将正则匹配改成普通的string处理 3.严格完成以上任务要求,不得破坏原有其他功能,不得偷懒省略,以最小改动实现。\n- 当前错误:请描述当前错误\n**任务**:\n1. 不要仅仅消除报错(Suppress),要解决根本原因。\n2. 先读取相关代码和日志,诊断根因(多步推理,不要先给结论)。\n3. 提供至少一种修复方案,并说明为什么这样做。\n4. 编写测试用例复现该 Bug 并确认修复有效。\n**输出**:提供 diff 和两句话的根因分析。\n**约束**:只修 bug,不做重构;最小化改动;不要假设错误是微不足道的。",
6
+ "mode": "attended",
7
+ "steps": [
8
+ {
9
+ "status": "done",
10
+ "durationMs": 15309
11
+ },
12
+ {
13
+ "status": "pending"
14
+ }
15
+ ],
16
+ "currentStepIndex": 0,
17
+ "loopCounts": {},
18
+ "planFilePath": ".pi-dev-output/pi-plans/20260521-1730-grill-input-wrap-back-fix.md",
19
+ "taskSummary": "fix - 修复 当前目录 中的 问题: 1.commit提交哈希:01413c9edb1dee3af525bfabdb4f55de7f0a3b4b里面的改动, 现在的超时时间显示位置: ``` ▶ ⠏ 🔧 修复代码 → 审查 (52.6s) |__ ⠏ worker · |__ 超时时间60m ``` 2.问题2,`第 0 次循环`只在排队时候显示了,等loop组开始工作连`第 1 次循环`的提示都不见了 2.根据git diff --name-status获取的文件变动信息,是通过正则筛选的,但很明显git diff --name-status给的结果如下: ```git diff --name-statusM .gitignoreM Cargo.lock ``` 非常的规整,正则现在会识别出来一些无关东西,判断不出来是哪里来的,git diff --name-status直接拿到的信息`X 空格 filepath 换行`只需要简单的string处理即可,无需复杂正则,而且正则效果不好。",
20
+ "workflowType": "自定义",
21
+ "fileChanges": [
22
+ {
23
+ "agent": "planner",
24
+ "stepIndex": 0,
25
+ "type": "new",
26
+ "filePath": ".pi-dev-output/pi-workflow/checkpoint-archive-mpfhyxc5.json",
27
+ "timestamp": "2026-05-21T12:59:04.213Z"
28
+ },
29
+ {
30
+ "agent": "planner",
31
+ "stepIndex": 0,
32
+ "type": "new",
33
+ "filePath": ".pi-dev-output/pi-workflow/checkpoint-archive-mpfi382e.json",
34
+ "timestamp": "2026-05-21T13:02:24.733Z"
35
+ }
36
+ ],
37
+ "subAgentRuns": 2,
38
+ "filesModified": 0,
39
+ "filesCreated": 2,
40
+ "agentRunHistory": [
41
+ {
42
+ "agent": "planner",
43
+ "stepIndex": 0,
44
+ "startedAt": "2026-05-21T12:58:31.215Z",
45
+ "durationMs": 32941,
46
+ "exitCode": 143,
47
+ "toolCount": 0
48
+ },
49
+ {
50
+ "agent": "planner",
51
+ "stepIndex": 0,
52
+ "startedAt": "2026-05-21T13:02:09.425Z",
53
+ "durationMs": 15259,
54
+ "exitCode": 143,
55
+ "toolCount": 0
56
+ }
57
+ ],
58
+ "baseline": [
59
+ {
60
+ "path": ".pi-dev-output/pi-grill/answers/answer-mpfhy5x2-20260521-2058.md",
61
+ "hash": "609a8f2ace3d8a0d2ead3b31cb1027223c819336"
62
+ },
63
+ {
64
+ "path": ".pi-dev-output/pi-grill/answers/answer-mpfi2th1-20260521-2102.md",
65
+ "hash": "e8cd2855d32632a6486d8c793af320ed6cd74835"
66
+ },
67
+ {
68
+ "path": ".pi-dev-output/pi-workflow/checkpoint-archive-mpfhyxc5.json",
69
+ "hash": "9041bd1be486c6141080e623760514026c6b409b"
70
+ },
71
+ {
72
+ "path": ".pi-dev-output/pi-workflow/checkpoint-archive-mpfi2unc.json",
73
+ "hash": "72c8520e2d0784be27b4482b03895f82c1239a89"
74
+ }
75
+ ]
76
+ }
@@ -0,0 +1,31 @@
1
+ # Release v0.4.2
2
+
3
+ ## 🚀 Features
4
+
5
+ - **Esc 双击确认停止机制** — 工作流运行期间,首次按 Esc 提示再次确认,3 秒内双击才终止工作流,避免误触中断
6
+ - **Grill 交互增强**
7
+ - 选项标签超长时自动截断,完整文本显示在 description 中
8
+ - 输入框上方增加实时换行预览区域
9
+ - 左方向键支持返回上一题
10
+ - 优化导航提示文字
11
+ - **独立超时配置** — reviewer 子代理新增 `reviewTimeoutMs` 独立超时,不再与 worker 共用超时时间(trimmer: 20min, worker: 30min, reviewer: 15min)
12
+ - **新主题** — 添加 `oh-my-pi-titanium` 主题
13
+ - **循环计数显示** — loop-group 在工作时正确显示 `第 N 次循环`,时序与执行同步
14
+
15
+ ## 🐛 Bug Fixes
16
+
17
+ - **Git diff 解析** — 改用简单字符串分割替代正则匹配,避免解析出 `checkpoint-${planId}.json`、`[],\n\t\t\t\toutputs:` 等垃圾条目
18
+ - **循环计数器时序** — 修复循环计数从 planner 进入 loop 时显示错误(0→1→1→2 的问题),现正确为 0→1→2→3
19
+ - **超时显示位置** — 超时时间从 loop 组迁移到具体的子代理步骤上,显示更准确
20
+ - **工作流状态重置** — 修复 sub-step 状态重置函数名错误
21
+
22
+ ## 🔧 Refactor
23
+
24
+ - UI 循环计数显示重构,子步骤时长/超时信息改进
25
+ - 文件变更检测支持 git 标准状态码(M/A/D)
26
+
27
+ ## 📦 Files Changed
28
+
29
+ ```
30
+ 41 files changed, 7351 insertions(+), 62 deletions(-)
31
+ ```
@@ -0,0 +1,42 @@
1
+ # Release v0.4.3
2
+
3
+ ## 🐛 Bug Fixes
4
+
5
+ ### Grill 选项完整文本不可见(选项不换行)
6
+ 选项列表中超长文本会被截断显示 `...`,但完整文本只能通过 `description` 列查看,而该列**单行显示且宽度受限**,导致长文本依旧被截断,用户无法阅读完整选项内容。
7
+
8
+ **修复方案**:移除 `description` 列,改为**选项预览面板**。
9
+
10
+ ```
11
+ 之前: (a) a[推荐] 最终用户... a[推荐] 最终用户(运维/... ← 灰色列单行截断
12
+ 之后: (a) a[推荐] 最终用户(运维/系统管理员)——需要快...
13
+ a[推荐] 最终用户(运维/系统管理员)——需要快速部署
14
+ rksh-server、通过客户端连接远程服务器、使用终端和 ← 预览面板自动换行
15
+ ```
16
+
17
+ - 选项列表下方新增 `Text` 预览面板,展示当前选中选项的完整文本
18
+ - 使用 `wrapTextWithAnsi` 实现自动换行,灰色 `dim` 样式
19
+ - 跟随 `↑↓` 导航实时更新,初始化时默认显示第一个选项
20
+ - 选中 `✏️ 自定义输入` 或 `← 返回上一题` 时预览自动清空
21
+
22
+ ### Grill 左方向键与输入框光标左移冲突
23
+ 选项列表中 `←` 键被拦截用于"返回上一题",导致自定义输入框中 `←` 键无法正常移动光标编辑文本。
24
+
25
+ **修复方案**:将返回上一题的快捷键从 `←` 改为 `Ctrl+Shift+←`,输入框中 `←`/`→` 恢复为光标移动。
26
+
27
+ | 场景 | 之前 | 之后 |
28
+ |------|------|------|
29
+ | 选项列表返回 | `←` | `Ctrl+Shift+←` |
30
+ | 输入框返回 | `←`(吃掉键,无法编辑) | `Ctrl+Shift+←` |
31
+ | 输入框光标 | ❌ 左移被拦截 | ✅ `←`/`→` 正常移动 |
32
+
33
+ ## 📦 Files Changed
34
+
35
+ ```
36
+ 2 files changed, 65 insertions(+), 15 deletions(-)
37
+ ```
38
+
39
+ ### Changed
40
+
41
+ - `extensions/grill-me-agent.ts` — 预览面板 + 动态截断宽度 + 键位修正
42
+ - `extensions/ui-helpers.ts` — 移除 `Key.left` 拦截,恢复输入框光标左移
package/README.md CHANGED
@@ -56,7 +56,8 @@ pi-package/
56
56
  │ ├── git-commands.ts # git-sub-agent 命令
57
57
  │ ├── grill-me-agent.ts # Grill + PRD 运行时:设计评审、PRD 生成
58
58
  │ ├── sub-agents.ts # 子代理系统:git-sub-agent + review-sub-agent
59
- └── workflow-engine.ts # 工作流编排引擎(由 dev-prompts.ts 引入)
59
+ ├── workflow-engine.ts # 工作流编排引擎(由 dev-prompts.ts 引入)
60
+ │ └── ui-helpers.ts # TUI 组件构建器(Select/Confirm/Input/Widget)
60
61
  └── themes/
61
62
  └── claude-code-theme.json # Claude Code CLI 风格主题
62
63
  ```
@@ -342,11 +343,22 @@ Grill("拷问式评审")是提交方案前由 AI sub-agent 从多个维度
342
343
  ### 交互形式
343
344
 
344
345
  每道问题在 TUI 中以 SelectList 呈现:
345
- - 选项列表:`(a) ...` `(b) ...` `(c) ...`
346
+ - 选项列表:`(a) ...` `(b) ...` `(c) ...`(完整显示,不截断)
346
347
  - 最末选项:`✏️ 自定义输入` — 可输入自己的回答
347
- - 导航:↑↓ 选择,Enter 确认,Esc 取消全部评审
348
+ - 导航:↑↓ 选择,Enter 确认
349
+ - 返回:`Ctrl+Shift+←` 返回上一题(输入框中也可用 `Ctrl+Shift+←` 返回上一步)
350
+ - 跳过:`Ctrl+Shift+→` 在输入框中跳过当前输入并继续
351
+ - 取消:Esc 取消全部评审
348
352
  - 进度:标题栏显示 `问题 3/18`
349
353
 
354
+ ### 输入框特性
355
+
356
+ 自定义输入和 `/dev-*` 向导中的输入框支持:
357
+ - **实时换行预览**:输入超长文本时,输入框上方会显示完整的换行预览(灰色文字),实时跟随输入变化
358
+ - **光标操作**:`←` 和 `→` 键可正常移动光标编辑已有内容(不触发返回)
359
+ - **返回上一题**:`Ctrl+Shift+←` 在输入框中返回上一题
360
+ - **跳过输入**:`Ctrl+Shift+→` 提交当前内容(可为空)并继续
361
+
350
362
  ## PRD 文档生成
351
363
 
352
364
  仅 `/dev-feat` 命令在执行完成后自动触发 PRD 生成。其余 `/dev-*` 命令不包含此阶段。
@@ -407,6 +419,12 @@ A: 在 `extensions/grill-me-agent.ts` 中修改对应 AgentDef 的 `systemPrompt
407
419
  **Q: 评审结果是否影响原提示词?**
408
420
  A: 评审问答以「设计评审记录」区块追加到原提示词末尾,原提示词内容不变。主代理执行时会同时参考原需求 + 评审中确认的决策。
409
421
 
422
+ **Q: Grill 中如何返回上一题?**
423
+ A: 使用 `Ctrl+Shift+←` 返回上一题(在选项列表和自定义输入框中均适用)。裸 `←` 键在选项列表中无效果,在输入框中用于光标左移编辑文本。
424
+
425
+ **Q: 自定义输入框中的键位有哪些?**
426
+ A: `Enter` 确认提交,`Esc` 取消返回选项列表,`Ctrl+Shift+←` 返回上一题,`Ctrl+Shift+→` 跳过输入并继续,方向键 `←`/`→` 用于移动光标编辑已有文本。
427
+
410
428
  **Q: `grill-with-docs` skill 和 `/dev-*` 内置的 Grill 有什么区别?**
411
429
  A: `grill-with-docs` 是可独立调用的 skill(`/skill:grill-with-docs`),侧重领域术语统一和文档同步(更新 CONTEXT.md、创建 ADR)。`/dev-*` 内置的 Grill 是任务向导的一部分,侧重方案评审,不涉及文档持久化。
412
430
 
@@ -381,7 +381,8 @@ const FEAT_WORKFLOW_STEPS: WorkflowStepDef[] = [
381
381
  loopAgentName: "worker",
382
382
  reviewAgentName: "reviewer",
383
383
  maxLoops: 3,
384
- timeoutMs: 900_000,
384
+ timeoutMs: 1_800_000,
385
+ reviewTimeoutMs: 900_000,
385
386
  },
386
387
  {
387
388
  id: "trimmer-reviewer",
@@ -390,7 +391,8 @@ const FEAT_WORKFLOW_STEPS: WorkflowStepDef[] = [
390
391
  loopAgentName: "trimmer",
391
392
  reviewAgentName: "reviewer",
392
393
  maxLoops: 3,
393
- timeoutMs: 300_000,
394
+ timeoutMs: 1_200_000,
395
+ reviewTimeoutMs: 900_000,
394
396
  },
395
397
  {
396
398
  id: "docWriter",
@@ -416,7 +418,8 @@ const FIX_WORKFLOW_STEPS: WorkflowStepDef[] = [
416
418
  loopAgentName: "worker",
417
419
  reviewAgentName: "reviewer",
418
420
  maxLoops: 3,
419
- timeoutMs: 900_000,
421
+ timeoutMs: 1_800_000,
422
+ reviewTimeoutMs: 900_000,
420
423
  },
421
424
  {
422
425
  id: "docWriter",
@@ -442,7 +445,8 @@ const REFACTOR_WORKFLOW_STEPS: WorkflowStepDef[] = [
442
445
  loopAgentName: "worker",
443
446
  reviewAgentName: "reviewer",
444
447
  maxLoops: 3,
445
- timeoutMs: 900_000,
448
+ timeoutMs: 1_800_000,
449
+ reviewTimeoutMs: 900_000,
446
450
  },
447
451
  {
448
452
  id: "trimmer-reviewer",
@@ -451,7 +455,8 @@ const REFACTOR_WORKFLOW_STEPS: WorkflowStepDef[] = [
451
455
  loopAgentName: "trimmer",
452
456
  reviewAgentName: "reviewer",
453
457
  maxLoops: 3,
454
- timeoutMs: 300_000,
458
+ timeoutMs: 1_200_000,
459
+ reviewTimeoutMs: 900_000,
455
460
  },
456
461
  ];
457
462
 
@@ -470,7 +475,8 @@ const PERF_WORKFLOW_STEPS: WorkflowStepDef[] = [
470
475
  loopAgentName: "worker",
471
476
  reviewAgentName: "reviewer",
472
477
  maxLoops: 3,
473
- timeoutMs: 900_000,
478
+ timeoutMs: 1_800_000,
479
+ reviewTimeoutMs: 900_000,
474
480
  },
475
481
  ];
476
482
 
@@ -489,7 +495,8 @@ const TEST_WORKFLOW_STEPS: WorkflowStepDef[] = [
489
495
  loopAgentName: "worker",
490
496
  reviewAgentName: "reviewer",
491
497
  maxLoops: 3,
492
- timeoutMs: 900_000,
498
+ timeoutMs: 1_800_000,
499
+ reviewTimeoutMs: 900_000,
493
500
  },
494
501
  ];
495
502
 
@@ -518,7 +525,8 @@ const STYLE_WORKFLOW_STEPS: WorkflowStepDef[] = [
518
525
  loopAgentName: "trimmer",
519
526
  reviewAgentName: "reviewer",
520
527
  maxLoops: 2,
521
- timeoutMs: 300_000,
528
+ timeoutMs: 1_200_000,
529
+ reviewTimeoutMs: 900_000,
522
530
  },
523
531
  ];
524
532
 
@@ -21,6 +21,10 @@ import {
21
21
  SelectList,
22
22
  Text,
23
23
  Spacer,
24
+ matchesKey,
25
+ Key,
26
+ truncateToWidth,
27
+ wrapTextWithAnsi,
24
28
  type SelectItem,
25
29
  } from "@earendil-works/pi-tui";
26
30
  import { uiSelect, uiConfirm, uiInput } from "./ui-helpers";
@@ -589,7 +593,23 @@ export async function runGrillPhase(
589
593
  };
590
594
  }
591
595
 
592
- /** Show a single grill question as TUI SelectList + custom input option. */
596
+ /**
597
+ * Show a single grill question as TUI SelectList with option items and custom input entry.
598
+ *
599
+ * Navigation:
600
+ * - ↑↓ 选择, Enter 确认, Esc 取消全部评审
601
+ * - Ctrl+Shift+← 返回上一题(仅当 backable=true 且 currentIndex > 1 时生效)
602
+ * - 选择 "✏️ 自定义输入" 进入文本输入模式
603
+ *
604
+ * @param ctx - Extension command context for TUI rendering
605
+ * @param q - The grill question to display
606
+ * @param currentIndex - 1-based index of current question
607
+ * @param totalCount - Total number of questions
608
+ * @param titlePrefix - Prefix for the question title bar
609
+ * @param backable - Whether navigating back to previous question is allowed
610
+ * @param previousAnswer - Previous answer to pre-fill as "上次选择" marker
611
+ * @returns Selected option text, "__BACK__" for back navigation, or null for cancel
612
+ */
593
613
  async function showQuestionTUI(
594
614
  ctx: ExtensionCommandContext,
595
615
  q: GrillQuestion,
@@ -599,12 +619,21 @@ async function showQuestionTUI(
599
619
  backable = false,
600
620
  previousAnswer?: string,
601
621
  ): Promise<string | null> {
602
- const selectItems: SelectItem[] = q.options.map((opt, i) => ({
603
- value: `opt-${i}`,
604
- label: opt === previousAnswer
605
- ? `(${String.fromCharCode(97 + i)}) ${opt} - 上次选择`
606
- : `(${String.fromCharCode(97 + i)}) ${opt}`,
607
- }));
622
+ // 根据终端宽度计算截断宽度
623
+ const termWidth = process.stdout.columns || 120;
624
+ const maxOptWidth = Math.min(termWidth - 12, 100);
625
+ const selectItems: SelectItem[] = q.options.map((opt, i) => {
626
+ const prefix = `(${String.fromCharCode(97 + i)}) `;
627
+ const label = opt === previousAnswer
628
+ ? `${prefix}${opt} - 上次选择`
629
+ : `${prefix}${opt}`;
630
+ const truncated = truncateToWidth(label, maxOptWidth, "...");
631
+ return {
632
+ value: `opt-${i}`,
633
+ label: truncated,
634
+ // 完整文本由下方的预览面板展示(支持换行),description 列无法换行故移除
635
+ };
636
+ });
608
637
 
609
638
  const customLabel = previousAnswer && !q.options.includes(previousAnswer)
610
639
  ? `✏️ 自定义输入 - 上次选择`
@@ -639,14 +668,46 @@ async function showQuestionTUI(
639
668
  description: (s) => theme.fg("muted", s),
640
669
  scrollInfo: (s) => theme.fg("dim", s),
641
670
  noMatch: (s) => theme.fg("warning", s),
671
+ }, {
672
+ minPrimaryColumnWidth: 30,
673
+ maxPrimaryColumnWidth: maxOptWidth + 2,
674
+ truncatePrimary: ({ text, maxWidth }) => truncateToWidth(text, maxWidth, "..."),
642
675
  });
643
676
  selectList.onSelect = (item) => done(item.value);
644
677
  selectList.onCancel = () => done(null);
645
678
  container.addChild(selectList);
646
679
 
680
+ // 完整选项预览面板(支持换行,展示当前选中选项的完整文本)
681
+ const previewWidth = Math.max(30, termWidth - 8);
682
+ const previewText = new Text("", 0, 0);
683
+ container.addChild(new Spacer(1));
684
+ container.addChild(previewText);
685
+
686
+ // 初始化预览为第一个选项
687
+ if (q.options.length > 0) {
688
+ const initialWrapped = wrapTextWithAnsi(q.options[0], previewWidth);
689
+ previewText.setText(
690
+ initialWrapped.map(l => theme.fg("dim", ` ${l}`)).join("\n")
691
+ );
692
+ }
693
+
694
+ selectList.onSelectionChange = (item) => {
695
+ if (item.value.startsWith("opt-")) {
696
+ const idx = parseInt(item.value.replace("opt-", ""), 10);
697
+ const fullText = q.options[idx];
698
+ const wrapped = wrapTextWithAnsi(fullText, previewWidth);
699
+ previewText.setText(
700
+ wrapped.map(l => theme.fg("dim", ` ${l}`)).join("\n")
701
+ );
702
+ } else {
703
+ previewText.setText("");
704
+ }
705
+ tui.requestRender();
706
+ };
707
+
647
708
  container.addChild(new Spacer(1));
648
709
  const hint = backable && currentIndex > 1
649
- ? " ↑↓ 导航 • Enter 选择 • 选择←返回上一题 • Esc 取消全部评审"
710
+ ? " ↑↓ 导航 • Enter 选择 • Ctrl+Shift+← 返回上一题 • Esc 取消全部评审"
650
711
  : " ↑↓ 导航 • Enter 选择 • Esc 取消全部评审";
651
712
  container.addChild(
652
713
  new Text(theme.fg("dim", hint), 0, 0),
@@ -657,6 +718,11 @@ async function showQuestionTUI(
657
718
  render: (w) => container.render(w),
658
719
  invalidate: () => container.invalidate(),
659
720
  handleInput: (data) => {
721
+ // Ctrl+Shift+← → 返回上一题(SelectList 不处理该键,需自行拦截)
722
+ if (backable && currentIndex > 1 && matchesKey(data, Key.ctrlShift("left"))) {
723
+ done("__BACK__");
724
+ return;
725
+ }
660
726
  selectList.handleInput(data);
661
727
  tui.requestRender();
662
728
  },
@@ -205,7 +205,12 @@ export function uiConfirm(
205
205
  // ── Input (replaces ctx.ui.input) ────────────────────────────
206
206
 
207
207
  /**
208
- * Show an input dialog with proper wrapping.
208
+ * Show an input dialog with proper wrapping and live preview.
209
+ *
210
+ * Features:
211
+ * - 实时换行预览:输入框上方显示完整的换行预览(跟随输入实时更新)
212
+ * - 方向键 ←/→ 可正常移动光标编辑已有内容
213
+ *
209
214
  * Returns the entered string, or BACK_MARKER on back, or undefined on cancel.
210
215
  * When backable=true, supports Ctrl+Shift+← for back and Ctrl+Shift+→ for submit+next.
211
216
  */
@@ -229,6 +234,11 @@ export function uiInput(
229
234
  }
230
235
  container.addChild(new Spacer(1));
231
236
 
237
+ // 实时换行预览区域(在输入框上方)
238
+ const previewText = new Text("", 0, 0);
239
+ container.addChild(previewText);
240
+ container.addChild(new Spacer(1));
241
+
232
242
  const input = new Input(placeholder ?? "", width - 2);
233
243
  if (initialValue) {
234
244
  input.setValue(initialValue);
@@ -255,6 +265,7 @@ export function uiInput(
255
265
  render: (w) => container.render(w),
256
266
  invalidate: () => container.invalidate(),
257
267
  handleInput: (data) => {
268
+
258
269
  // Intercept back/next keys before passing to Input
259
270
  if (backable) {
260
271
  // Ctrl+Shift+← → go back to previous question
@@ -269,6 +280,19 @@ export function uiInput(
269
280
  }
270
281
  }
271
282
  input.handleInput(data);
283
+
284
+ // 读取更新后的 value,更新预览
285
+ const val = input.getValue();
286
+ if (val.length > 0) {
287
+ const wrapped = wrapTextWithAnsi(val, width - 4);
288
+ const previewContent = wrapped
289
+ .map(l => theme.fg("dim", ` ${l}`))
290
+ .join("\n");
291
+ previewText.setText(previewContent);
292
+ } else {
293
+ previewText.setText("");
294
+ }
295
+
272
296
  tui.requestRender();
273
297
  },
274
298
  };
@@ -362,7 +386,7 @@ function formatDurationFull(ms: number): string {
362
386
  return `${m}m${s}s`;
363
387
  }
364
388
 
365
- function formatTimeout(ms: number): string {
389
+ export function formatTimeout(ms: number): string {
366
390
  const m = Math.floor(ms / 60000);
367
391
  const s = Math.floor((ms % 60000) / 1000);
368
392
  return s > 0 ? `${m}m${s}s` : `${m}m`;
@@ -462,8 +486,13 @@ function buildWidgetLines(state: WorkflowWidgetState, theme: Theme, expanded: bo
462
486
  loopStr = dim(theme, ` · 第 ${s.loopCount} 次循环`);
463
487
  } else if (s.maxLoops != null) {
464
488
  if (isRunning) {
465
- // Immediately show 1 次循环 when loop-group starts
466
- loopStr = dim(theme, ` · 第 1 次循环`);
489
+ // loop-group 开始运行时,loopCount 已经通过 executeLoopGroup 在循环开头设置了,
490
+ // 所以不需要 fallback 显示"第 1 次循环"
491
+ // 直接使用 s.loopCount 的值
492
+ if (s.loopCount == null || s.loopCount === 0) {
493
+ // 安全 fallback(理论上不会走到这里)
494
+ loopStr = dim(theme, ` · 第 1 次循环`);
495
+ }
467
496
  } else if (isPending) {
468
497
  loopStr = dim(theme, ` · 第 0 次循环`);
469
498
  }
@@ -516,9 +545,32 @@ function buildWidgetLines(state: WorkflowWidgetState, theme: Theme, expanded: bo
516
545
  ? theme.fg("accent", spinnerFrame())
517
546
  : dim(theme, "◦");
518
547
 
519
- // Agent line: " |__ ✓ worker ·"
548
+ // Agent line: " |__ ✓ worker · (52.6s/超时时间60m)"
549
+ // Build inline duration and timeout info for sub-step
550
+ let subDurStr = "";
551
+ let subTimeoutStr = "";
552
+ let subDurClose = "";
553
+ let elapsedMs: number | undefined;
554
+ if (sub.startedAt) {
555
+ elapsedMs = Date.now() - sub.startedAt;
556
+ } else if (sub.durationMs != null) {
557
+ elapsedMs = sub.durationMs;
558
+ }
559
+ if (elapsedMs != null) {
560
+ subDurStr = dim(theme, ` (${formatDurationFull(elapsedMs)}`);
561
+ } else if (isSubRunning) {
562
+ subDurStr = dim(theme, ` (0s`);
563
+ }
564
+ // Extract timeout info from sub.detail (e.g. "超时时间60m")
565
+ // detail is set in runAgentWithProgress as `超时时间${formatTimeout(timeoutMs)}`
566
+ if (sub.detail && sub.detail.includes("超时时间")) {
567
+ subTimeoutStr = dim(theme, `/${sub.detail}`);
568
+ }
569
+ if (subDurStr) {
570
+ subDurClose = dim(theme, ")");
571
+ }
520
572
  const agentConnector = dim(theme, "|__");
521
- lines.push(`${agentIndent}${agentConnector} ${subIcon} ${sub.agent} ·`);
573
+ lines.push(`${agentIndent}${agentConnector} ${subIcon} ${sub.agent} ·${subDurStr}${subTimeoutStr}${subDurClose}`);
522
574
 
523
575
  // ── Children (tools, outputs, or "正在排队") ──
524
576
  const childItems: string[] = [];
@@ -536,7 +588,7 @@ function buildWidgetLines(state: WorkflowWidgetState, theme: Theme, expanded: bo
536
588
  childItems.push(`output:${o}`);
537
589
  }
538
590
  }
539
- if (childItems.length === 0 && sub.detail) {
591
+ if (childItems.length === 0 && sub.detail && !sub.detail.includes("超时时间")) {
540
592
  childItems.push(sub.detail);
541
593
  }
542
594
  }