@huseynsnmz/pi-board 0.15.3 → 0.17.0

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 (216) hide show
  1. package/CHANGELOG.md +24 -4
  2. package/CONTRIBUTING.md +7 -0
  3. package/README.md +55 -419
  4. package/SECURITY.md +19 -0
  5. package/docs/how-it-works.md +31 -0
  6. package/docs/reference.md +277 -0
  7. package/package.json +5 -11
  8. package/src/commands/agent-board.ts +26 -2
  9. package/src/commands/attach-flow.ts +4 -0
  10. package/src/core/double-press.mjs +6 -2
  11. package/src/core/slash-commands.mjs +52 -0
  12. package/src/index.ts +1 -1
  13. package/src/runtime/service.mjs +12 -0
  14. package/src/ui/dashboard.ts +59 -25
  15. package/src/ui/pty-attach.ts +8 -3
  16. package/IMPLEMENTATION_PLAN.md +0 -920
  17. package/PRD.md +0 -484
  18. package/PROGRESS.md +0 -142
  19. package/VERIFY.md +0 -115
  20. package/docs/BATCH_SELECTION_READ_FLOW.md +0 -277
  21. package/docs/EXPLORATION.md +0 -187
  22. package/docs/PTY_ATTACH_IMPLEMENTATION_PLAN.md +0 -581
  23. package/docs/RELEASE.md +0 -148
  24. package/docs/superpowers/plans/2026-08-15-screenlog-gc.md +0 -704
  25. package/docs/superpowers/plans/2026-08-16-attach-double-cursor-jiggle-retry.md +0 -499
  26. package/docs/superpowers/plans/2026-08-21-attach-coldstart-jiggle-rearm.md +0 -797
  27. package/docs/superpowers/plans/2026-08-21-dashboard-keypress-lag.md +0 -366
  28. package/docs/superpowers/plans/2026-08-21-issue-14-needs-input-no-auto-done.md +0 -576
  29. package/docs/superpowers/plans/2026-08-22-jiggle-shrink-and-hold.md +0 -337
  30. package/docs/superpowers/plans/2026-08-22-launch-cwd-favorites.md +0 -916
  31. package/docs/superpowers/plans/2026-08-22-question-tool-grouping.md +0 -211
  32. package/docs/superpowers/plans/2026-08-22-rename-state-labels.md +0 -182
  33. package/docs/superpowers/plans/2026-08-22-stable-list-order.md +0 -51
  34. package/docs/superpowers/plans/2026-08-27-locks-acquirelock-spin.md +0 -614
  35. package/docs/superpowers/plans/2026-08-27-pty-runner-test-flaky.md +0 -32
  36. package/docs/superpowers/plans/2026-08-29-code-refs-badges.md +0 -223
  37. package/docs/superpowers/plans/2026-08-30-circular-navigation.md +0 -308
  38. package/docs/superpowers/plans/2026-08-30-post-exit-timing-fix.md +0 -37
  39. package/docs/superpowers/plans/2026-08-30-pty-attach-quality-debt.md +0 -311
  40. package/docs/superpowers/plans/2026-08-30-readme-v2.md +0 -294
  41. package/docs/superpowers/plans/2026-09-01-attach-detach-gate-cursor-anchor.md +0 -284
  42. package/docs/superpowers/plans/2026-09-02-attach-detach-editor-state.md +0 -722
  43. package/docs/superpowers/plans/2026-09-03-code-refs-pr-backlink-narrow.md +0 -551
  44. package/docs/superpowers/plans/2026-09-03-detach-gate-glyph-fallback.md +0 -146
  45. package/docs/superpowers/plans/2026-09-04-evidence-outputpreview.md +0 -209
  46. package/docs/superpowers/plans/2026-09-04-warm-host-reclaim.md +0 -796
  47. package/docs/superpowers/plans/2026-09-05-issue-13-drainnextfollowup-pty-probe.md +0 -114
  48. package/docs/superpowers/plans/2026-09-05-issue-38-windows-wezterm-ime-cursor.md +0 -73
  49. package/docs/superpowers/plans/2026-09-05-issue-39-truncate-codepoint-boundary.md +0 -143
  50. package/docs/superpowers/plans/2026-09-05-issue-61-mention-fallback-guards.md +0 -226
  51. package/docs/superpowers/plans/2026-09-05-issue-63-flaky-manual-completion.md +0 -87
  52. package/docs/superpowers/plans/2026-09-05-issue-64-changelog-release-helper.md +0 -53
  53. package/docs/superpowers/plans/2026-09-05-pty-host-stacking-sock-race.md +0 -731
  54. package/docs/superpowers/plans/2026-09-08-attach-ctrl-left-detach.md +0 -30
  55. package/docs/superpowers/plans/2026-09-08-dashboard-shrink-repaint.md +0 -68
  56. package/docs/superpowers/plans/2026-09-08-issue-11-attach-runtime-desync-heal.md +0 -917
  57. package/docs/superpowers/plans/2026-09-08-legacy-stale-host-recovery.md +0 -125
  58. package/docs/superpowers/plans/2026-09-08-spawn-async-error-swallow.md +0 -56
  59. package/docs/superpowers/plans/2026-09-08-stale-model-attach-guard.md +0 -96
  60. package/docs/superpowers/plans/2026-09-09-claimpid-blocks-replace.md +0 -267
  61. package/docs/superpowers/plans/2026-09-09-harden-runner-architecture.md +0 -603
  62. package/docs/superpowers/plans/2026-09-09-single-writer-completion.md +0 -252
  63. package/docs/superpowers/plans/2026-09-10-reader-consistency.md +0 -115
  64. package/docs/superpowers/plans/2026-09-14-attach-cursor-dectcem-gate.md +0 -469
  65. package/docs/superpowers/plans/2026-09-14-attach-snapshot.md +0 -92
  66. package/docs/superpowers/plans/2026-09-14-host-meta-orphan-lock.md +0 -771
  67. package/docs/superpowers/plans/2026-09-14-issue-106-terminal-frame-cognition.md +0 -299
  68. package/docs/superpowers/plans/2026-09-14-issue-113-foreground-preview-race.md +0 -609
  69. package/docs/superpowers/plans/2026-09-14-terminal-model.md +0 -145
  70. package/docs/superpowers/plans/2026-09-15-coordinator-pipe-root-normalize.md +0 -224
  71. package/docs/superpowers/plans/2026-09-15-lease-publish-eprem-reclaim.md +0 -341
  72. package/docs/superpowers/plans/2026-09-18-detach-anchor-reporter-endpoint.md +0 -875
  73. package/docs/superpowers/plans/2026-09-20-control-lifecycle.md +0 -116
  74. package/docs/superpowers/plans/2026-09-20-issue-121-perf-gate-out-of-coverage.md +0 -517
  75. package/docs/superpowers/plans/2026-09-23-a5-mid-stream-assertion.md +0 -25
  76. package/docs/superpowers/plans/2026-09-23-cwd-browse-tab-drill.md +0 -393
  77. package/docs/superpowers/plans/2026-09-23-terminal-query-forwarding.md +0 -51
  78. package/docs/superpowers/plans/2026-09-23-unconditional-detach-key.md +0 -58
  79. package/docs/superpowers/plans/2026-09-24-control-reconcile-flake.md +0 -698
  80. package/docs/superpowers/plans/2026-09-24-probe-attached-clients.md +0 -605
  81. package/docs/superpowers/plans/2026-09-29-issue-145-holding-semantic-state.md +0 -1183
  82. package/docs/superpowers/plans/2026-09-29-issue-145-user-verification.md +0 -66
  83. package/docs/superpowers/plans/2026-09-30-board-delete-key-x-y-confirm.md +0 -649
  84. package/docs/superpowers/plans/2026-09-30-issue-153-runner-stop-boot-window.md +0 -618
  85. package/docs/superpowers/plans/2026-09-30-issue-95-test-discipline.md +0 -555
  86. package/docs/superpowers/plans/2026-09-30-publish-dispatch-tag-check.md +0 -259
  87. package/docs/superpowers/plans/2026-10-06-attach-settle-scheme-delivery.md +0 -751
  88. package/docs/superpowers/plans/2026-10-09-issue-167-attach-middle-paste.md +0 -509
  89. package/docs/superpowers/plans/2026-10-09-issue-169-fullscreen-mouse-ownership.md +0 -346
  90. package/docs/superpowers/plans/2026-10-10-attach-delete-in-place.md +0 -832
  91. package/docs/superpowers/plans/2026-10-10-issue-174-selection-mirror-paste-source.md +0 -510
  92. package/docs/superpowers/plans/2026-10-14-issue-170-attach-reply-guard.md +0 -771
  93. package/docs/superpowers/plans/2026-10-14-issue-178-accept-registration-race.md +0 -1257
  94. package/docs/superpowers/plans/2026-10-14-issue-184-snapshot-window-contract.md +0 -657
  95. package/docs/superpowers/plans/2026-10-14-issue-185-attach-wheel-alt-screen.md +0 -47
  96. package/docs/superpowers/plans/2026-10-14-issue-187-motionless-drag.md +0 -931
  97. package/docs/superpowers/plans/2026-10-14-issue-189-mixed-input-segments.md +0 -823
  98. package/docs/superpowers/plans/2026-10-15-issue-188-x10-mouse-release.md +0 -526
  99. package/docs/superpowers/plans/2026-10-16-issue-198-attach-wheel-forward-fallback.md +0 -32
  100. package/docs/superpowers/plans/2026-10-18-attach-wheel-replay-pseudo-scrollback.md +0 -437
  101. package/docs/superpowers/plans/2026-10-19-issue-213-reply-guard-csi-key.md +0 -257
  102. package/docs/superpowers/plans/2026-10-19-issue-214-attach-forward-alt-modifier.md +0 -202
  103. package/docs/superpowers/plans/2026-10-21-issue-221-ask-user-question.md +0 -198
  104. package/docs/superpowers/plans/2026-10-23-issue-222-attach-ready.md +0 -290
  105. package/docs/superpowers/plans/2026-10-23-issue-225-monotonic-foreground-state.md +0 -450
  106. package/docs/superpowers/plans/2026-10-23-issue-229-vim-jkl.md +0 -449
  107. package/docs/superpowers/plans/2026-10-23-issue-232-attach-bg-only-cells.md +0 -153
  108. package/docs/superpowers/plans/2026-10-23-lease-heartbeat-staleness.md +0 -537
  109. package/docs/superpowers/plans/2026-10-23-selected-row-highlight.md +0 -405
  110. package/docs/superpowers/plans/2026-10-24-issue-217-windows-pid-reuse-stopping.md +0 -367
  111. package/docs/superpowers/plans/2026-10-24-issue-230-warm-host-background-runs.md +0 -806
  112. package/docs/superpowers/plans/2026-10-24-issue-238-windows-process-identity-token.md +0 -490
  113. package/docs/superpowers/plans/2026-10-24-issue-241-coordinator-root-exit.md +0 -461
  114. package/docs/superpowers/plans/2026-10-25-issue-239-coordinator-replacement-identity.md +0 -504
  115. package/docs/superpowers/plans/2026-10-26-issue-245-key-hints-doc-sync.md +0 -244
  116. package/docs/superpowers/plans/2026-10-27-issue-172-fullscreen-selection-clear.md +0 -344
  117. package/docs/superpowers/plans/2026-10-27-issue-252-select-key-hints.md +0 -209
  118. package/docs/superpowers/plans/2026-10-28-issue-255-coordinator-endpoint-zombie.md +0 -442
  119. package/docs/superpowers/plans/2026-10-28-issue-257-legacy-pty-runner-endpoint-cleanup.md +0 -256
  120. package/docs/superpowers/plans/2026-11-03-cwd-favorites-full-pool.md +0 -266
  121. package/docs/superpowers/specs/2026-08-15-screenlog-gc-design.md +0 -105
  122. package/docs/superpowers/specs/2026-08-16-attach-double-cursor-jiggle-retry-design.md +0 -142
  123. package/docs/superpowers/specs/2026-08-21-attach-coldstart-jiggle-rearm-design.md +0 -110
  124. package/docs/superpowers/specs/2026-08-21-dashboard-keypress-lag-design.md +0 -59
  125. package/docs/superpowers/specs/2026-08-21-issue-14-needs-input-no-auto-done-design.md +0 -109
  126. package/docs/superpowers/specs/2026-08-22-cwd-favorites-design.md +0 -131
  127. package/docs/superpowers/specs/2026-08-22-jiggle-shrink-and-hold-design.md +0 -111
  128. package/docs/superpowers/specs/2026-08-22-question-tool-grouping-design.md +0 -114
  129. package/docs/superpowers/specs/2026-08-22-rename-state-labels-design.md +0 -80
  130. package/docs/superpowers/specs/2026-08-27-locks-acquirelock-spin-design.md +0 -97
  131. package/docs/superpowers/specs/2026-08-27-pty-runner-test-flaky-design.md +0 -35
  132. package/docs/superpowers/specs/2026-08-29-code-refs-badges-design.md +0 -128
  133. package/docs/superpowers/specs/2026-08-30-circular-navigation-design.md +0 -47
  134. package/docs/superpowers/specs/2026-08-30-eprm-atomicwrite-race-design.md +0 -77
  135. package/docs/superpowers/specs/2026-08-30-post-exit-timing-fix-design.md +0 -80
  136. package/docs/superpowers/specs/2026-08-30-pty-attach-quality-debt-design.md +0 -105
  137. package/docs/superpowers/specs/2026-08-30-readme-v2-design.md +0 -115
  138. package/docs/superpowers/specs/2026-09-01-attach-detach-gate-cursor-anchor-design.md +0 -78
  139. package/docs/superpowers/specs/2026-09-02-attach-detach-editor-state-design.md +0 -120
  140. package/docs/superpowers/specs/2026-09-03-code-refs-pr-backlink-narrow-design.md +0 -92
  141. package/docs/superpowers/specs/2026-09-03-detach-gate-glyph-fallback-design.md +0 -99
  142. package/docs/superpowers/specs/2026-09-04-evidence-outputpreview-design.md +0 -50
  143. package/docs/superpowers/specs/2026-09-04-warm-host-reclaim-design.md +0 -106
  144. package/docs/superpowers/specs/2026-09-05-issue-13-drainnextfollowup-pty-probe-design.md +0 -64
  145. package/docs/superpowers/specs/2026-09-05-issue-38-windows-wezterm-ime-design.md +0 -48
  146. package/docs/superpowers/specs/2026-09-05-issue-39-truncate-codepoint-boundary-design.md +0 -64
  147. package/docs/superpowers/specs/2026-09-05-issue-61-mention-fallback-design.md +0 -71
  148. package/docs/superpowers/specs/2026-09-05-issue-63-flaky-manual-completion-design.md +0 -49
  149. package/docs/superpowers/specs/2026-09-05-issue-64-changelog-helper-design.md +0 -76
  150. package/docs/superpowers/specs/2026-09-05-pty-host-stacking-sock-race-design.md +0 -510
  151. package/docs/superpowers/specs/2026-09-07-issue-11-attach-runtime-desync-heal-design.md +0 -130
  152. package/docs/superpowers/specs/2026-09-08-attach-ctrl-left-detach-design.md +0 -58
  153. package/docs/superpowers/specs/2026-09-08-dashboard-shrink-repaint-design.md +0 -52
  154. package/docs/superpowers/specs/2026-09-08-legacy-stale-host-recovery-design.md +0 -87
  155. package/docs/superpowers/specs/2026-09-08-spawn-async-error-swallow-design.md +0 -56
  156. package/docs/superpowers/specs/2026-09-08-stale-model-attach-guard-design.md +0 -79
  157. package/docs/superpowers/specs/2026-09-09-claimpid-blocks-replace-design.md +0 -147
  158. package/docs/superpowers/specs/2026-09-09-harden-runner-architecture-design.md +0 -304
  159. package/docs/superpowers/specs/2026-09-14-attach-cursor-dectcem-gate-design.md +0 -114
  160. package/docs/superpowers/specs/2026-09-14-host-meta-orphan-lock-design.md +0 -120
  161. package/docs/superpowers/specs/2026-09-14-issue-106-terminal-frame-cognition-design.md +0 -146
  162. package/docs/superpowers/specs/2026-09-14-issue-113-foreground-preview-race-design.md +0 -116
  163. package/docs/superpowers/specs/2026-09-15-coordinator-pipe-root-normalize-design.md +0 -84
  164. package/docs/superpowers/specs/2026-09-15-lease-publish-eprem-reclaim-design.md +0 -92
  165. package/docs/superpowers/specs/2026-09-18-detach-anchor-reporter-endpoint-design.md +0 -123
  166. package/docs/superpowers/specs/2026-09-20-issue-121-perf-gate-out-of-coverage-design.md +0 -204
  167. package/docs/superpowers/specs/2026-09-23-a5-mid-stream-assertion-design.md +0 -43
  168. package/docs/superpowers/specs/2026-09-23-cwd-browse-tab-drill-design.md +0 -95
  169. package/docs/superpowers/specs/2026-09-23-terminal-query-forwarding-design.md +0 -63
  170. package/docs/superpowers/specs/2026-09-23-unconditional-detach-key-design.md +0 -57
  171. package/docs/superpowers/specs/2026-09-24-control-reconcile-flake-design.md +0 -98
  172. package/docs/superpowers/specs/2026-09-24-probe-attached-clients-design.md +0 -190
  173. package/docs/superpowers/specs/2026-09-29-issue-145-holding-semantic-state-design.md +0 -394
  174. package/docs/superpowers/specs/2026-09-30-board-delete-key-x-y-confirm-design.md +0 -86
  175. package/docs/superpowers/specs/2026-09-30-issue-153-runner-stop-boot-window-design.md +0 -75
  176. package/docs/superpowers/specs/2026-09-30-issue-95-test-discipline-design.md +0 -226
  177. package/docs/superpowers/specs/2026-09-30-publish-dispatch-tag-check-design.md +0 -129
  178. package/docs/superpowers/specs/2026-10-06-attach-settle-scheme-delivery-design.md +0 -196
  179. package/docs/superpowers/specs/2026-10-09-issue-167-attach-middle-paste-design.md +0 -125
  180. package/docs/superpowers/specs/2026-10-09-issue-169-fullscreen-mouse-ownership-design.md +0 -130
  181. package/docs/superpowers/specs/2026-10-10-attach-delete-in-place-design.md +0 -224
  182. package/docs/superpowers/specs/2026-10-10-issue-174-selection-mirror-paste-source-design.md +0 -154
  183. package/docs/superpowers/specs/2026-10-14-issue-170-attach-reply-guard-design.md +0 -195
  184. package/docs/superpowers/specs/2026-10-14-issue-178-accept-registration-race-design.md +0 -287
  185. package/docs/superpowers/specs/2026-10-14-issue-184-snapshot-window-contract-design.md +0 -288
  186. package/docs/superpowers/specs/2026-10-14-issue-185-attach-wheel-alt-screen-design.md +0 -126
  187. package/docs/superpowers/specs/2026-10-14-issue-187-motionless-drag-design.md +0 -269
  188. package/docs/superpowers/specs/2026-10-14-issue-189-mixed-input-segments-design.md +0 -369
  189. package/docs/superpowers/specs/2026-10-15-issue-188-x10-mouse-release-design.md +0 -252
  190. package/docs/superpowers/specs/2026-10-16-issue-198-attach-wheel-forward-fallback-design.md +0 -67
  191. package/docs/superpowers/specs/2026-10-18-attach-wheel-replay-pseudo-scrollback-design.md +0 -250
  192. package/docs/superpowers/specs/2026-10-19-issue-213-reply-guard-csi-key-design.md +0 -165
  193. package/docs/superpowers/specs/2026-10-19-issue-214-attach-forward-alt-modifier-design.md +0 -131
  194. package/docs/superpowers/specs/2026-10-20-issue-221-ask-user-question-design.md +0 -270
  195. package/docs/superpowers/specs/2026-10-22-issue-225-monotonic-foreground-state-design.md +0 -225
  196. package/docs/superpowers/specs/2026-10-23-issue-222-attach-ready-design.md +0 -204
  197. package/docs/superpowers/specs/2026-10-23-issue-229-vim-jkl-design.md +0 -90
  198. package/docs/superpowers/specs/2026-10-23-issue-232-attach-bg-only-cells-design.md +0 -146
  199. package/docs/superpowers/specs/2026-10-23-lease-heartbeat-staleness-design.md +0 -271
  200. package/docs/superpowers/specs/2026-10-23-selected-row-highlight-design.md +0 -191
  201. package/docs/superpowers/specs/2026-10-24-issue-217-windows-pid-reuse-stopping-design.md +0 -334
  202. package/docs/superpowers/specs/2026-10-24-issue-230-warm-host-background-runs-design.md +0 -283
  203. package/docs/superpowers/specs/2026-10-24-issue-238-windows-process-identity-token-design.md +0 -286
  204. package/docs/superpowers/specs/2026-10-24-issue-241-coordinator-root-exit-design.md +0 -381
  205. package/docs/superpowers/specs/2026-10-25-issue-239-coordinator-replacement-identity-design.md +0 -331
  206. package/docs/superpowers/specs/2026-10-26-issue-245-key-hints-doc-sync-design.md +0 -72
  207. package/docs/superpowers/specs/2026-10-27-issue-172-fullscreen-selection-clear-design.md +0 -248
  208. package/docs/superpowers/specs/2026-10-27-issue-252-select-key-hints-design.md +0 -95
  209. package/docs/superpowers/specs/2026-10-28-issue-255-coordinator-endpoint-zombie-design.md +0 -380
  210. package/docs/superpowers/specs/2026-10-28-issue-257-legacy-pty-runner-endpoint-cleanup-design.md +0 -197
  211. package/docs/superpowers/specs/2026-11-03-cwd-favorites-full-pool-design.md +0 -106
  212. package/scripts/budget-audit.mjs +0 -135
  213. package/scripts/release.mjs +0 -446
  214. package/scripts/release_helper.mjs +0 -309
  215. package/scripts/run-perf-gate.mjs +0 -40
  216. package/scripts/run-tests.mjs +0 -375
@@ -1,257 +0,0 @@
1
- # reply guard CSI-key collision — Implementation Plan
2
-
3
- > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
-
5
- **Goal:** Stop the reply guard from swallowing kitty/modifyOtherKeys PageDown (`\x1b[6;<mods>u` / `\x1b[6;<mods>~`) while keeping every genuine cell-size reply fragment armed.
6
-
7
- **Architecture:** One new pure predicate in `src/core/terminal-reply-guard.mjs`: a *complete* CSI key sequence is user input and must be classified before the `REPLY_HEADS` prefix loop. Every fragment shape the guard protects today has no `u`/`~` terminator, so the predicate cannot weaken #170's protection.
8
-
9
- **Tech Stack:** ESM `.mjs` core module, `node:test` + `assert/strict`, TypeScript component (unchanged), `@earendil-works/pi-tui` (unchanged).
10
-
11
- **Spec:** `docs/superpowers/specs/2026-10-19-issue-213-reply-guard-csi-key-design.md`
12
-
13
- ## Global Constraints
14
-
15
- - Tabs for indentation; English comments and commit messages (repo AGENTS.md).
16
- - No new dependencies.
17
- - `npm run verify` must pass on CI (typecheck + perf gate + tests + coverage + pack dry-run). Locally only the targeted suites plus `typecheck`/`pack:dry` are authoritative — this Windows host has a pre-existing ~72-test baseline failure set that also fails on `main`.
18
- - New test names carry the `#213-` prefix (spec §6 "验收 ID 作用域") to avoid colliding with the `A1:`/`A2:`/… names already in `test/terminal-reply-guard.test.mjs`.
19
- - Do not change `REPLY_HEADS`, the absorption window (250 ms / 64 bytes), or the kill switch.
20
-
21
- ## Review Focus
22
-
23
- - **A genuine cell-size fragment that looks like a key** — `\x1b[6;` and `\x1b[6;24` (no terminator) must still `reply-head` and arm; only `u`/`~`-terminated sequences are keys.
24
- - **A complete reply that looks like a key** — `\x1b[6;24;80t` must still be `reply-complete`/drop (DROP_SHAPES runs first).
25
- - **Other kitty functional keys that share a head prefix** — `\x1b[6;1:2;65u` (alternate keys) must also be treated as a key, which is why the predicate is `(?:[;:]\d+)*` and not the narrower form in the issue text.
26
- - **A key that arrives while the guard is armed** — an armed window must not swallow it silently; the state machine's `armed + other` path drops the stale buffer and passes the event.
27
- - **Plain text right after a key** — forwarding must stay byte-exact (no duplication, no reordering).
28
-
29
- ---
30
-
31
- ### Task 1: Classify complete CSI keys before the head prefixes
32
-
33
- **Files:**
34
- - Modify: `src/core/terminal-reply-guard.mjs` (add `CSI_KEY_SEQUENCE` near the other shape constants, and one branch in `classifyReplyEvent`)
35
- - Test: `test/terminal-reply-guard.test.mjs`
36
-
37
- **Interfaces:**
38
- - Produces: `classifyReplyEvent(data)` now returns `{ kind: "other" }` for `CSI <num>[;<mod>[:<event>][;<alt>]]{u,~}`. No exported symbol changes.
39
-
40
- - [ ] **Step 1: Write the failing tests**
41
-
42
- Append to `test/terminal-reply-guard.test.mjs`:
43
-
44
- ```js
45
- // Issue #213: kitty / modifyOtherKeys PageDown shares the cell-size head prefix.
46
- test("#213-A1: a complete CSI key sequence is user input, never a reply head", () => {
47
- for (const seq of ["\x1b[6;1u", "\x1b[6;5u", "\x1b[6;1:2u", "\x1b[6;1:2;65u", "\x1b[6;5~", "\x1b[6;2~"]) {
48
- assert.deepEqual(classifyReplyEvent(seq), { kind: "other" }, `${JSON.stringify(seq)} must be a key`);
49
- }
50
- });
51
-
52
- test("#213-A2: neighbouring keys are unaffected", () => {
53
- for (const seq of ["\x1b[5;1u", "\x1b[5;5~", "\x1b[57422u", "\x1b[57421u", "\x1b[3;5~"]) {
54
- assert.deepEqual(classifyReplyEvent(seq), { kind: "other" }, `${JSON.stringify(seq)} must stay a key`);
55
- }
56
- });
57
-
58
- test("#213-A3: genuine cell-size fragments still arm", () => {
59
- for (const seq of ["\x1b[6;", "\x1b[6;24"]) {
60
- assert.deepEqual(classifyReplyEvent(seq), { kind: "reply-head", family: "cell-size" });
61
- }
62
- });
63
-
64
- test("#213-A4: a complete cell-size reply still drops", () => {
65
- assert.deepEqual(classifyReplyEvent("\x1b[6;24;80t"), { kind: "reply-complete", family: "cell-size", action: "drop" });
66
- });
67
- ```
68
-
69
- - [ ] **Step 2: Run the tests to verify they fail**
70
-
71
- Run: `node --test test/terminal-reply-guard.test.mjs`
72
- Expected: FAIL — `#213-A1` reports `{ kind: "reply-head", family: "cell-size" }` for `\x1b[6;1u` / `\x1b[6;5~`.
73
-
74
- - [ ] **Step 3: Implement the predicate**
75
-
76
- In `src/core/terminal-reply-guard.mjs`, next to `REPLY_HEADS`:
77
-
78
- ```js
79
- // Issue #213: kitty keyboard protocol keys that carry a modifier use the legacy
80
- // functional-key encoding `CSI <num>;<mods>u`, and PageDown's legacy number IS 6
81
- // (`\x1b[6;1u` = no modifier, mods field is bitmask+1). modifyOtherKeys uses the same
82
- // prefix with `~` (`\x1b[6;5~`). Both therefore share the cell-size head prefix
83
- // (`\x1b[6;`), which used to arm the guard and swallow the key — asymmetric with
84
- // PageUp (key number 5) and invisible to the previous tests.
85
- //
86
- // A COMPLETE CSI key is user input, never a reply fragment: check it before the head
87
- // prefixes. Only the terminator decides — geniune cell-size fragments (`\x1b[6;`,
88
- // `\x1b[6;24`) carry no `u`/`~` and still arm below. `(?:[;:]\d+)*` also covers the
89
- // alternate-key form (`\x1b[6;1:2;65u`), which a narrower `(?:;\d+)?(?::\d+)?` misses.
90
- const CSI_KEY_SEQUENCE = /^\x1b\[\d+(?:[;:]\d+)*[u~]$/;
91
- ```
92
-
93
- and in `classifyReplyEvent`, after the `FORWARD_SHAPES` loop and **before** the `REPLY_HEADS` loop:
94
-
95
- ```js
96
- // Issue #213: a complete CSI key wins over the head prefixes (see CSI_KEY_SEQUENCE).
97
- if (CSI_KEY_SEQUENCE.test(data)) return { kind: "other" };
98
- ```
99
-
100
- - [ ] **Step 4: Run the tests to verify they pass**
101
-
102
- Run: `node --test test/terminal-reply-guard.test.mjs test/pty-scroll.test.mjs`
103
- Expected: PASS (the four new tests plus the existing 15).
104
-
105
- - [ ] **Step 5: Commit**
106
-
107
- ```bash
108
- git add src/core/terminal-reply-guard.mjs test/terminal-reply-guard.test.mjs
109
- git commit -m "fix(attach): classify complete CSI keys before the reply-guard head prefixes (issue #213)"
110
- ```
111
-
112
- ---
113
-
114
- ### Task 2: Prove the key actually reaches the child
115
-
116
- **Files:**
117
- - Modify: `test-support/attach-reply-guard-smoke.ts` (new case + result fields)
118
- - Modify: `test/pty-attach-reply-guard.test.mjs` (assertions)
119
-
120
- **Interfaces:**
121
- - Consumes: `classifyReplyEvent` behaviour from Task 1 through the real `PtyAttachComponent.handleInput`.
122
- - Produces: `out.guardedPageDown: string[]`, `out.guardedPageDownThenText: string[]` in the smoke's JSON.
123
-
124
- - [ ] **Step 1: Write the failing assertions**
125
-
126
- In `test/pty-attach-reply-guard.test.mjs`:
127
-
128
- ```js
129
- assert.deepEqual(parsed.guardedPageDown, ["\x1b[6;1u", "\x1b[6;5~"], "kitty and modifyOtherKeys PageDown must be forwarded to the child, not swallowed");
130
- assert.deepEqual(parsed.guardedPageDownThenText, ["\x1b[6;1u", "x"], "text after a key must still be forwarded verbatim");
131
- ```
132
-
133
- - [ ] **Step 2: Run to verify it fails**
134
-
135
- Run: `node --test test/pty-attach-reply-guard.test.mjs`
136
- Expected: FAIL — `parsed.guardedPageDown` is `undefined`.
137
-
138
- - [ ] **Step 3: Implement the smoke case**
139
-
140
- In `test-support/attach-reply-guard-smoke.ts`, next to the existing `guardedFragments` case:
141
-
142
- ```ts
143
- // Issue #213: a complete CSI key must reach the child, not the absorption window.
144
- f.attach.handleInput("\x1b[6;1u");
145
- f.attach.handleInput("\x1b[6;5~");
146
- out.guardedPageDown = [...f.sent];
147
-
148
- // …and the bytes that follow a key are still forwarded verbatim.
149
- f.attach.handleInput("\x1b[6;1u");
150
- f.attach.handleInput("x");
151
- out.guardedPageDownThenText = [...f.sent];
152
- ```
153
-
154
- - [ ] **Step 4: Run to verify it passes**
155
-
156
- Run: `node --test test/pty-attach-reply-guard.test.mjs`
157
- Expected: PASS.
158
-
159
- - [ ] **Step 5: Commit**
160
-
161
- ```bash
162
- git add test-support/attach-reply-guard-smoke.ts test/pty-attach-reply-guard.test.mjs
163
- git commit -m "test(attach): pin that kitty PageDown reaches the child through the reply guard (issue #213)"
164
- ```
165
-
166
- ---
167
-
168
- ### Task 3: Retire the pinned invariant in the docs
169
-
170
- **Files:**
171
- - Modify: `src/core/terminal-reply-guard.mjs` (module header comment)
172
- - Modify: `docs/superpowers/specs/2026-10-14-issue-170-attach-reply-guard-design.md` (§3.3 design-points list)
173
-
174
- **Interfaces:**
175
- - Consumes: nothing at runtime; produces the text the A8 static check greps for.
176
-
177
- - [ ] **Step 1: Update the module header**
178
-
179
- In the `terminal-reply-guard.mjs` header, extend the paragraph that lists what arms a window with:
180
-
181
- ```
182
- * Issue #213: `\x1b[6;` is NOT a "can never be a key" prefix — kitty's functional-key
183
- * encoding puts PageDown at `CSI 6;<mods>u` (and modifyOtherKeys at `\x1b[6;<mods>~`).
184
- * The rule is therefore "a COMPLETE CSI key sequence (`u`/`~` terminated) beats the head
185
- * prefixes", not "these prefixes are never keys". Fragments without a terminator still arm.
186
- ```
187
-
188
- - [ ] **Step 2: Append the correction to the #170 design doc**
189
-
190
- In `docs/superpowers/specs/2026-10-14-issue-170-attach-reply-guard-design.md`, at the end of the §3.3 design-points list (do NOT rewrite L106 — append):
191
-
192
- ```markdown
193
- 7. **修正(issue #213)**:上一条「`ESC[6;` 永不可能是按键前缀」已被推翻 —— kitty 功能键编码
194
- 把 PageDown 放在 `CSI 6;<mods>u`(modifyOtherKeys 为 `\x1b[6;<mods>~`),与 cell-size 的
195
- `ESC[6;` 共享前缀。现行规则是「**完整 CSI 键序列(以 `u`/`~` 终结)优先于 head 前缀**」,
196
- 不带终结符的片段仍然武装。实现见 `CSI_KEY_SEQUENCE`。
197
- ```
198
-
199
- - [ ] **Step 3: Verify the A8 greps**
200
-
201
- Run:
202
- ```bash
203
- grep -n "complete CSI key" src/core/terminal-reply-guard.mjs
204
- grep -n "完整 CSI 键序列优先.*#213" docs/superpowers/specs/2026-10-14-issue-170-attach-reply-guard-design.md
205
- grep -c "can never be a key" src/core/terminal-reply-guard.mjs
206
- ```
207
- Expected: the first two each match at least once (the first inside the module header, not inside a function body); the third prints 0.
208
-
209
- - [ ] **Step 4: Commit**
210
-
211
- ```bash
212
- git add src/core/terminal-reply-guard.mjs docs/superpowers/specs/2026-10-14-issue-170-attach-reply-guard-design.md
213
- git commit -m "docs(attach): retire the 'ESC[6; is never a key' invariant (issue #213)"
214
- ```
215
-
216
- ---
217
-
218
- ### Task 4: Full gate
219
-
220
- **Files:** none (verification only).
221
-
222
- - [ ] **Step 1: Type/static checks**
223
-
224
- Run: `npm run typecheck && npm run pack:dry`
225
- Expected: both clean.
226
-
227
- - [ ] **Step 2: Targeted suites**
228
-
229
- Run: `node --test test/terminal-reply-guard.test.mjs test/pty-attach-reply-guard.test.mjs test/pty-scroll.test.mjs test/pty-attach-wheel.test.mjs`
230
- Expected: all green.
231
-
232
- - [ ] **Step 3: Baseline comparison**
233
-
234
- Run: `node --test test/pty-attach-*.test.mjs` and compare the failing set against the known Windows-host baseline (same set as on `main`; no new names).
235
- Expected: no new failures.
236
-
237
- - [ ] **Step 4: Commit**
238
-
239
- Nothing to commit — this task is the gate the PR description cites.
240
-
241
- ---
242
-
243
- ### Task 5: Manual verification (U1)
244
-
245
- **Files:** none.
246
-
247
- - [ ] **Step 1: Load the fix into a real pi**
248
-
249
- Per spec §6.1: `pi install "$(pwd)"` (README.md:54) or symlink the worktree to
250
- `~/.pi/agent/extensions/agent-board`, **not** the `~/.pi/agent/git/...` cache clone.
251
-
252
- - [ ] **Step 2: Restart pi completely** and open a new attach session.
253
-
254
- - [ ] **Step 3: Press PageDown in the attach view** (WezTerm + pi fullscreen).
255
- Expected: the panel scrolls one page backwards, mirroring PageUp; no swallowed key.
256
-
257
- - [ ] **Step 4: Record the outcome** in the issue (pass/fail + observed behaviour).
@@ -1,202 +0,0 @@
1
- # attach forward Alt modifier — Implementation Plan
2
-
3
- > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
-
5
- **Goal:** Preserve the Alt modifier when the attach view forwards a wheel notch to a fullscreen child, so Alt+wheel keeps its 5× accelerator.
6
-
7
- **Architecture:** `encodeWheelReport` gains an optional `alt` flag that sets SGR bit 3 (`+8`); `forwardWheelToChild` passes `event.alt === true` through. Nothing else changes — the local scroll path never rebuilds the report.
8
-
9
- **Tech Stack:** ESM `.mjs` pure helper, TypeScript component shell, `node:test` + `assert/strict`.
10
-
11
- **Spec:** `docs/superpowers/specs/2026-10-19-issue-214-attach-forward-alt-modifier-design.md`
12
-
13
- ## Global Constraints
14
-
15
- - Tabs for indentation; English comments and commit messages (repo AGENTS.md).
16
- - No new dependencies.
17
- - Local `npm run verify` cannot go green on this Windows host (pre-existing ~72-test baseline that also fails on `main`); the authoritative local gates are the targeted suites plus `npm run typecheck` and `npm run pack:dry`. CI is the final gate.
18
- - `alt` must be compared strictly (`=== true`), never by truthiness: a stray `1`/`"yes"` must not set bit 3.
19
- - Do not touch the local scroll path, the reply guard, or the input classifier.
20
-
21
- ## Review Focus
22
-
23
- - **A truthy non-boolean `alt`** — `alt: 1` and `alt: "yes"` must NOT set bit 3, because the component passes `event.alt === true` (an older pi-tui could hand over something unexpected).
24
- - **Plain wheel parity** — with no Alt, the encoded report must stay byte-identical to today's (`\x1b[<64;…M` / `\x1b[<65;…M`); the existing #198/#205 assertions must keep passing untouched.
25
- - **The forward hop is the only seam** — the legacy regular-mode path forwards the raw SGR unchanged and already carries Alt; a change that also rewrote that path would be a scope violation.
26
- - **Down direction** — `alt + down` is `73`, not `72`; an implementation that ORs the bit onto a fixed 64 would break down-scroll.
27
- - **Child-side consumption** — the byte must decode as `button & 8 !== 0` under pi-tui's `parseWheelEvent`; `72`/`73` satisfy that and `64`/`65` do not.
28
-
29
- ---
30
-
31
- ### Task 1: Encode the Alt bit in `encodeWheelReport`
32
-
33
- **Files:**
34
- - Modify: `src/core/pty-scroll.mjs` (`encodeWheelReport`, ~L370)
35
- - Test: `test/pty-scroll.test.mjs`
36
-
37
- **Interfaces:**
38
- - Produces: `encodeWheelReport(direction: "up"|"down", { col?: number, row?: number, alt?: boolean }) -> string`. Default `alt: false` keeps every existing call site identical.
39
-
40
- - [ ] **Step 1: Write the failing tests**
41
-
42
- Append to `test/pty-scroll.test.mjs`:
43
-
44
- ```js
45
- // Issue #214: Alt is SGR bit 3 (value 8); the child's pi-tui reads it to apply the
46
- // 5x wheel multiplier, so the forward hop must not strip it.
47
- test("#214-A1: Alt+wheel-up encodes button 72", () => {
48
- assert.equal(encodeWheelReport("up", { col: 5, row: 3, alt: true }), "\x1b[<72;5;3M");
49
- });
50
-
51
- test("#214-A2: Alt+wheel-down encodes button 73", () => {
52
- assert.equal(encodeWheelReport("down", { col: 5, row: 3, alt: true }), "\x1b[<73;5;3M");
53
- });
54
-
55
- test("#214-A3: without a strict boolean true, bit 3 stays clear", () => {
56
- for (const alt of [false, undefined, null, 0, 1, "yes"]) {
57
- assert.equal(encodeWheelReport("up", { col: 5, row: 3, alt }), "\x1b[<64;5;3M", `alt=${String(alt)} must not set bit 3`);
58
- assert.equal(encodeWheelReport("down", { col: 5, row: 3, alt }), "\x1b[<65;5;3M", `alt=${String(alt)} must not set bit 3`);
59
- }
60
- });
61
- ```
62
-
63
- - [ ] **Step 2: Run the tests to verify they fail**
64
-
65
- Run: `node --test test/pty-scroll.test.mjs`
66
- Expected: FAIL — `#214-A1` gets `\x1b[<64;5;3M` instead of `72`.
67
-
68
- - [ ] **Step 3: Implement**
69
-
70
- ```js
71
- export function encodeWheelReport(direction, { col, row, alt = false } = {}) {
72
- const base = direction === "up" ? 64 : 65;
73
- // Issue #214: SGR bit 3 (value 8) is Alt. pi-tui multiplies by 5 when it is set,
74
- // so the forward hop must carry it. Strict comparison: a truthy non-boolean must
75
- // not flip the bit.
76
- const button = alt === true ? base + 8 : base;
77
- const cell = (v) => (Number.isFinite(v) ? Math.max(1, Math.floor(v)) : 1);
78
- return `\x1b[<${button};${cell(col)};${cell(row)}M`;
79
- }
80
- ```
81
-
82
- Keep the existing JSDoc's description of the direction/coordinates; add one line about `alt`.
83
-
84
- - [ ] **Step 4: Run the tests to verify they pass**
85
-
86
- Run: `node --test test/pty-scroll.test.mjs`
87
- Expected: PASS (three new tests plus the existing suite).
88
-
89
- - [ ] **Step 5: Commit**
90
-
91
- ```bash
92
- git add src/core/pty-scroll.mjs test/pty-scroll.test.mjs
93
- git commit -m "feat(attach): carry the Alt modifier in the forwarded wheel report (issue #214)"
94
- ```
95
-
96
- ---
97
-
98
- ### Task 2: Pass `event.alt` through the forward hop
99
-
100
- **Files:**
101
- - Modify: `src/ui/pty-attach.ts` (`forwardWheelToChild`, ~L1477)
102
- - Modify: `test-support/pty-attach-wheel-fallback-scenario.ts` (new alt round)
103
- - Modify: `test/pty-attach-wheel-fallback.test.mjs` (new assertions)
104
-
105
- **Interfaces:**
106
- - Consumes: `encodeWheelReport(…, { alt })` from Task 1.
107
- - Produces: scenario result fields `forwardedAltUp` / `forwardedAltDown`.
108
-
109
- - [ ] **Step 1: Write the failing assertion**
110
-
111
- In `test/pty-attach-wheel-fallback.test.mjs`, after the existing plain-wheel assertions (which must stay untouched):
112
-
113
- ```js
114
- assert.equal(parsed.forwardedAltUp, "\x1b[<72;4;3M", "Alt+wheel-up must reach the child as button 72");
115
- assert.equal(parsed.forwardedAltDown, "\x1b[<73;4;3M", "Alt+wheel-down must reach the child as button 73");
116
- ```
117
-
118
- - [ ] **Step 2: Run to verify it fails**
119
-
120
- Run: `node --test test/pty-attach-wheel-fallback.test.mjs`
121
- Expected: FAIL — `parsed.forwardedAltUp` is `undefined`.
122
-
123
- - [ ] **Step 3: Implement**
124
-
125
- In `src/ui/pty-attach.ts` `forwardWheelToChild`, add the flag:
126
-
127
- ```ts
128
- data: encodeWheelReport(linesUp > 0 ? "up" : "down", {
129
- col: clampInt(event.x + 1, 1, this.cols),
130
- row: clampInt(event.y, 1, Math.max(1, this.bodyHeight())),
131
- // Issue #214: the overlay already received the Alt bit from pi-tui; carry it
132
- // into the synthesized report so the child keeps its 5x wheel accelerator.
133
- alt: event.alt === true,
134
- }),
135
- ```
136
-
137
- In `test-support/pty-attach-wheel-fallback-scenario.ts`, add one more round using the existing `mouseBase` (do not modify the existing rounds, whose assertions are #198's regression pins):
138
-
139
- ```ts
140
- // Issue #214: Alt must survive the forward hop.
141
- let beforeAlt = forwardedInputs().length;
142
- (component as any).handleMouse({ ...wheelEvent(-3), alt: true });
143
- if (await waitFor(() => forwardedInputs().length > beforeAlt, 3000)) {
144
- result.forwardedAltUp = forwardedInputs().at(-1) ?? null;
145
- }
146
- beforeAlt = forwardedInputs().length;
147
- (component as any).handleMouse({ ...wheelEvent(2), alt: true });
148
- if (await waitFor(() => forwardedInputs().length > beforeAlt, 3000)) {
149
- result.forwardedAltDown = forwardedInputs().at(-1) ?? null;
150
- }
151
- ```
152
-
153
- Declare both fields in the scenario's `result` object (`null as string | null`).
154
-
155
- - [ ] **Step 4: Run to verify it passes**
156
-
157
- Run: `node --test test/pty-attach-wheel-fallback.test.mjs`
158
- Expected: PASS — the two new assertions and every existing #198 assertion.
159
-
160
- - [ ] **Step 5: Commit**
161
-
162
- ```bash
163
- git add src/ui/pty-attach.ts test-support/pty-attach-wheel-fallback-scenario.ts test/pty-attach-wheel-fallback.test.mjs
164
- git commit -m "fix(attach): forward the Alt modifier on the wheel hop (issue #214)"
165
- ```
166
-
167
- ---
168
-
169
- ### Task 3: Regression gate
170
-
171
- **Files:** none.
172
-
173
- - [ ] **Step 1: Targeted suites**
174
-
175
- Run: `node --test test/pty-scroll.test.mjs test/pty-attach-wheel-fallback.test.mjs test/pty-attach-wheel.test.mjs test/pty-attach-frame-overflow.test.mjs test/pty-attach-alt-wheel.test.mjs test/pty-attach-mouse-mode.test.mjs test/terminal-reply-guard.test.mjs`
176
- Expected: all green.
177
-
178
- - [ ] **Step 2: Static gates**
179
-
180
- Run: `npm run typecheck && npm run pack:dry`
181
- Expected: both clean.
182
-
183
- - [ ] **Step 3: Baseline comparison**
184
-
185
- Run: `node --test test/pty-attach-*.test.mjs` and compare the failing set against the known Windows-host baseline. Expected: no new names.
186
-
187
- ---
188
-
189
- ### Task 4: Manual verification (U1)
190
-
191
- **Files:** none.
192
-
193
- - [ ] **Step 1: Load this checkout into pi**
194
-
195
- `pi install "$(pwd)"` (README.md:54) or symlink to `~/.pi/agent/extensions/agent-board` — **not** the `~/.pi/agent/git/...` cache clone.
196
-
197
- - [ ] **Step 2: Restart pi completely**, open a new attach session on a fullscreen child.
198
-
199
- - [ ] **Step 3: Hold Alt and roll one wheel notch** in the attach view.
200
- Expected: the child scrolls 5 lines, matching the behaviour outside attach.
201
-
202
- - [ ] **Step 4: Record the outcome** in the issue.
@@ -1,198 +0,0 @@
1
- # Recognize `ask_user_question` (name OR questionnaire shape) Implementation Plan
2
-
3
- > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
-
5
- **Goal:** Make an interactive run blocked on `ask_user_question` — or on any tool using the questionnaire arg shape — group under "Needs answer" instead of "RUNNING".
6
-
7
- **Architecture:** Two private helpers in `src/core/events.mjs` (`isQuestionArgShape`, `isQuestionToolCall`); the start gate uses `isQuestionToolCall(name, args)`, the end gate becomes name-agnostic and removes by `toolCallId`. Two reducer tests in `test/events.test.mjs` drive the public surface (`createRunStatus` / `reduceEvent` / `projectViewState`).
8
-
9
- **Tech Stack:** Node 22, `node:test` + `assert/strict`, plain ESM `.mjs` (no build step).
10
-
11
- **Spec:** `docs/superpowers/specs/2026-10-20-issue-221-ask-user-question-design.md`
12
-
13
- ## Global Constraints
14
-
15
- - Tabs for indentation; English comments and commit messages.
16
- - Conventional commit subject with the PR number appended as `(#N)` (this branch's PR number is filled in at PR time; use `(#221)` for the issue-numbered commits that predate the PR).
17
- - Run `npm run verify` before claiming completion — `npm test` alone skips the perf gate and coverage thresholds.
18
- - Do not touch `detectNeedsInput()`, `message_end` classification, or the `"ask_questions"` no-id fallback ids (`events.mjs:242/250`).
19
- - Detached (non-interactive) runs must keep the legacy `currentTool` behavior.
20
-
21
- ## Review Focus
22
-
23
- - A tool whose args are `{ questions: [] }` or `{ questions: "…" }` must **not** be treated as awaiting input (shape check needs a non-empty array whose first item has a string `question`).
24
- - Detached runs: the shape fallback must never produce `needs_input` (headless sessions cannot answer).
25
- - Parallel calls: ending one question must not clear another pending one (existing multi-question test at `test/events.test.mjs:135`).
26
- - An end event for a non-question tool must be a no-op for `pendingQuestions`.
27
- - `args` missing entirely on a question-tool start (`ask_questions` with `{}`) must still count via the name set.
28
-
29
- ---
30
-
31
- ### Task 1: Hybrid question-tool detection
32
-
33
- **Files:**
34
- - Modify: `src/core/events.mjs:20-21` (set), `:86` (start gate), `:100` (end gate), add helpers near `questionFromArgs` (`:232`)
35
- - Test: `test/events.test.mjs` (append after the existing `interactive pi question tool …` test at `:185-204`)
36
-
37
- **Interfaces:**
38
- - Consumes: `createRunStatus(config, pid, now)`, `reduceEvent(status, event, now, opts)`, `projectViewState(status, now)` from `src/core/events.mjs`.
39
- - Produces: module-private `isQuestionArgShape(args: unknown): boolean` and `isQuestionToolCall(name: string, args: unknown): boolean`; no exports change.
40
-
41
- - [ ] **Step 1: Write the failing tests**
42
-
43
- Append to `test/events.test.mjs`:
44
-
45
- ```js
46
- test("interactive ask_user_question waits for input and clears on end", () => {
47
- const s = createRunStatus(cfg(), 1, 1000);
48
- reduceEvent(s, {
49
- type: "tool_execution_start",
50
- toolCallId: "q1",
51
- toolName: "ask_user_question",
52
- args: { questions: [{ question: "Which approach?", header: "Approach", options: [{ label: "A", description: "a" }] }] },
53
- }, 2000, { interactive: true });
54
- assert.equal(s.semanticState, "needs_input");
55
- assert.equal(s.summary, "Which approach?");
56
- assert.equal(s.question, "Which approach?");
57
- assert.equal(s.currentTool, null);
58
- assert.deepEqual(s.pendingQuestions, [{ toolCallId: "q1", question: "Which approach?" }]);
59
- assert.equal(projectViewState(s, 2100).needsInput, true);
60
-
61
- reduceEvent(s, { type: "tool_execution_end", toolCallId: "q1", toolName: "ask_user_question", isError: false }, 2400, { interactive: true });
62
- assert.equal(s.semanticState, "working");
63
- assert.equal(s.question, null);
64
- assert.deepEqual(s.pendingQuestions, []);
65
-
66
- const detached = createRunStatus(cfg(), 1, 1000);
67
- reduceEvent(detached, {
68
- type: "tool_execution_start",
69
- toolCallId: "q1",
70
- toolName: "ask_user_question",
71
- args: { questions: [{ question: "Which approach?" }] },
72
- }, 2000);
73
- assert.equal(detached.semanticState, "working");
74
- assert.equal(detached.currentTool.name, "ask_user_question");
75
- assert.deepEqual(detached.pendingQuestions, []);
76
- });
77
-
78
- test("interactive unknown questionnaire shape is treated as a pending question", () => {
79
- const s = createRunStatus(cfg(), 1, 1000);
80
- reduceEvent(s, {
81
- type: "tool_execution_start",
82
- toolCallId: "q1",
83
- toolName: "mystery_questionnaire",
84
- args: { questions: [{ question: "Ship it?", header: "Release", options: [{ label: "Yes" }] }] },
85
- }, 2000, { interactive: true });
86
- assert.equal(s.semanticState, "needs_input");
87
- assert.equal(s.question, "Ship it?");
88
- assert.equal(s.currentTool, null);
89
- assert.deepEqual(s.pendingQuestions, [{ toolCallId: "q1", question: "Ship it?" }]);
90
-
91
- reduceEvent(s, { type: "tool_execution_end", toolCallId: "q1", toolName: "mystery_questionnaire", isError: false }, 2400, { interactive: true });
92
- assert.equal(s.semanticState, "working");
93
- assert.equal(s.question, null);
94
- assert.deepEqual(s.pendingQuestions, []);
95
-
96
- for (const args of [{ questions: [] }, { questions: "nope" }, { command: "ls" }]) {
97
- const negative = createRunStatus(cfg(), 1, 1000);
98
- reduceEvent(negative, { type: "tool_execution_start", toolCallId: "t1", toolName: "bash", args }, 2000, { interactive: true });
99
- assert.equal(negative.semanticState, "working", JSON.stringify(args));
100
- assert.deepEqual(negative.pendingQuestions, []);
101
- }
102
- });
103
- ```
104
-
105
- - [ ] **Step 2: Run the tests to verify they fail**
106
-
107
- Run: `npm test -- --test-name-pattern="ask_user_question|unknown questionnaire"`
108
- Expected: FAIL — the first test reports `semanticState === "working"` / `summary === "Ask_user_question"`; the second reports `semanticState === "working"` for `mystery_questionnaire`.
109
-
110
- - [ ] **Step 3: Implement the detection helpers**
111
-
112
- In `src/core/events.mjs`, replace the set definition:
113
-
114
- ```js
115
- /** Tool names whose interactive execution blocks on a user answer. */
116
- const QUESTION_TOOL_NAMES = new Set(["ask_user_question", "ask_questions", "question", "questionnaire"]);
117
-
118
- /** Questionnaire args shared by pi/Claude-Code style question tools. */
119
- function isQuestionArgShape(args) {
120
- const questions = Array.isArray(args?.questions) ? args.questions : null;
121
- return Boolean(questions && questions.length > 0 && typeof questions[0]?.question === "string");
122
- }
123
-
124
- /** A start event is a question when its name is known or its args carry the questionnaire shape. */
125
- function isQuestionToolCall(name, args) {
126
- return QUESTION_TOOL_NAMES.has(name) || isQuestionArgShape(args);
127
- }
128
- ```
129
-
130
- - [ ] **Step 4: Wire the start and end gates**
131
-
132
- In the `tool_execution_start` case, replace
133
-
134
- ```js
135
- if (opts.interactive && QUESTION_TOOL_NAMES.has(name)) {
136
- ```
137
-
138
- with
139
-
140
- ```js
141
- if (opts.interactive && isQuestionToolCall(name, args)) {
142
- ```
143
-
144
- In the `tool_execution_end` case, replace
145
-
146
- ```js
147
- if (opts.interactive && QUESTION_TOOL_NAMES.has(event.toolName ?? "")) removePendingQuestion(status, event.toolCallId);
148
- ```
149
-
150
- with
151
-
152
- ```js
153
- // The end event carries no args, so a shape-matched start cannot be recognized here;
154
- // remove by call id instead (a no-op when this call had no pending question).
155
- if (opts.interactive) removePendingQuestion(status, event.toolCallId);
156
- ```
157
-
158
- - [ ] **Step 5: Run the reducer tests and the whole suite**
159
-
160
- Run: `npm test`
161
- Expected: PASS — the two new tests plus the existing `ask_questions` / `question` / `questionnaire` / parallel-question tests stay green. (A1–A4, A5–A7)
162
-
163
- - [ ] **Step 6: Run the full gate**
164
-
165
- Run: `npm run verify`
166
- Expected: PASS — typecheck, perf gate, tests, coverage thresholds (85/80/70), pack dry-run. (A8)
167
-
168
- - [ ] **Step 7: Commit**
169
-
170
- ```bash
171
- git add src/core/events.mjs test/events.test.mjs
172
- git commit -m "fix: recognize ask_user_question and questionnaire-shaped tools as pending questions (#221)"
173
- ```
174
-
175
- ---
176
-
177
- ### Task 2: Post-merge live verification (manual, not a code task)
178
-
179
- **Files:** none.
180
-
181
- - [ ] **Step 1: Update the board clone the running process loads**
182
-
183
- ```bash
184
- cd ~/.pi/agent/git/github.com/zhuxixi/pi-agent-board
185
- git checkout -- package-lock.json
186
- git pull
187
- git log -1 --oneline
188
- ```
189
-
190
- Expected: HEAD is the merged commit from this branch.
191
-
192
- - [ ] **Step 2: Restart Pi / the board service**, then open the dashboard.
193
-
194
- - [ ] **Step 3: Dispatch a session that blocks on `ask_user_question`** (the `@juicesharp/rpiv-ask-user-question` extension is installed at user level), answer it via attach.
195
-
196
- Expected: the row sits under **Needs answer** with the question text as summary; after answering it returns to **RUNNING**. (U1)
197
-
198
- If the merge has not happened yet, record U1 as `pending` — do not mark it passed.