@opengsd/gsd-pi 1.2.0-dev.84c56d87 → 1.2.0-dev.9ad8ae33

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 (460) hide show
  1. package/dist/headless-events.js +7 -5
  2. package/dist/mcp-server.js +2 -1
  3. package/dist/resource-loader.d.ts +10 -5
  4. package/dist/resource-loader.js +121 -6
  5. package/dist/resources/.managed-resources-content-hash +1 -1
  6. package/dist/resources/GSD-WORKFLOW.md +5 -4
  7. package/dist/resources/extensions/async-jobs/async-bash-tool.js +30 -64
  8. package/dist/resources/extensions/async-jobs/await-tool.js +80 -12
  9. package/dist/resources/extensions/async-jobs/index.js +65 -0
  10. package/dist/resources/extensions/async-jobs/job-manager.js +12 -1
  11. package/dist/resources/extensions/bg-shell/bg-shell-command.js +6 -6
  12. package/dist/resources/extensions/bg-shell/bg-shell-tool.js +10 -7
  13. package/dist/resources/extensions/bg-shell/overlay.js +9 -6
  14. package/dist/resources/extensions/bg-shell/process-manager.js +54 -25
  15. package/dist/resources/extensions/bg-shell/readiness-detector.js +11 -0
  16. package/dist/resources/extensions/gsd/auto/custom-verify-retry-store.js +17 -2
  17. package/dist/resources/extensions/gsd/auto/detect-stuck.js +33 -13
  18. package/dist/resources/extensions/gsd/auto/dispatch-history.js +105 -0
  19. package/dist/resources/extensions/gsd/auto/dispatch-key.js +37 -0
  20. package/dist/resources/extensions/gsd/auto/loop.js +4 -1
  21. package/dist/resources/extensions/gsd/auto/orchestrator.js +89 -54
  22. package/dist/resources/extensions/gsd/auto/phases.js +49 -6
  23. package/dist/resources/extensions/gsd/auto/session.js +3 -0
  24. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +11 -34
  25. package/dist/resources/extensions/gsd/auto-dispatch.js +50 -58
  26. package/dist/resources/extensions/gsd/auto-model-selection.js +36 -13
  27. package/dist/resources/extensions/gsd/auto-post-unit.js +30 -12
  28. package/dist/resources/extensions/gsd/auto-prompts.js +78 -19
  29. package/dist/resources/extensions/gsd/auto-start.js +35 -15
  30. package/dist/resources/extensions/gsd/auto-unit-closeout.js +45 -21
  31. package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +5 -4
  32. package/dist/resources/extensions/gsd/auto-verification.js +23 -30
  33. package/dist/resources/extensions/gsd/auto-worktree.js +14 -1
  34. package/dist/resources/extensions/gsd/auto.js +37 -1
  35. package/dist/resources/extensions/gsd/blocked-models.js +28 -0
  36. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +26 -6
  37. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +23 -6
  38. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +2 -2
  39. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +145 -50
  40. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +302 -80
  41. package/dist/resources/extensions/gsd/browser-daemon-auto-prep.js +83 -0
  42. package/dist/resources/extensions/gsd/closeout-wizard.js +92 -0
  43. package/dist/resources/extensions/gsd/commands/context.js +16 -2
  44. package/dist/resources/extensions/gsd/commands-handlers.js +46 -3
  45. package/dist/resources/extensions/gsd/consent-question.js +353 -0
  46. package/dist/resources/extensions/gsd/consent-verdict.js +63 -0
  47. package/dist/resources/extensions/gsd/constants.js +0 -2
  48. package/dist/resources/extensions/gsd/crash-recovery.js +8 -3
  49. package/dist/resources/extensions/gsd/db/queries.js +26 -0
  50. package/dist/resources/extensions/gsd/db-writer.js +8 -17
  51. package/dist/resources/extensions/gsd/dispatch-guard.js +10 -35
  52. package/dist/resources/extensions/gsd/doctor-engine-checks.js +5 -5
  53. package/dist/resources/extensions/gsd/doctor-git-checks.js +2 -18
  54. package/dist/resources/extensions/gsd/engine-hook-contract.js +70 -0
  55. package/dist/resources/extensions/gsd/exec-sandbox.js +30 -10
  56. package/dist/resources/extensions/gsd/files.js +33 -19
  57. package/dist/resources/extensions/gsd/gsd-command-home.js +22 -12
  58. package/dist/resources/extensions/gsd/gsd-db.js +2 -1
  59. package/dist/resources/extensions/gsd/guidance.js +60 -0
  60. package/dist/resources/extensions/gsd/guided-flow.js +6 -3
  61. package/dist/resources/extensions/gsd/markdown-renderer.js +10 -0
  62. package/dist/resources/extensions/gsd/milestone-closeout.js +85 -24
  63. package/dist/resources/extensions/gsd/milestone-planning-persistence.js +2 -2
  64. package/dist/resources/extensions/gsd/milestone-reopen-events.js +3 -5
  65. package/dist/resources/extensions/gsd/parsers-legacy.js +16 -4
  66. package/dist/resources/extensions/gsd/preferences-models.js +2 -2
  67. package/dist/resources/extensions/gsd/projection-flush.js +7 -0
  68. package/dist/resources/extensions/gsd/prompts/complete-slice.md +3 -3
  69. package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
  70. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  71. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  72. package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -1
  73. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  74. package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  75. package/dist/resources/extensions/gsd/prompts/replan-slice.md +1 -1
  76. package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  77. package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
  78. package/dist/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
  79. package/dist/resources/extensions/gsd/prompts/run-uat.md +7 -5
  80. package/dist/resources/extensions/gsd/prompts/system.md +5 -2
  81. package/dist/resources/extensions/gsd/prompts/triage-captures.md +1 -1
  82. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +1 -1
  83. package/dist/resources/extensions/gsd/reactive-graph.js +8 -1
  84. package/dist/resources/extensions/gsd/roadmap-slices.js +25 -3
  85. package/dist/resources/extensions/gsd/safety/destructive-confirmation.js +108 -0
  86. package/dist/resources/extensions/gsd/session-lock.js +1 -1
  87. package/dist/resources/extensions/gsd/state.js +5 -0
  88. package/dist/resources/extensions/gsd/tool-contract.js +14 -3
  89. package/dist/resources/extensions/gsd/tool-presentation-plan.js +4 -4
  90. package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -2
  91. package/dist/resources/extensions/gsd/tools/complete-slice.js +22 -12
  92. package/dist/resources/extensions/gsd/tools/complete-task.js +3 -2
  93. package/dist/resources/extensions/gsd/tools/exec-tool.js +5 -0
  94. package/dist/resources/extensions/gsd/tools/plan-slice.js +2 -2
  95. package/dist/resources/extensions/gsd/tools/plan-task.js +2 -2
  96. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +2 -2
  97. package/dist/resources/extensions/gsd/tools/reopen-milestone.js +2 -2
  98. package/dist/resources/extensions/gsd/tools/reopen-slice.js +2 -2
  99. package/dist/resources/extensions/gsd/tools/reopen-task.js +2 -2
  100. package/dist/resources/extensions/gsd/tools/replan-slice.js +2 -2
  101. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +67 -2
  102. package/dist/resources/extensions/gsd/uat-policy.js +40 -15
  103. package/dist/resources/extensions/gsd/unit-context-composer.js +65 -0
  104. package/dist/resources/extensions/gsd/verdict-parser.js +1 -1
  105. package/dist/resources/extensions/gsd/verification-verdict.js +2 -1
  106. package/dist/resources/extensions/gsd/workflow-event-ledger.js +91 -0
  107. package/dist/resources/extensions/gsd/workflow-event-vocabulary.js +46 -0
  108. package/dist/resources/extensions/gsd/workflow-events.js +6 -18
  109. package/dist/resources/extensions/gsd/workflow-reconcile.js +21 -56
  110. package/dist/resources/extensions/gsd/worktree-lifecycle.js +3 -2
  111. package/dist/resources/extensions/gsd/worktree-manager.js +7 -1
  112. package/dist/resources/extensions/gsd/worktree.js +8 -1
  113. package/dist/resources/extensions/shared/gsd-browser-cli.js +45 -3
  114. package/dist/resources/shared/gsd-browser-path-sync.js +214 -0
  115. package/dist/resources/shared/package-manager-detection.js +1 -1
  116. package/dist/resources/shared/package.json +3 -0
  117. package/dist/resources/skills/create-skill/SKILL.md +3 -0
  118. package/dist/resources/skills/create-skill/references/skill-structure.md +1 -0
  119. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  120. package/dist/update-check.d.ts +2 -0
  121. package/dist/update-check.js +24 -1
  122. package/dist/update-cmd.js +20 -3
  123. package/dist/web/standalone/.next/BUILD_ID +1 -1
  124. package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
  125. package/dist/web/standalone/.next/build-manifest.json +3 -3
  126. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  127. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  128. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  129. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  130. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  131. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  132. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  133. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  134. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  135. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  136. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  137. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  138. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  139. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  140. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  141. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  142. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  143. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  144. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
  146. package/dist/web/standalone/.next/server/app/index.html +1 -1
  147. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  148. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  149. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  150. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  151. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  152. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  153. package/dist/web/standalone/.next/server/app-paths-manifest.json +6 -6
  154. package/dist/web/standalone/.next/server/chunks/8357.js +2 -2
  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/pages/404.html +1 -1
  158. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  159. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  160. package/dist/web/standalone/.next/static/chunks/{796.cf859a427a2cb2ac.js → 796.e0bdc932325d7e03.js} +1 -1
  161. package/dist/web/standalone/.next/static/chunks/{webpack-fbea77b5f9953368.js → webpack-f0285ce91d4ec9ef.js} +1 -1
  162. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  163. package/dist/web/standalone/node_modules/postcss/lib/container.js +26 -18
  164. package/dist/web/standalone/node_modules/postcss/lib/css-syntax-error.js +47 -14
  165. package/dist/web/standalone/node_modules/postcss/lib/declaration.js +4 -4
  166. package/dist/web/standalone/node_modules/postcss/lib/fromJSON.js +3 -3
  167. package/dist/web/standalone/node_modules/postcss/lib/input.js +54 -29
  168. package/dist/web/standalone/node_modules/postcss/lib/lazy-result.js +47 -37
  169. package/dist/web/standalone/node_modules/postcss/lib/map-generator.js +26 -9
  170. package/dist/web/standalone/node_modules/postcss/lib/no-work-result.js +57 -55
  171. package/dist/web/standalone/node_modules/postcss/lib/node.js +99 -31
  172. package/dist/web/standalone/node_modules/postcss/lib/parse.js +1 -1
  173. package/dist/web/standalone/node_modules/postcss/lib/parser.js +10 -9
  174. package/dist/web/standalone/node_modules/postcss/lib/postcss.js +12 -12
  175. package/dist/web/standalone/node_modules/postcss/lib/previous-map.js +30 -11
  176. package/dist/web/standalone/node_modules/postcss/lib/processor.js +7 -7
  177. package/dist/web/standalone/node_modules/postcss/lib/result.js +5 -5
  178. package/dist/web/standalone/node_modules/postcss/lib/rule.js +6 -6
  179. package/dist/web/standalone/node_modules/postcss/lib/stringifier.js +69 -28
  180. package/dist/web/standalone/node_modules/postcss/lib/tokenize.js +6 -2
  181. package/dist/web/standalone/node_modules/postcss/package.json +48 -48
  182. package/package.json +2 -2
  183. package/packages/cloud-mcp-gateway/package.json +2 -2
  184. package/packages/contracts/dist/rpc.d.ts +1 -0
  185. package/packages/contracts/dist/rpc.d.ts.map +1 -1
  186. package/packages/contracts/dist/rpc.js.map +1 -1
  187. package/packages/contracts/package.json +1 -1
  188. package/packages/daemon/package.json +4 -4
  189. package/packages/gsd-agent-core/package.json +5 -5
  190. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +5 -0
  191. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  192. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +5 -0
  193. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  194. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  195. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +7 -0
  196. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  197. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  198. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js +8 -1
  199. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  200. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.d.ts.map +1 -1
  201. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js +11 -1
  202. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js.map +1 -1
  203. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts.map +1 -1
  204. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js +4 -4
  205. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js.map +1 -1
  206. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  207. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js +3 -1
  208. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js.map +1 -1
  209. package/packages/gsd-agent-modes/package.json +7 -7
  210. package/packages/mcp-server/dist/cli.js +10 -5
  211. package/packages/mcp-server/dist/cli.js.map +1 -1
  212. package/packages/mcp-server/dist/moonshot-tool-schema.d.ts +29 -0
  213. package/packages/mcp-server/dist/moonshot-tool-schema.d.ts.map +1 -0
  214. package/packages/mcp-server/dist/moonshot-tool-schema.js +50 -0
  215. package/packages/mcp-server/dist/moonshot-tool-schema.js.map +1 -0
  216. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  217. package/packages/mcp-server/dist/server.js +4 -0
  218. package/packages/mcp-server/dist/server.js.map +1 -1
  219. package/packages/mcp-server/dist/workflow-tools.d.ts +18 -18
  220. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  221. package/packages/mcp-server/dist/workflow-tools.js +99 -38
  222. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  223. package/packages/mcp-server/package.json +5 -4
  224. package/packages/native/package.json +1 -1
  225. package/packages/pi-agent-core/dist/harness/env/nodejs.d.ts +1 -0
  226. package/packages/pi-agent-core/dist/harness/env/nodejs.d.ts.map +1 -1
  227. package/packages/pi-agent-core/dist/harness/env/nodejs.js +34 -3
  228. package/packages/pi-agent-core/dist/harness/env/nodejs.js.map +1 -1
  229. package/packages/pi-agent-core/dist/index.d.ts +1 -0
  230. package/packages/pi-agent-core/dist/index.d.ts.map +1 -1
  231. package/packages/pi-agent-core/dist/index.js +3 -0
  232. package/packages/pi-agent-core/dist/index.js.map +1 -1
  233. package/packages/pi-agent-core/package.json +1 -1
  234. package/packages/pi-ai/README.md +1 -0
  235. package/packages/pi-ai/dist/image-models.generated.d.ts +2 -2
  236. package/packages/pi-ai/dist/image-models.generated.js +6 -6
  237. package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
  238. package/packages/pi-ai/dist/index.d.ts +2 -0
  239. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  240. package/packages/pi-ai/dist/index.js +2 -0
  241. package/packages/pi-ai/dist/index.js.map +1 -1
  242. package/packages/pi-ai/dist/models.generated.d.ts +419 -221
  243. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  244. package/packages/pi-ai/dist/models.generated.js +460 -261
  245. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  246. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  247. package/packages/pi-ai/dist/providers/anthropic.js +12 -7
  248. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  249. package/packages/pi-ai/dist/providers/google-shared.d.ts +5 -0
  250. package/packages/pi-ai/dist/providers/google-shared.d.ts.map +1 -1
  251. package/packages/pi-ai/dist/providers/google-shared.js +12 -3
  252. package/packages/pi-ai/dist/providers/google-shared.js.map +1 -1
  253. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  254. package/packages/pi-ai/dist/providers/openai-completions.js +7 -3
  255. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  256. package/packages/pi-ai/dist/utils/moonshot-tool-schema.d.ts +9 -0
  257. package/packages/pi-ai/dist/utils/moonshot-tool-schema.d.ts.map +1 -0
  258. package/packages/pi-ai/dist/utils/moonshot-tool-schema.js +34 -0
  259. package/packages/pi-ai/dist/utils/moonshot-tool-schema.js.map +1 -0
  260. package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  261. package/packages/pi-ai/dist/utils/oauth/github-copilot.js +6 -2
  262. package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
  263. package/packages/pi-ai/package.json +3 -2
  264. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +2 -2
  265. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  266. package/packages/pi-coding-agent/dist/core/auth-storage.js +19 -13
  267. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  268. package/packages/pi-coding-agent/dist/core/provider-readiness.d.ts.map +1 -1
  269. package/packages/pi-coding-agent/dist/core/provider-readiness.js +13 -6
  270. package/packages/pi-coding-agent/dist/core/provider-readiness.js.map +1 -1
  271. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts +11 -0
  272. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
  273. package/packages/pi-coding-agent/dist/core/tools/bash.js +53 -11
  274. package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  275. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  276. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  277. package/packages/pi-coding-agent/dist/index.js +1 -1
  278. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  279. package/packages/pi-coding-agent/dist/utils/shell.d.ts +28 -2
  280. package/packages/pi-coding-agent/dist/utils/shell.d.ts.map +1 -1
  281. package/packages/pi-coding-agent/dist/utils/shell.js +56 -10
  282. package/packages/pi-coding-agent/dist/utils/shell.js.map +1 -1
  283. package/packages/pi-coding-agent/package.json +7 -7
  284. package/packages/pi-tui/package.json +2 -2
  285. package/packages/rpc-client/package.json +2 -2
  286. package/pkg/package.json +1 -1
  287. package/src/resources/GSD-WORKFLOW.md +5 -4
  288. package/src/resources/extensions/async-jobs/async-bash-cancel.test.ts +360 -0
  289. package/src/resources/extensions/async-jobs/async-bash-tool.ts +33 -56
  290. package/src/resources/extensions/async-jobs/await-tool.test.ts +139 -0
  291. package/src/resources/extensions/async-jobs/await-tool.ts +82 -12
  292. package/src/resources/extensions/async-jobs/index.ts +79 -0
  293. package/src/resources/extensions/async-jobs/job-manager.ts +21 -1
  294. package/src/resources/extensions/bg-shell/bg-shell-command.ts +6 -6
  295. package/src/resources/extensions/bg-shell/bg-shell-tool.ts +10 -6
  296. package/src/resources/extensions/bg-shell/overlay.ts +9 -5
  297. package/src/resources/extensions/bg-shell/process-manager.ts +50 -25
  298. package/src/resources/extensions/bg-shell/readiness-detector.ts +12 -0
  299. package/src/resources/extensions/bg-shell/tests/lifecycle-and-utilities.test.ts +48 -1
  300. package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +40 -1
  301. package/src/resources/extensions/gsd/auto/custom-verify-retry-store.ts +21 -3
  302. package/src/resources/extensions/gsd/auto/detect-stuck.ts +32 -9
  303. package/src/resources/extensions/gsd/auto/dispatch-history.ts +152 -0
  304. package/src/resources/extensions/gsd/auto/dispatch-key.ts +39 -0
  305. package/src/resources/extensions/gsd/auto/loop.ts +4 -1
  306. package/src/resources/extensions/gsd/auto/orchestrator.ts +98 -56
  307. package/src/resources/extensions/gsd/auto/phases.ts +65 -26
  308. package/src/resources/extensions/gsd/auto/session.ts +3 -0
  309. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +18 -48
  310. package/src/resources/extensions/gsd/auto-dispatch.ts +48 -61
  311. package/src/resources/extensions/gsd/auto-model-selection.ts +41 -12
  312. package/src/resources/extensions/gsd/auto-post-unit.ts +33 -12
  313. package/src/resources/extensions/gsd/auto-prompts.ts +115 -35
  314. package/src/resources/extensions/gsd/auto-start.ts +36 -18
  315. package/src/resources/extensions/gsd/auto-unit-closeout.ts +83 -28
  316. package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +4 -4
  317. package/src/resources/extensions/gsd/auto-verification.ts +26 -28
  318. package/src/resources/extensions/gsd/auto-worktree.ts +14 -1
  319. package/src/resources/extensions/gsd/auto.ts +44 -1
  320. package/src/resources/extensions/gsd/blocked-models.ts +49 -0
  321. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +34 -5
  322. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +23 -6
  323. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +2 -2
  324. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +163 -55
  325. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +350 -86
  326. package/src/resources/extensions/gsd/browser-daemon-auto-prep.ts +108 -0
  327. package/src/resources/extensions/gsd/closeout-wizard.ts +102 -0
  328. package/src/resources/extensions/gsd/commands/context.ts +16 -2
  329. package/src/resources/extensions/gsd/commands-handlers.ts +46 -3
  330. package/src/resources/extensions/gsd/consent-question.ts +431 -0
  331. package/src/resources/extensions/gsd/consent-verdict.ts +86 -0
  332. package/src/resources/extensions/gsd/constants.ts +0 -3
  333. package/src/resources/extensions/gsd/crash-recovery.ts +10 -2
  334. package/src/resources/extensions/gsd/db/queries.ts +37 -0
  335. package/src/resources/extensions/gsd/db-writer.ts +11 -19
  336. package/src/resources/extensions/gsd/dispatch-guard.ts +8 -31
  337. package/src/resources/extensions/gsd/doctor-engine-checks.ts +5 -4
  338. package/src/resources/extensions/gsd/doctor-git-checks.ts +2 -19
  339. package/src/resources/extensions/gsd/engine-hook-contract.ts +79 -0
  340. package/src/resources/extensions/gsd/exec-sandbox.ts +49 -9
  341. package/src/resources/extensions/gsd/files.ts +33 -12
  342. package/src/resources/extensions/gsd/gsd-command-home.ts +13 -3
  343. package/src/resources/extensions/gsd/gsd-db.ts +4 -3
  344. package/src/resources/extensions/gsd/guidance.ts +78 -0
  345. package/src/resources/extensions/gsd/guided-flow.ts +21 -26
  346. package/src/resources/extensions/gsd/markdown-renderer.ts +11 -0
  347. package/src/resources/extensions/gsd/milestone-closeout.ts +109 -24
  348. package/src/resources/extensions/gsd/milestone-planning-persistence.ts +2 -2
  349. package/src/resources/extensions/gsd/milestone-reopen-events.ts +3 -6
  350. package/src/resources/extensions/gsd/parsers-legacy.ts +16 -4
  351. package/src/resources/extensions/gsd/preferences-models.ts +2 -1
  352. package/src/resources/extensions/gsd/projection-flush.ts +20 -0
  353. package/src/resources/extensions/gsd/prompts/complete-slice.md +3 -3
  354. package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
  355. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  356. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  357. package/src/resources/extensions/gsd/prompts/quick-task.md +1 -1
  358. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  359. package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  360. package/src/resources/extensions/gsd/prompts/replan-slice.md +1 -1
  361. package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  362. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  363. package/src/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
  364. package/src/resources/extensions/gsd/prompts/run-uat.md +7 -5
  365. package/src/resources/extensions/gsd/prompts/system.md +5 -2
  366. package/src/resources/extensions/gsd/prompts/triage-captures.md +1 -1
  367. package/src/resources/extensions/gsd/prompts/validate-milestone.md +1 -1
  368. package/src/resources/extensions/gsd/reactive-graph.ts +11 -1
  369. package/src/resources/extensions/gsd/roadmap-slices.ts +28 -3
  370. package/src/resources/extensions/gsd/safety/destructive-confirmation.ts +134 -0
  371. package/src/resources/extensions/gsd/session-lock.ts +1 -1
  372. package/src/resources/extensions/gsd/state.ts +5 -0
  373. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +97 -1
  374. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +198 -26
  375. package/src/resources/extensions/gsd/tests/auto-remote-session-lock-cleanup.test.ts +65 -3
  376. package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +236 -0
  377. package/src/resources/extensions/gsd/tests/blocked-models.test.ts +19 -0
  378. package/src/resources/extensions/gsd/tests/browser-daemon-auto-prep.test.ts +144 -0
  379. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +42 -0
  380. package/src/resources/extensions/gsd/tests/consent-question.test.ts +351 -0
  381. package/src/resources/extensions/gsd/tests/custom-verify-retry-store.test.ts +67 -0
  382. package/src/resources/extensions/gsd/tests/db-writer.test.ts +15 -4
  383. package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +10 -10
  384. package/src/resources/extensions/gsd/tests/destructive-confirmation.test.ts +303 -0
  385. package/src/resources/extensions/gsd/tests/discuss-routing-fixes.test.ts +12 -2
  386. package/src/resources/extensions/gsd/tests/dispatch-history.test.ts +273 -0
  387. package/src/resources/extensions/gsd/tests/doctor-git-checks-terminal.test.ts +73 -0
  388. package/src/resources/extensions/gsd/tests/dynamic-bash-no-cap.test.ts +132 -0
  389. package/src/resources/extensions/gsd/tests/engine-hook-contract.test.ts +148 -0
  390. package/src/resources/extensions/gsd/tests/exec-graceful-kill.test.ts +193 -0
  391. package/src/resources/extensions/gsd/tests/exec-tool.test.ts +29 -1
  392. package/src/resources/extensions/gsd/tests/gsd-command-home.test.ts +120 -0
  393. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +27 -0
  394. package/src/resources/extensions/gsd/tests/guidance.test.ts +23 -0
  395. package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +2 -6
  396. package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +199 -0
  397. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +3 -1
  398. package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +95 -4
  399. package/src/resources/extensions/gsd/tests/model-unittype-mapping.test.ts +32 -1
  400. package/src/resources/extensions/gsd/tests/oauth-api-model-routing.test.ts +167 -0
  401. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +18 -0
  402. package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +138 -0
  403. package/src/resources/extensions/gsd/tests/phases-terminal-complete-idempotent.test.ts +242 -0
  404. package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +63 -2
  405. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +124 -6
  406. package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +68 -0
  407. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +19 -1
  408. package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +17 -0
  409. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +41 -0
  410. package/src/resources/extensions/gsd/tests/tool-unavailable-retry.test.ts +33 -0
  411. package/src/resources/extensions/gsd/tests/transport-gate-double-complete.test.ts +139 -0
  412. package/src/resources/extensions/gsd/tests/uat-policy.test.ts +88 -0
  413. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +44 -0
  414. package/src/resources/extensions/gsd/tests/uok-audit-unified.test.ts +8 -0
  415. package/src/resources/extensions/gsd/tests/verification-verdict.test.ts +2 -0
  416. package/src/resources/extensions/gsd/tests/workflow-events.test.ts +19 -0
  417. package/src/resources/extensions/gsd/tests/workflow-reconcile.test.ts +20 -0
  418. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +273 -38
  419. package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +22 -0
  420. package/src/resources/extensions/gsd/tests/worktree.test.ts +18 -0
  421. package/src/resources/extensions/gsd/tests/write-gate-seam.test.ts +358 -0
  422. package/src/resources/extensions/gsd/tests/write-gate.test.ts +67 -1
  423. package/src/resources/extensions/gsd/tool-contract.ts +38 -3
  424. package/src/resources/extensions/gsd/tool-presentation-plan.ts +4 -4
  425. package/src/resources/extensions/gsd/tools/complete-milestone.ts +3 -2
  426. package/src/resources/extensions/gsd/tools/complete-slice.ts +22 -12
  427. package/src/resources/extensions/gsd/tools/complete-task.ts +3 -2
  428. package/src/resources/extensions/gsd/tools/exec-tool.ts +4 -0
  429. package/src/resources/extensions/gsd/tools/plan-slice.ts +2 -2
  430. package/src/resources/extensions/gsd/tools/plan-task.ts +2 -2
  431. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +2 -2
  432. package/src/resources/extensions/gsd/tools/reopen-milestone.ts +2 -2
  433. package/src/resources/extensions/gsd/tools/reopen-slice.ts +2 -2
  434. package/src/resources/extensions/gsd/tools/reopen-task.ts +2 -2
  435. package/src/resources/extensions/gsd/tools/replan-slice.ts +2 -2
  436. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +81 -2
  437. package/src/resources/extensions/gsd/uat-policy.ts +60 -15
  438. package/src/resources/extensions/gsd/unit-context-composer.ts +99 -0
  439. package/src/resources/extensions/gsd/verdict-parser.ts +1 -1
  440. package/src/resources/extensions/gsd/verification-verdict.ts +4 -2
  441. package/src/resources/extensions/gsd/workflow-event-ledger.ts +131 -0
  442. package/src/resources/extensions/gsd/workflow-event-vocabulary.ts +59 -0
  443. package/src/resources/extensions/gsd/workflow-events.ts +12 -20
  444. package/src/resources/extensions/gsd/workflow-reconcile.ts +29 -62
  445. package/src/resources/extensions/gsd/worktree-lifecycle.ts +3 -8
  446. package/src/resources/extensions/gsd/worktree-manager.ts +6 -1
  447. package/src/resources/extensions/gsd/worktree.ts +7 -1
  448. package/src/resources/extensions/shared/gsd-browser-cli.ts +54 -3
  449. package/src/resources/shared/gsd-browser-path-sync.ts +273 -0
  450. package/src/resources/shared/package-manager-detection.ts +1 -1
  451. package/src/resources/shared/package.json +3 -0
  452. package/src/resources/skills/create-skill/SKILL.md +3 -0
  453. package/src/resources/skills/create-skill/references/skill-structure.md +1 -0
  454. package/dist/resources/extensions/gsd/user-input-boundary.js +0 -218
  455. package/dist/resources/skills/gsd-browser/SKILL.md +0 -41
  456. package/src/resources/extensions/gsd/tests/user-input-boundary.test.ts +0 -173
  457. package/src/resources/extensions/gsd/user-input-boundary.ts +0 -216
  458. package/src/resources/skills/gsd-browser/SKILL.md +0 -41
  459. /package/dist/web/standalone/.next/static/{AOpDeK_gJHU8OZjRo31gQ → FBNo5cT_chy7YNoAQsU3o}/_buildManifest.js +0 -0
  460. /package/dist/web/standalone/.next/static/{AOpDeK_gJHU8OZjRo31gQ → FBNo5cT_chy7YNoAQsU3o}/_ssgManifest.js +0 -0
@@ -53,6 +53,15 @@ export class AsyncJobManager {
53
53
  };
54
54
  job.promise = runFn(abortController.signal)
55
55
  .then((resultText) => {
56
+ if (job.status === "cancelled") {
57
+ // Already cancelled by cancel(). The runFn resolves (not rejects) even
58
+ // when aborted — async_bash's safeResolve returns "Command aborted"
59
+ // rather than throwing — so without this guard the status would be
60
+ // clobbered back to "completed", mislabeling a user-cancelled job.
61
+ // Mirrors the symmetric guard in the .catch branch below.
62
+ this.scheduleEviction(id);
63
+ return;
64
+ }
56
65
  job.status = "completed";
57
66
  job.resultText = resultText;
58
67
  this.scheduleEviction(id);
@@ -143,8 +152,10 @@ export class AsyncJobManager {
143
152
  const cb = this.onJobComplete;
144
153
  job.deliveryTimer = setTimeout(() => {
145
154
  job.deliveryTimer = undefined;
146
- if (!job.awaited)
155
+ if (!job.awaited) {
156
+ job.delivered = true;
147
157
  cb(job);
158
+ }
148
159
  }, 0);
149
160
  // Allow process to exit even if timer is pending
150
161
  if (typeof job.deliveryTimer === "object" && "unref" in job.deliveryTimer) {
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { Key } from "@gsd/pi-tui";
5
5
  import { shortcutDesc } from "../shared/terminal.js";
6
- import { processes, killProcess, getGroupStatus, cleanupAll, } from "./process-manager.js";
6
+ import { processes, terminateProcess, getGroupStatus, cleanupAll, } from "./process-manager.js";
7
7
  import { generateDigest, getOutput, formatDigestText, } from "./output-formatter.js";
8
8
  import { formatUptime } from "./utilities.js";
9
9
  import { BgManagerOverlay } from "./overlay.js";
@@ -119,11 +119,11 @@ export function registerBgShellCommand(pi, state) {
119
119
  ctx.ui.notify(`No process with id '${id}'`, "error");
120
120
  return;
121
121
  }
122
- killProcess(id, "SIGTERM");
123
- await new Promise(r => setTimeout(r, 300));
124
- if (bg.alive) {
125
- killProcess(id, "SIGKILL");
126
- await new Promise(r => setTimeout(r, 200));
122
+ // Graceful ladder (SIGTERM → grace → SIGKILL) via killProcessTree.
123
+ terminateProcess(id);
124
+ const deadline = Date.now() + 6_000; // grace (5s) + slack
125
+ while (bg.alive && Date.now() < deadline) {
126
+ await new Promise(r => setTimeout(r, 100));
127
127
  }
128
128
  if (!bg.alive)
129
129
  processes.delete(id);
@@ -4,7 +4,7 @@
4
4
  import { StringEnum, Type } from "@gsd/pi-ai";
5
5
  import { Text } from "@gsd/pi-tui";
6
6
  import { DEFAULT_READY_TIMEOUT } from "./types.js";
7
- import { processes, startProcess, killProcess, restartProcess, getInfo, getGroupStatus, persistManifest, } from "./process-manager.js";
7
+ import { processes, startProcess, killProcess, terminateProcess, restartProcess, getInfo, getGroupStatus, persistManifest, } from "./process-manager.js";
8
8
  import { generateDigest, getHighlights, getOutput, formatDigestText, } from "./output-formatter.js";
9
9
  import { waitForReady } from "./readiness-detector.js";
10
10
  import { queryShellEnv, sendAndWait, runOnSession } from "./interaction.js";
@@ -22,7 +22,8 @@ export function registerBgShellTool(pi, state) {
22
22
  "signal (send OS signal), list (all processes with status), kill (terminate), restart (kill + relaunch), " +
23
23
  "group_status (health of a process group), highlights (significant output lines only).",
24
24
  promptGuidelines: [
25
- "Use bg_shell to start long-running processes (servers, watchers, builds) that should not block the agent.",
25
+ "Use bg_shell for processes that STAY ALIVE and you interact with over time: servers, watchers, daemons, REPLs. For a command that runs to completion and exits (terraform apply, migrations, builds, tests, installs), use async_bash or sync bash instead — NOT bg_shell.",
26
+ "'wait_for_ready' is for long-lived processes that signal readiness (open a port / print a pattern). Never use it on a run-to-completion command: that command exits instead of becoming ready, so a clean exit-0 is reported as 'not ready'. If you see that, switch to async_bash.",
26
27
  "After starting a server, use 'wait_for_ready' to efficiently block until it's listening — avoids polling loops entirely.",
27
28
  "Use 'digest' instead of 'output' when you just need status — it returns a structured ~30-token summary instead of ~2000 tokens of raw output.",
28
29
  "Use 'highlights' to see only significant output (errors, URLs, results) — typically 5-15 lines instead of hundreds.",
@@ -511,11 +512,13 @@ export function registerBgShellTool(pi, state) {
511
512
  isError: true, details: undefined,
512
513
  };
513
514
  }
514
- const killed = killProcess(params.id, "SIGTERM");
515
- await new Promise(r => setTimeout(r, 300));
516
- if (bg.alive) {
517
- killProcess(params.id, "SIGKILL");
518
- await new Promise(r => setTimeout(r, 200));
515
+ // Graceful termination: SIGTERM → grace → SIGKILL via the shared
516
+ // killProcessTree ladder (same path bash/async_bash/exec use), so a
517
+ // stateful process gets a clean-shutdown window instead of a bare kill.
518
+ const killed = terminateProcess(params.id);
519
+ const deadline = Date.now() + 6_000; // grace (5s) + slack
520
+ while (bg.alive && Date.now() < deadline) {
521
+ await new Promise(r => setTimeout(r, 100));
519
522
  }
520
523
  const info = getInfo(bg);
521
524
  if (!bg.alive)
@@ -4,7 +4,7 @@
4
4
  import { truncateToWidth, visibleWidth, matchesKey, Key } from "@gsd/pi-tui";
5
5
  import { ERROR_PATTERNS, WARNING_PATTERNS } from "./types.js";
6
6
  import { formatUptime, formatTimeAgo } from "./utilities.js";
7
- import { processes, killProcess, cleanupAll, restartProcess, } from "./process-manager.js";
7
+ import { processes, terminateProcess, cleanupAll, restartProcess, } from "./process-manager.js";
8
8
  export class BgManagerOverlay {
9
9
  tui;
10
10
  theme;
@@ -110,17 +110,20 @@ export class BgManagerOverlay {
110
110
  }
111
111
  return;
112
112
  }
113
- // x or d = kill selected
113
+ // x or d = kill selected (graceful ladder via killProcessTree).
114
+ // Use a short interactive grace: the user explicitly asked to kill, so give the
115
+ // process a brief clean-shutdown window then force, and re-render AFTER that grace
116
+ // plus slack so the row reflects the SIGKILL escalation (a 300ms re-render against
117
+ // the default 5s grace would show the process still alive).
114
118
  if (data === "x" || data === "d") {
115
119
  const proc = procs[this.selected];
116
120
  if (proc && proc.alive) {
117
- killProcess(proc.id, "SIGTERM");
121
+ const OVERLAY_KILL_GRACE_MS = 500;
122
+ terminateProcess(proc.id, OVERLAY_KILL_GRACE_MS);
118
123
  setTimeout(() => {
119
- if (proc.alive)
120
- killProcess(proc.id, "SIGKILL");
121
124
  this.invalidate();
122
125
  this.tui.requestRender();
123
- }, 300);
126
+ }, OVERLAY_KILL_GRACE_MS + 400);
124
127
  }
125
128
  return;
126
129
  }
@@ -6,7 +6,7 @@ import { spawn, spawnSync } from "node:child_process";
6
6
  import { randomUUID } from "node:crypto";
7
7
  import { writeFileSync, readFileSync, existsSync, mkdirSync } from "node:fs";
8
8
  import { join } from "node:path";
9
- import { getShellConfig, sanitizeCommand } from "@gsd/pi-coding-agent";
9
+ import { getShellConfig, sanitizeCommand, killProcessTree } from "@gsd/pi-coding-agent";
10
10
  import { rewriteCommandWithRtk } from "../shared/rtk.js";
11
11
  import { MAX_BUFFER_LINES, MAX_EVENTS, DEAD_PROCESS_TTL, } from "./types.js";
12
12
  import { restoreWindowsVTInput, formatUptime } from "./utilities.js";
@@ -221,6 +221,38 @@ export function startProcess(opts) {
221
221
  return bg;
222
222
  }
223
223
  // ── Process Kill ───────────────────────────────────────────────────────────
224
+ /**
225
+ * Gracefully terminate a process and its tree using the shared killProcessTree
226
+ * ladder (SIGTERM → grace window → SIGKILL), the same path bash/async_bash/exec
227
+ * use. This is the "I want it dead, cleanly" intent — use it for the `kill`
228
+ * action, `restart`, and session cleanup. For sending a SPECIFIC signal the
229
+ * agent chose on purpose (SIGINT, SIGHUP, …) use killProcess(), which delivers
230
+ * that exact signal once and does not escalate.
231
+ *
232
+ * `graceMs` overrides the SIGTERM→SIGKILL window (default: killProcessTree's 5s);
233
+ * session cleanup passes a shorter grace so it stays snappy between units.
234
+ *
235
+ * Returns false only when the process is unknown/already dead; the actual
236
+ * SIGKILL escalation fires asynchronously after the grace window, so callers
237
+ * should not assume the process is dead the instant this returns.
238
+ */
239
+ export function terminateProcess(id, graceMs) {
240
+ const bg = processes.get(id);
241
+ if (!bg)
242
+ return false;
243
+ if (!bg.alive)
244
+ return true;
245
+ if (!bg.proc.pid) {
246
+ // No pid to target a tree; fall back to a direct graceful signal.
247
+ try {
248
+ bg.proc.kill("SIGTERM");
249
+ }
250
+ catch { /* already gone */ }
251
+ return true;
252
+ }
253
+ killProcessTree(bg.proc.pid, graceMs !== undefined ? { graceMs } : undefined);
254
+ return true;
255
+ }
224
256
  export function killProcess(id, sig = "SIGTERM") {
225
257
  const bg = processes.get(id);
226
258
  if (!bg)
@@ -272,13 +304,14 @@ export async function restartProcess(id) {
272
304
  return null;
273
305
  const config = old.startConfig;
274
306
  const restartCount = old.restartCount + 1;
275
- // Kill old process
307
+ // Kill old process via the graceful ladder, then wait for it to actually die.
308
+ // killProcessTree escalates SIGTERM → grace → SIGKILL asynchronously, so poll
309
+ // for death rather than assuming a fixed sleep is enough.
276
310
  if (old.alive) {
277
- killProcess(id, "SIGTERM");
278
- await new Promise(r => setTimeout(r, 300));
279
- if (old.alive) {
280
- killProcess(id, "SIGKILL");
281
- await new Promise(r => setTimeout(r, 200));
311
+ terminateProcess(id);
312
+ const deadline = Date.now() + 6_000; // grace (5s) + slack
313
+ while (old.alive && Date.now() < deadline) {
314
+ await new Promise(r => setTimeout(r, 100));
282
315
  }
283
316
  }
284
317
  processes.delete(id);
@@ -328,24 +361,16 @@ export function pruneDeadProcesses() {
328
361
  }
329
362
  }
330
363
  export function cleanupAll() {
364
+ // Deliberately a bare, synchronous SIGKILL — not the graceful ladder. This runs
365
+ // from process 'exit'/signal handlers where timers no longer fire, so a deferred
366
+ // SIGKILL would never be delivered and children would be orphaned when we vanish.
367
+ // Immediate force-kill is the correct teardown semantics here.
331
368
  for (const [id, bg] of processes) {
332
369
  if (bg.alive)
333
370
  killProcess(id, "SIGKILL");
334
371
  }
335
372
  processes.clear();
336
373
  }
337
- /**
338
- * Kill all alive, non-persistent bg processes.
339
- * Called between auto-mode units to prevent orphaned servers from
340
- * keeping ports bound across task boundaries (#1209).
341
- */
342
- export function killSessionProcesses() {
343
- for (const [id, bg] of processes) {
344
- if (bg.alive && !bg.persistAcrossSessions) {
345
- killProcess(id, "SIGTERM");
346
- }
347
- }
348
- }
349
374
  async function waitForProcessExit(bg, timeoutMs) {
350
375
  if (!bg.alive)
351
376
  return true;
@@ -359,20 +384,24 @@ async function waitForProcessExit(bg, timeoutMs) {
359
384
  });
360
385
  return !bg.alive;
361
386
  }
387
+ /**
388
+ * Terminate the alive, non-persistent processes owned by a session, gracefully.
389
+ * Routes through the shared killProcessTree ladder (SIGTERM → grace → SIGKILL)
390
+ * via terminateProcess, with a short grace (default 300ms) so cleanup between
391
+ * units stays snappy; killProcessTree handles the SIGKILL escalation itself, so
392
+ * there is no separate force-kill pass here.
393
+ */
362
394
  export async function cleanupSessionProcesses(sessionFile, options) {
363
395
  const graceMs = Math.max(0, options?.graceMs ?? 300);
364
396
  const matches = Array.from(processes.values()).filter((bg) => bg.alive && !bg.persistAcrossSessions && bg.ownerSessionFile === sessionFile);
365
397
  if (matches.length === 0)
366
398
  return [];
367
399
  for (const bg of matches) {
368
- killProcess(bg.id, "SIGTERM");
400
+ terminateProcess(bg.id, graceMs);
369
401
  }
370
402
  if (graceMs > 0) {
371
- await Promise.all(matches.map((bg) => waitForProcessExit(bg, graceMs)));
372
- }
373
- for (const bg of matches) {
374
- if (bg.alive)
375
- killProcess(bg.id, "SIGKILL");
403
+ // Wait past the grace so the SIGKILL escalation has fired and exits are observed.
404
+ await Promise.all(matches.map((bg) => waitForProcessExit(bg, graceMs + 200)));
376
405
  }
377
406
  return matches.map((bg) => bg.id);
378
407
  }
@@ -72,6 +72,17 @@ export async function waitForReady(bg, timeout, signal) {
72
72
  return { ready: false, detail: "Cancelled" };
73
73
  }
74
74
  if (!bg.alive) {
75
+ // A clean exit-0 means the command ran to completion — it was a batch
76
+ // command (e.g. terraform apply, a migration, a build), not a long-lived
77
+ // server. That is success, not a readiness failure; say so plainly and
78
+ // point at the right tool so the agent stops using wait_for_ready here.
79
+ if (bg.exitCode === 0) {
80
+ return {
81
+ ready: false,
82
+ detail: "Process completed (exit 0) before signaling readiness — it ran to completion rather than staying alive. " +
83
+ "wait_for_ready is for long-lived servers/watchers; for a run-to-completion command use async_bash (or bg_shell 'run').",
84
+ };
85
+ }
75
86
  const stderrLines = bg.output.filter(l => l.stream === "stderr").slice(-5).map(l => l.line);
76
87
  const stderrContext = stderrLines.length > 0 ? `\nstderr:\n${stderrLines.join("\n").slice(0, 500)}` : "";
77
88
  return {
@@ -4,6 +4,12 @@ import { readFileSync, mkdirSync, unlinkSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { atomicWriteSync } from "../atomic-write.js";
6
6
  import { gsdRoot } from "../paths.js";
7
+ function ensureExhaustedVerificationUnits(s) {
8
+ if (!s.exhaustedVerificationUnits) {
9
+ s.exhaustedVerificationUnits = new Set();
10
+ }
11
+ return s.exhaustedVerificationUnits;
12
+ }
7
13
  export function customVerifyRetryStateDir(s) {
8
14
  return s.activeRunDir ? join(s.activeRunDir, "runtime") : join(gsdRoot(s.basePath), "runtime");
9
15
  }
@@ -11,7 +17,8 @@ export function customVerifyRetryStatePath(s) {
11
17
  return join(customVerifyRetryStateDir(s), "custom-verify-retries.json");
12
18
  }
13
19
  export function hydrateCustomVerifyRetryCounts(s, deps) {
14
- if (s.verificationRetryCount.size > 0) {
20
+ const exhaustedUnits = ensureExhaustedVerificationUnits(s);
21
+ if (s.verificationRetryCount.size > 0 || exhaustedUnits.size > 0) {
15
22
  return s.verificationRetryCount;
16
23
  }
17
24
  try {
@@ -24,6 +31,12 @@ export function hydrateCustomVerifyRetryCounts(s, deps) {
24
31
  s.verificationRetryCount.set(key, Math.floor(value));
25
32
  }
26
33
  }
34
+ const exhausted = raw && typeof raw === "object" && Array.isArray(raw.exhausted) ? raw.exhausted : [];
35
+ for (const key of exhausted) {
36
+ if (typeof key === "string" && key.length > 0) {
37
+ exhaustedUnits.add(key);
38
+ }
39
+ }
27
40
  }
28
41
  catch (err) {
29
42
  deps.logFailure(err);
@@ -32,15 +45,17 @@ export function hydrateCustomVerifyRetryCounts(s, deps) {
32
45
  }
33
46
  export function saveCustomVerifyRetryCounts(s, deps) {
34
47
  const retryCounts = s.verificationRetryCount;
48
+ const exhaustedUnits = ensureExhaustedVerificationUnits(s);
35
49
  const filePath = customVerifyRetryStatePath(s);
36
50
  try {
37
- if (!retryCounts || retryCounts.size === 0) {
51
+ if ((!retryCounts || retryCounts.size === 0) && (!exhaustedUnits || exhaustedUnits.size === 0)) {
38
52
  unlinkSync(filePath);
39
53
  return;
40
54
  }
41
55
  mkdirSync(customVerifyRetryStateDir(s), { recursive: true });
42
56
  atomicWriteSync(filePath, JSON.stringify({
43
57
  counts: Object.fromEntries(retryCounts),
58
+ exhausted: [...exhaustedUnits],
44
59
  updatedAt: new Date().toISOString(),
45
60
  }) + "\n");
46
61
  }
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * Leaf node in the import DAG.
5
5
  */
6
+ import { parseDispatchKey } from "./dispatch-key.js";
6
7
  import { summarizeLogs } from "../workflow-logger.js";
7
8
  import { getLatestForUnit } from "../db/unit-dispatches.js";
8
9
  /**
@@ -11,6 +12,18 @@ import { getLatestForUnit } from "../db/unit-dispatches.js";
11
12
  * and similar Node.js filesystem error messages.
12
13
  */
13
14
  const ENOENT_PATH_RE = /ENOENT[^']*'([^']+)'/;
15
+ function rowInsideRetryBudget(row) {
16
+ if (!row)
17
+ return false;
18
+ if (row.attempt_n >= row.max_attempts)
19
+ return false;
20
+ if (!row.next_run_at)
21
+ return false;
22
+ const nextRun = Date.parse(row.next_run_at);
23
+ if (!Number.isFinite(nextRun))
24
+ return false;
25
+ return nextRun > Date.now();
26
+ }
14
27
  /**
15
28
  * Phase B / codex review MEDIUM B3 — retry coupling.
16
29
  *
@@ -20,23 +33,26 @@ const ENOENT_PATH_RE = /ENOENT[^']*'([^']+)'/;
20
33
  * waiting on its own backoff. Suppress the stuck verdict in that case so
21
34
  * the retry budget can fully drain before we declare stuck.
22
35
  *
36
+ * Window keys are compound (`unitType:unitId`, legacy `unitType/unitId`)
37
+ * while the production dispatch ledger keys rows by the bare unit id with
38
+ * the unit type in its own column. Look the bare unit id up first (with a
39
+ * unit_type match — the production shape), then fall back to the full
40
+ * compound key (test fixtures / legacy rows).
41
+ *
23
42
  * Returns true if the dispatch ledger says we should suppress the stuck
24
43
  * signal; false (no suppression) when the ledger is unavailable or has
25
44
  * no opinion.
26
45
  */
27
46
  function retryBudgetSuppresses(unitKey) {
28
47
  try {
29
- const latest = getLatestForUnit(unitKey);
30
- if (!latest)
31
- return false;
32
- if (latest.attempt_n >= latest.max_attempts)
33
- return false;
34
- if (!latest.next_run_at)
35
- return false;
36
- const nextRun = Date.parse(latest.next_run_at);
37
- if (!Number.isFinite(nextRun))
38
- return false;
39
- return nextRun > Date.now();
48
+ const parsed = parseDispatchKey(unitKey);
49
+ if (parsed) {
50
+ const bare = getLatestForUnit(parsed.unitId);
51
+ if (bare && bare.unit_type === parsed.unitType && rowInsideRetryBudget(bare)) {
52
+ return true;
53
+ }
54
+ }
55
+ return rowInsideRetryBudget(getLatestForUnit(unitKey));
40
56
  }
41
57
  catch {
42
58
  return false;
@@ -66,6 +82,10 @@ export function detectStuck(window, _retryContext) {
66
82
  const suffix = loggerSummary ? ` — ${loggerSummary}` : "";
67
83
  const last = window[window.length - 1];
68
84
  const prev = window[window.length - 2];
85
+ // Rules 2 and 2b share one retry-budget verdict for `last.key` — compute it
86
+ // at most once per invocation (it hits the dispatch ledger).
87
+ let suppressionVerdict;
88
+ const suppressed = () => (suppressionVerdict ??= retryBudgetSuppresses(last.key));
69
89
  // Rule 1: Same error repeated consecutively
70
90
  if (last.error && prev.error && last.error === prev.error) {
71
91
  return {
@@ -77,7 +97,7 @@ export function detectStuck(window, _retryContext) {
77
97
  // says we're inside the retry-backoff window (codex MEDIUM B3).
78
98
  if (window.length >= 3) {
79
99
  const lastThree = window.slice(-3);
80
- if (lastThree.every((u) => u.key === last.key) && !retryBudgetSuppresses(last.key)) {
100
+ if (lastThree.every((u) => u.key === last.key) && !suppressed()) {
81
101
  return {
82
102
  stuck: true,
83
103
  reason: `${last.key} derived 3 consecutive times without progress${suffix}`,
@@ -87,7 +107,7 @@ export function detectStuck(window, _retryContext) {
87
107
  // Rule 2b: Same unit key 3+ times anywhere in the active window — same
88
108
  // retry-budget suppression as Rule 2.
89
109
  const countInWindow = window.filter((entry) => entry.key === last.key).length;
90
- if (countInWindow >= 3 && !retryBudgetSuppresses(last.key)) {
110
+ if (countInWindow >= 3 && !suppressed()) {
91
111
  return {
92
112
  stuck: true,
93
113
  reason: `${last.key} derived ${countInWindow} times in last ${window.length} attempts without progress${suffix}`,
@@ -0,0 +1,105 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Dispatch History module — the single home for the auto
3
+ // orchestrator's dispatch-decision window, cross-session rehydration, and
4
+ // stuck detection (#482 / #442 deepening).
5
+ /**
6
+ * auto/dispatch-history.ts — Dispatch History module.
7
+ *
8
+ * Owns the sliding window of recent dispatch decisions that the Auto
9
+ * Orchestration module consults for idempotency and stuck-loop detection.
10
+ *
11
+ * Before this module existed the orchestrator kept a private in-memory
12
+ * `dispatchKeyWindow: string[]` that was reset to `[]` in start()/resume().
13
+ * Because a fresh orchestrator is constructed per session, the window never
14
+ * saw dispatches from a previous session — a unit could be re-dispatched
15
+ * across session restarts indefinitely (issue #482: 146 re-dispatches of the
16
+ * same unit). This module rehydrates the window from the DB dispatch ledger
17
+ * (`unit_dispatches`, via getRecentUnitKeysForProjectRoot) so stuck detection
18
+ * survives process restarts, and it delegates the verdict to the full
19
+ * detect-stuck rule set (repeat-error / consecutive / oscillation / ENOENT,
20
+ * with retry-budget suppression) instead of the bare saturation count.
21
+ *
22
+ * Key format: the canonical dispatch key is `${unitType}:${unitId}`
23
+ * (e.g. "execute-task:M001/S01/T01"). The legacy auto/phases.ts path and the
24
+ * DB rehydration helper use `${unitType}/${unitId}`; normalizeDispatchKey
25
+ * converts those on rehydrate so one format lives in the window. The key
26
+ * grammar itself lives in auto/dispatch-key.ts and is re-exported here for
27
+ * import stability.
28
+ */
29
+ import { buildDispatchKey, normalizeDispatchKey } from "./dispatch-key.js";
30
+ import { detectStuck } from "./detect-stuck.js";
31
+ import { getLatestForUnit, getRecentUnitKeysForProjectRoot, } from "../db/unit-dispatches.js";
32
+ import { debugLog } from "../debug-logger.js";
33
+ export { buildDispatchKey, normalizeDispatchKey, parseDispatchKey } from "./dispatch-key.js";
34
+ /**
35
+ * Size of the dispatch-decision ring buffer. Mirrors the legacy
36
+ * `STUCK_WINDOW_SIZE` in auto/phases.ts so behaviour is preserved across the
37
+ * cutover (issue #5791).
38
+ */
39
+ export const STUCK_WINDOW_SIZE = 6;
40
+ function lookupLatestLedgerError(unitType, unitId) {
41
+ try {
42
+ const row = getLatestForUnit(unitId);
43
+ // The ledger keys rows by bare unit id; require a unit_type match so
44
+ // another unit type's error on the same id is never attached (it would
45
+ // trip the repeat-error rule spuriously).
46
+ if (!row || row.unit_type !== unitType)
47
+ return undefined;
48
+ return row.error_summary ?? undefined;
49
+ }
50
+ catch {
51
+ return undefined;
52
+ }
53
+ }
54
+ export function createDispatchHistory(options) {
55
+ const windowSize = options.windowSize ?? STUCK_WINDOW_SIZE;
56
+ let window = [];
57
+ return {
58
+ recordDispatch(unitType, unitId) {
59
+ const key = buildDispatchKey(unitType, unitId);
60
+ // Ledger errors only feed the repeat-error/ENOENT rules, which need a
61
+ // prior occurrence of the same unit in the window — first-dispatch
62
+ // advances (the common case) pay zero DB cost.
63
+ const error = window.some((entry) => entry.key === key)
64
+ ? lookupLatestLedgerError(unitType, unitId)
65
+ : undefined;
66
+ window.push({ key, error });
67
+ while (window.length > windowSize)
68
+ window.shift();
69
+ return key;
70
+ },
71
+ getRecentWindow() {
72
+ return window;
73
+ },
74
+ countMatching(key) {
75
+ return window.filter((entry) => entry.key === key).length;
76
+ },
77
+ detectStuck() {
78
+ return detectStuck(window);
79
+ },
80
+ rehydrate() {
81
+ const scopeId = options.resolveScopeId();
82
+ if (!scopeId)
83
+ return 0;
84
+ try {
85
+ const persisted = getRecentUnitKeysForProjectRoot(scopeId, windowSize);
86
+ if (persisted.length === 0)
87
+ return 0;
88
+ window = persisted.map(({ key }) => ({ key: normalizeDispatchKey(key) }));
89
+ while (window.length > windowSize)
90
+ window.shift();
91
+ return window.length;
92
+ }
93
+ catch (err) {
94
+ debugLog("dispatchHistory", {
95
+ phase: "rehydrate-failed",
96
+ error: err instanceof Error ? err.message : String(err),
97
+ });
98
+ return 0;
99
+ }
100
+ },
101
+ clearOnRecovery() {
102
+ window = [];
103
+ },
104
+ };
105
+ }
@@ -0,0 +1,37 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Dispatch-key grammar — the single home for building, parsing,
3
+ // and normalizing the auto orchestrator's dispatch keys.
4
+ /**
5
+ * auto/dispatch-key.ts — Dispatch-key grammar.
6
+ *
7
+ * Canonical key: `${unitType}:${unitId}` (e.g. "execute-task:M001/S01/T01").
8
+ * Legacy key: `${unitType}/${unitId}` (auto/phases.ts, DB rehydration). Unit
9
+ * ids themselves contain "/" (M001/S01/T01) — the first segment is the unit
10
+ * type.
11
+ *
12
+ * Leaf node in the import DAG: both dispatch-history.ts and detect-stuck.ts
13
+ * consume this grammar, so it lives below them.
14
+ */
15
+ /** Build the canonical dispatch key for a unit. One format, one home. */
16
+ export function buildDispatchKey(unitType, unitId) {
17
+ return `${unitType}:${unitId}`;
18
+ }
19
+ /** Split a canonical or legacy dispatch key into its unit type and id. */
20
+ export function parseDispatchKey(key) {
21
+ const colon = key.indexOf(":");
22
+ if (colon > 0) {
23
+ return { unitType: key.slice(0, colon), unitId: key.slice(colon + 1) };
24
+ }
25
+ const slash = key.indexOf("/");
26
+ if (slash > 0) {
27
+ return { unitType: key.slice(0, slash), unitId: key.slice(slash + 1) };
28
+ }
29
+ return null;
30
+ }
31
+ /** Normalize a legacy `${unitType}/${unitId}` key to the canonical format. */
32
+ export function normalizeDispatchKey(key) {
33
+ if (key.includes(":"))
34
+ return key;
35
+ const parsed = parseDispatchKey(key);
36
+ return parsed ? buildDispatchKey(parsed.unitType, parsed.unitId) : key;
37
+ }
@@ -13,7 +13,8 @@ import { mkdirSync, writeFileSync } from "node:fs";
13
13
  import { join } from "node:path";
14
14
  import { MAX_LOOP_ITERATIONS, } from "./types.js";
15
15
  import { _clearCurrentResolve } from "./resolve.js";
16
- import { runGuards, runFinalize, STUCK_WINDOW_SIZE } from "./phases.js";
16
+ import { runGuards, runFinalize } from "./phases.js";
17
+ import { STUCK_WINDOW_SIZE } from "./dispatch-history.js";
17
18
  import { debugLog } from "../debug-logger.js";
18
19
  import { isInfrastructureError, isTransientCooldownError, getCooldownRetryAfterMs, COOLDOWN_FALLBACK_WAIT_MS, MAX_COOLDOWN_RETRIES } from "./infra-errors.js";
19
20
  import { ModelPolicyDispatchBlockedError } from "../auto-model-selection.js";
@@ -278,6 +279,8 @@ export async function autoLoop(ctx, pi, s, deps, options) {
278
279
  const unitDispatchDeps = createExecutionGraphUnitDispatchDeps();
279
280
  // Load persisted stuck state so counters survive session restarts (#3704)
280
281
  const persisted = loadStuckState(s);
282
+ // Load persisted verification retry state so the exhausted-unit guard fires on restart (#651)
283
+ hydrateCustomVerifyRetryCounts(s, { logFailure: logCustomVerifyRetryLoadFailure });
281
284
  const loopState = {
282
285
  recentUnits: persisted.recentUnits,
283
286
  stuckRecoveryAttempts: persisted.stuckRecoveryAttempts,