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