gsd-pi 2.54.0 → 2.55.0-dev.9ec7cdf

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 (262) hide show
  1. package/dist/cli.js +19 -19
  2. package/dist/headless-ui.d.ts +27 -1
  3. package/dist/headless-ui.js +203 -13
  4. package/dist/headless.js +60 -3
  5. package/dist/resources/extensions/bg-shell/bg-shell-lifecycle.js +2 -2
  6. package/dist/resources/extensions/bg-shell/utilities.js +34 -5
  7. package/dist/resources/extensions/gsd/auto/phases.js +19 -3
  8. package/dist/resources/extensions/gsd/auto-dispatch.js +1 -1
  9. package/dist/resources/extensions/gsd/auto-model-selection.js +17 -1
  10. package/dist/resources/extensions/gsd/auto-prompts.js +9 -0
  11. package/dist/resources/extensions/gsd/auto-start.js +12 -5
  12. package/dist/resources/extensions/gsd/auto-worktree.js +39 -14
  13. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +5 -1
  14. package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +18 -0
  15. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +18 -5
  16. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +20 -0
  17. package/dist/resources/extensions/gsd/commands/catalog.js +2 -1
  18. package/dist/resources/extensions/gsd/commands/handlers/parallel.js +15 -1
  19. package/dist/resources/extensions/gsd/crash-recovery.js +2 -2
  20. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +413 -0
  21. package/dist/resources/extensions/gsd/parallel-orchestrator.js +5 -1
  22. package/dist/resources/extensions/gsd/session-lock.js +46 -12
  23. package/dist/resources/extensions/gsd/skill-health.js +2 -2
  24. package/dist/resources/extensions/gsd/visualizer-overlay.js +3 -3
  25. package/dist/resources/extensions/shared/format-utils.js +1 -1
  26. package/dist/resources/extensions/subagent/worker-registry.js +2 -1
  27. package/dist/web/standalone/.next/BUILD_ID +1 -1
  28. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  29. package/dist/web/standalone/.next/build-manifest.json +4 -4
  30. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  31. package/dist/web/standalone/.next/react-loadable-manifest.json +2 -2
  32. package/dist/web/standalone/.next/required-server-files.json +3 -3
  33. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  34. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  35. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  36. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  44. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  45. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  46. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  47. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  48. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  50. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  54. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  55. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  56. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  57. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  58. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  59. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  60. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  61. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  62. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  63. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  64. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  65. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  66. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  68. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  69. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  70. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  72. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  74. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  76. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  78. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  79. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  80. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  100. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  106. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  120. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  122. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  124. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  126. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  135. package/dist/web/standalone/.next/server/app/index.html +1 -1
  136. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  137. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  138. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  139. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  140. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  141. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  142. package/dist/web/standalone/.next/server/app/page.js +2 -2
  143. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  144. package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
  145. package/dist/web/standalone/.next/server/chunks/2229.js +2 -2
  146. package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
  147. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  148. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  149. package/dist/web/standalone/.next/server/middleware.js +2 -2
  150. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  151. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  152. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  153. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  154. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  155. package/dist/web/standalone/.next/static/chunks/6502.2305d0afd2385711.js +9 -0
  156. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  157. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  158. package/dist/web/standalone/.next/static/chunks/app/page-0c485498795110d6.js +1 -0
  159. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  160. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  161. package/dist/web/standalone/.next/static/chunks/{webpack-bca0e732db0dcec3.js → webpack-4332cbd5dd1be584.js} +1 -1
  162. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  163. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  164. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  165. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  166. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  167. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  168. package/dist/web/standalone/server.js +1 -1
  169. package/package.json +6 -4
  170. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +1 -1
  171. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  172. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  173. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts +2 -0
  174. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
  175. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +14 -2
  176. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
  177. package/packages/pi-coding-agent/package.json +1 -1
  178. package/packages/pi-coding-agent/src/core/model-registry.ts +1 -1
  179. package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +16 -2
  180. package/pkg/package.json +1 -1
  181. package/scripts/ensure-workspace-builds.cjs +45 -41
  182. package/src/resources/extensions/bg-shell/bg-shell-lifecycle.ts +2 -2
  183. package/src/resources/extensions/bg-shell/utilities.ts +39 -4
  184. package/src/resources/extensions/gsd/auto/phases.ts +25 -4
  185. package/src/resources/extensions/gsd/auto-dispatch.ts +1 -1
  186. package/src/resources/extensions/gsd/auto-model-selection.ts +21 -1
  187. package/src/resources/extensions/gsd/auto-prompts.ts +15 -0
  188. package/src/resources/extensions/gsd/auto-start.ts +13 -5
  189. package/src/resources/extensions/gsd/auto-worktree.ts +46 -13
  190. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +5 -4
  191. package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +53 -0
  192. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +19 -6
  193. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +24 -0
  194. package/src/resources/extensions/gsd/commands/catalog.ts +2 -1
  195. package/src/resources/extensions/gsd/commands/handlers/parallel.ts +19 -1
  196. package/src/resources/extensions/gsd/crash-recovery.ts +2 -3
  197. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +497 -0
  198. package/src/resources/extensions/gsd/parallel-orchestrator.ts +6 -1
  199. package/src/resources/extensions/gsd/session-lock.ts +46 -12
  200. package/src/resources/extensions/gsd/skill-health.ts +2 -2
  201. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +139 -0
  202. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +28 -0
  203. package/src/resources/extensions/gsd/tests/{all-milestones-complete-merge.test.ts → integration/all-milestones-complete-merge.test.ts} +3 -3
  204. package/src/resources/extensions/gsd/tests/{atomic-task-closeout.test.ts → integration/atomic-task-closeout.test.ts} +1 -1
  205. package/src/resources/extensions/gsd/tests/{auto-preflight.test.ts → integration/auto-preflight.test.ts} +1 -1
  206. package/src/resources/extensions/gsd/tests/{auto-recovery.test.ts → integration/auto-recovery.test.ts} +7 -7
  207. package/src/resources/extensions/gsd/tests/{auto-secrets-gate.test.ts → integration/auto-secrets-gate.test.ts} +2 -2
  208. package/src/resources/extensions/gsd/tests/{auto-stash-merge.test.ts → integration/auto-stash-merge.test.ts} +3 -3
  209. package/src/resources/extensions/gsd/tests/{auto-worktree-milestone-merge.test.ts → integration/auto-worktree-milestone-merge.test.ts} +4 -4
  210. package/src/resources/extensions/gsd/tests/{auto-worktree.test.ts → integration/auto-worktree.test.ts} +5 -5
  211. package/src/resources/extensions/gsd/tests/{continue-here.test.ts → integration/continue-here.test.ts} +3 -3
  212. package/src/resources/extensions/gsd/tests/{doctor-completion-deferral.test.ts → integration/doctor-completion-deferral.test.ts} +1 -1
  213. package/src/resources/extensions/gsd/tests/{doctor-delimiter-fix.test.ts → integration/doctor-delimiter-fix.test.ts} +1 -1
  214. package/src/resources/extensions/gsd/tests/{doctor-enhancements.test.ts → integration/doctor-enhancements.test.ts} +3 -3
  215. package/src/resources/extensions/gsd/tests/{doctor-environment-worktree.test.ts → integration/doctor-environment-worktree.test.ts} +1 -1
  216. package/src/resources/extensions/gsd/tests/{doctor-environment.test.ts → integration/doctor-environment.test.ts} +1 -1
  217. package/src/resources/extensions/gsd/tests/{doctor-fixlevel.test.ts → integration/doctor-fixlevel.test.ts} +2 -2
  218. package/src/resources/extensions/gsd/tests/{doctor-git.test.ts → integration/doctor-git.test.ts} +1 -1
  219. package/src/resources/extensions/gsd/tests/{doctor-proactive.test.ts → integration/doctor-proactive.test.ts} +1 -1
  220. package/src/resources/extensions/gsd/tests/{doctor-roadmap-summary-atomicity.test.ts → integration/doctor-roadmap-summary-atomicity.test.ts} +1 -1
  221. package/src/resources/extensions/gsd/tests/{doctor-runtime.test.ts → integration/doctor-runtime.test.ts} +1 -1
  222. package/src/resources/extensions/gsd/tests/{doctor.test.ts → integration/doctor.test.ts} +1 -1
  223. package/src/resources/extensions/gsd/tests/{e2e-workflow-pipeline-integration.test.ts → integration/e2e-workflow-pipeline-integration.test.ts} +5 -5
  224. package/src/resources/extensions/gsd/tests/{feature-branch-lifecycle-integration.test.ts → integration/feature-branch-lifecycle-integration.test.ts} +4 -4
  225. package/src/resources/extensions/gsd/tests/{git-locale.test.ts → integration/git-locale.test.ts} +4 -4
  226. package/src/resources/extensions/gsd/tests/{git-self-heal.test.ts → integration/git-self-heal.test.ts} +1 -1
  227. package/src/resources/extensions/gsd/tests/{git-service.test.ts → integration/git-service.test.ts} +4 -4
  228. package/src/resources/extensions/gsd/tests/{gitignore-tracked-gsd.test.ts → integration/gitignore-tracked-gsd.test.ts} +2 -2
  229. package/src/resources/extensions/gsd/tests/{idle-recovery.test.ts → integration/idle-recovery.test.ts} +3 -3
  230. package/src/resources/extensions/gsd/tests/{inherited-repo-home-dir.test.ts → integration/inherited-repo-home-dir.test.ts} +1 -1
  231. package/src/resources/extensions/gsd/tests/{integration-lifecycle.test.ts → integration/integration-lifecycle.test.ts} +4 -4
  232. package/src/resources/extensions/gsd/tests/{integration-mixed-milestones.test.ts → integration/integration-mixed-milestones.test.ts} +6 -6
  233. package/src/resources/extensions/gsd/tests/{integration-proof.test.ts → integration/integration-proof.test.ts} +12 -12
  234. package/src/resources/extensions/gsd/tests/{migrate-command.test.ts → integration/migrate-command.test.ts} +2 -2
  235. package/src/resources/extensions/gsd/tests/{milestone-transition-worktree.test.ts → integration/milestone-transition-worktree.test.ts} +3 -3
  236. package/src/resources/extensions/gsd/tests/{parallel-merge.test.ts → integration/parallel-merge.test.ts} +3 -3
  237. package/src/resources/extensions/gsd/tests/{parallel-workers-multi-milestone-e2e.test.ts → integration/parallel-workers-multi-milestone-e2e.test.ts} +3 -3
  238. package/src/resources/extensions/gsd/tests/{paths.test.ts → integration/paths.test.ts} +1 -1
  239. package/src/resources/extensions/gsd/tests/{plugin-importer-live.test.ts → integration/plugin-importer-live.test.ts} +2 -2
  240. package/src/resources/extensions/gsd/tests/{queue-completed-milestone-perf.test.ts → integration/queue-completed-milestone-perf.test.ts} +3 -3
  241. package/src/resources/extensions/gsd/tests/{queue-reorder-e2e.test.ts → integration/queue-reorder-e2e.test.ts} +5 -5
  242. package/src/resources/extensions/gsd/tests/{quick-branch-lifecycle.test.ts → integration/quick-branch-lifecycle.test.ts} +5 -5
  243. package/src/resources/extensions/gsd/tests/{run-uat.test.ts → integration/run-uat.test.ts} +4 -4
  244. package/src/resources/extensions/gsd/tests/{token-savings.test.ts → integration/token-savings.test.ts} +3 -3
  245. package/src/resources/extensions/gsd/tests/{worktree-e2e.test.ts → integration/worktree-e2e.test.ts} +4 -4
  246. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +55 -0
  247. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +60 -0
  248. package/src/resources/extensions/gsd/tests/parallel-worker-lock-contention.test.ts +226 -0
  249. package/src/resources/extensions/gsd/tests/plan-milestone-queue-context.test.ts +48 -0
  250. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +61 -19
  251. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +98 -0
  252. package/src/resources/extensions/gsd/tests/register-extension-guard.test.ts +59 -0
  253. package/src/resources/extensions/gsd/tests/worktree-preferences-sync.test.ts +49 -24
  254. package/src/resources/extensions/gsd/visualizer-overlay.ts +3 -3
  255. package/src/resources/extensions/shared/format-utils.ts +1 -1
  256. package/src/resources/extensions/subagent/worker-registry.ts +2 -1
  257. package/dist/web/standalone/.next/static/chunks/4024.87fd909ae0110f50.js +0 -9
  258. package/dist/web/standalone/.next/static/chunks/app/page-fbecd1237e2d6d1f.js +0 -1
  259. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  260. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  261. /package/dist/web/standalone/.next/static/{KixbEdSRlU9zzYdZdrJ7A → k92jvAf8IfV4dZE3nnrAr}/_buildManifest.js +0 -0
  262. /package/dist/web/standalone/.next/static/{KixbEdSRlU9zzYdZdrJ7A → k92jvAf8IfV4dZE3nnrAr}/_ssgManifest.js +0 -0
@@ -22,6 +22,8 @@ import { logWarning } from "./workflow-logger.js";
22
22
  import { loadEffectiveGSDPreferences } from "./preferences.js";
23
23
  import { nativeGetCurrentBranch, nativeDetectMainBranch, nativeWorkingTreeStatus, nativeAddAllWithExclusions, nativeCommit, nativeCheckoutBranch, nativeMergeSquash, nativeConflictFiles, nativeCheckoutTheirs, nativeAddPaths, nativeRmForce, nativeBranchDelete, nativeBranchExists, nativeDiffNumstat, nativeUpdateRef, nativeIsAncestor, } from "./native-git-bridge.js";
24
24
  const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
25
+ const PROJECT_PREFERENCES_FILE = "PREFERENCES.md";
26
+ const LEGACY_PROJECT_PREFERENCES_FILE = "preferences.md";
25
27
  // ─── Shared Constants & Helpers ─────────────────────────────────────────────
26
28
  /**
27
29
  * Root-level .gsd/ state files synced between worktree and project root.
@@ -37,7 +39,7 @@ const ROOT_STATE_FILES = [
37
39
  "QUEUE.md",
38
40
  "completed-units.json",
39
41
  "metrics.json",
40
- // NOTE: preferences.md is intentionally NOT in ROOT_STATE_FILES.
42
+ // NOTE: project preferences are intentionally NOT in ROOT_STATE_FILES.
41
43
  // Forward-sync (main → worktree) is handled explicitly in syncGsdStateToWorktree().
42
44
  // Back-sync (worktree → main) must NEVER overwrite the project root's copy
43
45
  // because the project root is authoritative for preferences (#2684).
@@ -134,6 +136,11 @@ export function syncProjectRootToWorktree(projectRoot, worktreePath_, milestoneI
134
136
  return;
135
137
  const prGsd = join(projectRoot, ".gsd");
136
138
  const wtGsd = join(worktreePath_, ".gsd");
139
+ // When .gsd is a symlink to the same external directory in both locations,
140
+ // cpSync rejects the copy because source === destination (ERR_FS_CP_EINVAL).
141
+ // Compare realpaths and skip when they resolve to the same physical path (#2184).
142
+ if (isSamePath(prGsd, wtGsd))
143
+ return;
137
144
  // Copy milestone directory from project root to worktree — additive only.
138
145
  // force:false prevents cpSync from overwriting existing worktree files.
139
146
  // Without this, worktree-authoritative files (e.g. VALIDATION.md written
@@ -169,6 +176,11 @@ export function syncStateToProjectRoot(worktreePath_, projectRoot, milestoneId)
169
176
  return;
170
177
  const wtGsd = join(worktreePath_, ".gsd");
171
178
  const prGsd = join(projectRoot, ".gsd");
179
+ // When .gsd is a symlink to the same external directory in both locations,
180
+ // cpSync rejects the copy because source === destination (ERR_FS_CP_EINVAL).
181
+ // Compare realpaths and skip when they resolve to the same physical path (#2184).
182
+ if (isSamePath(wtGsd, prGsd))
183
+ return;
172
184
  // 1. STATE.md — the quick-glance status used by initial deriveState()
173
185
  safeCopy(join(wtGsd, "STATE.md"), join(prGsd, "STATE.md"), { force: true });
174
186
  // 2. Milestone directory — ROADMAP, slice PLANs, task summaries
@@ -338,19 +350,26 @@ export function syncGsdStateToWorktree(mainBasePath, worktreePath_) {
338
350
  }
339
351
  }
340
352
  }
341
- // Forward-sync preferences.md from project root to worktree (additive only).
342
- // NOT in ROOT_STATE_FILES because syncWorktreeStateBack() must never overwrite
343
- // the project root's preferences the project root is authoritative (#2684).
353
+ // Forward-sync project preferences from project root to worktree (additive only).
354
+ // Prefer the canonical uppercase file name, but keep the legacy lowercase
355
+ // fallback so older repos still work on case-sensitive filesystems.
344
356
  {
345
- const src = join(mainGsd, "preferences.md");
346
- const dst = join(wtGsd, "preferences.md");
347
- if (existsSync(src) && !existsSync(dst)) {
348
- try {
349
- cpSync(src, dst);
350
- synced.push("preferences.md");
351
- }
352
- catch {
353
- /* non-fatal */
357
+ const worktreeHasPreferences = existsSync(join(wtGsd, PROJECT_PREFERENCES_FILE))
358
+ || existsSync(join(wtGsd, LEGACY_PROJECT_PREFERENCES_FILE));
359
+ if (!worktreeHasPreferences) {
360
+ for (const file of [PROJECT_PREFERENCES_FILE, LEGACY_PROJECT_PREFERENCES_FILE]) {
361
+ const src = join(mainGsd, file);
362
+ const dst = join(wtGsd, file);
363
+ if (existsSync(src)) {
364
+ try {
365
+ cpSync(src, dst);
366
+ synced.push(file);
367
+ }
368
+ catch {
369
+ /* non-fatal */
370
+ }
371
+ break;
372
+ }
354
373
  }
355
374
  }
356
375
  }
@@ -836,10 +855,16 @@ function copyPlanningArtifacts(srcBase, wtPath) {
836
855
  "STATE.md",
837
856
  "KNOWLEDGE.md",
838
857
  "OVERRIDES.md",
839
- "preferences.md",
840
858
  ]) {
841
859
  safeCopy(join(srcGsd, file), join(dstGsd, file), { force: true });
842
860
  }
861
+ // Seed canonical PREFERENCES.md when available; fall back to legacy lowercase.
862
+ if (existsSync(join(srcGsd, PROJECT_PREFERENCES_FILE))) {
863
+ safeCopy(join(srcGsd, PROJECT_PREFERENCES_FILE), join(dstGsd, PROJECT_PREFERENCES_FILE), { force: true });
864
+ }
865
+ else if (existsSync(join(srcGsd, LEGACY_PROJECT_PREFERENCES_FILE))) {
866
+ safeCopy(join(srcGsd, LEGACY_PROJECT_PREFERENCES_FILE), join(dstGsd, LEGACY_PROJECT_PREFERENCES_FILE), { force: true });
867
+ }
843
868
  // Shared WAL (R012): worktrees use the project root's DB directly.
844
869
  // No longer copy gsd.db into the worktree — the DB path resolver in
845
870
  // ensureDbOpen() detects the worktree location and opens the root DB.
@@ -5,6 +5,7 @@ import { pauseAutoForProviderError } from "../provider-error-pause.js";
5
5
  import { isSessionSwitchInFlight, resolveAgentEnd } from "../auto-loop.js";
6
6
  import { resolveModelId } from "../auto-model-selection.js";
7
7
  import { clearDiscussionFlowState } from "./write-gate.js";
8
+ import { resumeAutoAfterProviderDelay } from "./provider-error-resume.js";
8
9
  import { classifyError, createRetryState, resetRetryState, isTransient, } from "../error-classifier.js";
9
10
  const retryState = createRetryState();
10
11
  const MAX_NETWORK_RETRIES = 2;
@@ -28,7 +29,10 @@ async function pauseTransientWithBackoff(cls, pi, ctx, errorDetail, isRateLimit)
28
29
  retryAfterMs,
29
30
  resume: allowAutoResume
30
31
  ? () => {
31
- pi.sendMessage({ customType: "gsd-auto-timeout-recovery", content: "Continue execution — provider error recovery delay elapsed.", display: false }, { triggerTurn: true });
32
+ void resumeAutoAfterProviderDelay(pi, ctx).catch((err) => {
33
+ const message = err instanceof Error ? err.message : String(err);
34
+ ctx.ui.notify(`Provider error recovery delay elapsed, but auto-mode failed to resume: ${message}`, "error");
35
+ });
32
36
  }
33
37
  : undefined,
34
38
  });
@@ -0,0 +1,18 @@
1
+ import { getAutoDashboardData, startAuto } from "../auto.js";
2
+ const defaultDeps = {
3
+ getSnapshot: () => getAutoDashboardData(),
4
+ startAuto,
5
+ };
6
+ export async function resumeAutoAfterProviderDelay(pi, ctx, deps = defaultDeps) {
7
+ const snapshot = deps.getSnapshot();
8
+ if (snapshot.active)
9
+ return "already-active";
10
+ if (!snapshot.paused)
11
+ return "not-paused";
12
+ if (!snapshot.basePath) {
13
+ ctx.ui.notify("Provider error recovery delay elapsed, but no paused auto-mode base path was available. Leaving auto-mode paused.", "warning");
14
+ return "missing-base";
15
+ }
16
+ await deps.startAuto(ctx, pi, snapshot.basePath, false, { step: snapshot.stepMode });
17
+ return "resumed";
18
+ }
@@ -6,14 +6,27 @@ import { registerDynamicTools } from "./dynamic-tools.js";
6
6
  import { registerJournalTools } from "./journal-tools.js";
7
7
  import { registerHooks } from "./register-hooks.js";
8
8
  import { registerShortcuts } from "./register-shortcuts.js";
9
+ export function handleRecoverableExtensionProcessError(err) {
10
+ if (err.code === "EPIPE") {
11
+ process.exit(0);
12
+ }
13
+ if (err.code === "ENOENT") {
14
+ const syscall = err.syscall;
15
+ if (syscall?.startsWith("spawn")) {
16
+ process.stderr.write(`[gsd] spawn ENOENT: ${err.path ?? "unknown"} — command not found\n`);
17
+ return true;
18
+ }
19
+ if (syscall === "uv_cwd") {
20
+ process.stderr.write(`[gsd] ENOENT (${syscall}): ${err.message}\n`);
21
+ return true;
22
+ }
23
+ }
24
+ return false;
25
+ }
9
26
  function installEpipeGuard() {
10
27
  if (!process.listeners("uncaughtException").some((listener) => listener.name === "_gsdEpipeGuard")) {
11
28
  const _gsdEpipeGuard = (err) => {
12
- if (err.code === "EPIPE") {
13
- process.exit(0);
14
- }
15
- if (err.code === "ENOENT" && err.syscall?.startsWith("spawn")) {
16
- process.stderr.write(`[gsd] spawn ENOENT: ${err.path ?? "unknown"} — command not found\n`);
29
+ if (handleRecoverableExtensionProcessError(err)) {
17
30
  return;
18
31
  }
19
32
  throw err;
@@ -2,6 +2,7 @@ import { existsSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { Key } from "@gsd/pi-tui";
4
4
  import { GSDDashboardOverlay } from "../dashboard-overlay.js";
5
+ import { ParallelMonitorOverlay } from "../parallel-monitor-overlay.js";
5
6
  import { shortcutDesc } from "../../shared/mod.js";
6
7
  export function registerShortcuts(pi) {
7
8
  pi.registerShortcut(Key.ctrlAlt("g"), {
@@ -22,4 +23,23 @@ export function registerShortcuts(pi) {
22
23
  });
23
24
  },
24
25
  });
26
+ pi.registerShortcut(Key.ctrlAlt("p"), {
27
+ description: shortcutDesc("Open parallel worker monitor", "/gsd parallel watch"),
28
+ handler: async (ctx) => {
29
+ const parallelDir = join(process.cwd(), ".gsd", "parallel");
30
+ if (!existsSync(parallelDir)) {
31
+ ctx.ui.notify("No parallel workers found. Run /gsd parallel start first.", "info");
32
+ return;
33
+ }
34
+ await ctx.ui.custom((tui, theme, _kb, done) => new ParallelMonitorOverlay(tui, theme, () => done()), {
35
+ overlay: true,
36
+ overlayOptions: {
37
+ width: "90%",
38
+ minWidth: 80,
39
+ maxHeight: "92%",
40
+ anchor: "center",
41
+ },
42
+ });
43
+ },
44
+ });
25
45
  }
@@ -47,7 +47,7 @@ export const TOP_LEVEL_SUBCOMMANDS = [
47
47
  { cmd: "inspect", desc: "Show SQLite DB diagnostics" },
48
48
  { cmd: "knowledge", desc: "Add persistent project knowledge (rule, pattern, or lesson)" },
49
49
  { cmd: "new-milestone", desc: "Create a milestone from a specification document (headless)" },
50
- { cmd: "parallel", desc: "Parallel milestone orchestration (start, status, stop, merge)" },
50
+ { cmd: "parallel", desc: "Parallel milestone orchestration (start, status, stop, merge, watch)" },
51
51
  { cmd: "cmux", desc: "Manage cmux integration (status, sidebar, notifications, splits)" },
52
52
  { cmd: "park", desc: "Park a milestone — skip without deleting" },
53
53
  { cmd: "unpark", desc: "Reactivate a parked milestone" },
@@ -87,6 +87,7 @@ const NESTED_COMPLETIONS = {
87
87
  { cmd: "pause", desc: "Pause a specific worker" },
88
88
  { cmd: "resume", desc: "Resume a paused worker" },
89
89
  { cmd: "merge", desc: "Merge completed milestone branches" },
90
+ { cmd: "watch", desc: "Live TUI dashboard monitoring all workers" },
90
91
  ],
91
92
  setup: [
92
93
  { cmd: "llm", desc: "Configure LLM provider settings" },
@@ -87,6 +87,20 @@ export async function handleParallelCommand(trimmed, _ctx, pi) {
87
87
  emitParallelMessage(pi, formatMergeResults(results));
88
88
  return true;
89
89
  }
90
- emitParallelMessage(pi, `Unknown parallel subcommand "${subcommand}". Usage: /gsd parallel [start|status|stop|pause|resume|merge]`);
90
+ if (subcommand === "watch") {
91
+ const root = projectRoot();
92
+ const { ParallelMonitorOverlay } = await import("../../parallel-monitor-overlay.js");
93
+ await _ctx.ui.custom((tui, theme, _kb, done) => new ParallelMonitorOverlay(tui, theme, () => done(), root), {
94
+ overlay: true,
95
+ overlayOptions: {
96
+ width: "90%",
97
+ minWidth: 80,
98
+ maxHeight: "92%",
99
+ anchor: "center",
100
+ },
101
+ });
102
+ return true;
103
+ }
104
+ emitParallelMessage(pi, `Unknown parallel subcommand "${subcommand}". Usage: /gsd parallel [start|status|stop|pause|resume|merge|watch]`);
91
105
  return true;
92
106
  }
@@ -13,9 +13,9 @@ import { readFileSync, unlinkSync, existsSync } from "node:fs";
13
13
  import { join } from "node:path";
14
14
  import { gsdRoot } from "./paths.js";
15
15
  import { atomicWriteSync } from "./atomic-write.js";
16
- const LOCK_FILE = "auto.lock";
16
+ import { effectiveLockFile } from "./session-lock.js";
17
17
  function lockPath(basePath) {
18
- return join(gsdRoot(basePath), LOCK_FILE);
18
+ return join(gsdRoot(basePath), effectiveLockFile());
19
19
  }
20
20
  /** Write or update the lock file with current auto-mode state. */
21
21
  export function writeLock(basePath, unitType, unitId, sessionFile) {
@@ -0,0 +1,413 @@
1
+ /**
2
+ * GSD Parallel Monitor Overlay
3
+ *
4
+ * Full-screen TUI overlay showing real-time parallel worker progress.
5
+ * Opened via `/gsd parallel watch` or Ctrl+Alt+P.
6
+ * Reads the same data sources as `scripts/parallel-monitor.mjs` but
7
+ * renders as a native pi-tui overlay with theme integration.
8
+ */
9
+ import { existsSync, statSync, readFileSync, openSync, readSync, closeSync, readdirSync } from "node:fs";
10
+ import { join } from "node:path";
11
+ import { spawnSync } from "node:child_process";
12
+ import { matchesKey, Key } from "@gsd/pi-tui";
13
+ import { formatDuration } from "../shared/mod.js";
14
+ // ─── Data Helpers ─────────────────────────────────────────────────────────
15
+ function readJsonSafe(filePath) {
16
+ try {
17
+ return JSON.parse(readFileSync(filePath, "utf-8"));
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ }
23
+ function isPidAlive(pid) {
24
+ try {
25
+ process.kill(pid, 0);
26
+ return true;
27
+ }
28
+ catch {
29
+ return false;
30
+ }
31
+ }
32
+ function tailRead(filePath, maxBytes) {
33
+ try {
34
+ const stat = statSync(filePath);
35
+ const readSize = Math.min(stat.size, maxBytes);
36
+ const fd = openSync(filePath, "r");
37
+ const buf = Buffer.alloc(readSize);
38
+ readSync(fd, buf, 0, readSize, Math.max(0, stat.size - readSize));
39
+ closeSync(fd);
40
+ return buf.toString("utf-8");
41
+ }
42
+ catch {
43
+ return "";
44
+ }
45
+ }
46
+ function discoverWorkers(basePath) {
47
+ const parallelDir = join(basePath, ".gsd", "parallel");
48
+ const worktreeDir = join(basePath, ".gsd", "worktrees");
49
+ const mids = new Set();
50
+ if (existsSync(parallelDir)) {
51
+ try {
52
+ for (const f of readdirSync(parallelDir)) {
53
+ if (f.endsWith(".status.json"))
54
+ mids.add(f.replace(".status.json", ""));
55
+ const m = f.match(/^(M\d+)\.(stderr|stdout)\.log$/);
56
+ if (m)
57
+ mids.add(m[1]);
58
+ }
59
+ }
60
+ catch { /* skip */ }
61
+ }
62
+ if (existsSync(worktreeDir)) {
63
+ try {
64
+ for (const d of readdirSync(worktreeDir)) {
65
+ if (d.startsWith("M") && existsSync(join(worktreeDir, d, ".gsd", "auto.lock"))) {
66
+ mids.add(d);
67
+ }
68
+ }
69
+ }
70
+ catch { /* skip */ }
71
+ }
72
+ return [...mids].sort();
73
+ }
74
+ function querySliceProgress(basePath, mid) {
75
+ const dbPath = join(basePath, ".gsd", "worktrees", mid, ".gsd", "gsd.db");
76
+ if (!existsSync(dbPath))
77
+ return [];
78
+ try {
79
+ const sql = `SELECT s.id, s.status, COUNT(t.id), SUM(CASE WHEN t.status='complete' THEN 1 ELSE 0 END) FROM slices s LEFT JOIN tasks t ON s.milestone_id=t.milestone_id AND s.id=t.slice_id WHERE s.milestone_id='${mid}' GROUP BY s.id ORDER BY s.id`;
80
+ const result = spawnSync("sqlite3", [dbPath, sql], { timeout: 3000, encoding: "utf-8" });
81
+ const out = (result.stdout || "").trim();
82
+ if (!out || result.status !== 0)
83
+ return [];
84
+ return out.split("\n").map((line) => {
85
+ const [id, status, total, done] = line.split("|");
86
+ return { id, status, total: parseInt(total, 10), done: parseInt(done || "0", 10) };
87
+ });
88
+ }
89
+ catch {
90
+ return [];
91
+ }
92
+ }
93
+ function extractCostFromNdjson(basePath, mid) {
94
+ const stdoutPath = join(basePath, ".gsd", "parallel", `${mid}.stdout.log`);
95
+ if (!existsSync(stdoutPath))
96
+ return 0;
97
+ try {
98
+ const content = readFileSync(stdoutPath, "utf-8");
99
+ let total = 0;
100
+ for (const line of content.split("\n")) {
101
+ if (!line.includes("message_end"))
102
+ continue;
103
+ try {
104
+ const obj = JSON.parse(line);
105
+ if (obj.type === "message_end") {
106
+ const cost = obj.message?.usage?.cost?.total;
107
+ if (typeof cost === "number")
108
+ total += cost;
109
+ }
110
+ }
111
+ catch { /* skip */ }
112
+ }
113
+ return total;
114
+ }
115
+ catch {
116
+ return 0;
117
+ }
118
+ }
119
+ function queryRecentCompletions(basePath, mid) {
120
+ const dbPath = join(basePath, ".gsd", "worktrees", mid, ".gsd", "gsd.db");
121
+ if (!existsSync(dbPath))
122
+ return [];
123
+ try {
124
+ const sql = `SELECT id, slice_id, one_liner FROM tasks WHERE milestone_id='${mid}' AND status='complete' AND completed_at IS NOT NULL ORDER BY completed_at DESC LIMIT 5`;
125
+ const result = spawnSync("sqlite3", [dbPath, sql], { timeout: 3000, encoding: "utf-8" });
126
+ const out = (result.stdout || "").trim();
127
+ if (!out || result.status !== 0)
128
+ return [];
129
+ return out.split("\n").map((line) => {
130
+ const [taskId, sliceId, oneLiner] = line.split("|");
131
+ return `✓ ${mid}/${sliceId}/${taskId}${oneLiner ? ": " + oneLiner : ""}`;
132
+ });
133
+ }
134
+ catch {
135
+ return [];
136
+ }
137
+ }
138
+ function collectWorkerData(basePath) {
139
+ const mids = discoverWorkers(basePath);
140
+ const parallelDir = join(basePath, ".gsd", "parallel");
141
+ const workers = [];
142
+ for (const mid of mids) {
143
+ const status = readJsonSafe(join(parallelDir, `${mid}.status.json`));
144
+ const lock = readJsonSafe(join(basePath, ".gsd", "worktrees", mid, ".gsd", "auto.lock"));
145
+ const slices = querySliceProgress(basePath, mid);
146
+ const pid = lock?.pid || status?.pid || 0;
147
+ const alive = pid ? isPidAlive(pid) : false;
148
+ // Heartbeat: prefer status.json if PID matches, else use file mtime
149
+ let heartbeatAge = Infinity;
150
+ const statusPidMatches = status?.pid === pid && status?.lastHeartbeat;
151
+ if (statusPidMatches) {
152
+ heartbeatAge = Date.now() - status.lastHeartbeat;
153
+ }
154
+ else {
155
+ const mtimes = [];
156
+ const stdoutLog = join(parallelDir, `${mid}.stdout.log`);
157
+ const stderrLog = join(parallelDir, `${mid}.stderr.log`);
158
+ if (existsSync(stdoutLog))
159
+ mtimes.push(statSync(stdoutLog).mtimeMs);
160
+ if (existsSync(stderrLog))
161
+ mtimes.push(statSync(stderrLog).mtimeMs);
162
+ if (lock?.unitStartedAt)
163
+ mtimes.push(new Date(lock.unitStartedAt).getTime());
164
+ if (mtimes.length > 0)
165
+ heartbeatAge = Date.now() - Math.max(...mtimes);
166
+ }
167
+ let cost = status?.cost || 0;
168
+ if (cost === 0)
169
+ cost = extractCostFromNdjson(basePath, mid);
170
+ const totalTasks = slices.reduce((sum, s) => sum + s.total, 0);
171
+ const doneTasks = slices.reduce((sum, s) => sum + s.done, 0);
172
+ const doneSlices = slices.filter((s) => s.status === "complete").length;
173
+ const elapsed = status?.startedAt
174
+ ? Date.now() - status.startedAt
175
+ : lock?.startedAt
176
+ ? Date.now() - new Date(lock.startedAt).getTime()
177
+ : 0;
178
+ // Errors from stderr (last 4KB, only new content)
179
+ const errors = [];
180
+ const stderrLog = join(parallelDir, `${mid}.stderr.log`);
181
+ if (existsSync(stderrLog)) {
182
+ const content = tailRead(stderrLog, 4096);
183
+ for (const line of content.trim().split("\n").slice(-5)) {
184
+ if (line.includes("error") || line.includes("Error") || line.includes("exited")) {
185
+ errors.push(line.trim());
186
+ }
187
+ }
188
+ }
189
+ workers.push({
190
+ mid,
191
+ pid,
192
+ alive,
193
+ state: alive ? "running" : (status?.state || "dead"),
194
+ cost,
195
+ heartbeatAge,
196
+ currentUnit: lock?.unitId || null,
197
+ unitType: lock?.unitType || null,
198
+ unitElapsed: lock?.unitStartedAt ? Date.now() - new Date(lock.unitStartedAt).getTime() : 0,
199
+ elapsed,
200
+ totalTasks,
201
+ doneTasks,
202
+ totalSlices: slices.length,
203
+ doneSlices,
204
+ slices,
205
+ errors,
206
+ });
207
+ }
208
+ return workers;
209
+ }
210
+ // ─── Rendering Helpers ────────────────────────────────────────────────────
211
+ function unitTypeLabel(unitType) {
212
+ const labels = {
213
+ "execute-task": "EXEC",
214
+ "research-slice": "RSRCH",
215
+ "plan-slice": "PLAN",
216
+ "complete-slice": "DONE",
217
+ "complete-task": "DONE",
218
+ "reassess": "ASSESS",
219
+ "validate": "VALID",
220
+ "reassess-roadmap": "ASSESS",
221
+ };
222
+ return labels[unitType || ""] || (unitType || "---").toUpperCase().slice(0, 5);
223
+ }
224
+ function progressBar(done, total, width) {
225
+ if (total === 0)
226
+ return "░".repeat(width);
227
+ const filled = Math.round((done / total) * width);
228
+ return "█".repeat(filled) + "░".repeat(width - filled);
229
+ }
230
+ function healthGlyph(alive, heartbeatAge) {
231
+ if (!alive)
232
+ return "○";
233
+ return "●";
234
+ }
235
+ // ─── Overlay Class ────────────────────────────────────────────────────────
236
+ export class ParallelMonitorOverlay {
237
+ tui;
238
+ theme;
239
+ onClose;
240
+ basePath;
241
+ refreshTimer;
242
+ workers = [];
243
+ events = [];
244
+ cachedLines;
245
+ scrollOffset = 0;
246
+ disposed = false;
247
+ resizeHandler = null;
248
+ constructor(tui, theme, onClose, basePath) {
249
+ this.tui = tui;
250
+ this.theme = theme;
251
+ this.onClose = onClose;
252
+ this.basePath = basePath || process.cwd();
253
+ this.resizeHandler = () => {
254
+ if (this.disposed)
255
+ return;
256
+ this.invalidate();
257
+ this.tui.requestRender();
258
+ };
259
+ process.stdout.on("resize", this.resizeHandler);
260
+ this.refresh();
261
+ this.refreshTimer = setInterval(() => this.refresh(), 5000);
262
+ }
263
+ refresh() {
264
+ if (this.disposed)
265
+ return;
266
+ this.workers = collectWorkerData(this.basePath);
267
+ // Collect completion events
268
+ for (const wk of this.workers) {
269
+ const completions = queryRecentCompletions(this.basePath, wk.mid);
270
+ for (const evt of completions) {
271
+ if (!this.events.includes(evt))
272
+ this.events.push(evt);
273
+ }
274
+ }
275
+ this.events = this.events.slice(-10);
276
+ this.cachedLines = undefined;
277
+ this.tui.requestRender();
278
+ }
279
+ dispose() {
280
+ this.disposed = true;
281
+ clearInterval(this.refreshTimer);
282
+ if (this.resizeHandler) {
283
+ process.stdout.removeListener("resize", this.resizeHandler);
284
+ this.resizeHandler = null;
285
+ }
286
+ }
287
+ handleInput(data) {
288
+ if (matchesKey(data, Key.escape) || data === "q") {
289
+ this.dispose();
290
+ this.onClose();
291
+ return;
292
+ }
293
+ if (matchesKey(data, Key.down) || data === "j") {
294
+ this.scrollOffset++;
295
+ this.invalidate();
296
+ this.tui.requestRender();
297
+ return;
298
+ }
299
+ if (matchesKey(data, Key.up) || data === "k") {
300
+ this.scrollOffset = Math.max(0, this.scrollOffset - 1);
301
+ this.invalidate();
302
+ this.tui.requestRender();
303
+ return;
304
+ }
305
+ }
306
+ invalidate() {
307
+ this.cachedLines = undefined;
308
+ }
309
+ render(width) {
310
+ if (this.cachedLines)
311
+ return this.cachedLines;
312
+ const t = this.theme;
313
+ const lines = [];
314
+ const w = Math.max(width, 60);
315
+ // Header
316
+ const totalCost = this.workers.reduce((s, wk) => s + wk.cost, 0);
317
+ const aliveCount = this.workers.filter((wk) => wk.alive).length;
318
+ const now = new Date().toLocaleTimeString();
319
+ lines.push(t.bold(t.fg("accent", " GSD Parallel Monitor ")));
320
+ lines.push(t.fg("muted", ` ${now} │ ${aliveCount}/${this.workers.length} alive │ Total: `) +
321
+ t.bold(`$${totalCost.toFixed(2)}`) +
322
+ t.fg("muted", " │ 5s refresh"));
323
+ lines.push(t.fg("muted", "─".repeat(w)));
324
+ if (this.workers.length === 0) {
325
+ lines.push("");
326
+ lines.push(t.fg("warning", " No parallel workers found."));
327
+ lines.push(t.fg("muted", " Run /gsd parallel start to begin."));
328
+ }
329
+ else {
330
+ for (const wk of this.workers) {
331
+ lines.push("");
332
+ // Health + ID + state
333
+ const healthColor = wk.alive ? "success" : "error";
334
+ const glyph = healthGlyph(wk.alive, wk.heartbeatAge);
335
+ const stateText = wk.alive
336
+ ? t.fg("success", "RUNNING")
337
+ : t.fg("error", t.bold("DEAD"));
338
+ const heartbeatText = wk.heartbeatAge === Infinity
339
+ ? "never"
340
+ : formatDuration(wk.heartbeatAge) + " ago";
341
+ lines.push(` ${t.fg(healthColor, glyph)} ${t.bold(wk.mid)} ${stateText} ` +
342
+ t.fg("muted", `PID ${wk.pid} │ elapsed ${formatDuration(wk.elapsed)} │ `) +
343
+ `cost ${t.bold("$" + wk.cost.toFixed(2))} ` +
344
+ t.fg("muted", "│ heartbeat ") + t.fg(healthColor, heartbeatText));
345
+ // Current unit
346
+ if (wk.currentUnit) {
347
+ const phaseColor = wk.unitType === "execute-task" ? "accent"
348
+ : wk.unitType === "research-slice" ? "warning"
349
+ : wk.unitType?.includes("complete") ? "success"
350
+ : "text";
351
+ lines.push(` ${t.fg("muted", "▸")} ${t.fg(phaseColor, unitTypeLabel(wk.unitType))} ${wk.currentUnit} ` +
352
+ t.fg("muted", `(${formatDuration(wk.unitElapsed)})`));
353
+ }
354
+ else if (!wk.alive) {
355
+ lines.push(` ${t.fg("muted", "▸")} ${t.fg("error", "stopped")}`);
356
+ }
357
+ else {
358
+ lines.push(` ${t.fg("muted", "▸ idle / between units")}`);
359
+ }
360
+ // Slice progress chips
361
+ if (wk.slices.length > 0) {
362
+ const chips = wk.slices.map((s) => {
363
+ const pct = s.total > 0 ? s.done / s.total : 0;
364
+ const color = s.status === "complete" ? "success" : pct > 0 ? "warning" : "muted";
365
+ return t.fg(color, `${s.id}:${s.done}/${s.total}`);
366
+ });
367
+ lines.push(` ${t.fg("muted", "slices")} ${chips.join(" ")}`);
368
+ // Task progress bar
369
+ const bar = progressBar(wk.doneTasks, wk.totalTasks, 25);
370
+ const pct = wk.totalTasks > 0 ? Math.round((wk.doneTasks / wk.totalTasks) * 100) : 0;
371
+ lines.push(` ${t.fg("muted", "tasks")} ${t.fg("success", bar)} ${wk.doneTasks}/${wk.totalTasks} ` +
372
+ t.fg("muted", `(${pct}%) │ slices done ${wk.doneSlices}/${wk.totalSlices}`));
373
+ }
374
+ // Errors
375
+ for (const err of wk.errors.slice(-2)) {
376
+ const truncated = err.length > w - 10 ? err.slice(0, w - 11) + "…" : err;
377
+ lines.push(` ${t.fg("error", "⚠ " + truncated)}`);
378
+ }
379
+ }
380
+ }
381
+ // Event feed
382
+ lines.push("");
383
+ lines.push(t.fg("muted", "─".repeat(w)));
384
+ lines.push(` ${t.bold("Recent Events")}`);
385
+ if (this.events.length === 0) {
386
+ lines.push(t.fg("muted", " No events yet..."));
387
+ }
388
+ else {
389
+ for (const evt of this.events.slice(-8)) {
390
+ const mid = evt.match(/^✓ (M\d+)\//)?.[1] || "";
391
+ const truncated = evt.length > w - 10 ? evt.slice(0, w - 11) + "…" : evt;
392
+ lines.push(` ${t.fg("muted", "│")} ${t.fg("accent", mid)} ${truncated.replace(/^✓ M\d+\//, "")}`);
393
+ }
394
+ }
395
+ // Footer
396
+ lines.push("");
397
+ const allDone = this.workers.length > 0 && this.workers.every((wk) => !wk.alive);
398
+ if (allDone) {
399
+ lines.push(t.bold(t.fg("success", " ALL WORKERS COMPLETE")));
400
+ for (const wk of this.workers) {
401
+ lines.push(` ${wk.mid} $${wk.cost.toFixed(2)} │ ${wk.doneSlices}/${wk.totalSlices} slices ` +
402
+ `${wk.doneTasks}/${wk.totalTasks} tasks │ ${formatDuration(wk.elapsed)}`);
403
+ }
404
+ lines.push(` ${t.bold("Total: $" + this.workers.reduce((s, wk) => s + wk.cost, 0).toFixed(2))}`);
405
+ }
406
+ lines.push(t.fg("muted", " ESC/q to close │ ↑↓ scroll"));
407
+ // Apply scroll — use terminal rows as height estimate
408
+ const termHeight = process.stdout.rows || 40;
409
+ const visible = lines.slice(this.scrollOffset, this.scrollOffset + termHeight);
410
+ this.cachedLines = visible;
411
+ return visible;
412
+ }
413
+ }