@huseynsnmz/pi-board 0.0.0-stage → 0.14.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 (297) hide show
  1. package/CHANGELOG.md +256 -0
  2. package/IMPLEMENTATION_PLAN.md +920 -0
  3. package/LICENSE +21 -0
  4. package/PRD.md +484 -0
  5. package/PROGRESS.md +142 -0
  6. package/README.md +439 -2
  7. package/VERIFY.md +115 -0
  8. package/docs/BATCH_SELECTION_READ_FLOW.md +277 -0
  9. package/docs/EXPLORATION.md +187 -0
  10. package/docs/PTY_ATTACH_IMPLEMENTATION_PLAN.md +581 -0
  11. package/docs/RELEASE.md +148 -0
  12. package/docs/superpowers/plans/2026-08-15-screenlog-gc.md +704 -0
  13. package/docs/superpowers/plans/2026-08-16-attach-double-cursor-jiggle-retry.md +499 -0
  14. package/docs/superpowers/plans/2026-08-21-attach-coldstart-jiggle-rearm.md +797 -0
  15. package/docs/superpowers/plans/2026-08-21-dashboard-keypress-lag.md +366 -0
  16. package/docs/superpowers/plans/2026-08-21-issue-14-needs-input-no-auto-done.md +576 -0
  17. package/docs/superpowers/plans/2026-08-22-jiggle-shrink-and-hold.md +337 -0
  18. package/docs/superpowers/plans/2026-08-22-launch-cwd-favorites.md +916 -0
  19. package/docs/superpowers/plans/2026-08-22-question-tool-grouping.md +211 -0
  20. package/docs/superpowers/plans/2026-08-22-rename-state-labels.md +182 -0
  21. package/docs/superpowers/plans/2026-08-22-stable-list-order.md +51 -0
  22. package/docs/superpowers/plans/2026-08-27-locks-acquirelock-spin.md +614 -0
  23. package/docs/superpowers/plans/2026-08-27-pty-runner-test-flaky.md +32 -0
  24. package/docs/superpowers/plans/2026-08-29-code-refs-badges.md +223 -0
  25. package/docs/superpowers/plans/2026-08-30-circular-navigation.md +308 -0
  26. package/docs/superpowers/plans/2026-08-30-post-exit-timing-fix.md +37 -0
  27. package/docs/superpowers/plans/2026-08-30-pty-attach-quality-debt.md +311 -0
  28. package/docs/superpowers/plans/2026-08-30-readme-v2.md +294 -0
  29. package/docs/superpowers/plans/2026-09-01-attach-detach-gate-cursor-anchor.md +284 -0
  30. package/docs/superpowers/plans/2026-09-02-attach-detach-editor-state.md +722 -0
  31. package/docs/superpowers/plans/2026-09-03-code-refs-pr-backlink-narrow.md +551 -0
  32. package/docs/superpowers/plans/2026-09-03-detach-gate-glyph-fallback.md +146 -0
  33. package/docs/superpowers/plans/2026-09-04-evidence-outputpreview.md +209 -0
  34. package/docs/superpowers/plans/2026-09-04-warm-host-reclaim.md +796 -0
  35. package/docs/superpowers/plans/2026-09-05-issue-13-drainnextfollowup-pty-probe.md +114 -0
  36. package/docs/superpowers/plans/2026-09-05-issue-38-windows-wezterm-ime-cursor.md +73 -0
  37. package/docs/superpowers/plans/2026-09-05-issue-39-truncate-codepoint-boundary.md +143 -0
  38. package/docs/superpowers/plans/2026-09-05-issue-61-mention-fallback-guards.md +226 -0
  39. package/docs/superpowers/plans/2026-09-05-issue-63-flaky-manual-completion.md +87 -0
  40. package/docs/superpowers/plans/2026-09-05-issue-64-changelog-release-helper.md +53 -0
  41. package/docs/superpowers/plans/2026-09-05-pty-host-stacking-sock-race.md +731 -0
  42. package/docs/superpowers/plans/2026-09-08-attach-ctrl-left-detach.md +30 -0
  43. package/docs/superpowers/plans/2026-09-08-dashboard-shrink-repaint.md +68 -0
  44. package/docs/superpowers/plans/2026-09-08-issue-11-attach-runtime-desync-heal.md +917 -0
  45. package/docs/superpowers/plans/2026-09-08-legacy-stale-host-recovery.md +125 -0
  46. package/docs/superpowers/plans/2026-09-08-spawn-async-error-swallow.md +56 -0
  47. package/docs/superpowers/plans/2026-09-08-stale-model-attach-guard.md +96 -0
  48. package/docs/superpowers/plans/2026-09-09-claimpid-blocks-replace.md +267 -0
  49. package/docs/superpowers/plans/2026-09-09-harden-runner-architecture.md +603 -0
  50. package/docs/superpowers/plans/2026-09-09-single-writer-completion.md +252 -0
  51. package/docs/superpowers/plans/2026-09-10-reader-consistency.md +115 -0
  52. package/docs/superpowers/plans/2026-09-14-attach-cursor-dectcem-gate.md +469 -0
  53. package/docs/superpowers/plans/2026-09-14-attach-snapshot.md +92 -0
  54. package/docs/superpowers/plans/2026-09-14-host-meta-orphan-lock.md +771 -0
  55. package/docs/superpowers/plans/2026-09-14-issue-106-terminal-frame-cognition.md +299 -0
  56. package/docs/superpowers/plans/2026-09-14-issue-113-foreground-preview-race.md +609 -0
  57. package/docs/superpowers/plans/2026-09-14-terminal-model.md +145 -0
  58. package/docs/superpowers/plans/2026-09-15-coordinator-pipe-root-normalize.md +224 -0
  59. package/docs/superpowers/plans/2026-09-15-lease-publish-eprem-reclaim.md +341 -0
  60. package/docs/superpowers/plans/2026-09-18-detach-anchor-reporter-endpoint.md +875 -0
  61. package/docs/superpowers/plans/2026-09-20-control-lifecycle.md +116 -0
  62. package/docs/superpowers/plans/2026-09-20-issue-121-perf-gate-out-of-coverage.md +517 -0
  63. package/docs/superpowers/plans/2026-09-23-a5-mid-stream-assertion.md +25 -0
  64. package/docs/superpowers/plans/2026-09-23-cwd-browse-tab-drill.md +393 -0
  65. package/docs/superpowers/plans/2026-09-23-terminal-query-forwarding.md +51 -0
  66. package/docs/superpowers/plans/2026-09-23-unconditional-detach-key.md +58 -0
  67. package/docs/superpowers/plans/2026-09-24-control-reconcile-flake.md +698 -0
  68. package/docs/superpowers/plans/2026-09-24-probe-attached-clients.md +605 -0
  69. package/docs/superpowers/plans/2026-09-29-issue-145-holding-semantic-state.md +1183 -0
  70. package/docs/superpowers/plans/2026-09-29-issue-145-user-verification.md +66 -0
  71. package/docs/superpowers/plans/2026-09-30-board-delete-key-x-y-confirm.md +649 -0
  72. package/docs/superpowers/plans/2026-09-30-issue-153-runner-stop-boot-window.md +618 -0
  73. package/docs/superpowers/plans/2026-09-30-issue-95-test-discipline.md +555 -0
  74. package/docs/superpowers/plans/2026-09-30-publish-dispatch-tag-check.md +259 -0
  75. package/docs/superpowers/plans/2026-10-06-attach-settle-scheme-delivery.md +751 -0
  76. package/docs/superpowers/plans/2026-10-09-issue-167-attach-middle-paste.md +509 -0
  77. package/docs/superpowers/plans/2026-10-09-issue-169-fullscreen-mouse-ownership.md +346 -0
  78. package/docs/superpowers/plans/2026-10-10-attach-delete-in-place.md +832 -0
  79. package/docs/superpowers/plans/2026-10-10-issue-174-selection-mirror-paste-source.md +510 -0
  80. package/docs/superpowers/plans/2026-10-14-issue-170-attach-reply-guard.md +771 -0
  81. package/docs/superpowers/plans/2026-10-14-issue-178-accept-registration-race.md +1257 -0
  82. package/docs/superpowers/plans/2026-10-14-issue-184-snapshot-window-contract.md +657 -0
  83. package/docs/superpowers/plans/2026-10-14-issue-185-attach-wheel-alt-screen.md +47 -0
  84. package/docs/superpowers/plans/2026-10-14-issue-187-motionless-drag.md +931 -0
  85. package/docs/superpowers/plans/2026-10-14-issue-189-mixed-input-segments.md +823 -0
  86. package/docs/superpowers/plans/2026-10-15-issue-188-x10-mouse-release.md +526 -0
  87. package/docs/superpowers/plans/2026-10-16-issue-198-attach-wheel-forward-fallback.md +32 -0
  88. package/docs/superpowers/plans/2026-10-18-attach-wheel-replay-pseudo-scrollback.md +437 -0
  89. package/docs/superpowers/plans/2026-10-19-issue-213-reply-guard-csi-key.md +257 -0
  90. package/docs/superpowers/plans/2026-10-19-issue-214-attach-forward-alt-modifier.md +202 -0
  91. package/docs/superpowers/plans/2026-10-21-issue-221-ask-user-question.md +198 -0
  92. package/docs/superpowers/plans/2026-10-23-issue-222-attach-ready.md +290 -0
  93. package/docs/superpowers/plans/2026-10-23-issue-225-monotonic-foreground-state.md +450 -0
  94. package/docs/superpowers/plans/2026-10-23-issue-229-vim-jkl.md +449 -0
  95. package/docs/superpowers/plans/2026-10-23-issue-232-attach-bg-only-cells.md +153 -0
  96. package/docs/superpowers/plans/2026-10-23-lease-heartbeat-staleness.md +537 -0
  97. package/docs/superpowers/plans/2026-10-23-selected-row-highlight.md +405 -0
  98. package/docs/superpowers/plans/2026-10-24-issue-217-windows-pid-reuse-stopping.md +367 -0
  99. package/docs/superpowers/plans/2026-10-24-issue-230-warm-host-background-runs.md +806 -0
  100. package/docs/superpowers/plans/2026-10-24-issue-238-windows-process-identity-token.md +490 -0
  101. package/docs/superpowers/plans/2026-10-24-issue-241-coordinator-root-exit.md +461 -0
  102. package/docs/superpowers/plans/2026-10-25-issue-239-coordinator-replacement-identity.md +504 -0
  103. package/docs/superpowers/plans/2026-10-26-issue-245-key-hints-doc-sync.md +244 -0
  104. package/docs/superpowers/plans/2026-10-27-issue-172-fullscreen-selection-clear.md +344 -0
  105. package/docs/superpowers/plans/2026-10-27-issue-252-select-key-hints.md +209 -0
  106. package/docs/superpowers/plans/2026-10-28-issue-255-coordinator-endpoint-zombie.md +442 -0
  107. package/docs/superpowers/plans/2026-10-28-issue-257-legacy-pty-runner-endpoint-cleanup.md +256 -0
  108. package/docs/superpowers/plans/2026-11-03-cwd-favorites-full-pool.md +266 -0
  109. package/docs/superpowers/specs/2026-08-15-screenlog-gc-design.md +105 -0
  110. package/docs/superpowers/specs/2026-08-16-attach-double-cursor-jiggle-retry-design.md +142 -0
  111. package/docs/superpowers/specs/2026-08-21-attach-coldstart-jiggle-rearm-design.md +110 -0
  112. package/docs/superpowers/specs/2026-08-21-dashboard-keypress-lag-design.md +59 -0
  113. package/docs/superpowers/specs/2026-08-21-issue-14-needs-input-no-auto-done-design.md +109 -0
  114. package/docs/superpowers/specs/2026-08-22-cwd-favorites-design.md +131 -0
  115. package/docs/superpowers/specs/2026-08-22-jiggle-shrink-and-hold-design.md +111 -0
  116. package/docs/superpowers/specs/2026-08-22-question-tool-grouping-design.md +114 -0
  117. package/docs/superpowers/specs/2026-08-22-rename-state-labels-design.md +80 -0
  118. package/docs/superpowers/specs/2026-08-27-locks-acquirelock-spin-design.md +97 -0
  119. package/docs/superpowers/specs/2026-08-27-pty-runner-test-flaky-design.md +35 -0
  120. package/docs/superpowers/specs/2026-08-29-code-refs-badges-design.md +128 -0
  121. package/docs/superpowers/specs/2026-08-30-circular-navigation-design.md +47 -0
  122. package/docs/superpowers/specs/2026-08-30-eprm-atomicwrite-race-design.md +77 -0
  123. package/docs/superpowers/specs/2026-08-30-post-exit-timing-fix-design.md +80 -0
  124. package/docs/superpowers/specs/2026-08-30-pty-attach-quality-debt-design.md +105 -0
  125. package/docs/superpowers/specs/2026-08-30-readme-v2-design.md +115 -0
  126. package/docs/superpowers/specs/2026-09-01-attach-detach-gate-cursor-anchor-design.md +78 -0
  127. package/docs/superpowers/specs/2026-09-02-attach-detach-editor-state-design.md +120 -0
  128. package/docs/superpowers/specs/2026-09-03-code-refs-pr-backlink-narrow-design.md +92 -0
  129. package/docs/superpowers/specs/2026-09-03-detach-gate-glyph-fallback-design.md +99 -0
  130. package/docs/superpowers/specs/2026-09-04-evidence-outputpreview-design.md +50 -0
  131. package/docs/superpowers/specs/2026-09-04-warm-host-reclaim-design.md +106 -0
  132. package/docs/superpowers/specs/2026-09-05-issue-13-drainnextfollowup-pty-probe-design.md +64 -0
  133. package/docs/superpowers/specs/2026-09-05-issue-38-windows-wezterm-ime-design.md +48 -0
  134. package/docs/superpowers/specs/2026-09-05-issue-39-truncate-codepoint-boundary-design.md +64 -0
  135. package/docs/superpowers/specs/2026-09-05-issue-61-mention-fallback-design.md +71 -0
  136. package/docs/superpowers/specs/2026-09-05-issue-63-flaky-manual-completion-design.md +49 -0
  137. package/docs/superpowers/specs/2026-09-05-issue-64-changelog-helper-design.md +76 -0
  138. package/docs/superpowers/specs/2026-09-05-pty-host-stacking-sock-race-design.md +510 -0
  139. package/docs/superpowers/specs/2026-09-07-issue-11-attach-runtime-desync-heal-design.md +130 -0
  140. package/docs/superpowers/specs/2026-09-08-attach-ctrl-left-detach-design.md +58 -0
  141. package/docs/superpowers/specs/2026-09-08-dashboard-shrink-repaint-design.md +52 -0
  142. package/docs/superpowers/specs/2026-09-08-legacy-stale-host-recovery-design.md +87 -0
  143. package/docs/superpowers/specs/2026-09-08-spawn-async-error-swallow-design.md +56 -0
  144. package/docs/superpowers/specs/2026-09-08-stale-model-attach-guard-design.md +79 -0
  145. package/docs/superpowers/specs/2026-09-09-claimpid-blocks-replace-design.md +147 -0
  146. package/docs/superpowers/specs/2026-09-09-harden-runner-architecture-design.md +304 -0
  147. package/docs/superpowers/specs/2026-09-14-attach-cursor-dectcem-gate-design.md +114 -0
  148. package/docs/superpowers/specs/2026-09-14-host-meta-orphan-lock-design.md +120 -0
  149. package/docs/superpowers/specs/2026-09-14-issue-106-terminal-frame-cognition-design.md +146 -0
  150. package/docs/superpowers/specs/2026-09-14-issue-113-foreground-preview-race-design.md +116 -0
  151. package/docs/superpowers/specs/2026-09-15-coordinator-pipe-root-normalize-design.md +84 -0
  152. package/docs/superpowers/specs/2026-09-15-lease-publish-eprem-reclaim-design.md +92 -0
  153. package/docs/superpowers/specs/2026-09-18-detach-anchor-reporter-endpoint-design.md +123 -0
  154. package/docs/superpowers/specs/2026-09-20-issue-121-perf-gate-out-of-coverage-design.md +204 -0
  155. package/docs/superpowers/specs/2026-09-23-a5-mid-stream-assertion-design.md +43 -0
  156. package/docs/superpowers/specs/2026-09-23-cwd-browse-tab-drill-design.md +95 -0
  157. package/docs/superpowers/specs/2026-09-23-terminal-query-forwarding-design.md +63 -0
  158. package/docs/superpowers/specs/2026-09-23-unconditional-detach-key-design.md +57 -0
  159. package/docs/superpowers/specs/2026-09-24-control-reconcile-flake-design.md +98 -0
  160. package/docs/superpowers/specs/2026-09-24-probe-attached-clients-design.md +190 -0
  161. package/docs/superpowers/specs/2026-09-29-issue-145-holding-semantic-state-design.md +394 -0
  162. package/docs/superpowers/specs/2026-09-30-board-delete-key-x-y-confirm-design.md +86 -0
  163. package/docs/superpowers/specs/2026-09-30-issue-153-runner-stop-boot-window-design.md +75 -0
  164. package/docs/superpowers/specs/2026-09-30-issue-95-test-discipline-design.md +226 -0
  165. package/docs/superpowers/specs/2026-09-30-publish-dispatch-tag-check-design.md +129 -0
  166. package/docs/superpowers/specs/2026-10-06-attach-settle-scheme-delivery-design.md +196 -0
  167. package/docs/superpowers/specs/2026-10-09-issue-167-attach-middle-paste-design.md +125 -0
  168. package/docs/superpowers/specs/2026-10-09-issue-169-fullscreen-mouse-ownership-design.md +130 -0
  169. package/docs/superpowers/specs/2026-10-10-attach-delete-in-place-design.md +224 -0
  170. package/docs/superpowers/specs/2026-10-10-issue-174-selection-mirror-paste-source-design.md +154 -0
  171. package/docs/superpowers/specs/2026-10-14-issue-170-attach-reply-guard-design.md +195 -0
  172. package/docs/superpowers/specs/2026-10-14-issue-178-accept-registration-race-design.md +287 -0
  173. package/docs/superpowers/specs/2026-10-14-issue-184-snapshot-window-contract-design.md +288 -0
  174. package/docs/superpowers/specs/2026-10-14-issue-185-attach-wheel-alt-screen-design.md +126 -0
  175. package/docs/superpowers/specs/2026-10-14-issue-187-motionless-drag-design.md +269 -0
  176. package/docs/superpowers/specs/2026-10-14-issue-189-mixed-input-segments-design.md +369 -0
  177. package/docs/superpowers/specs/2026-10-15-issue-188-x10-mouse-release-design.md +252 -0
  178. package/docs/superpowers/specs/2026-10-16-issue-198-attach-wheel-forward-fallback-design.md +67 -0
  179. package/docs/superpowers/specs/2026-10-18-attach-wheel-replay-pseudo-scrollback-design.md +250 -0
  180. package/docs/superpowers/specs/2026-10-19-issue-213-reply-guard-csi-key-design.md +165 -0
  181. package/docs/superpowers/specs/2026-10-19-issue-214-attach-forward-alt-modifier-design.md +131 -0
  182. package/docs/superpowers/specs/2026-10-20-issue-221-ask-user-question-design.md +270 -0
  183. package/docs/superpowers/specs/2026-10-22-issue-225-monotonic-foreground-state-design.md +225 -0
  184. package/docs/superpowers/specs/2026-10-23-issue-222-attach-ready-design.md +204 -0
  185. package/docs/superpowers/specs/2026-10-23-issue-229-vim-jkl-design.md +90 -0
  186. package/docs/superpowers/specs/2026-10-23-issue-232-attach-bg-only-cells-design.md +146 -0
  187. package/docs/superpowers/specs/2026-10-23-lease-heartbeat-staleness-design.md +271 -0
  188. package/docs/superpowers/specs/2026-10-23-selected-row-highlight-design.md +191 -0
  189. package/docs/superpowers/specs/2026-10-24-issue-217-windows-pid-reuse-stopping-design.md +334 -0
  190. package/docs/superpowers/specs/2026-10-24-issue-230-warm-host-background-runs-design.md +283 -0
  191. package/docs/superpowers/specs/2026-10-24-issue-238-windows-process-identity-token-design.md +286 -0
  192. package/docs/superpowers/specs/2026-10-24-issue-241-coordinator-root-exit-design.md +381 -0
  193. package/docs/superpowers/specs/2026-10-25-issue-239-coordinator-replacement-identity-design.md +331 -0
  194. package/docs/superpowers/specs/2026-10-26-issue-245-key-hints-doc-sync-design.md +72 -0
  195. package/docs/superpowers/specs/2026-10-27-issue-172-fullscreen-selection-clear-design.md +248 -0
  196. package/docs/superpowers/specs/2026-10-27-issue-252-select-key-hints-design.md +95 -0
  197. package/docs/superpowers/specs/2026-10-28-issue-255-coordinator-endpoint-zombie-design.md +380 -0
  198. package/docs/superpowers/specs/2026-10-28-issue-257-legacy-pty-runner-endpoint-cleanup-design.md +197 -0
  199. package/docs/superpowers/specs/2026-11-03-cwd-favorites-full-pool-design.md +106 -0
  200. package/index.ts +6 -0
  201. package/package.json +84 -4
  202. package/runner/job-runner-legacy.mjs +68 -0
  203. package/runner/job-runner.mjs +834 -0
  204. package/runner/pty-runner-legacy.mjs +50 -0
  205. package/runner/pty-runner.mjs +2156 -0
  206. package/runner/state-coordinator.mjs +673 -0
  207. package/runner/state-runner.mjs +203 -0
  208. package/runner/title-runner.mjs +80 -0
  209. package/scripts/budget-audit.mjs +135 -0
  210. package/scripts/patch-vulns.mjs +59 -0
  211. package/scripts/release.mjs +446 -0
  212. package/scripts/release_helper.mjs +309 -0
  213. package/scripts/run-perf-gate.mjs +40 -0
  214. package/scripts/run-tests.mjs +375 -0
  215. package/src/commands/agent-board.ts +363 -0
  216. package/src/commands/attach-decision.mjs +66 -0
  217. package/src/commands/attach-flow.ts +247 -0
  218. package/src/commands/bg.ts +80 -0
  219. package/src/core/atomic.mjs +184 -0
  220. package/src/core/attach-delete.mjs +24 -0
  221. package/src/core/attach-frame-proof.mjs +862 -0
  222. package/src/core/attach-frame-reporter.mjs +648 -0
  223. package/src/core/attach-frame-stream.mjs +266 -0
  224. package/src/core/attach-view-proof.mjs +709 -0
  225. package/src/core/auto-state.mjs +377 -0
  226. package/src/core/background-runs.mjs +273 -0
  227. package/src/core/code-refs-store.mjs +315 -0
  228. package/src/core/code-refs.mjs +913 -0
  229. package/src/core/control-protocol.mjs +482 -0
  230. package/src/core/coordinator-client.mjs +633 -0
  231. package/src/core/coordinator-journal.mjs +282 -0
  232. package/src/core/coordinator-protocol.mjs +14 -0
  233. package/src/core/cwd-stats.mjs +114 -0
  234. package/src/core/dashboard-render.mjs +10 -0
  235. package/src/core/derive.mjs +117 -0
  236. package/src/core/diagnostics.mjs +133 -0
  237. package/src/core/double-press.mjs +22 -0
  238. package/src/core/editor-state-reporter.mjs +112 -0
  239. package/src/core/events.mjs +286 -0
  240. package/src/core/evidence.mjs +245 -0
  241. package/src/core/follow-up-queue.mjs +211 -0
  242. package/src/core/foreground-preview-cache.mjs +117 -0
  243. package/src/core/foreground-projection-cache.mjs +99 -0
  244. package/src/core/frame-witness-update.mjs +35 -0
  245. package/src/core/heuristics.mjs +313 -0
  246. package/src/core/host-coordination.mjs +300 -0
  247. package/src/core/host-crash.mjs +79 -0
  248. package/src/core/host-probe.mjs +207 -0
  249. package/src/core/host-protocol.mjs +73 -0
  250. package/src/core/ids.mjs +35 -0
  251. package/src/core/ime-cursor-coalesce.mjs +193 -0
  252. package/src/core/invocation.mjs +43 -0
  253. package/src/core/launch-options.mjs +418 -0
  254. package/src/core/launch.mjs +157 -0
  255. package/src/core/locks.mjs +530 -0
  256. package/src/core/paths.mjs +183 -0
  257. package/src/core/pid.mjs +75 -0
  258. package/src/core/prewarm-schedule.mjs +41 -0
  259. package/src/core/process-identity-probe.mjs +228 -0
  260. package/src/core/prompt-transport.mjs +13 -0
  261. package/src/core/pty-attach-jiggle-controller.mjs +356 -0
  262. package/src/core/pty-attach-jiggle-retry.mjs +109 -0
  263. package/src/core/pty-attach-reconnect.mjs +61 -0
  264. package/src/core/pty-attach-render.mjs +229 -0
  265. package/src/core/pty-links.mjs +71 -0
  266. package/src/core/pty-scroll.mjs +522 -0
  267. package/src/core/pty-support.mjs +327 -0
  268. package/src/core/repo.mjs +103 -0
  269. package/src/core/rows.mjs +378 -0
  270. package/src/core/screen-log-gc.mjs +198 -0
  271. package/src/core/screen-log.mjs +160 -0
  272. package/src/core/session-view.mjs +174 -0
  273. package/src/core/state-commands.mjs +757 -0
  274. package/src/core/status-consistency.mjs +98 -0
  275. package/src/core/steering-prompts.mjs +34 -0
  276. package/src/core/steering.mjs +133 -0
  277. package/src/core/stop-latch.mjs +41 -0
  278. package/src/core/store.mjs +523 -0
  279. package/src/core/terminal-attach-client.mjs +981 -0
  280. package/src/core/terminal-attach-protocol.mjs +460 -0
  281. package/src/core/terminal-model.mjs +222 -0
  282. package/src/core/terminal-query-sequences.mjs +239 -0
  283. package/src/core/terminal-reply-guard.mjs +222 -0
  284. package/src/core/terminal-snapshot.mjs +440 -0
  285. package/src/core/test-knobs.mjs +28 -0
  286. package/src/core/title.mjs +43 -0
  287. package/src/core/types.mjs +414 -0
  288. package/src/core/warm-host-sweeper.mjs +168 -0
  289. package/src/core/worktree.mjs +65 -0
  290. package/src/index.ts +274 -0
  291. package/src/runtime/service.mjs +3183 -0
  292. package/src/ui/chrome.ts +20 -0
  293. package/src/ui/dashboard-decisions.mjs +55 -0
  294. package/src/ui/dashboard-evidence.mjs +85 -0
  295. package/src/ui/dashboard.ts +2141 -0
  296. package/src/ui/double-left.ts +36 -0
  297. package/src/ui/pty-attach.ts +3091 -0
@@ -0,0 +1,277 @@
1
+ # Batch Selection + Read/Unread Flow
2
+
3
+ **Status:** Proposed
4
+ **Context:** Pi Agent Board dashboard
5
+ **Created:** 2026-06-02
6
+
7
+ ## Assumption
8
+
9
+ "Move to done to delete that batch" is treated as a **safe 2-step cleanup flow**:
10
+
11
+ 1. bulk-select sessions,
12
+ 2. move them to **Done**,
13
+ 3. optionally delete that same batch from **Done** with a second explicit confirmation.
14
+
15
+ This keeps "done" non-destructive and makes deletion intentional.
16
+
17
+ ---
18
+
19
+ ## 1. Goals
20
+
21
+ ### A. Multi-session selection
22
+ - Let users select multiple rows from the board.
23
+ - Let users batch-mark them as **Done**.
24
+ - Let users immediately clean up that same batch from the **Done** group.
25
+
26
+ ### B. Read / Unread
27
+ - Mark a session as **Read** once the user has actually visited it via attach / transcript-style deep view.
28
+ - Show a **subtle unread hint** on rows with unseen activity.
29
+ - If new activity happens after a visit, the row becomes **Unread** again.
30
+
31
+ ---
32
+
33
+ ## 2. UX overview
34
+
35
+ ### Normal mode
36
+ - Board behaves exactly like today: single active row, peek, attach, done, delete.
37
+
38
+ ### Selection mode
39
+ - User enters a temporary **multi-select mode**.
40
+ - Selection count appears in header/footer: `3 selected`.
41
+ - Rows show a subtle selected marker.
42
+ - Actions become batch-oriented:
43
+ - **Mark selected done**
44
+ - **Delete selected** (only from Done, with confirm)
45
+ - **Clear selection**
46
+
47
+ ### Read state
48
+ - Rows can be either:
49
+ - **Unread** = new activity since last visit
50
+ - **Read** = user has already visited the latest activity
51
+ - Unread should not overpower semantic state; it is a secondary signal.
52
+
53
+ ---
54
+
55
+ ## 3. Multi-session selection flow
56
+
57
+ ## 3.1 Enter selection mode
58
+
59
+ **Trigger**
60
+ - User presses a dedicated shortcut from board list view.
61
+ - Suggested shortcut: `m` for **multi-select**.
62
+
63
+ **Result**
64
+ - Footer changes to selection hints.
65
+ - Selection starts empty; the current row stays focused but is **not** auto-selected.
66
+ - Header shows selection count.
67
+
68
+ ---
69
+
70
+ ## 3.2 Build the selection
71
+
72
+ While in selection mode:
73
+ - `↑ / ↓` moves cursor
74
+ - `space` toggles current row in selection
75
+ - `a` selects all visible rows in current filter/group
76
+ - `u` clears the current selection without leaving selection mode
77
+ - `esc` clears selection and exits selection mode
78
+
79
+ Optional later:
80
+ - `A` = select all rows in current state group
81
+ - `u` = unselect all
82
+
83
+ ---
84
+
85
+ ## 3.3 Move selected rows to Done
86
+
87
+ **Trigger**
88
+ - User presses `d` while in selection mode.
89
+
90
+ **Confirmation copy**
91
+ - `Mark 5 selected sessions as done? (y/N)`
92
+
93
+ **Rules**
94
+ - Running/live rows are skipped.
95
+ - Needs-input / idle / failed / stopped rows may be moved to Done.
96
+ - Confirmation should mention skipped rows if any.
97
+
98
+ **Result**
99
+ - Matching rows are moved to `Done`.
100
+ - The same rows remain selected.
101
+ - Board auto-scrolls/focuses to the `Done` section.
102
+ - Toast example: `Moved 5 to Done · skipped 2 live`
103
+
104
+ This preserves the batch as a temporary working set for cleanup.
105
+
106
+ ---
107
+
108
+ ## 3.4 Delete the same batch from Done
109
+
110
+ **Trigger**
111
+ - With the same batch still selected, user presses `ctrl+x` or a batch delete key.
112
+
113
+ **Guardrail**
114
+ - Deletion is only enabled when all selected rows are already in `Done`.
115
+ - If selection includes non-Done rows, show: `Only Done sessions can be batch deleted`.
116
+
117
+ **Confirmation copy**
118
+ - `Delete 5 done sessions? Session files are preserved. (y/N)`
119
+
120
+ **Result**
121
+ - Selected rows are archived from dashboard.
122
+ - Selection clears.
123
+ - Toast example: `Deleted 5 done sessions`
124
+
125
+ ---
126
+
127
+ ## 4. Read / Unread flow
128
+
129
+ ## 4.1 What counts as Read
130
+
131
+ A session becomes **Read** when the user intentionally opens that session in a deeper way:
132
+ - attach via `enter` / `→`
133
+ - transcript/full-session view
134
+
135
+ **Not read by default:**
136
+ - merely highlighting a row
137
+ - passive polling updates
138
+ - quick list navigation
139
+ - optional: peek can remain non-read to preserve signal
140
+
141
+ ---
142
+
143
+ ## 4.2 When a row becomes Unread again
144
+
145
+ A previously read row becomes **Unread** when a new assistant reply lands after the last visit, for example:
146
+ - new assistant message
147
+ - new blocker/question raised in that message
148
+ - session re-enters `needs_input` because of that message
149
+
150
+ User-authored actions alone should not create unread state, and intermediate tool churn alone should not flip unread.
151
+
152
+ ---
153
+
154
+ ## 4.3 Recommended subtle hint
155
+
156
+ Use the **stage icon itself** as the unread signal:
157
+ - **Unread:** stronger/heavier variant of that stage icon
158
+ - **Read:** lighter/default variant of that stage icon
159
+
160
+ Examples:
161
+ - queued: `○` → `◎`
162
+ - needs input: `◇` → `◆`
163
+ - done: `✓` → `✔`
164
+
165
+ Optional secondary treatment:
166
+ - unread row title slightly brighter
167
+ - read row title normal
168
+
169
+ Avoid:
170
+ - loud badges like `UNREAD`
171
+ - full-row highlight
172
+ - strong color conflict with semantic states like Failed / Needs input
173
+
174
+ ### Priority of signals
175
+ 1. semantic state (Running, Needs input, Done, Failed)
176
+ 2. selection state
177
+ 3. unread hint
178
+
179
+ Unread should help scanning, not dominate the board.
180
+
181
+ ---
182
+
183
+ ## 5. State model
184
+
185
+ ## 5.1 Batch selection
186
+
187
+ Selection can stay **ephemeral UI state** in the dashboard component:
188
+ - `selectedIds: Set<string>`
189
+ - `selectionMode: boolean`
190
+
191
+ No persistence needed for V1.
192
+
193
+ ## 5.2 Read state
194
+
195
+ Recommended durable fields:
196
+ - `lastVisitedAt: number | null`
197
+ - `lastAgentActivityAt: number | null`
198
+
199
+ Derived flag:
200
+ - `isUnread = lastAgentActivityAt > lastVisitedAt`
201
+
202
+ This is simpler and more reliable than storing a raw boolean.
203
+
204
+ ---
205
+
206
+ ## 6. Edge cases
207
+
208
+ ### Live rows inside a batch
209
+ - Cannot be batch-done or batch-deleted.
210
+ - Skip and report counts.
211
+
212
+ ### Mixed-state selection
213
+ - Batch done: allowed for inactive non-done rows.
214
+ - Batch delete: only allowed if every selected row is already Done.
215
+
216
+ ### New activity during selection
217
+ - Keep row selected.
218
+ - If it receives new output, unread hint can appear even while selected.
219
+
220
+ ### Re-attaching to a row
221
+ - Refresh `lastVisitedAt`.
222
+ - Any prior unread marker clears.
223
+
224
+ ### Peek behavior
225
+ - Recommend: peek does **not** mark read.
226
+ - Reason: peek is triage, attach/transcript is actual visit.
227
+
228
+ ---
229
+
230
+ ## 7. Suggested implementation slices
231
+
232
+ ### Dashboard (`src/ui/dashboard.ts`)
233
+ - add selection mode
234
+ - add `selectedIds`
235
+ - batch confirm flows
236
+ - render selected marker + unread marker
237
+
238
+ ### Service (`src/runtime/service.mjs`)
239
+ - add `markCompletedMany(viewIds)`
240
+ - add `archiveMany(viewIds)`
241
+ - add `markVisited(viewId)`
242
+
243
+ ### Store/types (`src/core/types.mjs`, `src/core/store.mjs`)
244
+ - add read-tracking timestamps
245
+ - expose unread derivation on rows
246
+
247
+ ### Attach / session view entry points
248
+ - mark row visited when attach or transcript view opens
249
+
250
+ ---
251
+
252
+ ## 8. Recommended default interaction summary
253
+
254
+ ### Batch
255
+ - `m` enter multi-select
256
+ - `space` toggle row
257
+ - `a` select all visible
258
+ - `u` clear selection
259
+ - `d` move selected to Done
260
+ - `ctrl+x` delete selected Done batch
261
+ - `esc` clear/exit
262
+
263
+ ### Read state
264
+ - attach / transcript => mark read
265
+ - new assistant reply => unread again
266
+ - unread indicator => subtle left dot/bar
267
+
268
+ ---
269
+
270
+ ## 9. Acceptance criteria
271
+
272
+ - User can select multiple sessions from the board.
273
+ - User can mark that selection Done in one action.
274
+ - User can immediately delete that same batch from Done with explicit confirmation.
275
+ - A session becomes Read after attach/transcript visit.
276
+ - New agent activity turns a previously read row back to Unread.
277
+ - Unread is visible through a subtle row hint, not a loud badge.
@@ -0,0 +1,187 @@
1
+ # Exploration & Decisions — Pi Agent Board Extension
2
+
3
+ **For future agents.** This is the distilled gist of the codebase recon done before
4
+ implementing `IMPLEMENTATION_PLAN.md`. Read this first; it saves re-deriving the Pi API.
5
+
6
+ Runtime target: `/opt/homebrew/lib/node_modules/@earendil-works/pi-coding-agent` (v0.75.5).
7
+ `pi` binary is on PATH at `/opt/homebrew/bin/pi` → `dist/cli.js`.
8
+
9
+ ---
10
+
11
+ ## 1. How a Pi extension is loaded & shaped
12
+
13
+ - An extension is a TS module: `export default function (pi: ExtensionAPI) { ... }`
14
+ (may be `async`; Pi awaits it before `session_start`).
15
+ - Auto-discovered from `~/.pi/agent/extensions/*.ts` or `<name>/index.ts` (global) and
16
+ `.pi/extensions/...` (project). `/reload` hot-reloads them. `pi -e ./path.ts` for quick tests.
17
+ - Pi's loader (jiti) resolves `@earendil-works/pi-coding-agent`, `@earendil-works/pi-ai`,
18
+ `@earendil-works/pi-tui`, `typebox` **for you at runtime** — you do not bundle them.
19
+ For local typechecking we path-map those to the global install (see `tsconfig.json`).
20
+ - Loaded TS runs via jiti. **Detached external processes (our runner) must NOT rely on
21
+ jiti** → the runner is a plain `.mjs` that we spawn with `node`.
22
+
23
+ ## 2. ExtensionAPI surface we actually use
24
+
25
+ - `pi.registerCommand(name, { description, handler(args, ctx), getArgumentCompletions? })`
26
+ — handler `ctx` is `ExtensionCommandContext` (adds `newSession/fork/switchSession/
27
+ navigateTree/reload/waitForIdle`). Session-switch methods deadlock from event handlers,
28
+ so they exist only on command ctx.
29
+ - `pi.registerShortcut(KeyId, { description, handler(ctx) })`.
30
+ - `pi.registerFlag(name, { description, type:"boolean"|"string", default })` + `pi.getFlag`.
31
+ - `pi.exec(cmd, args, { signal, timeout })` → `{ stdout, stderr, code, killed }`.
32
+ - `pi.appendEntry(customType, data)` persists extension state into the session JSONL
33
+ (custom entry, not sent to LLM). Restore by scanning `ctx.sessionManager.getEntries()`.
34
+ - `ctx.ui.*`: `select/confirm/input/editor/notify`, `setStatus/setWidget/setFooter`,
35
+ and **`custom<T>(factory, opts?) => Promise<T>`** (the dashboard surface).
36
+ - `ctx.switchSession(path, { withSession })` — attach = switch the interactive session to
37
+ a managed session file. `withSession(ctx)` runs **after** the old runtime is torn down;
38
+ only use the fresh `ctx` passed in, never captured old `pi`/`ctx`/`sessionManager`.
39
+
40
+ ### `ctx.ui.custom` (the important one)
41
+ ```ts
42
+ const result = await ctx.ui.custom<T>((tui, theme, keybindings, done) => {
43
+ // return a Component: { render(width): string[]; invalidate(); handleInput?(data) }
44
+ // call tui.requestRender() after state changes to repaint
45
+ // call done(value) to close the surface and resolve the promise
46
+ });
47
+ ```
48
+ - It **replaces the editor** with your component until `done()` (full-screen-ish surface).
49
+ - It returns a `Promise`, NOT a handle. Live updates: hold a `tui` ref, call
50
+ `tui.requestRender()` from a `setInterval`/file-watch/poll loop. Terminal height =
51
+ `tui.terminal.rows`, width comes in as the `render(width)` arg.
52
+ - `{ overlay: true, overlayOptions }` renders a floating modal instead (used for peek/dialogs
53
+ if we want; MVP keeps peek as an internal mode of the dashboard component).
54
+
55
+ ## 3. TUI building blocks (`@earendil-works/pi-tui`)
56
+
57
+ Components implement `{ render(width:number):string[]; invalidate():void; handleInput?(data):void }`
58
+ — each rendered line **must not exceed `width`** (use `truncateToWidth`).
59
+ Exports we use: `Text, Box, Container, Spacer, Markdown, SelectList, SettingsList,
60
+ matchesKey, Key, truncateToWidth, visibleWidth, wrapTextWithAnsi, type Component`.
61
+ From coding-agent: `DynamicBorder, BorderedLoader, getMarkdownTheme, getSettingsListTheme,
62
+ keyHint`. Theme: `theme.fg(color, text)`, `theme.bold/italic/strikethrough`.
63
+ Key input: `matchesKey(data, Key.up)` etc. After state change in `handleInput`, call
64
+ `tui.requestRender()`.
65
+
66
+ ## 4. Sessions & the headless worker (the core mechanism)
67
+
68
+ - Session files are JSONL trees (`id`/`parentId`); first line is a header
69
+ `{"type":"session","version":3,"id":"<uuid>","timestamp":"<iso>","cwd":"<cwd>"}`.
70
+ - **Launching a managed run:**
71
+ `pi --mode json -p --session <file> "<prompt>"`, spawned with `cwd: <repo|worktree>`.
72
+ - `--session <path>` with a path that contains `/` or ends `.jsonl` → `SessionManager.open(path)`.
73
+ If the file **doesn't exist**, Pi creates a fresh session and persists to that **exact path**
74
+ (verified in `dist/core/session-manager.ts` `open()` → `setSessionFile` else-branch).
75
+ The header `cwd` becomes the spawn `cwd`, so always set `cwd` correctly.
76
+ - `--mode json` = one-shot: it `session.subscribe((e)=>stdout(JSON.stringify(e)+"\n"))`,
77
+ runs the single prompt to completion (a full agent loop, many turns/tools), then exits
78
+ **with `agent_end` as the last event** — `worker.on("close")` is the run-finished signal.
79
+ - **⚠️ AUTH/NETWORK CAVEAT (verified 2026-05-30):** the one-shot exits cleanly *only when the
80
+ worker can actually reach the model provider*. With no/blocked pi provider auth (or a
81
+ network/proxy stall), `pi --mode json -p` records `session → agent_start → turn_start →
82
+ message_start(user) → message_end(user)` and then **blocks indefinitely at the provider
83
+ request** (idle, ~3% CPU) — **no assistant reply, no `agent_end`, no process exit**. This is
84
+ a pi/runtime condition, NOT an extension bug. The runner handles it correctly: the row stays
85
+ `working/alive` until the user stops it (`s` in the dashboard → SIGTERM → finalized `stopped`).
86
+ Note: pi's own provider auth is separate from Claude Code's `/login`. Validate with
87
+ `pi --mode json -p --no-session "say hi"` — it must print events ending in `agent_end` and exit.
88
+ - `-p` forces non-interactive. `--model <m>`, `--tools a,b`, `--append-system-prompt <file>`
89
+ are available. `--no-session` = ephemeral (we do NOT use it for managed rows; we want persistence).
90
+ - Reply / resume = launch another run against the **same** `--session <file>`; Pi appends to it.
91
+
92
+ ### JSON event vocabulary actually emitted (parse these in the runner)
93
+ First line: the session header `{type:"session",...}`. Then per `AgentSessionEvent`:
94
+ `agent_start`, `agent_end`, `turn_start`, `turn_end`,
95
+ `message_start` / `message_update` / `message_end` (each has `.message`: an AgentMessage),
96
+ `tool_execution_start` `{toolCallId,toolName,args}`,
97
+ `tool_execution_update` `{...,partialResult}`,
98
+ `tool_execution_end` `{toolCallId,toolName,result,isError}`,
99
+ `tool_call`, `tool_result`, plus `queue_update`, `compaction_*`, `auto_retry_*`,
100
+ `model_select`, `thinking_level_*`, `session_*`.
101
+ > NOTE: the subagent example reads `tool_result_end` — that string is **never emitted** (dead
102
+ > defensive code). Use `message_end` (role assistant → text/toolCalls/usage/stopReason/errorMessage)
103
+ > and `tool_execution_start/end` for live state. `agent_end` ⇒ run finished.
104
+
105
+ AssistantMessage shape: `{ role:"assistant", content:(TextContent|ThinkingContent|ToolCall)[],
106
+ model, usage, stopReason:"stop"|"length"|"toolUse"|"error"|"aborted", errorMessage? }`.
107
+ ToolCall content block: `{ type:"toolCall", id, name, arguments }`.
108
+
109
+ ## 5. Architecture chosen (matches plan §3.3) — file-backed detached runner
110
+
111
+ ```
112
+ Pi extension (parent, interactive) runner/job-runner.mjs (detached, 1 per run)
113
+ /agent-board dashboard (ctx.ui.custom) ─► spawns: pi --mode json -p --session <file> <prompt>
114
+ store reader/writer parses JSON event lines from worker stdout
115
+ launches runner via child_process writes runs/<runId>/{status.json,events.jsonl,
116
+ polls store files to repaint stdout.log,stderr.log,pid.json}
117
+ updates views/<id>/state.json on progress/exit
118
+ ```
119
+ Why detached `.mjs` runner (not spawn pi directly from the extension, not a daemon):
120
+ survives parent `/reload`/exit, durable status, no socket/daemon complexity. Each run is the
121
+ durable monitor for one worker.
122
+
123
+ ### Store layout (user-scoped) — `~/.pi/agent/agent-board/`
124
+ ```
125
+ roster.json { version, views: [viewId...] }
126
+ views/<viewId>/meta.json stable: id,name,cwd,sessionFile,createdAt,pinned,kind,
127
+ worktreeMode,worktreePath,defaultModel,source,archived
128
+ views/<viewId>/state.json derived: currentRunId,semanticState,processState,summary,
129
+ needsInput,hasError,latestAssistantPreview,latestTool,lastActivityAt
130
+ views/<viewId>/runs/<runId>/status.json per-run durable snapshot (pid,startedAt,endedAt,
131
+ exitCode,semanticState,currentTool,latestAssistantPreview,question,error)
132
+ views/<viewId>/runs/<runId>/{events.jsonl,stdout.log,stderr.log,pid.json}
133
+ sessions/<viewId>.jsonl the managed Pi session file (Pi creates/persists it)
134
+ ```
135
+ - One row = one real Pi session file. One run = one detached worker against it.
136
+ - Both `state.json` (row, long-lived) and `status.json` (run, single attempt) exist so
137
+ reply/resume/attach stay clean.
138
+ - Atomic writes: write tmp + `rename`. Liveness: `pid.json` + `process.kill(pid,0)`.
139
+
140
+ ## 6. State derivation rules (plan §9)
141
+
142
+ Semantic: `queued`→`working` (alive, once assistant/tool activity); on clean exit
143
+ `needs_input` (assistant ends asking) | `completed` | `idle`; on bad exit `failed`;
144
+ user-stopped `stopped`. Process: `alive` if pid live else `exited`.
145
+ Summary priority: model-summary(opt-in) → active tool (`Editing x`,`Running tests`) →
146
+ blocker/question → first sentence of latest assistant → stderr/error → fallback status.
147
+ Needs-input heuristic: assistant text ends in `?` or contains `need your input`/
148
+ `which option`/`should I`/`please confirm`.
149
+
150
+ ## 7. Safety / worktree (plan §11)
151
+
152
+ Same-repo parallel **writers** require git worktree isolation (locked decision). MVP:
153
+ explicit `worktreeMode:"worktree"` per dispatch → `git worktree add` under
154
+ `~/.pi/agent/agent-board/worktrees/<viewId>`; persist `worktreePath`; block a 2nd active
155
+ writer in the same repo if not isolated. Delete row ≠ delete data: archive row, keep
156
+ session file; worktree removal needs explicit confirm.
157
+
158
+ ## 8. Decisions made for this implementation
159
+
160
+ 1. **Runner is `runner/job-runner.mjs` (plain ESM)**, spawned with `node`. It imports only
161
+ `node:*`. The pi binary it spawns is passed in via run-config (`piBin`, default `"pi"`).
162
+ 2. **Extension code is TS**, loaded by Pi via jiti. We add `package.json` + `tsconfig.json`
163
+ only for local typecheck/tests (path-mapped to the global pi install); not needed at runtime.
164
+ 3. **Summaries: cheap model is the DEFAULT** (`DEFAULT_SUMMARY_MODEL = "gpt-4o"` in the runner;
165
+ user choice 2026-05-30), with the **heuristic kept as a graceful fallback** (no API key /
166
+ offline / timeout) and still used for live state detection (needs-input, semantic state).
167
+ Override with `AGENT_BOARD_SUMMARY_MODEL=<model>`, disable with `=off`. The summary call has a
168
+ 15s safety timeout and runs *after* the heuristic terminal state is already persisted, so a
169
+ slow/unreachable summary model never stalls the dashboard (row flips to final state at once,
170
+ summary upgrades a few seconds later). gpt-4o needs OpenAI auth; without it → heuristic.
171
+ 4. **Dashboard = single `ctx.ui.custom` component** owning list + peek + dispatch input as
172
+ internal modes (plan §10.4 recommendation). Live updates via a poll loop calling
173
+ `tui.requestRender()` (store files are the source of truth; cheap to re-read).
174
+ 5. **Attach = `ctx.switchSession(sessionFile, { withSession })`** from the `/agent-board` command
175
+ handler. If the row's run is alive → confirm interrupt (stop run) then attach (locked decision).
176
+ 6. **Verification without a TTY/API key:** unit tests for all pure logic + an integration test
177
+ that runs the real `.mjs` runner against a **fake `pi`** stub script emitting canned JSON
178
+ event lines, asserting the durable store artifacts. (`test-support/fake-pi.mjs`.)
179
+
180
+ ## 9. Gotchas
181
+
182
+ - `ctx.ui.custom` callback's component must return lines ≤ width; always `truncateToWidth`.
183
+ - Rebuild themed content on `invalidate()` (theme change) if you pre-bake `theme.fg(...)`.
184
+ - Don't import `pi-subagents/src/...` internals (unstable) — reference only.
185
+ - Session-switch `withSession` runs post-teardown; capture only plain strings/ids.
186
+ - The runner must update `status.json` atomically and frequently but throttled (avoid fs thrash).
187
+ - `process.kill(pid, 0)` throws ESRCH if dead, EPERM if alive-but-not-ours → treat EPERM as alive.