@huseynsnmz/pi-board 0.15.1 → 0.16.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 (215) hide show
  1. package/CHANGELOG.md +26 -4
  2. package/CONTRIBUTING.md +7 -0
  3. package/README.md +53 -419
  4. package/SECURITY.md +19 -0
  5. package/docs/how-it-works.md +31 -0
  6. package/docs/reference.md +276 -0
  7. package/package.json +18 -19
  8. package/src/index.ts +9 -1
  9. package/src/runtime/service.mjs +12 -0
  10. package/src/ui/dashboard.ts +14 -8
  11. package/src/ui/double-left.ts +5 -1
  12. package/src/ui/focus.ts +7 -1
  13. package/src/ui/waiting.ts +10 -5
  14. package/IMPLEMENTATION_PLAN.md +0 -920
  15. package/PRD.md +0 -484
  16. package/PROGRESS.md +0 -142
  17. package/VERIFY.md +0 -115
  18. package/docs/BATCH_SELECTION_READ_FLOW.md +0 -277
  19. package/docs/EXPLORATION.md +0 -187
  20. package/docs/PTY_ATTACH_IMPLEMENTATION_PLAN.md +0 -581
  21. package/docs/RELEASE.md +0 -148
  22. package/docs/superpowers/plans/2026-08-15-screenlog-gc.md +0 -704
  23. package/docs/superpowers/plans/2026-08-16-attach-double-cursor-jiggle-retry.md +0 -499
  24. package/docs/superpowers/plans/2026-08-21-attach-coldstart-jiggle-rearm.md +0 -797
  25. package/docs/superpowers/plans/2026-08-21-dashboard-keypress-lag.md +0 -366
  26. package/docs/superpowers/plans/2026-08-21-issue-14-needs-input-no-auto-done.md +0 -576
  27. package/docs/superpowers/plans/2026-08-22-jiggle-shrink-and-hold.md +0 -337
  28. package/docs/superpowers/plans/2026-08-22-launch-cwd-favorites.md +0 -916
  29. package/docs/superpowers/plans/2026-08-22-question-tool-grouping.md +0 -211
  30. package/docs/superpowers/plans/2026-08-22-rename-state-labels.md +0 -182
  31. package/docs/superpowers/plans/2026-08-22-stable-list-order.md +0 -51
  32. package/docs/superpowers/plans/2026-08-27-locks-acquirelock-spin.md +0 -614
  33. package/docs/superpowers/plans/2026-08-27-pty-runner-test-flaky.md +0 -32
  34. package/docs/superpowers/plans/2026-08-29-code-refs-badges.md +0 -223
  35. package/docs/superpowers/plans/2026-08-30-circular-navigation.md +0 -308
  36. package/docs/superpowers/plans/2026-08-30-post-exit-timing-fix.md +0 -37
  37. package/docs/superpowers/plans/2026-08-30-pty-attach-quality-debt.md +0 -311
  38. package/docs/superpowers/plans/2026-08-30-readme-v2.md +0 -294
  39. package/docs/superpowers/plans/2026-09-01-attach-detach-gate-cursor-anchor.md +0 -284
  40. package/docs/superpowers/plans/2026-09-02-attach-detach-editor-state.md +0 -722
  41. package/docs/superpowers/plans/2026-09-03-code-refs-pr-backlink-narrow.md +0 -551
  42. package/docs/superpowers/plans/2026-09-03-detach-gate-glyph-fallback.md +0 -146
  43. package/docs/superpowers/plans/2026-09-04-evidence-outputpreview.md +0 -209
  44. package/docs/superpowers/plans/2026-09-04-warm-host-reclaim.md +0 -796
  45. package/docs/superpowers/plans/2026-09-05-issue-13-drainnextfollowup-pty-probe.md +0 -114
  46. package/docs/superpowers/plans/2026-09-05-issue-38-windows-wezterm-ime-cursor.md +0 -73
  47. package/docs/superpowers/plans/2026-09-05-issue-39-truncate-codepoint-boundary.md +0 -143
  48. package/docs/superpowers/plans/2026-09-05-issue-61-mention-fallback-guards.md +0 -226
  49. package/docs/superpowers/plans/2026-09-05-issue-63-flaky-manual-completion.md +0 -87
  50. package/docs/superpowers/plans/2026-09-05-issue-64-changelog-release-helper.md +0 -53
  51. package/docs/superpowers/plans/2026-09-05-pty-host-stacking-sock-race.md +0 -731
  52. package/docs/superpowers/plans/2026-09-08-attach-ctrl-left-detach.md +0 -30
  53. package/docs/superpowers/plans/2026-09-08-dashboard-shrink-repaint.md +0 -68
  54. package/docs/superpowers/plans/2026-09-08-issue-11-attach-runtime-desync-heal.md +0 -917
  55. package/docs/superpowers/plans/2026-09-08-legacy-stale-host-recovery.md +0 -125
  56. package/docs/superpowers/plans/2026-09-08-spawn-async-error-swallow.md +0 -56
  57. package/docs/superpowers/plans/2026-09-08-stale-model-attach-guard.md +0 -96
  58. package/docs/superpowers/plans/2026-09-09-claimpid-blocks-replace.md +0 -267
  59. package/docs/superpowers/plans/2026-09-09-harden-runner-architecture.md +0 -603
  60. package/docs/superpowers/plans/2026-09-09-single-writer-completion.md +0 -252
  61. package/docs/superpowers/plans/2026-09-10-reader-consistency.md +0 -115
  62. package/docs/superpowers/plans/2026-09-14-attach-cursor-dectcem-gate.md +0 -469
  63. package/docs/superpowers/plans/2026-09-14-attach-snapshot.md +0 -92
  64. package/docs/superpowers/plans/2026-09-14-host-meta-orphan-lock.md +0 -771
  65. package/docs/superpowers/plans/2026-09-14-issue-106-terminal-frame-cognition.md +0 -299
  66. package/docs/superpowers/plans/2026-09-14-issue-113-foreground-preview-race.md +0 -609
  67. package/docs/superpowers/plans/2026-09-14-terminal-model.md +0 -145
  68. package/docs/superpowers/plans/2026-09-15-coordinator-pipe-root-normalize.md +0 -224
  69. package/docs/superpowers/plans/2026-09-15-lease-publish-eprem-reclaim.md +0 -341
  70. package/docs/superpowers/plans/2026-09-18-detach-anchor-reporter-endpoint.md +0 -875
  71. package/docs/superpowers/plans/2026-09-20-control-lifecycle.md +0 -116
  72. package/docs/superpowers/plans/2026-09-20-issue-121-perf-gate-out-of-coverage.md +0 -517
  73. package/docs/superpowers/plans/2026-09-23-a5-mid-stream-assertion.md +0 -25
  74. package/docs/superpowers/plans/2026-09-23-cwd-browse-tab-drill.md +0 -393
  75. package/docs/superpowers/plans/2026-09-23-terminal-query-forwarding.md +0 -51
  76. package/docs/superpowers/plans/2026-09-23-unconditional-detach-key.md +0 -58
  77. package/docs/superpowers/plans/2026-09-24-control-reconcile-flake.md +0 -698
  78. package/docs/superpowers/plans/2026-09-24-probe-attached-clients.md +0 -605
  79. package/docs/superpowers/plans/2026-09-29-issue-145-holding-semantic-state.md +0 -1183
  80. package/docs/superpowers/plans/2026-09-29-issue-145-user-verification.md +0 -66
  81. package/docs/superpowers/plans/2026-09-30-board-delete-key-x-y-confirm.md +0 -649
  82. package/docs/superpowers/plans/2026-09-30-issue-153-runner-stop-boot-window.md +0 -618
  83. package/docs/superpowers/plans/2026-09-30-issue-95-test-discipline.md +0 -555
  84. package/docs/superpowers/plans/2026-09-30-publish-dispatch-tag-check.md +0 -259
  85. package/docs/superpowers/plans/2026-10-06-attach-settle-scheme-delivery.md +0 -751
  86. package/docs/superpowers/plans/2026-10-09-issue-167-attach-middle-paste.md +0 -509
  87. package/docs/superpowers/plans/2026-10-09-issue-169-fullscreen-mouse-ownership.md +0 -346
  88. package/docs/superpowers/plans/2026-10-10-attach-delete-in-place.md +0 -832
  89. package/docs/superpowers/plans/2026-10-10-issue-174-selection-mirror-paste-source.md +0 -510
  90. package/docs/superpowers/plans/2026-10-14-issue-170-attach-reply-guard.md +0 -771
  91. package/docs/superpowers/plans/2026-10-14-issue-178-accept-registration-race.md +0 -1257
  92. package/docs/superpowers/plans/2026-10-14-issue-184-snapshot-window-contract.md +0 -657
  93. package/docs/superpowers/plans/2026-10-14-issue-185-attach-wheel-alt-screen.md +0 -47
  94. package/docs/superpowers/plans/2026-10-14-issue-187-motionless-drag.md +0 -931
  95. package/docs/superpowers/plans/2026-10-14-issue-189-mixed-input-segments.md +0 -823
  96. package/docs/superpowers/plans/2026-10-15-issue-188-x10-mouse-release.md +0 -526
  97. package/docs/superpowers/plans/2026-10-16-issue-198-attach-wheel-forward-fallback.md +0 -32
  98. package/docs/superpowers/plans/2026-10-18-attach-wheel-replay-pseudo-scrollback.md +0 -437
  99. package/docs/superpowers/plans/2026-10-19-issue-213-reply-guard-csi-key.md +0 -257
  100. package/docs/superpowers/plans/2026-10-19-issue-214-attach-forward-alt-modifier.md +0 -202
  101. package/docs/superpowers/plans/2026-10-21-issue-221-ask-user-question.md +0 -198
  102. package/docs/superpowers/plans/2026-10-23-issue-222-attach-ready.md +0 -290
  103. package/docs/superpowers/plans/2026-10-23-issue-225-monotonic-foreground-state.md +0 -450
  104. package/docs/superpowers/plans/2026-10-23-issue-229-vim-jkl.md +0 -449
  105. package/docs/superpowers/plans/2026-10-23-issue-232-attach-bg-only-cells.md +0 -153
  106. package/docs/superpowers/plans/2026-10-23-lease-heartbeat-staleness.md +0 -537
  107. package/docs/superpowers/plans/2026-10-23-selected-row-highlight.md +0 -405
  108. package/docs/superpowers/plans/2026-10-24-issue-217-windows-pid-reuse-stopping.md +0 -367
  109. package/docs/superpowers/plans/2026-10-24-issue-230-warm-host-background-runs.md +0 -806
  110. package/docs/superpowers/plans/2026-10-24-issue-238-windows-process-identity-token.md +0 -490
  111. package/docs/superpowers/plans/2026-10-24-issue-241-coordinator-root-exit.md +0 -461
  112. package/docs/superpowers/plans/2026-10-25-issue-239-coordinator-replacement-identity.md +0 -504
  113. package/docs/superpowers/plans/2026-10-26-issue-245-key-hints-doc-sync.md +0 -244
  114. package/docs/superpowers/plans/2026-10-27-issue-172-fullscreen-selection-clear.md +0 -344
  115. package/docs/superpowers/plans/2026-10-27-issue-252-select-key-hints.md +0 -209
  116. package/docs/superpowers/plans/2026-10-28-issue-255-coordinator-endpoint-zombie.md +0 -442
  117. package/docs/superpowers/plans/2026-10-28-issue-257-legacy-pty-runner-endpoint-cleanup.md +0 -256
  118. package/docs/superpowers/plans/2026-11-03-cwd-favorites-full-pool.md +0 -266
  119. package/docs/superpowers/specs/2026-08-15-screenlog-gc-design.md +0 -105
  120. package/docs/superpowers/specs/2026-08-16-attach-double-cursor-jiggle-retry-design.md +0 -142
  121. package/docs/superpowers/specs/2026-08-21-attach-coldstart-jiggle-rearm-design.md +0 -110
  122. package/docs/superpowers/specs/2026-08-21-dashboard-keypress-lag-design.md +0 -59
  123. package/docs/superpowers/specs/2026-08-21-issue-14-needs-input-no-auto-done-design.md +0 -109
  124. package/docs/superpowers/specs/2026-08-22-cwd-favorites-design.md +0 -131
  125. package/docs/superpowers/specs/2026-08-22-jiggle-shrink-and-hold-design.md +0 -111
  126. package/docs/superpowers/specs/2026-08-22-question-tool-grouping-design.md +0 -114
  127. package/docs/superpowers/specs/2026-08-22-rename-state-labels-design.md +0 -80
  128. package/docs/superpowers/specs/2026-08-27-locks-acquirelock-spin-design.md +0 -97
  129. package/docs/superpowers/specs/2026-08-27-pty-runner-test-flaky-design.md +0 -35
  130. package/docs/superpowers/specs/2026-08-29-code-refs-badges-design.md +0 -128
  131. package/docs/superpowers/specs/2026-08-30-circular-navigation-design.md +0 -47
  132. package/docs/superpowers/specs/2026-08-30-eprm-atomicwrite-race-design.md +0 -77
  133. package/docs/superpowers/specs/2026-08-30-post-exit-timing-fix-design.md +0 -80
  134. package/docs/superpowers/specs/2026-08-30-pty-attach-quality-debt-design.md +0 -105
  135. package/docs/superpowers/specs/2026-08-30-readme-v2-design.md +0 -115
  136. package/docs/superpowers/specs/2026-09-01-attach-detach-gate-cursor-anchor-design.md +0 -78
  137. package/docs/superpowers/specs/2026-09-02-attach-detach-editor-state-design.md +0 -120
  138. package/docs/superpowers/specs/2026-09-03-code-refs-pr-backlink-narrow-design.md +0 -92
  139. package/docs/superpowers/specs/2026-09-03-detach-gate-glyph-fallback-design.md +0 -99
  140. package/docs/superpowers/specs/2026-09-04-evidence-outputpreview-design.md +0 -50
  141. package/docs/superpowers/specs/2026-09-04-warm-host-reclaim-design.md +0 -106
  142. package/docs/superpowers/specs/2026-09-05-issue-13-drainnextfollowup-pty-probe-design.md +0 -64
  143. package/docs/superpowers/specs/2026-09-05-issue-38-windows-wezterm-ime-design.md +0 -48
  144. package/docs/superpowers/specs/2026-09-05-issue-39-truncate-codepoint-boundary-design.md +0 -64
  145. package/docs/superpowers/specs/2026-09-05-issue-61-mention-fallback-design.md +0 -71
  146. package/docs/superpowers/specs/2026-09-05-issue-63-flaky-manual-completion-design.md +0 -49
  147. package/docs/superpowers/specs/2026-09-05-issue-64-changelog-helper-design.md +0 -76
  148. package/docs/superpowers/specs/2026-09-05-pty-host-stacking-sock-race-design.md +0 -510
  149. package/docs/superpowers/specs/2026-09-07-issue-11-attach-runtime-desync-heal-design.md +0 -130
  150. package/docs/superpowers/specs/2026-09-08-attach-ctrl-left-detach-design.md +0 -58
  151. package/docs/superpowers/specs/2026-09-08-dashboard-shrink-repaint-design.md +0 -52
  152. package/docs/superpowers/specs/2026-09-08-legacy-stale-host-recovery-design.md +0 -87
  153. package/docs/superpowers/specs/2026-09-08-spawn-async-error-swallow-design.md +0 -56
  154. package/docs/superpowers/specs/2026-09-08-stale-model-attach-guard-design.md +0 -79
  155. package/docs/superpowers/specs/2026-09-09-claimpid-blocks-replace-design.md +0 -147
  156. package/docs/superpowers/specs/2026-09-09-harden-runner-architecture-design.md +0 -304
  157. package/docs/superpowers/specs/2026-09-14-attach-cursor-dectcem-gate-design.md +0 -114
  158. package/docs/superpowers/specs/2026-09-14-host-meta-orphan-lock-design.md +0 -120
  159. package/docs/superpowers/specs/2026-09-14-issue-106-terminal-frame-cognition-design.md +0 -146
  160. package/docs/superpowers/specs/2026-09-14-issue-113-foreground-preview-race-design.md +0 -116
  161. package/docs/superpowers/specs/2026-09-15-coordinator-pipe-root-normalize-design.md +0 -84
  162. package/docs/superpowers/specs/2026-09-15-lease-publish-eprem-reclaim-design.md +0 -92
  163. package/docs/superpowers/specs/2026-09-18-detach-anchor-reporter-endpoint-design.md +0 -123
  164. package/docs/superpowers/specs/2026-09-20-issue-121-perf-gate-out-of-coverage-design.md +0 -204
  165. package/docs/superpowers/specs/2026-09-23-a5-mid-stream-assertion-design.md +0 -43
  166. package/docs/superpowers/specs/2026-09-23-cwd-browse-tab-drill-design.md +0 -95
  167. package/docs/superpowers/specs/2026-09-23-terminal-query-forwarding-design.md +0 -63
  168. package/docs/superpowers/specs/2026-09-23-unconditional-detach-key-design.md +0 -57
  169. package/docs/superpowers/specs/2026-09-24-control-reconcile-flake-design.md +0 -98
  170. package/docs/superpowers/specs/2026-09-24-probe-attached-clients-design.md +0 -190
  171. package/docs/superpowers/specs/2026-09-29-issue-145-holding-semantic-state-design.md +0 -394
  172. package/docs/superpowers/specs/2026-09-30-board-delete-key-x-y-confirm-design.md +0 -86
  173. package/docs/superpowers/specs/2026-09-30-issue-153-runner-stop-boot-window-design.md +0 -75
  174. package/docs/superpowers/specs/2026-09-30-issue-95-test-discipline-design.md +0 -226
  175. package/docs/superpowers/specs/2026-09-30-publish-dispatch-tag-check-design.md +0 -129
  176. package/docs/superpowers/specs/2026-10-06-attach-settle-scheme-delivery-design.md +0 -196
  177. package/docs/superpowers/specs/2026-10-09-issue-167-attach-middle-paste-design.md +0 -125
  178. package/docs/superpowers/specs/2026-10-09-issue-169-fullscreen-mouse-ownership-design.md +0 -130
  179. package/docs/superpowers/specs/2026-10-10-attach-delete-in-place-design.md +0 -224
  180. package/docs/superpowers/specs/2026-10-10-issue-174-selection-mirror-paste-source-design.md +0 -154
  181. package/docs/superpowers/specs/2026-10-14-issue-170-attach-reply-guard-design.md +0 -195
  182. package/docs/superpowers/specs/2026-10-14-issue-178-accept-registration-race-design.md +0 -287
  183. package/docs/superpowers/specs/2026-10-14-issue-184-snapshot-window-contract-design.md +0 -288
  184. package/docs/superpowers/specs/2026-10-14-issue-185-attach-wheel-alt-screen-design.md +0 -126
  185. package/docs/superpowers/specs/2026-10-14-issue-187-motionless-drag-design.md +0 -269
  186. package/docs/superpowers/specs/2026-10-14-issue-189-mixed-input-segments-design.md +0 -369
  187. package/docs/superpowers/specs/2026-10-15-issue-188-x10-mouse-release-design.md +0 -252
  188. package/docs/superpowers/specs/2026-10-16-issue-198-attach-wheel-forward-fallback-design.md +0 -67
  189. package/docs/superpowers/specs/2026-10-18-attach-wheel-replay-pseudo-scrollback-design.md +0 -250
  190. package/docs/superpowers/specs/2026-10-19-issue-213-reply-guard-csi-key-design.md +0 -165
  191. package/docs/superpowers/specs/2026-10-19-issue-214-attach-forward-alt-modifier-design.md +0 -131
  192. package/docs/superpowers/specs/2026-10-20-issue-221-ask-user-question-design.md +0 -270
  193. package/docs/superpowers/specs/2026-10-22-issue-225-monotonic-foreground-state-design.md +0 -225
  194. package/docs/superpowers/specs/2026-10-23-issue-222-attach-ready-design.md +0 -204
  195. package/docs/superpowers/specs/2026-10-23-issue-229-vim-jkl-design.md +0 -90
  196. package/docs/superpowers/specs/2026-10-23-issue-232-attach-bg-only-cells-design.md +0 -146
  197. package/docs/superpowers/specs/2026-10-23-lease-heartbeat-staleness-design.md +0 -271
  198. package/docs/superpowers/specs/2026-10-23-selected-row-highlight-design.md +0 -191
  199. package/docs/superpowers/specs/2026-10-24-issue-217-windows-pid-reuse-stopping-design.md +0 -334
  200. package/docs/superpowers/specs/2026-10-24-issue-230-warm-host-background-runs-design.md +0 -283
  201. package/docs/superpowers/specs/2026-10-24-issue-238-windows-process-identity-token-design.md +0 -286
  202. package/docs/superpowers/specs/2026-10-24-issue-241-coordinator-root-exit-design.md +0 -381
  203. package/docs/superpowers/specs/2026-10-25-issue-239-coordinator-replacement-identity-design.md +0 -331
  204. package/docs/superpowers/specs/2026-10-26-issue-245-key-hints-doc-sync-design.md +0 -72
  205. package/docs/superpowers/specs/2026-10-27-issue-172-fullscreen-selection-clear-design.md +0 -248
  206. package/docs/superpowers/specs/2026-10-27-issue-252-select-key-hints-design.md +0 -95
  207. package/docs/superpowers/specs/2026-10-28-issue-255-coordinator-endpoint-zombie-design.md +0 -380
  208. package/docs/superpowers/specs/2026-10-28-issue-257-legacy-pty-runner-endpoint-cleanup-design.md +0 -197
  209. package/docs/superpowers/specs/2026-11-03-cwd-favorites-full-pool-design.md +0 -106
  210. package/scripts/budget-audit.mjs +0 -135
  211. package/scripts/patch-vulns.mjs +0 -59
  212. package/scripts/release.mjs +0 -446
  213. package/scripts/release_helper.mjs +0 -309
  214. package/scripts/run-perf-gate.mjs +0 -40
  215. 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.