gsd-pi 2.52.0 → 2.53.0-dev.a67436f

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 (247) hide show
  1. package/README.md +55 -32
  2. package/dist/headless-query.js +1 -1
  3. package/dist/headless-ui.d.ts +2 -2
  4. package/dist/headless-ui.js +18 -15
  5. package/dist/headless.d.ts +11 -0
  6. package/dist/headless.js +178 -38
  7. package/dist/resources/extensions/get-secrets-from-user.js +7 -0
  8. package/dist/resources/extensions/gsd/auto/phases.js +28 -8
  9. package/dist/resources/extensions/gsd/auto-dispatch.js +5 -1
  10. package/dist/resources/extensions/gsd/auto-worktree.js +70 -14
  11. package/dist/resources/extensions/gsd/auto.js +22 -0
  12. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +4 -10
  13. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +3 -3
  14. package/dist/resources/extensions/gsd/docs/preferences-reference.md +2 -2
  15. package/dist/resources/extensions/gsd/git-service.js +4 -3
  16. package/dist/resources/extensions/gsd/guided-flow.js +4 -3
  17. package/dist/resources/extensions/gsd/markdown-renderer.js +5 -4
  18. package/dist/resources/extensions/gsd/parallel-orchestrator.js +18 -2
  19. package/dist/resources/extensions/gsd/preferences-types.js +1 -1
  20. package/dist/resources/extensions/gsd/state.js +18 -29
  21. package/dist/resources/extensions/gsd/status-guards.js +12 -0
  22. package/dist/resources/extensions/gsd/tools/complete-milestone.js +4 -3
  23. package/dist/resources/extensions/gsd/tools/complete-slice.js +4 -3
  24. package/dist/resources/extensions/gsd/tools/complete-task.js +4 -3
  25. package/dist/resources/extensions/gsd/tools/plan-milestone.js +4 -14
  26. package/dist/resources/extensions/gsd/tools/plan-slice.js +4 -14
  27. package/dist/resources/extensions/gsd/tools/plan-task.js +4 -14
  28. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +6 -7
  29. package/dist/resources/extensions/gsd/tools/reopen-slice.js +4 -3
  30. package/dist/resources/extensions/gsd/tools/reopen-task.js +5 -4
  31. package/dist/resources/extensions/gsd/tools/replan-slice.js +5 -6
  32. package/dist/resources/extensions/gsd/validation.js +21 -0
  33. package/dist/resources/extensions/shared/rtk.js +14 -4
  34. package/dist/rtk.js +3 -1
  35. package/dist/web/standalone/.next/BUILD_ID +1 -1
  36. package/dist/web/standalone/.next/app-path-routes-manifest.json +20 -20
  37. package/dist/web/standalone/.next/build-manifest.json +4 -4
  38. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  39. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  40. package/dist/web/standalone/.next/required-server-files.json +3 -3
  41. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  42. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  43. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  44. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  52. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  53. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  54. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  55. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  56. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  58. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  62. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  63. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  64. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  65. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  66. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  68. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  69. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  70. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  72. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  74. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  76. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  78. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  79. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  80. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  108. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  114. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  128. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  130. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  132. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  134. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  137. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  143. package/dist/web/standalone/.next/server/app/index.html +1 -1
  144. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  145. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  146. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  147. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  148. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  149. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  150. package/dist/web/standalone/.next/server/app/page.js +2 -2
  151. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  152. package/dist/web/standalone/.next/server/app-paths-manifest.json +20 -20
  153. package/dist/web/standalone/.next/server/chunks/2229.js +1 -1
  154. package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
  155. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  156. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  157. package/dist/web/standalone/.next/server/middleware.js +2 -2
  158. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  159. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  160. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  161. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  162. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  163. package/dist/web/standalone/.next/static/chunks/4024.87fd909ae0110f50.js +9 -0
  164. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  165. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  166. package/dist/web/standalone/.next/static/chunks/app/page-b950e4e384cc62b3.js +1 -0
  167. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  168. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  169. package/dist/web/standalone/.next/static/chunks/{webpack-024d82be84800e52.js → webpack-bca0e732db0dcec3.js} +1 -1
  170. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  171. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  172. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  173. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  174. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  175. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  176. package/dist/web/standalone/server.js +1 -1
  177. package/package.json +1 -1
  178. package/packages/mcp-server/README.md +6 -6
  179. package/packages/mcp-server/package.json +14 -4
  180. package/packages/mcp-server/src/cli.ts +1 -1
  181. package/packages/mcp-server/src/index.ts +1 -1
  182. package/packages/mcp-server/src/mcp-server.test.ts +2 -2
  183. package/packages/mcp-server/src/session-manager.ts +2 -2
  184. package/packages/mcp-server/src/types.ts +1 -1
  185. package/packages/pi-coding-agent/package.json +1 -1
  186. package/packages/rpc-client/README.md +125 -0
  187. package/packages/rpc-client/examples/basic-usage.ts +13 -0
  188. package/packages/rpc-client/package.json +17 -3
  189. package/packages/rpc-client/src/index.ts +10 -0
  190. package/packages/rpc-client/src/jsonl.ts +64 -0
  191. package/packages/rpc-client/src/rpc-client.test.ts +568 -0
  192. package/packages/rpc-client/src/rpc-client.ts +666 -0
  193. package/packages/rpc-client/src/rpc-types.ts +399 -0
  194. package/packages/rpc-client/tsconfig.examples.json +17 -0
  195. package/packages/rpc-client/tsconfig.json +24 -0
  196. package/pkg/package.json +1 -1
  197. package/scripts/ensure-workspace-builds.cjs +36 -8
  198. package/src/resources/extensions/get-secrets-from-user.ts +8 -0
  199. package/src/resources/extensions/gsd/auto/phases.ts +38 -7
  200. package/src/resources/extensions/gsd/auto-dispatch.ts +6 -1
  201. package/src/resources/extensions/gsd/auto-worktree.ts +73 -14
  202. package/src/resources/extensions/gsd/auto.ts +21 -0
  203. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +4 -11
  204. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +3 -3
  205. package/src/resources/extensions/gsd/docs/preferences-reference.md +2 -2
  206. package/src/resources/extensions/gsd/git-service.ts +4 -3
  207. package/src/resources/extensions/gsd/guided-flow.ts +4 -3
  208. package/src/resources/extensions/gsd/markdown-renderer.ts +5 -4
  209. package/src/resources/extensions/gsd/parallel-orchestrator.ts +23 -1
  210. package/src/resources/extensions/gsd/preferences-types.ts +1 -1
  211. package/src/resources/extensions/gsd/state.ts +18 -29
  212. package/src/resources/extensions/gsd/status-guards.ts +13 -0
  213. package/src/resources/extensions/gsd/tests/active-milestone-id-guard.test.ts +91 -0
  214. package/src/resources/extensions/gsd/tests/auto-stale-lock-self-kill.test.ts +87 -0
  215. package/src/resources/extensions/gsd/tests/auto-worktree-auto-resolve.test.ts +80 -0
  216. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +1 -1
  217. package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +39 -0
  218. package/src/resources/extensions/gsd/tests/git-service.test.ts +64 -30
  219. package/src/resources/extensions/gsd/tests/milestone-report-path.test.ts +51 -0
  220. package/src/resources/extensions/gsd/tests/parallel-orchestrator-zombie-cleanup.test.ts +277 -0
  221. package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +103 -0
  222. package/src/resources/extensions/gsd/tests/preferences.test.ts +1 -1
  223. package/src/resources/extensions/gsd/tests/rate-limit-model-fallback.test.ts +90 -0
  224. package/src/resources/extensions/gsd/tests/session-lock-transient-read.test.ts +9 -8
  225. package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +125 -0
  226. package/src/resources/extensions/gsd/tests/status-guards.test.ts +30 -0
  227. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +12 -2
  228. package/src/resources/extensions/gsd/tests/validation-gate-patterns.test.ts +124 -0
  229. package/src/resources/extensions/gsd/tests/validation.test.ts +72 -0
  230. package/src/resources/extensions/gsd/tools/complete-milestone.ts +4 -3
  231. package/src/resources/extensions/gsd/tools/complete-slice.ts +4 -3
  232. package/src/resources/extensions/gsd/tools/complete-task.ts +4 -3
  233. package/src/resources/extensions/gsd/tools/plan-milestone.ts +4 -16
  234. package/src/resources/extensions/gsd/tools/plan-slice.ts +4 -16
  235. package/src/resources/extensions/gsd/tools/plan-task.ts +4 -16
  236. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +6 -7
  237. package/src/resources/extensions/gsd/tools/reopen-slice.ts +4 -3
  238. package/src/resources/extensions/gsd/tools/reopen-task.ts +5 -4
  239. package/src/resources/extensions/gsd/tools/replan-slice.ts +5 -7
  240. package/src/resources/extensions/gsd/validation.ts +23 -0
  241. package/src/resources/extensions/shared/rtk.ts +22 -4
  242. package/dist/web/standalone/.next/static/chunks/4024.21054f459af5cc78.js +0 -9
  243. package/dist/web/standalone/.next/static/chunks/app/page-fbecd1237e2d6d1f.js +0 -1
  244. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  245. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  246. /package/dist/web/standalone/.next/static/{vlgS2rkXjxeKhgXhdp4lh → YO-PWFRitlHM-L-dotlmm}/_buildManifest.js +0 -0
  247. /package/dist/web/standalone/.next/static/{vlgS2rkXjxeKhgXhdp4lh → YO-PWFRitlHM-L-dotlmm}/_ssgManifest.js +0 -0
package/README.md CHANGED
@@ -27,55 +27,78 @@ One command. Walk away. Come back to a built project with clean git history.
27
27
 
28
28
  ---
29
29
 
30
- ## What's New in v2.46.0
30
+ ## What's New in v2.52.0
31
31
 
32
- ### Single-Writer State Engine
32
+ ### VS Code Extension & Web UI
33
33
 
34
- The biggest architectural change since DB-backed planning tools. The single-writer engine enforces disciplined state transitions through three iterations:
34
+ - **VS Code integration** status bar, file decorations, bash terminal, session tree, conversation history, and code lens. (#2651)
35
+ - **Dark mode contrast** — raised token floor and flattened opacity tier system for better readability. (#2734)
36
+ - **Auth token gate** — synthetic 401 on missing token, unauthenticated boot state, and recovery screen. (#2740)
35
37
 
36
- - **v2 discipline layer** — adds a write-side discipline layer on top of the DB architecture, ensuring all state mutations flow through controlled tool calls.
37
- - **v3 — state machine guards, actor identity, reversibility** — introduces formal state machine guards, tracks which actor (human vs agent) initiated each transition, and makes transitions reversible.
38
- - **Hardened** — closes TOCTOU race conditions, intercepts bypass attempts, and resolves status inconsistencies.
38
+ ### Capability Metadata & Model Routing
39
39
 
40
- All prompts are now aligned with the single-writer tool API, and a new **workflow-logger** is wired into the engine, tool, manifest, and reconcile paths for full observability. (#2494)
41
-
42
- ### v2.45.0 — New Commands and Capabilities
43
-
44
- - **`/gsd rethink`** — conversational project reorganization. Rethink your milestone structure, slice decomposition, or overall approach through guided discussion. (#2459)
45
- - **`/gsd mcp`** — MCP server status and connectivity. Check which MCP servers are configured, connected, and healthy. (#2362)
46
- - **Complete offline mode** — GSD now works fully offline with local models. (#2429)
47
- - **Global KNOWLEDGE.md injection** — `~/.gsd/agent/KNOWLEDGE.md` is injected into the system prompt, so cross-project knowledge persists globally. (#2331)
48
- - **Mobile-responsive web UI** — the browser interface now works on phones and tablets. (#2354)
49
- - **DB tool previews** — `renderCall`/`renderResult` previews on DB tools show what each tool call does before and after execution. (#2273)
50
- - **Message timestamps** — user and assistant messages now include timestamps. (#2368)
40
+ - **Capability-based model selection** replaced model-ID pattern matching with capability metadata, making custom provider integration more reliable. (#2548)
51
41
 
52
42
  ### Key Changes
53
43
 
54
- - **Default isolation mode changed to `none`** `git.isolation` now defaults to `none` instead of `worktree`. Projects that rely on worktree isolation should set `git.isolation: worktree` explicitly in preferences. (#2481)
55
- - **Startup checks** — GSD now validates Node.js version and git availability at startup, with clear error messages. (#2463)
56
- - **Worktree lifecycle journaling** — worktree create, switch, merge, and remove events are recorded in the event journal. (#2486)
57
- - **Milestone verification gate** — milestone completion is blocked when verification fails, preventing premature closure. (#2500)
44
+ - **`--bare` mode**wired across headless, pi-coding-agent, and resource-loader for minimal-output operation.
45
+ - **RPC protocol v2** — new types, init handshake with version detection, and runId generation on prompt/steer/follow_up commands.
46
+ - **PREFERENCES.md rename** — `preferences.md` renamed to `PREFERENCES.md` for consistency. (#2700, #2738)
47
+ - **Comprehensive SQLite audit** — indexes, caching, safety, and reconciliation fixes across gsd-db.
48
+ - **Unified error classifier** — three overlapping error classifiers consolidated into a single classify-decide-act pipeline.
58
49
 
59
50
  ### Key Fixes
60
51
 
61
- - **Auto-mode stability** — recovery attempts reset on unit re-dispatch (#2424), survivor branch recovery handles `phase=complete` (#2427), and auto mode stops on real merge conflicts (#2428).
62
- - **Supervision timeouts** — now respect task `est:` annotations, so complex tasks get proportionally longer timeouts. (#2434)
63
- - **`auto_pr: true` fixed** — three interacting bugs prevented auto-PR creation; all three are resolved. (#2433)
64
- - **Rich task plan preservation** — plans survive DB roundtrip without losing structured content. (#2453)
65
- - **Artifact truncation prevention** — `saveArtifactToDb` no longer overwrites larger files with truncated content. (#2447)
66
- - **Worktree teardown** — submodule state is detected and preserved during teardown (#2425), and worktree merge back to main works after `stopAuto` on milestone completion (#2430).
67
- - **Windows portability** — `retentionDays=0` handling and CRLF fixes on Windows. (#2460)
68
- - **Voice on Linux** — misleading portaudio error on PEP 668 systems replaced with actionable guidance. (#2407)
52
+ - **Auto-mode stops on provider errors** — auto loop now halts after provider errors instead of retrying indefinitely. (#2762, #2764)
53
+ - **Transaction safety** — state machine guards moved inside transactions in 5 tool handlers (#2752), and `transaction()` made re-entrant.
54
+ - **Worktree seeding** — `preferences.md` seeded into auto-mode worktrees and included in worktree sync. (#2693)
55
+ - **Idle watchdog** — interactive tools exempted from stall detection (#2676), and filesystem activity no longer overrides stalled-tool detection. (#2697)
56
+ - **Milestone guards** — `allSlicesDone` guarded against vacuous truth on empty slice arrays (#2679), and `complete-milestone` dispatch blocked when validation is `needs-remediation`. (#2682)
57
+ - **Docker overhaul** — fragile setup replaced with proven container patterns. (#2716)
58
+ - **Windows** — EINVAL prevented by disabling detached process groups on Win32. (#2744)
59
+ - **Audit log** — `setLogBasePath` wired into engine init to resurrect audit logging. (#2745)
60
+
61
+ ### v2.51.0 — Skills, RTK, and Verification
62
+
63
+ - **`/terminal` command** — direct shell execution from the slash command interface. (#2349)
64
+ - **Managed RTK integration** — RTK binary auto-provisioned with opt-in preference and web UI toggle. (#2620)
65
+ - **Verification classes** — compliance checked before milestone completion, with classes injected into validation prompts. (#2621, #2623)
66
+ - **Skills overhaul** — 30+ new skill packs covering major frameworks, databases, and cloud platforms; curated catalog with `~/.agents/skills/` as primary directory.
67
+
68
+ ### v2.50.0 — Quality Gates
69
+
70
+ - **Quality gates** — 8-question quality gates added to planning and completion templates, with parallel evaluation via `evaluating-gates` phase.
71
+ - **Structured error propagation** — errors wired through `UnitResult` for better diagnostics.
72
+
73
+ ### v2.49.0 — Git Trailers & Yolo Mode
74
+
75
+ - **`--yolo` flag** — `/gsd auto --yolo` for non-interactive project init.
76
+ - **Git trailers** — GSD metadata moved from commit subject scopes to git trailers.
77
+
78
+ ### v2.48.0 — Forensics & Discussion
79
+
80
+ - **`/gsd discuss` for queued milestones** — target milestones still in the queue. (#2349)
81
+ - **Enhanced forensics** — journal and activity log awareness added to `/gsd forensics`.
82
+
83
+ ### v2.47.0 — External Providers
84
+
85
+ - **External tool execution mode** — `externalToolExecution` mode for external providers in agent-core.
86
+ - **Claude Code CLI provider** — new provider extension for Claude Code CLI. (#2382)
69
87
 
70
- ### Previous highlights (v2.42–v2.44)
88
+ ### Previous highlights (v2.42–v2.46)
71
89
 
90
+ - **Single-writer state engine** — disciplined state transitions with machine guards, actor identity, reversibility, and TOCTOU hardening. (#2494)
91
+ - **`/gsd rethink`** — conversational project reorganization. (#2459)
92
+ - **`/gsd mcp`** — MCP server status and connectivity. (#2362)
93
+ - **Complete offline mode** — fully offline with local models. (#2429)
94
+ - **Global KNOWLEDGE.md injection** — cross-project knowledge via `~/.gsd/agent/KNOWLEDGE.md`. (#2331)
95
+ - **Mobile-responsive web UI** — browser interface works on phones and tablets. (#2354)
96
+ - **Default isolation mode changed to `none`** — set `git.isolation: worktree` explicitly if needed. (#2481)
72
97
  - **Non-API-key provider extensions** — support for Claude Code CLI and similar providers. (#2382)
73
98
  - **Docker sandbox template** — official Docker template for isolated auto mode. (#2360)
74
99
  - **DB-backed planning tools** — write-side state transitions use atomic SQLite tool calls. (#2141)
75
100
  - **Declarative workflow engine** — YAML workflows through auto-loop. (#2024)
76
101
  - **`/gsd fast`** — toggle service tier for prioritized API routing. (#1862)
77
- - **Forensics dedup** — duplicate detection before issue creation. (#2105)
78
- - **Startup optimizations** — pre-compiled extensions, compile cache, batch discovery. (#2125)
79
102
 
80
103
  ---
81
104
 
@@ -36,7 +36,7 @@ export async function handleQuery(basePath) {
36
36
  const state = await deriveState(basePath);
37
37
  // Derive next dispatch action
38
38
  let next;
39
- if (!state.activeMilestone) {
39
+ if (!state.activeMilestone?.id) {
40
40
  next = {
41
41
  action: 'stop',
42
42
  reason: state.phase === 'complete' ? 'All milestones complete.' : state.nextAction,
@@ -18,6 +18,6 @@ interface ExtensionUIRequest {
18
18
  [key: string]: unknown;
19
19
  }
20
20
  export type { ExtensionUIRequest };
21
- export declare function handleExtensionUIRequest(event: ExtensionUIRequest, writeToStdin: (data: string) => void): void;
21
+ export declare function handleExtensionUIRequest(event: ExtensionUIRequest, client: RpcClient): void;
22
22
  export declare function formatProgress(event: Record<string, unknown>, verbose: boolean): string | null;
23
- export declare function startSupervisedStdinReader(stdinWriter: (data: string) => void, client: RpcClient, onResponse: (id: string) => void): () => void;
23
+ export declare function startSupervisedStdinReader(client: RpcClient, onResponse: (id: string) => void): () => void;
@@ -5,13 +5,12 @@
5
5
  * formats progress events for stderr output, and reads orchestrator
6
6
  * commands from stdin in supervised mode.
7
7
  */
8
- import { attachJsonlLineReader, serializeJsonLine } from '@gsd/pi-coding-agent';
8
+ import { attachJsonlLineReader } from '@gsd/pi-coding-agent';
9
9
  // ---------------------------------------------------------------------------
10
10
  // Extension UI Auto-Responder
11
11
  // ---------------------------------------------------------------------------
12
- export function handleExtensionUIRequest(event, writeToStdin) {
12
+ export function handleExtensionUIRequest(event, client) {
13
13
  const { id, method } = event;
14
- let response;
15
14
  switch (method) {
16
15
  case 'select': {
17
16
  // Lock-guard prompts list "View status" first, but headless needs "Force start"
@@ -23,31 +22,30 @@ export function handleExtensionUIRequest(event, writeToStdin) {
23
22
  if (forceOption)
24
23
  selected = forceOption;
25
24
  }
26
- response = { type: 'extension_ui_response', id, value: selected };
25
+ client.sendUIResponse(id, { value: selected });
27
26
  break;
28
27
  }
29
28
  case 'confirm':
30
- response = { type: 'extension_ui_response', id, confirmed: true };
29
+ client.sendUIResponse(id, { confirmed: true });
31
30
  break;
32
31
  case 'input':
33
- response = { type: 'extension_ui_response', id, value: '' };
32
+ client.sendUIResponse(id, { value: '' });
34
33
  break;
35
34
  case 'editor':
36
- response = { type: 'extension_ui_response', id, value: event.prefill ?? '' };
35
+ client.sendUIResponse(id, { value: event.prefill ?? '' });
37
36
  break;
38
37
  case 'notify':
39
38
  case 'setStatus':
40
39
  case 'setWidget':
41
40
  case 'setTitle':
42
41
  case 'set_editor_text':
43
- response = { type: 'extension_ui_response', id, value: '' };
42
+ client.sendUIResponse(id, { value: '' });
44
43
  break;
45
44
  default:
46
45
  process.stderr.write(`[headless] Warning: unknown extension_ui_request method "${method}", cancelling\n`);
47
- response = { type: 'extension_ui_response', id, cancelled: true };
46
+ client.sendUIResponse(id, { cancelled: true });
48
47
  break;
49
48
  }
50
- writeToStdin(serializeJsonLine(response));
51
49
  }
52
50
  // ---------------------------------------------------------------------------
53
51
  // Progress Formatter
@@ -78,7 +76,7 @@ export function formatProgress(event, verbose) {
78
76
  // ---------------------------------------------------------------------------
79
77
  // Supervised Stdin Reader
80
78
  // ---------------------------------------------------------------------------
81
- export function startSupervisedStdinReader(stdinWriter, client, onResponse) {
79
+ export function startSupervisedStdinReader(client, onResponse) {
82
80
  return attachJsonlLineReader(process.stdin, (line) => {
83
81
  let msg;
84
82
  try {
@@ -90,12 +88,17 @@ export function startSupervisedStdinReader(stdinWriter, client, onResponse) {
90
88
  }
91
89
  const type = String(msg.type ?? '');
92
90
  switch (type) {
93
- case 'extension_ui_response':
94
- stdinWriter(line + '\n');
95
- if (typeof msg.id === 'string') {
96
- onResponse(msg.id);
91
+ case 'extension_ui_response': {
92
+ const id = String(msg.id ?? '');
93
+ const value = msg.value !== undefined ? String(msg.value) : undefined;
94
+ const confirmed = typeof msg.confirmed === 'boolean' ? msg.confirmed : undefined;
95
+ const cancelled = typeof msg.cancelled === 'boolean' ? msg.cancelled : undefined;
96
+ client.sendUIResponse(id, { value, confirmed, cancelled });
97
+ if (id) {
98
+ onResponse(id);
97
99
  }
98
100
  break;
101
+ }
99
102
  case 'prompt':
100
103
  client.prompt(String(msg.message ?? ''));
101
104
  break;
@@ -11,6 +11,7 @@
11
11
  * 10 — blocked (command reported a blocker)
12
12
  * 11 — cancelled (SIGINT/SIGTERM received)
13
13
  */
14
+ import type { SessionInfo } from '@gsd/pi-coding-agent';
14
15
  import type { OutputFormat } from './headless-types.js';
15
16
  export interface HeadlessOptions {
16
17
  timeout: number;
@@ -31,5 +32,15 @@ export interface HeadlessOptions {
31
32
  resumeSession?: string;
32
33
  bare?: boolean;
33
34
  }
35
+ export interface ResumeSessionResult {
36
+ session?: SessionInfo;
37
+ error?: string;
38
+ }
39
+ /**
40
+ * Resolve a session prefix to a single session.
41
+ * Exact id match is preferred over prefix match.
42
+ * Returns `{ session }` on unique match or `{ error }` on 0/ambiguous matches.
43
+ */
44
+ export declare function resolveResumeSession(sessions: SessionInfo[], prefix: string): ResumeSessionResult;
34
45
  export declare function parseHeadlessArgs(argv: string[]): HeadlessOptions;
35
46
  export declare function runHeadless(options: HeadlessOptions): Promise<void>;
package/dist/headless.js CHANGED
@@ -14,12 +14,35 @@
14
14
  import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
15
15
  import { join } from 'node:path';
16
16
  import { resolve } from 'node:path';
17
- import { RpcClient } from '@gsd/pi-coding-agent';
17
+ import { RpcClient, SessionManager } from '@gsd/pi-coding-agent';
18
+ import { getProjectSessionsDir } from './project-sessions.js';
18
19
  import { loadAndValidateAnswerFile, AnswerInjector } from './headless-answers.js';
19
- import { isTerminalNotification, isBlockedNotification, isMilestoneReadyNotification, isQuickCommand, FIRE_AND_FORGET_METHODS, IDLE_TIMEOUT_MS, NEW_MILESTONE_IDLE_TIMEOUT_MS, EXIT_SUCCESS, EXIT_ERROR, EXIT_BLOCKED, EXIT_CANCELLED, } from './headless-events.js';
20
+ import { isTerminalNotification, isBlockedNotification, isMilestoneReadyNotification, isQuickCommand, FIRE_AND_FORGET_METHODS, IDLE_TIMEOUT_MS, NEW_MILESTONE_IDLE_TIMEOUT_MS, EXIT_SUCCESS, EXIT_ERROR, EXIT_BLOCKED, EXIT_CANCELLED, mapStatusToExitCode, } from './headless-events.js';
20
21
  import { VALID_OUTPUT_FORMATS } from './headless-types.js';
21
22
  import { handleExtensionUIRequest, formatProgress, startSupervisedStdinReader, } from './headless-ui.js';
22
23
  import { loadContext, bootstrapGsdProject, } from './headless-context.js';
24
+ /**
25
+ * Resolve a session prefix to a single session.
26
+ * Exact id match is preferred over prefix match.
27
+ * Returns `{ session }` on unique match or `{ error }` on 0/ambiguous matches.
28
+ */
29
+ export function resolveResumeSession(sessions, prefix) {
30
+ // Exact match takes priority
31
+ const exact = sessions.find(s => s.id === prefix);
32
+ if (exact) {
33
+ return { session: exact };
34
+ }
35
+ // Prefix match
36
+ const matches = sessions.filter(s => s.id.startsWith(prefix));
37
+ if (matches.length === 0) {
38
+ return { error: `No session matching '${prefix}' found` };
39
+ }
40
+ if (matches.length > 1) {
41
+ const list = matches.map(s => ` ${s.id}`).join('\n');
42
+ return { error: `Ambiguous session prefix '${prefix}' matches ${matches.length} sessions:\n${list}` };
43
+ }
44
+ return { session: matches[0] };
45
+ }
23
46
  // ---------------------------------------------------------------------------
24
47
  // CLI Argument Parser
25
48
  // ---------------------------------------------------------------------------
@@ -256,6 +279,39 @@ async function runHeadlessOnce(options, restartCount) {
256
279
  let exitCode = 0;
257
280
  let milestoneReady = false; // tracks "Milestone X ready." for auto-chaining
258
281
  const recentEvents = [];
282
+ // JSON batch mode: cost aggregation (cumulative-max pattern per K004)
283
+ let cumulativeCostUsd = 0;
284
+ let cumulativeInputTokens = 0;
285
+ let cumulativeOutputTokens = 0;
286
+ let cumulativeCacheReadTokens = 0;
287
+ let cumulativeCacheWriteTokens = 0;
288
+ let lastSessionId;
289
+ // Emit HeadlessJsonResult to stdout for --output-format json batch mode
290
+ function emitBatchJsonResult() {
291
+ if (options.outputFormat !== 'json')
292
+ return;
293
+ const duration = Date.now() - startTime;
294
+ const status = blocked ? 'blocked'
295
+ : exitCode === EXIT_CANCELLED ? 'cancelled'
296
+ : exitCode === EXIT_ERROR ? (totalEvents === 0 ? 'error' : 'timeout')
297
+ : 'success';
298
+ const result = {
299
+ status,
300
+ exitCode,
301
+ sessionId: lastSessionId,
302
+ duration,
303
+ cost: {
304
+ total: cumulativeCostUsd,
305
+ input_tokens: cumulativeInputTokens,
306
+ output_tokens: cumulativeOutputTokens,
307
+ cache_read_tokens: cumulativeCacheReadTokens,
308
+ cache_write_tokens: cumulativeCacheWriteTokens,
309
+ },
310
+ toolCalls: toolCallCount,
311
+ events: totalEvents,
312
+ };
313
+ process.stdout.write(JSON.stringify(result) + '\n');
314
+ }
259
315
  function trackEvent(event) {
260
316
  totalEvents++;
261
317
  const type = String(event.type ?? 'unknown');
@@ -272,8 +328,11 @@ async function runHeadlessOnce(options, restartCount) {
272
328
  if (recentEvents.length > 20)
273
329
  recentEvents.shift();
274
330
  }
275
- // Stdin writer for sending extension_ui_response to child
276
- let stdinWriter = null;
331
+ // Client started flag replaces old stdinWriter null-check
332
+ let clientStarted = false;
333
+ // Adapter for AnswerInjector — wraps client.sendUIResponse in a writeToStdin-compatible callback
334
+ // Initialized after client.start(); events won't fire before then
335
+ let injectorStdinAdapter = () => { };
277
336
  // Supervised mode state
278
337
  const pendingResponseTimers = new Map();
279
338
  let supervisedFallback = false;
@@ -320,21 +379,54 @@ async function runHeadlessOnce(options, restartCount) {
320
379
  resetIdleTimer();
321
380
  // Answer injector: observe events for question metadata
322
381
  injector?.observeEvent(eventObj);
323
- // --json mode: forward events as JSONL to stdout (filtered if --events)
324
- if (options.json) {
382
+ // --json / --output-format stream-json: forward events as JSONL to stdout (filtered if --events)
383
+ // --output-format json (batch mode): suppress streaming, track cost for final result
384
+ if (options.json && options.outputFormat === 'stream-json') {
325
385
  const eventType = String(eventObj.type ?? '');
326
386
  if (!options.eventFilter || options.eventFilter.has(eventType)) {
327
387
  process.stdout.write(JSON.stringify(eventObj) + '\n');
328
388
  }
329
389
  }
330
- else {
390
+ else if (options.outputFormat === 'json') {
391
+ // Batch mode: silently track cost_update events (cumulative-max per K004)
392
+ const eventType = String(eventObj.type ?? '');
393
+ if (eventType === 'cost_update') {
394
+ const data = eventObj;
395
+ const cumCost = data.cumulativeCost;
396
+ if (cumCost) {
397
+ cumulativeCostUsd = Math.max(cumulativeCostUsd, Number(cumCost.costUsd ?? 0));
398
+ const tokens = data.tokens;
399
+ if (tokens) {
400
+ cumulativeInputTokens = Math.max(cumulativeInputTokens, tokens.input ?? 0);
401
+ cumulativeOutputTokens = Math.max(cumulativeOutputTokens, tokens.output ?? 0);
402
+ cumulativeCacheReadTokens = Math.max(cumulativeCacheReadTokens, tokens.cacheRead ?? 0);
403
+ cumulativeCacheWriteTokens = Math.max(cumulativeCacheWriteTokens, tokens.cacheWrite ?? 0);
404
+ }
405
+ }
406
+ }
407
+ // Track sessionId from init_result
408
+ if (eventType === 'init_result') {
409
+ lastSessionId = String(eventObj.sessionId ?? '');
410
+ }
411
+ }
412
+ else if (!options.json) {
331
413
  // Progress output to stderr
332
414
  const line = formatProgress(eventObj, !!options.verbose);
333
415
  if (line)
334
416
  process.stderr.write(line + '\n');
335
417
  }
418
+ // Handle execution_complete (v2 structured completion)
419
+ if (eventObj.type === 'execution_complete' && !completed) {
420
+ completed = true;
421
+ const status = String(eventObj.status ?? 'success');
422
+ exitCode = mapStatusToExitCode(status);
423
+ if (eventObj.status === 'blocked')
424
+ blocked = true;
425
+ resolveCompletion();
426
+ return;
427
+ }
336
428
  // Handle extension_ui_request
337
- if (eventObj.type === 'extension_ui_request' && stdinWriter) {
429
+ if (eventObj.type === 'extension_ui_request' && clientStarted) {
338
430
  // Check for terminal notification before auto-responding
339
431
  if (isBlockedNotification(eventObj)) {
340
432
  blocked = true;
@@ -348,7 +440,7 @@ async function runHeadlessOnce(options, restartCount) {
348
440
  }
349
441
  // Answer injection: try to handle with pre-supplied answers before supervised/auto
350
442
  if (injector && !FIRE_AND_FORGET_METHODS.has(String(eventObj.method ?? ''))) {
351
- if (injector.tryHandle(eventObj, stdinWriter)) {
443
+ if (injector.tryHandle(eventObj, injectorStdinAdapter)) {
352
444
  if (completed) {
353
445
  exitCode = blocked ? EXIT_BLOCKED : EXIT_SUCCESS;
354
446
  resolveCompletion();
@@ -364,13 +456,13 @@ async function runHeadlessOnce(options, restartCount) {
364
456
  const eventId = String(eventObj.id ?? '');
365
457
  const timer = setTimeout(() => {
366
458
  pendingResponseTimers.delete(eventId);
367
- handleExtensionUIRequest(eventObj, stdinWriter);
459
+ handleExtensionUIRequest(eventObj, client);
368
460
  process.stdout.write(JSON.stringify({ type: 'supervised_timeout', id: eventId, method }) + '\n');
369
461
  }, responseTimeout);
370
462
  pendingResponseTimers.set(eventId, timer);
371
463
  }
372
464
  else {
373
- handleExtensionUIRequest(eventObj, stdinWriter);
465
+ handleExtensionUIRequest(eventObj, client);
374
466
  }
375
467
  // If we detected a terminal notification, resolve after responding
376
468
  if (completed) {
@@ -393,13 +485,22 @@ async function runHeadlessOnce(options, restartCount) {
393
485
  process.stderr.write('\n[headless] Interrupted, stopping child process...\n');
394
486
  interrupted = true;
395
487
  exitCode = EXIT_CANCELLED;
396
- client.stop().finally(() => {
397
- if (timeoutTimer)
398
- clearTimeout(timeoutTimer);
399
- if (idleTimer)
400
- clearTimeout(idleTimer);
401
- process.exit(exitCode);
402
- });
488
+ // Kill child process — don't await, just fire and exit.
489
+ // The main flow may be awaiting a promise that resolves when the child dies,
490
+ // which would race with this handler. Exit synchronously to ensure correct exit code.
491
+ try {
492
+ client.stop().catch(() => { });
493
+ }
494
+ catch { }
495
+ if (timeoutTimer)
496
+ clearTimeout(timeoutTimer);
497
+ if (idleTimer)
498
+ clearTimeout(idleTimer);
499
+ // Emit batch JSON result if in json mode before exiting
500
+ if (options.outputFormat === 'json') {
501
+ emitBatchJsonResult();
502
+ }
503
+ process.exit(exitCode);
403
504
  };
404
505
  process.on('SIGINT', signalHandler);
405
506
  process.on('SIGTERM', signalHandler);
@@ -413,21 +514,55 @@ async function runHeadlessOnce(options, restartCount) {
413
514
  clearTimeout(timeoutTimer);
414
515
  process.exit(1);
415
516
  }
416
- // Access stdin writer from the internal process
417
- const internalProcess = client.process;
418
- if (!internalProcess?.stdin) {
419
- process.stderr.write('[headless] Error: Cannot access child process stdin\n');
420
- await client.stop();
421
- if (timeoutTimer)
422
- clearTimeout(timeoutTimer);
423
- process.exit(1);
517
+ // v2 protocol negotiation attempt init for structured completion events
518
+ let v2Enabled = false;
519
+ try {
520
+ await client.init({ clientId: 'gsd-headless' });
521
+ v2Enabled = true;
424
522
  }
425
- stdinWriter = (data) => {
426
- internalProcess.stdin.write(data);
523
+ catch {
524
+ process.stderr.write('[headless] Warning: v2 init failed, falling back to v1 string-matching\n');
525
+ }
526
+ clientStarted = true;
527
+ // --resume: resolve session ID and switch to it
528
+ if (options.resumeSession) {
529
+ const projectSessionsDir = getProjectSessionsDir(process.cwd());
530
+ const sessions = await SessionManager.list(process.cwd(), projectSessionsDir);
531
+ const result = resolveResumeSession(sessions, options.resumeSession);
532
+ if (result.error) {
533
+ process.stderr.write(`[headless] Error: ${result.error}\n`);
534
+ await client.stop();
535
+ if (timeoutTimer)
536
+ clearTimeout(timeoutTimer);
537
+ process.exit(1);
538
+ }
539
+ const matched = result.session;
540
+ const switchResult = await client.switchSession(matched.path);
541
+ if (switchResult.cancelled) {
542
+ process.stderr.write(`[headless] Error: Session switch to '${matched.id}' was cancelled by an extension\n`);
543
+ await client.stop();
544
+ if (timeoutTimer)
545
+ clearTimeout(timeoutTimer);
546
+ process.exit(1);
547
+ }
548
+ process.stderr.write(`[headless] Resuming session ${matched.id}\n`);
549
+ }
550
+ // Build injector adapter — wraps client.sendUIResponse for AnswerInjector's writeToStdin interface
551
+ injectorStdinAdapter = (data) => {
552
+ try {
553
+ const parsed = JSON.parse(data.trim());
554
+ if (parsed.type === 'extension_ui_response' && parsed.id) {
555
+ const { id, value, values, confirmed, cancelled } = parsed;
556
+ client.sendUIResponse(id, { value, values, confirmed, cancelled });
557
+ }
558
+ }
559
+ catch {
560
+ process.stderr.write('[headless] Warning: injector adapter received unparseable data\n');
561
+ }
427
562
  };
428
563
  // Start supervised stdin reader for orchestrator commands
429
564
  if (options.supervised) {
430
- stopSupervisedReader = startSupervisedStdinReader(stdinWriter, client, (id) => {
565
+ stopSupervisedReader = startSupervisedStdinReader(client, (id) => {
431
566
  const timer = pendingResponseTimers.get(id);
432
567
  if (timer) {
433
568
  clearTimeout(timer);
@@ -437,15 +572,18 @@ async function runHeadlessOnce(options, restartCount) {
437
572
  // Ensure stdin is in flowing mode for JSONL reading
438
573
  process.stdin.resume();
439
574
  }
440
- // Detect child process crash
441
- internalProcess.on('exit', (code) => {
442
- if (!completed) {
443
- const msg = `[headless] Child process exited unexpectedly with code ${code ?? 'null'}\n`;
444
- process.stderr.write(msg);
445
- exitCode = EXIT_ERROR;
446
- resolveCompletion();
447
- }
448
- });
575
+ // Detect child process crash (read-only exit event subscription — not stdin access)
576
+ const internalProcess = client.process;
577
+ if (internalProcess) {
578
+ internalProcess.on('exit', (code) => {
579
+ if (!completed) {
580
+ const msg = `[headless] Child process exited unexpectedly with code ${code ?? 'null'}\n`;
581
+ process.stderr.write(msg);
582
+ exitCode = EXIT_ERROR;
583
+ resolveCompletion();
584
+ }
585
+ });
586
+ }
449
587
  if (!options.json) {
450
588
  process.stderr.write(`[headless] Running /gsd ${options.command}${options.commandArgs.length > 0 ? ' ' + options.commandArgs.join(' ') : ''}...\n`);
451
589
  }
@@ -530,5 +668,7 @@ async function runHeadlessOnce(options, restartCount) {
530
668
  }
531
669
  }
532
670
  }
671
+ // Emit structured JSON result in batch mode
672
+ emitBatchJsonResult();
533
673
  return { exitCode, interrupted };
534
674
  }
@@ -25,6 +25,11 @@ function maskPreview(value) {
25
25
  function shellEscapeSingle(value) {
26
26
  return `'${value.replace(/'/g, `'\\''`)}'`;
27
27
  }
28
+ function hydrateProcessEnv(key, value) {
29
+ // Make newly collected secrets immediately visible to the current session.
30
+ // Some extensions read process.env directly and do not reload .env on every call.
31
+ process.env[key] = value;
32
+ }
28
33
  async function writeEnvKey(filePath, key, value) {
29
34
  let content = "";
30
35
  try {
@@ -246,6 +251,7 @@ async function applySecrets(provided, destination, opts) {
246
251
  try {
247
252
  await writeEnvKey(opts.envFilePath, key, value);
248
253
  applied.push(key);
254
+ hydrateProcessEnv(key, value);
249
255
  }
250
256
  catch (err) {
251
257
  errors.push(`${key}: ${err.message}`);
@@ -265,6 +271,7 @@ async function applySecrets(provided, destination, opts) {
265
271
  }
266
272
  else {
267
273
  applied.push(key);
274
+ hydrateProcessEnv(key, value);
268
275
  }
269
276
  }
270
277
  catch (err) {