@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,77 +0,0 @@
|
|
|
1
|
-
# Issue #48 根因报告 + 修复 spec
|
|
2
|
-
|
|
3
|
-
Windows: pty-runner dies with uncaught EPERM when host.json atomicWrite races a reader; attach view stuck in reconnect loop
|
|
4
|
-
|
|
5
|
-
状态:**草稿,待用户确认**(2026-08-30)
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## 1. 根因(已代码层验证,非推测)
|
|
10
|
-
|
|
11
|
-
### 机制(Windows 特有)
|
|
12
|
-
libuv 打开文件默认共享模式不含 `FILE_SHARE_DELETE`;Node `renameSync` 在 Windows 映射为 MoveFileExW(MOVEFILE_REPLACE_EXISTING),替换已存在目标需先删除旧目标,而删除要求所有持句柄进程带 FILE_SHARE_DELETE——否则抛 `EPERM: operation not permitted`(本机 100/100 复现,见 issue 正文复现代码)。
|
|
13
|
-
|
|
14
|
-
### 触发链
|
|
15
|
-
1. `runner/pty-runner.mjs` 1s 心跳 `update()` → `persist()` → `writeHost()` → `atomicWriteJson()`(`src/core/atomic.mjs`:写 `.tmp` → `renameSync`,**无重试**)
|
|
16
|
-
2. service 渲染路径高频 `readHost()/loadRow()`(`readFileSync` host.json)——密集工具调用 + 任意面板渲染即构成 reader
|
|
17
|
-
3. 窗口重叠 → renameSync 抛 EPERM → **无 try/catch**(update/persist 均无防护,全文件无 uncaughtException 兜底)→ runner 以 `detached: true, stdio: "ignore"`(`launch.mjs`)静默死亡,留 `.tmp` 残留
|
|
18
|
-
4. 连锁:runner 死 → ConPTY 断 → 托管 child pi 随死
|
|
19
|
-
5. attach 视图:只有 socket `{type:"exit"}` 消息才置 `status="host exited"`(`pty-attach.ts` onSocketData L871);崩溃 runner 不发 → `scheduleReconnect()` 150ms **无限重连**
|
|
20
|
-
6. 逃生失败:`handleInput` 中 `←`/`ctrl+]` 仅当 `childInputLooksEmpty()` 才 detach(崩溃画面停在非空输出行,不满足);`send()`(L852)`!connected` 时**静默丢弃**;pi-tui 无系统级兜底键
|
|
21
|
-
|
|
22
|
-
### 次要发现
|
|
23
|
-
- `failEarly()` 硬编码 `/tmp/pi-agent-board-pty-runner.err`(Windows 无 /tmp,写失败被吞)——崩溃零痕迹的原因之一
|
|
24
|
-
- 二次崩溃无 `host_reconciled` 诊断(reconcile 只在 panel open / session_start 跑)→ idle 行掩盖崩溃
|
|
25
|
-
|
|
26
|
-
## 2. 修复设计(四层,L1-L3 必做,L4 待确认)
|
|
27
|
-
|
|
28
|
-
### L1 `src/core/atomic.mjs` — rename 重试(根因层)
|
|
29
|
-
- 新增内部 `renameWithRetry(tmp, file, opts?)`:
|
|
30
|
-
- 错误码白名单重试:`EPERM` / `EBUSY` / `EACCES`(Windows 共享冲突三兄弟)
|
|
31
|
-
- 3 次重试 + 退避 10ms → 50ms → 250ms(同步调用,上限阻塞 ~310ms,可接受)
|
|
32
|
-
- 重试无需重写 tmp(写文件已在 rename 前完成,tmp 内容完整)
|
|
33
|
-
- 全部失败:`unlinkSync(tmp)`(try/catch 清理残留)→ 抛**原错误**(保持调用方语义,由 L2 降级)
|
|
34
|
-
- `atomicWrite` 调 `renameWithRetry`;导出 `renameWithRetry` 供单测注入失败回调
|
|
35
|
-
|
|
36
|
-
### L2 `runner/pty-runner.mjs` — 持久化防御 + 崩溃兜底(防御层)
|
|
37
|
-
- `persist()` 包 try/catch:失败时 `appendDiagnostic(root, viewId, { type: "persist_error", ... })`(`core/diagnostics.mjs` 仅依赖 atomic/paths,runner 可安全导入)→ 降级继续,下个心跳 tick 再试,**不杀进程**
|
|
38
|
-
- `update()`:persist 失败不影响 `broadcast()`(socket 消息是 attach 主通道,host.json 短暂陈旧可接受)
|
|
39
|
-
- `process.on("uncaughtException")` 一次性兜底:
|
|
40
|
-
1. 移除 handler(防循环)
|
|
41
|
-
2. appendDiagnostic 记录
|
|
42
|
-
3. 尽力最终 persist:`state:"failed", error, endedAt`(try/catch 包住)
|
|
43
|
-
4. `broadcast({ type: "exit", exitCode: 1 })` ← **关键**:让已连 attach 正常退出,不再无限重连
|
|
44
|
-
5. `process.exit(1)`
|
|
45
|
-
- `failEarly` 的 `/tmp` 硬编码 → `os.tmpdir()`(Windows 兼容小修)
|
|
46
|
-
|
|
47
|
-
### L3 `src/ui/pty-attach.ts` — 逃生键 + 重连超时(UI 层)
|
|
48
|
-
- **逃生**:DETACH 分支条件改为 `if (!this.connected || this.childInputLooksEmpty()) this.detach()` —— 断连/启动中随时可按 `←`/`ctrl+]` 退出(`send({type:"detach"})` 在 !connected 时被丢弃,无害)
|
|
49
|
-
- **重连超时**(新增 `everConnected` 标志,首个 socket connect 成功置位):
|
|
50
|
-
- `everConnected === true` 断开后:15s 重连窗口 → 超时置 `status = "host exited"`、停止重连、保持可 detach(覆盖 issue 主场景:host 已崩)
|
|
51
|
-
- `everConnected === false`(host 冷启动中,service 在 attach 前已 launchHost):保留无限重连 + 宽松上限 120s → 超时置 `status = "host not reachable"` 停止重连(覆盖 launchHost 失败场景)
|
|
52
|
-
- 超时到期不自动 `done()`,显示错误状态等用户按 `←` 退出(避免突然弹走)
|
|
53
|
-
|
|
54
|
-
### L4(可选,待确认)— idle 行掩盖崩溃的展示级检测
|
|
55
|
-
- 落点:`loadRow()` 已派生 `hostAlive`;dashboard 渲染时对 `host.state==="alive" && !hostAlive && lastSeenAt 陈旧(>10s)` 的行显示 host-lost 标记(**仅展示级,不改 state.json**,避免与 resume/重启中误判)
|
|
56
|
-
- 不做 state 级 reconcile 触发时机修改(范围外)
|
|
57
|
-
|
|
58
|
-
## 3. 测试计划(TDD 先行)
|
|
59
|
-
|
|
60
|
-
| 层 | 测试 | 方式 |
|
|
61
|
-
|---|---|---|
|
|
62
|
-
| L1 | `renameWithRetry` 重试次数/退避/白名单/最终抛错/失败后 tmp 清理/happy path | 单测注入失败回调(新增 `test/atomic-retry.test.mjs`) |
|
|
63
|
-
| L2 | persist 持续失败 → runner 不崩溃 + diagnostics 有记录 + host.json 陈旧但不死 | 集成:host.json 目标路径被同名**目录**占位(rename 必失败,跨平台稳定触发) |
|
|
64
|
-
| L2 | 崩溃兜底 `finalizeCrash`:写 failed 状态 + broadcast exit | 抽纯函数单测(外部无法稳定注入 uncaughtException) |
|
|
65
|
-
| L3 | handleInput 逃生分支(!connected 时 detach) | 现有 pty-attach render 测试基建(mock tui/theme/term) |
|
|
66
|
-
| L3 | 重连超时状态机(everConnected × 超时 × 状态文案) | 抽纯函数或组件级测试 |
|
|
67
|
-
|
|
68
|
-
回归:全量 `node --test test/*.test.mjs` + `npm run typecheck`(Windows 全量已知 6 个既有失败,基线对比排除回归)。
|
|
69
|
-
|
|
70
|
-
## 4. 非目标
|
|
71
|
-
- 不做 unlink-then-rename 兜底(短暂缺失窗口影响并发 reader;重试已覆盖)
|
|
72
|
-
- 不改 reconcile 触发时机(L4 仅展示级)
|
|
73
|
-
- 不做历史 `.tmp` 残留 GC(screen-log-gc 范围外,可后续单列)
|
|
74
|
-
|
|
75
|
-
## 5. 交付
|
|
76
|
-
- worktree:`issue-48-eprm-atomicwrite-race`(spec 批准后)
|
|
77
|
-
- PR 标题:`fix: harden host.json persistence against Windows EPERM rename races (issue #48)`,覆盖 L1-L3(L4 视确认)
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
# Spec: fix deterministic post-exit test failure after #43 (issue #46)
|
|
2
|
-
|
|
3
|
-
## Problem
|
|
4
|
-
|
|
5
|
-
`test/runner.integration.test.mjs` → `runner does not clobber a manual completion
|
|
6
|
-
made during post-exit model passes` fails deterministically on Node 24 since
|
|
7
|
-
#43 (`ffc2c8c`). Upstream `main` CI is red (`e14cc41`, `ffc2c8c`) and blocks
|
|
8
|
-
subsequent PRs (#44).
|
|
9
|
-
|
|
10
|
-
## Root cause (research: `~/.claude/github-issue-driven/zhuxixi/pi-agent-board/issue-46/`)
|
|
11
|
-
|
|
12
|
-
#43 added a synchronous `updateCodeRefsFromEvidence()` call (git subprocesses,
|
|
13
|
-
hundreds of ms) into `runner/job-runner.mjs`'s `persist()` chain, widening two
|
|
14
|
-
pre-existing race windows:
|
|
15
|
-
|
|
16
|
-
### Window 1 — `markCompleted` rejected (CI failure point)
|
|
17
|
-
`persist()` order: `writeStatus` (endedAt visible) → `updateCodeRefsFromEvidence`
|
|
18
|
-
(slow) → `writeState` (semanticState converges). The test sees `endedAt` while
|
|
19
|
-
`state.json` is still `working`; the runner process is still alive
|
|
20
|
-
(`pid.json` records the runner pid) → `isAgentBusy(row)` → `markCompleted`
|
|
21
|
-
returns `'Wait for the active run to finish before marking done'`.
|
|
22
|
-
|
|
23
|
-
### Window 2 — manual completion clobbered (the actual bug)
|
|
24
|
-
`finalizeSemanticState` (`src/core/derive.mjs:33`) returns `"idle"` for a clean
|
|
25
|
-
worker exit. `applyHeuristicAutoState` (`runner/job-runner.mjs:360`) calls
|
|
26
|
-
`applyAutoStateToStatus` with the **in-memory** status; `isManualCompletion`
|
|
27
|
-
requires `semanticState === "completed"` (it is `"idle"`), so the guard does not
|
|
28
|
-
fire and the post-exit heuristic classification (`in_progress`, since
|
|
29
|
-
`autoStateDoneDisabled()` defaults to true) overwrites the manual completion —
|
|
30
|
-
`state.json` becomes `idle` + `autoState: {kind: "in_progress"}`.
|
|
31
|
-
`maybeModelAutoState` has a fresh-read guard for this exact case;
|
|
32
|
-
`applyHeuristicAutoState` does not.
|
|
33
|
-
|
|
34
|
-
## Fix (minimal, four changes in `runner/job-runner.mjs`)
|
|
35
|
-
|
|
36
|
-
### Change 1 — persist order
|
|
37
|
-
Move `writeState` before `updateCodeRefsFromEvidence` inside `persist()`:
|
|
38
|
-
```js
|
|
39
|
-
writeStatus(root, status);
|
|
40
|
-
writeRunEvidence(root, evidence);
|
|
41
|
-
writeEvidence(root, evidence);
|
|
42
|
-
writeState(root, projectViewState(status, now, readState(root, viewId)));
|
|
43
|
-
updateCodeRefsFromEvidence(root, viewId, evidence, meta);
|
|
44
|
-
```
|
|
45
|
-
Semantics unchanged (code-refs extraction depends only on evidence + git).
|
|
46
|
-
Verified experimentally: markCompleted assertion passes again.
|
|
47
|
-
|
|
48
|
-
### Change 2 — heuristic persist goes through persistUnlessManual
|
|
49
|
-
In the close handler, the `applyHeuristicAutoState` branch's `persist(true)`
|
|
50
|
-
becomes `persistUnlessManual(true)` so a manual completion racing the
|
|
51
|
-
heuristic classification is not overwritten. `persistUnlessManual` checks
|
|
52
|
-
`isManualCompletion(readState(...))` (state.json — the only place
|
|
53
|
-
`completeView` writes the `completed` + `autoState: null` signal; it only
|
|
54
|
-
clears autoState in status.json) before writing.
|
|
55
|
-
|
|
56
|
-
### Change 3 — applyHeuristicAutoState manual-completion guard
|
|
57
|
-
Skip classification when state.json shows a manual completion, so the
|
|
58
|
-
in-memory status isn't mutated in a way a later drain/persist could replay
|
|
59
|
-
over the user's verdict:
|
|
60
|
-
```js
|
|
61
|
-
const latestState = readState(config.root, config.viewId);
|
|
62
|
-
if (isManualCompletion(latestState)) return false;
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
### Change 4 — drainQueuedFollowUp / finalizeSteeringIfNeeded guards
|
|
66
|
-
`drainQueuedFollowUp` (follow-up runs) and `finalizeSteeringIfNeeded`
|
|
67
|
-
(plan approval resurrection) both early-return on
|
|
68
|
-
`isManualCompletion(readState(...))` so the exit chain never starts new
|
|
69
|
-
work or resurrects a row the user just manually completed.
|
|
70
|
-
|
|
71
|
-
## Non-goals
|
|
72
|
-
- No changes to #44/#45 code (windowsHide / control socket)
|
|
73
|
-
- No handling of Windows-local EPERM cleanup noise (environment-only)
|
|
74
|
-
- No auto-state state machine refactor
|
|
75
|
-
|
|
76
|
-
## Verification
|
|
77
|
-
1. clobber test ≥3× on Node 24: all pass (assertion part)
|
|
78
|
-
2. Full `test/runner.integration.test.mjs`: failure set not worse than baseline
|
|
79
|
-
3. `npm run typecheck` if present
|
|
80
|
-
4. CI green (Node 22/24) after merge
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
# Spec: pty-attach.ts legacy quality debt cleanup (issue #8)
|
|
2
|
-
|
|
3
|
-
- **Date**: 2026-08-30
|
|
4
|
-
- **Issue**: zhuxixi/pi-agent-board#8
|
|
5
|
-
- **Type**: chore — zero behavior change (comments + signature trim only)
|
|
6
|
-
- **Status**: approved and implemented (final review clean; see docs/superpowers/plans/2026-08-30-pty-attach-quality-debt.md)
|
|
7
|
-
|
|
8
|
-
## Background
|
|
9
|
-
|
|
10
|
-
pi-lens flagged 10 legacy quality issues in `src/ui/pty-attach.ts` on 2026-08-21
|
|
11
|
-
(upstream original author's style, pre-existing). The issue tracked them for
|
|
12
|
-
separate cleanup so they wouldn't pollute feature PRs. Since then, #45 and #48
|
|
13
|
-
modified the file, so every line number in the issue has drifted. A fresh scan
|
|
14
|
-
of current main (1324 lines) found:
|
|
15
|
-
|
|
16
|
-
- **9 empty `catch {}` blocks** (issue said 8): L484, 510, 724, 745, 755, 766,
|
|
17
|
-
924, 951, 1010
|
|
18
|
-
- **1 `as unknown as` cast without a SAFETY comment**: L795 (`currentSize()`)
|
|
19
|
-
- **1 unused parameter**: L970 (`project(height, width)` — `width` never read)
|
|
20
|
-
|
|
21
|
-
Two other catches are out of scope: L902 (`onSocketData`, multi-line catch with
|
|
22
|
-
an explanatory comment) and L1048 (`openExternalTarget`, catch returns `false`).
|
|
23
|
-
|
|
24
|
-
## Goals
|
|
25
|
-
|
|
26
|
-
1. Every empty catch documents *why* silence is correct (intentional vs forgot).
|
|
27
|
-
2. The as-cast states the invariant that makes it safe.
|
|
28
|
-
3. No unused parameters in `project()`.
|
|
29
|
-
4. Zero behavior change: no logic, no logging, no reformatting beyond the edits.
|
|
30
|
-
|
|
31
|
-
## Non-goals
|
|
32
|
-
|
|
33
|
-
- No logging infrastructure (no logger is imported in pty-attach today; these
|
|
34
|
-
failures have no consumer; `forwardTerminalProtocols` runs at frame frequency
|
|
35
|
-
and would spam).
|
|
36
|
-
- No changes outside `src/ui/pty-attach.ts`.
|
|
37
|
-
- No touching L902/L1048 (already documented/behavioral).
|
|
38
|
-
- No drive-by refactors of nearby code.
|
|
39
|
-
|
|
40
|
-
## Decision: empty catches stay silent + explanatory comment (Option A)
|
|
41
|
-
|
|
42
|
-
Rejected alternative (Option B): debug-level logging — needs new UI-layer log
|
|
43
|
-
plumbing, has no consumer, and high-frequency paths would flood output.
|
|
44
|
-
|
|
45
|
-
Precedent in this repo: `dashboard.ts` uses `/* best effort: stats must never
|
|
46
|
-
block dispatch */` style comments for the same pattern.
|
|
47
|
-
|
|
48
|
-
Per-site comment text (implementation is mechanical):
|
|
49
|
-
|
|
50
|
-
| Line | Method | Failure tolerated | Comment to add |
|
|
51
|
-
|------|--------|-------------------|----------------|
|
|
52
|
-
| 484 | `enableMouseScroll()` | `terminal.write(XTSHIFTESCAPE/MOUSE_ENABLE)` | `/* best-effort: some terminals reject these sequences; mouse reporting is optional */` |
|
|
53
|
-
| 510 | `disableMouseScroll()` | `terminal.write(MOUSE_DISABLE)` | `/* best-effort: terminal may already be gone at teardown */` |
|
|
54
|
-
| 724 | `copySelectionToClipboard()` | OSC52 write | `/* best-effort: OSC52 clipboard support is optional */` |
|
|
55
|
-
| 745 | `pastePrimarySelection()` inner timer | `child.kill("SIGKILL")` | `/* the child may have already exited before the timeout fired */` |
|
|
56
|
-
| 755 | `pastePrimarySelection()` outer | `spawn("xclip")` | `/* silent no-op when xclip is absent — documented contract of this helper */` |
|
|
57
|
-
| 766 | `writePrimarySelection()` | `spawn("xclip")` | `/* silent no-op when xclip is absent */` |
|
|
58
|
-
| 924 | `forwardTerminalProtocols()` | per-sequence `terminal.write` | `/* best-effort: forwarded sequences are enhancements, never critical */` |
|
|
59
|
-
| 951 | `replayScreenLog()` | screen.log read/replay | `/* best-effort: a missing or racing screen.log must not block attach */` |
|
|
60
|
-
| 1010 | `close()` | `socket.destroy()` | `/* best-effort teardown: socket may already be destroyed */` |
|
|
61
|
-
|
|
62
|
-
## Decision: SAFETY comment for the as-cast (L795)
|
|
63
|
-
|
|
64
|
-
`currentSize()` reads `this.tui.terminal as unknown as { cols?: number;
|
|
65
|
-
columns?: number; rows?: number } | undefined`. Comment to add above the line:
|
|
66
|
-
|
|
67
|
-
```
|
|
68
|
-
// SAFETY: duck-typed read — Pi TUI's Terminal type does not consistently expose
|
|
69
|
-
// cols/columns/rows across versions (see resizeIfNeeded below). Runtime
|
|
70
|
-
// fallbacks (120/24) keep this safe when the fields are absent.
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
## Decision: delete `width` param (not `_` prefix)
|
|
74
|
-
|
|
75
|
-
`private project(height: number, width: number)` → `private project(height: number)`;
|
|
76
|
-
single call site L271 `this.project(bodyHeight, width)` → `this.project(bodyHeight)`.
|
|
77
|
-
Deleting is cleaner than `_width`: private method, exactly one caller, no
|
|
78
|
-
interface stability concerns.
|
|
79
|
-
|
|
80
|
-
## Verification
|
|
81
|
-
|
|
82
|
-
1. `npm run typecheck` — clean.
|
|
83
|
-
2. `npm test` — all pass (attach-related suites must stay green).
|
|
84
|
-
3. `grep -c "catch {}" src/ui/pty-attach.ts` — 0 (each expanded to a documented
|
|
85
|
-
3-line catch block).
|
|
86
|
-
4. `grep -n "project(" src/ui/pty-attach.ts` — signature and call site both
|
|
87
|
-
single-arg.
|
|
88
|
-
5. Coverage thresholds unaffected (comments + signature trim don't move lines/funcs/branches).
|
|
89
|
-
|
|
90
|
-
## Risks & mitigations
|
|
91
|
-
|
|
92
|
-
- **Line drift vs this spec**: implementation re-locates sites by method name
|
|
93
|
-
(as in the table), not by line number.
|
|
94
|
-
- **Untracked files in main checkout** (`scratch/`, `test-support/*`,
|
|
95
|
-
`test/pty-attach-*.test.mjs`): all work happens in a worktree; staging is
|
|
96
|
-
per-file, never `git add -A`.
|
|
97
|
-
- **Zero-behavior guarantee**: no statement is added/removed except the param
|
|
98
|
-
deletion and its call-site argument; review diff must show comments + two-line
|
|
99
|
-
signature/call-site change only.
|
|
100
|
-
|
|
101
|
-
## Rollout
|
|
102
|
-
|
|
103
|
-
- Worktree: `issue-8-pty-attach-quality-debt` (from main).
|
|
104
|
-
- Spec lands in worktree `docs/superpowers/specs/2026-08-30-pty-attach-quality-debt-design.md`
|
|
105
|
-
as the first commit, then plan → implement → local CR → PR.
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
# Design: User-first README v2 for Pi Agent Board
|
|
2
|
-
|
|
3
|
-
**Issue:** #51
|
|
4
|
-
**Date:** 2026-08-30
|
|
5
|
-
**Status:** Approved
|
|
6
|
-
**Language:** English
|
|
7
|
-
|
|
8
|
-
## Outcome
|
|
9
|
-
|
|
10
|
-
Rewrite `README.md` into a user-first English guide that accurately describes the currently shipped Pi Agent Board package, verified by current source, package metadata, tests, and manual verification notes.
|
|
11
|
-
|
|
12
|
-
The README will take a new user from installation to a first background session, then serve as a practical reference for dashboard actions, attach mode, filters, configuration, limitations, troubleshooting, and maintainer entry points.
|
|
13
|
-
|
|
14
|
-
This is documentation-only. No product behavior changes are part of the work.
|
|
15
|
-
|
|
16
|
-
## Source of truth
|
|
17
|
-
|
|
18
|
-
- `package.json` is authoritative for package identity, version, Node engine, repository, scripts, and Pi package metadata.
|
|
19
|
-
- `src/index.ts`, `src/commands/*`, `src/ui/dashboard.ts`, `src/ui/pty-attach.ts`, `src/runtime/service.mjs`, and `src/core/*` are authoritative for shipped behavior.
|
|
20
|
-
- `PRD.md`, `PROGRESS.md`, `REMAINING_WORK.md`, and dated design/plan documents are historical or planning material; they must not advertise disabled or planned behavior.
|
|
21
|
-
- Every command, shortcut, environment variable, default, and limitation must be checked against an exact source location or a verified command.
|
|
22
|
-
- Clearly distinguish shipped behavior, fallback behavior, disabled behavior, and planned/internal behavior.
|
|
23
|
-
- Avoid hard-coded test counts; state that CI and `npm run verify` are authoritative.
|
|
24
|
-
- Use `AGENT_BOARD_*` names for new configuration. Mention selected `AGENT_VIEW_*` names only as migration aliases.
|
|
25
|
-
|
|
26
|
-
## Information architecture
|
|
27
|
-
|
|
28
|
-
Use this task-oriented structure:
|
|
29
|
-
|
|
30
|
-
1. Title, package links, value proposition
|
|
31
|
-
2. What it does / when to use it
|
|
32
|
-
3. Requirements
|
|
33
|
-
4. Installation
|
|
34
|
-
5. Quick start
|
|
35
|
-
6. Entry points
|
|
36
|
-
7. Dashboard workflow
|
|
37
|
-
8. Views and actions
|
|
38
|
-
9. States, grouping, and filters
|
|
39
|
-
10. Attach mode
|
|
40
|
-
11. Persistence, safety, and limitations
|
|
41
|
-
12. Configuration
|
|
42
|
-
13. Troubleshooting
|
|
43
|
-
14. Development
|
|
44
|
-
15. Publishing
|
|
45
|
-
16. Further reading
|
|
46
|
-
|
|
47
|
-
The first half should be readable without knowing Pi internals. Advanced QA and implementation details should be linked rather than expanded inline.
|
|
48
|
-
|
|
49
|
-
## Content requirements
|
|
50
|
-
|
|
51
|
-
### Positioning
|
|
52
|
-
|
|
53
|
-
State that Agent Board is a full-screen TUI dashboard for dispatching, monitoring, inspecting, replying to, attaching to, and managing multiple durable background Pi sessions. Emphasize global cross-project visibility, resumability, dashboard triage, inline reply/evidence, and PTY/JSON fallback. Do not imply cloud execution, multi-user sharing, automatic worktree isolation, or full Claude parity.
|
|
54
|
-
|
|
55
|
-
### Requirements and installation
|
|
56
|
-
|
|
57
|
-
Include Pi, Node.js 20+, working Pi provider authentication, and PTY support for live attach/start-and-attach. Use `pi install npm:@zhuxixi/pi-agent-board` everywhere. Keep local path installation and symlink discovery as separate alternatives. Include a short one-shot auth sanity check and link detailed checks to `VERIFY.md`.
|
|
58
|
-
|
|
59
|
-
### Quick start and entry points
|
|
60
|
-
|
|
61
|
-
Use a concrete five-step first-task flow: `i` INSERT mode → type task → `Enter` to open the Start session dialog → review cwd/model/thinking/action → `Enter` on Start session to launch. Explain Space Peek, `r` in Peek, `v`, `e`, attach with Enter/Right/`>`, and that PTY detach with `Left` is gated by the child input line: it detaches on empty input, is forwarded while editing, and remains unconditional when disconnected. `Ctrl+]` is not a detach key — it is passed through to the child Pi editor.
|
|
62
|
-
|
|
63
|
-
Document `/agent-board`, `pi /agent-board`, `pi --agent-board`, and `/bg [prompt]`, including that `--agent-board` startup cannot attach and normal `/agent-board` is required for attach.
|
|
64
|
-
|
|
65
|
-
### Dashboard and actions
|
|
66
|
-
|
|
67
|
-
Explain Normal vs INSERT mode, draft-vs-empty `Enter`, Ctrl+N entering INSERT mode with a pre-filled `hello` prompt (the next Enter opens the launch flow), cwd favorites/path completion, model/thinking/action fields, persisted launch preferences, and PTY-dependent start-and-attach fallback.
|
|
68
|
-
|
|
69
|
-
Document exact destructive semantics: `d` confirms inactive Done; manual completion is default; Ctrl+X twice quickly archives; archive preserves the session file; X deletes inactive rows in the selected state; `m` batch selection supports Space/a/u/d/Ctrl+X.
|
|
70
|
-
|
|
71
|
-
Keep shortcut reference separated by view. State that `r` is available from Peek/Transcript/Evidence, not directly from the main list, and pending Pi questions must be answered via attach rather than inline reply.
|
|
72
|
-
|
|
73
|
-
### States, views, and filters
|
|
74
|
-
|
|
75
|
-
Document the seven labels: Queued, Running, Needs answer, Needs instructions, Done, Failed, Stopped. Explain separate process liveness, state grouping, folder grouping, pinned-first stable creation ordering, unread indicators, Peek, read-only transcript, Evidence/Diagnostics, durable FIFO follow-up queue, and `qN`.
|
|
76
|
-
|
|
77
|
-
Document filter syntax: `s:`, `review:ready`, `diag:stalled`, `evidence:error`, `queued:true|yes|1`, `steer:`, and free-text AND over name/summary/cwd. Explain aliases and case-insensitivity. Caveat that `diag:stalled` can consume persisted diagnostics but there is no general current provider-stall detector.
|
|
78
|
-
|
|
79
|
-
Explain locally extracted issue/PR badges and optional per-root `providers.json`, without asserting an unverified custom schema.
|
|
80
|
-
|
|
81
|
-
### Attach and persistence
|
|
82
|
-
|
|
83
|
-
Describe PTY attach, conditional `Left` detach (empty child input detaches; edited input forwards the key; disconnected hosts can always be exited), `Ctrl+]` passthrough to the child Pi editor, PageUp/PageDown/Home/End/mouse wheel scrollback, link opening, drag/double-click copy, optional X11 middle-click paste, clipboard/image passthrough, cold-host loading/reconnect, warm host pool, and Windows named-pipe/hidden-console behavior without promising terminal-emulator parity.
|
|
84
|
-
|
|
85
|
-
Explain PTY vs JSON fallback, adopted external-session PTY requirement, and `!` diagnostics. Document the default store at `~/.pi/agent/agent-board/`, high-level artifacts, persistence across reload/restart/worker exit, and stale-row reconciliation.
|
|
86
|
-
|
|
87
|
-
Prominently state that worktree isolation is currently disabled and not automatically created; same-repository concurrent writes are unsafe unless the user manually avoids overlap or supplies isolation. Also state no cloud/multi-user coordination, row deletion preserves session files, auth remains required, startup attach limitation, and PTY native-dependency limitation.
|
|
88
|
-
|
|
89
|
-
### Configuration
|
|
90
|
-
|
|
91
|
-
List supported user-facing settings with exact defaults/disable values: ROOT, AUTO_STATE, AUTO_STATE_MODEL, AUTO_STATE_NO_DONE, SUMMARY_MODEL, TITLE_MODEL, TITLE_THINKING_LEVEL, CODE_REFS, DISABLE_PTY, FORCE_PTY, ATTACH_MOUSE, ENABLE_MOUSE_SCROLL, WHEEL_LINES, MAX_WARM_HOSTS, WARM_HOST_TTL_MS, ATTACH_NATIVE_PASTE, FORWARD_OSC52, and FORWARD_IMAGES.
|
|
92
|
-
|
|
93
|
-
Do not list internal child markers. Do not advertise `AGENT_BOARD_ALLOW_PIPE_FALLBACK` as a normal user toggle because current service dispatch does not pass the ambient variable into the injected PTY runner config. Mention selected legacy `AGENT_VIEW_*` aliases only as compatibility paths.
|
|
94
|
-
|
|
95
|
-
### Troubleshooting and maintainer sections
|
|
96
|
-
|
|
97
|
-
Troubleshoot stuck Running/auth, `node-pty unavailable`, slow/reconnecting attach, start-and-attach fallback, rejected inline replies, and same-repo conflicts. Link `VERIFY.md`.
|
|
98
|
-
|
|
99
|
-
Keep Development to `npm install` and `npm run verify`; explain verify briefly. Keep Publishing to verify, version bump, and publish. Link further reading (`VERIFY.md`, `PRD.md`, `PROGRESS.md`, and relevant design docs) without turning README into a historical log.
|
|
100
|
-
|
|
101
|
-
## Supporting documentation
|
|
102
|
-
|
|
103
|
-
Correct the old unscoped install command in `VERIFY.md` from `pi install npm:pi-agent-board` to `pi install npm:@zhuxixi/pi-agent-board`, because README links to it. Make no other supporting-doc changes.
|
|
104
|
-
|
|
105
|
-
## Validation
|
|
106
|
-
|
|
107
|
-
- Line-by-line compare the final README with source and package metadata.
|
|
108
|
-
- Check Markdown link targets and stale package names/status wording.
|
|
109
|
-
- Run targeted documentation scans.
|
|
110
|
-
- Run verification in the isolated worktree; do not count the main session's untracked PTY tests as evidence.
|
|
111
|
-
- Final diff should contain README, this approved spec, the implementation plan, and the one-line VERIFY correction only.
|
|
112
|
-
|
|
113
|
-
## Non-goals
|
|
114
|
-
|
|
115
|
-
No runtime behavior, worktree implementation, plan-approval UI, provider-stall detection, docs generator, changelog, or broad PRD/progress rewrite.
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
# Issue #66 Spec:← detach 门禁锚点重构(反白假光标)
|
|
2
|
-
|
|
3
|
-
> Draft:2026-09-01。state: **pending user review**(⏸ 等用户确认后再进 worktree)
|
|
4
|
-
|
|
5
|
-
## 1. 根因(调研结论,详见 issue 评论)
|
|
6
|
-
|
|
7
|
-
`childInputLooksEmpty()` 以 **xterm 硬件光标所在行**为锚点(`baseY + cursorY`),而 pi 渲染时:
|
|
8
|
-
- streaming:差分帧只重绘 Working 行,光标**持续**停在 `⠙ Working...` 行(~120ms/帧);
|
|
9
|
-
- attach 完成瞬间:光标停在输出区/提示行。
|
|
10
|
-
|
|
11
|
-
两态下输入框为空但光标行非空 → `isProbablyEmptyPiInputLine` 判非空 → `←` 被转发给子进程。
|
|
12
|
-
v0.5.1 起 `ctrl+]` 已透传 Pi,`←` 是 attach 表面**唯一 detach 键** → 用户被困。
|
|
13
|
-
|
|
14
|
-
**可靠锚点(实证)**:pi 输入行恒渲染**反白假光标**(`\x1b[7m...\x1b[27m`,pi-tui `input.js` render + 本机 screen.log 双重确认),且反白 cell 在 xterm buffer 中**持久存在**(差分帧不重绘也保留)。从 buffer 底部向上扫"含 inverse cell 的行"即可定位输入行,**不依赖光标位置**。
|
|
15
|
-
|
|
16
|
-
## 2. 修复设计
|
|
17
|
-
|
|
18
|
-
### 2.1 `src/core/pty-input.mjs`
|
|
19
|
-
|
|
20
|
-
新增纯函数(现有 `isProbablyEmptyPiInputLine` 不变):
|
|
21
|
-
|
|
22
|
-
```js
|
|
23
|
-
export function isProbablyPiInputLine(line) // 行首(trim 左空白)为 prompt/continuation 字形
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
字形集:`›>┃│|┆╎╏:`(与 isProbablyEmptyPiInputLine 的 trim 字符集一致,兼容有字形 pi 版本)。
|
|
27
|
-
|
|
28
|
-
### 2.2 `src/ui/pty-attach.ts` — `childInputLooksEmpty()` 重构
|
|
29
|
-
|
|
30
|
-
三层判定,按优先级:
|
|
31
|
-
|
|
32
|
-
1. **反白假光标锚点**(主):从 `active.baseY + active.length - 1` 向上扫,找第一个含 `isInverse()` cell 的行 → 返回 `isProbablyEmptyPiInputLine(line.translateToString(true))`;
|
|
33
|
-
2. **字形行 fallback**(兼容无反白光标渲染的 pi 变体):找不到反白行时,同向扫 `isProbablyPiInputLine` 行 → 判空;
|
|
34
|
-
3. **逃生兜底**:都找不到(损坏 buffer / replay 窗口无输入行帧)→ 视为空 → `←` 可 detach。
|
|
35
|
-
|
|
36
|
-
行为矩阵:
|
|
37
|
-
|
|
38
|
-
| 状态 | 反白行 | 字形行 | 判定 | ← 行为 |
|
|
39
|
-
|------|--------|--------|------|--------|
|
|
40
|
-
| 输入框空(attach 后 / streaming 中) | 空行 | — | 空 | detach ✓ |
|
|
41
|
-
| 输入框有草稿 | 非空行 | — | 非空 | 转发(不抢编辑键)✓ |
|
|
42
|
-
| 损坏残影 buffer | 无 | 无 | 空 | detach(逃生)✓ |
|
|
43
|
-
| 有字形 pi 版本 | 空行(`> `) | `> ` | 空 | detach ✓ |
|
|
44
|
-
|
|
45
|
-
## 3. 非目标
|
|
46
|
-
|
|
47
|
-
- ❌ ← 无条件 detach(违背"编辑中不抢键"产品意图,attach-flow.ts 亦保留门禁)
|
|
48
|
-
- ❌ 控制 socket 编辑器状态查询(pi 无此协议)
|
|
49
|
-
- ❌ 修改 `ctrl+]` 语义(v0.5.1 已透传 Pi,保持)
|
|
50
|
-
|
|
51
|
-
## 4. 验收矩阵
|
|
52
|
-
|
|
53
|
-
| ID | 功能点 | 验收方式 | 具体验证 | 通过标准 |
|
|
54
|
-
|----|--------|----------|----------|----------|
|
|
55
|
-
| A1 | `isProbablyPiInputLine` 纯函数行为 | 自动化验证(unit) | `node --test test/pty-input.test.mjs` | 字形行 true;内容行/空行 false |
|
|
56
|
-
| A2 | 反白锚点:空输入行(光标漂移)→ detach | 自动化验证(unit) | `node --test test/pty-attach-detach-gate.test.mjs`(smoke B3) | `leftDetachesWhenCursorOffEmptyInputLine === true` |
|
|
57
|
-
| A3 | 反白锚点:草稿行 → 转发 | 自动化验证(unit) | 同上(smoke B) | `leftStaysGatedOnNonEmptyLine === true` |
|
|
58
|
-
| A4 | 反白锚点:无假光标行 + 空行 fallback | 自动化验证(unit) | 同上(smoke 新增) | `leftDetachesOnEmptyInput === true` 保持 |
|
|
59
|
-
| A5 | 损坏 buffer(无输入行)→ 逃生 | 自动化验证(unit) | 同上(smoke B1) | `leftEscapesOnGarbledBuffer === true` |
|
|
60
|
-
| A6 | 全套回归 + 覆盖率门禁 | 自动化验证(build/static + unit) | `npm run verify`(typecheck + 全部测试 + coverage 门禁 lines85/funcs80/branches70 + pack:dry,Node 22/24) | 全绿;无既有测试被改语义 |
|
|
61
|
-
| U1 | attach 后输入框空按 ← 回退 | 用户实测 | attach 进入 pi session → 立即按 ← | 回退到 dashboard,无需 ↑↓/输入删除 |
|
|
62
|
-
| U2 | pi 思考中(Working...)按 ← 回退 | 用户实测 | 触发 pi 思考(Working 动画)→ 按 ← | 回退到 dashboard |
|
|
63
|
-
|
|
64
|
-
## 5. 可测性拆分设计
|
|
65
|
-
|
|
66
|
-
| 函数 | 位置 | 职责 | 测试边界 |
|
|
67
|
-
|------|------|------|----------|
|
|
68
|
-
| `isProbablyPiInputLine(line)` | pty-input.mjs(纯函数) | 行首字形判定 | 输入字符串 → boolean;不碰 buffer/term |
|
|
69
|
-
| `findLastInverseCellLine(active)` | pty-attach.ts(私有,仅依赖 xterm buffer 接口) | 底部向上扫含 inverse cell 的行号 | 输入 fake buffer({baseY, length, getLine})→ 行号/null;**不依赖 cursorY** |
|
|
70
|
-
| `childInputLooksEmpty()` | pty-attach.ts | 三层组合判定 | 输入 buffer 状态 → boolean;经 smoke 场景验证 |
|
|
71
|
-
|
|
72
|
-
约束:`findLastInverseCellLine` 只读 buffer(无副作用);`isProbablyPiInputLine` 无状态;smoke 场景构造 xterm 渲染序列(`\x1b[7m \x1b[27m` 等)验证端到端判定,不 mock 内部函数。
|
|
73
|
-
|
|
74
|
-
## 6. 风险
|
|
75
|
-
|
|
76
|
-
- pi 未来版本假光标不再反白渲染 → 字形/兜底 fallback 接管(判定仍可用,只是可能放宽为"视为空")
|
|
77
|
-
- 多行编辑器最后一行空 → 判空 detach(与现状光标行逻辑一致,非回归)
|
|
78
|
-
- buffer 中其他反白元素(选中文本等)在输入行下方 → 极罕见;底部扫描以"最靠下"优先,选中文本在输出区(输入行上方)不干扰
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
# Issue #68 Spec:editor_state 推送 —— detach 门禁从渲染启发式迁移到子 pi 编辑器真实状态
|
|
2
|
-
|
|
3
|
-
> Draft:2026-09-02。state: **pending user review**(⏸ 等用户确认后再进 worktree)
|
|
4
|
-
|
|
5
|
-
## 1. 背景与根因
|
|
6
|
-
|
|
7
|
-
#66 的三层渲染启发式(反色假光标锚点 → 字形行 → 逃生兜底)在真实 pi 渲染下全部失效:
|
|
8
|
-
- 空编辑器行行首**无 prompt 字形**;attach buffer 常**无反色 cell**(差分渲染不重绘编辑器行);
|
|
9
|
-
- 结果 tier-1 落空 → tier-2 误命中聊天区 markdown 表格行(`│ ... │`)/ 冒号行(`:` 开头)→ 判"有草稿" → `←` 被吞(#68/#69 实锤)。
|
|
10
|
-
|
|
11
|
-
渲染流**不是**编辑器状态的可靠载体。根治方案(本 spec):子 pi 扩展直接读取 `ctx.ui.getEditorText()`(pi 官方 API,权威状态),经现有 control socket 推送给 runner → broadcast 给 attach 面板 → 门禁直接用。渲染启发式降级为 fallback。
|
|
12
|
-
|
|
13
|
-
## 2. 架构设计
|
|
14
|
-
|
|
15
|
-
```
|
|
16
|
-
子 pi 扩展(isHostedChild 时 session_start 启动上报循环)
|
|
17
|
-
└─ 每 100ms 轮询 ctx.ui.getEditorText(),文本变化时:
|
|
18
|
-
→ {type:"editor_state", empty} → control socket(JSONL client)
|
|
19
|
-
pty-runner(handleClientLine 新 case "editor_state")
|
|
20
|
-
├─ 缓存 host.editorEmpty + persist 到 host.json(可选,见 §7)
|
|
21
|
-
├─ broadcast({type:"editor_state", empty}) 给所有 attach clients
|
|
22
|
-
└─ hello 消息带初始 editorEmpty(新 attach 立即同步)
|
|
23
|
-
attach 面板(pty-attach.ts)
|
|
24
|
-
├─ 缓存 this.editorEmpty: boolean | null(null = 未知)
|
|
25
|
-
├─ onSocketData 新 case "editor_state" 更新缓存
|
|
26
|
-
└─ ← 判定:resolveEditorEmpty(editorEmpty, 启发式结果)
|
|
27
|
-
editorEmpty !== null ? editorEmpty : 现有启发式(fallback)
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
### 2.1 协议(JSONL,向后兼容)
|
|
31
|
-
|
|
32
|
-
```jsonc
|
|
33
|
-
// 子 pi 扩展 → runner
|
|
34
|
-
{ "type": "editor_state", "empty": true }
|
|
35
|
-
|
|
36
|
-
// runner → attach clients(广播 + hello 初始值)
|
|
37
|
-
{ "type": "editor_state", "empty": true }
|
|
38
|
-
{ "type": "hello", "status": {...}, "editorEmpty": true }
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
旧端忽略未知字段/消息(现有 parser 已忽略未知 type)→ 协议向后兼容。
|
|
42
|
-
|
|
43
|
-
### 2.2 子 pi 扩展上报循环(新文件 `src/core/editor-state-reporter.mjs`)
|
|
44
|
-
|
|
45
|
-
- 激活条件:`process.env.AGENT_BOARD_CHILD === "1" || process.env.AGENT_VIEW_CHILD === "1"`(与 index.ts 同判据)
|
|
46
|
-
- socket 路径:`controlSocketPathFor(process.platform, root, viewId)`(复用 `src/core/paths.mjs`)
|
|
47
|
-
- 连接策略:runner **先 spawn 子 pi 后 listen** → 初始连接带重试(退避 1s→2s→…封顶 5s,永久重试);断开后同样重连
|
|
48
|
-
- 轮询:`setInterval(100ms)` 读 `getEditorText()`,`text !== lastText` 才发送(dedupe,避免常发)
|
|
49
|
-
- 生命周期:session_start 启动、进程退出自然回收;`stop()` 供测试
|
|
50
|
-
- 依赖注入设计(可测性,§5):`createEditorStateReporter({ getEditorText, connect, intervalMs = 100, scheduler = defaultScheduler })` → `{ start, stop }`;`scheduler = { interval(fn, ms), timeout(fn, ms), clear(handle) }`(默认 setInterval/setTimeout 包装);生产接线在 index.ts
|
|
51
|
-
|
|
52
|
-
### 2.3 runner 改动(`runner/pty-runner.mjs`)
|
|
53
|
-
|
|
54
|
-
- `handleClientLine` switch 加 `case "editor_state"`:`host.editorEmpty = !!msg.empty; broadcast({type:"editor_state", empty: host.editorEmpty})`(不 persist,见 §7)
|
|
55
|
-
- `hello` 消息:`{ type:"hello", status: host, editorEmpty: host.editorEmpty ?? null }`
|
|
56
|
-
- 子 pi 断开(child exit)→ editorEmpty 复位 null 并 broadcast(attach 端退回启发式)
|
|
57
|
-
|
|
58
|
-
### 2.4 attach 面板改动(`src/ui/pty-attach.ts`)
|
|
59
|
-
|
|
60
|
-
- 字段 `private editorEmpty: boolean | null = null`
|
|
61
|
-
- `onSocketData`:`case "editor_state"` → `this.editorEmpty = !!msg.empty`;`case "hello"` → 读 `msg.editorEmpty`
|
|
62
|
-
- `←` 判定(handleInput):`shouldEscapeAttach(this.connected, this.resolveEditorEmpty())`
|
|
63
|
-
- 纯函数 `resolveEditorEmpty(editorEmpty, heuristic)`(放 `src/core/pty-input.mjs` 或新纯函数文件):
|
|
64
|
-
`editorEmpty === null ? heuristic : editorEmpty`
|
|
65
|
-
- `ctrl+]` 语义不变;现有三层启发式**原样保留**(fallback)
|
|
66
|
-
|
|
67
|
-
## 3. 行为矩阵
|
|
68
|
-
|
|
69
|
-
| 场景 | editorEmpty | 判定 | ← 行为 |
|
|
70
|
-
|------|-------------|------|--------|
|
|
71
|
-
| 空输入(attach/streaming/任意时刻) | true | 空 | detach ✓ |
|
|
72
|
-
| 草稿(多行/单行/autocomplete 后/↑ 召回) | false | 非空 | 转发(编辑保护)✓ |
|
|
73
|
-
| 提交瞬间 | true(清空) | 空 | detach ✓ |
|
|
74
|
-
| 子 pi 扩展缺失/socket 断流 | null | 启发式 | 现状 fallback 行为 |
|
|
75
|
-
| 损坏 buffer + editor_state 正常 | true/false | 权威 | 正确判定(不再被残影误导)✓ |
|
|
76
|
-
|
|
77
|
-
## 4. 非目标
|
|
78
|
-
|
|
79
|
-
- ❌ 删除现有渲染启发式(保留为 fallback)
|
|
80
|
-
- ❌ #69 的短期 tier-2 收紧(#68 长期方案落地后启发式仅 fallback;#69 由维护者按需处理)
|
|
81
|
-
- ❌ RPC 模式特判(子 pi 恒 PTY/TUI;RPC 下 getEditorText 恒 "" → empty 恒 true → ← 恒 detach,无 TUI 草稿概念,方向安全)
|
|
82
|
-
- ❌ 击键级拦截(评论 2 已排除:会覆盖编辑器光标行为,hack 且危险)
|
|
83
|
-
|
|
84
|
-
## 5. 可测性拆分设计
|
|
85
|
-
|
|
86
|
-
| 单元 | 位置 | 职责 | 测试边界 |
|
|
87
|
-
|------|------|------|----------|
|
|
88
|
-
| `createEditorStateReporter({getEditorText, connect, intervalMs, scheduler})` | src/core/editor-state-reporter.mjs | 轮询、变化 dedupe、断线重连退避、stop | 依赖全注入(scheduler 为 {interval, timeout, clear} 手动时钟);fake connect/getEditorText/scheduler → 断言 send 序列与时机;不碰真实 socket/interval |
|
|
89
|
-
| `resolveEditorEmpty(editorEmpty, heuristic)` | src/core/pty-input.mjs | 判定优先级 | 纯函数:4 种输入组合 → boolean |
|
|
90
|
-
| runner `editor_state` case + hello 字段 | runner/pty-runner.mjs | 缓存、broadcast、复位 | 现有 integration harness(pty-runner.integration.test.mjs 模式):注入 client line → 断言 broadcast 与 hello 载荷 |
|
|
91
|
-
| attach `editorEmpty` 缓存 + 判定 | src/ui/pty-attach.ts | 消息 case、字段、← 判定 | detach-gate-smoke 场景 H(editor_state 消息 + 草稿 buffer → detach);场景 I(草稿 + editor_state:false → 转发) |
|
|
92
|
-
|
|
93
|
-
约束:reporter 的生产接线只做「注入真实依赖」,纯逻辑全在工厂函数内;实现不得把轮询/重连逻辑重新耦合进 index.ts。
|
|
94
|
-
|
|
95
|
-
## 6. 验收矩阵
|
|
96
|
-
|
|
97
|
-
| ID | 功能点 | 验收方式 | 具体验证 | 通过标准 |
|
|
98
|
-
|----|--------|----------|----------|----------|
|
|
99
|
-
| A1 | reporter:轮询变化才上报、dedupe、stop | 自动化验证(unit) | `node --test test/editor-state-reporter.test.mjs` | 变化序列上报次数/内容精确匹配;无变化零上报 |
|
|
100
|
-
| A2 | reporter:初始连接重试退避 + 断线重连 | 自动化验证(unit) | 同上 | fake connect 先拒后通 → 按退避序列重试并在连通后恢复上报 |
|
|
101
|
-
| A3 | runner:editor_state case 缓存 + broadcast + hello 初始值 + exit 复位 | 自动化验证(integration) | `node --test test/pty-runner.integration.test.mjs` | 注入 editor_state line → 其他 client 收到广播;新 client hello 带 editorEmpty;exit 后 editorEmpty=null 广播 |
|
|
102
|
-
| A4 | attach:editor_state 消息更新缓存、判定优先级(null 走启发式) | 自动化验证(unit) | detach-gate-smoke 场景 H/I | 空输入+editorEmpty=true → detach;草稿+editorEmpty=false → 转发;无消息 → 走启发式(既有场景全绿) |
|
|
103
|
-
| A5 | 纯函数 resolveEditorEmpty | 自动化验证(unit) | `node --test test/pty-input.test.mjs` | 4 组合精确匹配 |
|
|
104
|
-
| A6 | 全套回归 + 覆盖率门禁 | 自动化验证(build/static + unit) | `npm run verify`(typecheck + 全测 + c8 门禁 85/80/70 + pack:dry);CI Node 22/24 | 全绿;既有 430+ 测试无语义改动 |
|
|
105
|
-
| U1 | 空输入 attach 后 ← 回退 | 用户实测 | attach 进入 → 立即 ← | 回 dashboard |
|
|
106
|
-
| U2 | Working... 中 ← 回退 | 用户实测 | 思考动画中 ← | 回 dashboard |
|
|
107
|
-
| U3 | 草稿中 ← 左移不被抢 | 用户实测 | 输入草稿 → ← | 光标左移,不 detach |
|
|
108
|
-
| U4 | 断流 fallback(子 pi 扩展被禁用时仍可退出) | 用户实测 | 临时禁用扩展的 editor_state 上报(env 开关)→ ← | 仍可回退(启发式兜底) |
|
|
109
|
-
|
|
110
|
-
## 7. 决策与风险
|
|
111
|
-
|
|
112
|
-
- **不 persist editorEmpty**(首版):host.json 是行状态持久化,editor 状态是瞬态(毫秒级),重启后 attach 的 hello 里 null → 启发式兜底 1-2s 内收到首个 editor_state 修正。避免 host.json 写放大(100ms 级变化频率)。
|
|
113
|
-
- **100ms 轮询开销**:getEditorText 是内存 join,忽略不计;socket 仅在变化时写。
|
|
114
|
-
- **风险:editorEmpty=false 但用户实际想退出**(草稿场景):与既有"编辑中不抢键"哲学一致(attach-flow 同样);用户清空草稿即可退出。
|
|
115
|
-
- **风险:子 pi 扩展未安装/旧版**(fallback 启发式接管,U4 验证)。
|
|
116
|
-
- **风险:runner 老版本**(不认识 editor_state case,忽略)→ attach 收不到 → 启发式兜底,向后兼容。
|
|
117
|
-
|
|
118
|
-
## 8. 关联
|
|
119
|
-
|
|
120
|
-
- #68(本 issue 长期方案)、#66/#67(启发式起源与失效)、#69(同症状短期方案,由维护者处理)、#42/#48(门禁可靠性原则:视图必须始终可退出)
|