gsd-pi 2.76.0-dev.4100bd590 → 2.76.0-dev.479ad0e78

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 (362) 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/onboarding.js +45 -0
  5. package/dist/resource-loader.d.ts +1 -1
  6. package/dist/resource-loader.js +2 -8
  7. package/dist/resources/extensions/claude-code-cli/readiness.js +4 -3
  8. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +77 -17
  9. package/dist/resources/extensions/gsd/auto/loop.js +9 -0
  10. package/dist/resources/extensions/gsd/auto/phases.js +58 -5
  11. package/dist/resources/extensions/gsd/auto/run-unit.js +38 -2
  12. package/dist/resources/extensions/gsd/auto/session.js +22 -1
  13. package/dist/resources/extensions/gsd/auto-dispatch.js +16 -3
  14. package/dist/resources/extensions/gsd/auto-model-selection.js +14 -3
  15. package/dist/resources/extensions/gsd/auto-post-unit.js +25 -2
  16. package/dist/resources/extensions/gsd/auto-prompts.js +14 -0
  17. package/dist/resources/extensions/gsd/auto-recovery.js +32 -1
  18. package/dist/resources/extensions/gsd/auto-start.js +58 -57
  19. package/dist/resources/extensions/gsd/auto-worktree.js +51 -53
  20. package/dist/resources/extensions/gsd/auto.js +70 -28
  21. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +17 -1
  22. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +39 -9
  23. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +93 -0
  24. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
  25. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +52 -6
  26. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +34 -2
  27. package/dist/resources/extensions/gsd/clean-root-preflight.js +93 -0
  28. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +968 -23
  29. package/dist/resources/extensions/gsd/compaction-snapshot.js +121 -0
  30. package/dist/resources/extensions/gsd/complexity-classifier.js +5 -3
  31. package/dist/resources/extensions/gsd/error-classifier.js +10 -3
  32. package/dist/resources/extensions/gsd/exec-history.js +120 -0
  33. package/dist/resources/extensions/gsd/exec-sandbox.js +258 -0
  34. package/dist/resources/extensions/gsd/gitignore.js +1 -0
  35. package/dist/resources/extensions/gsd/gsd-db.js +149 -31
  36. package/dist/resources/extensions/gsd/guided-flow.js +190 -1
  37. package/dist/resources/extensions/gsd/health-widget.js +4 -1
  38. package/dist/resources/extensions/gsd/init-wizard.js +15 -1
  39. package/dist/resources/extensions/gsd/key-manager.js +28 -0
  40. package/dist/resources/extensions/gsd/model-router.js +36 -3
  41. package/dist/resources/extensions/gsd/pre-execution-checks.js +44 -9
  42. package/dist/resources/extensions/gsd/preferences-types.js +9 -0
  43. package/dist/resources/extensions/gsd/preferences-validation.js +83 -0
  44. package/dist/resources/extensions/gsd/preferences.js +17 -17
  45. package/dist/resources/extensions/gsd/prompt-loader.js +22 -7
  46. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +8 -0
  47. package/dist/resources/extensions/gsd/prompts/discuss.md +29 -2
  48. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
  49. package/dist/resources/extensions/gsd/safety/evidence-collector.js +96 -0
  50. package/dist/resources/extensions/gsd/safety/file-change-validator.js +13 -5
  51. package/dist/resources/extensions/gsd/safety/safety-harness.js +5 -1
  52. package/dist/resources/extensions/gsd/token-counter.js +22 -5
  53. package/dist/resources/extensions/gsd/tools/complete-milestone.js +16 -10
  54. package/dist/resources/extensions/gsd/tools/exec-search-tool.js +59 -0
  55. package/dist/resources/extensions/gsd/tools/exec-tool.js +126 -0
  56. package/dist/resources/extensions/gsd/tools/resume-tool.js +23 -0
  57. package/dist/resources/extensions/gsd/uok/plan-v2.js +20 -3
  58. package/dist/resources/extensions/gsd/workflow-mcp.js +3 -0
  59. package/dist/resources/extensions/gsd/worktree-resolver.js +50 -10
  60. package/dist/resources/skills/verify-before-complete/SKILL.md +2 -1
  61. package/dist/resources/skills/write-docs/SKILL.md +2 -1
  62. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  63. package/dist/web/standalone/.next/BUILD_ID +1 -1
  64. package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
  65. package/dist/web/standalone/.next/build-manifest.json +2 -2
  66. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  67. package/dist/web/standalone/.next/required-server-files.json +1 -1
  68. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/index.html +1 -1
  85. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
  92. package/dist/web/standalone/.next/server/chunks/6897.js +2 -2
  93. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  94. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  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/server.js +1 -1
  99. package/dist/welcome-screen.js +6 -1
  100. package/dist/wizard.js +2 -0
  101. package/package.json +1 -1
  102. package/packages/mcp-server/dist/remote-questions.d.ts +45 -0
  103. package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -0
  104. package/packages/mcp-server/dist/remote-questions.js +732 -0
  105. package/packages/mcp-server/dist/remote-questions.js.map +1 -0
  106. package/packages/mcp-server/dist/server.d.ts +7 -0
  107. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  108. package/packages/mcp-server/dist/server.js +70 -8
  109. package/packages/mcp-server/dist/server.js.map +1 -1
  110. package/packages/mcp-server/dist/session-manager.d.ts +14 -0
  111. package/packages/mcp-server/dist/session-manager.d.ts.map +1 -1
  112. package/packages/mcp-server/dist/session-manager.js +49 -1
  113. package/packages/mcp-server/dist/session-manager.js.map +1 -1
  114. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  115. package/packages/mcp-server/dist/workflow-tools.js +64 -25
  116. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  117. package/packages/mcp-server/package.json +2 -1
  118. package/packages/mcp-server/src/mcp-server.test.ts +67 -0
  119. package/packages/mcp-server/src/remote-questions.test.ts +294 -0
  120. package/packages/mcp-server/src/remote-questions.ts +916 -0
  121. package/packages/mcp-server/src/server.ts +89 -14
  122. package/packages/mcp-server/src/session-manager.ts +43 -1
  123. package/packages/mcp-server/src/workflow-tools.test.ts +146 -1
  124. package/packages/mcp-server/src/workflow-tools.ts +84 -43
  125. package/packages/mcp-server/tsconfig.test.json +19 -0
  126. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  127. package/packages/pi-ai/dist/models/custom.d.ts +38 -0
  128. package/packages/pi-ai/dist/models/custom.d.ts.map +1 -1
  129. package/packages/pi-ai/dist/models/custom.js +41 -0
  130. package/packages/pi-ai/dist/models/custom.js.map +1 -1
  131. package/packages/pi-ai/dist/providers/anthropic-auth.test.js +1 -1
  132. package/packages/pi-ai/dist/providers/anthropic-auth.test.js.map +1 -1
  133. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  134. package/packages/pi-ai/dist/providers/anthropic-shared.js +27 -4
  135. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  136. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  137. package/packages/pi-ai/dist/providers/anthropic.js +8 -3
  138. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  139. package/packages/pi-ai/dist/providers/minimax-tool-name.test.d.ts +2 -0
  140. package/packages/pi-ai/dist/providers/minimax-tool-name.test.d.ts.map +1 -0
  141. package/packages/pi-ai/dist/providers/minimax-tool-name.test.js +80 -0
  142. package/packages/pi-ai/dist/providers/minimax-tool-name.test.js.map +1 -0
  143. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  144. package/packages/pi-ai/dist/providers/openai-completions.js +60 -15
  145. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  146. package/packages/pi-ai/dist/providers/simple-options.d.ts +10 -0
  147. package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  148. package/packages/pi-ai/dist/providers/simple-options.js +16 -1
  149. package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
  150. package/packages/pi-ai/dist/providers/think-tag-parser.d.ts +17 -0
  151. package/packages/pi-ai/dist/providers/think-tag-parser.d.ts.map +1 -0
  152. package/packages/pi-ai/dist/providers/think-tag-parser.js +75 -0
  153. package/packages/pi-ai/dist/providers/think-tag-parser.js.map +1 -0
  154. package/packages/pi-ai/dist/providers/think-tag-parser.test.d.ts +2 -0
  155. package/packages/pi-ai/dist/providers/think-tag-parser.test.d.ts.map +1 -0
  156. package/packages/pi-ai/dist/providers/think-tag-parser.test.js +41 -0
  157. package/packages/pi-ai/dist/providers/think-tag-parser.test.js.map +1 -0
  158. package/packages/pi-ai/src/models/custom.ts +42 -0
  159. package/packages/pi-ai/src/providers/anthropic-auth.test.ts +1 -1
  160. package/packages/pi-ai/src/providers/anthropic-shared.ts +26 -5
  161. package/packages/pi-ai/src/providers/anthropic.ts +9 -3
  162. package/packages/pi-ai/src/providers/minimax-tool-name.test.ts +98 -0
  163. package/packages/pi-ai/src/providers/openai-completions.ts +57 -16
  164. package/packages/pi-ai/src/providers/simple-options.ts +17 -1
  165. package/packages/pi-ai/src/providers/think-tag-parser.test.ts +44 -0
  166. package/packages/pi-ai/src/providers/think-tag-parser.ts +94 -0
  167. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  168. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +3 -2
  169. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
  170. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +2 -0
  171. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  172. package/packages/pi-coding-agent/dist/core/agent-session.js +7 -0
  173. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  174. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +2 -0
  175. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  176. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  177. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +7 -0
  178. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  179. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  180. package/packages/pi-coding-agent/dist/core/model-discovery.d.ts +3 -1
  181. package/packages/pi-coding-agent/dist/core/model-discovery.d.ts.map +1 -1
  182. package/packages/pi-coding-agent/dist/core/model-discovery.js +92 -12
  183. package/packages/pi-coding-agent/dist/core/model-discovery.js.map +1 -1
  184. package/packages/pi-coding-agent/dist/core/model-discovery.test.js +16 -1
  185. package/packages/pi-coding-agent/dist/core/model-discovery.test.js.map +1 -1
  186. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts +2 -0
  187. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts.map +1 -0
  188. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js +203 -0
  189. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js.map +1 -0
  190. package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js +61 -1
  191. package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js.map +1 -1
  192. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +5 -0
  193. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  194. package/packages/pi-coding-agent/dist/core/model-registry.js +90 -10
  195. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  196. package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts +2 -0
  197. package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts.map +1 -0
  198. package/packages/pi-coding-agent/dist/core/redact-secrets.js +49 -0
  199. package/packages/pi-coding-agent/dist/core/redact-secrets.js.map +1 -0
  200. package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts +2 -0
  201. package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts.map +1 -0
  202. package/packages/pi-coding-agent/dist/core/redact-secrets.test.js +67 -0
  203. package/packages/pi-coding-agent/dist/core/redact-secrets.test.js.map +1 -0
  204. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  205. package/packages/pi-coding-agent/dist/core/session-manager.js +10 -6
  206. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  207. package/packages/pi-coding-agent/dist/core/session-manager.test.js +45 -1
  208. package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
  209. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts +1 -1
  210. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
  211. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +5 -4
  212. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
  213. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
  214. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +13 -7
  215. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
  216. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts +7 -6
  217. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  218. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js +29 -21
  219. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  220. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  221. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +13 -1
  222. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  223. package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +3 -2
  224. package/packages/pi-coding-agent/src/core/agent-session.ts +11 -0
  225. package/packages/pi-coding-agent/src/core/extensions/runner.ts +2 -0
  226. package/packages/pi-coding-agent/src/core/extensions/types.ts +7 -0
  227. package/packages/pi-coding-agent/src/core/model-discovery.test.ts +19 -0
  228. package/packages/pi-coding-agent/src/core/model-discovery.ts +99 -12
  229. package/packages/pi-coding-agent/src/core/model-registry-custom-caps.test.ts +245 -0
  230. package/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts +75 -0
  231. package/packages/pi-coding-agent/src/core/model-registry.ts +102 -10
  232. package/packages/pi-coding-agent/src/core/redact-secrets.test.ts +86 -0
  233. package/packages/pi-coding-agent/src/core/redact-secrets.ts +58 -0
  234. package/packages/pi-coding-agent/src/core/session-manager.test.ts +65 -1
  235. package/packages/pi-coding-agent/src/core/session-manager.ts +10 -6
  236. package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +6 -6
  237. package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +16 -7
  238. package/packages/pi-coding-agent/src/modes/interactive/components/skill-invocation-message.ts +36 -22
  239. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +13 -1
  240. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  241. package/scripts/link-workspace-packages.cjs +1 -0
  242. package/src/resources/extensions/claude-code-cli/readiness.ts +4 -3
  243. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +78 -17
  244. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +149 -5
  245. package/src/resources/extensions/gsd/auto/loop-deps.ts +14 -0
  246. package/src/resources/extensions/gsd/auto/loop.ts +9 -0
  247. package/src/resources/extensions/gsd/auto/phases.ts +82 -4
  248. package/src/resources/extensions/gsd/auto/run-unit.ts +40 -2
  249. package/src/resources/extensions/gsd/auto/session.ts +35 -2
  250. package/src/resources/extensions/gsd/auto-dispatch.ts +16 -3
  251. package/src/resources/extensions/gsd/auto-model-selection.ts +17 -2
  252. package/src/resources/extensions/gsd/auto-post-unit.ts +29 -3
  253. package/src/resources/extensions/gsd/auto-prompts.ts +28 -1
  254. package/src/resources/extensions/gsd/auto-recovery.ts +26 -1
  255. package/src/resources/extensions/gsd/auto-start.ts +60 -68
  256. package/src/resources/extensions/gsd/auto-worktree.ts +62 -63
  257. package/src/resources/extensions/gsd/auto.ts +73 -28
  258. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +23 -1
  259. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +40 -9
  260. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +109 -0
  261. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
  262. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +54 -6
  263. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +35 -2
  264. package/src/resources/extensions/gsd/clean-root-preflight.ts +111 -0
  265. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +898 -32
  266. package/src/resources/extensions/gsd/compaction-snapshot.ts +165 -0
  267. package/src/resources/extensions/gsd/complexity-classifier.ts +5 -3
  268. package/src/resources/extensions/gsd/error-classifier.ts +10 -3
  269. package/src/resources/extensions/gsd/exec-history.ts +153 -0
  270. package/src/resources/extensions/gsd/exec-sandbox.ts +326 -0
  271. package/src/resources/extensions/gsd/gitignore.ts +1 -1
  272. package/src/resources/extensions/gsd/gsd-db.ts +157 -33
  273. package/src/resources/extensions/gsd/guided-flow.ts +222 -1
  274. package/src/resources/extensions/gsd/health-widget.ts +3 -1
  275. package/src/resources/extensions/gsd/init-wizard.ts +15 -1
  276. package/src/resources/extensions/gsd/journal.ts +2 -1
  277. package/src/resources/extensions/gsd/key-manager.ts +28 -0
  278. package/src/resources/extensions/gsd/model-router.ts +42 -1
  279. package/src/resources/extensions/gsd/pre-execution-checks.ts +46 -10
  280. package/src/resources/extensions/gsd/preferences-types.ts +46 -0
  281. package/src/resources/extensions/gsd/preferences-validation.ts +79 -0
  282. package/src/resources/extensions/gsd/preferences.ts +17 -17
  283. package/src/resources/extensions/gsd/prompt-loader.ts +30 -7
  284. package/src/resources/extensions/gsd/prompts/discuss-headless.md +8 -0
  285. package/src/resources/extensions/gsd/prompts/discuss.md +29 -2
  286. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
  287. package/src/resources/extensions/gsd/safety/evidence-collector.ts +119 -0
  288. package/src/resources/extensions/gsd/safety/file-change-validator.ts +17 -4
  289. package/src/resources/extensions/gsd/safety/safety-harness.ts +9 -0
  290. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +188 -2
  291. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +12 -0
  292. package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +12 -0
  293. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +49 -0
  294. package/src/resources/extensions/gsd/tests/auto-start-bootstrap-await-3420.test.ts +141 -0
  295. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +33 -3
  296. package/src/resources/extensions/gsd/tests/auto-thinking-restore.test.ts +38 -0
  297. package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +23 -0
  298. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +186 -0
  299. package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +123 -0
  300. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +61 -1
  301. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
  302. package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
  303. package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +3 -3
  304. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +2 -0
  305. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +31 -0
  306. package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +1 -1
  307. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +1 -1
  308. package/src/resources/extensions/gsd/tests/escalation.test.ts +1 -1
  309. package/src/resources/extensions/gsd/tests/exec-history.test.ts +237 -0
  310. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +210 -0
  311. package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +58 -0
  312. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +447 -1
  313. package/src/resources/extensions/gsd/tests/init-wizard.test.ts +27 -0
  314. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +1 -0
  315. package/src/resources/extensions/gsd/tests/integration/gitignore-tracked-gsd.test.ts +1 -0
  316. package/src/resources/extensions/gsd/tests/integration/idle-recovery.test.ts +30 -0
  317. package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +1 -1
  318. package/src/resources/extensions/gsd/tests/issue-4540-regressions.test.ts +288 -0
  319. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +2 -0
  320. package/src/resources/extensions/gsd/tests/key-manager.test.ts +9 -0
  321. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
  322. package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +12 -0
  323. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  324. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +19 -0
  325. package/src/resources/extensions/gsd/tests/plan-gate-failed-doctor-heal-hint.test.ts +37 -0
  326. package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +14 -0
  327. package/src/resources/extensions/gsd/tests/pre-exec-gate-loop.test.ts +272 -0
  328. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +356 -0
  329. package/src/resources/extensions/gsd/tests/preferences.test.ts +110 -0
  330. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +44 -0
  331. package/src/resources/extensions/gsd/tests/prompt-loader-extension-dir.test.ts +49 -0
  332. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +48 -0
  333. package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +388 -0
  334. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +9 -3
  335. package/src/resources/extensions/gsd/tests/resume-dispatch-worktree.test.ts +230 -0
  336. package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +205 -0
  337. package/src/resources/extensions/gsd/tests/save-gate-result-render.test.ts +95 -0
  338. package/src/resources/extensions/gsd/tests/schema-v21-sequence.test.ts +413 -0
  339. package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +32 -40
  340. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +56 -0
  341. package/src/resources/extensions/gsd/tests/token-counter.test.ts +105 -1
  342. package/src/resources/extensions/gsd/tests/tool-compatibility.test.ts +107 -0
  343. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +23 -0
  344. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +65 -2
  345. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +35 -0
  346. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +6 -1
  347. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +78 -5
  348. package/src/resources/extensions/gsd/tests/write-gate.test.ts +64 -0
  349. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +3 -1
  350. package/src/resources/extensions/gsd/token-counter.ts +22 -5
  351. package/src/resources/extensions/gsd/tools/complete-milestone.ts +15 -9
  352. package/src/resources/extensions/gsd/tools/exec-search-tool.ts +81 -0
  353. package/src/resources/extensions/gsd/tools/exec-tool.ts +183 -0
  354. package/src/resources/extensions/gsd/tools/resume-tool.ts +40 -0
  355. package/src/resources/extensions/gsd/uok/plan-v2.ts +26 -3
  356. package/src/resources/extensions/gsd/workflow-logger.ts +3 -1
  357. package/src/resources/extensions/gsd/workflow-mcp.ts +3 -0
  358. package/src/resources/extensions/gsd/worktree-resolver.ts +54 -9
  359. package/src/resources/skills/verify-before-complete/SKILL.md +2 -1
  360. package/src/resources/skills/write-docs/SKILL.md +2 -1
  361. /package/dist/web/standalone/.next/static/{YnUwu2WWaT0_hyTLUF4nq → JgU2F-5N9mTyB7kUSSk9A}/_buildManifest.js +0 -0
  362. /package/dist/web/standalone/.next/static/{YnUwu2WWaT0_hyTLUF4nq → JgU2F-5N9mTyB7kUSSk9A}/_ssgManifest.js +0 -0
@@ -12,12 +12,41 @@ import { execFileSync } from 'node:child_process';
12
12
  * `src/resources/extensions/gsd/pre-execution-checks.ts`.
13
13
  */
14
14
  export const CLAUDE_COMMAND = process.platform === 'win32' ? 'claude.cmd' : 'claude';
15
+ /**
16
+ * Ordered list of binary names to probe for the Claude Code CLI.
17
+ *
18
+ * Windows installs vary: npm-global installs produce a `claude.cmd` shim,
19
+ * direct binary installs produce `claude.exe`, and Git Bash wrappers may
20
+ * expose a bare `claude` shim. Try all three so no valid install is missed.
21
+ */
22
+ const CLAUDE_COMMAND_CANDIDATES = process.platform === 'win32' ? [CLAUDE_COMMAND, 'claude.exe', 'claude'] : [CLAUDE_COMMAND];
23
+ /**
24
+ * Try to run `args` against each candidate binary.
25
+ * Returns the output buffer on first success, throws the last error if all fail.
26
+ */
27
+ function execClaudeCheck(args) {
28
+ let lastError;
29
+ for (const command of CLAUDE_COMMAND_CANDIDATES) {
30
+ try {
31
+ return execFileSync(command, args, { timeout: 5_000, stdio: 'pipe' });
32
+ }
33
+ catch (error) {
34
+ lastError = error;
35
+ const code = error?.code;
36
+ // EINVAL can surface on Windows Git Bash for .cmd spawn failures.
37
+ if (code === 'ENOENT' || code === 'EINVAL')
38
+ continue;
39
+ throw error;
40
+ }
41
+ }
42
+ throw lastError ?? new Error(`Claude CLI not found (tried: ${CLAUDE_COMMAND_CANDIDATES.join(', ')})`);
43
+ }
15
44
  /**
16
45
  * Check if the `claude` binary is installed (regardless of auth state).
17
46
  */
18
47
  export function isClaudeBinaryInstalled() {
19
48
  try {
20
- execFileSync(CLAUDE_COMMAND, ['--version'], { timeout: 5_000, stdio: 'pipe' });
49
+ execClaudeCheck(['--version']);
21
50
  return true;
22
51
  }
23
52
  catch {
@@ -29,13 +58,13 @@ export function isClaudeBinaryInstalled() {
29
58
  */
30
59
  export function isClaudeCliReady() {
31
60
  try {
32
- execFileSync(CLAUDE_COMMAND, ['--version'], { timeout: 5_000, stdio: 'pipe' });
61
+ execClaudeCheck(['--version']);
33
62
  }
34
63
  catch {
35
64
  return false;
36
65
  }
37
66
  try {
38
- const output = execFileSync(CLAUDE_COMMAND, ['auth', 'status'], { timeout: 5_000, stdio: 'pipe' })
67
+ const output = execClaudeCheck(['auth', 'status'])
39
68
  .toString()
40
69
  .toLowerCase();
41
70
  return !(/not logged in|no credentials|unauthenticated|not authenticated/i.test(output));
@@ -1,6 +1,11 @@
1
1
  /**
2
2
  * Minimal tool interface matching GSD's AgentTool shape.
3
3
  * Avoids a direct dependency on @gsd/pi-agent-core from this compiled module.
4
+ *
5
+ * `details` and `isError` are optional fields that runtime tool implementations
6
+ * may populate. The MCP transport drops non-standard fields, so the wrapper at
7
+ * the call site mirrors `details` into `structuredContent` and forwards
8
+ * `isError` directly. See #4472.
4
9
  */
5
10
  export interface McpToolDef {
6
11
  name: string;
@@ -13,6 +18,8 @@ export interface McpToolDef {
13
18
  data?: string;
14
19
  mimeType?: string;
15
20
  }>;
21
+ details?: Record<string, unknown>;
22
+ isError?: boolean;
16
23
  }>;
17
24
  }
18
25
  /**
@@ -1,3 +1,21 @@
1
+ /**
2
+ * Strict plain-object guard. True only for object literals and
3
+ * `Object.create(null)` — not for `Date`, `URL`, `Map`, `Set`, class instances,
4
+ * or arrays. Used to gate `structuredContent` forwarding so the MCP transport
5
+ * receives only true JSON objects (the protocol contract). See #4477 review.
6
+ *
7
+ * Mirrored in `packages/mcp-server/src/workflow-tools.ts` for the
8
+ * `adaptExecutorResult` adapter on the workflow path. Keep both copies in
9
+ * sync if the contract definition needs to evolve.
10
+ */
11
+ function isPlainObject(value) {
12
+ if (value === null || typeof value !== 'object')
13
+ return false;
14
+ if (Array.isArray(value))
15
+ return false;
16
+ const proto = Object.getPrototypeOf(value);
17
+ return proto === null || proto === Object.prototype;
18
+ }
1
19
  // MCP SDK subpath imports use wildcard exports (./*) in @modelcontextprotocol/sdk's
2
20
  // package.json export map. The wildcard maps "./foo" → "./dist/cjs/foo" (no .js
3
21
  // suffix), so bare subpath specifiers like `${MCP_PKG}/server/stdio` resolve to
@@ -83,7 +101,23 @@ export async function startMcpServer(options) {
83
101
  // by stringifying into a text block so clients see the payload.
84
102
  return { type: 'text', text: JSON.stringify(block) };
85
103
  });
86
- return { content };
104
+ // Forward a tool's runtime `details` field to MCP's `structuredContent`
105
+ // channel. The protocol drops non-standard fields on the wire, so tools
106
+ // that populate `details` for client-side renderers (e.g. save_gate_result)
107
+ // would otherwise arrive empty on the other side. See #4472.
108
+ //
109
+ // Use a strict plain-object guard (prototype-chain check) rather than just
110
+ // `typeof === 'object' && !Array.isArray()` — Date, URL, Map, Set, and
111
+ // class instances would otherwise pass through and end up as
112
+ // `structuredContent`, violating the protocol's JSON-object contract.
113
+ // The mirror discipline applies in `workflow-tools.ts adaptExecutorResult`.
114
+ const base = { content };
115
+ if (isPlainObject(result.details)) {
116
+ base.structuredContent = result.details;
117
+ }
118
+ if (result.isError === true)
119
+ base.isError = true;
120
+ return base;
87
121
  }
88
122
  catch (err) {
89
123
  // AbortError from a cancelled tool surfaces as a normal error — MCP
@@ -60,6 +60,8 @@ const OTHER_PROVIDERS = [
60
60
  { value: 'xai', label: 'xAI (Grok)', hint: 'console.x.ai' },
61
61
  { value: 'openrouter', label: 'OpenRouter', hint: '200+ models — openrouter.ai/keys' },
62
62
  { value: 'mistral', label: 'Mistral', hint: 'console.mistral.ai/api-keys' },
63
+ { value: 'minimax', label: 'MiniMax', hint: 'platform.minimax.io (Anthropic-compatible recommended)' },
64
+ { value: 'minimax-cn', label: 'MiniMax CN', hint: 'api.minimaxi.com (Anthropic-compatible)' },
63
65
  { value: 'ollama-cloud', label: 'Ollama Cloud' },
64
66
  { value: 'custom-openai', label: 'Custom (OpenAI-compatible)', hint: 'Ollama, LM Studio, vLLM, proxies — see docs/providers.md' },
65
67
  ];
@@ -131,6 +133,36 @@ function persistDefaultProvider(providerId) {
131
133
  // Non-fatal: startup fallback logic will still run.
132
134
  }
133
135
  }
136
+ /**
137
+ * Persist the selected default model to settings.json.
138
+ */
139
+ function persistDefaultModel(modelId) {
140
+ const settingsPath = join(agentDir, 'settings.json');
141
+ try {
142
+ const raw = existsSync(settingsPath) ? JSON.parse(readFileSync(settingsPath, 'utf-8')) : {};
143
+ raw.defaultModel = modelId;
144
+ mkdirSync(dirname(settingsPath), { recursive: true });
145
+ writeFileSync(settingsPath, JSON.stringify(raw, null, 2), 'utf-8');
146
+ }
147
+ catch {
148
+ // Non-fatal: startup fallback logic will still run.
149
+ }
150
+ }
151
+ function detectNativeProviderFromBaseUrl(baseUrl) {
152
+ try {
153
+ const hostname = new URL(baseUrl).hostname.toLowerCase();
154
+ if (hostname === 'api.minimax.io' || hostname.endsWith('.minimax.io')) {
155
+ return 'minimax';
156
+ }
157
+ if (hostname === 'api.minimaxi.com' || hostname.endsWith('.minimaxi.com')) {
158
+ return 'minimax-cn';
159
+ }
160
+ }
161
+ catch {
162
+ // ignore parse failures; handled by prior validation
163
+ }
164
+ return null;
165
+ }
134
166
  /** Sentinel returned by runStep when the user cancels — tells the caller
135
167
  * to abort the entire wizard. */
136
168
  const STEP_CANCELLED = Symbol('step-cancelled');
@@ -558,9 +590,22 @@ async function runCustomOpenAIFlow(p, pc, authStorage) {
558
590
  if (p.isCancel(modelId) || !modelId)
559
591
  return false;
560
592
  const trimmedModelId = modelId.trim();
593
+ const nativeProvider = detectNativeProviderFromBaseUrl(trimmedUrl);
594
+ if (nativeProvider) {
595
+ const envVar = nativeProvider === 'minimax' ? 'MINIMAX_API_KEY' : 'MINIMAX_CN_API_KEY';
596
+ authStorage.set(nativeProvider, { type: 'api_key', key: trimmedKey });
597
+ persistDefaultProvider(nativeProvider);
598
+ persistDefaultModel(trimmedModelId);
599
+ process.env[envVar] = trimmedKey;
600
+ p.log.success(`${pc.green('MiniMax')} detected — configured as native provider (${pc.cyan(nativeProvider)})`);
601
+ p.log.info(`Model: ${pc.cyan(trimmedModelId)}`);
602
+ p.log.info(pc.dim('Using Anthropic-compatible MiniMax integration for full model metadata and clean thinking output.'));
603
+ return true;
604
+ }
561
605
  // Save API key to auth storage
562
606
  authStorage.set('custom-openai', { type: 'api_key', key: trimmedKey });
563
607
  persistDefaultProvider('custom-openai');
608
+ persistDefaultModel(trimmedModelId);
564
609
  // Write or merge into models.json
565
610
  const modelsJsonPath = join(agentDir, 'models.json');
566
611
  let config = { providers: {} };
@@ -21,6 +21,6 @@ export declare function getNewerManagedResourceVersion(agentDir: string, current
21
21
  *
22
22
  * Inspectable: `ls ~/.gsd/agent/extensions/`
23
23
  */
24
- export declare function initResources(agentDir: string): void;
24
+ export declare function initResources(agentDir: string, skillsDir?: string): void;
25
25
  export declare function hasStaleCompiledExtensionSiblings(extensionsDir: string, sourceDir?: string): boolean;
26
26
  export declare function buildResourceLoader(agentDir: string): DefaultResourceLoader;
@@ -506,7 +506,7 @@ function pruneRemovedBundledExtensions(manifest, agentDir) {
506
506
  *
507
507
  * Inspectable: `ls ~/.gsd/agent/extensions/`
508
508
  */
509
- export function initResources(agentDir) {
509
+ export function initResources(agentDir, skillsDir = join(homedir(), '.agents', 'skills')) {
510
510
  mkdirSync(agentDir, { recursive: true });
511
511
  const currentVersion = getBundledGsdVersion();
512
512
  const manifest = readManagedResourceManifest(agentDir);
@@ -538,13 +538,7 @@ export function initResources(agentDir) {
538
538
  // Sync bundled resources — overwrite so updates land on next launch.
539
539
  syncResourceDir(bundledExtensionsDir, join(agentDir, 'extensions'));
540
540
  syncResourceDir(join(resourcesDir, 'agents'), join(agentDir, 'agents'));
541
- // Skills are no longer force-synced here. Users install skills via the
542
- // skills.sh CLI (`npx skills add <repo>`) into ~/.agents/skills/ which
543
- // is the industry-standard Agent Skills ecosystem directory.
544
- //
545
- // Migration from the legacy ~/.gsd/agent/skills/ directory is handled
546
- // above the manifest check so it runs on every launch (including retries
547
- // after partial copy failures).
541
+ syncResourceDir(join(resourcesDir, 'skills'), skillsDir);
548
542
  // Sync GSD-WORKFLOW.md to agentDir as a fallback for when GSD_WORKFLOW_PATH
549
543
  // env var is not set (e.g. fork/dev builds, alternative entry points).
550
544
  const workflowSrc = join(resourcesDir, 'GSD-WORKFLOW.md');
@@ -18,10 +18,11 @@ import { execFileSync } from "node:child_process";
18
18
  const CLAUDE_COMMAND = process.platform === "win32" ? "claude.cmd" : "claude";
19
19
  /**
20
20
  * Windows installs vary: some environments expose `claude.cmd` (npm shim),
21
- * others expose a `claude` shim on PATH (for example Git Bash wrappers).
22
- * Try both to avoid false "not installed" results in readiness checks.
21
+ * `claude.exe` (direct binary install), or a bare `claude` shim on PATH
22
+ * (for example Git Bash wrappers). Try all three to avoid false "not
23
+ * installed" results in readiness checks.
23
24
  */
24
- const CLAUDE_COMMAND_CANDIDATES = process.platform === "win32" ? [CLAUDE_COMMAND, "claude"] : [CLAUDE_COMMAND];
25
+ const CLAUDE_COMMAND_CANDIDATES = process.platform === "win32" ? [CLAUDE_COMMAND, "claude.exe", "claude"] : [CLAUDE_COMMAND];
25
26
  function execClaude(args) {
26
27
  let lastError;
27
28
  for (const command of CLAUDE_COMMAND_CANDIDATES) {
@@ -484,25 +484,23 @@ export function makeAbortedMessage(model, lastTextContent) {
484
484
  /**
485
485
  * Resolve the Claude Code permission mode for the current run.
486
486
  *
487
- * GSD subagents run underneath a host Claude Code session the user has
488
- * already consented to, and their work (edits, shell inspection, MCP calls)
489
- * spans the full workflow toolset. Defaulting the inner SDK to
490
- * `bypassPermissions` avoids per-tool approval prompts that offer no
491
- * meaningful safety beyond what the host session and the subagent prompts
492
- * already enforce. `GSD_CLAUDE_CODE_PERMISSION_MODE` lets security-conscious
493
- * users opt into a stricter mode (`acceptEdits`, `default`, `plan`).
487
+ * Defaults to `acceptEdits`, which auto-approves file reads/edits but
488
+ * surfaces a permission dialog for dangerous operations (e.g. general Bash,
489
+ * Agent, WebFetch). This prevents tools outside the allowlist from being
490
+ * silently denied the SDK emits an `extension_ui_request` event so the
491
+ * user sees a prompt instead of a silent refusal that Claude Code mistakes
492
+ * for user rejection (#4383).
494
493
  *
495
- * Tradeoff: bypass means a prompt-injection payload read from an untrusted
496
- * file could trigger tool calls without a second gate. Accepted for GSD
497
- * because the workflow is explicit user intent and the alternative
498
- * (#4099) is continuous approval fatigue that blocks real work.
494
+ * Set `GSD_CLAUDE_CODE_PERMISSION_MODE` to `bypassPermissions` to restore
495
+ * the old always-approve behaviour, or to `default` / `plan` for stricter
496
+ * modes.
499
497
  */
500
498
  export async function resolveClaudePermissionMode(env = process.env) {
501
499
  const override = env.GSD_CLAUDE_CODE_PERMISSION_MODE?.trim();
502
500
  if (override === "bypassPermissions" || override === "acceptEdits" || override === "default" || override === "plan") {
503
501
  return override;
504
502
  }
505
- return "bypassPermissions";
503
+ return "acceptEdits";
506
504
  }
507
505
  // NOTE: These helpers intentionally mirror @gsd/pi-ai anthropic-shared
508
506
  // behavior so this extension remains typecheck-stable even when the published
@@ -554,7 +552,7 @@ function mapThinkingLevelToAnthropicEffort(level, modelId) {
554
552
  export function buildSdkOptions(modelId, prompt, overrides, extraOptions = {}) {
555
553
  const { reasoning, ...sdkExtraOptions } = extraOptions;
556
554
  const mcpServers = buildWorkflowMcpServers();
557
- const permissionMode = overrides?.permissionMode ?? "bypassPermissions";
555
+ const permissionMode = overrides?.permissionMode ?? "acceptEdits";
558
556
  const disallowedTools = ["AskUserQuestion"];
559
557
  // Pre-authorize the safe built-ins and every registered workflow MCP
560
558
  // server's tools. `acceptEdits` mode (the interactive default) only
@@ -637,6 +635,68 @@ function normalizeToolResultContent(content) {
637
635
  }
638
636
  return blocks.length > 0 ? blocks : [{ type: "text", text: "" }];
639
637
  }
638
+ /**
639
+ * Extract a `details` payload from an MCP tool-result block.
640
+ *
641
+ * MCP's `CallToolResult` carries structured data in `structuredContent` — the
642
+ * protocol's supported channel for non-text payloads. Claude Code's synthetic
643
+ * user message may surface that field in one of two shapes depending on SDK
644
+ * version: as a sibling on the `mcp_tool_result` block itself, or as a
645
+ * dedicated content sub-block with `type: "structuredContent"`. Snake-case
646
+ * (`structured_content`) is accepted defensively in case a transport hop
647
+ * rewrites casing. All other shapes fall back to an empty object so callers
648
+ * can rely on `details` being present.
649
+ */
650
+ function extractStructuredDetailsFromBlock(block) {
651
+ const sibling = block.structuredContent ?? block.structured_content;
652
+ if (sibling && typeof sibling === "object" && !Array.isArray(sibling)) {
653
+ return sibling;
654
+ }
655
+ if (Array.isArray(block.content)) {
656
+ for (const item of block.content) {
657
+ if (!item || typeof item !== "object")
658
+ continue;
659
+ const sub = item;
660
+ if (sub.type !== "structuredContent" && sub.type !== "structured_content")
661
+ continue;
662
+ const payload = sub.structuredContent ?? sub.structured_content ?? sub.data ?? sub.value;
663
+ if (payload && typeof payload === "object" && !Array.isArray(payload)) {
664
+ return payload;
665
+ }
666
+ }
667
+ }
668
+ // Return undefined (not {}) when no structured payload is present, matching
669
+ // the pre-#4477 contract where `details` was nullable. An empty-object
670
+ // sentinel is truthy and breaks downstream consumers that gate on
671
+ // `if (details)`. `undefined` matches the type of the field these results
672
+ // flow into (`Record<string, unknown> | undefined`).
673
+ return undefined;
674
+ }
675
+ /**
676
+ * True for items that are MCP `structuredContent` pseudo-blocks living inside
677
+ * a tool-result `content[]` array. These blocks carry the structured payload
678
+ * (extracted separately by `extractStructuredDetailsFromBlock`) and must NOT
679
+ * leak into the visible content rendered to the user — otherwise the renderer
680
+ * stringifies the JSON pseudo-block and shows it next to the actual tool
681
+ * output. See PR #4477 review (CodeRabbit, post-fix-round).
682
+ */
683
+ function isStructuredContentPseudoBlock(item) {
684
+ if (!item || typeof item !== "object")
685
+ return false;
686
+ const type = item.type;
687
+ return type === "structuredContent" || type === "structured_content";
688
+ }
689
+ /**
690
+ * Strip `structuredContent` pseudo-blocks from a tool-result content array
691
+ * before normalization. The structured payload is extracted via the sibling
692
+ * `structuredContent` field (or a dedicated extractor pass on the raw block);
693
+ * the visible content path must not include the pseudo-block itself.
694
+ */
695
+ function stripStructuredContentPseudoBlocks(content) {
696
+ if (!Array.isArray(content))
697
+ return content;
698
+ return content.filter((item) => !isStructuredContentPseudoBlock(item));
699
+ }
640
700
  /** Extract tool result payloads from an SDK synthetic user message, keyed by tool-use ID. */
641
701
  export function extractToolResultsFromSdkUserMessage(message) {
642
702
  const extracted = [];
@@ -657,8 +717,8 @@ export function extractToolResultsFromSdkUserMessage(message) {
657
717
  extracted.push({
658
718
  toolUseId,
659
719
  result: {
660
- content: normalizeToolResultContent(block.content),
661
- details: {},
720
+ content: normalizeToolResultContent(stripStructuredContentPseudoBlocks(block.content)),
721
+ details: extractStructuredDetailsFromBlock(block),
662
722
  isError: block.is_error === true,
663
723
  },
664
724
  });
@@ -672,8 +732,8 @@ export function extractToolResultsFromSdkUserMessage(message) {
672
732
  extracted.push({
673
733
  toolUseId,
674
734
  result: {
675
- content: normalizeToolResultContent(toolResult.content),
676
- details: {},
735
+ content: normalizeToolResultContent(stripStructuredContentPseudoBlocks(toolResult.content)),
736
+ details: extractStructuredDetailsFromBlock(toolResult),
677
737
  isError: toolResult.is_error === true,
678
738
  },
679
739
  });
@@ -30,6 +30,13 @@ function stuckStatePath(basePath) {
30
30
  function loadStuckState(basePath) {
31
31
  try {
32
32
  const data = JSON.parse(readFileSync(stuckStatePath(basePath), "utf-8"));
33
+ // Only load state written by a DIFFERENT process (real session restart).
34
+ // If the PID matches the current process, this state was written by an earlier
35
+ // autoLoop call in the same process (e.g., a test that completed before this
36
+ // one), not by a crashed session — skip it to prevent test state pollution.
37
+ if (data.pid === process.pid) {
38
+ return { recentUnits: [], stuckRecoveryAttempts: 0 };
39
+ }
33
40
  return {
34
41
  recentUnits: Array.isArray(data.recentUnits) ? data.recentUnits : [],
35
42
  stuckRecoveryAttempts: typeof data.stuckRecoveryAttempts === "number" ? data.stuckRecoveryAttempts : 0,
@@ -45,6 +52,7 @@ function saveStuckState(basePath, state) {
45
52
  const filePath = stuckStatePath(basePath);
46
53
  mkdirSync(join(gsdRoot(basePath), "runtime"), { recursive: true });
47
54
  writeFileSync(filePath, JSON.stringify({
55
+ pid: process.pid,
48
56
  recentUnits: state.recentUnits.slice(-20), // keep last 20 entries
49
57
  stuckRecoveryAttempts: state.stuckRecoveryAttempts,
50
58
  updatedAt: new Date().toISOString(),
@@ -497,6 +505,7 @@ export async function autoLoop(ctx, pi, s, deps, options) {
497
505
  consecutiveCooldowns = 0;
498
506
  recentErrorMessages.length = 0;
499
507
  deps.emitJournalEvent({ ts: new Date().toISOString(), flowId, seq: nextSeq(), eventType: "iteration-end", data: { iteration } });
508
+ saveStuckState(s.basePath, loopState); // persist across session restarts (#4382)
500
509
  debugLog("autoLoop", { phase: "iteration-complete", iteration });
501
510
  finishTurn("completed");
502
511
  }
@@ -30,7 +30,8 @@ import { isDbAvailable, getMilestoneSlices } from "../gsd-db.js";
30
30
  import { ensurePlanV2Graph } from "../uok/plan-v2.js";
31
31
  import { resolveUokFlags } from "../uok/flags.js";
32
32
  import { UokGateRunner } from "../uok/gate-runner.js";
33
- import { resetEvidence } from "../safety/evidence-collector.js";
33
+ import { resetEvidence, loadEvidenceFromDisk } from "../safety/evidence-collector.js";
34
+ import { parseUnitId } from "../unit-id.js";
34
35
  import { createCheckpoint, cleanupCheckpoint, rollbackToCheckpoint } from "../safety/git-checkpoint.js";
35
36
  import { resolveSafetyHarnessConfig } from "../safety/safety-harness.js";
36
37
  import { getWorkflowTransportSupportError, getRequiredWorkflowToolsForAutoUnit, supportsStructuredQuestions, } from "../workflow-mcp.js";
@@ -47,7 +48,11 @@ export function resetSessionTimeoutState() {
47
48
  * Exported for testing as _resolveReportBasePath.
48
49
  */
49
50
  export function _resolveReportBasePath(s) {
50
- return s.originalBasePath || s.basePath;
51
+ // Strip /.gsd/worktrees/ suffix when basePath is itself a worktree path and
52
+ // originalBasePath is falsy — prevents reports landing in the worktree (#3729).
53
+ const resolved = s.originalBasePath || s.basePath;
54
+ const markerIdx = resolved.indexOf("/.gsd/worktrees/");
55
+ return markerIdx !== -1 ? resolved.slice(0, markerIdx) : resolved;
51
56
  }
52
57
  /**
53
58
  * Resolve the authoritative project base for dispatch guards.
@@ -55,7 +60,12 @@ export function _resolveReportBasePath(s) {
55
60
  * unit is running inside an auto worktree.
56
61
  */
57
62
  export function _resolveDispatchGuardBasePath(s) {
58
- return s.originalBasePath || s.basePath;
63
+ // Strip /.gsd/worktrees/ suffix when basePath is itself a worktree path and
64
+ // originalBasePath is falsy — prevents guard checks running against the
65
+ // worktree instead of the project root (#3729).
66
+ const resolved = s.originalBasePath || s.basePath;
67
+ const markerIdx = resolved.indexOf("/.gsd/worktrees/");
68
+ return markerIdx !== -1 ? resolved.slice(0, markerIdx) : resolved;
59
69
  }
60
70
  const PLAN_V2_GATE_PHASES = new Set([
61
71
  "executing",
@@ -295,7 +305,7 @@ export async function runPreDispatch(ic, loopState) {
295
305
  findings: reason,
296
306
  milestoneId: state.activeMilestone?.id ?? undefined,
297
307
  });
298
- ctx.ui.notify(`Plan gate failed-closed: ${reason}`, "error");
308
+ ctx.ui.notify(`Plan gate failed-closed: ${reason}\n\nIf this keeps happening, try: /gsd doctor heal`, "error");
299
309
  await deps.pauseAuto(ctx, pi);
300
310
  return { action: "break", reason: "plan-v2-gate-failed" };
301
311
  }
@@ -389,6 +399,8 @@ export async function runPreDispatch(ic, loopState) {
389
399
  loopState.recentUnits.length = 0;
390
400
  loopState.stuckRecoveryAttempts = 0;
391
401
  // Worktree lifecycle on milestone transition — merge current, enter next
402
+ // #2909: preflight — warn + stash dirty working tree before merge
403
+ const preflightTransition = deps.preflightCleanRoot(s.originalBasePath || s.basePath, s.currentMilestoneId, ctx.ui.notify.bind(ctx.ui));
392
404
  try {
393
405
  deps.resolver.mergeAndExit(s.currentMilestoneId, ctx.ui);
394
406
  }
@@ -405,6 +417,10 @@ export async function runPreDispatch(ic, loopState) {
405
417
  await deps.stopAuto(ctx, pi, `Merge error on milestone ${s.currentMilestoneId}: ${String(mergeErr)}`);
406
418
  return { action: "break", reason: "merge-failed" };
407
419
  }
420
+ // #2909: postflight — restore stashed changes after successful merge
421
+ if (preflightTransition.stashPushed) {
422
+ deps.postflightPopStash(s.originalBasePath || s.basePath, s.currentMilestoneId, ctx.ui.notify.bind(ctx.ui));
423
+ }
408
424
  // PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
409
425
  deps.invalidateAllCaches();
410
426
  state = await deps.deriveState(s.basePath);
@@ -459,6 +475,8 @@ export async function runPreDispatch(ic, loopState) {
459
475
  if (incomplete.length === 0 && state.registry.length > 0) {
460
476
  // All milestones complete — merge milestone branch before stopping
461
477
  if (s.currentMilestoneId) {
478
+ // #2909: preflight — warn + stash dirty working tree before merge
479
+ const preflightAllComplete = deps.preflightCleanRoot(s.originalBasePath || s.basePath, s.currentMilestoneId, ctx.ui.notify.bind(ctx.ui));
462
480
  try {
463
481
  deps.resolver.mergeAndExit(s.currentMilestoneId, ctx.ui);
464
482
  // Prevent stopAuto from attempting the same merge (#2645)
@@ -475,6 +493,10 @@ export async function runPreDispatch(ic, loopState) {
475
493
  await deps.stopAuto(ctx, pi, `Merge error on milestone ${s.currentMilestoneId}: ${String(mergeErr)}`);
476
494
  return { action: "break", reason: "merge-failed" };
477
495
  }
496
+ // #2909: postflight — restore stashed changes after successful merge
497
+ if (preflightAllComplete.stashPushed) {
498
+ deps.postflightPopStash(s.originalBasePath || s.basePath, s.currentMilestoneId, ctx.ui.notify.bind(ctx.ui));
499
+ }
478
500
  // PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
479
501
  }
480
502
  deps.sendDesktopNotification("GSD", "All milestones complete!", "success", "milestone", basename(s.originalBasePath || s.basePath));
@@ -539,6 +561,8 @@ export async function runPreDispatch(ic, loopState) {
539
561
  if (state.phase === "complete") {
540
562
  // Milestone merge on complete (before closeout so branch state is clean)
541
563
  if (s.currentMilestoneId) {
564
+ // #2909: preflight — warn + stash dirty working tree before merge
565
+ const preflightComplete = deps.preflightCleanRoot(s.originalBasePath || s.basePath, s.currentMilestoneId, ctx.ui.notify.bind(ctx.ui));
542
566
  try {
543
567
  deps.resolver.mergeAndExit(s.currentMilestoneId, ctx.ui);
544
568
  // Prevent stopAuto from attempting the same merge (#2645)
@@ -555,6 +579,10 @@ export async function runPreDispatch(ic, loopState) {
555
579
  await deps.stopAuto(ctx, pi, `Merge error on milestone ${s.currentMilestoneId}: ${String(mergeErr)}`);
556
580
  return { action: "break", reason: "merge-failed" };
557
581
  }
582
+ // #2909: postflight — restore stashed changes after successful merge
583
+ if (preflightComplete.stashPushed) {
584
+ deps.postflightPopStash(s.originalBasePath || s.basePath, s.currentMilestoneId, ctx.ui.notify.bind(ctx.ui));
585
+ }
558
586
  // PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
559
587
  }
560
588
  deps.sendDesktopNotification("GSD", `Milestone ${mid} complete!`, "success", "milestone", basename(s.originalBasePath || s.basePath));
@@ -1028,6 +1056,14 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
1028
1056
  const safetyConfig = resolveSafetyHarnessConfig(prefs?.safety_harness);
1029
1057
  if (safetyConfig.enabled && safetyConfig.evidence_collection) {
1030
1058
  resetEvidence();
1059
+ // Restore persisted evidence so session-restart resumes don't produce
1060
+ // false-positive "no bash calls" warnings (Bug #4385).
1061
+ if (s.basePath && unitType === "execute-task") {
1062
+ const { milestone: eMid, slice: eSid, task: eTid } = parseUnitId(unitId);
1063
+ if (eMid && eSid && eTid) {
1064
+ loadEvidenceFromDisk(s.basePath, eMid, eSid, eTid);
1065
+ }
1066
+ }
1031
1067
  }
1032
1068
  // Only checkpoint code-executing units (not lifecycle/planning units)
1033
1069
  if (safetyConfig.enabled && safetyConfig.checkpoints && unitType === "execute-task") {
@@ -1094,7 +1130,7 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
1094
1130
  logWarning("engine", "Prompt reorder failed", { error: msg });
1095
1131
  }
1096
1132
  // Select and apply model (with tier escalation on retry — normal units only)
1097
- const modelResult = await deps.selectAndApplyModel(ctx, pi, unitType, unitId, s.basePath, prefs, s.verbose, s.autoModeStartModel, sidecarItem ? undefined : { isRetry, previousTier }, undefined, s.manualSessionModelOverride);
1133
+ const modelResult = await deps.selectAndApplyModel(ctx, pi, unitType, unitId, s.basePath, prefs, s.verbose, s.autoModeStartModel, sidecarItem ? undefined : { isRetry, previousTier }, undefined, s.manualSessionModelOverride, s.autoModeStartThinkingLevel);
1098
1134
  s.currentUnitRouting =
1099
1135
  modelResult.routing;
1100
1136
  s.currentUnitModel =
@@ -1107,6 +1143,9 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
1107
1143
  if (match) {
1108
1144
  const ok = await pi.setModel(match, { persist: false });
1109
1145
  if (ok) {
1146
+ if (s.autoModeStartThinkingLevel) {
1147
+ pi.setThinkingLevel(s.autoModeStartThinkingLevel);
1148
+ }
1110
1149
  s.currentUnitModel = match;
1111
1150
  ctx.ui.notify(`Hook model override: ${match.provider}/${match.id}`, "info");
1112
1151
  }
@@ -1427,6 +1466,20 @@ export async function runFinalize(ic, iterData, loopState, sidecarItem) {
1427
1466
  }
1428
1467
  else {
1429
1468
  // s.pendingVerificationRetry was set by postUnitPreVerification.
1469
+ // Emit a dedicated journal event so forensics can distinguish bounded
1470
+ // verification retries from genuine stuck-loop dispatch repetitions (#4540).
1471
+ const retryInfo = s.pendingVerificationRetry;
1472
+ deps.emitJournalEvent({
1473
+ ts: new Date().toISOString(),
1474
+ flowId: ic.flowId,
1475
+ seq: ic.nextSeq(),
1476
+ eventType: "artifact-verification-retry",
1477
+ data: {
1478
+ unitType: preUnitSnapshot?.type,
1479
+ unitId: retryInfo?.unitId,
1480
+ attempt: retryInfo?.attempt,
1481
+ },
1482
+ });
1430
1483
  // Continue the loop — next iteration will inject the retry context into the prompt.
1431
1484
  debugLog("autoLoop", { phase: "artifact-verification-retry", iteration: ic.iteration });
1432
1485
  return { action: "continue" };
@@ -26,15 +26,24 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
26
26
  let sessionResult;
27
27
  let sessionTimeoutHandle;
28
28
  const mySessionSwitchGeneration = ++sessionSwitchGeneration;
29
+ // #3731: Cancellation controller for newSession(). When the session-creation
30
+ // timeout fires, we abort this controller so that the still-in-flight
31
+ // newSession() discards itself after await this.abort() completes, preventing
32
+ // it from capturing the (now-root) process.cwd() and rebuilding the tool
33
+ // runtime with the wrong cwd.
34
+ const sessionAbortController = new AbortController();
29
35
  _setSessionSwitchInFlight(true);
30
36
  try {
31
- const sessionPromise = s.cmdCtx.newSession().finally(() => {
37
+ const sessionPromise = s.cmdCtx.newSession({ abortSignal: sessionAbortController.signal }).finally(() => {
32
38
  if (sessionSwitchGeneration === mySessionSwitchGeneration) {
33
39
  _setSessionSwitchInFlight(false);
34
40
  }
35
41
  });
36
42
  const timeoutPromise = new Promise((resolve) => {
37
- sessionTimeoutHandle = setTimeout(() => resolve({ cancelled: true }), NEW_SESSION_TIMEOUT_MS);
43
+ sessionTimeoutHandle = setTimeout(() => {
44
+ sessionAbortController.abort();
45
+ resolve({ cancelled: true });
46
+ }, NEW_SESSION_TIMEOUT_MS);
38
47
  });
39
48
  sessionResult = await Promise.race([sessionPromise, timeoutPromise]);
40
49
  }
@@ -92,6 +101,33 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
92
101
  catch (e) {
93
102
  logWarning("engine", "Failed to chdir to basePath before dispatch", { basePath: s.basePath, error: String(e) });
94
103
  }
104
+ // ── Provider request-readiness pre-check (#4555) ──
105
+ // Verify the provider can accept requests before dispatching. If the token
106
+ // has expired since bootstrap, return cancelled immediately so the unit is
107
+ // not wasted on a guaranteed 401.
108
+ {
109
+ const provider = s.currentUnitModel?.provider ?? ctx.model?.provider;
110
+ const registry = ctx.modelRegistry;
111
+ if (provider && registry != null && typeof registry.isProviderRequestReady === "function") {
112
+ let ready = false;
113
+ try {
114
+ ready = registry.isProviderRequestReady(provider);
115
+ }
116
+ catch {
117
+ ready = false;
118
+ }
119
+ if (!ready) {
120
+ return {
121
+ status: "cancelled",
122
+ errorContext: {
123
+ message: `Provider ${provider} is not request-ready (login/token expired)`,
124
+ category: "provider",
125
+ isTransient: false,
126
+ },
127
+ };
128
+ }
129
+ }
130
+ }
95
131
  // ── Send the prompt ──
96
132
  debugLog("runUnit", { phase: "send-message", unitType, unitId });
97
133
  pi.sendMessage({ customType: "gsd-auto", content: prompt, display: s.verbose }, { triggerTurn: true });