@opengsd/gsd-pi 1.5.0-dev.4fb2d460 → 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 (234) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +114 -36
  3. package/dist/resources/extensions/gsd/activity-log.js +1 -1
  4. package/dist/resources/extensions/gsd/auto/orchestrator.js +16 -10
  5. package/dist/resources/extensions/gsd/auto-artifact-paths.js +2 -2
  6. package/dist/resources/extensions/gsd/auto-dispatch.js +53 -15
  7. package/dist/resources/extensions/gsd/auto-model-selection.js +28 -1
  8. package/dist/resources/extensions/gsd/auto-post-unit.js +4 -3
  9. package/dist/resources/extensions/gsd/auto-prompts.js +29 -117
  10. package/dist/resources/extensions/gsd/auto-start.js +53 -30
  11. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +12 -2
  12. package/dist/resources/extensions/gsd/auto-timers.js +9 -2
  13. package/dist/resources/extensions/gsd/auto.js +97 -31
  14. package/dist/resources/extensions/gsd/compat/compat-marker.js +44 -0
  15. package/dist/resources/extensions/gsd/config-overlay.js +7 -2
  16. package/dist/resources/extensions/gsd/db/engine.js +33 -0
  17. package/dist/resources/extensions/gsd/db-adapter.js +30 -3
  18. package/dist/resources/extensions/gsd/docs/preferences-reference.md +3 -3
  19. package/dist/resources/extensions/gsd/journal.js +1 -1
  20. package/dist/resources/extensions/gsd/markdown-renderer.js +34 -91
  21. package/dist/resources/extensions/gsd/metrics.js +2 -2
  22. package/dist/resources/extensions/gsd/paths.js +70 -49
  23. package/dist/resources/extensions/gsd/post-unit-hooks.js +27 -0
  24. package/dist/resources/extensions/gsd/preferences-models.js +66 -15
  25. package/dist/resources/extensions/gsd/preferences-validation.js +66 -10
  26. package/dist/resources/extensions/gsd/rule-registry.js +106 -10
  27. package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +1 -10
  28. package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +3 -24
  29. package/dist/resources/extensions/gsd/state-reconciliation/index.js +10 -0
  30. package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -5
  31. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +16 -10
  32. package/dist/resources/extensions/gsd/tools/validate-milestone.js +3 -8
  33. package/dist/resources/extensions/gsd/uat-dispatch.js +94 -0
  34. package/dist/resources/extensions/gsd/uok/audit-toggle.js +31 -2
  35. package/dist/resources/extensions/gsd/uok/audit.js +3 -0
  36. package/dist/resources/extensions/gsd/uok/gate-runner.js +22 -30
  37. package/dist/resources/extensions/gsd/uok/gitops.js +18 -15
  38. package/dist/resources/extensions/gsd/uok/kernel.js +120 -46
  39. package/dist/resources/extensions/gsd/uok/model-policy.js +17 -14
  40. package/dist/resources/extensions/gsd/workflow-event-ledger.js +1 -1
  41. package/dist/resources/extensions/gsd/workflow-logger.js +1 -1
  42. package/dist/resources/extensions/gsd/workflow-projections.js +9 -26
  43. package/dist/resources/extensions/gsd/workspace.js +5 -14
  44. package/dist/resources/extensions/shared/gsd-browser-cli.js +29 -0
  45. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  46. package/dist/web/standalone/.next/BUILD_ID +1 -1
  47. package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
  48. package/dist/web/standalone/.next/build-manifest.json +2 -2
  49. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  50. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  51. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  59. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  67. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  68. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  69. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  70. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  71. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  72. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  73. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  74. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  75. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  76. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  77. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  78. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  79. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  80. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  81. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  82. package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js.nft.json +1 -1
  83. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  84. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  85. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  86. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  87. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  88. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  89. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  90. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  91. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  92. package/dist/web/standalone/.next/server/app/api/shutdown/route.js.nft.json +1 -1
  93. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  94. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  95. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  96. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  97. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  98. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  99. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  100. package/dist/web/standalone/.next/server/app/index.html +1 -1
  101. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  102. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  104. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  106. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  107. package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
  108. package/dist/web/standalone/.next/server/chunks/{1128.js → 1991.js} +1 -1
  109. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  110. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  111. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  112. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  113. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  114. package/dist/worktree-cli-create.d.ts +10 -0
  115. package/dist/worktree-cli-create.js +11 -0
  116. package/dist/worktree-cli-format.d.ts +12 -0
  117. package/dist/worktree-cli-format.js +38 -0
  118. package/dist/worktree-cli-plan.d.ts +16 -0
  119. package/dist/worktree-cli-plan.js +19 -0
  120. package/dist/worktree-cli-session.d.ts +13 -0
  121. package/dist/worktree-cli-session.js +18 -0
  122. package/dist/worktree-cli-status.d.ts +35 -0
  123. package/dist/worktree-cli-status.js +54 -0
  124. package/dist/worktree-cli.d.ts +1 -16
  125. package/dist/worktree-cli.js +28 -122
  126. package/integrations/hermes/open_gsd_hermes/gsd_client.py +1 -1
  127. package/integrations/hermes/pyproject.toml +1 -1
  128. package/package.json +1 -1
  129. package/packages/cloud-mcp-gateway/package.json +2 -2
  130. package/packages/contracts/package.json +1 -1
  131. package/packages/daemon/dist/orchestrator-agent.d.ts +21 -0
  132. package/packages/daemon/dist/orchestrator-agent.d.ts.map +1 -0
  133. package/packages/daemon/dist/orchestrator-agent.js +113 -0
  134. package/packages/daemon/dist/orchestrator-agent.js.map +1 -0
  135. package/packages/daemon/dist/orchestrator-tools.d.ts +12 -0
  136. package/packages/daemon/dist/orchestrator-tools.d.ts.map +1 -0
  137. package/packages/daemon/dist/orchestrator-tools.js +137 -0
  138. package/packages/daemon/dist/orchestrator-tools.js.map +1 -0
  139. package/packages/daemon/dist/orchestrator.d.ts +4 -56
  140. package/packages/daemon/dist/orchestrator.d.ts.map +1 -1
  141. package/packages/daemon/dist/orchestrator.js +13 -303
  142. package/packages/daemon/dist/orchestrator.js.map +1 -1
  143. package/packages/daemon/dist/orchestrator.test.js +11 -1
  144. package/packages/daemon/dist/orchestrator.test.js.map +1 -1
  145. package/packages/daemon/package.json +4 -4
  146. package/packages/gsd-agent-core/package.json +5 -5
  147. package/packages/gsd-agent-modes/package.json +7 -7
  148. package/packages/mcp-server/package.json +4 -4
  149. package/packages/native/package.json +1 -1
  150. package/packages/pi-agent-core/package.json +1 -1
  151. package/packages/pi-ai/package.json +1 -1
  152. package/packages/pi-coding-agent/package.json +7 -7
  153. package/packages/pi-tui/package.json +2 -2
  154. package/packages/rpc-client/package.json +2 -2
  155. package/pkg/package.json +1 -1
  156. package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +146 -33
  157. package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +25 -0
  158. package/src/resources/extensions/browser-tools/tests/managed-gsd-browser-tools.test.mjs +93 -0
  159. package/src/resources/extensions/gsd/activity-log.ts +1 -1
  160. package/src/resources/extensions/gsd/auto/orchestrator.ts +17 -11
  161. package/src/resources/extensions/gsd/auto-artifact-paths.ts +2 -1
  162. package/src/resources/extensions/gsd/auto-dispatch.ts +59 -14
  163. package/src/resources/extensions/gsd/auto-model-selection.ts +33 -1
  164. package/src/resources/extensions/gsd/auto-post-unit.ts +4 -2
  165. package/src/resources/extensions/gsd/auto-prompts.ts +53 -113
  166. package/src/resources/extensions/gsd/auto-start.ts +57 -28
  167. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +12 -2
  168. package/src/resources/extensions/gsd/auto-timers.ts +9 -2
  169. package/src/resources/extensions/gsd/auto.ts +103 -30
  170. package/src/resources/extensions/gsd/compat/compat-marker.ts +47 -0
  171. package/src/resources/extensions/gsd/config-overlay.ts +7 -1
  172. package/src/resources/extensions/gsd/db/engine.ts +36 -0
  173. package/src/resources/extensions/gsd/db-adapter.ts +29 -3
  174. package/src/resources/extensions/gsd/docs/preferences-reference.md +3 -3
  175. package/src/resources/extensions/gsd/journal.ts +1 -1
  176. package/src/resources/extensions/gsd/markdown-renderer.ts +52 -103
  177. package/src/resources/extensions/gsd/metrics.ts +2 -2
  178. package/src/resources/extensions/gsd/paths.ts +87 -48
  179. package/src/resources/extensions/gsd/post-unit-hooks.ts +32 -0
  180. package/src/resources/extensions/gsd/preferences-models.ts +74 -20
  181. package/src/resources/extensions/gsd/preferences-types.ts +24 -1
  182. package/src/resources/extensions/gsd/preferences-validation.ts +77 -9
  183. package/src/resources/extensions/gsd/rule-registry.ts +111 -9
  184. package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +1 -9
  185. package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +2 -22
  186. package/src/resources/extensions/gsd/state-reconciliation/index.ts +14 -0
  187. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +41 -2
  188. package/src/resources/extensions/gsd/tests/auto-post-unit-artifact-diagnostic.test.ts +48 -1
  189. package/src/resources/extensions/gsd/tests/auto-start-project-milestone-reconcile.test.ts +29 -1
  190. package/src/resources/extensions/gsd/tests/auto-timeout-recovery-supervisor-inflight-guard.test.ts +159 -0
  191. package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +3 -1
  192. package/src/resources/extensions/gsd/tests/compat-marker.test.ts +85 -0
  193. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +45 -0
  194. package/src/resources/extensions/gsd/tests/db-adapter.test.ts +39 -0
  195. package/src/resources/extensions/gsd/tests/db-writable-probe.test.ts +66 -0
  196. package/src/resources/extensions/gsd/tests/flat-phase-renderer.test.ts +22 -1
  197. package/src/resources/extensions/gsd/tests/flat-phase-validation-path.test.ts +72 -32
  198. package/src/resources/extensions/gsd/tests/integration/paths.test.ts +88 -1
  199. package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +2 -1
  200. package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +0 -1
  201. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +269 -0
  202. package/src/resources/extensions/gsd/tests/post-unit-retry-on-orchestrator-bridge.test.ts +8 -1
  203. package/src/resources/extensions/gsd/tests/read-uat-gate-verdict.test.ts +54 -0
  204. package/src/resources/extensions/gsd/tests/reassess-handler.test.ts +49 -1
  205. package/src/resources/extensions/gsd/tests/rule-registry.test.ts +95 -1
  206. package/src/resources/extensions/gsd/tests/sibling-model-fallbacks.test.ts +189 -0
  207. package/src/resources/extensions/gsd/tests/single-field-model-thinking.test.ts +156 -0
  208. package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +6 -1
  209. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +32 -0
  210. package/src/resources/extensions/gsd/tests/supervisor-model-swap-inflight-guard.test.ts +181 -0
  211. package/src/resources/extensions/gsd/tests/supervisor-model-swap-thinking-clamp.test.ts +122 -0
  212. package/src/resources/extensions/gsd/tests/uat-dispatch.test.ts +211 -0
  213. package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +39 -0
  214. package/src/resources/extensions/gsd/tests/uok-kernel-path.test.ts +226 -2
  215. package/src/resources/extensions/gsd/tests/uok-model-policy.test.ts +6 -0
  216. package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +213 -2
  217. package/src/resources/extensions/gsd/tools/complete-milestone.ts +9 -5
  218. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +19 -8
  219. package/src/resources/extensions/gsd/tools/validate-milestone.ts +13 -9
  220. package/src/resources/extensions/gsd/types.ts +23 -4
  221. package/src/resources/extensions/gsd/uat-dispatch.ts +176 -0
  222. package/src/resources/extensions/gsd/uok/audit-toggle.ts +32 -2
  223. package/src/resources/extensions/gsd/uok/audit.ts +2 -0
  224. package/src/resources/extensions/gsd/uok/gate-runner.ts +26 -36
  225. package/src/resources/extensions/gsd/uok/gitops.ts +21 -18
  226. package/src/resources/extensions/gsd/uok/kernel.ts +169 -48
  227. package/src/resources/extensions/gsd/uok/model-policy.ts +20 -17
  228. package/src/resources/extensions/gsd/workflow-event-ledger.ts +1 -1
  229. package/src/resources/extensions/gsd/workflow-logger.ts +1 -1
  230. package/src/resources/extensions/gsd/workflow-projections.ts +9 -24
  231. package/src/resources/extensions/gsd/workspace.ts +5 -21
  232. package/src/resources/extensions/shared/gsd-browser-cli.ts +29 -0
  233. /package/dist/web/standalone/.next/static/{gXoWec4UNqcR0tkuHwYCr → NPMNi2HV8BuK3baQsEUhk}/_buildManifest.js +0 -0
  234. /package/dist/web/standalone/.next/static/{gXoWec4UNqcR0tkuHwYCr → NPMNi2HV8BuK3baQsEUhk}/_ssgManifest.js +0 -0
@@ -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
@@ -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
  }
@@ -98,6 +98,50 @@ export function writeCompatMarker(basePath, marker) {
98
98
  writeFileSync(tmp, JSON.stringify(marker, null, 2), "utf-8");
99
99
  renameSync(tmp, path);
100
100
  }
101
+ /**
102
+ * Remove projection entries whose backing file no longer exists on disk.
103
+ *
104
+ * gsd-pi never deletes marker entries on its own: both drift detectors skip
105
+ * files missing from disk (`if (!existsSync(abs)) continue;`) and the write-time
106
+ * flush only ever adds or refreshes entries. So when a phase directory is
107
+ * renamed or removed (e.g. `phases/29-new-milestone-m029/` →
108
+ * `phases/29-frontend-code-debt-cleanup/`), the old projection paths linger in
109
+ * `.compat.json` forever as phantom entries pointing at directories that no
110
+ * longer exist (#1257). They never reconcile, and `.compat.json` keeps drifting
111
+ * from disk reality so `git status` stops being a reliable proxy for "did the
112
+ * engine touch my plans?".
113
+ *
114
+ * This prunes those orphaned entries across the `.gsd/` projections and the
115
+ * `.planning/` projections/passthrough maps. It is safe: a missing-file entry is
116
+ * inert (every detector already ignores it), and if the file is later
117
+ * re-projected the write-time flush / unseeded-file detection re-seeds an
118
+ * accurate baseline.
119
+ *
120
+ * Returns the number of entries removed; when nothing is orphaned the marker is
121
+ * left untouched (no needless write, no `lastProjectedAt` churn).
122
+ */
123
+ export function pruneOrphanedProjectionEntries(basePath) {
124
+ if (!existsSync(compatMarkerPath(basePath)))
125
+ return 0;
126
+ const marker = readCompatMarker(basePath);
127
+ let removed = 0;
128
+ const pruneMap = (map, root) => {
129
+ for (const relPath of Object.keys(map)) {
130
+ if (!existsSync(join(basePath, root, relPath))) {
131
+ delete map[relPath];
132
+ removed++;
133
+ }
134
+ }
135
+ };
136
+ pruneMap(marker.projections, ".gsd");
137
+ if (marker.planning) {
138
+ pruneMap(marker.planning.projections, ".planning");
139
+ pruneMap(marker.planning.passthrough, ".planning");
140
+ }
141
+ if (removed > 0)
142
+ writeCompatMarker(basePath, marker);
143
+ return removed;
144
+ }
101
145
  function quarantine(basePath, raw) {
102
146
  const path = compatMarkerPath(basePath);
103
147
  const badPath = `${path}.bad-${Date.now()}`;
@@ -121,8 +121,13 @@ function collectConfigSections(options) {
121
121
  if (prefs?.auto_supervisor) {
122
122
  const sup = resolveAutoSupervisorConfig();
123
123
  const supRows = [];
124
- if (sup.model)
125
- supRows.push({ label: "Model", value: sup.model });
124
+ if (sup.model) {
125
+ let modelVal = sup.model;
126
+ if (sup.modelFallbacks && sup.modelFallbacks.length > 0) {
127
+ modelVal += ` \u2192 ${sup.modelFallbacks.join(" \u2192 ")}`;
128
+ }
129
+ supRows.push({ label: "Model", value: modelVal });
130
+ }
126
131
  supRows.push({ label: "Soft timeout", value: `${sup.soft_timeout_minutes}m` });
127
132
  supRows.push({ label: "Idle timeout", value: `${sup.idle_timeout_minutes}m` });
128
133
  supRows.push({ label: "Stalled tool timeout", value: `${sup.stalled_tool_timeout_minutes}m` });
@@ -801,3 +801,36 @@ export function getDb() {
801
801
  export function getDbOrNull() {
802
802
  return currentDb;
803
803
  }
804
+ /**
805
+ * Confirm the open handle can actually write, not just that it opened.
806
+ *
807
+ * A schema-current database performs zero writes during open, so a handle that
808
+ * opened successfully but is not writable (read-only file, WAL/SHM permission
809
+ * mismatch, or a stale/moved handle → SQLITE_READONLY_DBMOVED) otherwise passes
810
+ * the open-only availability check and only fails much later at the first real
811
+ * write. The probe forces a genuine page write by re-writing the current
812
+ * `PRAGMA user_version` value back inside an IMMEDIATE transaction: a bare
813
+ * `BEGIN IMMEDIATE; ROLLBACK` is not sufficient because a moved handle does not
814
+ * fail until a page is actually dirtied. The value is unchanged, so the probe
815
+ * is idempotent, and the transaction is rolled back so nothing persists.
816
+ */
817
+ export function probeDbWritable() {
818
+ const db = currentDb;
819
+ if (!db)
820
+ return { ok: false, detail: "No database is open." };
821
+ try {
822
+ const row = db.prepare("PRAGMA user_version").get();
823
+ const current = typeof row?.user_version === "number" ? row.user_version : 0;
824
+ db.exec("BEGIN IMMEDIATE");
825
+ try {
826
+ db.exec(`PRAGMA user_version = ${current}`);
827
+ }
828
+ finally {
829
+ db.exec("ROLLBACK");
830
+ }
831
+ return { ok: true };
832
+ }
833
+ catch (err) {
834
+ return { ok: false, detail: err.message };
835
+ }
836
+ }
@@ -11,19 +11,46 @@ export function normalizeDbRow(row) {
11
11
  export function normalizeDbRows(rows) {
12
12
  return rows.map((row) => normalizeDbRow(row));
13
13
  }
14
+ function isPlainObject(value) {
15
+ if (value === null || typeof value !== "object")
16
+ return false;
17
+ const proto = Object.getPrototypeOf(value);
18
+ return proto === Object.prototype || proto === null;
19
+ }
20
+ // node:sqlite accepts binding-object keys with or without the ':'/'@'/'$'
21
+ // prefix, but better-sqlite3 accepts only bare keys — strip the prefix so
22
+ // call sites written against node:sqlite also work on the fallback provider.
23
+ function normalizeBindParams(params) {
24
+ return params.map((param) => {
25
+ if (!isPlainObject(param))
26
+ return param;
27
+ let changed = false;
28
+ const normalized = {};
29
+ for (const [key, value] of Object.entries(param)) {
30
+ if (key.length > 1 && (key[0] === ":" || key[0] === "@" || key[0] === "$")) {
31
+ normalized[key.slice(1)] = value;
32
+ changed = true;
33
+ }
34
+ else {
35
+ normalized[key] = value;
36
+ }
37
+ }
38
+ return changed ? normalized : param;
39
+ });
40
+ }
14
41
  export function createDbAdapter(rawDb) {
15
42
  const db = rawDb;
16
43
  const stmtCache = new Map();
17
44
  function wrapStmt(raw) {
18
45
  return {
19
46
  run(...params) {
20
- return raw.run(...params);
47
+ return raw.run(...normalizeBindParams(params));
21
48
  },
22
49
  get(...params) {
23
- return normalizeDbRow(raw.get(...params));
50
+ return normalizeDbRow(raw.get(...normalizeBindParams(params)));
24
51
  },
25
52
  all(...params) {
26
- return normalizeDbRows(raw.all(...params));
53
+ return normalizeDbRows(raw.all(...normalizeBindParams(params)));
27
54
  },
28
55
  };
29
56
  }
@@ -138,7 +138,7 @@ Diagnostics record the file path, scope (global/project), severity (error/warnin
138
138
  - `off` — skill discovery is disabled entirely.
139
139
 
140
140
  - `auto_supervisor`: configures the auto-mode supervisor that monitors agent progress and enforces timeouts. Keys:
141
- - `model`: model ID to use for the supervisor process (defaults to the currently active model).
141
+ - `model`: model to use for the supervisor process (defaults to the currently active model). Accepts a bare model ID **or** the same `{ model, provider?, fallbacks? }` object form as `models.<phase>`, so it honors `fallbacks[]` on a transient provider trip.
142
142
  - `soft_timeout_minutes`: minutes before the supervisor issues a soft warning (default: 20).
143
143
  - `idle_timeout_minutes`: minutes of inactivity before the supervisor intervenes (default: 10).
144
144
  - `hard_timeout_minutes`: minutes before the supervisor forces termination (default: 30).
@@ -228,7 +228,7 @@ In `"parent"` mode, slice/task `targetRepositories` default to the declared chil
228
228
  - `enabled`: boolean — set `false` to force sequential task execution. Default: `true`.
229
229
  - `max_parallel`: number — maximum tasks to dispatch in one batch, range `1`-`8`. Default: `2`.
230
230
  - `isolation_mode`: `"same-tree"` — currently the only supported value.
231
- - `subagent_model`: string — optional model override for reactive task subagents. Falls back to the `models.subagent` routing when omitted.
231
+ - `subagent_model`: optional model override for reactive task subagents. Accepts a bare model ID **or** the `{ model, provider?, fallbacks? }` object form (parity with `models.<phase>`). Falls back to the `models.subagent` routing when omitted.
232
232
 
233
233
  - `remote_questions`: route interactive questions to Slack/Discord for headless auto-mode. Keys:
234
234
  - `channel`: `"slack"` or `"discord"` — channel type.
@@ -326,7 +326,7 @@ In `"parent"` mode, slice/task `targetRepositories` default to the declared chil
326
326
  - `after`: string[] — unit types that trigger this hook (e.g., `["execute-task"]`).
327
327
  - `prompt`: string — prompt sent to the LLM. Supports `{milestoneId}`, `{sliceId}`, `{taskId}` substitutions.
328
328
  - `max_cycles`: number — max times this hook fires per trigger (default: 1, max: 10).
329
- - `model`: string — optional model override.
329
+ - `model`: optional model override. Accepts a bare model ID **or** the same `{ model, provider?, fallbacks? }` object form as `models.<phase>`. With `fallbacks[]`, a transient trip of the primary provider falls back to the next entry instead of hard-failing the hook — which, for a `blocking` hook with `on_block: { action: pause }`, would otherwise pause the whole run.
330
330
  - `artifact`: string — expected output file name (relative to task/slice dir). Hook is skipped if file already exists (idempotent).
331
331
  - `criticality`: `"advisory"` or `"blocking"` — advisory preserves current best-effort behavior; blocking requires clean hook completion plus a valid outcome verdict before auto-mode advances. Default: `"advisory"`.
332
332
  - `retry_on`: string — if this file is produced instead of the artifact, re-run the trigger unit then re-run hooks.
@@ -51,7 +51,7 @@ export function emitJournalEvent(basePath, entry) {
51
51
  catch {
52
52
  // Silent failure — journal must never break auto-mode
53
53
  }
54
- if (!isUnifiedAuditEnabled())
54
+ if (!isUnifiedAuditEnabled(basePath))
55
55
  return;
56
56
  try {
57
57
  const causedBy = entry.causedBy