@opengsd/gsd-pi 1.2.0-dev.d6c5343c → 1.2.0-dev.ddc97c10

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 (225) hide show
  1. package/dist/mcp-server.js +2 -1
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/gsd/auto/orchestrator.js +28 -10
  4. package/dist/resources/extensions/gsd/auto/phases.js +47 -4
  5. package/dist/resources/extensions/gsd/auto/session.js +3 -0
  6. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +3 -2
  7. package/dist/resources/extensions/gsd/auto-dispatch.js +11 -2
  8. package/dist/resources/extensions/gsd/auto-model-selection.js +11 -7
  9. package/dist/resources/extensions/gsd/auto-post-unit.js +18 -6
  10. package/dist/resources/extensions/gsd/auto-unit-closeout.js +45 -21
  11. package/dist/resources/extensions/gsd/auto-verification.js +14 -2
  12. package/dist/resources/extensions/gsd/auto.js +37 -1
  13. package/dist/resources/extensions/gsd/blocked-models.js +28 -0
  14. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +26 -6
  15. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +2 -2
  16. package/dist/resources/extensions/gsd/closeout-wizard.js +92 -0
  17. package/dist/resources/extensions/gsd/commands/context.js +16 -2
  18. package/dist/resources/extensions/gsd/commands-handlers.js +46 -3
  19. package/dist/resources/extensions/gsd/consent-question.js +16 -0
  20. package/dist/resources/extensions/gsd/crash-recovery.js +8 -3
  21. package/dist/resources/extensions/gsd/doctor-engine-checks.js +3 -3
  22. package/dist/resources/extensions/gsd/doctor-git-checks.js +2 -18
  23. package/dist/resources/extensions/gsd/gsd-command-home.js +22 -12
  24. package/dist/resources/extensions/gsd/gsd-db.js +2 -1
  25. package/dist/resources/extensions/gsd/guided-flow.js +6 -3
  26. package/dist/resources/extensions/gsd/milestone-closeout.js +73 -2
  27. package/dist/resources/extensions/gsd/milestone-planning-persistence.js +2 -2
  28. package/dist/resources/extensions/gsd/projection-flush.js +7 -0
  29. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  30. package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
  31. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  32. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  33. package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -1
  34. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  35. package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  36. package/dist/resources/extensions/gsd/prompts/replan-slice.md +1 -1
  37. package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  38. package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
  39. package/dist/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
  40. package/dist/resources/extensions/gsd/prompts/run-uat.md +1 -1
  41. package/dist/resources/extensions/gsd/prompts/triage-captures.md +1 -1
  42. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +1 -1
  43. package/dist/resources/extensions/gsd/roadmap-slices.js +25 -3
  44. package/dist/resources/extensions/gsd/session-lock.js +1 -1
  45. package/dist/resources/extensions/gsd/tool-contract.js +14 -3
  46. package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -2
  47. package/dist/resources/extensions/gsd/tools/complete-slice.js +2 -2
  48. package/dist/resources/extensions/gsd/tools/complete-task.js +3 -2
  49. package/dist/resources/extensions/gsd/tools/plan-slice.js +2 -2
  50. package/dist/resources/extensions/gsd/tools/plan-task.js +2 -2
  51. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +2 -2
  52. package/dist/resources/extensions/gsd/tools/reopen-milestone.js +2 -2
  53. package/dist/resources/extensions/gsd/tools/reopen-slice.js +2 -2
  54. package/dist/resources/extensions/gsd/tools/reopen-task.js +2 -2
  55. package/dist/resources/extensions/gsd/tools/replan-slice.js +2 -2
  56. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +67 -2
  57. package/dist/resources/extensions/gsd/verification-verdict.js +2 -1
  58. package/dist/resources/extensions/shared/gsd-browser-cli.js +21 -2
  59. package/dist/resources/shared/gsd-browser-path-sync.js +214 -0
  60. package/dist/resources/shared/package-manager-detection.js +1 -1
  61. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  62. package/dist/update-check.d.ts +2 -0
  63. package/dist/update-check.js +24 -1
  64. package/dist/update-cmd.js +20 -3
  65. package/dist/web/standalone/.next/BUILD_ID +1 -1
  66. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  67. package/dist/web/standalone/.next/build-manifest.json +2 -2
  68. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  69. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/index.html +1 -1
  86. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  93. package/dist/web/standalone/.next/server/chunks/8357.js +2 -2
  94. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  95. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  96. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  97. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  98. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  99. package/package.json +1 -1
  100. package/packages/cloud-mcp-gateway/package.json +2 -2
  101. package/packages/contracts/package.json +1 -1
  102. package/packages/daemon/package.json +4 -4
  103. package/packages/gsd-agent-core/package.json +5 -5
  104. package/packages/gsd-agent-modes/package.json +7 -7
  105. package/packages/mcp-server/dist/cli.js +10 -5
  106. package/packages/mcp-server/dist/cli.js.map +1 -1
  107. package/packages/mcp-server/dist/moonshot-tool-schema.d.ts +29 -0
  108. package/packages/mcp-server/dist/moonshot-tool-schema.d.ts.map +1 -0
  109. package/packages/mcp-server/dist/moonshot-tool-schema.js +50 -0
  110. package/packages/mcp-server/dist/moonshot-tool-schema.js.map +1 -0
  111. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  112. package/packages/mcp-server/dist/server.js +4 -0
  113. package/packages/mcp-server/dist/server.js.map +1 -1
  114. package/packages/mcp-server/dist/workflow-tools.d.ts +18 -18
  115. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  116. package/packages/mcp-server/dist/workflow-tools.js +99 -38
  117. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  118. package/packages/mcp-server/package.json +5 -4
  119. package/packages/native/package.json +1 -1
  120. package/packages/pi-agent-core/package.json +1 -1
  121. package/packages/pi-ai/dist/index.d.ts +2 -0
  122. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  123. package/packages/pi-ai/dist/index.js +2 -0
  124. package/packages/pi-ai/dist/index.js.map +1 -1
  125. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  126. package/packages/pi-ai/dist/providers/anthropic.js +12 -7
  127. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  128. package/packages/pi-ai/dist/providers/google-shared.d.ts +5 -0
  129. package/packages/pi-ai/dist/providers/google-shared.d.ts.map +1 -1
  130. package/packages/pi-ai/dist/providers/google-shared.js +12 -3
  131. package/packages/pi-ai/dist/providers/google-shared.js.map +1 -1
  132. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  133. package/packages/pi-ai/dist/providers/openai-completions.js +7 -3
  134. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  135. package/packages/pi-ai/dist/utils/moonshot-tool-schema.d.ts +9 -0
  136. package/packages/pi-ai/dist/utils/moonshot-tool-schema.d.ts.map +1 -0
  137. package/packages/pi-ai/dist/utils/moonshot-tool-schema.js +34 -0
  138. package/packages/pi-ai/dist/utils/moonshot-tool-schema.js.map +1 -0
  139. package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  140. package/packages/pi-ai/dist/utils/oauth/github-copilot.js +6 -2
  141. package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
  142. package/packages/pi-ai/package.json +1 -1
  143. package/packages/pi-coding-agent/package.json +7 -7
  144. package/packages/pi-tui/package.json +2 -2
  145. package/packages/rpc-client/package.json +2 -2
  146. package/pkg/package.json +1 -1
  147. package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +11 -0
  148. package/src/resources/extensions/gsd/auto/orchestrator.ts +28 -10
  149. package/src/resources/extensions/gsd/auto/phases.ts +63 -24
  150. package/src/resources/extensions/gsd/auto/session.ts +3 -0
  151. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +10 -16
  152. package/src/resources/extensions/gsd/auto-dispatch.ts +11 -10
  153. package/src/resources/extensions/gsd/auto-model-selection.ts +16 -7
  154. package/src/resources/extensions/gsd/auto-post-unit.ts +21 -6
  155. package/src/resources/extensions/gsd/auto-unit-closeout.ts +83 -28
  156. package/src/resources/extensions/gsd/auto-verification.ts +18 -2
  157. package/src/resources/extensions/gsd/auto.ts +44 -1
  158. package/src/resources/extensions/gsd/blocked-models.ts +49 -0
  159. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +34 -5
  160. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +2 -2
  161. package/src/resources/extensions/gsd/closeout-wizard.ts +102 -0
  162. package/src/resources/extensions/gsd/commands/context.ts +16 -2
  163. package/src/resources/extensions/gsd/commands-handlers.ts +46 -3
  164. package/src/resources/extensions/gsd/consent-question.ts +15 -0
  165. package/src/resources/extensions/gsd/crash-recovery.ts +10 -2
  166. package/src/resources/extensions/gsd/doctor-engine-checks.ts +3 -3
  167. package/src/resources/extensions/gsd/doctor-git-checks.ts +2 -19
  168. package/src/resources/extensions/gsd/gsd-command-home.ts +13 -3
  169. package/src/resources/extensions/gsd/gsd-db.ts +4 -3
  170. package/src/resources/extensions/gsd/guided-flow.ts +21 -26
  171. package/src/resources/extensions/gsd/milestone-closeout.ts +97 -2
  172. package/src/resources/extensions/gsd/milestone-planning-persistence.ts +2 -2
  173. package/src/resources/extensions/gsd/projection-flush.ts +20 -0
  174. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  175. package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
  176. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  177. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  178. package/src/resources/extensions/gsd/prompts/quick-task.md +1 -1
  179. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  180. package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  181. package/src/resources/extensions/gsd/prompts/replan-slice.md +1 -1
  182. package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  183. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  184. package/src/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
  185. package/src/resources/extensions/gsd/prompts/run-uat.md +1 -1
  186. package/src/resources/extensions/gsd/prompts/triage-captures.md +1 -1
  187. package/src/resources/extensions/gsd/prompts/validate-milestone.md +1 -1
  188. package/src/resources/extensions/gsd/roadmap-slices.ts +28 -3
  189. package/src/resources/extensions/gsd/session-lock.ts +1 -1
  190. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +69 -0
  191. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +97 -0
  192. package/src/resources/extensions/gsd/tests/auto-remote-session-lock-cleanup.test.ts +65 -3
  193. package/src/resources/extensions/gsd/tests/blocked-models.test.ts +19 -0
  194. package/src/resources/extensions/gsd/tests/consent-question.test.ts +15 -0
  195. package/src/resources/extensions/gsd/tests/doctor-git-checks-terminal.test.ts +73 -0
  196. package/src/resources/extensions/gsd/tests/gsd-command-home.test.ts +120 -0
  197. package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +2 -6
  198. package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +95 -4
  199. package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +0 -1
  200. package/src/resources/extensions/gsd/tests/phases-terminal-complete-idempotent.test.ts +242 -0
  201. package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +63 -2
  202. package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +68 -0
  203. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +19 -1
  204. package/src/resources/extensions/gsd/tests/tool-unavailable-retry.test.ts +33 -0
  205. package/src/resources/extensions/gsd/tests/transport-gate-double-complete.test.ts +139 -0
  206. package/src/resources/extensions/gsd/tests/verification-verdict.test.ts +2 -0
  207. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +273 -38
  208. package/src/resources/extensions/gsd/tool-contract.ts +38 -3
  209. package/src/resources/extensions/gsd/tools/complete-milestone.ts +3 -2
  210. package/src/resources/extensions/gsd/tools/complete-slice.ts +2 -2
  211. package/src/resources/extensions/gsd/tools/complete-task.ts +3 -2
  212. package/src/resources/extensions/gsd/tools/plan-slice.ts +2 -2
  213. package/src/resources/extensions/gsd/tools/plan-task.ts +2 -2
  214. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +2 -2
  215. package/src/resources/extensions/gsd/tools/reopen-milestone.ts +2 -2
  216. package/src/resources/extensions/gsd/tools/reopen-slice.ts +2 -2
  217. package/src/resources/extensions/gsd/tools/reopen-task.ts +2 -2
  218. package/src/resources/extensions/gsd/tools/replan-slice.ts +2 -2
  219. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +81 -2
  220. package/src/resources/extensions/gsd/verification-verdict.ts +4 -2
  221. package/src/resources/extensions/shared/gsd-browser-cli.ts +23 -2
  222. package/src/resources/shared/gsd-browser-path-sync.ts +273 -0
  223. package/src/resources/shared/package-manager-detection.ts +1 -1
  224. /package/dist/web/standalone/.next/static/{jmTLg6xZmAuq_LIqKOxrH → McokybTayhff1xEVc-d3T}/_buildManifest.js +0 -0
  225. /package/dist/web/standalone/.next/static/{jmTLg6xZmAuq_LIqKOxrH → McokybTayhff1xEVc-d3T}/_ssgManifest.js +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gsd/pi-ai",
3
- "version": "1.2.0-dev.d6c5343c",
3
+ "version": "1.2.0-dev.ddc97c10",
4
4
  "description": "Unified LLM API with automatic model discovery and provider configuration",
5
5
  "type": "module",
6
6
  "gsd": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gsd/pi-coding-agent",
3
- "version": "1.2.0-dev.d6c5343c",
3
+ "version": "1.2.0-dev.ddc97c10",
4
4
  "description": "Coding agent CLI (vendored from earendil-works/pi)",
5
5
  "type": "module",
6
6
  "gsd": {
@@ -33,7 +33,7 @@
33
33
  "copy-assets": "node scripts/copy-assets.cjs"
34
34
  },
35
35
  "dependencies": {
36
- "@opengsd/contracts": "^1.2.0-dev.d6c5343c",
36
+ "@opengsd/contracts": "^1.2.0-dev.ddc97c10",
37
37
  "@mariozechner/jiti": "^2.6.2",
38
38
  "@silvia-odwyer/photon-node": "0.3.4",
39
39
  "chalk": "5.6.2",
@@ -53,11 +53,11 @@
53
53
  "typebox": "1.1.38",
54
54
  "undici": "7.26.0",
55
55
  "yaml": "2.9.0",
56
- "@gsd/agent-core": "^1.2.0-dev.d6c5343c",
57
- "@gsd/native": "^1.2.0-dev.d6c5343c",
58
- "@gsd/pi-agent-core": "^1.2.0-dev.d6c5343c",
59
- "@gsd/pi-ai": "^1.2.0-dev.d6c5343c",
60
- "@gsd/pi-tui": "^1.2.0-dev.d6c5343c",
56
+ "@gsd/agent-core": "^1.2.0-dev.ddc97c10",
57
+ "@gsd/native": "^1.2.0-dev.ddc97c10",
58
+ "@gsd/pi-agent-core": "^1.2.0-dev.ddc97c10",
59
+ "@gsd/pi-ai": "^1.2.0-dev.ddc97c10",
60
+ "@gsd/pi-tui": "^1.2.0-dev.ddc97c10",
61
61
  "@sinclair/typebox": "^0.34.41"
62
62
  },
63
63
  "devDependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gsd/pi-tui",
3
- "version": "1.2.0-dev.d6c5343c",
3
+ "version": "1.2.0-dev.ddc97c10",
4
4
  "description": "Terminal UI library (vendored from earendil-works/pi)",
5
5
  "type": "module",
6
6
  "gsd": {
@@ -21,7 +21,7 @@
21
21
  "build": "node ../../scripts/clean-package-dist.cjs && tsc -p tsconfig.json --incremental false"
22
22
  },
23
23
  "dependencies": {
24
- "@gsd/native": "^1.2.0-dev.d6c5343c",
24
+ "@gsd/native": "^1.2.0-dev.ddc97c10",
25
25
  "get-east-asian-width": "1.6.0",
26
26
  "marked": "15.0.12",
27
27
  "@sinclair/typebox": "^0.34.41"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengsd/rpc-client",
3
- "version": "1.2.0-dev.d6c5343c",
3
+ "version": "1.2.0-dev.ddc97c10",
4
4
  "description": "Standalone RPC client SDK for GSD — zero internal dependencies",
5
5
  "license": "MIT",
6
6
  "gsd": {
@@ -34,7 +34,7 @@
34
34
  "test": "node --test dist/rpc-client.test.js"
35
35
  },
36
36
  "dependencies": {
37
- "@opengsd/contracts": "^1.2.0-dev.d6c5343c"
37
+ "@opengsd/contracts": "^1.2.0-dev.ddc97c10"
38
38
  },
39
39
  "engines": {
40
40
  "node": ">=22.0.0"
package/pkg/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glittercowboy/gsd",
3
- "version": "1.2.0-dev.d6c5343c",
3
+ "version": "1.2.0-dev.ddc97c10",
4
4
  "piConfig": {
5
5
  "name": "gsd",
6
6
  "configDir": ".gsd"
@@ -38,6 +38,17 @@ describe("resolveGsdBrowserMcpLaunchConfig identity flags", () => {
38
38
  assert.equal(args[args.indexOf("--identity-key") + 1], "custom-key");
39
39
  });
40
40
 
41
+ it("splits GSD_BROWSER_MCP_COMMAND command lines before spawning", () => {
42
+ const commandLine = '"C:\\Program Files\\nodejs\\node.exe" "C:\\Users\\Test User\\AppData\\Roaming\\npm\\node_modules\\@opengsd\\gsd-browser\\bin\\gsd-browser"';
43
+ const { command, args } = resolveGsdBrowserMcpLaunchConfig("C:\\Users\\Test User\\project", {
44
+ GSD_BROWSER_MCP_COMMAND: commandLine,
45
+ });
46
+
47
+ assert.equal(command, "C:\\Program Files\\nodejs\\node.exe");
48
+ assert.equal(args[0], "C:\\Users\\Test User\\AppData\\Roaming\\npm\\node_modules\\@opengsd\\gsd-browser\\bin\\gsd-browser");
49
+ assert.equal(args[1], "mcp");
50
+ });
51
+
41
52
  it("uses a path-safe identity-project identifier", () => {
42
53
  const { args } = resolveGsdBrowserMcpLaunchConfig("/tmp/example/project", {});
43
54
  const projectId = args[args.indexOf("--identity-project") + 1];
@@ -22,7 +22,7 @@ type BlockedAdvanceResult = Extract<AutoAdvanceResult, { kind: "blocked" }>;
22
22
  import { debugCount, debugLog, debugTime } from "../debug-logger.js";
23
23
  import { reconcileBeforeDispatch } from "../state-reconciliation.js";
24
24
  import { isLegalEdge, IllegalPhaseTransitionError } from "../state-transition-matrix.js";
25
- import { resolveDispatch } from "../auto-dispatch.js";
25
+ import { hasPendingDeepStage, resolveDispatch } from "../auto-dispatch.js";
26
26
  import { classifyFailure } from "../recovery-classification.js";
27
27
  import { verifyExpectedArtifact, refreshRecoveryDbForArtifact } from "../auto-recovery.js";
28
28
  import { invalidateAllCaches } from "../cache.js";
@@ -193,14 +193,25 @@ export async function decideOrchestratorDispatch(
193
193
  ): Promise<DispatchDecision> {
194
194
  const state = input.stateSnapshot;
195
195
  const active = state.activeMilestone;
196
- if (!active) return null;
197
-
198
196
  const activeSession = input.session ?? session;
199
197
  const activeDispatchBasePath = activeSession?.basePath || dispatchBasePath;
200
- if (activeSession && shouldAdoptActiveMilestone(state, activeSession, activeDispatchBasePath)) {
198
+ const prefs = loadEffectiveGSDPreferences(activeDispatchBasePath)?.preferences;
199
+ if (!active) {
200
+ if (state.phase !== "pre-planning") return null;
201
+ if (!hasPendingDeepStage(prefs, activeDispatchBasePath)) {
202
+ return {
203
+ kind: "blocked",
204
+ reason: state.nextAction || "No active milestone. Run /gsd unpark <id> or create a new milestone.",
205
+ action: "stop",
206
+ };
207
+ }
208
+ }
209
+
210
+ if (active && activeSession && shouldAdoptActiveMilestone(state, activeSession, activeDispatchBasePath)) {
201
211
  activeSession.currentMilestoneId = active.id;
202
212
  }
203
- const prefs = loadEffectiveGSDPreferences(activeDispatchBasePath)?.preferences;
213
+ const dispatchMid = active?.id ?? activeSession?.currentMilestoneId ?? "";
214
+ const dispatchMidTitle = active?.title ?? "";
204
215
 
205
216
  // Derive session-derived dispatch inputs the same way phases.ts:runDispatch does
206
217
  // (#5789). Prefer caller-supplied values when present so test harnesses and
@@ -232,8 +243,15 @@ export async function decideOrchestratorDispatch(
232
243
  ? "true"
233
244
  : "false");
234
245
 
246
+ // Only replay a milestone-scoped verification retry when a milestone is
247
+ // active. Pre-PR (#712 fix), `!active` returned null before reaching this
248
+ // block, so the retry was preserved for a future tick. The new
249
+ // pre-planning + deep-pending fall-through must keep that contract:
250
+ // otherwise a stale execute-task / complete-slice / complete-milestone
251
+ // retry whose target milestone has since been parked would preempt
252
+ // project-level deep rules like `discuss-project`.
235
253
  const pendingRetry = session?.pendingVerificationRetryDispatch;
236
- if (session && pendingRetry) {
254
+ if (session && pendingRetry && active) {
237
255
  session.pendingVerificationRetryDispatch = null;
238
256
  const alreadyClosedReason = getAlreadyClosedDispatchReason(
239
257
  pendingRetry.unitType,
@@ -255,8 +273,8 @@ export async function decideOrchestratorDispatch(
255
273
 
256
274
  const action = await resolveDispatch({
257
275
  basePath: activeDispatchBasePath,
258
- mid: active.id,
259
- midTitle: active.title,
276
+ mid: dispatchMid,
277
+ midTitle: dispatchMidTitle,
260
278
  state,
261
279
  prefs,
262
280
  session: activeSession,
@@ -300,8 +318,8 @@ export async function decideOrchestratorDispatch(
300
318
  prompt: action.prompt,
301
319
  pauseAfterUatDispatch: action.pauseAfterDispatch ?? false,
302
320
  state,
303
- mid: active.id,
304
- midTitle: active.title,
321
+ mid: dispatchMid,
322
+ midTitle: dispatchMidTitle,
305
323
  };
306
324
  session.pendingOrchestrationDispatch = pending;
307
325
  }
@@ -20,8 +20,8 @@ import {
20
20
  type PreVerificationOpts,
21
21
  } from "../auto-post-unit.js";
22
22
  import { lastAssistantText } from "../consent-question.js";
23
- import { resolveEffectiveUnitIsolationMode } from "../preferences.js";
24
- import type { Phase } from "../types.js";
23
+ import { resolveEffectiveUnitIsolationMode, getIsolationMode } from "../preferences.js";
24
+ import type { GSDState, Phase } from "../types.js";
25
25
  import {
26
26
  MAX_RECOVERY_CHARS,
27
27
  BUDGET_THRESHOLDS,
@@ -62,9 +62,10 @@ import { writeUnitRuntimeRecord } from "../unit-runtime.js";
62
62
  import { withTimeout, FINALIZE_PRE_TIMEOUT_MS, FINALIZE_POST_TIMEOUT_MS } from "./finalize-timeout.js";
63
63
  import { getEligibleSlices } from "../slice-parallel-eligibility.js";
64
64
  import { isSliceParallelActive, startSliceParallel } from "../slice-parallel-orchestrator.js";
65
- import { isDbAvailable, getMilestoneSlices, getSlice, getTask } from "../gsd-db.js";
65
+ import { isDbAvailable, getMilestone, getMilestoneSlices, getSlice, getTask } from "../gsd-db.js";
66
66
  import { refreshWorkflowDatabaseFromDisk } from "../db-workspace.js";
67
67
  import { isClosedStatus } from "../status-guards.js";
68
+ import { findUnmergedCompletedMilestones } from "../unmerged-milestone-guard.js";
68
69
  import { setRuntimeKv } from "../db/runtime-kv.js";
69
70
  import { getLatestForUnit } from "../db/unit-dispatches.js";
70
71
  import { reconcileBeforeSpawn } from "../state-reconciliation.js";
@@ -82,11 +83,8 @@ import { parseUnitId } from "../unit-id.js";
82
83
  import { createCheckpoint, cleanupCheckpoint, rollbackToCheckpoint } from "../safety/git-checkpoint.js";
83
84
  import { resolveSafetyHarnessConfig } from "../safety/safety-harness.js";
84
85
  import { getContextPauseAction } from "../auto-budget.js";
85
- import {
86
- getWorkflowTransportSupportError,
87
- getRequiredWorkflowToolsForAutoUnit,
88
- supportsStructuredQuestions,
89
- } from "../workflow-mcp.js";
86
+ import { supportsStructuredQuestions } from "../workflow-mcp.js";
87
+ import { getUnitWorkflowDispatchReadinessError } from "../tool-contract.js";
90
88
  import { prepareWorkflowMcpForProject } from "../workflow-mcp-auto-prep.js";
91
89
  import {
92
90
  applyThinkingLevelForModel,
@@ -838,6 +836,39 @@ async function failClosedOnFinalizeTimeout(
838
836
  return { action: "break", reason: progressKind };
839
837
  }
840
838
 
839
+ export async function shouldSkipTerminalMilestoneCloseout(
840
+ s: AutoSession,
841
+ state: Pick<GSDState, "phase" | "lastCompletedMilestone" | "activeMilestone">,
842
+ mid?: string | null,
843
+ ): Promise<{ skip: boolean; milestoneId?: string }> {
844
+ const closeoutMilestoneId = mid ?? s.currentMilestoneId ?? state.lastCompletedMilestone?.id;
845
+ if (s.completionStopInProgress) {
846
+ return { skip: true, milestoneId: closeoutMilestoneId };
847
+ }
848
+ if (!closeoutMilestoneId) {
849
+ return { skip: false };
850
+ }
851
+ if (isDbAvailable()) refreshWorkflowDatabaseFromDisk();
852
+ const closeoutBasePath = s.originalBasePath || s.canonicalProjectRoot || s.basePath;
853
+ let closeoutMergePending = false;
854
+ if (getIsolationMode(closeoutBasePath) !== "none") {
855
+ try {
856
+ const blockers = await findUnmergedCompletedMilestones(closeoutBasePath);
857
+ closeoutMergePending = blockers.some((blocker) => blocker.milestoneId === closeoutMilestoneId);
858
+ } catch {
859
+ // Fail open: without git/DB inspection we cannot safely treat closeout as done.
860
+ closeoutMergePending = true;
861
+ }
862
+ }
863
+ const milestoneAlreadyClosedOut = isDbAvailable()
864
+ && isClosedStatus(getMilestone(closeoutMilestoneId)?.status ?? "")
865
+ && !closeoutMergePending;
866
+ if (milestoneAlreadyClosedOut) {
867
+ return { skip: true, milestoneId: closeoutMilestoneId };
868
+ }
869
+ return { skip: false, milestoneId: closeoutMilestoneId };
870
+ }
871
+
841
872
  // ─── runPreDispatch ───────────────────────────────────────────────────────────
842
873
 
843
874
  /**
@@ -1279,6 +1310,14 @@ export async function runPreDispatch(
1279
1310
 
1280
1311
  // ── Terminal conditions ──────────────────────────────────────────────
1281
1312
 
1313
+ if (state.phase === "complete") {
1314
+ const closeoutSkip = await shouldSkipTerminalMilestoneCloseout(s, state, mid);
1315
+ if (closeoutSkip.skip) {
1316
+ debugLog("autoLoop", { phase: "complete", reason: "milestone-already-closed", milestoneId: closeoutSkip.milestoneId });
1317
+ return { action: "break", reason: "milestone-complete" };
1318
+ }
1319
+ }
1320
+
1282
1321
  if (!mid) {
1283
1322
  if (s.currentUnit) {
1284
1323
  await deps.closeoutUnit(
@@ -2324,22 +2363,19 @@ export async function runUnitPhase(
2324
2363
  ? `${(s.currentUnitModel as any).provider ?? ""}/${(s.currentUnitModel as any).id ?? ""}`
2325
2364
  : null;
2326
2365
 
2327
- const compatibilityError = getWorkflowTransportSupportError(
2328
- s.currentUnitModel?.provider ?? ctx.model?.provider,
2329
- getRequiredWorkflowToolsForAutoUnit(unitType),
2330
- {
2331
- projectRoot: s.basePath,
2332
- surface: "auto-mode",
2333
- unitType,
2334
- authMode: s.currentUnitModel?.provider
2335
- ? ctx.modelRegistry.getProviderAuthMode(s.currentUnitModel.provider)
2336
- : ctx.model?.provider
2337
- ? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider)
2338
- : undefined,
2339
- baseUrl: (s.currentUnitModel as any)?.baseUrl ?? ctx.model?.baseUrl,
2340
- activeTools: typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [],
2341
- },
2342
- );
2366
+ const compatibilityError = getUnitWorkflowDispatchReadinessError({
2367
+ provider: s.currentUnitModel?.provider ?? ctx.model?.provider,
2368
+ projectRoot: s.basePath,
2369
+ surface: "auto-mode",
2370
+ unitType,
2371
+ authMode: s.currentUnitModel?.provider
2372
+ ? ctx.modelRegistry.getProviderAuthMode(s.currentUnitModel.provider)
2373
+ : ctx.model?.provider
2374
+ ? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider)
2375
+ : undefined,
2376
+ baseUrl: (s.currentUnitModel as any)?.baseUrl ?? ctx.model?.baseUrl,
2377
+ activeTools: typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [],
2378
+ });
2343
2379
  const workflowMcpPrepModel = s.currentUnitModel;
2344
2380
  if (compatibilityError) {
2345
2381
  s.currentUnitRouting = prevUnitRouting;
@@ -2409,6 +2445,9 @@ export async function runUnitPhase(
2409
2445
  causedBy: "unit-start",
2410
2446
  });
2411
2447
  s.lastToolInvocationError = null; // #2883: clear stale error from previous unit
2448
+ if (nextDispatchCount <= 1) {
2449
+ s.toolUnavailableRetries = 0;
2450
+ }
2412
2451
  const unitStartSeq = ic.nextSeq();
2413
2452
  deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: unitStartSeq, eventType: "unit-start", data: { unitType, unitId } });
2414
2453
  deps.captureAvailableSkills();
@@ -212,6 +212,8 @@ export class AutoSession {
212
212
  /** Set when a GSD tool execution ends with isError due to malformed/truncated
213
213
  * JSON arguments. Checked by postUnitPreVerification to break retry loops. */
214
214
  lastToolInvocationError: string | null = null;
215
+ /** Consecutive tool-unavailable retries for the current unit (MCP startup race). */
216
+ toolUnavailableRetries = 0;
215
217
  /** Agent-end messages from the just-finished unit, consumed during finalize. */
216
218
  lastUnitAgentEndMessages: unknown[] | null = null;
217
219
  /** Set when turn-level git action fails during closeout. */
@@ -406,6 +408,7 @@ export class AutoSession {
406
408
  this.lastPreExecFailure = null;
407
409
  this.preExecRetryCount.clear();
408
410
  this.lastToolInvocationError = null;
411
+ this.toolUnavailableRetries = 0;
409
412
  this.lastUnitAgentEndMessages = null;
410
413
  this.lastGitActionFailure = null;
411
414
  this.lastGitActionStatus = null;
@@ -31,10 +31,7 @@ import { loadEffectiveGSDPreferences } from "./preferences.js";
31
31
  import type { MinimalModelRegistry } from "./context-budget.js";
32
32
  import { pauseAuto } from "./auto.js";
33
33
  import { resolveCanonicalMilestoneRoot } from "./worktree-manager.js";
34
- import {
35
- getWorkflowTransportSupportError,
36
- getRequiredWorkflowToolsForAutoUnit,
37
- } from "./workflow-mcp.js";
34
+ import { getUnitWorkflowDispatchReadinessError } from "./tool-contract.js";
38
35
 
39
36
  export async function dispatchDirectPhase(
40
37
  ctx: ExtensionCommandContext,
@@ -256,18 +253,15 @@ export async function dispatchDirectPhase(
256
253
  return;
257
254
  }
258
255
 
259
- const compatibilityError = getWorkflowTransportSupportError(
260
- ctx.model?.provider,
261
- getRequiredWorkflowToolsForAutoUnit(unitType),
262
- {
263
- projectRoot,
264
- surface: "direct phase dispatch",
265
- unitType,
266
- authMode: ctx.model?.provider ? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider) : undefined,
267
- baseUrl: ctx.model?.baseUrl,
268
- activeTools: typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [],
269
- },
270
- );
256
+ const compatibilityError = getUnitWorkflowDispatchReadinessError({
257
+ provider: ctx.model?.provider,
258
+ projectRoot,
259
+ surface: "direct phase dispatch",
260
+ unitType,
261
+ authMode: ctx.model?.provider ? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider) : undefined,
262
+ baseUrl: ctx.model?.baseUrl,
263
+ activeTools: typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [],
264
+ });
271
265
  if (compatibilityError) {
272
266
  ctx.ui.notify(compatibilityError, "error");
273
267
  return;
@@ -91,11 +91,8 @@ import { isAutoActive } from "./auto.js";
91
91
  import { hostWriteGateAdapter } from "./bootstrap/write-gate.js";
92
92
  import { ensureWorkflowPreferencesCaptured } from "./planning-depth.js";
93
93
  import { MILESTONE_ID_RE } from "./milestone-ids.js";
94
- import {
95
- getWorkflowTransportSupportError,
96
- getRequiredWorkflowToolsForAutoUnit,
97
- resolveWorkflowMcpProjectRoot,
98
- } from "./workflow-mcp.js";
94
+ import { resolveWorkflowMcpProjectRoot } from "./workflow-mcp.js";
95
+ import { getUnitWorkflowDispatchReadinessError } from "./tool-contract.js";
99
96
  import { prepareBrowserDaemonForUat } from "./browser-daemon-auto-prep.js";
100
97
  import {
101
98
  PROJECT_RESEARCH_INFLIGHT_MARKER,
@@ -745,11 +742,15 @@ export const DISPATCH_RULES: DispatchRule[] = [
745
742
  // Transport preflight: verify required MCP tools are actually connected
746
743
  // before consuming a retry attempt. Fixes tool-starved sessions burning
747
744
  // all MAX_UAT_ATTEMPTS before stopping (#477).
748
- const transportError = getWorkflowTransportSupportError(
749
- sessionProvider,
750
- getRequiredWorkflowToolsForAutoUnit("run-uat"),
751
- { projectRoot: basePath, surface: "auto-mode", unitType: "run-uat", authMode: sessionAuthMode, baseUrl: sessionBaseUrl, activeTools },
752
- );
745
+ const transportError = getUnitWorkflowDispatchReadinessError({
746
+ provider: sessionProvider,
747
+ projectRoot: basePath,
748
+ surface: "auto-mode",
749
+ unitType: "run-uat",
750
+ authMode: sessionAuthMode,
751
+ baseUrl: sessionBaseUrl,
752
+ activeTools,
753
+ });
753
754
  if (transportError) {
754
755
  return { action: "stop" as const, reason: transportError, level: "warning" as const };
755
756
  }
@@ -18,7 +18,7 @@ import { getSessionModelOverride } from "./session-model-override.js";
18
18
  import { logWarning } from "./workflow-logger.js";
19
19
  import { resolveUokFlags } from "./uok/flags.js";
20
20
  import { applyModelPolicyFilter } from "./uok/model-policy.js";
21
- import { isModelBlocked } from "./blocked-models.js";
21
+ import { isModelBlocked, isModelTemporarilyUnavailable } from "./blocked-models.js";
22
22
  import { getRequiredWorkflowToolsForAutoUnit, isWorkflowMcpSurfaceTool } from "./workflow-mcp.js";
23
23
 
24
24
  /**
@@ -272,6 +272,15 @@ function buildModelPolicyBlockReasons(
272
272
  }];
273
273
  }
274
274
 
275
+ function isModelUnavailable(
276
+ basePath: string,
277
+ provider: string | undefined,
278
+ id: string | undefined,
279
+ ): boolean {
280
+ return isModelBlocked(basePath, provider, id) ||
281
+ isModelTemporarilyUnavailable(basePath, provider, id);
282
+ }
283
+
275
284
  function restoreToolBaseline(pi: ExtensionAPI): void {
276
285
  const key = pi as unknown as object;
277
286
  const baseline = TOOL_BASELINE.get(key);
@@ -817,9 +826,9 @@ export async function selectAndApplyModel(
817
826
  // (issue #4513). The block is persisted in .gsd/runtime/blocked-models.json
818
827
  // so it survives /gsd auto restarts — without this, the same dead model
819
828
  // gets reselected after every restart.
820
- if (isModelBlocked(basePath, model.provider, model.id)) {
829
+ if (isModelUnavailable(basePath, model.provider, model.id)) {
821
830
  ctx.ui.notify(
822
- `Skipping blocked model ${model.provider}/${model.id} (provider rejected it for this account).`,
831
+ `Skipping unavailable model ${model.provider}/${model.id}.`,
823
832
  "warning",
824
833
  );
825
834
  continue;
@@ -896,7 +905,7 @@ export async function selectAndApplyModel(
896
905
  for (const model of buildPolicyEligibleFallbackOrder(ctx, routingEligibleModels, autoModeStartModel)) {
897
906
  const key = `${model.provider.toLowerCase()}/${model.id.toLowerCase()}`;
898
907
  if (!policyAllowedModelKeys.has(key)) continue;
899
- if (isModelBlocked(basePath, model.provider, model.id)) continue;
908
+ if (isModelUnavailable(basePath, model.provider, model.id)) continue;
900
909
  const ok = await pi.setModel(model, { persist: false });
901
910
  if (!ok) continue;
902
911
  appliedModel = model;
@@ -926,10 +935,10 @@ export async function selectAndApplyModel(
926
935
  autoModeStartModel,
927
936
  effectiveSessionModelOverride,
928
937
  );
929
- const startBlocked = isModelBlocked(basePath, autoModeStartModel.provider, autoModeStartModel.id);
938
+ const startBlocked = isModelUnavailable(basePath, autoModeStartModel.provider, autoModeStartModel.id);
930
939
  if (startBlocked) {
931
940
  ctx.ui.notify(
932
- `Auto-mode start model ${autoModeStartModel.provider}/${autoModeStartModel.id} is blocked for this account. Using current session model instead.`,
941
+ `Auto-mode start model ${autoModeStartModel.provider}/${autoModeStartModel.id} is unavailable. Using current session model instead.`,
933
942
  "warning",
934
943
  );
935
944
  } else {
@@ -940,7 +949,7 @@ export async function selectAndApplyModel(
940
949
  const ok = await pi.setModel(startModel, { persist: false });
941
950
  if (!ok) {
942
951
  const byId = availableModels.find(
943
- m => m.id === autoModeStartModel.id && !isModelBlocked(basePath, m.provider, m.id),
952
+ m => m.id === autoModeStartModel.id && !isModelUnavailable(basePath, m.provider, m.id),
944
953
  );
945
954
  if (byId) {
946
955
  const fallbackOk = await pi.setModel(byId, { persist: false });
@@ -2025,16 +2025,30 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
2025
2025
  );
2026
2026
  } else if (!triggerArtifactVerified) {
2027
2027
  if (s.lastToolInvocationError && isToolUnavailableError(s.lastToolInvocationError)) {
2028
- // Tool-unavailable is the one transient invocation error: the
2029
- // workflow MCP server registers its surface asynchronously, so a
2030
- // Unit's first call can race the registration. Fall through to the
2031
- // bounded verification retry instead of pausing.
2032
- debugLog("postUnit", { phase: "tool-unavailable-retry", unitType: s.currentUnit.type, unitId: s.currentUnit.id, error: s.lastToolInvocationError });
2028
+ // Tool-unavailable is transient: the workflow MCP server registers
2029
+ // its surface asynchronously, so a Unit's first call can race the
2030
+ // registration. Retry with escalating delay, bounded at 3 attempts.
2031
+ // ponytail: MAX constant so the guard, log, and display all agree
2032
+ const MAX_TOOL_UNAVAIL_RETRIES = 3;
2033
+ if (s.toolUnavailableRetries >= MAX_TOOL_UNAVAIL_RETRIES) {
2034
+ debugLog("postUnit", { phase: "tool-unavailable-exhausted", unitType: s.currentUnit.type, unitId: s.currentUnit.id, retries: s.toolUnavailableRetries });
2035
+ ctx.ui.notify(
2036
+ `Tool unavailable for ${s.currentUnit.type} after ${MAX_TOOL_UNAVAIL_RETRIES} retries: ${s.lastToolInvocationError}. MCP server may not be starting — pausing auto-mode.`,
2037
+ "error",
2038
+ );
2039
+ s.lastToolInvocationError = null;
2040
+ await pauseAuto(ctx, pi);
2041
+ return "dispatched";
2042
+ }
2043
+ s.toolUnavailableRetries++;
2044
+ const delayMs = s.toolUnavailableRetries * 1000;
2045
+ debugLog("postUnit", { phase: "tool-unavailable-retry", unitType: s.currentUnit.type, unitId: s.currentUnit.id, error: s.lastToolInvocationError, attempt: s.toolUnavailableRetries, delayMs });
2033
2046
  ctx.ui.notify(
2034
- `Tool unavailable for ${s.currentUnit.type}: ${s.lastToolInvocationError}. The tool surface may still be registering — retrying.`,
2047
+ `Tool unavailable for ${s.currentUnit.type}: ${s.lastToolInvocationError}. Waiting ${delayMs}ms for MCP server retry ${s.toolUnavailableRetries}/${MAX_TOOL_UNAVAIL_RETRIES}.`,
2035
2048
  "warning",
2036
2049
  );
2037
2050
  s.lastToolInvocationError = null;
2051
+ await new Promise(r => setTimeout(r, delayMs));
2038
2052
  } else if (s.lastToolInvocationError) {
2039
2053
  const isUserSkip = /queued user message/i.test(s.lastToolInvocationError);
2040
2054
  const errMsg = isUserSkip
@@ -2193,6 +2207,7 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
2193
2207
  if (s.pendingVerificationRetry?.unitId === s.currentUnit.id) {
2194
2208
  s.pendingVerificationRetry = null;
2195
2209
  }
2210
+ s.toolUnavailableRetries = 0;
2196
2211
  s.verificationRetryCount.delete(retryKey);
2197
2212
  s.verificationRetryFailureHashes.delete(retryKey);
2198
2213
  s.exhaustedVerificationUnits.delete(retryKey);