gsd-pi 2.76.0-dev.82e249f7b → 2.76.0-dev.97f5583d9

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 (271) hide show
  1. package/dist/claude-cli-check.js +32 -3
  2. package/dist/mcp-server.d.ts +7 -0
  3. package/dist/mcp-server.js +35 -1
  4. package/dist/resource-loader.d.ts +1 -1
  5. package/dist/resource-loader.js +2 -8
  6. package/dist/resources/extensions/claude-code-cli/readiness.js +4 -3
  7. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +77 -17
  8. package/dist/resources/extensions/gsd/auto/phases.js +42 -1
  9. package/dist/resources/extensions/gsd/auto/run-unit.js +27 -0
  10. package/dist/resources/extensions/gsd/auto/session.js +12 -0
  11. package/dist/resources/extensions/gsd/auto-dispatch.js +16 -3
  12. package/dist/resources/extensions/gsd/auto-model-selection.js +1 -1
  13. package/dist/resources/extensions/gsd/auto-post-unit.js +25 -2
  14. package/dist/resources/extensions/gsd/auto-prompts.js +14 -0
  15. package/dist/resources/extensions/gsd/auto-recovery.js +13 -0
  16. package/dist/resources/extensions/gsd/auto-start.js +27 -18
  17. package/dist/resources/extensions/gsd/auto-worktree.js +51 -53
  18. package/dist/resources/extensions/gsd/auto.js +55 -27
  19. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +17 -1
  20. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +39 -9
  21. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +93 -0
  22. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
  23. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +51 -5
  24. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +34 -2
  25. package/dist/resources/extensions/gsd/clean-root-preflight.js +93 -0
  26. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +968 -23
  27. package/dist/resources/extensions/gsd/compaction-snapshot.js +121 -0
  28. package/dist/resources/extensions/gsd/error-classifier.js +10 -3
  29. package/dist/resources/extensions/gsd/exec-history.js +120 -0
  30. package/dist/resources/extensions/gsd/exec-sandbox.js +258 -0
  31. package/dist/resources/extensions/gsd/gsd-db.js +115 -7
  32. package/dist/resources/extensions/gsd/guided-flow.js +189 -0
  33. package/dist/resources/extensions/gsd/health-widget.js +4 -1
  34. package/dist/resources/extensions/gsd/init-wizard.js +15 -1
  35. package/dist/resources/extensions/gsd/key-manager.js +6 -0
  36. package/dist/resources/extensions/gsd/model-router.js +36 -3
  37. package/dist/resources/extensions/gsd/pre-execution-checks.js +35 -9
  38. package/dist/resources/extensions/gsd/preferences-types.js +9 -0
  39. package/dist/resources/extensions/gsd/preferences-validation.js +83 -0
  40. package/dist/resources/extensions/gsd/preferences.js +17 -17
  41. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +8 -0
  42. package/dist/resources/extensions/gsd/prompts/discuss.md +29 -2
  43. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
  44. package/dist/resources/extensions/gsd/safety/evidence-collector.js +96 -0
  45. package/dist/resources/extensions/gsd/safety/file-change-validator.js +13 -5
  46. package/dist/resources/extensions/gsd/safety/safety-harness.js +5 -1
  47. package/dist/resources/extensions/gsd/token-counter.js +22 -5
  48. package/dist/resources/extensions/gsd/tools/exec-search-tool.js +59 -0
  49. package/dist/resources/extensions/gsd/tools/exec-tool.js +126 -0
  50. package/dist/resources/extensions/gsd/tools/resume-tool.js +23 -0
  51. package/dist/resources/extensions/gsd/uok/plan-v2.js +20 -3
  52. package/dist/resources/extensions/gsd/workflow-mcp.js +3 -0
  53. package/dist/resources/skills/verify-before-complete/SKILL.md +2 -1
  54. package/dist/resources/skills/write-docs/SKILL.md +2 -1
  55. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  56. package/dist/web/standalone/.next/BUILD_ID +1 -1
  57. package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
  58. package/dist/web/standalone/.next/build-manifest.json +2 -2
  59. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  60. package/dist/web/standalone/.next/required-server-files.json +1 -1
  61. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/index.html +1 -1
  78. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
  85. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  86. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  87. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  88. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  89. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  90. package/dist/web/standalone/server.js +1 -1
  91. package/package.json +1 -1
  92. package/packages/mcp-server/dist/remote-questions.d.ts +45 -0
  93. package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -0
  94. package/packages/mcp-server/dist/remote-questions.js +732 -0
  95. package/packages/mcp-server/dist/remote-questions.js.map +1 -0
  96. package/packages/mcp-server/dist/server.d.ts +7 -0
  97. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  98. package/packages/mcp-server/dist/server.js +41 -4
  99. package/packages/mcp-server/dist/server.js.map +1 -1
  100. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  101. package/packages/mcp-server/dist/workflow-tools.js +64 -25
  102. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  103. package/packages/mcp-server/package.json +2 -1
  104. package/packages/mcp-server/src/mcp-server.test.ts +30 -0
  105. package/packages/mcp-server/src/remote-questions.test.ts +294 -0
  106. package/packages/mcp-server/src/remote-questions.ts +916 -0
  107. package/packages/mcp-server/src/server.ts +62 -10
  108. package/packages/mcp-server/src/workflow-tools.test.ts +146 -1
  109. package/packages/mcp-server/src/workflow-tools.ts +84 -43
  110. package/packages/mcp-server/tsconfig.test.json +19 -0
  111. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  112. package/packages/pi-ai/dist/providers/anthropic-auth.test.js +1 -1
  113. package/packages/pi-ai/dist/providers/anthropic-auth.test.js.map +1 -1
  114. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  115. package/packages/pi-ai/dist/providers/anthropic-shared.js +27 -4
  116. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  117. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  118. package/packages/pi-ai/dist/providers/anthropic.js +8 -3
  119. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  120. package/packages/pi-ai/dist/providers/minimax-tool-name.test.d.ts +2 -0
  121. package/packages/pi-ai/dist/providers/minimax-tool-name.test.d.ts.map +1 -0
  122. package/packages/pi-ai/dist/providers/minimax-tool-name.test.js +80 -0
  123. package/packages/pi-ai/dist/providers/minimax-tool-name.test.js.map +1 -0
  124. package/packages/pi-ai/dist/providers/simple-options.d.ts +10 -0
  125. package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  126. package/packages/pi-ai/dist/providers/simple-options.js +16 -1
  127. package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
  128. package/packages/pi-ai/src/providers/anthropic-auth.test.ts +1 -1
  129. package/packages/pi-ai/src/providers/anthropic-shared.ts +26 -5
  130. package/packages/pi-ai/src/providers/anthropic.ts +9 -3
  131. package/packages/pi-ai/src/providers/minimax-tool-name.test.ts +98 -0
  132. package/packages/pi-ai/src/providers/simple-options.ts +17 -1
  133. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  134. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts +2 -0
  135. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts.map +1 -0
  136. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js +203 -0
  137. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js.map +1 -0
  138. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  139. package/packages/pi-coding-agent/dist/core/model-registry.js +14 -0
  140. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  141. package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts +2 -0
  142. package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts.map +1 -0
  143. package/packages/pi-coding-agent/dist/core/redact-secrets.js +49 -0
  144. package/packages/pi-coding-agent/dist/core/redact-secrets.js.map +1 -0
  145. package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts +2 -0
  146. package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts.map +1 -0
  147. package/packages/pi-coding-agent/dist/core/redact-secrets.test.js +67 -0
  148. package/packages/pi-coding-agent/dist/core/redact-secrets.test.js.map +1 -0
  149. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  150. package/packages/pi-coding-agent/dist/core/session-manager.js +9 -5
  151. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  152. package/packages/pi-coding-agent/dist/core/session-manager.test.js +25 -1
  153. package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
  154. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts +1 -1
  155. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
  156. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +5 -4
  157. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
  158. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts +7 -6
  159. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  160. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js +29 -21
  161. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  162. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  163. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +13 -1
  164. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  165. package/packages/pi-coding-agent/src/core/model-registry-custom-caps.test.ts +245 -0
  166. package/packages/pi-coding-agent/src/core/model-registry.ts +16 -0
  167. package/packages/pi-coding-agent/src/core/redact-secrets.test.ts +86 -0
  168. package/packages/pi-coding-agent/src/core/redact-secrets.ts +58 -0
  169. package/packages/pi-coding-agent/src/core/session-manager.test.ts +36 -1
  170. package/packages/pi-coding-agent/src/core/session-manager.ts +9 -5
  171. package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +6 -6
  172. package/packages/pi-coding-agent/src/modes/interactive/components/skill-invocation-message.ts +36 -22
  173. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +13 -1
  174. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  175. package/src/resources/extensions/claude-code-cli/readiness.ts +4 -3
  176. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +78 -17
  177. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +149 -5
  178. package/src/resources/extensions/gsd/auto/loop-deps.ts +13 -0
  179. package/src/resources/extensions/gsd/auto/phases.ts +66 -1
  180. package/src/resources/extensions/gsd/auto/run-unit.ts +29 -0
  181. package/src/resources/extensions/gsd/auto/session.ts +22 -0
  182. package/src/resources/extensions/gsd/auto-dispatch.ts +16 -3
  183. package/src/resources/extensions/gsd/auto-model-selection.ts +1 -1
  184. package/src/resources/extensions/gsd/auto-post-unit.ts +29 -3
  185. package/src/resources/extensions/gsd/auto-prompts.ts +28 -1
  186. package/src/resources/extensions/gsd/auto-recovery.ts +15 -0
  187. package/src/resources/extensions/gsd/auto-start.ts +29 -19
  188. package/src/resources/extensions/gsd/auto-worktree.ts +62 -63
  189. package/src/resources/extensions/gsd/auto.ts +58 -27
  190. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +23 -1
  191. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +40 -9
  192. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +109 -0
  193. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
  194. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +53 -5
  195. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +35 -2
  196. package/src/resources/extensions/gsd/clean-root-preflight.ts +111 -0
  197. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +898 -32
  198. package/src/resources/extensions/gsd/compaction-snapshot.ts +165 -0
  199. package/src/resources/extensions/gsd/error-classifier.ts +10 -3
  200. package/src/resources/extensions/gsd/exec-history.ts +153 -0
  201. package/src/resources/extensions/gsd/exec-sandbox.ts +326 -0
  202. package/src/resources/extensions/gsd/gsd-db.ts +122 -7
  203. package/src/resources/extensions/gsd/guided-flow.ts +221 -0
  204. package/src/resources/extensions/gsd/health-widget.ts +3 -1
  205. package/src/resources/extensions/gsd/init-wizard.ts +15 -1
  206. package/src/resources/extensions/gsd/journal.ts +2 -1
  207. package/src/resources/extensions/gsd/key-manager.ts +6 -0
  208. package/src/resources/extensions/gsd/model-router.ts +42 -1
  209. package/src/resources/extensions/gsd/pre-execution-checks.ts +36 -10
  210. package/src/resources/extensions/gsd/preferences-types.ts +46 -0
  211. package/src/resources/extensions/gsd/preferences-validation.ts +79 -0
  212. package/src/resources/extensions/gsd/preferences.ts +17 -17
  213. package/src/resources/extensions/gsd/prompts/discuss-headless.md +8 -0
  214. package/src/resources/extensions/gsd/prompts/discuss.md +29 -2
  215. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
  216. package/src/resources/extensions/gsd/safety/evidence-collector.ts +119 -0
  217. package/src/resources/extensions/gsd/safety/file-change-validator.ts +17 -4
  218. package/src/resources/extensions/gsd/safety/safety-harness.ts +9 -0
  219. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +119 -1
  220. package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +12 -0
  221. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +49 -0
  222. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +186 -0
  223. package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +123 -0
  224. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
  225. package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
  226. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +2 -0
  227. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +31 -0
  228. package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +1 -1
  229. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +1 -1
  230. package/src/resources/extensions/gsd/tests/escalation.test.ts +1 -1
  231. package/src/resources/extensions/gsd/tests/exec-history.test.ts +124 -0
  232. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +210 -0
  233. package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +58 -0
  234. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +152 -1
  235. package/src/resources/extensions/gsd/tests/init-wizard.test.ts +27 -0
  236. package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +1 -1
  237. package/src/resources/extensions/gsd/tests/issue-4540-regressions.test.ts +288 -0
  238. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +2 -0
  239. package/src/resources/extensions/gsd/tests/key-manager.test.ts +7 -0
  240. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
  241. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  242. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +19 -0
  243. package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +14 -0
  244. package/src/resources/extensions/gsd/tests/pre-exec-gate-loop.test.ts +272 -0
  245. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +234 -0
  246. package/src/resources/extensions/gsd/tests/preferences.test.ts +110 -0
  247. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +44 -0
  248. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +48 -0
  249. package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +388 -0
  250. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +9 -3
  251. package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +205 -0
  252. package/src/resources/extensions/gsd/tests/save-gate-result-render.test.ts +95 -0
  253. package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +32 -40
  254. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +56 -0
  255. package/src/resources/extensions/gsd/tests/token-counter.test.ts +105 -1
  256. package/src/resources/extensions/gsd/tests/tool-compatibility.test.ts +107 -0
  257. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +23 -0
  258. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +65 -2
  259. package/src/resources/extensions/gsd/tests/write-gate.test.ts +64 -0
  260. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +3 -1
  261. package/src/resources/extensions/gsd/token-counter.ts +22 -5
  262. package/src/resources/extensions/gsd/tools/exec-search-tool.ts +81 -0
  263. package/src/resources/extensions/gsd/tools/exec-tool.ts +183 -0
  264. package/src/resources/extensions/gsd/tools/resume-tool.ts +40 -0
  265. package/src/resources/extensions/gsd/uok/plan-v2.ts +26 -3
  266. package/src/resources/extensions/gsd/workflow-logger.ts +3 -1
  267. package/src/resources/extensions/gsd/workflow-mcp.ts +3 -0
  268. package/src/resources/skills/verify-before-complete/SKILL.md +2 -1
  269. package/src/resources/skills/write-docs/SKILL.md +2 -1
  270. /package/dist/web/standalone/.next/static/{ecSsu49rxxcpbNmVP4mLD → lLdDRDspgYzfz0bJAmUSz}/_buildManifest.js +0 -0
  271. /package/dist/web/standalone/.next/static/{ecSsu49rxxcpbNmVP4mLD → lLdDRDspgYzfz0bJAmUSz}/_ssgManifest.js +0 -0
@@ -21,10 +21,11 @@ const CLAUDE_COMMAND = process.platform === "win32" ? "claude.cmd" : "claude";
21
21
 
22
22
  /**
23
23
  * Windows installs vary: some environments expose `claude.cmd` (npm shim),
24
- * others expose a `claude` shim on PATH (for example Git Bash wrappers).
25
- * Try both to avoid false "not installed" results in readiness checks.
24
+ * `claude.exe` (direct binary install), or a bare `claude` shim on PATH
25
+ * (for example Git Bash wrappers). Try all three to avoid false "not
26
+ * installed" results in readiness checks.
26
27
  */
27
- const CLAUDE_COMMAND_CANDIDATES = process.platform === "win32" ? [CLAUDE_COMMAND, "claude"] : [CLAUDE_COMMAND];
28
+ const CLAUDE_COMMAND_CANDIDATES = process.platform === "win32" ? [CLAUDE_COMMAND, "claude.exe", "claude"] : [CLAUDE_COMMAND];
28
29
 
29
30
  function execClaude(args: string[]): Buffer {
30
31
  let lastError: unknown;
@@ -692,18 +692,16 @@ export function makeAbortedMessage(model: string, lastTextContent: string): Assi
692
692
  /**
693
693
  * Resolve the Claude Code permission mode for the current run.
694
694
  *
695
- * GSD subagents run underneath a host Claude Code session the user has
696
- * already consented to, and their work (edits, shell inspection, MCP calls)
697
- * spans the full workflow toolset. Defaulting the inner SDK to
698
- * `bypassPermissions` avoids per-tool approval prompts that offer no
699
- * meaningful safety beyond what the host session and the subagent prompts
700
- * already enforce. `GSD_CLAUDE_CODE_PERMISSION_MODE` lets security-conscious
701
- * users opt into a stricter mode (`acceptEdits`, `default`, `plan`).
695
+ * Defaults to `acceptEdits`, which auto-approves file reads/edits but
696
+ * surfaces a permission dialog for dangerous operations (e.g. general Bash,
697
+ * Agent, WebFetch). This prevents tools outside the allowlist from being
698
+ * silently denied the SDK emits an `extension_ui_request` event so the
699
+ * user sees a prompt instead of a silent refusal that Claude Code mistakes
700
+ * for user rejection (#4383).
702
701
  *
703
- * Tradeoff: bypass means a prompt-injection payload read from an untrusted
704
- * file could trigger tool calls without a second gate. Accepted for GSD
705
- * because the workflow is explicit user intent and the alternative
706
- * (#4099) is continuous approval fatigue that blocks real work.
702
+ * Set `GSD_CLAUDE_CODE_PERMISSION_MODE` to `bypassPermissions` to restore
703
+ * the old always-approve behaviour, or to `default` / `plan` for stricter
704
+ * modes.
707
705
  */
708
706
  export async function resolveClaudePermissionMode(
709
707
  env: NodeJS.ProcessEnv = process.env,
@@ -712,7 +710,7 @@ export async function resolveClaudePermissionMode(
712
710
  if (override === "bypassPermissions" || override === "acceptEdits" || override === "default" || override === "plan") {
713
711
  return override;
714
712
  }
715
- return "bypassPermissions";
713
+ return "acceptEdits";
716
714
  }
717
715
 
718
716
  // NOTE: These helpers intentionally mirror @gsd/pi-ai anthropic-shared
@@ -772,7 +770,7 @@ export function buildSdkOptions(
772
770
  ): Record<string, unknown> {
773
771
  const { reasoning, ...sdkExtraOptions } = extraOptions;
774
772
  const mcpServers = buildWorkflowMcpServers();
775
- const permissionMode = overrides?.permissionMode ?? "bypassPermissions";
773
+ const permissionMode = overrides?.permissionMode ?? "acceptEdits";
776
774
  const disallowedTools = ["AskUserQuestion"];
777
775
  // Pre-authorize the safe built-ins and every registered workflow MCP
778
776
  // server's tools. `acceptEdits` mode (the interactive default) only
@@ -867,6 +865,69 @@ function normalizeToolResultContent(content: unknown): ExternalToolResultContent
867
865
  return blocks.length > 0 ? blocks : [{ type: "text", text: "" }];
868
866
  }
869
867
 
868
+ /**
869
+ * Extract a `details` payload from an MCP tool-result block.
870
+ *
871
+ * MCP's `CallToolResult` carries structured data in `structuredContent` — the
872
+ * protocol's supported channel for non-text payloads. Claude Code's synthetic
873
+ * user message may surface that field in one of two shapes depending on SDK
874
+ * version: as a sibling on the `mcp_tool_result` block itself, or as a
875
+ * dedicated content sub-block with `type: "structuredContent"`. Snake-case
876
+ * (`structured_content`) is accepted defensively in case a transport hop
877
+ * rewrites casing. All other shapes fall back to an empty object so callers
878
+ * can rely on `details` being present.
879
+ */
880
+ function extractStructuredDetailsFromBlock(block: Record<string, unknown>): Record<string, unknown> | undefined {
881
+ const sibling = block.structuredContent ?? (block as Record<string, unknown>).structured_content;
882
+ if (sibling && typeof sibling === "object" && !Array.isArray(sibling)) {
883
+ return sibling as Record<string, unknown>;
884
+ }
885
+
886
+ if (Array.isArray(block.content)) {
887
+ for (const item of block.content) {
888
+ if (!item || typeof item !== "object") continue;
889
+ const sub = item as Record<string, unknown>;
890
+ if (sub.type !== "structuredContent" && sub.type !== "structured_content") continue;
891
+ const payload = sub.structuredContent ?? sub.structured_content ?? sub.data ?? sub.value;
892
+ if (payload && typeof payload === "object" && !Array.isArray(payload)) {
893
+ return payload as Record<string, unknown>;
894
+ }
895
+ }
896
+ }
897
+
898
+ // Return undefined (not {}) when no structured payload is present, matching
899
+ // the pre-#4477 contract where `details` was nullable. An empty-object
900
+ // sentinel is truthy and breaks downstream consumers that gate on
901
+ // `if (details)`. `undefined` matches the type of the field these results
902
+ // flow into (`Record<string, unknown> | undefined`).
903
+ return undefined;
904
+ }
905
+
906
+ /**
907
+ * True for items that are MCP `structuredContent` pseudo-blocks living inside
908
+ * a tool-result `content[]` array. These blocks carry the structured payload
909
+ * (extracted separately by `extractStructuredDetailsFromBlock`) and must NOT
910
+ * leak into the visible content rendered to the user — otherwise the renderer
911
+ * stringifies the JSON pseudo-block and shows it next to the actual tool
912
+ * output. See PR #4477 review (CodeRabbit, post-fix-round).
913
+ */
914
+ function isStructuredContentPseudoBlock(item: unknown): boolean {
915
+ if (!item || typeof item !== "object") return false;
916
+ const type = (item as Record<string, unknown>).type;
917
+ return type === "structuredContent" || type === "structured_content";
918
+ }
919
+
920
+ /**
921
+ * Strip `structuredContent` pseudo-blocks from a tool-result content array
922
+ * before normalization. The structured payload is extracted via the sibling
923
+ * `structuredContent` field (or a dedicated extractor pass on the raw block);
924
+ * the visible content path must not include the pseudo-block itself.
925
+ */
926
+ function stripStructuredContentPseudoBlocks(content: unknown): unknown {
927
+ if (!Array.isArray(content)) return content;
928
+ return content.filter((item) => !isStructuredContentPseudoBlock(item));
929
+ }
930
+
870
931
  /** Extract tool result payloads from an SDK synthetic user message, keyed by tool-use ID. */
871
932
  export function extractToolResultsFromSdkUserMessage(message: SDKUserMessage): Array<{
872
933
  toolUseId: string;
@@ -890,8 +951,8 @@ export function extractToolResultsFromSdkUserMessage(message: SDKUserMessage): A
890
951
  extracted.push({
891
952
  toolUseId,
892
953
  result: {
893
- content: normalizeToolResultContent(block.content),
894
- details: {},
954
+ content: normalizeToolResultContent(stripStructuredContentPseudoBlocks(block.content)),
955
+ details: extractStructuredDetailsFromBlock(block),
895
956
  isError: block.is_error === true,
896
957
  },
897
958
  });
@@ -906,8 +967,8 @@ export function extractToolResultsFromSdkUserMessage(message: SDKUserMessage): A
906
967
  extracted.push({
907
968
  toolUseId,
908
969
  result: {
909
- content: normalizeToolResultContent(toolResult.content),
910
- details: {},
970
+ content: normalizeToolResultContent(stripStructuredContentPseudoBlocks(toolResult.content)),
971
+ details: extractStructuredDetailsFromBlock(toolResult),
911
972
  isError: toolResult.is_error === true,
912
973
  },
913
974
  });
@@ -372,13 +372,146 @@ describe("stream-adapter — Claude Code external tool results", () => {
372
372
  toolUseId: "tool-bash-1",
373
373
  result: {
374
374
  content: [{ type: "text", text: "line 1\nline 2" }],
375
- details: {},
375
+ // extractStructuredDetailsFromBlock returns undefined when no
376
+ // structured payload exists, restoring the pre-#4477 nullable
377
+ // contract (#4477 review feedback).
378
+ details: undefined,
376
379
  isError: false,
377
380
  },
378
381
  },
379
382
  ]);
380
383
  });
381
384
 
385
+ test("extractToolResultsFromSdkUserMessage reads structuredContent as a sibling field (#4472)", () => {
386
+ const message: SDKUserMessage = {
387
+ type: "user",
388
+ session_id: "sess-1",
389
+ parent_tool_use_id: "tool-mcp-1",
390
+ message: {
391
+ role: "user",
392
+ content: [
393
+ {
394
+ type: "mcp_tool_result",
395
+ tool_use_id: "tool-mcp-1",
396
+ content: [{ type: "text", text: "Gate Q3 result saved: verdict=pass" }],
397
+ is_error: false,
398
+ structuredContent: { gateId: "Q3", verdict: "pass" },
399
+ } as unknown as Record<string, unknown>,
400
+ ],
401
+ },
402
+ };
403
+
404
+ const results = extractToolResultsFromSdkUserMessage(message);
405
+ assert.deepEqual(results[0].result.details, { gateId: "Q3", verdict: "pass" });
406
+ });
407
+
408
+ test("extractToolResultsFromSdkUserMessage reads structuredContent from a content sub-block (#4472)", () => {
409
+ const message: SDKUserMessage = {
410
+ type: "user",
411
+ session_id: "sess-1",
412
+ parent_tool_use_id: "tool-mcp-2",
413
+ message: {
414
+ role: "user",
415
+ content: [
416
+ {
417
+ type: "mcp_tool_result",
418
+ tool_use_id: "tool-mcp-2",
419
+ content: [
420
+ { type: "text", text: "Gate Q4 result saved: verdict=flag" },
421
+ { type: "structuredContent", structuredContent: { gateId: "Q4", verdict: "flag" } },
422
+ ],
423
+ is_error: false,
424
+ } as unknown as Record<string, unknown>,
425
+ ],
426
+ },
427
+ };
428
+
429
+ const results = extractToolResultsFromSdkUserMessage(message);
430
+ assert.deepEqual(results[0].result.details, { gateId: "Q4", verdict: "flag" });
431
+ });
432
+
433
+ test("#4477 extractToolResultsFromSdkUserMessage does NOT leak structuredContent pseudo-blocks into visible content", () => {
434
+ // Regression: when a content sub-block carries `type: "structuredContent"`,
435
+ // it carries the structured payload (extracted separately into `details`)
436
+ // and must NOT appear in the visible `content` array — otherwise the
437
+ // renderer stringifies the JSON pseudo-block and shows it next to the
438
+ // actual tool output. See PR #4477 review (CodeRabbit, post-fix-round).
439
+ const message: SDKUserMessage = {
440
+ type: "user",
441
+ session_id: "sess-1",
442
+ parent_tool_use_id: "tool-mcp-strip",
443
+ message: {
444
+ role: "user",
445
+ content: [
446
+ {
447
+ type: "mcp_tool_result",
448
+ tool_use_id: "tool-mcp-strip",
449
+ content: [
450
+ { type: "text", text: "Gate Q5 result saved: verdict=pass" },
451
+ { type: "structuredContent", structuredContent: { gateId: "Q5", verdict: "pass" } },
452
+ { type: "text", text: "second visible line" },
453
+ // snake_case variant — also a pseudo-block; also must be stripped
454
+ { type: "structured_content", structured_content: { extra: "data" } },
455
+ ],
456
+ is_error: false,
457
+ } as unknown as Record<string, unknown>,
458
+ ],
459
+ },
460
+ };
461
+
462
+ const results = extractToolResultsFromSdkUserMessage(message);
463
+ assert.equal(results.length, 1, "should extract one result");
464
+ const result = results[0].result;
465
+
466
+ // The structured payload IS extracted to `details`.
467
+ assert.deepEqual(result.details, { gateId: "Q5", verdict: "pass" });
468
+
469
+ // The visible content has the two text blocks but NEITHER pseudo-block.
470
+ const visibleTexts = result.content.map((c: any) => c.text);
471
+ assert.deepEqual(
472
+ visibleTexts,
473
+ ["Gate Q5 result saved: verdict=pass", "second visible line"],
474
+ "visible content must include only the two text blocks; both structuredContent variants must be stripped",
475
+ );
476
+
477
+ // Belt-and-suspenders: assert no rendered text shows the JSON serialization
478
+ // of a pseudo-block. We don't check for bare keys like "gateId" or "verdict"
479
+ // because those are legitimate words in the gate-result message text. The
480
+ // regression signature would be a JSON-shaped substring that could only
481
+ // appear via stringification.
482
+ const allText = visibleTexts.join("\n");
483
+ assert.ok(
484
+ !allText.includes('"structuredContent"'),
485
+ "rendered content must not include the pseudo-block type marker as JSON text",
486
+ );
487
+ assert.ok(
488
+ !allText.includes('"structured_content"'),
489
+ "rendered content must not include the snake_case pseudo-block type marker as JSON text",
490
+ );
491
+ });
492
+
493
+ test("extractToolResultsFromSdkUserMessage accepts snake_case structured_content defensively (#4472)", () => {
494
+ const message: SDKUserMessage = {
495
+ type: "user",
496
+ session_id: "sess-1",
497
+ parent_tool_use_id: "tool-mcp-3",
498
+ message: {
499
+ role: "user",
500
+ content: [
501
+ {
502
+ type: "mcp_tool_result",
503
+ tool_use_id: "tool-mcp-3",
504
+ content: [{ type: "text", text: "ok" }],
505
+ structured_content: { operation: "save_gate_result" },
506
+ } as unknown as Record<string, unknown>,
507
+ ],
508
+ },
509
+ };
510
+
511
+ const results = extractToolResultsFromSdkUserMessage(message);
512
+ assert.deepEqual(results[0].result.details, { operation: "save_gate_result" });
513
+ });
514
+
382
515
  test("extractToolResultsFromSdkUserMessage falls back to tool_use_result", () => {
383
516
  const message: SDKUserMessage = {
384
517
  type: "user",
@@ -398,7 +531,9 @@ describe("stream-adapter — Claude Code external tool results", () => {
398
531
  toolUseId: "tool-read-1",
399
532
  result: {
400
533
  content: [{ type: "text", text: "file contents" }],
401
- details: {},
534
+ // undefined (not {}) per the restored nullable contract — see
535
+ // the analogous assertion in the tool_result test above.
536
+ details: undefined,
402
537
  isError: true,
403
538
  },
404
539
  },
@@ -1081,11 +1216,15 @@ describe("stream-adapter — permission mode (F10)", () => {
1081
1216
  }
1082
1217
  }
1083
1218
 
1084
- test("buildSdkOptions defaults to bypassPermissions for backwards compatibility", () => {
1219
+ test("buildSdkOptions defaults to acceptEdits (#4383)", () => {
1085
1220
  clearWorkflowMcpEnv();
1086
1221
  const opts = buildSdkOptions("claude-sonnet-4-6", "test");
1087
- assert.equal(opts.permissionMode, "bypassPermissions");
1088
- assert.equal(opts.allowDangerouslySkipPermissions, true);
1222
+ assert.equal(opts.permissionMode, "acceptEdits");
1223
+ assert.equal(
1224
+ opts.allowDangerouslySkipPermissions,
1225
+ false,
1226
+ "allowDangerouslySkipPermissions must be false when permissionMode is acceptEdits",
1227
+ );
1089
1228
  });
1090
1229
 
1091
1230
  test("buildSdkOptions respects explicit acceptEdits override", () => {
@@ -1099,6 +1238,11 @@ describe("stream-adapter — permission mode (F10)", () => {
1099
1238
  );
1100
1239
  });
1101
1240
 
1241
+ test("resolveClaudePermissionMode defaults to acceptEdits when no env var is set (#4383)", async () => {
1242
+ const mode = await resolveClaudePermissionMode({});
1243
+ assert.equal(mode, "acceptEdits");
1244
+ });
1245
+
1102
1246
  test("resolveClaudePermissionMode honours the GSD_CLAUDE_CODE_PERMISSION_MODE env override", async () => {
1103
1247
  const env = { GSD_CLAUDE_CODE_PERMISSION_MODE: "acceptEdits" } as NodeJS.ProcessEnv;
1104
1248
  const mode = await resolveClaudePermissionMode(env);
@@ -22,6 +22,7 @@ import type { CmuxLogLevel } from "../../cmux/index.js";
22
22
  import type { JournalEntry } from "../journal.js";
23
23
  import type { MergeReconcileResult } from "../auto-recovery.js";
24
24
  import type { UokTurnObserver } from "../uok/contracts.js";
25
+ import type { PreflightResult } from "../clean-root-preflight.js";
25
26
 
26
27
  /**
27
28
  * Dependencies injected by the caller (auto.ts startAuto) so autoLoop
@@ -122,6 +123,18 @@ export interface LoopDeps {
122
123
  ) => string | null;
123
124
  reconcileMergeState: (basePath: string, ctx: ExtensionContext) => MergeReconcileResult;
124
125
 
126
+ // Clean-root preflight gate (#2909)
127
+ preflightCleanRoot: (
128
+ basePath: string,
129
+ milestoneId: string,
130
+ notify: (message: string, level: "info" | "warning" | "error") => void,
131
+ ) => PreflightResult;
132
+ postflightPopStash: (
133
+ basePath: string,
134
+ milestoneId: string,
135
+ notify: (message: string, level: "info" | "warning" | "error") => void,
136
+ ) => void;
137
+
125
138
  // Budget/context/secrets
126
139
  getLedger: () => unknown;
127
140
  getProjectTotals: (units: unknown) => { cost: number };
@@ -54,7 +54,8 @@ import type { MinimalModelRegistry } from "../context-budget.js";
54
54
  import { ensurePlanV2Graph } from "../uok/plan-v2.js";
55
55
  import { resolveUokFlags } from "../uok/flags.js";
56
56
  import { UokGateRunner } from "../uok/gate-runner.js";
57
- import { resetEvidence } from "../safety/evidence-collector.js";
57
+ import { resetEvidence, loadEvidenceFromDisk } from "../safety/evidence-collector.js";
58
+ import { parseUnitId } from "../unit-id.js";
58
59
  import { createCheckpoint, cleanupCheckpoint, rollbackToCheckpoint } from "../safety/git-checkpoint.js";
59
60
  import { resolveSafetyHarnessConfig } from "../safety/safety-harness.js";
60
61
  import {
@@ -545,6 +546,12 @@ export async function runPreDispatch(
545
546
  loopState.stuckRecoveryAttempts = 0;
546
547
 
547
548
  // Worktree lifecycle on milestone transition — merge current, enter next
549
+ // #2909: preflight — warn + stash dirty working tree before merge
550
+ const preflightTransition = deps.preflightCleanRoot(
551
+ s.originalBasePath || s.basePath,
552
+ s.currentMilestoneId!,
553
+ ctx.ui.notify.bind(ctx.ui),
554
+ );
548
555
  try {
549
556
  deps.resolver.mergeAndExit(s.currentMilestoneId!, ctx.ui);
550
557
  } catch (mergeErr) {
@@ -566,6 +573,14 @@ export async function runPreDispatch(
566
573
  await deps.stopAuto(ctx, pi, `Merge error on milestone ${s.currentMilestoneId}: ${String(mergeErr)}`);
567
574
  return { action: "break", reason: "merge-failed" };
568
575
  }
576
+ // #2909: postflight — restore stashed changes after successful merge
577
+ if (preflightTransition.stashPushed) {
578
+ deps.postflightPopStash(
579
+ s.originalBasePath || s.basePath,
580
+ s.currentMilestoneId!,
581
+ ctx.ui.notify.bind(ctx.ui),
582
+ );
583
+ }
569
584
 
570
585
  // PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
571
586
 
@@ -644,6 +659,12 @@ export async function runPreDispatch(
644
659
  if (incomplete.length === 0 && state.registry.length > 0) {
645
660
  // All milestones complete — merge milestone branch before stopping
646
661
  if (s.currentMilestoneId) {
662
+ // #2909: preflight — warn + stash dirty working tree before merge
663
+ const preflightAllComplete = deps.preflightCleanRoot(
664
+ s.originalBasePath || s.basePath,
665
+ s.currentMilestoneId,
666
+ ctx.ui.notify.bind(ctx.ui),
667
+ );
647
668
  try {
648
669
  deps.resolver.mergeAndExit(s.currentMilestoneId, ctx.ui);
649
670
  // Prevent stopAuto from attempting the same merge (#2645)
@@ -665,6 +686,14 @@ export async function runPreDispatch(
665
686
  await deps.stopAuto(ctx, pi, `Merge error on milestone ${s.currentMilestoneId}: ${String(mergeErr)}`);
666
687
  return { action: "break", reason: "merge-failed" };
667
688
  }
689
+ // #2909: postflight — restore stashed changes after successful merge
690
+ if (preflightAllComplete.stashPushed) {
691
+ deps.postflightPopStash(
692
+ s.originalBasePath || s.basePath,
693
+ s.currentMilestoneId,
694
+ ctx.ui.notify.bind(ctx.ui),
695
+ );
696
+ }
668
697
 
669
698
  // PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
670
699
  }
@@ -758,6 +787,12 @@ export async function runPreDispatch(
758
787
  if (state.phase === "complete") {
759
788
  // Milestone merge on complete (before closeout so branch state is clean)
760
789
  if (s.currentMilestoneId) {
790
+ // #2909: preflight — warn + stash dirty working tree before merge
791
+ const preflightComplete = deps.preflightCleanRoot(
792
+ s.originalBasePath || s.basePath,
793
+ s.currentMilestoneId,
794
+ ctx.ui.notify.bind(ctx.ui),
795
+ );
761
796
  try {
762
797
  deps.resolver.mergeAndExit(s.currentMilestoneId, ctx.ui);
763
798
  // Prevent stopAuto from attempting the same merge (#2645)
@@ -779,6 +814,14 @@ export async function runPreDispatch(
779
814
  await deps.stopAuto(ctx, pi, `Merge error on milestone ${s.currentMilestoneId}: ${String(mergeErr)}`);
780
815
  return { action: "break", reason: "merge-failed" };
781
816
  }
817
+ // #2909: postflight — restore stashed changes after successful merge
818
+ if (preflightComplete.stashPushed) {
819
+ deps.postflightPopStash(
820
+ s.originalBasePath || s.basePath,
821
+ s.currentMilestoneId,
822
+ ctx.ui.notify.bind(ctx.ui),
823
+ );
824
+ }
782
825
 
783
826
  // PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
784
827
  }
@@ -1385,6 +1428,14 @@ export async function runUnitPhase(
1385
1428
  );
1386
1429
  if (safetyConfig.enabled && safetyConfig.evidence_collection) {
1387
1430
  resetEvidence();
1431
+ // Restore persisted evidence so session-restart resumes don't produce
1432
+ // false-positive "no bash calls" warnings (Bug #4385).
1433
+ if (s.basePath && unitType === "execute-task") {
1434
+ const { milestone: eMid, slice: eSid, task: eTid } = parseUnitId(unitId);
1435
+ if (eMid && eSid && eTid) {
1436
+ loadEvidenceFromDisk(s.basePath, eMid, eSid, eTid);
1437
+ }
1438
+ }
1388
1439
  }
1389
1440
  // Only checkpoint code-executing units (not lifecycle/planning units)
1390
1441
  if (safetyConfig.enabled && safetyConfig.checkpoints && unitType === "execute-task") {
@@ -1928,6 +1979,20 @@ export async function runFinalize(
1928
1979
  debugLog("autoLoop", { phase: "sidecar-artifact-retry-skipped", iteration: ic.iteration });
1929
1980
  } else {
1930
1981
  // s.pendingVerificationRetry was set by postUnitPreVerification.
1982
+ // Emit a dedicated journal event so forensics can distinguish bounded
1983
+ // verification retries from genuine stuck-loop dispatch repetitions (#4540).
1984
+ const retryInfo = s.pendingVerificationRetry;
1985
+ deps.emitJournalEvent({
1986
+ ts: new Date().toISOString(),
1987
+ flowId: ic.flowId,
1988
+ seq: ic.nextSeq(),
1989
+ eventType: "artifact-verification-retry",
1990
+ data: {
1991
+ unitType: preUnitSnapshot?.type,
1992
+ unitId: retryInfo?.unitId,
1993
+ attempt: retryInfo?.attempt,
1994
+ },
1995
+ });
1931
1996
  // Continue the loop — next iteration will inject the retry context into the prompt.
1932
1997
  debugLog("autoLoop", { phase: "artifact-verification-retry", iteration: ic.iteration });
1933
1998
  return { action: "continue" };
@@ -118,6 +118,35 @@ export async function runUnit(
118
118
  logWarning("engine", "Failed to chdir to basePath before dispatch", { basePath: s.basePath, error: String(e) });
119
119
  }
120
120
 
121
+ // ── Provider request-readiness pre-check (#4555) ──
122
+ // Verify the provider can accept requests before dispatching. If the token
123
+ // has expired since bootstrap, return cancelled immediately so the unit is
124
+ // not wasted on a guaranteed 401.
125
+ {
126
+ const provider = s.currentUnitModel?.provider ?? ctx.model?.provider;
127
+ const registry = (ctx as any).modelRegistry;
128
+
129
+ if (provider && registry != null && typeof registry.isProviderRequestReady === "function") {
130
+ let ready = false;
131
+ try {
132
+ ready = registry.isProviderRequestReady(provider);
133
+ } catch {
134
+ ready = false;
135
+ }
136
+
137
+ if (!ready) {
138
+ return {
139
+ status: "cancelled",
140
+ errorContext: {
141
+ message: `Provider ${provider} is not request-ready (login/token expired)`,
142
+ category: "provider",
143
+ isTransient: false,
144
+ },
145
+ };
146
+ }
147
+ }
148
+ }
149
+
121
150
  // ── Send the prompt ──
122
151
  debugLog("runUnit", { phase: "send-message", unitType, unitId });
123
152
 
@@ -64,6 +64,15 @@ export interface SidecarItem {
64
64
  captureId?: string;
65
65
  }
66
66
 
67
+ export interface PreExecFailure {
68
+ /** Milestone/slice that failed (e.g. "M001/S02"). */
69
+ unitId: string;
70
+ /** Verbatim blocking check strings from the failed gate run. */
71
+ blockingFindings: string[];
72
+ /** Condensed gate verdict excerpt for context (status + rationale). */
73
+ verdictExcerpt: string;
74
+ }
75
+
67
76
  // ─── Constants ───────────────────────────────────────────────────────────────
68
77
 
69
78
  export const MAX_UNIT_DISPATCHES = 3;
@@ -139,6 +148,18 @@ export class AutoSession {
139
148
  // ── Sidecar queue ─────────────────────────────────────────────────────
140
149
  sidecarQueue: SidecarItem[] = [];
141
150
 
151
+ // ── Pre-exec gate failure context (#4551) ───────────────────────────
152
+ /**
153
+ * Persisted when a pre-execution gate fails on a plan-slice or refine-slice
154
+ * unit. The planning → plan-slice dispatch rule reads this field and injects
155
+ * the failure details into the next re-dispatch prompt so the LLM can fix the
156
+ * specific issues instead of producing an identical plan.
157
+ *
158
+ * Cleared after it has been consumed (injected into the prompt) to avoid
159
+ * stale context bleeding into unrelated slices.
160
+ */
161
+ lastPreExecFailure: PreExecFailure | null = null;
162
+
142
163
  // ── Tool invocation errors (#2883) ──────────────────────────────────
143
164
  /** Set when a GSD tool execution ends with isError due to malformed/truncated
144
165
  * JSON arguments. Checked by postUnitPreVerification to break retry loops. */
@@ -267,6 +288,7 @@ export class AutoSession {
267
288
  this.sidecarQueue = [];
268
289
  this.rewriteAttemptCount = 0;
269
290
  this.consecutiveCompleteBootstraps = 0;
291
+ this.lastPreExecFailure = null;
270
292
  this.lastToolInvocationError = null;
271
293
  this.lastGitActionFailure = null;
272
294
  this.lastGitActionStatus = null;
@@ -568,15 +568,28 @@ export const DISPATCH_RULES: DispatchRule[] = [
568
568
  },
569
569
  {
570
570
  name: "planning → plan-slice",
571
- match: async ({ state, mid, midTitle, basePath, sessionContextWindow, modelRegistry }) => {
571
+ match: async ({ state, mid, midTitle, basePath, sessionContextWindow, modelRegistry, session }) => {
572
572
  if (state.phase !== "planning") return null;
573
573
  if (!state.activeSlice) return missingSliceStop(mid, state.phase);
574
574
  const sid = state.activeSlice!.id;
575
575
  const sTitle = state.activeSlice!.title;
576
+ // #4551: Consume any persisted pre-exec failure for this slice so the
577
+ // re-dispatched prompt includes the exact blocked references. Clear the
578
+ // field immediately after reading to prevent stale context leaking into
579
+ // a later, unrelated plan-slice run.
580
+ const unitId = `${mid}/${sid}`;
581
+ let priorPreExecFailure: { blockingFindings: string[]; verdictExcerpt: string } | undefined;
582
+ if (session?.lastPreExecFailure?.unitId === unitId) {
583
+ priorPreExecFailure = {
584
+ blockingFindings: session.lastPreExecFailure.blockingFindings,
585
+ verdictExcerpt: session.lastPreExecFailure.verdictExcerpt,
586
+ };
587
+ session.lastPreExecFailure = null;
588
+ }
576
589
  return {
577
590
  action: "dispatch",
578
591
  unitType: "plan-slice",
579
- unitId: `${mid}/${sid}`,
592
+ unitId,
580
593
  prompt: await buildPlanSlicePrompt(
581
594
  mid,
582
595
  midTitle,
@@ -584,7 +597,7 @@ export const DISPATCH_RULES: DispatchRule[] = [
584
597
  sTitle,
585
598
  basePath,
586
599
  undefined,
587
- { sessionContextWindow, modelRegistry },
600
+ { sessionContextWindow, modelRegistry, priorPreExecFailure },
588
601
  ),
589
602
  };
590
603
  },
@@ -395,7 +395,7 @@ export async function selectAndApplyModel(
395
395
  // ADR-005: Adjust active tool set for the selected model's provider capabilities.
396
396
  // Hard-filter incompatible tools, then let extensions override via adjust_tool_set hook.
397
397
  const activeToolNames = pi.getActiveTools();
398
- const { toolNames: compatibleTools, removedTools } = adjustToolSet(activeToolNames, model.api);
398
+ const { toolNames: compatibleTools, removedTools } = adjustToolSet(activeToolNames, model.api, model.provider);
399
399
  let finalToolNames = compatibleTools;
400
400
 
401
401
  // Fire adjust_tool_set hook — extensions can override the filtered tool set