@huseynsnmz/pi-board 0.15.1 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/CHANGELOG.md +26 -4
  2. package/CONTRIBUTING.md +7 -0
  3. package/README.md +53 -419
  4. package/SECURITY.md +19 -0
  5. package/docs/how-it-works.md +31 -0
  6. package/docs/reference.md +276 -0
  7. package/package.json +18 -19
  8. package/src/index.ts +9 -1
  9. package/src/runtime/service.mjs +12 -0
  10. package/src/ui/dashboard.ts +14 -8
  11. package/src/ui/double-left.ts +5 -1
  12. package/src/ui/focus.ts +7 -1
  13. package/src/ui/waiting.ts +10 -5
  14. package/IMPLEMENTATION_PLAN.md +0 -920
  15. package/PRD.md +0 -484
  16. package/PROGRESS.md +0 -142
  17. package/VERIFY.md +0 -115
  18. package/docs/BATCH_SELECTION_READ_FLOW.md +0 -277
  19. package/docs/EXPLORATION.md +0 -187
  20. package/docs/PTY_ATTACH_IMPLEMENTATION_PLAN.md +0 -581
  21. package/docs/RELEASE.md +0 -148
  22. package/docs/superpowers/plans/2026-08-15-screenlog-gc.md +0 -704
  23. package/docs/superpowers/plans/2026-08-16-attach-double-cursor-jiggle-retry.md +0 -499
  24. package/docs/superpowers/plans/2026-08-21-attach-coldstart-jiggle-rearm.md +0 -797
  25. package/docs/superpowers/plans/2026-08-21-dashboard-keypress-lag.md +0 -366
  26. package/docs/superpowers/plans/2026-08-21-issue-14-needs-input-no-auto-done.md +0 -576
  27. package/docs/superpowers/plans/2026-08-22-jiggle-shrink-and-hold.md +0 -337
  28. package/docs/superpowers/plans/2026-08-22-launch-cwd-favorites.md +0 -916
  29. package/docs/superpowers/plans/2026-08-22-question-tool-grouping.md +0 -211
  30. package/docs/superpowers/plans/2026-08-22-rename-state-labels.md +0 -182
  31. package/docs/superpowers/plans/2026-08-22-stable-list-order.md +0 -51
  32. package/docs/superpowers/plans/2026-08-27-locks-acquirelock-spin.md +0 -614
  33. package/docs/superpowers/plans/2026-08-27-pty-runner-test-flaky.md +0 -32
  34. package/docs/superpowers/plans/2026-08-29-code-refs-badges.md +0 -223
  35. package/docs/superpowers/plans/2026-08-30-circular-navigation.md +0 -308
  36. package/docs/superpowers/plans/2026-08-30-post-exit-timing-fix.md +0 -37
  37. package/docs/superpowers/plans/2026-08-30-pty-attach-quality-debt.md +0 -311
  38. package/docs/superpowers/plans/2026-08-30-readme-v2.md +0 -294
  39. package/docs/superpowers/plans/2026-09-01-attach-detach-gate-cursor-anchor.md +0 -284
  40. package/docs/superpowers/plans/2026-09-02-attach-detach-editor-state.md +0 -722
  41. package/docs/superpowers/plans/2026-09-03-code-refs-pr-backlink-narrow.md +0 -551
  42. package/docs/superpowers/plans/2026-09-03-detach-gate-glyph-fallback.md +0 -146
  43. package/docs/superpowers/plans/2026-09-04-evidence-outputpreview.md +0 -209
  44. package/docs/superpowers/plans/2026-09-04-warm-host-reclaim.md +0 -796
  45. package/docs/superpowers/plans/2026-09-05-issue-13-drainnextfollowup-pty-probe.md +0 -114
  46. package/docs/superpowers/plans/2026-09-05-issue-38-windows-wezterm-ime-cursor.md +0 -73
  47. package/docs/superpowers/plans/2026-09-05-issue-39-truncate-codepoint-boundary.md +0 -143
  48. package/docs/superpowers/plans/2026-09-05-issue-61-mention-fallback-guards.md +0 -226
  49. package/docs/superpowers/plans/2026-09-05-issue-63-flaky-manual-completion.md +0 -87
  50. package/docs/superpowers/plans/2026-09-05-issue-64-changelog-release-helper.md +0 -53
  51. package/docs/superpowers/plans/2026-09-05-pty-host-stacking-sock-race.md +0 -731
  52. package/docs/superpowers/plans/2026-09-08-attach-ctrl-left-detach.md +0 -30
  53. package/docs/superpowers/plans/2026-09-08-dashboard-shrink-repaint.md +0 -68
  54. package/docs/superpowers/plans/2026-09-08-issue-11-attach-runtime-desync-heal.md +0 -917
  55. package/docs/superpowers/plans/2026-09-08-legacy-stale-host-recovery.md +0 -125
  56. package/docs/superpowers/plans/2026-09-08-spawn-async-error-swallow.md +0 -56
  57. package/docs/superpowers/plans/2026-09-08-stale-model-attach-guard.md +0 -96
  58. package/docs/superpowers/plans/2026-09-09-claimpid-blocks-replace.md +0 -267
  59. package/docs/superpowers/plans/2026-09-09-harden-runner-architecture.md +0 -603
  60. package/docs/superpowers/plans/2026-09-09-single-writer-completion.md +0 -252
  61. package/docs/superpowers/plans/2026-09-10-reader-consistency.md +0 -115
  62. package/docs/superpowers/plans/2026-09-14-attach-cursor-dectcem-gate.md +0 -469
  63. package/docs/superpowers/plans/2026-09-14-attach-snapshot.md +0 -92
  64. package/docs/superpowers/plans/2026-09-14-host-meta-orphan-lock.md +0 -771
  65. package/docs/superpowers/plans/2026-09-14-issue-106-terminal-frame-cognition.md +0 -299
  66. package/docs/superpowers/plans/2026-09-14-issue-113-foreground-preview-race.md +0 -609
  67. package/docs/superpowers/plans/2026-09-14-terminal-model.md +0 -145
  68. package/docs/superpowers/plans/2026-09-15-coordinator-pipe-root-normalize.md +0 -224
  69. package/docs/superpowers/plans/2026-09-15-lease-publish-eprem-reclaim.md +0 -341
  70. package/docs/superpowers/plans/2026-09-18-detach-anchor-reporter-endpoint.md +0 -875
  71. package/docs/superpowers/plans/2026-09-20-control-lifecycle.md +0 -116
  72. package/docs/superpowers/plans/2026-09-20-issue-121-perf-gate-out-of-coverage.md +0 -517
  73. package/docs/superpowers/plans/2026-09-23-a5-mid-stream-assertion.md +0 -25
  74. package/docs/superpowers/plans/2026-09-23-cwd-browse-tab-drill.md +0 -393
  75. package/docs/superpowers/plans/2026-09-23-terminal-query-forwarding.md +0 -51
  76. package/docs/superpowers/plans/2026-09-23-unconditional-detach-key.md +0 -58
  77. package/docs/superpowers/plans/2026-09-24-control-reconcile-flake.md +0 -698
  78. package/docs/superpowers/plans/2026-09-24-probe-attached-clients.md +0 -605
  79. package/docs/superpowers/plans/2026-09-29-issue-145-holding-semantic-state.md +0 -1183
  80. package/docs/superpowers/plans/2026-09-29-issue-145-user-verification.md +0 -66
  81. package/docs/superpowers/plans/2026-09-30-board-delete-key-x-y-confirm.md +0 -649
  82. package/docs/superpowers/plans/2026-09-30-issue-153-runner-stop-boot-window.md +0 -618
  83. package/docs/superpowers/plans/2026-09-30-issue-95-test-discipline.md +0 -555
  84. package/docs/superpowers/plans/2026-09-30-publish-dispatch-tag-check.md +0 -259
  85. package/docs/superpowers/plans/2026-10-06-attach-settle-scheme-delivery.md +0 -751
  86. package/docs/superpowers/plans/2026-10-09-issue-167-attach-middle-paste.md +0 -509
  87. package/docs/superpowers/plans/2026-10-09-issue-169-fullscreen-mouse-ownership.md +0 -346
  88. package/docs/superpowers/plans/2026-10-10-attach-delete-in-place.md +0 -832
  89. package/docs/superpowers/plans/2026-10-10-issue-174-selection-mirror-paste-source.md +0 -510
  90. package/docs/superpowers/plans/2026-10-14-issue-170-attach-reply-guard.md +0 -771
  91. package/docs/superpowers/plans/2026-10-14-issue-178-accept-registration-race.md +0 -1257
  92. package/docs/superpowers/plans/2026-10-14-issue-184-snapshot-window-contract.md +0 -657
  93. package/docs/superpowers/plans/2026-10-14-issue-185-attach-wheel-alt-screen.md +0 -47
  94. package/docs/superpowers/plans/2026-10-14-issue-187-motionless-drag.md +0 -931
  95. package/docs/superpowers/plans/2026-10-14-issue-189-mixed-input-segments.md +0 -823
  96. package/docs/superpowers/plans/2026-10-15-issue-188-x10-mouse-release.md +0 -526
  97. package/docs/superpowers/plans/2026-10-16-issue-198-attach-wheel-forward-fallback.md +0 -32
  98. package/docs/superpowers/plans/2026-10-18-attach-wheel-replay-pseudo-scrollback.md +0 -437
  99. package/docs/superpowers/plans/2026-10-19-issue-213-reply-guard-csi-key.md +0 -257
  100. package/docs/superpowers/plans/2026-10-19-issue-214-attach-forward-alt-modifier.md +0 -202
  101. package/docs/superpowers/plans/2026-10-21-issue-221-ask-user-question.md +0 -198
  102. package/docs/superpowers/plans/2026-10-23-issue-222-attach-ready.md +0 -290
  103. package/docs/superpowers/plans/2026-10-23-issue-225-monotonic-foreground-state.md +0 -450
  104. package/docs/superpowers/plans/2026-10-23-issue-229-vim-jkl.md +0 -449
  105. package/docs/superpowers/plans/2026-10-23-issue-232-attach-bg-only-cells.md +0 -153
  106. package/docs/superpowers/plans/2026-10-23-lease-heartbeat-staleness.md +0 -537
  107. package/docs/superpowers/plans/2026-10-23-selected-row-highlight.md +0 -405
  108. package/docs/superpowers/plans/2026-10-24-issue-217-windows-pid-reuse-stopping.md +0 -367
  109. package/docs/superpowers/plans/2026-10-24-issue-230-warm-host-background-runs.md +0 -806
  110. package/docs/superpowers/plans/2026-10-24-issue-238-windows-process-identity-token.md +0 -490
  111. package/docs/superpowers/plans/2026-10-24-issue-241-coordinator-root-exit.md +0 -461
  112. package/docs/superpowers/plans/2026-10-25-issue-239-coordinator-replacement-identity.md +0 -504
  113. package/docs/superpowers/plans/2026-10-26-issue-245-key-hints-doc-sync.md +0 -244
  114. package/docs/superpowers/plans/2026-10-27-issue-172-fullscreen-selection-clear.md +0 -344
  115. package/docs/superpowers/plans/2026-10-27-issue-252-select-key-hints.md +0 -209
  116. package/docs/superpowers/plans/2026-10-28-issue-255-coordinator-endpoint-zombie.md +0 -442
  117. package/docs/superpowers/plans/2026-10-28-issue-257-legacy-pty-runner-endpoint-cleanup.md +0 -256
  118. package/docs/superpowers/plans/2026-11-03-cwd-favorites-full-pool.md +0 -266
  119. package/docs/superpowers/specs/2026-08-15-screenlog-gc-design.md +0 -105
  120. package/docs/superpowers/specs/2026-08-16-attach-double-cursor-jiggle-retry-design.md +0 -142
  121. package/docs/superpowers/specs/2026-08-21-attach-coldstart-jiggle-rearm-design.md +0 -110
  122. package/docs/superpowers/specs/2026-08-21-dashboard-keypress-lag-design.md +0 -59
  123. package/docs/superpowers/specs/2026-08-21-issue-14-needs-input-no-auto-done-design.md +0 -109
  124. package/docs/superpowers/specs/2026-08-22-cwd-favorites-design.md +0 -131
  125. package/docs/superpowers/specs/2026-08-22-jiggle-shrink-and-hold-design.md +0 -111
  126. package/docs/superpowers/specs/2026-08-22-question-tool-grouping-design.md +0 -114
  127. package/docs/superpowers/specs/2026-08-22-rename-state-labels-design.md +0 -80
  128. package/docs/superpowers/specs/2026-08-27-locks-acquirelock-spin-design.md +0 -97
  129. package/docs/superpowers/specs/2026-08-27-pty-runner-test-flaky-design.md +0 -35
  130. package/docs/superpowers/specs/2026-08-29-code-refs-badges-design.md +0 -128
  131. package/docs/superpowers/specs/2026-08-30-circular-navigation-design.md +0 -47
  132. package/docs/superpowers/specs/2026-08-30-eprm-atomicwrite-race-design.md +0 -77
  133. package/docs/superpowers/specs/2026-08-30-post-exit-timing-fix-design.md +0 -80
  134. package/docs/superpowers/specs/2026-08-30-pty-attach-quality-debt-design.md +0 -105
  135. package/docs/superpowers/specs/2026-08-30-readme-v2-design.md +0 -115
  136. package/docs/superpowers/specs/2026-09-01-attach-detach-gate-cursor-anchor-design.md +0 -78
  137. package/docs/superpowers/specs/2026-09-02-attach-detach-editor-state-design.md +0 -120
  138. package/docs/superpowers/specs/2026-09-03-code-refs-pr-backlink-narrow-design.md +0 -92
  139. package/docs/superpowers/specs/2026-09-03-detach-gate-glyph-fallback-design.md +0 -99
  140. package/docs/superpowers/specs/2026-09-04-evidence-outputpreview-design.md +0 -50
  141. package/docs/superpowers/specs/2026-09-04-warm-host-reclaim-design.md +0 -106
  142. package/docs/superpowers/specs/2026-09-05-issue-13-drainnextfollowup-pty-probe-design.md +0 -64
  143. package/docs/superpowers/specs/2026-09-05-issue-38-windows-wezterm-ime-design.md +0 -48
  144. package/docs/superpowers/specs/2026-09-05-issue-39-truncate-codepoint-boundary-design.md +0 -64
  145. package/docs/superpowers/specs/2026-09-05-issue-61-mention-fallback-design.md +0 -71
  146. package/docs/superpowers/specs/2026-09-05-issue-63-flaky-manual-completion-design.md +0 -49
  147. package/docs/superpowers/specs/2026-09-05-issue-64-changelog-helper-design.md +0 -76
  148. package/docs/superpowers/specs/2026-09-05-pty-host-stacking-sock-race-design.md +0 -510
  149. package/docs/superpowers/specs/2026-09-07-issue-11-attach-runtime-desync-heal-design.md +0 -130
  150. package/docs/superpowers/specs/2026-09-08-attach-ctrl-left-detach-design.md +0 -58
  151. package/docs/superpowers/specs/2026-09-08-dashboard-shrink-repaint-design.md +0 -52
  152. package/docs/superpowers/specs/2026-09-08-legacy-stale-host-recovery-design.md +0 -87
  153. package/docs/superpowers/specs/2026-09-08-spawn-async-error-swallow-design.md +0 -56
  154. package/docs/superpowers/specs/2026-09-08-stale-model-attach-guard-design.md +0 -79
  155. package/docs/superpowers/specs/2026-09-09-claimpid-blocks-replace-design.md +0 -147
  156. package/docs/superpowers/specs/2026-09-09-harden-runner-architecture-design.md +0 -304
  157. package/docs/superpowers/specs/2026-09-14-attach-cursor-dectcem-gate-design.md +0 -114
  158. package/docs/superpowers/specs/2026-09-14-host-meta-orphan-lock-design.md +0 -120
  159. package/docs/superpowers/specs/2026-09-14-issue-106-terminal-frame-cognition-design.md +0 -146
  160. package/docs/superpowers/specs/2026-09-14-issue-113-foreground-preview-race-design.md +0 -116
  161. package/docs/superpowers/specs/2026-09-15-coordinator-pipe-root-normalize-design.md +0 -84
  162. package/docs/superpowers/specs/2026-09-15-lease-publish-eprem-reclaim-design.md +0 -92
  163. package/docs/superpowers/specs/2026-09-18-detach-anchor-reporter-endpoint-design.md +0 -123
  164. package/docs/superpowers/specs/2026-09-20-issue-121-perf-gate-out-of-coverage-design.md +0 -204
  165. package/docs/superpowers/specs/2026-09-23-a5-mid-stream-assertion-design.md +0 -43
  166. package/docs/superpowers/specs/2026-09-23-cwd-browse-tab-drill-design.md +0 -95
  167. package/docs/superpowers/specs/2026-09-23-terminal-query-forwarding-design.md +0 -63
  168. package/docs/superpowers/specs/2026-09-23-unconditional-detach-key-design.md +0 -57
  169. package/docs/superpowers/specs/2026-09-24-control-reconcile-flake-design.md +0 -98
  170. package/docs/superpowers/specs/2026-09-24-probe-attached-clients-design.md +0 -190
  171. package/docs/superpowers/specs/2026-09-29-issue-145-holding-semantic-state-design.md +0 -394
  172. package/docs/superpowers/specs/2026-09-30-board-delete-key-x-y-confirm-design.md +0 -86
  173. package/docs/superpowers/specs/2026-09-30-issue-153-runner-stop-boot-window-design.md +0 -75
  174. package/docs/superpowers/specs/2026-09-30-issue-95-test-discipline-design.md +0 -226
  175. package/docs/superpowers/specs/2026-09-30-publish-dispatch-tag-check-design.md +0 -129
  176. package/docs/superpowers/specs/2026-10-06-attach-settle-scheme-delivery-design.md +0 -196
  177. package/docs/superpowers/specs/2026-10-09-issue-167-attach-middle-paste-design.md +0 -125
  178. package/docs/superpowers/specs/2026-10-09-issue-169-fullscreen-mouse-ownership-design.md +0 -130
  179. package/docs/superpowers/specs/2026-10-10-attach-delete-in-place-design.md +0 -224
  180. package/docs/superpowers/specs/2026-10-10-issue-174-selection-mirror-paste-source-design.md +0 -154
  181. package/docs/superpowers/specs/2026-10-14-issue-170-attach-reply-guard-design.md +0 -195
  182. package/docs/superpowers/specs/2026-10-14-issue-178-accept-registration-race-design.md +0 -287
  183. package/docs/superpowers/specs/2026-10-14-issue-184-snapshot-window-contract-design.md +0 -288
  184. package/docs/superpowers/specs/2026-10-14-issue-185-attach-wheel-alt-screen-design.md +0 -126
  185. package/docs/superpowers/specs/2026-10-14-issue-187-motionless-drag-design.md +0 -269
  186. package/docs/superpowers/specs/2026-10-14-issue-189-mixed-input-segments-design.md +0 -369
  187. package/docs/superpowers/specs/2026-10-15-issue-188-x10-mouse-release-design.md +0 -252
  188. package/docs/superpowers/specs/2026-10-16-issue-198-attach-wheel-forward-fallback-design.md +0 -67
  189. package/docs/superpowers/specs/2026-10-18-attach-wheel-replay-pseudo-scrollback-design.md +0 -250
  190. package/docs/superpowers/specs/2026-10-19-issue-213-reply-guard-csi-key-design.md +0 -165
  191. package/docs/superpowers/specs/2026-10-19-issue-214-attach-forward-alt-modifier-design.md +0 -131
  192. package/docs/superpowers/specs/2026-10-20-issue-221-ask-user-question-design.md +0 -270
  193. package/docs/superpowers/specs/2026-10-22-issue-225-monotonic-foreground-state-design.md +0 -225
  194. package/docs/superpowers/specs/2026-10-23-issue-222-attach-ready-design.md +0 -204
  195. package/docs/superpowers/specs/2026-10-23-issue-229-vim-jkl-design.md +0 -90
  196. package/docs/superpowers/specs/2026-10-23-issue-232-attach-bg-only-cells-design.md +0 -146
  197. package/docs/superpowers/specs/2026-10-23-lease-heartbeat-staleness-design.md +0 -271
  198. package/docs/superpowers/specs/2026-10-23-selected-row-highlight-design.md +0 -191
  199. package/docs/superpowers/specs/2026-10-24-issue-217-windows-pid-reuse-stopping-design.md +0 -334
  200. package/docs/superpowers/specs/2026-10-24-issue-230-warm-host-background-runs-design.md +0 -283
  201. package/docs/superpowers/specs/2026-10-24-issue-238-windows-process-identity-token-design.md +0 -286
  202. package/docs/superpowers/specs/2026-10-24-issue-241-coordinator-root-exit-design.md +0 -381
  203. package/docs/superpowers/specs/2026-10-25-issue-239-coordinator-replacement-identity-design.md +0 -331
  204. package/docs/superpowers/specs/2026-10-26-issue-245-key-hints-doc-sync-design.md +0 -72
  205. package/docs/superpowers/specs/2026-10-27-issue-172-fullscreen-selection-clear-design.md +0 -248
  206. package/docs/superpowers/specs/2026-10-27-issue-252-select-key-hints-design.md +0 -95
  207. package/docs/superpowers/specs/2026-10-28-issue-255-coordinator-endpoint-zombie-design.md +0 -380
  208. package/docs/superpowers/specs/2026-10-28-issue-257-legacy-pty-runner-endpoint-cleanup-design.md +0 -197
  209. package/docs/superpowers/specs/2026-11-03-cwd-favorites-full-pool-design.md +0 -106
  210. package/scripts/budget-audit.mjs +0 -135
  211. package/scripts/patch-vulns.mjs +0 -59
  212. package/scripts/release.mjs +0 -446
  213. package/scripts/release_helper.mjs +0 -309
  214. package/scripts/run-perf-gate.mjs +0 -40
  215. package/scripts/run-tests.mjs +0 -375
@@ -1,367 +0,0 @@
1
- # Issue #217 — Precise Host Identity Corroboration 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:** On platforms where `captureStartToken()` is `null` (Windows, macOS), refine an `unknown` host-role observation with deterministic evidence — role-specific command-line match, falling back to process creation time vs `*SpawnedAt` — so a reused pid no longer strands a `stopping` host, and `recovery_pending` is never silent.
6
-
7
- **Architecture:** The decision is a pure function in `src/core/host-coordination.mjs`. A new I/O shell `src/core/process-identity-probe.mjs` runs one async `execFile` per pid (Windows CIM JSON, macOS `ps`) behind injectable seams. `service.mjs` keeps all wiring: `observeHostRole` becomes async, receives `{ sessionFile, memo }`, and only probes when the fast token path returns `unknown`. Diagnostics use a new `appendDiagnosticOnce`.
8
-
9
- **Tech Stack:** Node ESM `.mjs`, `node:test` + `assert/strict`, `node:child_process.execFile` (async, `windowsHide: true`), PowerShell `Get-CimInstance Win32_Process`, POSIX `ps -p <pid> -o etime=,command=`.
10
-
11
- **Spec:** `docs/superpowers/specs/2026-10-24-issue-217-windows-pid-reuse-stopping-design.md`
12
-
13
- **Worktree:** `C:/Users/27499/proj/opensource/pi-agent-board/.pi/worktrees/issue-217-windows-pid-reuse-stopping` — every path below is relative to it; never edit the main checkout.
14
-
15
- ## Global Constraints
16
-
17
- - Tabs for indentation; English comments; conventional commit subjects (`(#217)` lands on the PR, not on local commits).
18
- - `host-coordination.mjs` stays pure: no fs/net/process I/O, no `resolveTestMs` import.
19
- - `probe == null` MUST return `unknown` — never `foreign`. This is the #70 contract.
20
- - Role-specific fragment: child → `meta.sessionFile`, runner → host `configPath`. Never use `sessionFile` for the runner.
21
- - Do not change `processIdentityState`, `captureStartToken` (all copies), lock identity, or `serviceIdentity()`.
22
- - `recoverHost(viewId, expectedInstanceId, sessionFile?)` — third parameter optional so existing two-arg calls keep working.
23
- - Probe memo is created by the caller and shared across the loop and the final re-check; the resolver stopping branch creates its own local memo.
24
- - Every spawn/exec uses `windowsHide: true` and a `timeout`.
25
- - `git add <explicit files>` only.
26
- - Gate: `npm run typecheck` + the listed test files; `npm run verify` if the local node-pty environment allows it (known Windows `AttachConsole failed` limitation).
27
-
28
- ## Review Focus
29
-
30
- 1. **Process dies between `isAlive` and the probe** — `probe == null` must stay `unknown` for that round; the next poll's `defaultObserveProcess` classifies `dead`. → A8 (Task 3).
31
- 2. **Path prefix trap** (`session.jsonl` inside `session-2.jsonl`, `view_abc` inside `view_abcd`) — matching must be boundary-aware, not a raw `includes`. → A1 (Task 1).
32
- 3. **Mixed slash/case/quoting in command lines** — Windows paths arrive as `C:\…` inside `"…"`; comparison normalizes slashes and case. → A1 (Task 1).
33
- 4. **Command line unreadable (permission / system process)** — fall back to the creation-time window; a missing `spawnedAt` yields `unknown`. → A2 (Task 1).
34
- 5. **Clock step between spawn and recovery** — creation-time fallback uses a 5 s tolerance and only applies when the command-line signal is unavailable. → A2/A15 (Task 1).
35
-
36
- ---
37
-
38
- ### Task 1: Pure decision functions + tolerance knob
39
-
40
- **Files:**
41
- - Modify: `src/core/host-coordination.mjs` (append after `processIdentityState`)
42
- - Modify: `src/runtime/service.mjs` (add the constant + reader next to `resolveHostRecoveryPollMs` at :90)
43
- - Test: `test/host-coordination.test.mjs`, `test/test-knobs.test.mjs`
44
-
45
- **Interfaces:**
46
- - Produces:
47
- - `export function commandLineMentions(commandLine: string, fragment: string): boolean`
48
- - `export function classifyUnverifiableProcess({ spawnedAt, probe, expectedCommandFragment, toleranceMs }): "owned" | "foreign" | "unknown"`
49
- - `export const HOST_IDENTITY_CREATION_TOLERANCE_MS = 5_000` (in `service.mjs`)
50
- - `export function resolveIdentityCreationToleranceMs(env = process.env): number` (in `service.mjs`)
51
- - `probe` shape: `{ creationTimeMs: number|null, commandLine: string|null } | null`.
52
-
53
- - [ ] **Step 1: Write the failing truth-table tests**
54
-
55
- In `test/host-coordination.test.mjs`, add:
56
-
57
- ```js
58
- test("commandLineMentions normalizes slashes/case and respects path boundaries", () => {
59
- assert.equal(commandLineMentions('node C:\\x\\cli.js --session "C:\\a b\\s.jsonl"', "C:/a b/s.jsonl"), true);
60
- assert.equal(commandLineMentions("NODE --session c:\\A\\S.JSONL", "C:\\a\\s.jsonl"), true);
61
- // prefix trap: view_abc must not match view_abcd
62
- assert.equal(commandLineMentions("node x --session C:\\v\\view_abcd.jsonl", "C:\\v\\view_abc.jsonl"), false);
63
- assert.equal(commandLineMentions("node x --session C:\\v\\session-2.jsonl", "C:\\v\\session.jsonl"), false);
64
- assert.equal(commandLineMentions("", "C:\\v\\s.jsonl"), false);
65
- });
66
-
67
- test("classifyUnverifiableProcess truth table", () => {
68
- const probe = (creationTimeMs, commandLine) => ({ creationTimeMs, commandLine });
69
- const base = { spawnedAt: 1_000_000, expectedCommandFragment: "C:\\v\\s.jsonl", toleranceMs: 5_000 };
70
- assert.equal(classifyUnverifiableProcess({ ...base, probe: null }), "unknown");
71
- assert.equal(classifyUnverifiableProcess({ ...base, probe: probe(9_999_999, "node --session C:\\v\\s.jsonl") }), "owned");
72
- assert.equal(classifyUnverifiableProcess({ ...base, probe: probe(9_999_999, "node other.js") }), "foreign");
73
- assert.equal(classifyUnverifiableProcess({ ...base, probe: probe(1_002_000, null) }), "owned");
74
- assert.equal(classifyUnverifiableProcess({ ...base, probe: probe(900_000, null) }), "foreign");
75
- assert.equal(classifyUnverifiableProcess({ ...base, probe: probe(1_020_000, null) }), "foreign");
76
- // command line wins over a null spawnedAt
77
- assert.equal(classifyUnverifiableProcess({ ...base, spawnedAt: null, probe: probe(5, "node --session C:\\v\\s.jsonl") }), "owned");
78
- // no signal at all
79
- assert.equal(classifyUnverifiableProcess({ ...base, spawnedAt: null, probe: probe(1_000_000, null) }), "unknown");
80
- assert.equal(classifyUnverifiableProcess({ ...base, expectedCommandFragment: null, probe: probe(1_000_000, "node x") }), "owned");
81
- });
82
- ```
83
-
84
- In `test/test-knobs.test.mjs`, extend both reader tests:
85
-
86
- ```js
87
- assert.equal(HOST_IDENTITY_CREATION_TOLERANCE_MS, 5_000);
88
- assert.equal(resolveIdentityCreationToleranceMs({}), 5_000);
89
- assert.equal(resolveIdentityCreationToleranceMs({ AGENT_BOARD_TEST_IDENTITY_CREATION_TOLERANCE_MS: "25" }), 25);
90
- ```
91
-
92
- - [ ] **Step 2: Run to verify they fail**
93
-
94
- Run: `node --test test/host-coordination.test.mjs test/test-knobs.test.mjs`
95
- Expected: FAIL — `commandLineMentions is not a function` / import error for the new reader.
96
-
97
- - [ ] **Step 3: Implement the pure functions**
98
-
99
- `host-coordination.mjs`: `commandLineMentions` normalizes `\`→`/` and lowercases both sides, then scans every occurrence and accepts only when both neighbours are start/end or whitespace/quote (boundary-aware). `classifyUnverifiableProcess` follows the spec D1 pseudo-code exactly: `probe == null → unknown`; command line + fragment → `owned`/`foreign`; else creation window `|creation - spawnedAt| <= tolerance → owned`, otherwise `foreign`; missing `spawnedAt`/`creation` → `unknown`.
100
-
101
- `service.mjs`: add the constant and reader (uses `resolveTestMs`) beside the existing readers.
102
-
103
- - [ ] **Step 4: Run to verify they pass**
104
-
105
- Run: `node --test test/host-coordination.test.mjs test/test-knobs.test.mjs`
106
- Expected: PASS.
107
-
108
- - [ ] **Step 5: Commit**
109
-
110
- ```bash
111
- git add src/core/host-coordination.mjs src/runtime/service.mjs test/host-coordination.test.mjs test/test-knobs.test.mjs
112
- git commit -m "feat(core): add identity corroboration pure functions and tolerance knob (issue #217)"
113
- ```
114
-
115
- **Acceptance:** A1, A2, A15.
116
-
117
- ---
118
-
119
- ### Task 2: Process identity probe module
120
-
121
- **Files:**
122
- - Create: `src/core/process-identity-probe.mjs`
123
- - Create: `test/process-identity-probe.test.mjs`
124
-
125
- **Interfaces:**
126
- - Produces:
127
- - `export const PROBE_TIMEOUT_MS = 3_000`
128
- - `export function parseUnixMillis(text: string): number|null`
129
- - `export function parseProbeJson(text: string): {creationTimeMs:number|null, commandLine:string|null}|null`
130
- - `export function parsePsElapsedMs(text: string): number|null`
131
- - `export function parsePsProbeLine(text: string, nowMs: number): {creationTimeMs:number|null, commandLine:string|null}|null`
132
- - `export function resolveProbeCommand(platform: string, pid: number): {command:string, args:string[]}|null`
133
- - `export async function readProcessIdentityProbe(pid, { platform = process.platform, execFileFn = execFile, timeoutMs = PROBE_TIMEOUT_MS, nowMs = Date.now } = {}): Promise<{creationTimeMs:number|null, commandLine:string|null}|null>`
134
- - Consumes nothing from Task 1.
135
-
136
- - [ ] **Step 1: Write the failing parser/command tests**
137
-
138
- In `test/process-identity-probe.test.mjs`:
139
-
140
- ```js
141
- test("parseUnixMillis accepts positive integers only", () => {
142
- assert.equal(parseUnixMillis("1792718500139"), 1_792_718_500_139);
143
- assert.equal(parseUnixMillis(""), null);
144
- assert.equal(parseUnixMillis("abc"), null);
145
- assert.equal(parseUnixMillis("-5"), null);
146
- });
147
-
148
- test("parseProbeJson parses the CIM object and rejects junk", () => {
149
- assert.deepEqual(parseProbeJson('{"creation":1792718500139,"cmd":"node x"}'),
150
- { creationTimeMs: 1_792_718_500_139, commandLine: "node x" });
151
- assert.deepEqual(parseProbeJson('{"creation":null,"cmd":null}'), { creationTimeMs: null, commandLine: null });
152
- assert.equal(parseProbeJson("not json"), null);
153
- });
154
-
155
- test("parsePsElapsedMs handles every etime shape", () => {
156
- assert.equal(parsePsElapsedMs("05:03"), 303_000);
157
- assert.equal(parsePsElapsedMs("01:02:03"), 3_723_000);
158
- assert.equal(parsePsElapsedMs("2-01:02:03"), 176_523_000);
159
- assert.equal(parsePsElapsedMs("nope"), null);
160
- });
161
-
162
- test("parsePsProbeLine splits elapsed from the command", () => {
163
- assert.deepEqual(parsePsProbeLine("05:03 node /x --session y z", 1_000_000),
164
- { creationTimeMs: 697_000, commandLine: "node /x --session y z" });
165
- });
166
-
167
- test("resolveProbeCommand is platform- and pid-aware", () => {
168
- assert.match(resolveProbeCommand("win32", 123).args.at(-1), /ProcessId=123/);
169
- assert.deepEqual(resolveProbeCommand("darwin", 123), { command: "ps", args: ["-p", "123", "-o", "etime=,command="] });
170
- assert.equal(resolveProbeCommand("linux", 123), null);
171
- assert.equal(resolveProbeCommand("win32", 0), null);
172
- assert.equal(resolveProbeCommand("win32", 1.5), null);
173
- });
174
- ```
175
-
176
- - [ ] **Step 2: Run to verify they fail**
177
-
178
- Run: `node --test test/process-identity-probe.test.mjs`
179
- Expected: FAIL — module not found.
180
-
181
- - [ ] **Step 3: Implement the parsers and command resolver**
182
-
183
- Windows command: `powershell.exe -NoProfile -NonInteractive -Command "$p = Get-CimInstance Win32_Process -Filter \"ProcessId=<pid>\" -ErrorAction SilentlyContinue; if ($p) { [pscustomobject]@{creation=[long]([datetimeoffset]$p.CreationDate).ToUnixTimeMilliseconds(); cmd=$p.CommandLine} | ConvertTo-Json -Compress }"`. macOS: `ps -p <pid> -o etime=,command=`. Other platforms: `null`.
184
-
185
- - [ ] **Step 4: Write the failing execution-layer tests**
186
-
187
- ```js
188
- test("readProcessIdentityProbe parses stdout and honors exec options", async () => {
189
- const calls = [];
190
- const execFileFn = (cmd, args, opts, cb) => {
191
- calls.push({ cmd, args, opts });
192
- cb(null, '{"creation":1792718500139,"cmd":"node x --session y"}\n', "");
193
- };
194
- const probe = await readProcessIdentityProbe(123, { platform: "win32", execFileFn });
195
- assert.deepEqual(probe, { creationTimeMs: 1_792_718_500_139, commandLine: "node x --session y" });
196
- assert.equal(calls[0].opts.windowsHide, true);
197
- assert.equal(calls[0].opts.timeout, 3_000);
198
- });
199
-
200
- test("readProcessIdentityProbe returns null on failure/empty output", async () => { /* non-zero exit, timeout, empty stdout → null */ });
201
- ```
202
-
203
- - [ ] **Step 5: Run to verify they fail, then implement the I/O shell**
204
-
205
- Run: `node --test test/process-identity-probe.test.mjs` → FAIL.
206
- Implement `readProcessIdentityProbe` with `execFile(command, args, { timeout, windowsHide: true, maxBuffer: 64 * 1024 }, cb)` wrapped in a promise; non-zero exit / error / empty trimmed stdout → `null`.
207
-
208
- - [ ] **Step 6: Run to verify they pass + commit**
209
-
210
- Run: `node --test test/process-identity-probe.test.mjs` → PASS.
211
-
212
- ```bash
213
- git add src/core/process-identity-probe.mjs test/process-identity-probe.test.mjs
214
- git commit -m "feat(core): add cross-platform process identity probe (issue #217)"
215
- ```
216
-
217
- **Acceptance:** A3, A4, A5.
218
-
219
- ---
220
-
221
- ### Task 3: Wire the probe into recovery
222
-
223
- **Files:**
224
- - Modify: `src/runtime/service.mjs` (`createService` opts at :100-160; `observeHostRole` :961; `recoverHost` :982-1050; resolver stopping branch :1304-1318; final re-check :1046)
225
- - Test: `test/host-recovery.test.mjs`, `test/host-resolver.test.mjs`
226
-
227
- **Interfaces:**
228
- - Consumes: `classifyUnverifiableProcess`, `resolveIdentityCreationToleranceMs` (Task 1); `readProcessIdentityProbe` (Task 2).
229
- - Produces: `createService({ probeProcessIdentity })` injection point; `observeHostRole(host, role, { sessionFile, memo })` async.
230
-
231
- - [ ] **Step 1: Update existing unknown→pending tests to inject a null probe**
232
-
233
- In `test/host-recovery.test.mjs:179` and `test/host-resolver.test.mjs:350`, add `probeProcessIdentity: async () => null` to the `service(...)` overrides. Run both files; the existing assertions must still pass (they will fail after Step 3 without this).
234
-
235
- - [ ] **Step 2: Write the failing recovery tests**
236
-
237
- Add to `test/host-recovery.test.mjs`:
238
-
239
- ```js
240
- test("recoverHost releases an unverifiable reused pid when the command line is foreign", async () => {
241
- // host record: stopping, runnerIdentity/childIdentity startToken null, runnerSpawnedAt/childSpawnedAt set
242
- // observeProcess: () => "unknown"; probeProcessIdentity: async () => ({ creationTimeMs: 9, commandLine: "node other.js" })
243
- // expect: res.ok === true, res.recovered === true, signals.length === 0, new instance claimed
244
- });
245
-
246
- test("recoverHost signals an unverifiable owned pid when the command line matches", async () => {
247
- // same record, probe commandLine includes the view's sessionFile
248
- // expect: exactly one SIGTERM to the owned identity, replacement claimed
249
- });
250
-
251
- test("recoverHost probes at most twice per call (shared memo)", async () => {
252
- // probeProcessIdentity counts calls; runner+child both unknown
253
- // expect probeCalls <= 2
254
- });
255
- ```
256
-
257
- Add to `test/host-resolver.test.mjs`:
258
-
259
- ```js
260
- test("resolver advances a stranded stopping host when the child command line is foreign (issue #217)", async () => {
261
- // stopping host, staleStop true, observeProcess unknown, probe foreign, probeHost scripted ["missing","ready"]
262
- // expect result.kind === "pty" (no recovery_pending)
263
- });
264
- ```
265
-
266
- - [ ] **Step 3: Run to verify they fail**
267
-
268
- Run: `node --test test/host-recovery.test.mjs test/host-resolver.test.mjs`
269
- Expected: FAIL — the foreign cases still return `recovery_pending`; memo count unbounded.
270
-
271
- - [ ] **Step 4: Implement the wiring**
272
-
273
- Follow spec D3 exactly:
274
- - `const probeProcessIdentityImpl = opts.probeProcessIdentity ?? readProcessIdentityProbe;`
275
- - `async function observeHostRole(host, role, { sessionFile, memo })` — fast path unchanged; on `unknown` with a pid, `memo.set(pid, await probeProcessIdentityImpl(pid))` once, then `classifyUnverifiableProcess({ spawnedAt, probe, expectedCommandFragment: role === "child" ? sessionFile : host?.configPath ?? null, toleranceMs: resolveIdentityCreationToleranceMs() })`.
276
- - `recoverHost(viewId, expectedInstanceId, sessionFile = loadRow(root, viewId)?.meta.sessionFile ?? null)` creates `const probeMemo = new Map()` and awaits every `observeHostRole(..., { sessionFile, memo: probeMemo })` (loop + final re-check).
277
- - Resolver stopping branch creates its own local memo and passes its existing `sessionFile`; the resolver's `recoverHost` calls also pass `sessionFile`.
278
-
279
- - [ ] **Step 5: Run to verify they pass**
280
-
281
- Run: `node --test test/host-recovery.test.mjs test/host-resolver.test.mjs`
282
- Expected: PASS (including the pre-existing unknown→pending assertions).
283
-
284
- - [ ] **Step 6: Commit**
285
-
286
- ```bash
287
- git add src/runtime/service.mjs test/host-recovery.test.mjs test/host-resolver.test.mjs
288
- git commit -m "fix(host): corroborate unverifiable process identity before stranding recovery (issue #217)"
289
- ```
290
-
291
- **Acceptance:** A6, A7, A8, A9, A11, A13.
292
-
293
- ---
294
-
295
- ### Task 4: Recovery-pending diagnostics
296
-
297
- **Files:**
298
- - Modify: `src/core/diagnostics.mjs` (add `appendDiagnosticOnce`)
299
- - Modify: `src/runtime/service.mjs` (`:1018`, `:1047`, `:1308`)
300
- - Test: `test/diagnostics.test.mjs`, `test/host-recovery.test.mjs`
301
-
302
- **Interfaces:**
303
- - Produces: `export function appendDiagnosticOnce(root, viewId, patch, opts = {}): DiagnosticEvent|null` — `opts.match` (array of `details` keys, default `[]`), `opts.limit` (default 20); returns `null` when a matching recent event exists.
304
-
305
- - [ ] **Step 1: Write the failing unit test**
306
-
307
- ```js
308
- test("appendDiagnosticOnce writes once per match key", () => {
309
- const root = mkdtempSync(join(tmpdir(), "agentview-diag-"));
310
- createView(root, { id: "v1", name: "a", cwd: "/r" });
311
- const patch = { source: "service", level: "warn", code: "host_recovery_pending", details: { instanceId: "i1", role: "child" } };
312
- assert.ok(appendDiagnosticOnce(root, "v1", patch, { match: ["instanceId", "role"] }));
313
- assert.equal(appendDiagnosticOnce(root, "v1", patch, { match: ["instanceId", "role"] }), null);
314
- assert.ok(appendDiagnosticOnce(root, "v1", { ...patch, details: { instanceId: "i2", role: "child" } }, { match: ["instanceId", "role"] }));
315
- });
316
- ```
317
-
318
- - [ ] **Step 2: Run to verify it fails**
319
-
320
- Run: `node --test test/diagnostics.test.mjs` → FAIL (no export).
321
-
322
- - [ ] **Step 3: Implement and wire**
323
-
324
- `appendDiagnosticOnce` reads the tail (default 20) via `readJsonlTail` + `normalizeDiagnostic`, returns `null` on a same-code same-key match, otherwise appends. In `service.mjs`, before the three producing returns, call it with `code: "host_recovery_pending"`, `details: { instanceId, role, observation, probe: probe ? "available" : "unavailable", path: "recover" }` (or `"attach"` at :1308), `{ match: ["instanceId", "role"] }`. Do not add it to the two relay returns (:1315, :1383).
325
-
326
- - [ ] **Step 4: Write the failing integration assertion**
327
-
328
- In `test/host-recovery.test.mjs`, extend the unknown→pending test: assert `readDiagnostics(root, "v1").some((e) => e.code === "host_recovery_pending")`, then call `recoverHost` again and assert the count did not grow.
329
-
330
- - [ ] **Step 5: Run to verify they pass + commit**
331
-
332
- Run: `node --test test/diagnostics.test.mjs test/host-recovery.test.mjs` → PASS.
333
-
334
- ```bash
335
- git add src/core/diagnostics.mjs src/runtime/service.mjs test/diagnostics.test.mjs test/host-recovery.test.mjs
336
- git commit -m "feat(diagnostics): record host recovery pending with dedupe (issue #217)"
337
- ```
338
-
339
- **Acceptance:** A10.
340
-
341
- ---
342
-
343
- ### Task 5: Verification and manual-verification record
344
-
345
- **Files:** none (verification only)
346
-
347
- - [ ] **Step 1: Typecheck**
348
-
349
- Run: `npm run typecheck` → 0 errors.
350
-
351
- - [ ] **Step 2: Run the targeted suites**
352
-
353
- Run: `node --test test/host-recovery.test.mjs test/host-resolver.test.mjs test/host-coordination.test.mjs test/process-identity-probe.test.mjs test/diagnostics.test.mjs test/test-knobs.test.mjs` → all pass.
354
-
355
- - [ ] **Step 3: Attempt the full gate and record the environment verdict**
356
-
357
- Run: `npm run verify`. If it fails only on the known Windows `AttachConsole failed` / node-pty paths (JFox `202609051440498495`), record the failing files and the reason in the PR body; otherwise fix.
358
-
359
- - [ ] **Step 4: Record U1/U2 status honestly**
360
-
361
- Write the U1/U2 results (or `pending` with the exact blocker) into the PR body. Do not claim manual verification without observed evidence.
362
-
363
- - [ ] **Step 5: Commit any verification-only documentation, if needed**
364
-
365
- `git add <explicit files> && git commit -m "test: record issue #217 verification results"` only if a file changed.
366
-
367
- **Acceptance:** A12, A14, U1, U2.