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

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 (264) hide show
  1. package/dist/cli-model-override.d.ts +15 -0
  2. package/dist/cli-model-override.js +21 -0
  3. package/dist/cli.js +1 -18
  4. package/dist/loader.js +6 -4
  5. package/dist/register-agent-bundles.d.ts +11 -2
  6. package/dist/register-agent-bundles.js +18 -4
  7. package/dist/resources/.managed-resources-content-hash +1 -1
  8. package/dist/resources/extensions/ask-user-questions.js +3 -2
  9. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +447 -215
  10. package/dist/resources/extensions/claude-code-cli/turn-assembler.js +33 -1
  11. package/dist/resources/extensions/gsd/auto/closeout.js +215 -0
  12. package/dist/resources/extensions/gsd/auto/dispatch-history.js +21 -6
  13. package/dist/resources/extensions/gsd/auto/dispatch.js +365 -0
  14. package/dist/resources/extensions/gsd/auto/finalize.js +347 -0
  15. package/dist/resources/extensions/gsd/auto/loop.js +4 -1
  16. package/dist/resources/extensions/gsd/auto/milestone-lease-reclaim.js +56 -0
  17. package/dist/resources/extensions/gsd/auto/orchestrator.js +85 -15
  18. package/dist/resources/extensions/gsd/auto/phase-helpers.js +146 -0
  19. package/dist/resources/extensions/gsd/auto/phases.js +17 -2372
  20. package/dist/resources/extensions/gsd/auto/pre-dispatch.js +534 -0
  21. package/dist/resources/extensions/gsd/auto/unit-phase.js +694 -0
  22. package/dist/resources/extensions/gsd/auto/workflow-unit-dispatch.js +1 -1
  23. package/dist/resources/extensions/gsd/auto/worktree-safety-phase.js +125 -0
  24. package/dist/resources/extensions/gsd/auto-worktree.js +1 -1
  25. package/dist/resources/extensions/gsd/auto.js +15 -1
  26. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +37 -7
  27. package/dist/resources/extensions/gsd/commands-mcp-status.js +2 -2
  28. package/dist/resources/extensions/gsd/commands-workflow-templates.js +9 -2
  29. package/dist/resources/extensions/gsd/db/queries.js +30 -0
  30. package/dist/resources/extensions/gsd/doctor-environment.js +256 -125
  31. package/dist/resources/extensions/gsd/guided-flow.js +88 -2
  32. package/dist/resources/extensions/gsd/health-widget.js +87 -28
  33. package/dist/resources/extensions/gsd/mcp-bridge.js +10 -0
  34. package/dist/resources/extensions/gsd/milestone-settlement.js +2 -2
  35. package/dist/resources/extensions/gsd/notifications.js +12 -7
  36. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  37. package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -1
  38. package/dist/resources/extensions/gsd/prompts/run-uat.md +2 -0
  39. package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -1
  40. package/dist/resources/extensions/gsd/skill-activation.js +3 -6
  41. package/dist/resources/extensions/gsd/state.js +6 -2
  42. package/dist/resources/extensions/gsd/tool-surface-readiness.js +83 -31
  43. package/dist/resources/extensions/gsd/tools/complete-task.js +62 -0
  44. package/dist/resources/extensions/gsd/unit-context-composer.js +1 -1
  45. package/dist/resources/extensions/gsd/unit-registry.js +34 -4
  46. package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +2 -0
  47. package/dist/resources/extensions/gsd/workflow-mcp-readiness-cache.js +105 -0
  48. package/dist/resources/extensions/gsd/worktree-safety.js +28 -26
  49. package/dist/resources/extensions/mcp-client/manager.js +6 -1
  50. package/dist/runtime-checks.d.ts +10 -0
  51. package/dist/runtime-checks.js +27 -0
  52. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  53. package/dist/web/standalone/.next/BUILD_ID +1 -1
  54. package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
  55. package/dist/web/standalone/.next/build-manifest.json +2 -2
  56. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  57. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  58. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  66. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/index.html +1 -1
  74. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
  81. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  82. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  83. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  84. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  85. package/package.json +2 -2
  86. package/packages/cloud-mcp-gateway/package.json +2 -2
  87. package/packages/contracts/package.json +1 -1
  88. package/packages/daemon/package.json +4 -4
  89. package/packages/gsd-agent-core/dist/sdk.d.ts.map +1 -1
  90. package/packages/gsd-agent-core/dist/sdk.js +6 -4
  91. package/packages/gsd-agent-core/dist/sdk.js.map +1 -1
  92. package/packages/gsd-agent-core/package.json +5 -5
  93. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  94. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  95. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js +10 -0
  96. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js.map +1 -1
  97. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +8 -0
  98. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  99. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +50 -6
  100. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  101. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts +2 -0
  102. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
  103. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +34 -5
  104. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
  105. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts +1 -0
  106. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  107. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +12 -0
  108. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
  109. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.d.ts.map +1 -1
  110. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js +4 -0
  111. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js.map +1 -1
  112. package/packages/gsd-agent-modes/package.json +7 -7
  113. package/packages/mcp-server/README.md +12 -3
  114. package/packages/mcp-server/dist/cli-runner.d.ts +40 -0
  115. package/packages/mcp-server/dist/cli-runner.d.ts.map +1 -0
  116. package/packages/mcp-server/dist/cli-runner.js +137 -0
  117. package/packages/mcp-server/dist/cli-runner.js.map +1 -0
  118. package/packages/mcp-server/dist/cli.js +2 -58
  119. package/packages/mcp-server/dist/cli.js.map +1 -1
  120. package/packages/mcp-server/dist/pid-registry.d.ts +46 -0
  121. package/packages/mcp-server/dist/pid-registry.d.ts.map +1 -0
  122. package/packages/mcp-server/dist/pid-registry.js +452 -0
  123. package/packages/mcp-server/dist/pid-registry.js.map +1 -0
  124. package/packages/mcp-server/dist/probe-mode.d.ts +4 -0
  125. package/packages/mcp-server/dist/probe-mode.d.ts.map +1 -0
  126. package/packages/mcp-server/dist/probe-mode.js +10 -0
  127. package/packages/mcp-server/dist/probe-mode.js.map +1 -0
  128. package/packages/mcp-server/dist/stdio-watchdog.d.ts +8 -0
  129. package/packages/mcp-server/dist/stdio-watchdog.d.ts.map +1 -0
  130. package/packages/mcp-server/dist/stdio-watchdog.js +40 -0
  131. package/packages/mcp-server/dist/stdio-watchdog.js.map +1 -0
  132. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  133. package/packages/mcp-server/dist/workflow-tools.js +62 -43
  134. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  135. package/packages/mcp-server/package.json +5 -5
  136. package/packages/native/package.json +1 -1
  137. package/packages/pi-agent-core/dist/agent-loop.js +43 -2
  138. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  139. package/packages/pi-agent-core/package.json +1 -1
  140. package/packages/pi-ai/package.json +1 -1
  141. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
  142. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  143. package/packages/pi-coding-agent/dist/core/settings-manager.js +11 -0
  144. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  145. package/packages/pi-coding-agent/dist/theme/theme.d.ts.map +1 -1
  146. package/packages/pi-coding-agent/dist/theme/theme.js +45 -17
  147. package/packages/pi-coding-agent/dist/theme/theme.js.map +1 -1
  148. package/packages/pi-coding-agent/package.json +7 -7
  149. package/packages/pi-tui/dist/index.d.ts +1 -1
  150. package/packages/pi-tui/dist/index.d.ts.map +1 -1
  151. package/packages/pi-tui/dist/index.js +1 -1
  152. package/packages/pi-tui/dist/index.js.map +1 -1
  153. package/packages/pi-tui/dist/terminal-image.d.ts +33 -0
  154. package/packages/pi-tui/dist/terminal-image.d.ts.map +1 -1
  155. package/packages/pi-tui/dist/terminal-image.js +54 -2
  156. package/packages/pi-tui/dist/terminal-image.js.map +1 -1
  157. package/packages/pi-tui/dist/tui.d.ts +8 -0
  158. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  159. package/packages/pi-tui/dist/tui.js +63 -18
  160. package/packages/pi-tui/dist/tui.js.map +1 -1
  161. package/packages/pi-tui/dist/utils.d.ts.map +1 -1
  162. package/packages/pi-tui/dist/utils.js +110 -36
  163. package/packages/pi-tui/dist/utils.js.map +1 -1
  164. package/packages/pi-tui/package.json +2 -2
  165. package/packages/rpc-client/package.json +2 -2
  166. package/pkg/dist/theme/theme.d.ts.map +1 -1
  167. package/pkg/dist/theme/theme.js +45 -17
  168. package/pkg/dist/theme/theme.js.map +1 -1
  169. package/pkg/package.json +1 -1
  170. package/src/resources/extensions/ask-user-questions.ts +7 -2
  171. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +531 -226
  172. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +672 -7
  173. package/src/resources/extensions/claude-code-cli/turn-assembler.ts +38 -1
  174. package/src/resources/extensions/gsd/auto/closeout.ts +309 -0
  175. package/src/resources/extensions/gsd/auto/dispatch-history.ts +22 -6
  176. package/src/resources/extensions/gsd/auto/dispatch.ts +449 -0
  177. package/src/resources/extensions/gsd/auto/finalize.ts +445 -0
  178. package/src/resources/extensions/gsd/auto/loop.ts +4 -1
  179. package/src/resources/extensions/gsd/auto/milestone-lease-reclaim.ts +74 -0
  180. package/src/resources/extensions/gsd/auto/orchestrator.ts +95 -15
  181. package/src/resources/extensions/gsd/auto/phase-helpers.ts +199 -0
  182. package/src/resources/extensions/gsd/auto/phases.ts +58 -3061
  183. package/src/resources/extensions/gsd/auto/pre-dispatch.ts +704 -0
  184. package/src/resources/extensions/gsd/auto/unit-phase.ts +910 -0
  185. package/src/resources/extensions/gsd/auto/workflow-unit-dispatch.ts +1 -1
  186. package/src/resources/extensions/gsd/auto/worktree-safety-phase.ts +149 -0
  187. package/src/resources/extensions/gsd/auto-worktree.ts +1 -1
  188. package/src/resources/extensions/gsd/auto.ts +20 -1
  189. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +56 -6
  190. package/src/resources/extensions/gsd/commands-mcp-status.ts +2 -2
  191. package/src/resources/extensions/gsd/commands-workflow-templates.ts +11 -4
  192. package/src/resources/extensions/gsd/db/queries.ts +29 -0
  193. package/src/resources/extensions/gsd/doctor-environment.ts +267 -142
  194. package/src/resources/extensions/gsd/guided-flow.ts +128 -2
  195. package/src/resources/extensions/gsd/health-widget.ts +91 -27
  196. package/src/resources/extensions/gsd/mcp-bridge.ts +39 -0
  197. package/src/resources/extensions/gsd/milestone-settlement.ts +2 -2
  198. package/src/resources/extensions/gsd/notifications.ts +13 -6
  199. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  200. package/src/resources/extensions/gsd/prompts/execute-task.md +2 -1
  201. package/src/resources/extensions/gsd/prompts/run-uat.md +2 -0
  202. package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -1
  203. package/src/resources/extensions/gsd/skill-activation.ts +3 -6
  204. package/src/resources/extensions/gsd/state.ts +7 -1
  205. package/src/resources/extensions/gsd/tests/auto-abort-pause-regression.test.ts +1 -1
  206. package/src/resources/extensions/gsd/tests/auto-blocked-remediation-message.test.ts +1 -1
  207. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +206 -22
  208. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +76 -12
  209. package/src/resources/extensions/gsd/tests/auto-pause-double-entry-guard.test.ts +1 -1
  210. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +77 -1
  211. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +2 -1
  212. package/src/resources/extensions/gsd/tests/auto-unit-closeout.test.ts +169 -1
  213. package/src/resources/extensions/gsd/tests/complete-task.test.ts +141 -5
  214. package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +2 -1
  215. package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +36 -0
  216. package/src/resources/extensions/gsd/tests/dispatch-history.test.ts +55 -0
  217. package/src/resources/extensions/gsd/tests/dist-redirect.mjs +8 -0
  218. package/src/resources/extensions/gsd/tests/engine-interfaces-contract.test.ts +117 -91
  219. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +113 -0
  220. package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +16 -0
  221. package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +15 -0
  222. package/src/resources/extensions/gsd/tests/integration/doctor-environment-async.test.ts +104 -0
  223. package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +18 -0
  224. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +47 -16
  225. package/src/resources/extensions/gsd/tests/mcp-readiness-preflight.test.ts +205 -0
  226. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +6 -5
  227. package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +1 -1
  228. package/src/resources/extensions/gsd/tests/milestone-report-path.test.ts +1 -1
  229. package/src/resources/extensions/gsd/tests/milestone-settlement.test.ts +92 -0
  230. package/src/resources/extensions/gsd/tests/milestone-transition-state-rebuild.test.ts +1 -1
  231. package/src/resources/extensions/gsd/tests/notifications.test.ts +64 -9
  232. package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +2 -2
  233. package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +5 -0
  234. package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +1 -1
  235. package/src/resources/extensions/gsd/tests/phases-terminal-complete-idempotent.test.ts +1 -1
  236. package/src/resources/extensions/gsd/tests/plan-gate-failed-doctor-heal-hint.test.ts +3 -3
  237. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +10 -2
  238. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +2 -4
  239. package/src/resources/extensions/gsd/tests/remote-notification-from-desktop.test.ts +31 -81
  240. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +7 -1
  241. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +20 -17
  242. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +7 -3
  243. package/src/resources/extensions/gsd/tests/stop-auto-race-null-unit.test.ts +1 -1
  244. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +4 -2
  245. package/src/resources/extensions/gsd/tests/tool-surface-readiness.test.ts +184 -10
  246. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +1 -1
  247. package/src/resources/extensions/gsd/tests/workflow-mcp-readiness-cache.test.ts +119 -0
  248. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +65 -2
  249. package/src/resources/extensions/gsd/tests/workflow-phase-contract-matrix.test.ts +332 -0
  250. package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +92 -0
  251. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +1 -1
  252. package/src/resources/extensions/gsd/tests/worktree-project-root-degrade.test.ts +1 -1
  253. package/src/resources/extensions/gsd/tests/worktree-safety-phase.test.ts +100 -0
  254. package/src/resources/extensions/gsd/tests/worktree-safety.test.ts +72 -0
  255. package/src/resources/extensions/gsd/tool-surface-readiness.ts +126 -19
  256. package/src/resources/extensions/gsd/tools/complete-task.ts +87 -0
  257. package/src/resources/extensions/gsd/unit-context-composer.ts +1 -1
  258. package/src/resources/extensions/gsd/unit-registry.ts +34 -4
  259. package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +2 -0
  260. package/src/resources/extensions/gsd/workflow-mcp-readiness-cache.ts +150 -0
  261. package/src/resources/extensions/gsd/worktree-safety.ts +41 -39
  262. package/src/resources/extensions/mcp-client/manager.ts +7 -1
  263. /package/dist/web/standalone/.next/static/{FBNo5cT_chy7YNoAQsU3o → xyMkEaICFHJoa98VgJyzY}/_buildManifest.js +0 -0
  264. /package/dist/web/standalone/.next/static/{FBNo5cT_chy7YNoAQsU3o → xyMkEaICFHJoa98VgJyzY}/_ssgManifest.js +0 -0
@@ -0,0 +1,449 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Auto-loop dispatch phase.
3
+
4
+ import type { DispatchAction } from "../auto-dispatch.js";
5
+ import { detectStuck } from "./detect-stuck.js";
6
+ import { STUCK_WINDOW_SIZE, lookupLatestLedgerError } from "./dispatch-history.js";
7
+ import {
8
+ verifyExpectedArtifact,
9
+ diagnoseExpectedArtifact,
10
+ buildLoopRemediationSteps,
11
+ refreshRecoveryDbForArtifact,
12
+ } from "../auto-recovery.js";
13
+ import { getConsecutiveDispatchBlocker } from "../dispatch-guard.js";
14
+ import { debugLog } from "../debug-logger.js";
15
+ import {
16
+ getToolBaselineSnapshot,
17
+ getRegisteredToolSnapshot,
18
+ } from "../auto-model-selection.js";
19
+ import { supportsStructuredQuestions } from "../workflow-mcp.js";
20
+ import type { MinimalModelRegistry } from "../context-budget.js";
21
+ import { isDbAvailable, getTask, getSlice } from "../gsd-db.js";
22
+ import { refreshWorkflowDatabaseFromDisk } from "../db-workspace.js";
23
+ import { isClosedStatus } from "../status-guards.js";
24
+ import { parseUnitId } from "../unit-id.js";
25
+ import { validateSourceWriteWorktreeSafety } from "./worktree-safety-phase.js";
26
+ import { closeoutAndStop } from "./closeout.js";
27
+ import {
28
+ persistStuckRecoveryAttempts,
29
+ _resolveDispatchGuardBasePath,
30
+ rememberRetryDispatch,
31
+ applyVerificationRetryPolicy,
32
+ } from "./phase-helpers.js";
33
+ import type { IterationContext, IterationData, LoopState, PhaseResult, PreDispatchData } from "./types.js";
34
+
35
+ export function getAlreadyClosedDispatchReason(unitType: string, unitId: string): string | null {
36
+ if (!isDbAvailable()) return null;
37
+ refreshWorkflowDatabaseFromDisk();
38
+ const { milestone, slice, task } = parseUnitId(unitId);
39
+ if (unitType === "execute-task" && milestone && slice && task) {
40
+ const row = getTask(milestone, slice, task);
41
+ return row && isClosedStatus(row.status)
42
+ ? `execute-task ${unitId} is already ${row.status}`
43
+ : null;
44
+ }
45
+ if (unitType === "complete-slice" && milestone && slice) {
46
+ const row = getSlice(milestone, slice);
47
+ return row && isClosedStatus(row.status)
48
+ ? `complete-slice ${unitId} is already ${row.status}`
49
+ : null;
50
+ }
51
+ return null;
52
+ }
53
+
54
+ function isUnhandledPhaseWarning(dispatchResult: DispatchAction): dispatchResult is Extract<DispatchAction, { action: "stop" }> {
55
+ return dispatchResult.action === "stop" &&
56
+ dispatchResult.level === "warning" &&
57
+ dispatchResult.matchedRule === "<no-match>" &&
58
+ /^Unhandled phase "/.test(dispatchResult.reason);
59
+ }
60
+
61
+ export { isUnhandledPhaseWarning };
62
+
63
+ /**
64
+ * Phase 3: Dispatch resolution — resolve next unit, stuck detection, pre-dispatch hooks.
65
+ * Returns break/continue to control the loop, or next with IterationData on success.
66
+ */
67
+ export async function runDispatch(
68
+ ic: IterationContext,
69
+ preData: PreDispatchData,
70
+ loopState: LoopState,
71
+ ): Promise<PhaseResult<IterationData>> {
72
+ const { ctx, pi, s, deps, prefs } = ic;
73
+ const { state, mid, midTitle } = preData;
74
+ const provider = ctx.model?.provider;
75
+ const authMode = provider && typeof ctx.modelRegistry?.getProviderAuthMode === "function"
76
+ ? ctx.modelRegistry.getProviderAuthMode(provider)
77
+ : undefined;
78
+ // Use the baseline snapshot rather than the live active-tool set: a prior
79
+ // unit's per-provider narrowing (hook overrides, Groq 128-tool cap, etc.)
80
+ // can strip required MCP tools from the live set even though
81
+ // selectAndApplyModel will restore them before the unit is dispatched.
82
+ // Checking a stale-narrowed set causes false transport-preflight warnings
83
+ // that repeat on every /gsd auto resume (#477 follow-up).
84
+ const activeTools = getToolBaselineSnapshot(pi);
85
+ const registeredTools = getRegisteredToolSnapshot(pi);
86
+ // Deep planning intentionally keeps human checkpoints in plain chat. In
87
+ // Claude Code/local MCP transports, structured question requests can be
88
+ // cancelled outside the normal chat flow, which made approval gates easy to
89
+ // skip or bury under tool output.
90
+ const structuredQuestionsAvailable = prefs?.planning_depth === "deep"
91
+ ? "false"
92
+ : supportsStructuredQuestions(activeTools, {
93
+ authMode,
94
+ baseUrl: ctx.model?.baseUrl,
95
+ }) ? "true" : "false";
96
+
97
+ debugLog("autoLoop", { phase: "dispatch-resolve", iteration: ic.iteration });
98
+ let dispatchResult = await deps.resolveDispatch({
99
+ basePath: s.basePath,
100
+ mid,
101
+ midTitle,
102
+ state,
103
+ prefs,
104
+ session: s,
105
+ structuredQuestionsAvailable,
106
+ sessionContextWindow: ctx.model?.contextWindow,
107
+ sessionProvider: ctx.model?.provider,
108
+ modelRegistry: ctx.modelRegistry as MinimalModelRegistry | undefined,
109
+ activeTools,
110
+ registeredTools,
111
+ sessionBaseUrl: ctx.model?.baseUrl,
112
+ sessionAuthMode: authMode,
113
+ });
114
+ if (isUnhandledPhaseWarning(dispatchResult)) {
115
+ deps.invalidateAllCaches();
116
+ const freshState = await deps.deriveState(s.canonicalProjectRoot);
117
+ const freshMid = freshState.activeMilestone?.id ?? mid;
118
+ const freshMidTitle = freshState.activeMilestone?.title ?? freshMid ?? midTitle;
119
+ debugLog("autoLoop", {
120
+ phase: "dispatch-unhandled-phase-retry",
121
+ iteration: ic.iteration,
122
+ stalePhase: state.phase,
123
+ freshPhase: freshState.phase,
124
+ });
125
+ dispatchResult = await deps.resolveDispatch({
126
+ basePath: s.basePath,
127
+ mid: freshMid,
128
+ midTitle: freshMidTitle,
129
+ state: freshState,
130
+ prefs,
131
+ session: s,
132
+ structuredQuestionsAvailable,
133
+ sessionContextWindow: ctx.model?.contextWindow,
134
+ sessionProvider: ctx.model?.provider,
135
+ modelRegistry: ctx.modelRegistry as MinimalModelRegistry | undefined,
136
+ activeTools,
137
+ registeredTools,
138
+ sessionBaseUrl: ctx.model?.baseUrl,
139
+ sessionAuthMode: authMode,
140
+ });
141
+ }
142
+
143
+ if (dispatchResult.action === "stop") {
144
+ deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: ic.nextSeq(), eventType: "dispatch-stop", rule: dispatchResult.matchedRule, data: { reason: dispatchResult.reason } });
145
+ // Warning-level stops are recoverable human checkpoints (e.g. UAT verdict
146
+ // gate) — pause instead of hard-stopping so the session is resumable with
147
+ // `/gsd auto`. Error/info-level stops remain hard stops for infrastructure
148
+ // failures and terminal conditions respectively.
149
+ // See: https://github.com/open-gsd/gsd-pi/issues/2474
150
+ if (dispatchResult.level === "warning") {
151
+ ctx.ui.notify(dispatchResult.reason, "warning");
152
+ await deps.pauseAuto(ctx, pi, {
153
+ message: dispatchResult.reason,
154
+ category: "unknown",
155
+ });
156
+ } else {
157
+ await closeoutAndStop(ctx, pi, s, deps, dispatchResult.reason);
158
+ }
159
+ debugLog("autoLoop", { phase: "exit", reason: "dispatch-stop" });
160
+ return { action: "break", reason: "dispatch-stop" };
161
+ }
162
+
163
+ if (dispatchResult.action !== "dispatch") {
164
+ // Non-dispatch action (e.g. "skip") — re-derive state
165
+ await new Promise((r) => setImmediate(r));
166
+ return { action: "continue" };
167
+ }
168
+
169
+ let unitType = dispatchResult.unitType;
170
+ let unitId = dispatchResult.unitId;
171
+ let prompt = dispatchResult.prompt;
172
+ let pauseAfterUatDispatch = dispatchResult.pauseAfterDispatch ?? false;
173
+ let dispatchState = state;
174
+ let dispatchMid = mid;
175
+ let dispatchMidTitle = midTitle;
176
+ const pendingRetryDispatch = s.pendingVerificationRetryDispatch;
177
+ if (pendingRetryDispatch) {
178
+ unitType = pendingRetryDispatch.unitType;
179
+ unitId = pendingRetryDispatch.unitId;
180
+ prompt = pendingRetryDispatch.prompt;
181
+ pauseAfterUatDispatch = pendingRetryDispatch.pauseAfterUatDispatch;
182
+ dispatchState = pendingRetryDispatch.state;
183
+ dispatchMid = pendingRetryDispatch.mid ?? mid;
184
+ dispatchMidTitle = pendingRetryDispatch.midTitle ?? midTitle;
185
+ s.pendingVerificationRetryDispatch = null;
186
+ debugLog("autoLoop", {
187
+ phase: "dispatch-pending-verification-retry",
188
+ unitType,
189
+ unitId,
190
+ });
191
+ }
192
+
193
+ const alreadyClosedReason = getAlreadyClosedDispatchReason(unitType, unitId);
194
+ if (alreadyClosedReason) {
195
+ s.pendingVerificationRetry = null;
196
+ loopState.recentUnits = [];
197
+ loopState.stuckRecoveryAttempts = Math.max(loopState.stuckRecoveryAttempts, 1);
198
+ deps.invalidateAllCaches();
199
+ debugLog("autoLoop", {
200
+ phase: "dispatch-skip-already-closed",
201
+ unitType,
202
+ unitId,
203
+ reason: alreadyClosedReason,
204
+ });
205
+ deps.emitJournalEvent({
206
+ ts: new Date().toISOString(),
207
+ flowId: ic.flowId,
208
+ seq: ic.nextSeq(),
209
+ eventType: "guard-block",
210
+ data: { unitType, unitId, reason: alreadyClosedReason },
211
+ });
212
+ ctx.ui.notify(`Skipping ${unitType} ${unitId}: ${alreadyClosedReason}.`, "info");
213
+ await new Promise((r) => setImmediate(r));
214
+ return { action: "continue" };
215
+ }
216
+
217
+ deps.emitJournalEvent({
218
+ ts: new Date().toISOString(),
219
+ flowId: ic.flowId,
220
+ seq: ic.nextSeq(),
221
+ eventType: "dispatch-match",
222
+ rule: pendingRetryDispatch ? "verification-retry" : dispatchResult.matchedRule,
223
+ data: { unitType, unitId },
224
+ });
225
+
226
+ // Resolve hooks and prior-slice gating before health/stuck accounting so
227
+ // those checks run against the final dispatch unit.
228
+ const preDispatchResult = deps.runPreDispatchHooks(
229
+ unitType,
230
+ unitId,
231
+ prompt,
232
+ s.basePath,
233
+ );
234
+ if (preDispatchResult.firedHooks.length > 0) {
235
+ ctx.ui.notify(
236
+ `Pre-dispatch hook${preDispatchResult.firedHooks.length > 1 ? "s" : ""}: ${preDispatchResult.firedHooks.join(", ")}`,
237
+ "info",
238
+ );
239
+ deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: ic.nextSeq(), eventType: "pre-dispatch-hook", data: { firedHooks: preDispatchResult.firedHooks, action: preDispatchResult.action } });
240
+ }
241
+ if (preDispatchResult.action === "skip") {
242
+ ctx.ui.notify(
243
+ `Skipping ${unitType} ${unitId} (pre-dispatch hook).`,
244
+ "info",
245
+ );
246
+ await new Promise((r) => setImmediate(r));
247
+ return { action: "continue" };
248
+ }
249
+ if (preDispatchResult.action === "replace") {
250
+ prompt = preDispatchResult.prompt ?? prompt;
251
+ if (preDispatchResult.unitType) unitType = preDispatchResult.unitType;
252
+ } else if (preDispatchResult.prompt) {
253
+ prompt = preDispatchResult.prompt;
254
+ }
255
+
256
+ const guardBasePath = _resolveDispatchGuardBasePath(s);
257
+ let mainBranch = "main";
258
+ try {
259
+ mainBranch = deps.getMainBranch(guardBasePath);
260
+ } catch (err) {
261
+ debugLog("autoLoop", { phase: "getMainBranch-failed", error: String(err) });
262
+ }
263
+ const priorSliceBlocker = deps.getPriorSliceCompletionBlocker(
264
+ guardBasePath,
265
+ mainBranch,
266
+ unitType,
267
+ unitId,
268
+ );
269
+ if (priorSliceBlocker) {
270
+ await deps.stopAuto(ctx, pi, priorSliceBlocker);
271
+ debugLog("autoLoop", { phase: "exit", reason: "prior-slice-blocker" });
272
+ return { action: "break", reason: "prior-slice-blocker" };
273
+ }
274
+
275
+ const consecutiveDispatchBlocker = getConsecutiveDispatchBlocker(
276
+ loopState,
277
+ state.phase,
278
+ unitType,
279
+ unitId,
280
+ );
281
+ if (consecutiveDispatchBlocker) {
282
+ await deps.stopAuto(ctx, pi, consecutiveDispatchBlocker);
283
+ debugLog("autoLoop", { phase: "exit", reason: "consecutive-dispatch-blocker" });
284
+ return { action: "break", reason: "consecutive-dispatch-blocker" };
285
+ }
286
+
287
+ const worktreeSafetyBlock = await validateSourceWriteWorktreeSafety(
288
+ ic,
289
+ unitType,
290
+ unitId,
291
+ mid,
292
+ "pre-dispatch",
293
+ );
294
+ if (worktreeSafetyBlock) return worktreeSafetyBlock;
295
+
296
+ // ── Sliding-window stuck detection with graduated recovery ──
297
+ const derivedKey = `${unitType}/${unitId}`;
298
+
299
+ // Always record this dispatch in the sliding window and run detection so
300
+ // Rules 1/3/4 can catch retry loops with repeated failure content (#5719).
301
+ // Rules 2/2b suppress legitimate retry backoff through the dispatch ledger.
302
+ //
303
+ // Mirror DispatchHistory.recordDispatch: attach the latest ledger error
304
+ // only on a repeat (the key already exists in the window) so a first
305
+ // dispatch never trips the repeat-error rule, and first-dispatch advances
306
+ // (the common path) pay zero DB cost. The ledger keys rows by the bare unit
307
+ // id with the unit type in its own column, so look up by (unitType, unitId)
308
+ // — the compound `derivedKey` would miss the row and silently drop
309
+ // repeat-error detection here. derivedKey stays the window-entry key.
310
+ const recentError = loopState.recentUnits.some((entry) => entry.key === derivedKey)
311
+ ? lookupLatestLedgerError(unitType, unitId)
312
+ : undefined;
313
+ loopState.recentUnits.push({ key: derivedKey, error: recentError });
314
+ while (loopState.recentUnits.length > STUCK_WINDOW_SIZE) {
315
+ loopState.recentUnits.shift();
316
+ }
317
+
318
+ const stuckSignal = detectStuck(loopState.recentUnits, {
319
+ pendingRetry: !!s.pendingVerificationRetry,
320
+ retryAttempt: s.pendingVerificationRetry?.attempt,
321
+ });
322
+ if (stuckSignal) {
323
+ debugLog("autoLoop", {
324
+ phase: "stuck-check",
325
+ unitType,
326
+ unitId,
327
+ reason: stuckSignal.reason,
328
+ recoveryAttempts: loopState.stuckRecoveryAttempts,
329
+ });
330
+
331
+ if (loopState.stuckRecoveryAttempts === 0) {
332
+ // Level 1: try verifying the artifact, then cache invalidation + retry
333
+ loopState.stuckRecoveryAttempts++;
334
+ persistStuckRecoveryAttempts(s, loopState);
335
+ const artifactExists = verifyExpectedArtifact(
336
+ unitType,
337
+ unitId,
338
+ s.basePath,
339
+ );
340
+ if (artifactExists) {
341
+ debugLog("autoLoop", {
342
+ phase: "stuck-recovery",
343
+ level: 1,
344
+ action: "artifact-found",
345
+ });
346
+ const recoveryDb = refreshRecoveryDbForArtifact(unitType, unitId, s.basePath);
347
+ if (!recoveryDb.ok) {
348
+ ctx.ui.notify(
349
+ recoveryDb.fatal
350
+ ? `${recoveryDb.message} Pausing auto-mode for manual recovery.`
351
+ : `${recoveryDb.message} Keeping stuck state for retry.`,
352
+ "warning",
353
+ );
354
+ if (recoveryDb.fatal) {
355
+ await deps.pauseAuto(ctx, pi);
356
+ return { action: "break", reason: recoveryDb.reason };
357
+ }
358
+ return { action: "continue" };
359
+ }
360
+ ctx.ui.notify(
361
+ `Stuck recovery: artifact for ${unitType} ${unitId} found on disk. Invalidating caches.`,
362
+ "info",
363
+ );
364
+ deps.invalidateAllCaches();
365
+ loopState.recentUnits.length = 0;
366
+ return { action: "continue" };
367
+ }
368
+ ctx.ui.notify(
369
+ `Stuck on ${unitType} ${unitId} (${stuckSignal.reason}). Invalidating caches and retrying.`,
370
+ "warning",
371
+ );
372
+ deps.invalidateAllCaches();
373
+ } else {
374
+ // Level 2: hard stop — genuinely stuck
375
+ deps.invalidateAllCaches();
376
+ const artifactExists = verifyExpectedArtifact(
377
+ unitType,
378
+ unitId,
379
+ s.basePath,
380
+ );
381
+ if (artifactExists) {
382
+ debugLog("autoLoop", {
383
+ phase: "stuck-recovery",
384
+ level: 2,
385
+ action: "artifact-found",
386
+ });
387
+ const recoveryDb = refreshRecoveryDbForArtifact(unitType, unitId, s.basePath);
388
+ if (recoveryDb.ok) {
389
+ ctx.ui.notify(
390
+ `Stuck recovery: artifact for ${unitType} ${unitId} found on disk after cache invalidation. Continuing.`,
391
+ "info",
392
+ );
393
+ loopState.recentUnits.length = 0;
394
+ return { action: "continue" };
395
+ }
396
+ ctx.ui.notify(
397
+ recoveryDb.fatal
398
+ ? `${recoveryDb.message} Pausing auto-mode for manual recovery.`
399
+ : `${recoveryDb.message} Stopping for manual recovery.`,
400
+ "warning",
401
+ );
402
+ if (recoveryDb.fatal) {
403
+ await deps.pauseAuto(ctx, pi);
404
+ return { action: "break", reason: recoveryDb.reason };
405
+ }
406
+ }
407
+ debugLog("autoLoop", {
408
+ phase: "stuck-detected",
409
+ unitType,
410
+ unitId,
411
+ reason: stuckSignal.reason,
412
+ });
413
+ const stuckDiag = diagnoseExpectedArtifact(unitType, unitId, s.basePath);
414
+ const stuckRemediation = buildLoopRemediationSteps(unitType, unitId, s.basePath);
415
+ const stuckParts = [`Stuck on ${unitType} ${unitId} — ${stuckSignal.reason}.`];
416
+ if (stuckDiag) stuckParts.push(`Expected: ${stuckDiag}`);
417
+ if (stuckRemediation) stuckParts.push(`To recover:\n${stuckRemediation}`);
418
+ ctx.ui.notify(stuckParts.join(" "), "error");
419
+ await deps.stopAuto(
420
+ ctx,
421
+ pi,
422
+ `Stuck: ${stuckSignal.reason}`,
423
+ );
424
+ return { action: "break", reason: "stuck-detected" };
425
+ }
426
+ } else {
427
+ // Progress detected — reset recovery counter
428
+ if (loopState.stuckRecoveryAttempts > 0) {
429
+ debugLog("autoLoop", {
430
+ phase: "stuck-counter-reset",
431
+ from: loopState.recentUnits[loopState.recentUnits.length - 2]?.key ?? "",
432
+ to: derivedKey,
433
+ });
434
+ loopState.stuckRecoveryAttempts = 0;
435
+ persistStuckRecoveryAttempts(s, loopState);
436
+ }
437
+ }
438
+
439
+ return {
440
+ action: "next",
441
+ data: {
442
+ unitType, unitId, prompt, finalPrompt: prompt,
443
+ pauseAfterUatDispatch,
444
+ state: dispatchState, mid: dispatchMid, midTitle: dispatchMidTitle,
445
+ isRetry: Boolean(pendingRetryDispatch), previousTier: undefined,
446
+ hookModelOverride: preDispatchResult.model,
447
+ },
448
+ };
449
+ }