@opengsd/gsd-pi 1.5.0-dev.553dc518 → 1.5.0-dev.c4c8885f

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 (302) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/extensions/ask-user-questions.js +1 -1
  3. package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +114 -36
  4. package/dist/resources/extensions/gsd/activity-log.js +1 -1
  5. package/dist/resources/extensions/gsd/auto/orchestrator.js +16 -10
  6. package/dist/resources/extensions/gsd/auto-artifact-paths.js +14 -5
  7. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +7 -2
  8. package/dist/resources/extensions/gsd/auto-dispatch.js +69 -17
  9. package/dist/resources/extensions/gsd/auto-model-selection.js +28 -1
  10. package/dist/resources/extensions/gsd/auto-post-unit.js +4 -3
  11. package/dist/resources/extensions/gsd/auto-prompts.js +29 -117
  12. package/dist/resources/extensions/gsd/auto-recovery.js +50 -6
  13. package/dist/resources/extensions/gsd/auto-start.js +54 -31
  14. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +12 -2
  15. package/dist/resources/extensions/gsd/auto-timers.js +9 -2
  16. package/dist/resources/extensions/gsd/auto.js +97 -31
  17. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +1 -1
  18. package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +67 -6
  19. package/dist/resources/extensions/gsd/codebase-generator.js +7 -1
  20. package/dist/resources/extensions/gsd/compat/compat-marker.js +44 -0
  21. package/dist/resources/extensions/gsd/config-overlay.js +7 -2
  22. package/dist/resources/extensions/gsd/db/engine.js +33 -0
  23. package/dist/resources/extensions/gsd/db/writers/cascades.js +20 -2
  24. package/dist/resources/extensions/gsd/db-adapter.js +30 -3
  25. package/dist/resources/extensions/gsd/docs/preferences-reference.md +3 -3
  26. package/dist/resources/extensions/gsd/flat-phase-migration.js +18 -0
  27. package/dist/resources/extensions/gsd/gsd-db.js +12 -11
  28. package/dist/resources/extensions/gsd/guided-flow.js +16 -7
  29. package/dist/resources/extensions/gsd/journal.js +1 -1
  30. package/dist/resources/extensions/gsd/markdown-renderer.js +34 -91
  31. package/dist/resources/extensions/gsd/md-importer.js +89 -23
  32. package/dist/resources/extensions/gsd/metrics.js +2 -2
  33. package/dist/resources/extensions/gsd/migration-auto-check.js +30 -3
  34. package/dist/resources/extensions/gsd/paths.js +87 -52
  35. package/dist/resources/extensions/gsd/post-unit-hooks.js +27 -0
  36. package/dist/resources/extensions/gsd/preferences-models.js +66 -15
  37. package/dist/resources/extensions/gsd/preferences-skills.js +10 -16
  38. package/dist/resources/extensions/gsd/preferences-validation.js +66 -10
  39. package/dist/resources/extensions/gsd/preferences.js +20 -0
  40. package/dist/resources/extensions/gsd/rule-registry.js +106 -10
  41. package/dist/resources/extensions/gsd/skill-discovery.js +36 -23
  42. package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +1 -10
  43. package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +3 -24
  44. package/dist/resources/extensions/gsd/state-reconciliation/index.js +10 -0
  45. package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -5
  46. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +16 -10
  47. package/dist/resources/extensions/gsd/tools/reopen-slice.js +5 -0
  48. package/dist/resources/extensions/gsd/tools/reopen-task.js +13 -5
  49. package/dist/resources/extensions/gsd/tools/validate-milestone.js +3 -8
  50. package/dist/resources/extensions/gsd/uat-dispatch.js +94 -0
  51. package/dist/resources/extensions/gsd/uok/audit-toggle.js +31 -2
  52. package/dist/resources/extensions/gsd/uok/audit.js +3 -0
  53. package/dist/resources/extensions/gsd/uok/gate-runner.js +22 -30
  54. package/dist/resources/extensions/gsd/uok/gitops.js +18 -15
  55. package/dist/resources/extensions/gsd/uok/kernel.js +120 -46
  56. package/dist/resources/extensions/gsd/uok/model-policy.js +17 -14
  57. package/dist/resources/extensions/gsd/workflow-event-ledger.js +1 -1
  58. package/dist/resources/extensions/gsd/workflow-logger.js +1 -1
  59. package/dist/resources/extensions/gsd/workflow-projections.js +9 -26
  60. package/dist/resources/extensions/gsd/workspace.js +5 -14
  61. package/dist/resources/extensions/shared/gsd-browser-cli.js +29 -0
  62. package/dist/resources/extensions/shared/interview-ui.js +62 -9
  63. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  64. package/dist/web/standalone/.next/BUILD_ID +1 -1
  65. package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
  66. package/dist/web/standalone/.next/build-manifest.json +2 -2
  67. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  68. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  85. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  86. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  87. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  88. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  89. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  90. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  91. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  92. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  93. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  94. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  95. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  96. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  97. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  98. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  99. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  100. package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js.nft.json +1 -1
  101. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  102. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  103. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  104. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  105. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  106. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  107. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  108. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  109. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  110. package/dist/web/standalone/.next/server/app/api/shutdown/route.js.nft.json +1 -1
  111. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  112. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  113. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  114. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  115. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  116. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  117. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  118. package/dist/web/standalone/.next/server/app/index.html +1 -1
  119. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  120. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  121. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  122. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  123. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  124. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  125. package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
  126. package/dist/web/standalone/.next/server/chunks/{1128.js → 1991.js} +1 -1
  127. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  128. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  129. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  130. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  131. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  132. package/dist/worktree-cli-create.d.ts +10 -0
  133. package/dist/worktree-cli-create.js +11 -0
  134. package/dist/worktree-cli-format.d.ts +12 -0
  135. package/dist/worktree-cli-format.js +38 -0
  136. package/dist/worktree-cli-plan.d.ts +16 -0
  137. package/dist/worktree-cli-plan.js +19 -0
  138. package/dist/worktree-cli-session.d.ts +13 -0
  139. package/dist/worktree-cli-session.js +18 -0
  140. package/dist/worktree-cli-status.d.ts +35 -0
  141. package/dist/worktree-cli-status.js +54 -0
  142. package/dist/worktree-cli.d.ts +1 -16
  143. package/dist/worktree-cli.js +28 -122
  144. package/integrations/hermes/open_gsd_hermes/gsd_client.py +1 -1
  145. package/integrations/hermes/pyproject.toml +1 -1
  146. package/package.json +1 -1
  147. package/packages/cloud-mcp-gateway/package.json +2 -2
  148. package/packages/contracts/package.json +1 -1
  149. package/packages/daemon/dist/orchestrator-agent.d.ts +21 -0
  150. package/packages/daemon/dist/orchestrator-agent.d.ts.map +1 -0
  151. package/packages/daemon/dist/orchestrator-agent.js +113 -0
  152. package/packages/daemon/dist/orchestrator-agent.js.map +1 -0
  153. package/packages/daemon/dist/orchestrator-tools.d.ts +12 -0
  154. package/packages/daemon/dist/orchestrator-tools.d.ts.map +1 -0
  155. package/packages/daemon/dist/orchestrator-tools.js +137 -0
  156. package/packages/daemon/dist/orchestrator-tools.js.map +1 -0
  157. package/packages/daemon/dist/orchestrator.d.ts +4 -56
  158. package/packages/daemon/dist/orchestrator.d.ts.map +1 -1
  159. package/packages/daemon/dist/orchestrator.js +13 -303
  160. package/packages/daemon/dist/orchestrator.js.map +1 -1
  161. package/packages/daemon/dist/orchestrator.test.js +11 -1
  162. package/packages/daemon/dist/orchestrator.test.js.map +1 -1
  163. package/packages/daemon/package.json +4 -4
  164. package/packages/gsd-agent-core/package.json +5 -5
  165. package/packages/gsd-agent-modes/package.json +7 -7
  166. package/packages/mcp-server/package.json +4 -4
  167. package/packages/native/package.json +1 -1
  168. package/packages/pi-agent-core/package.json +1 -1
  169. package/packages/pi-ai/package.json +1 -1
  170. package/packages/pi-coding-agent/dist/core/package-manager.d.ts +9 -0
  171. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  172. package/packages/pi-coding-agent/dist/core/package-manager.js +74 -53
  173. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  174. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +6 -0
  175. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  176. package/packages/pi-coding-agent/dist/core/resource-loader.js +30 -2
  177. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  178. package/packages/pi-coding-agent/dist/core/skill-directories.d.ts +66 -0
  179. package/packages/pi-coding-agent/dist/core/skill-directories.d.ts.map +1 -0
  180. package/packages/pi-coding-agent/dist/core/skill-directories.js +138 -0
  181. package/packages/pi-coding-agent/dist/core/skill-directories.js.map +1 -0
  182. package/packages/pi-coding-agent/dist/core/skills.d.ts +4 -1
  183. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  184. package/packages/pi-coding-agent/dist/core/skills.js +4 -1
  185. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  186. package/packages/pi-coding-agent/dist/index.d.ts +1 -0
  187. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  188. package/packages/pi-coding-agent/dist/index.js +1 -0
  189. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  190. package/packages/pi-coding-agent/package.json +7 -7
  191. package/packages/pi-tui/package.json +2 -2
  192. package/packages/rpc-client/package.json +2 -2
  193. package/pkg/package.json +1 -1
  194. package/src/resources/extensions/ask-user-questions.ts +1 -1
  195. package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +146 -33
  196. package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +25 -0
  197. package/src/resources/extensions/browser-tools/tests/managed-gsd-browser-tools.test.mjs +93 -0
  198. package/src/resources/extensions/gsd/activity-log.ts +1 -1
  199. package/src/resources/extensions/gsd/auto/orchestrator.ts +17 -11
  200. package/src/resources/extensions/gsd/auto-artifact-paths.ts +14 -4
  201. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +9 -2
  202. package/src/resources/extensions/gsd/auto-dispatch.ts +77 -16
  203. package/src/resources/extensions/gsd/auto-model-selection.ts +33 -1
  204. package/src/resources/extensions/gsd/auto-post-unit.ts +4 -2
  205. package/src/resources/extensions/gsd/auto-prompts.ts +53 -113
  206. package/src/resources/extensions/gsd/auto-recovery.ts +50 -6
  207. package/src/resources/extensions/gsd/auto-start.ts +58 -29
  208. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +12 -2
  209. package/src/resources/extensions/gsd/auto-timers.ts +9 -2
  210. package/src/resources/extensions/gsd/auto.ts +103 -30
  211. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +1 -1
  212. package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +66 -6
  213. package/src/resources/extensions/gsd/codebase-generator.ts +7 -1
  214. package/src/resources/extensions/gsd/compat/compat-marker.ts +47 -0
  215. package/src/resources/extensions/gsd/config-overlay.ts +7 -1
  216. package/src/resources/extensions/gsd/db/engine.ts +36 -0
  217. package/src/resources/extensions/gsd/db/writers/cascades.ts +23 -1
  218. package/src/resources/extensions/gsd/db-adapter.ts +29 -3
  219. package/src/resources/extensions/gsd/docs/preferences-reference.md +3 -3
  220. package/src/resources/extensions/gsd/flat-phase-migration.ts +20 -0
  221. package/src/resources/extensions/gsd/gsd-db.ts +20 -11
  222. package/src/resources/extensions/gsd/guided-flow.ts +19 -7
  223. package/src/resources/extensions/gsd/journal.ts +1 -1
  224. package/src/resources/extensions/gsd/markdown-renderer.ts +52 -103
  225. package/src/resources/extensions/gsd/md-importer.ts +93 -22
  226. package/src/resources/extensions/gsd/metrics.ts +2 -2
  227. package/src/resources/extensions/gsd/migration-auto-check.ts +32 -3
  228. package/src/resources/extensions/gsd/paths.ts +108 -51
  229. package/src/resources/extensions/gsd/post-unit-hooks.ts +32 -0
  230. package/src/resources/extensions/gsd/preferences-models.ts +74 -20
  231. package/src/resources/extensions/gsd/preferences-skills.ts +10 -16
  232. package/src/resources/extensions/gsd/preferences-types.ts +24 -1
  233. package/src/resources/extensions/gsd/preferences-validation.ts +77 -9
  234. package/src/resources/extensions/gsd/preferences.ts +20 -0
  235. package/src/resources/extensions/gsd/rule-registry.ts +111 -9
  236. package/src/resources/extensions/gsd/skill-discovery.ts +47 -23
  237. package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +1 -9
  238. package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +2 -22
  239. package/src/resources/extensions/gsd/state-reconciliation/index.ts +14 -0
  240. package/src/resources/extensions/gsd/tests/auto-artifact-paths.test.ts +169 -1
  241. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +41 -2
  242. package/src/resources/extensions/gsd/tests/auto-post-unit-artifact-diagnostic.test.ts +48 -1
  243. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +57 -0
  244. package/src/resources/extensions/gsd/tests/auto-start-project-milestone-reconcile.test.ts +29 -1
  245. package/src/resources/extensions/gsd/tests/auto-timeout-recovery-supervisor-inflight-guard.test.ts +159 -0
  246. package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +22 -0
  247. package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +3 -1
  248. package/src/resources/extensions/gsd/tests/compat-marker.test.ts +85 -0
  249. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +45 -0
  250. package/src/resources/extensions/gsd/tests/db-adapter.test.ts +39 -0
  251. package/src/resources/extensions/gsd/tests/db-writable-probe.test.ts +66 -0
  252. package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +27 -0
  253. package/src/resources/extensions/gsd/tests/flat-phase-renderer.test.ts +22 -1
  254. package/src/resources/extensions/gsd/tests/flat-phase-validation-path.test.ts +72 -32
  255. package/src/resources/extensions/gsd/tests/import-completion-lost-update.test.ts +196 -0
  256. package/src/resources/extensions/gsd/tests/integration/paths.test.ts +88 -1
  257. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +16 -15
  258. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +91 -0
  259. package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +2 -1
  260. package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +0 -1
  261. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +269 -0
  262. package/src/resources/extensions/gsd/tests/post-unit-retry-on-orchestrator-bridge.test.ts +8 -1
  263. package/src/resources/extensions/gsd/tests/preferences.test.ts +19 -0
  264. package/src/resources/extensions/gsd/tests/read-uat-gate-verdict.test.ts +54 -0
  265. package/src/resources/extensions/gsd/tests/reassess-handler.test.ts +49 -1
  266. package/src/resources/extensions/gsd/tests/reopen-slice.test.ts +52 -0
  267. package/src/resources/extensions/gsd/tests/rule-registry.test.ts +95 -1
  268. package/src/resources/extensions/gsd/tests/sibling-model-fallbacks.test.ts +189 -0
  269. package/src/resources/extensions/gsd/tests/single-field-model-thinking.test.ts +156 -0
  270. package/src/resources/extensions/gsd/tests/skill-discovery.test.ts +46 -0
  271. package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +6 -1
  272. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +32 -0
  273. package/src/resources/extensions/gsd/tests/supervisor-model-swap-inflight-guard.test.ts +181 -0
  274. package/src/resources/extensions/gsd/tests/supervisor-model-swap-thinking-clamp.test.ts +122 -0
  275. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +93 -0
  276. package/src/resources/extensions/gsd/tests/uat-dispatch.test.ts +211 -0
  277. package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +39 -0
  278. package/src/resources/extensions/gsd/tests/uok-kernel-path.test.ts +226 -2
  279. package/src/resources/extensions/gsd/tests/uok-model-policy.test.ts +6 -0
  280. package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +213 -2
  281. package/src/resources/extensions/gsd/tools/complete-milestone.ts +9 -5
  282. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +19 -8
  283. package/src/resources/extensions/gsd/tools/reopen-slice.ts +5 -0
  284. package/src/resources/extensions/gsd/tools/reopen-task.ts +13 -5
  285. package/src/resources/extensions/gsd/tools/validate-milestone.ts +13 -9
  286. package/src/resources/extensions/gsd/types.ts +23 -4
  287. package/src/resources/extensions/gsd/uat-dispatch.ts +176 -0
  288. package/src/resources/extensions/gsd/uok/audit-toggle.ts +32 -2
  289. package/src/resources/extensions/gsd/uok/audit.ts +2 -0
  290. package/src/resources/extensions/gsd/uok/gate-runner.ts +26 -36
  291. package/src/resources/extensions/gsd/uok/gitops.ts +21 -18
  292. package/src/resources/extensions/gsd/uok/kernel.ts +169 -48
  293. package/src/resources/extensions/gsd/uok/model-policy.ts +20 -17
  294. package/src/resources/extensions/gsd/workflow-event-ledger.ts +1 -1
  295. package/src/resources/extensions/gsd/workflow-logger.ts +1 -1
  296. package/src/resources/extensions/gsd/workflow-projections.ts +9 -24
  297. package/src/resources/extensions/gsd/workspace.ts +5 -21
  298. package/src/resources/extensions/shared/gsd-browser-cli.ts +29 -0
  299. package/src/resources/extensions/shared/interview-ui.ts +58 -11
  300. package/src/resources/extensions/shared/tests/interview-ui-border.test.ts +32 -0
  301. /package/dist/web/standalone/.next/static/{ZpFDazP-38PXBPiI_LdZf → NPMNi2HV8BuK3baQsEUhk}/_buildManifest.js +0 -0
  302. /package/dist/web/standalone/.next/static/{ZpFDazP-38PXBPiI_LdZf → NPMNi2HV8BuK3baQsEUhk}/_ssgManifest.js +0 -0
@@ -8,7 +8,7 @@
8
8
  * utility.
9
9
  */
10
10
  import { loadFile, parseContinue, parseSummary, loadActiveOverrides, formatOverridesSection } from "./files.js";
11
- import { hasVerdict, extractVerdict } from "./verdict-parser.js";
11
+ import { extractVerdict } from "./verdict-parser.js";
12
12
  import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
13
13
  import { resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveTasksDir, resolveTaskFiles, resolveTaskFile, relMilestoneFile, relSliceFile, relSlicePath, relMilestonePath, relTaskFile, resolveGsdRootFile, relGsdRootFile, resolveRuntimeFile, } from "./paths.js";
14
14
  import { resolveInlineLevel, loadEffectiveGSDPreferences } from "./preferences.js";
@@ -35,7 +35,8 @@ import { debugLog } from "./debug-logger.js";
35
35
  import { buildSkillActivationBlock, buildSkillDiscoveryVars } from "./skill-activation.js";
36
36
  import { findMilestoneIds } from "./milestone-ids.js";
37
37
  import { buildRunUatPresentationForType, RUN_UAT_TOOL_PRESENTATION_PLAN_ID } from "./tool-presentation-plan.js";
38
- import { classifyUatContentForRun, shouldDispatchUatForContent, } from "./uat-policy.js";
38
+ import { classifyUatContentForRun } from "./uat-policy.js";
39
+ import { checkNeedsRunUat as resolveNeedsRunUat } from "./uat-dispatch.js";
39
40
  import { buildWebAppUatGuidanceBlock } from "./web-app-uat.js";
40
41
  export { buildSkillActivationBlock, buildSkillDiscoveryVars };
41
42
  // ─── Preamble Cap ─────────────────────────────────────────────────────────────
@@ -1320,120 +1321,26 @@ export async function checkNeedsReassessment(base, mid, state) {
1320
1321
  return null;
1321
1322
  return { sliceId: lastDone };
1322
1323
  }
1324
+ export function getRoadmapCompletedSliceCandidates(roadmapContent) {
1325
+ return parseRoadmap(roadmapContent)
1326
+ .slices.filter((slice) => slice.done)
1327
+ .map((slice) => ({ sliceId: slice.id }))
1328
+ .reverse();
1329
+ }
1330
+ export async function loadRoadmapCompletedSliceCandidates(base, mid) {
1331
+ const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP");
1332
+ const roadmapContent = roadmapPath ? await loadFile(roadmapPath) : null;
1333
+ return roadmapContent ? getRoadmapCompletedSliceCandidates(roadmapContent) : [];
1334
+ }
1323
1335
  /**
1324
- * Check if the most recently completed slice needs a UAT run.
1325
- * Returns { sliceId, uatType } if UAT should be dispatched, null otherwise.
1326
- *
1327
- * Skips when:
1328
- * - No roadmap or no completed slices
1329
- * - uat_dispatch is not enabled and the UAT spec does not require runtime/browser evidence
1330
- * - No UAT file exists for the slice
1331
- * - UAT result file already exists (idempotent — already ran)
1332
- */
1333
- /**
1334
- * Resolve the effective UAT mode for the dispatch gate the same way
1335
- * `buildRunUatPrompt` does: classify the UAT body with the slice's SUMMARY as
1336
- * supplemental context, so a `browser-executable` UAT whose slice references a
1337
- * self-contained harness (`npm run test:uat`, `search-uat.mjs`, `npx
1338
- * playwright test`) is promoted to `runtime-executable` for the gate too.
1339
- *
1340
- * Without this, `checkNeedsRunUat` returns `browser-executable` while the
1341
- * prompt instructs runtime-only execution, causing the dispatch gate to
1342
- * require browser tools / warm up the browser daemon (or stop dispatch when
1343
- * browser MCP is unavailable) for UAT runs that never touch the browser. See
1344
- * cursor[bot] review on PR #696 for the M007/S01 regression.
1336
+ * Back-compat wrapper for callers that import UAT dispatch checks from the
1337
+ * prompt module. UAT dispatch architecture lives in `uat-dispatch.ts`; the
1338
+ * `state` parameter is intentionally ignored because DB/roadmap artifacts are
1339
+ * the source of truth for completed-slice discovery.
1345
1340
  */
1346
- async function resolveRunUatEffectiveType(base, mid, sliceId, uatContent) {
1347
- let summaryContent = "";
1348
- try {
1349
- const summaryPath = resolveSliceFile(base, mid, sliceId, "SUMMARY");
1350
- if (summaryPath) {
1351
- summaryContent = (await loadFile(summaryPath)) ?? "";
1352
- }
1353
- }
1354
- catch (err) {
1355
- logWarning("prompt", `resolveRunUatEffectiveType SUMMARY load failed: ${err instanceof Error ? err.message : String(err)}`);
1356
- }
1357
- return classifyUatContentForRun(uatContent, summaryContent).effectiveType;
1358
- }
1359
1341
  export async function checkNeedsRunUat(base, mid, state, prefs) {
1360
- // DB primary path — fall through to file-based when DB has no data for this milestone
1361
- try {
1362
- const { isDbAvailable, getMilestoneSlices } = await import("./gsd-db.js");
1363
- if (isDbAvailable()) {
1364
- const slices = getMilestoneSlices(mid);
1365
- if (slices.length > 0) {
1366
- const completedSlices = slices.filter(s => s.status === "complete");
1367
- if (completedSlices.length === 0)
1368
- return null;
1369
- for (const completed of [...completedSlices].reverse()) {
1370
- const sid = completed.id;
1371
- const uatFile = resolveSliceFile(base, mid, sid, "UAT");
1372
- if (!uatFile)
1373
- continue;
1374
- const uatContent = await loadFile(uatFile);
1375
- if (!uatContent)
1376
- continue;
1377
- // If the UAT file already contains a verdict, UAT has been run — skip
1378
- if (hasVerdict(uatContent))
1379
- continue;
1380
- // Also check the ASSESSMENT file — the run-uat prompt writes the verdict
1381
- // there (via gsd_uat_result_save), not into the
1382
- // UAT spec file. Without this check the unit re-dispatches indefinitely.
1383
- const assessmentFile = resolveSliceFile(base, mid, sid, "ASSESSMENT");
1384
- if (assessmentFile) {
1385
- const assessmentContent = await loadFile(assessmentFile);
1386
- if (assessmentContent && hasVerdict(assessmentContent))
1387
- continue;
1388
- }
1389
- if (!shouldDispatchUatForContent(uatContent, prefs))
1390
- continue;
1391
- const uatType = await resolveRunUatEffectiveType(base, mid, sid, uatContent);
1392
- return { sliceId: sid, uatType };
1393
- }
1394
- return null;
1395
- }
1396
- }
1397
- }
1398
- catch (err) {
1399
- logWarning("prompt", `checkNeedsRunUat DB lookup failed: ${err instanceof Error ? err.message : String(err)}`);
1400
- }
1401
- // File-based fallback using roadmap checkboxes
1402
- const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP");
1403
- if (!roadmapPath)
1404
- return null;
1405
- const roadmapContent = await loadFile(roadmapPath);
1406
- if (!roadmapContent)
1407
- return null;
1408
- const parsed = parseRoadmap(roadmapContent);
1409
- const completedFileSlices = parsed.slices.filter(s => s.done);
1410
- if (completedFileSlices.length === 0)
1411
- return null;
1412
- for (const completed of [...completedFileSlices].reverse()) {
1413
- const uatSid = completed.id;
1414
- const uatFileFb = resolveSliceFile(base, mid, uatSid, "UAT");
1415
- if (!uatFileFb)
1416
- continue;
1417
- const uatContentFb = await loadFile(uatFileFb);
1418
- if (!uatContentFb)
1419
- continue;
1420
- // If the UAT file already contains a verdict, UAT has been run — skip
1421
- if (hasVerdict(uatContentFb))
1422
- continue;
1423
- // Also check the ASSESSMENT file for the file-based fallback path (same
1424
- // reason as the DB path above — verdict lives in ASSESSMENT, not UAT).
1425
- const assessmentFileFb = resolveSliceFile(base, mid, uatSid, "ASSESSMENT");
1426
- if (assessmentFileFb) {
1427
- const assessmentContentFb = await loadFile(assessmentFileFb);
1428
- if (assessmentContentFb && hasVerdict(assessmentContentFb))
1429
- continue;
1430
- }
1431
- if (!shouldDispatchUatForContent(uatContentFb, prefs))
1432
- continue;
1433
- const uatType = await resolveRunUatEffectiveType(base, mid, uatSid, uatContentFb);
1434
- return { sliceId: uatSid, uatType };
1435
- }
1436
- return null;
1342
+ void state;
1343
+ return resolveNeedsRunUat(base, mid, prefs, await loadRoadmapCompletedSliceCandidates(base, mid));
1437
1344
  }
1438
1345
  export async function buildDiscussMilestoneInlinedContext(mid, base) {
1439
1346
  const inlined = [];
@@ -3401,10 +3308,15 @@ export async function buildReassessRoadmapPrompt(mid, midTitle, completedSliceId
3401
3308
  * that instructs the coordinator how to dispatch a `subagent` call. Either or
3402
3309
  * both may be absent (ADR-026 / #508).
3403
3310
  */
3404
- function subagentCallSuffix(model, thinking) {
3311
+ function subagentCallSuffix(model, thinking, fallbacks) {
3405
3312
  const parts = [];
3406
- if (model)
3407
- parts.push(`model: "${model}"`);
3313
+ if (model) {
3314
+ let modelHint = `model: "${model}"`;
3315
+ if (fallbacks && fallbacks.length > 0) {
3316
+ modelHint += ` (fallbacks, in order: ${fallbacks.map((f) => `"${f}"`).join(", ")})`;
3317
+ }
3318
+ parts.push(modelHint);
3319
+ }
3408
3320
  if (thinking)
3409
3321
  parts.push(`thinking: "${thinking}"`);
3410
3322
  return parts.length > 0 ? ` with ${parts.join(" and ")}` : "";
@@ -3490,7 +3402,7 @@ opts) {
3490
3402
  "",
3491
3403
  `When done, say: "Task ${tid} complete."`,
3492
3404
  ].join("\n");
3493
- const modelSuffix = subagentCallSuffix(subagentModel, opts?.subagentThinking);
3405
+ const modelSuffix = subagentCallSuffix(subagentModel, opts?.subagentThinking, opts?.subagentModelFallbacks);
3494
3406
  subagentSections.push([
3495
3407
  `### ${tid}: ${tTitle}`,
3496
3408
  "",
@@ -149,12 +149,56 @@ export function refreshRecoveryDbForArtifact(unitType, unitId, basePath) {
149
149
  };
150
150
  }
151
151
  if (!isClosedStatus(task.status)) {
152
- return {
153
- ok: false,
154
- fatal: true,
155
- reason: "execute-task-artifact-db-mismatch",
156
- message: `Stuck recovery found execute-task ${unitId} artifacts, but the DB task status is still '${task.status}' after refresh.`,
157
- };
152
+ // The artifact was already confirmed on disk by verifyExpectedArtifact
153
+ // upstream (Gate 1 accepts a checked disk checkbox as proof of completion),
154
+ // but the DB task row is still open. A bare refreshWorkflowDatabaseFromDisk
155
+ // never promotes the disk checkbox into task status (the checkbox is
156
+ // rendered *from* the DB, never *into* it), so this divergence can never
157
+ // self-heal. Promote the task to align the DB with disk instead of failing
158
+ // shut — otherwise the lowest-pending derivation re-picks the same task
159
+ // forever and execute-task hard-stops with "state did not advance" (#1204).
160
+ // Mirrors the promotion pattern used by the context-exhaustion and
161
+ // reactive-execute-blocker recovery paths.
162
+ const ts = new Date().toISOString();
163
+ // Only the DB promotion is load-bearing: if it throws, nothing advanced
164
+ // and failing shut is correct. The plan checkbox rewrite and cache clears
165
+ // are best-effort follow-ups — a throw there must NOT abort recovery, or
166
+ // the DB is left complete without ever emitting the stuck-artifact-recovery
167
+ // event (#1221), stranding worktree reconciliation.
168
+ try {
169
+ updateTaskStatus(mid, sid, tid, "complete", ts);
170
+ }
171
+ catch (e) {
172
+ return {
173
+ ok: false,
174
+ fatal: true,
175
+ reason: "execute-task-artifact-db-promote-failed",
176
+ message: `Stuck recovery found execute-task ${unitId} artifacts, but promoting the DB task status from '${task.status}' failed: ${getErrorMessage(e)}`,
177
+ };
178
+ }
179
+ try {
180
+ const artifactBase = resolveArtifactVerificationBase(unitId, basePath);
181
+ const planPath = resolveExpectedArtifactPath("plan-slice", `${mid}/${sid}`, artifactBase);
182
+ if (planPath && existsSync(planPath)) {
183
+ const planContent = readFileSync(planPath, "utf-8");
184
+ const updatedPlan = planContent.replace(new RegExp(`^(\\s*-\\s+)\\[ \\]\\s+\\*\\*${tid}:`, "m"), `$1[x] **${tid}:`);
185
+ if (updatedPlan !== planContent) {
186
+ atomicWriteSync(planPath, updatedPlan);
187
+ }
188
+ }
189
+ clearPathCache();
190
+ clearParseCache();
191
+ }
192
+ catch (e) {
193
+ logWarning("recovery", `stuck-artifact plan checkbox sync failed after DB promotion of ${unitId}: ${getErrorMessage(e)}`);
194
+ }
195
+ // Append event so worktree reconciliation can replay this recovery completion.
196
+ try {
197
+ appendEvent(basePath, { cmd: "complete-task", params: { milestoneId: mid, sliceId: sid, taskId: tid }, ts, actor: "system", trigger_reason: "stuck-artifact-recovery" });
198
+ }
199
+ catch (e) {
200
+ logWarning("recovery", `appendEvent failed for stuck-artifact task promotion: ${getErrorMessage(e)}`);
201
+ }
158
202
  }
159
203
  return { ok: true };
160
204
  }
@@ -36,10 +36,10 @@ import { emitWorktreeOrphaned } from "./worktree-telemetry.js";
36
36
  import { queryJournal } from "./journal.js";
37
37
  import { initMetrics } from "./metrics.js";
38
38
  import { initRoutingHistory } from "./routing-history.js";
39
- import { restoreHookState, resetHookState } from "./post-unit-hooks.js";
39
+ import { restoreHookState, resetHookState, reconcileRestoredHookDispatch } from "./post-unit-hooks.js";
40
40
  import { resetProactiveHealing, setLevelChangeCallback } from "./doctor-proactive.js";
41
41
  import { snapshotSkills } from "./skill-discovery.js";
42
- import { isDbAvailable, getMilestone, getAllMilestones, insertMilestone, updateMilestoneStatus } from "./gsd-db.js";
42
+ import { isDbAvailable, probeDbWritable, getMilestone, getAllMilestones, insertMilestone, updateMilestoneStatus } from "./gsd-db.js";
43
43
  import { getWorkflowDatabaseStatus, openExistingWorkflowDatabase, openWorkflowDatabase, resolveProjectRootDbPath, } from "./db-workspace.js";
44
44
  import { isClosedStatus } from "./status-guards.js";
45
45
  import { classifyMilestoneSummaryContent } from "./milestone-summary-classifier.js";
@@ -119,36 +119,42 @@ export function reconcileProjectMilestonesFromDisk(basePath) {
119
119
  export function reconcileMergedMilestonesFromJournal(basePath) {
120
120
  if (!isDbAvailable())
121
121
  return 0;
122
- const mergedAtByMilestone = new Map();
123
- for (const entry of queryJournal(basePath, { eventType: "worktree-merged" })) {
124
- const data = entry.data ?? {};
125
- const milestoneId = typeof data.milestoneId === "string" ? data.milestoneId : null;
126
- if (!milestoneId)
127
- continue;
128
- if (data.conflict === true)
129
- continue;
130
- const endedAt = typeof data.endedAt === "string" ? data.endedAt : entry.ts;
131
- const previous = mergedAtByMilestone.get(milestoneId);
132
- if (!previous || endedAt > previous)
133
- mergedAtByMilestone.set(milestoneId, endedAt);
134
- }
135
- let closed = 0;
136
- for (const [milestoneId, completedAt] of mergedAtByMilestone) {
137
- const existing = getMilestone(milestoneId);
138
- if (!existing) {
139
- insertMilestone({ id: milestoneId, title: milestoneId, status: "complete" });
140
- updateMilestoneStatus(milestoneId, "complete", completedAt);
141
- closed++;
142
- continue;
143
- }
144
- if (!isClosedStatus(existing.status)) {
145
- updateMilestoneStatus(milestoneId, "complete", completedAt);
146
- closed++;
122
+ try {
123
+ const mergedAtByMilestone = new Map();
124
+ for (const entry of queryJournal(basePath, { eventType: "worktree-merged" })) {
125
+ const data = entry.data ?? {};
126
+ const milestoneId = typeof data.milestoneId === "string" ? data.milestoneId : null;
127
+ if (!milestoneId)
128
+ continue;
129
+ if (data.conflict === true)
130
+ continue;
131
+ const endedAt = typeof data.endedAt === "string" ? data.endedAt : entry.ts;
132
+ const previous = mergedAtByMilestone.get(milestoneId);
133
+ if (!previous || endedAt > previous)
134
+ mergedAtByMilestone.set(milestoneId, endedAt);
135
+ }
136
+ let closed = 0;
137
+ for (const [milestoneId, completedAt] of mergedAtByMilestone) {
138
+ const existing = getMilestone(milestoneId);
139
+ if (!existing) {
140
+ insertMilestone({ id: milestoneId, title: milestoneId, status: "complete" });
141
+ updateMilestoneStatus(milestoneId, "complete", completedAt);
142
+ closed++;
143
+ continue;
144
+ }
145
+ if (!isClosedStatus(existing.status)) {
146
+ updateMilestoneStatus(milestoneId, "complete", completedAt);
147
+ closed++;
148
+ }
147
149
  }
150
+ if (closed > 0)
151
+ invalidateAllCaches();
152
+ return closed;
153
+ }
154
+ catch (err) {
155
+ logWarning("bootstrap", `merged-milestone journal reconciliation failed: ${err instanceof Error ? err.message : String(err)}`);
156
+ return 0;
148
157
  }
149
- if (closed > 0)
150
- invalidateAllCaches();
151
- return closed;
152
158
  }
153
159
  export function decideSurvivorAction(hasSurvivorBranch, phase) {
154
160
  if (!hasSurvivorBranch)
@@ -1254,6 +1260,10 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
1254
1260
  s.unitLifetimeDispatches.clear();
1255
1261
  resetHookState();
1256
1262
  restoreHookState(base);
1263
+ // A restored activeHook has no live dispatch (the sidecar queue is not
1264
+ // persisted); re-enqueue it so the hook runs instead of blocking the next
1265
+ // unrelated unit's close-out (#1246).
1266
+ reconcileRestoredHookDispatch(base, s.sidecarQueue);
1257
1267
  resetProactiveHealing();
1258
1268
  // Notify user on health level transitions (green→yellow→red and back)
1259
1269
  setLevelChangeCallback((_from, to, summary) => {
@@ -1377,6 +1387,19 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
1377
1387
  ctx.ui.notify(`SQLite database exists but failed to open: ${gsdDbPath}. ${phaseHint}${errorDetail}.${providerHint}`, "error");
1378
1388
  return releaseLockAndReturn();
1379
1389
  }
1390
+ // Gate: confirm the handle is actually writable, not just open. A
1391
+ // schema-current DB does zero writes during open, so a read-only /
1392
+ // DBMOVED handle otherwise passes the check above and only fails much
1393
+ // later at the first authoritative write (the uok-kernel-enter audit)
1394
+ // with an opaque "readonly database" error (#1234).
1395
+ if (existsSync(gsdDbPath) && isDbAvailable()) {
1396
+ const writable = probeDbWritable();
1397
+ if (!writable.ok) {
1398
+ const detail = writable.detail ? ` (${writable.detail})` : "";
1399
+ ctx.ui.notify(`SQLite database is not writable: ${gsdDbPath}.${detail} Check file/WAL permissions or reopen a stale handle before running auto-mode.`, "error");
1400
+ return releaseLockAndReturn();
1401
+ }
1402
+ }
1380
1403
  // Initialize metrics
1381
1404
  initMetrics(s.basePath);
1382
1405
  // Initialize routing history
@@ -1410,7 +1433,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
1410
1433
  }
1411
1434
  // Snapshot installed skills
1412
1435
  if (resolveSkillDiscoveryMode(base) !== "off") {
1413
- snapshotSkills();
1436
+ snapshotSkills({ cwd: s.basePath });
1414
1437
  }
1415
1438
  setAutoActiveStatus(ctx, s.stepMode ? "next" : "auto");
1416
1439
  ctx.ui.setWidget("gsd-health", undefined);
@@ -8,6 +8,8 @@ import { resolveExpectedArtifactPath, diagnoseExpectedArtifact, verifyExpectedAr
8
8
  import { existsSync } from "node:fs";
9
9
  import { bumpAndResolveSynthetic } from "./auto/resolve.js";
10
10
  import { finalizeProjectResearchTimeout } from "./project-research-policy.js";
11
+ import { applySupervisorModelIfConfigured } from "./auto-model-selection.js";
12
+ import { getInFlightToolCount } from "./auto-tool-tracking.js";
11
13
  export async function recoverTimedOutUnit(ctx, pi, unitType, unitId, reason, rctx) {
12
14
  // Note on turn epoch: the bump is intentionally NOT unconditional at
13
15
  // function entry. Two branches below (the "steering retry" paths) keep
@@ -79,11 +81,15 @@ export async function recoverTimedOutUnit(ctx, pi, unitType, unitId, reason, rct
79
81
  "Immediately finish the required durable output for this unit.",
80
82
  "If full completion is impossible, write the partial artifact/state needed for recovery and make the blocker explicit.",
81
83
  ];
84
+ const recoveryTrigger = getInFlightToolCount() === 0;
85
+ if (recoveryTrigger) {
86
+ await applySupervisorModelIfConfigured(ctx, pi, basePath);
87
+ }
82
88
  pi.sendMessage({
83
89
  customType: "gsd-auto-timeout-recovery",
84
90
  display: verbose,
85
91
  content: steeringLines.join("\n"),
86
- }, { triggerTurn: true, deliverAs: "steer" });
92
+ }, { triggerTurn: recoveryTrigger, deliverAs: "steer" });
87
93
  ctx.ui.notify(`${reason === "idle" ? "Idle" : "Timeout"} recovery: steering ${unitType} ${unitId} to finish durable output (attempt ${attemptNumber}, session ${recoveryAttempts + 1}/${maxRecoveryAttempts}).`, "warning");
88
94
  return "recovered";
89
95
  }
@@ -176,11 +182,15 @@ export async function recoverTimedOutUnit(ctx, pi, unitType, unitId, reason, rct
176
182
  "Write the required artifact now.",
177
183
  "If blocked, write the partial artifact and explicitly record the blocker instead of going silent.",
178
184
  ];
185
+ const recoveryTrigger = getInFlightToolCount() === 0;
186
+ if (recoveryTrigger) {
187
+ await applySupervisorModelIfConfigured(ctx, pi, basePath);
188
+ }
179
189
  pi.sendMessage({
180
190
  customType: "gsd-auto-timeout-recovery",
181
191
  display: verbose,
182
192
  content: steeringLines.join("\n"),
183
- }, { triggerTurn: true, deliverAs: "steer" });
193
+ }, { triggerTurn: recoveryTrigger, deliverAs: "steer" });
184
194
  ctx.ui.notify(`${reason === "idle" ? "Idle" : "Timeout"} recovery: steering ${unitType} ${unitId} to produce ${expected} (attempt ${attemptNumber}, session ${recoveryAttempts + 1}/${maxRecoveryAttempts}).`, "warning");
185
195
  return "recovered";
186
196
  }
@@ -11,6 +11,7 @@ import { resolveAutoSupervisorConfig } from "./preferences.js";
11
11
  import { computeBudgets, resolveExecutorContextWindow } from "./context-budget.js";
12
12
  import { getInFlightToolCount, getOldestInFlightToolStart, clearInFlightTools, hasInteractiveToolInFlight, } from "./auto-tool-tracking.js";
13
13
  import { detectWorkingTreeActivity } from "./auto-supervisor.js";
14
+ import { applySupervisorModelIfConfigured } from "./auto-model-selection.js";
14
15
  import { closeoutUnit } from "./auto-unit-closeout.js";
15
16
  import { saveActivityLog } from "./activity-log.js";
16
17
  import { recoverTimedOutUnit } from "./auto-timeout-recovery.js";
@@ -113,7 +114,7 @@ export function startUnitSupervision(sctx) {
113
114
  ? supervisor.stalled_tool_timeout_minutes * 60 * 1000
114
115
  : idleTimeoutMs;
115
116
  // ── 1. Soft timeout warning ──
116
- s.wrapupWarningHandle = setTimeout(() => {
117
+ s.wrapupWarningHandle = setTimeout(async () => {
117
118
  s.wrapupWarningHandle = null;
118
119
  if (!s.active || !s.currentUnit)
119
120
  return;
@@ -125,6 +126,9 @@ export function startUnitSupervision(sctx) {
125
126
  // Triggering during active tool calls causes tool results to be skipped
126
127
  // with "Skipped due to queued user message", leading to provider errors (#3512).
127
128
  const softTrigger = getInFlightToolCount() === 0;
129
+ if (softTrigger) {
130
+ await applySupervisorModelIfConfigured(ctx, pi, s.basePath);
131
+ }
128
132
  pi.sendMessage({
129
133
  customType: "gsd-auto-wrapup",
130
134
  display: s.verbose,
@@ -291,7 +295,7 @@ export function startUnitSupervision(sctx) {
291
295
  }
292
296
  const executorContextWindow = resolveExecutorContextWindow(ctx.modelRegistry, prefs, ctx.model?.contextWindow, ctx.model?.provider);
293
297
  const continueHereThreshold = computeBudgets(executorContextWindow).continueThresholdPercent;
294
- s.continueHereHandle = setInterval(() => {
298
+ s.continueHereHandle = setInterval(async () => {
295
299
  if (!s.active || !s.currentUnit || !s.cmdCtx)
296
300
  return;
297
301
  const runtime = readUnitRuntimeRecord(s.basePath, unitType, unitId);
@@ -316,6 +320,9 @@ export function startUnitSupervision(sctx) {
316
320
  }
317
321
  // Only trigger a new turn if no tools are currently in flight (#3512).
318
322
  const contextTrigger = getInFlightToolCount() === 0;
323
+ if (contextTrigger) {
324
+ await applySupervisorModelIfConfigured(ctx, pi, s.basePath);
325
+ }
319
326
  pi.sendMessage({
320
327
  customType: "gsd-auto-wrapup",
321
328
  display: s.verbose,
@@ -30,9 +30,10 @@ import { playNotificationBell, sendDesktopNotification } from "./notifications.j
30
30
  import { getBudgetAlertLevel, getNewBudgetAlertLevel, getBudgetEnforcementAction, HARD_CONTEXT_REROOT_THRESHOLD_PERCENT, resolveCompactionThresholdPercent, shouldRerootStepSessionForContext, shouldWarnStepSessionForContext, } from "./auto-budget.js";
31
31
  import { markToolStart as _markToolStart, markToolEnd as _markToolEnd, getOldestInFlightToolAgeMs as _getOldestInFlightToolAgeMs, clearInFlightTools, isToolInvocationError, isQueuedUserMessageSkip, isDeterministicPolicyError, } from "./auto-tool-tracking.js";
32
32
  import { closeoutUnit } from "./auto-unit-closeout.js";
33
- import { selectAndApplyModel, resolveModelId, clearToolBaseline } from "./auto-model-selection.js";
33
+ import { selectAndApplyModel, resolveModelId, clearToolBaseline, isModelUnavailable, applyThinkingLevelForModel } from "./auto-model-selection.js";
34
+ import { resolveModelWithFallbacksForUnit } from "./preferences-models.js";
34
35
  import { resetRoutingHistory, recordOutcome } from "./routing-history.js";
35
- import { resetHookState, runPreDispatchHooks, restoreHookState, clearPersistedHookState, } from "./post-unit-hooks.js";
36
+ import { resetHookState, runPreDispatchHooks, restoreHookState, reconcileRestoredHookDispatch, clearPersistedHookState, } from "./post-unit-hooks.js";
36
37
  import { runGSDDoctor, rebuildState } from "./doctor.js";
37
38
  import { preDispatchHealthGate, resetProactiveHealing, setLevelChangeCallback, } from "./doctor-proactive.js";
38
39
  import { clearSkillSnapshot } from "./skill-discovery.js";
@@ -332,11 +333,24 @@ function withDetachedAutoKeepalive(run) {
332
333
  }
333
334
  export const _withDetachedAutoKeepaliveForTest = withDetachedAutoKeepalive;
334
335
  export function startAutoDetached(ctx, pi, base, verboseMode, options) {
335
- void withDetachedAutoKeepalive(startAuto(ctx, pi, base, verboseMode, options)).catch((err) => {
336
+ void withDetachedAutoKeepalive(startAuto(ctx, pi, base, verboseMode, options)).catch(async (err) => {
336
337
  const message = getErrorMessage(err);
337
338
  ctx.ui.notify(`Auto-start failed: ${message}`, "error");
338
339
  logWarning("engine", `auto start error: ${message}`, { file: "auto.ts" });
339
340
  debugLog("auto-start-failed", { error: message });
341
+ // Backstop cleanup (#1235): if startAuto threw after auto-mode was activated
342
+ // (e.g. an exception during bootstrap, before the loop's try/finally could
343
+ // run cleanupAfterLoopExit), s.active and the on-disk auto lock leak, so the
344
+ // re-entry guard silently no-ops every later /gsd auto until restart. Clear
345
+ // the leaked activation here so a failed start is always recoverable.
346
+ if (s.active) {
347
+ try {
348
+ await cleanupAfterLoopExit(ctx);
349
+ }
350
+ catch (cleanupErr) {
351
+ logWarning("engine", `auto start cleanup failed: ${cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr)}`, { file: "auto.ts" });
352
+ }
353
+ }
340
354
  });
341
355
  }
342
356
  /** Returns true if the project is configured for `isolation:worktree` mode. */
@@ -2175,6 +2189,10 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
2175
2189
  ctx.ui.setWidget("gsd-health", undefined);
2176
2190
  ctx.ui.notify(s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "info");
2177
2191
  restoreHookState(s.basePath);
2192
+ // A restored activeHook has no live dispatch (the sidecar queue is not
2193
+ // persisted); re-enqueue it so the hook runs instead of blocking the next
2194
+ // unrelated unit's close-out (#1246).
2195
+ reconcileRestoredHookDispatch(s.basePath, s.sidecarQueue);
2178
2196
  // Re-sync managed resources on resume so long-lived auto sessions pick up
2179
2197
  // bundled extension updates before resume-time verification/state logic runs.
2180
2198
  // GSD_PKG_ROOT is set by loader.ts and points to the gsd-pi package root.
@@ -2228,15 +2246,19 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
2228
2246
  debugLog("resume-orchestration-resume", { error: err instanceof Error ? err.message : String(err) });
2229
2247
  }
2230
2248
  startAutoCommandPolling(s.basePath);
2231
- await runAutoLoopWithUok({
2232
- ctx,
2233
- pi,
2234
- s,
2235
- deps: loopDeps,
2236
- runKernelLoop: runUokKernelLoop,
2237
- runLegacyLoop: runLegacyAutoLoop,
2238
- });
2239
- await cleanupAfterLoopExit(ctx);
2249
+ try {
2250
+ await runAutoLoopWithUok({
2251
+ ctx,
2252
+ pi,
2253
+ s,
2254
+ deps: loopDeps,
2255
+ runKernelLoop: runUokKernelLoop,
2256
+ runLegacyLoop: runLegacyAutoLoop,
2257
+ });
2258
+ }
2259
+ finally {
2260
+ await cleanupAfterLoopExit(ctx);
2261
+ }
2240
2262
  return;
2241
2263
  }
2242
2264
  // ── Fresh start path — delegated to auto-start.ts ──
@@ -2252,8 +2274,16 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
2252
2274
  // for crash-recovery fidelity (#5405).
2253
2275
  registerAutoWorkerForSession(s, base);
2254
2276
  const ready = await bootstrapAutoSession(s, ctx, pi, base, verboseMode, requestedStepMode, bootstrapDeps, freshStartAssessment);
2255
- if (!ready)
2277
+ if (!ready) {
2278
+ // bootstrapAutoSession sets s.active = true (auto-start.ts) before several
2279
+ // post-activation bail-outs return false without a loop ever running (e.g.
2280
+ // the SQLite-unavailable gate). Without this, s.active and the auto lock
2281
+ // leak and every later /gsd auto silently no-ops via the re-entry guard
2282
+ // (#1235). Run the canonical cleanup so a failed bootstrap stays recoverable.
2283
+ if (s.active)
2284
+ await cleanupAfterLoopExit(ctx);
2256
2285
  return;
2286
+ }
2257
2287
  // Build scope after bootstrap has populated s.basePath / s.originalBasePath /
2258
2288
  // s.currentMilestoneId (including worktree setup inside bootstrapAutoSession).
2259
2289
  rebuildScope(s.basePath, s.currentMilestoneId);
@@ -2277,15 +2307,19 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
2277
2307
  }
2278
2308
  startAutoCommandPolling(s.basePath);
2279
2309
  // Dispatch the first unit
2280
- await runAutoLoopWithUok({
2281
- ctx,
2282
- pi,
2283
- s,
2284
- deps: loopDeps,
2285
- runKernelLoop: runUokKernelLoop,
2286
- runLegacyLoop: runLegacyAutoLoop,
2287
- });
2288
- await cleanupAfterLoopExit(ctx);
2310
+ try {
2311
+ await runAutoLoopWithUok({
2312
+ ctx,
2313
+ pi,
2314
+ s,
2315
+ deps: loopDeps,
2316
+ runKernelLoop: runUokKernelLoop,
2317
+ runLegacyLoop: runLegacyAutoLoop,
2318
+ });
2319
+ }
2320
+ finally {
2321
+ await cleanupAfterLoopExit(ctx);
2322
+ }
2289
2323
  }
2290
2324
  // describeNextUnit is imported from auto-dashboard.ts and re-exported
2291
2325
  export { describeNextUnit } from "./auto-dashboard.js";
@@ -2402,20 +2436,52 @@ export async function dispatchHookUnit(ctx, pi, hookName, triggerUnitType, trigg
2402
2436
  startedAt: hookStartedAt,
2403
2437
  workspaceRoot: s.basePath,
2404
2438
  });
2405
- if (hookModel) {
2406
- const availableModels = ctx.modelRegistry.getAvailable();
2407
- const match = resolveModelId(hookModel, availableModels, ctx.model?.provider);
2408
- if (match) {
2439
+ // Resolve the hook's model honoring the configured `fallbacks[]` chain
2440
+ // (#1229). The manual trigger path bypasses selectAndApplyModel, so resolve
2441
+ // the primary→fallbacks order here and apply the first candidate available in
2442
+ // the registry — a blocked or unconfigured primary then transparently drops
2443
+ // to a fallback instead of silently reverting to the session model.
2444
+ const availableModels = ctx.modelRegistry.getAvailable();
2445
+ const availableModelIds = availableModels.map((m) => `${m.provider}/${m.id}`);
2446
+ const hookModelConfig = resolveModelWithFallbacksForUnit(hookUnitType, targetBasePath, availableModelIds);
2447
+ const modelCandidates = hookModelConfig
2448
+ ? [hookModelConfig.primary, ...hookModelConfig.fallbacks]
2449
+ : hookModel
2450
+ ? [hookModel]
2451
+ : [];
2452
+ if (modelCandidates.length > 0) {
2453
+ let applied = false;
2454
+ for (const candidate of modelCandidates) {
2455
+ const match = resolveModelId(candidate, availableModels, ctx.model?.provider);
2456
+ if (!match)
2457
+ continue;
2458
+ // Skip models the runtime has marked blocked or temporarily unavailable
2459
+ // (e.g. a primary that just tripped a provider limit) so the configured
2460
+ // fallbacks[] chain actually engages — parity with auto-mode's
2461
+ // selectAndApplyModel, which consults the same blocked-models store (#1229).
2462
+ if (isModelUnavailable(targetBasePath, match.provider, match.id))
2463
+ continue;
2409
2464
  try {
2410
- await pi.setModel(match);
2465
+ if (await pi.setModel(match)) {
2466
+ // The manual trigger path bypasses selectAndApplyModel, so apply the
2467
+ // hook's per-field `thinking` (from `post_unit_hooks[].model`'s object
2468
+ // form) here against the just-set model rather than leaving the hook at
2469
+ // the session level (#1269). Absent → session level, unchanged.
2470
+ if (hookModelConfig?.thinking) {
2471
+ applyThinkingLevelForModel(pi, hookModelConfig.thinking, match, ctx);
2472
+ }
2473
+ applied = true;
2474
+ break;
2475
+ }
2411
2476
  }
2412
2477
  catch (err) {
2413
- /* non-fatal */
2414
- logWarning("dispatch", `hook model set failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
2478
+ /* non-fatal — try the next fallback */
2479
+ logWarning("dispatch", `hook model set failed for ${candidate}: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
2415
2480
  }
2416
2481
  }
2417
- else {
2418
- ctx.ui.notify(`Hook model "${hookModel}" not found in available models. Falling back to current session model. ` +
2482
+ if (!applied) {
2483
+ ctx.ui.notify(`Hook model${modelCandidates.length > 1 ? "s" : ""} "${modelCandidates.join(", ")}" not available. ` +
2484
+ `Falling back to current session model. ` +
2419
2485
  `Ensure the model is defined in models.json and has auth configured.`, "warning");
2420
2486
  }
2421
2487
  }