gsd-pi 2.69.0 → 2.70.0-dev.7ebda5e

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 (255) hide show
  1. package/dist/loader.js +4 -0
  2. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +150 -2
  3. package/dist/resources/extensions/gsd/auto-model-selection.js +33 -19
  4. package/dist/resources/extensions/gsd/auto-prompts.js +7 -3
  5. package/dist/resources/extensions/gsd/auto-start.js +25 -1
  6. package/dist/resources/extensions/gsd/auto.js +12 -8
  7. package/dist/resources/extensions/gsd/bootstrap/system-context.js +6 -2
  8. package/dist/resources/extensions/gsd/commands-cmux.js +30 -1
  9. package/dist/resources/extensions/gsd/commands-handlers.js +22 -8
  10. package/dist/resources/extensions/gsd/doctor-engine-checks.js +12 -0
  11. package/dist/resources/extensions/gsd/doctor-format.js +2 -0
  12. package/dist/resources/extensions/gsd/guided-flow.js +21 -10
  13. package/dist/resources/extensions/gsd/pre-execution-checks.js +5 -3
  14. package/dist/resources/extensions/gsd/validate-directory.js +30 -12
  15. package/dist/resources/extensions/gsd/workflow-mcp.js +64 -6
  16. package/dist/resources/extensions/slash-commands/audit.js +2 -1
  17. package/dist/resources/extensions/subagent/isolation.js +4 -2
  18. package/dist/update-check.d.ts +1 -0
  19. package/dist/update-check.js +30 -27
  20. package/dist/update-cmd.js +3 -11
  21. package/dist/web/standalone/.next/BUILD_ID +1 -1
  22. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  23. package/dist/web/standalone/.next/build-manifest.json +3 -3
  24. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  25. package/dist/web/standalone/.next/required-server-files.json +4 -4
  26. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  27. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  29. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  37. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  39. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  40. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  41. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  43. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  47. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  48. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  49. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  50. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  51. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  52. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  53. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  54. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  55. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  56. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  57. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  58. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  59. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  60. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  61. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  62. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  63. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  64. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  65. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  66. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  68. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  69. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  70. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  72. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  74. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  76. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  78. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  79. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  80. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  85. package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  95. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  101. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  117. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +2 -2
  121. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  130. package/dist/web/standalone/.next/server/app/index.html +1 -1
  131. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  132. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  133. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  134. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  135. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  136. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  137. package/dist/web/standalone/.next/server/app/page.js +2 -2
  138. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  139. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  140. package/dist/web/standalone/.next/server/chunks/63.js +3 -3
  141. package/dist/web/standalone/.next/server/chunks/6897.js +3 -3
  142. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  143. package/dist/web/standalone/.next/server/middleware.js +2 -2
  144. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  145. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  146. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  147. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  148. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  149. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  150. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  151. package/dist/web/standalone/.next/static/chunks/app/page-f1e30ab6bb269149.js +1 -0
  152. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  153. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  154. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  155. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  156. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  157. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  158. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  159. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  160. package/dist/web/standalone/server.js +1 -1
  161. package/dist/web-mode.js +4 -0
  162. package/package.json +11 -11
  163. package/packages/daemon/src/orchestrator.ts +9 -84
  164. package/packages/mcp-server/README.md +25 -3
  165. package/packages/mcp-server/dist/cli.d.ts +0 -1
  166. package/packages/mcp-server/dist/cli.d.ts.map +1 -1
  167. package/packages/mcp-server/dist/cli.js +4 -2
  168. package/packages/mcp-server/dist/cli.js.map +1 -1
  169. package/packages/mcp-server/dist/server.d.ts +32 -1
  170. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  171. package/packages/mcp-server/dist/server.js +118 -1
  172. package/packages/mcp-server/dist/server.js.map +1 -1
  173. package/packages/mcp-server/dist/tool-credentials.d.ts +6 -0
  174. package/packages/mcp-server/dist/tool-credentials.d.ts.map +1 -0
  175. package/packages/mcp-server/dist/tool-credentials.js +90 -0
  176. package/packages/mcp-server/dist/tool-credentials.js.map +1 -0
  177. package/packages/mcp-server/dist/workflow-tools.d.ts +3 -0
  178. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  179. package/packages/mcp-server/dist/workflow-tools.js +308 -4
  180. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  181. package/packages/mcp-server/src/cli.ts +5 -3
  182. package/packages/mcp-server/src/import-candidates.test.ts +48 -0
  183. package/packages/mcp-server/src/mcp-server.test.ts +85 -1
  184. package/packages/mcp-server/src/server.ts +188 -1
  185. package/packages/mcp-server/src/tool-credentials.test.ts +95 -0
  186. package/packages/mcp-server/src/tool-credentials.ts +97 -0
  187. package/packages/mcp-server/src/workflow-tools.test.ts +32 -25
  188. package/packages/mcp-server/src/workflow-tools.ts +398 -2
  189. package/packages/pi-agent-core/dist/agent.d.ts +8 -0
  190. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  191. package/packages/pi-agent-core/dist/agent.js +3 -0
  192. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  193. package/packages/pi-agent-core/src/agent.test.ts +82 -0
  194. package/packages/pi-agent-core/src/agent.ts +12 -0
  195. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  196. package/packages/pi-ai/dist/providers/anthropic.js +1 -23
  197. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  198. package/packages/pi-ai/dist/utils/oauth/index.d.ts +3 -2
  199. package/packages/pi-ai/dist/utils/oauth/index.d.ts.map +1 -1
  200. package/packages/pi-ai/dist/utils/oauth/index.js +3 -5
  201. package/packages/pi-ai/dist/utils/oauth/index.js.map +1 -1
  202. package/packages/pi-ai/src/providers/anthropic.ts +1 -31
  203. package/packages/pi-ai/src/utils/oauth/index.ts +3 -5
  204. package/packages/pi-coding-agent/dist/core/lsp/config.d.ts +1 -0
  205. package/packages/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -1
  206. package/packages/pi-coding-agent/dist/core/lsp/config.js +38 -15
  207. package/packages/pi-coding-agent/dist/core/lsp/config.js.map +1 -1
  208. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  209. package/packages/pi-coding-agent/dist/core/sdk.js +10 -0
  210. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  211. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
  212. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +3 -1
  213. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  214. package/packages/pi-coding-agent/package.json +1 -1
  215. package/packages/pi-coding-agent/src/core/lsp/config.ts +43 -17
  216. package/packages/pi-coding-agent/src/core/sdk.ts +8 -0
  217. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +7 -5
  218. package/pkg/package.json +1 -1
  219. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +227 -2
  220. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +172 -0
  221. package/src/resources/extensions/gsd/auto-model-selection.ts +39 -25
  222. package/src/resources/extensions/gsd/auto-prompts.ts +7 -3
  223. package/src/resources/extensions/gsd/auto-start.ts +34 -1
  224. package/src/resources/extensions/gsd/auto.ts +12 -8
  225. package/src/resources/extensions/gsd/bootstrap/system-context.ts +9 -5
  226. package/src/resources/extensions/gsd/commands-cmux.ts +32 -1
  227. package/src/resources/extensions/gsd/commands-handlers.ts +22 -7
  228. package/src/resources/extensions/gsd/doctor-engine-checks.ts +14 -0
  229. package/src/resources/extensions/gsd/doctor-format.ts +1 -0
  230. package/src/resources/extensions/gsd/doctor-types.ts +1 -0
  231. package/src/resources/extensions/gsd/guided-flow.ts +24 -8
  232. package/src/resources/extensions/gsd/pre-execution-checks.ts +6 -3
  233. package/src/resources/extensions/gsd/tests/cmux.test.ts +67 -1
  234. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +43 -0
  235. package/src/resources/extensions/gsd/tests/interactive-routing-bypass.test.ts +207 -0
  236. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +6 -2
  237. package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +48 -1
  238. package/src/resources/extensions/gsd/tests/resource-loader-import-path.test.ts +8 -7
  239. package/src/resources/extensions/gsd/tests/validate-directory.test.ts +33 -1
  240. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +87 -1
  241. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +48 -7
  242. package/src/resources/extensions/gsd/validate-directory.ts +33 -11
  243. package/src/resources/extensions/gsd/workflow-mcp.ts +74 -5
  244. package/src/resources/extensions/slash-commands/audit.ts +2 -1
  245. package/src/resources/extensions/subagent/isolation.ts +4 -3
  246. package/dist/web/standalone/.next/static/chunks/app/page-7115e62689b5fd84.js +0 -1
  247. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  248. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  249. package/packages/pi-ai/dist/utils/oauth/anthropic.d.ts +0 -17
  250. package/packages/pi-ai/dist/utils/oauth/anthropic.d.ts.map +0 -1
  251. package/packages/pi-ai/dist/utils/oauth/anthropic.js +0 -106
  252. package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +0 -1
  253. package/packages/pi-ai/src/utils/oauth/anthropic.ts +0 -140
  254. /package/dist/web/standalone/.next/static/{DrWdzskk28E5Qz-Wjw1mj → yvFbuOJuph5517lR7HBt2}/_buildManifest.js +0 -0
  255. /package/dist/web/standalone/.next/static/{DrWdzskk28E5Qz-Wjw1mj → yvFbuOJuph5517lR7HBt2}/_ssgManifest.js +0 -0
package/dist/loader.js CHANGED
@@ -93,6 +93,10 @@ if (!existsSync(appRoot)) {
93
93
  }
94
94
  // GSD_CODING_AGENT_DIR — tells pi's getAgentDir() to return ~/.gsd/agent/ instead of ~/.gsd/agent/
95
95
  process.env.GSD_CODING_AGENT_DIR = agentDir;
96
+ // GSD_PKG_ROOT — absolute path to gsd-pi package root. Used by deployed extensions
97
+ // (e.g. auto.ts resume path) to import modules like resource-loader.js that live
98
+ // in the package tree, not in the deployed ~/.gsd/agent/ tree.
99
+ process.env.GSD_PKG_ROOT = gsdRoot;
96
100
  // RTK environment — make ~/.gsd/agent/bin visible to all child-process paths,
97
101
  // not just the bash tool, and force-disable RTK telemetry for GSD-managed use.
98
102
  applyRtkProcessEnv(process.env);
@@ -10,6 +10,8 @@ import { EventStream } from "@gsd/pi-ai";
10
10
  import { execSync } from "node:child_process";
11
11
  import { PartialMessageBuilder, ZERO_USAGE, mapUsage } from "./partial-builder.js";
12
12
  import { buildWorkflowMcpServers } from "../gsd/workflow-mcp.js";
13
+ import { showInterviewRound } from "../shared/tui.js";
14
+ const OTHER_OPTION_LABEL = "None of the above";
13
15
  // ---------------------------------------------------------------------------
14
16
  // Stream factory
15
17
  // ---------------------------------------------------------------------------
@@ -124,6 +126,147 @@ export function makeStreamExhaustedErrorMessage(model, lastTextContent) {
124
126
  }
125
127
  return message;
126
128
  }
129
+ function readElicitationChoices(options) {
130
+ if (!Array.isArray(options))
131
+ return [];
132
+ return options
133
+ .map((option) => (typeof option?.const === "string" ? option.const : typeof option?.title === "string" ? option.title : ""))
134
+ .filter((option) => option.length > 0);
135
+ }
136
+ export function parseAskUserQuestionsElicitation(request) {
137
+ if (request.mode && request.mode !== "form")
138
+ return null;
139
+ const properties = request.requestedSchema?.properties;
140
+ if (!properties || typeof properties !== "object")
141
+ return null;
142
+ const questions = [];
143
+ for (const [fieldId, rawField] of Object.entries(properties)) {
144
+ if (fieldId.endsWith("__note"))
145
+ continue;
146
+ if (!rawField || typeof rawField !== "object")
147
+ return null;
148
+ const header = typeof rawField.title === "string" && rawField.title.length > 0 ? rawField.title : fieldId;
149
+ const question = typeof rawField.description === "string" ? rawField.description : "";
150
+ if (rawField.type === "array") {
151
+ const options = readElicitationChoices(rawField.items?.anyOf).map((label) => ({ label, description: "" }));
152
+ if (options.length === 0)
153
+ return null;
154
+ questions.push({
155
+ id: fieldId,
156
+ header,
157
+ question,
158
+ options,
159
+ allowMultiple: true,
160
+ });
161
+ continue;
162
+ }
163
+ if (rawField.type === "string") {
164
+ const noteFieldId = Object.prototype.hasOwnProperty.call(properties, `${fieldId}__note`)
165
+ ? `${fieldId}__note`
166
+ : undefined;
167
+ const options = readElicitationChoices(rawField.oneOf)
168
+ .filter((label) => label !== OTHER_OPTION_LABEL)
169
+ .map((label) => ({ label, description: "" }));
170
+ if (options.length === 0)
171
+ return null;
172
+ questions.push({
173
+ id: fieldId,
174
+ header,
175
+ question,
176
+ options,
177
+ noteFieldId,
178
+ });
179
+ continue;
180
+ }
181
+ return null;
182
+ }
183
+ return questions.length > 0 ? questions : null;
184
+ }
185
+ export function roundResultToElicitationContent(questions, result) {
186
+ const content = {};
187
+ for (const question of questions) {
188
+ const answer = result.answers[question.id];
189
+ if (!answer)
190
+ continue;
191
+ if (question.allowMultiple) {
192
+ const selected = Array.isArray(answer.selected) ? answer.selected : [answer.selected];
193
+ content[question.id] = selected;
194
+ continue;
195
+ }
196
+ const selected = Array.isArray(answer.selected) ? answer.selected[0] ?? "" : answer.selected;
197
+ content[question.id] = selected;
198
+ if (question.noteFieldId && selected === OTHER_OPTION_LABEL && answer.notes.trim().length > 0) {
199
+ content[question.noteFieldId] = answer.notes.trim();
200
+ }
201
+ }
202
+ return content;
203
+ }
204
+ function buildElicitationPromptTitle(request, question) {
205
+ const parts = [
206
+ request.serverName ? `[${request.serverName}]` : "",
207
+ question.header,
208
+ question.question,
209
+ ].filter((part) => part && part.trim().length > 0);
210
+ return parts.join("\n\n");
211
+ }
212
+ async function promptElicitationWithDialogs(request, questions, ui, signal) {
213
+ const content = {};
214
+ for (const question of questions) {
215
+ const title = buildElicitationPromptTitle(request, question);
216
+ if (question.allowMultiple) {
217
+ const selected = await ui.select(title, question.options.map((option) => option.label), {
218
+ allowMultiple: true,
219
+ signal,
220
+ });
221
+ if (Array.isArray(selected)) {
222
+ if (selected.length === 0)
223
+ return { action: "cancel" };
224
+ content[question.id] = selected;
225
+ continue;
226
+ }
227
+ if (typeof selected === "string" && selected.length > 0) {
228
+ content[question.id] = [selected];
229
+ continue;
230
+ }
231
+ return { action: "cancel" };
232
+ }
233
+ const selected = await ui.select(title, [...question.options.map((option) => option.label), OTHER_OPTION_LABEL], { signal });
234
+ if (typeof selected !== "string" || selected.length === 0) {
235
+ return { action: "cancel" };
236
+ }
237
+ content[question.id] = selected;
238
+ if (question.noteFieldId && selected === OTHER_OPTION_LABEL) {
239
+ const note = await ui.input(`${question.header} note`, "Explain your answer", { signal });
240
+ if (note === undefined)
241
+ return { action: "cancel" };
242
+ if (note.trim().length > 0) {
243
+ content[question.noteFieldId] = note.trim();
244
+ }
245
+ }
246
+ }
247
+ return { action: "accept", content };
248
+ }
249
+ export function createClaudeCodeElicitationHandler(ui) {
250
+ if (!ui)
251
+ return undefined;
252
+ return async (request, { signal }) => {
253
+ if (request.mode === "url") {
254
+ return { action: "decline" };
255
+ }
256
+ const questions = parseAskUserQuestionsElicitation(request);
257
+ if (!questions) {
258
+ return { action: "decline" };
259
+ }
260
+ const interviewResult = await showInterviewRound(questions, { signal }, { ui }).catch(() => undefined);
261
+ if (interviewResult && Object.keys(interviewResult.answers).length > 0) {
262
+ return {
263
+ action: "accept",
264
+ content: roundResultToElicitationContent(questions, interviewResult),
265
+ };
266
+ }
267
+ return promptElicitationWithDialogs(request, questions, ui, signal);
268
+ };
269
+ }
127
270
  // ---------------------------------------------------------------------------
128
271
  // SDK options builder
129
272
  // ---------------------------------------------------------------------------
@@ -133,7 +276,7 @@ export function makeStreamExhaustedErrorMessage(model, lastTextContent) {
133
276
  * Extracted for testability — callers can verify session persistence,
134
277
  * beta flags, and other configuration without mocking the full SDK.
135
278
  */
136
- export function buildSdkOptions(modelId, prompt) {
279
+ export function buildSdkOptions(modelId, prompt, extraOptions = {}) {
137
280
  const mcpServers = buildWorkflowMcpServers();
138
281
  return {
139
282
  pathToClaudeCodeExecutable: getClaudePath(),
@@ -147,6 +290,7 @@ export function buildSdkOptions(modelId, prompt) {
147
290
  systemPrompt: { type: "preset", preset: "claude_code" },
148
291
  ...(mcpServers ? { mcpServers } : {}),
149
292
  betas: modelId.includes("sonnet") ? ["context-1m-2025-08-07"] : [],
293
+ ...extraOptions,
150
294
  };
151
295
  }
152
296
  function normalizeToolResultContent(content) {
@@ -272,7 +416,11 @@ async function pumpSdkMessages(model, context, options, stream) {
272
416
  options.signal.addEventListener("abort", () => controller.abort(), { once: true });
273
417
  }
274
418
  const prompt = buildPromptFromContext(context);
275
- const sdkOpts = buildSdkOptions(modelId, prompt);
419
+ const sdkOpts = buildSdkOptions(modelId, prompt, typeof options?.extensionUIContext === "object"
420
+ ? {
421
+ onElicitation: createClaudeCodeElicitationHandler(options?.extensionUIContext),
422
+ }
423
+ : {});
276
424
  const queryResult = sdk.query({
277
425
  prompt,
278
426
  options: {
@@ -8,10 +8,17 @@ import { classifyUnitComplexity, tierLabel } from "./complexity-classifier.js";
8
8
  import { resolveModelForComplexity, escalateTier, getEligibleModels, loadCapabilityOverrides, adjustToolSet } from "./model-router.js";
9
9
  import { getLedger, getProjectTotals } from "./metrics.js";
10
10
  import { unitPhaseLabel } from "./auto-dashboard.js";
11
- export function resolvePreferredModelConfig(unitType, autoModeStartModel) {
11
+ export function resolvePreferredModelConfig(unitType, autoModeStartModel,
12
+ /** When false, only return explicit per-phase model configs — do not
13
+ * synthesize a routing ceiling from dynamic_routing.tier_models (#3962). */
14
+ isAutoMode = true) {
12
15
  const explicitConfig = resolveModelWithFallbacksForUnit(unitType);
13
16
  if (explicitConfig)
14
17
  return explicitConfig;
18
+ // In interactive mode, don't synthesize a routing-based model config.
19
+ // The user's session model (/model) should be used as-is (#3962).
20
+ if (!isAutoMode)
21
+ return undefined;
15
22
  const routingConfig = resolveDynamicRoutingConfig();
16
23
  if (!routingConfig.enabled || !routingConfig.tier_models)
17
24
  return undefined;
@@ -34,14 +41,23 @@ export function resolvePreferredModelConfig(unitType, autoModeStartModel) {
34
41
  *
35
42
  * Returns routing metadata for metrics tracking.
36
43
  */
37
- export async function selectAndApplyModel(ctx, pi, unitType, unitId, basePath, prefs, verbose, autoModeStartModel, retryContext) {
38
- const modelConfig = resolvePreferredModelConfig(unitType, autoModeStartModel);
44
+ export async function selectAndApplyModel(ctx, pi, unitType, unitId, basePath, prefs, verbose, autoModeStartModel, retryContext,
45
+ /** When false (interactive/guided-flow), skip dynamic routing and use the session model.
46
+ * Dynamic routing only applies in auto-mode where cost optimization is expected. (#3962) */
47
+ isAutoMode = true) {
48
+ const modelConfig = resolvePreferredModelConfig(unitType, autoModeStartModel, isAutoMode);
39
49
  let routing = null;
40
50
  let appliedModel = null;
41
51
  if (modelConfig) {
42
52
  const availableModels = ctx.modelRegistry.getAvailable();
43
53
  // ─── Dynamic Model Routing ─────────────────────────────────────────
54
+ // Dynamic routing (complexity-based downgrading) only applies in auto-mode.
55
+ // Interactive/guided-flow dispatches use the user's session model directly,
56
+ // respecting their /model selection without silent downgrades (#3962).
44
57
  const routingConfig = resolveDynamicRoutingConfig();
58
+ if (!isAutoMode) {
59
+ routingConfig.enabled = false;
60
+ }
45
61
  let effectiveModelConfig = modelConfig;
46
62
  let routingTierLabel = "";
47
63
  // Disable routing for flat-rate providers like GitHub Copilot (#3453).
@@ -85,9 +101,8 @@ export async function selectAndApplyModel(ctx, pi, unitType, unitId, basePath, p
85
101
  const escalated = escalateTier(retryContext.previousTier);
86
102
  if (escalated) {
87
103
  classification = { ...classification, tier: escalated, reason: "escalated after failure" };
88
- if (verbose) {
89
- ctx.ui.notify(`Tier escalation: ${retryContext.previousTier} → ${escalated} (retry after failure)`, "info");
90
- }
104
+ // Always notify on tier escalation — model changes should be visible (#3962)
105
+ ctx.ui.notify(`Tier escalation: ${retryContext.previousTier} → ${escalated} (retry after failure)`, "info");
91
106
  }
92
107
  }
93
108
  // Load user capability overrides from preferences (D-17: deep-merged with built-in profiles)
@@ -139,19 +154,18 @@ export async function selectAndApplyModel(ctx, pi, unitType, unitId, basePath, p
139
154
  primary: routingResult.modelId,
140
155
  fallbacks: routingResult.fallbacks,
141
156
  };
142
- if (verbose) {
143
- if (routingResult.selectionMethod === "capability-scored" && routingResult.capabilityScores) {
144
- // Verbose scoring breakdown for capability-scored decisions (D-20)
145
- const tierLbl = tierLabel(classification.tier);
146
- const scores = Object.entries(routingResult.capabilityScores)
147
- .sort(([, a], [, b]) => b - a)
148
- .map(([id, score]) => `${id}: ${score.toFixed(1)}`)
149
- .join(", ");
150
- ctx.ui.notify(`Dynamic routing [${tierLbl}]: ${routingResult.modelId} (capability-scored) — ${scores}`, "info");
151
- }
152
- else {
153
- ctx.ui.notify(`Dynamic routing [${tierLabel(classification.tier)}]: ${routingResult.modelId} (${classification.reason})`, "info");
154
- }
157
+ // Always notify on model downgrade — users should see when their
158
+ // model selection is overridden, not just in verbose mode (#3962).
159
+ if (routingResult.selectionMethod === "capability-scored" && routingResult.capabilityScores) {
160
+ const tierLbl = tierLabel(classification.tier);
161
+ const scores = Object.entries(routingResult.capabilityScores)
162
+ .sort(([, a], [, b]) => b - a)
163
+ .map(([id, score]) => `${id}: ${score.toFixed(1)}`)
164
+ .join(", ");
165
+ ctx.ui.notify(`Dynamic routing [${tierLbl}]: ${routingResult.modelId} (capability-scored) — ${scores}`, "info");
166
+ }
167
+ else {
168
+ ctx.ui.notify(`Dynamic routing [${tierLabel(classification.tier)}]: ${routingResult.modelId} (${classification.reason})`, "info");
155
169
  }
156
170
  }
157
171
  routingTierLabel = ` [${tierLabel(classification.tier)}]`;
@@ -876,7 +876,7 @@ export async function buildDiscussMilestonePrompt(mid, midTitle, base) {
876
876
  milestoneId: mid,
877
877
  milestoneTitle: midTitle,
878
878
  inlinedTemplates: discussTemplates,
879
- structuredQuestionsAvailable: "true",
879
+ structuredQuestionsAvailable: "false",
880
880
  commitInstruction: "Do not commit planning artifacts — .gsd/ is managed externally.",
881
881
  fastPathInstruction: "",
882
882
  });
@@ -1319,7 +1319,9 @@ export async function buildCompleteMilestonePrompt(mid, midTitle, base, level) {
1319
1319
  try {
1320
1320
  const { isDbAvailable, getMilestoneSlices } = await import("./gsd-db.js");
1321
1321
  if (isDbAvailable()) {
1322
- sliceIds = getMilestoneSlices(mid).map(s => s.id);
1322
+ sliceIds = getMilestoneSlices(mid)
1323
+ .filter(s => s.status !== "skipped")
1324
+ .map(s => s.id);
1323
1325
  }
1324
1326
  }
1325
1327
  catch (err) {
@@ -1415,7 +1417,9 @@ export async function buildValidateMilestonePrompt(mid, midTitle, base, level) {
1415
1417
  try {
1416
1418
  const { isDbAvailable, getMilestoneSlices } = await import("./gsd-db.js");
1417
1419
  if (isDbAvailable()) {
1418
- valSliceIds = getMilestoneSlices(mid).map(s => s.id);
1420
+ valSliceIds = getMilestoneSlices(mid)
1421
+ .filter(s => s.status !== "skipped")
1422
+ .map(s => s.id);
1419
1423
  }
1420
1424
  }
1421
1425
  catch (err) {
@@ -38,7 +38,7 @@ import { existsSync, mkdirSync, readdirSync, rmSync, statSync, unlinkSync, } fro
38
38
  import { join } from "node:path";
39
39
  import { sep as pathSep } from "node:path";
40
40
  import { resolveProjectRootDbPath } from "./bootstrap/dynamic-tools.js";
41
- import { resolveDefaultSessionModel } from "./preferences-models.js";
41
+ import { resolveDefaultSessionModel, resolveDynamicRoutingConfig } from "./preferences-models.js";
42
42
  /**
43
43
  * Bootstrap a fresh auto-mode session. Handles everything from git init
44
44
  * through secrets collection, returning when ready for the first
@@ -609,6 +609,30 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
609
609
  ? `Will loop through ${pendingCount} milestones.`
610
610
  : "Will loop until milestone complete.";
611
611
  ctx.ui.notify(`${modeLabel} started. ${scopeMsg}`, "info");
612
+ // Show dynamic routing status so users know upfront if models will be
613
+ // downgraded for simple tasks (#3962).
614
+ // Use the same effective logic as selectAndApplyModel: check flat-rate
615
+ // provider suppression and resolve the actual ceiling model.
616
+ const routingConfig = resolveDynamicRoutingConfig();
617
+ const startModelLabel = s.autoModeStartModel
618
+ ? `${s.autoModeStartModel.provider}/${s.autoModeStartModel.id}`
619
+ : ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : "default";
620
+ // Flat-rate providers (e.g. GitHub Copilot, claude-code) suppress routing
621
+ // at dispatch time (#3453) — reflect that in the banner.
622
+ const { isFlatRateProvider } = await import("./auto-model-selection.js");
623
+ const effectiveProvider = s.autoModeStartModel?.provider ?? ctx.model?.provider;
624
+ const effectivelyEnabled = routingConfig.enabled
625
+ && !(effectiveProvider && isFlatRateProvider(effectiveProvider));
626
+ // The actual ceiling may come from tier_models.heavy, not the start model.
627
+ const effectiveCeiling = (routingConfig.enabled && routingConfig.tier_models?.heavy)
628
+ ? routingConfig.tier_models.heavy
629
+ : startModelLabel;
630
+ if (effectivelyEnabled) {
631
+ ctx.ui.notify(`Dynamic routing: enabled — simple tasks may use cheaper models (ceiling: ${effectiveCeiling})`, "info");
632
+ }
633
+ else {
634
+ ctx.ui.notify(`Dynamic routing: disabled — all tasks will use ${startModelLabel}`, "info");
635
+ }
612
636
  updateSessionLock(lockBase(), "starting", s.currentMilestoneId ?? "unknown");
613
637
  writeLock(lockBase(), "starting", s.currentMilestoneId ?? "unknown");
614
638
  // Secrets collection gate
@@ -37,9 +37,9 @@ import { getRtkSessionSavings } from "../shared/rtk-session-stats.js";
37
37
  import { initMetrics, resetMetrics, getLedger, getProjectTotals, formatCost, formatTokenCount, } from "./metrics.js";
38
38
  import { logWarning } from "./workflow-logger.js";
39
39
  import { homedir } from "node:os";
40
- import { join, dirname } from "node:path";
40
+ import { join } from "node:path";
41
+ import { pathToFileURL } from "node:url";
41
42
  import { readFileSync, existsSync, mkdirSync, writeFileSync, unlinkSync } from "node:fs";
42
- import { createRequire } from "node:module";
43
43
  import { atomicWriteSync } from "./atomic-write.js";
44
44
  import { autoCommitCurrentBranch, captureIntegrationBranch, detectWorktreeName, getCurrentBranch, getMainBranch, setActiveMilestoneId, } from "./worktree.js";
45
45
  import { GitServiceImpl } from "./git-service.js";
@@ -1021,13 +1021,17 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1021
1021
  restoreHookState(s.basePath);
1022
1022
  // Re-sync managed resources on resume so long-lived auto sessions pick up
1023
1023
  // bundled extension updates before resume-time verification/state logic runs.
1024
+ // GSD_PKG_ROOT is set by loader.ts and points to the gsd-pi package root.
1025
+ // The relative import ("../../../resource-loader.js") only works from the source
1026
+ // tree; deployed extensions live at ~/.gsd/agent/extensions/gsd/ where the
1027
+ // relative path resolves to ~/.gsd/agent/resource-loader.js which doesn't exist.
1028
+ // Using GSD_PKG_ROOT constructs a correct absolute path in both contexts (#3949).
1024
1029
  const agentDir = process.env.GSD_CODING_AGENT_DIR || join(process.env.GSD_HOME || homedir(), ".gsd", "agent");
1025
- // Resolve resource-loader from the gsd-pi package root — the relative
1026
- // "../../../resource-loader.js" path only works from the source tree but
1027
- // breaks when extensions are deployed to ~/.gsd/agent/extensions/gsd/.
1028
- const _req = createRequire(import.meta.url);
1029
- const pkgRoot = dirname(_req.resolve("gsd-pi/package.json"));
1030
- const { initResources } = await import(join(pkgRoot, "dist", "resource-loader.js"));
1030
+ const pkgRoot = process.env.GSD_PKG_ROOT;
1031
+ const resourceLoaderPath = pkgRoot
1032
+ ? pathToFileURL(join(pkgRoot, "dist", "resource-loader.js")).href
1033
+ : new URL("../../../resource-loader.js", import.meta.url).href;
1034
+ const { initResources } = await import(resourceLoaderPath);
1031
1035
  initResources(agentDir);
1032
1036
  // Open the project DB before rebuild/derive so resume uses DB-backed
1033
1037
  // state instead of falling back to stale markdown parsing (#2940).
@@ -16,6 +16,7 @@ import { deriveState } from "../state.js";
16
16
  import { formatOverridesSection, formatShortcut, loadActiveOverrides, loadFile, parseContinue, parseSummary } from "../files.js";
17
17
  import { toPosixPath } from "../../shared/mod.js";
18
18
  import { markCmuxPromptShown, shouldPromptToEnableCmux } from "../../cmux/index.js";
19
+ import { autoEnableCmuxPreferences } from "../commands-cmux.js";
19
20
  const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
20
21
  /**
21
22
  * Bundled skill triggers — resolved dynamically at runtime instead of
@@ -64,10 +65,13 @@ export async function buildBeforeAgentStartResult(event, ctx) {
64
65
  shortcutDashboard: formatShortcut("Ctrl+Alt+G"),
65
66
  shortcutShell: formatShortcut("Ctrl+Alt+B"),
66
67
  });
67
- const loadedPreferences = loadEffectiveGSDPreferences();
68
+ let loadedPreferences = loadEffectiveGSDPreferences();
68
69
  if (shouldPromptToEnableCmux(loadedPreferences?.preferences)) {
69
70
  markCmuxPromptShown();
70
- ctx.ui.notify("cmux detected. Run /gsd cmux on to enable sidebar metadata, notifications, and visual subagent splits for this project.", "info");
71
+ if (autoEnableCmuxPreferences()) {
72
+ loadedPreferences = loadEffectiveGSDPreferences();
73
+ ctx.ui.notify("cmux detected — auto-enabled. Run /gsd cmux off to disable.", "info");
74
+ }
71
75
  }
72
76
  let preferenceBlock = "";
73
77
  if (loadedPreferences) {
@@ -1,8 +1,37 @@
1
- import { existsSync, readFileSync } from "node:fs";
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { clearCmuxSidebar, CmuxClient, detectCmuxEnvironment, resolveCmuxConfig } from "../cmux/index.js";
3
3
  import { saveFile } from "./files.js";
4
4
  import { getProjectGSDPreferencesPath, loadEffectiveGSDPreferences, loadProjectGSDPreferences, } from "./preferences.js";
5
5
  import { ensurePreferencesFile, serializePreferencesToFrontmatter } from "./commands-prefs-wizard.js";
6
+ /**
7
+ * Auto-enable cmux in project preferences when detected but never configured.
8
+ * Called at boot (before agent start) — no ExtensionCommandContext needed.
9
+ * Returns true if preferences were written, false if skipped.
10
+ */
11
+ export function autoEnableCmuxPreferences() {
12
+ const path = getProjectGSDPreferencesPath();
13
+ if (!existsSync(path))
14
+ return false;
15
+ const existing = loadProjectGSDPreferences();
16
+ const prefs = existing?.preferences ? { ...existing.preferences } : { version: 1 };
17
+ prefs.cmux = {
18
+ enabled: true,
19
+ notifications: true,
20
+ sidebar: true,
21
+ splits: false,
22
+ browser: false,
23
+ ...(prefs.cmux ?? {}),
24
+ };
25
+ prefs.cmux.enabled = true;
26
+ prefs.version = prefs.version || 1;
27
+ const frontmatter = serializePreferencesToFrontmatter(prefs);
28
+ let body = "\n# GSD Skill Preferences\n\nSee `~/.gsd/agent/extensions/gsd/docs/preferences-reference.md` for full field documentation and examples.\n";
29
+ const preserved = extractBodyAfterFrontmatter(readFileSync(path, "utf-8"));
30
+ if (preserved)
31
+ body = preserved;
32
+ writeFileSync(path, `---\n${frontmatter}---${body}`, "utf-8");
33
+ return true;
34
+ }
6
35
  function extractBodyAfterFrontmatter(content) {
7
36
  const start = content.startsWith("---\n") ? 4 : content.startsWith("---\r\n") ? 5 : -1;
8
37
  if (start === -1)
@@ -15,6 +15,26 @@ import { isAutoActive, checkRemoteAutoSession } from "./auto.js";
15
15
  import { getAutoWorktreePath } from "./auto-worktree.js";
16
16
  import { projectRoot } from "./commands/context.js";
17
17
  import { loadPrompt } from "./prompt-loader.js";
18
+ const UPDATE_REGISTRY_URL = "https://registry.npmjs.org/gsd-pi/latest";
19
+ const UPDATE_FETCH_TIMEOUT_MS = 5000;
20
+ async function fetchLatestVersionForCommand() {
21
+ const controller = new AbortController();
22
+ const timeout = setTimeout(() => controller.abort(), UPDATE_FETCH_TIMEOUT_MS);
23
+ try {
24
+ const res = await fetch(UPDATE_REGISTRY_URL, { signal: controller.signal });
25
+ if (!res.ok)
26
+ return null;
27
+ const data = (await res.json());
28
+ const latest = typeof data.version === "string" ? data.version.trim().replace(/^v/, "") : "";
29
+ return latest.length > 0 ? latest : null;
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ finally {
35
+ clearTimeout(timeout);
36
+ }
37
+ }
18
38
  export function dispatchDoctorHeal(pi, scope, reportText, structuredIssues) {
19
39
  const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".gsd", "agent", "GSD-WORKFLOW.md");
20
40
  const workflow = readFileSync(workflowPath, "utf-8");
@@ -311,14 +331,8 @@ export async function handleUpdate(ctx) {
311
331
  const NPM_PACKAGE = "gsd-pi";
312
332
  const current = process.env.GSD_VERSION || "0.0.0";
313
333
  ctx.ui.notify(`Current version: v${current}\nChecking npm registry...`, "info");
314
- let latest;
315
- try {
316
- latest = execSync(`npm view ${NPM_PACKAGE} version`, {
317
- encoding: "utf-8",
318
- stdio: ["ignore", "pipe", "ignore"],
319
- }).trim();
320
- }
321
- catch {
334
+ const latest = await fetchLatestVersionForCommand();
335
+ if (!latest) {
322
336
  ctx.ui.notify("Failed to reach npm registry. Check your network connection.", "error");
323
337
  return;
324
338
  }
@@ -6,6 +6,18 @@ import { deriveState } from "./state.js";
6
6
  import { readEvents } from "./workflow-events.js";
7
7
  import { renderAllProjections } from "./workflow-projections.js";
8
8
  export async function checkEngineHealth(basePath, issues, fixesApplied) {
9
+ const dbPath = join(basePath, ".gsd", "gsd.db");
10
+ if (!isDbAvailable() && existsSync(dbPath)) {
11
+ issues.push({
12
+ severity: "warning",
13
+ code: "db_unavailable",
14
+ scope: "project",
15
+ unitId: "project",
16
+ message: "Database unavailable — using filesystem state derivation (degraded mode). State queries may be slower and less reliable.",
17
+ file: ".gsd/gsd.db",
18
+ fixable: false,
19
+ });
20
+ }
9
21
  // ── DB constraint violation detection (full doctor only, not pre-dispatch per D-10) ──
10
22
  try {
11
23
  if (isDbAvailable()) {
@@ -1,6 +1,8 @@
1
1
  function matchesScope(unitId, scope) {
2
2
  if (!scope)
3
3
  return true;
4
+ if (unitId === "project" || unitId === "environment")
5
+ return true;
4
6
  return unitId === scope || unitId.startsWith(`${scope}/`) || unitId.startsWith(`${scope}`);
5
7
  }
6
8
  export function summarizeDoctorIssues(issues) {
@@ -35,7 +35,7 @@ import { findMilestoneIds, nextMilestoneId, reserveMilestoneId, getReservedMiles
35
35
  import { parkMilestone, discardMilestone } from "./milestone-actions.js";
36
36
  import { selectAndApplyModel } from "./auto-model-selection.js";
37
37
  import { DISCUSS_TOOLS_ALLOWLIST } from "./constants.js";
38
- import { getWorkflowTransportSupportError, getRequiredWorkflowToolsForGuidedUnit, } from "./workflow-mcp.js";
38
+ import { getWorkflowTransportSupportError, getRequiredWorkflowToolsForGuidedUnit, supportsStructuredQuestions, } from "./workflow-mcp.js";
39
39
  import { runPreparation, formatCodebaseBrief, formatPriorContextBrief, } from "./preparation.js";
40
40
  // ─── Re-exports (preserve public API for existing importers) ────────────────
41
41
  export { MILESTONE_ID_RE, generateMilestoneSuffix, nextMilestoneId, extractMilestoneSeq, parseMilestoneId, milestoneIdSort, maxMilestoneNum, findMilestoneIds, reserveMilestoneId, claimReservedId, getReservedMilestoneIds, clearReservedMilestoneIds, } from "./milestone-ids.js";
@@ -236,7 +236,8 @@ async function dispatchWorkflow(pi, note, customType = "gsd-run", ctx, unitType)
236
236
  // tier downgrade, fallback chains) — same path as auto-mode dispatches (#2958).
237
237
  if (ctx && unitType) {
238
238
  const prefs = loadEffectiveGSDPreferences()?.preferences;
239
- const result = await selectAndApplyModel(ctx, pi, unitType, /* unitId */ "", /* basePath */ process.cwd(), prefs, /* verbose */ false, /* autoModeStartModel */ null);
239
+ const result = await selectAndApplyModel(ctx, pi, unitType, /* unitId */ "", /* basePath */ process.cwd(), prefs, /* verbose */ false, /* autoModeStartModel */ null,
240
+ /* retryContext */ undefined, /* isAutoMode */ false);
240
241
  if (result.appliedModel) {
241
242
  debugLog("guided-flow-model-applied", {
242
243
  unitType,
@@ -294,6 +295,16 @@ async function dispatchWorkflow(pi, note, customType = "gsd-run", ctx, unitType)
294
295
  pi.setActiveTools(savedTools);
295
296
  }
296
297
  }
298
+ function getStructuredQuestionsAvailability(pi, ctx) {
299
+ if (!ctx)
300
+ return "false";
301
+ const provider = ctx.model?.provider;
302
+ const authMode = provider ? ctx.modelRegistry.getProviderAuthMode(provider) : undefined;
303
+ return supportsStructuredQuestions(pi.getActiveTools(), {
304
+ authMode,
305
+ baseUrl: ctx.model?.baseUrl,
306
+ }) ? "true" : "false";
307
+ }
297
308
  /**
298
309
  * Resolve a model ID string to a model object from available models.
299
310
  * Handles "provider/model" and bare ID formats.
@@ -600,7 +611,7 @@ export async function showDiscuss(ctx, pi, basePath) {
600
611
  });
601
612
  if (choice === "discuss_draft") {
602
613
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
603
- const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
614
+ const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
604
615
  const basePrompt = loadPrompt("guided-discuss-milestone", {
605
616
  milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
606
617
  commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
@@ -614,7 +625,7 @@ export async function showDiscuss(ctx, pi, basePath) {
614
625
  }
615
626
  else if (choice === "discuss_fresh") {
616
627
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
617
- const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
628
+ const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
618
629
  pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: mid, step: false, createdAt: Date.now() });
619
630
  await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
620
631
  milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
@@ -757,7 +768,7 @@ export async function showDiscuss(ctx, pi, basePath) {
757
768
  if (confirm !== "rediscuss")
758
769
  continue;
759
770
  }
760
- const sqAvail = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
771
+ const sqAvail = getStructuredQuestionsAvailability(pi, ctx);
761
772
  const prompt = await buildDiscussSlicePrompt(mid, chosen.id, chosen.title, basePath, { rediscuss: isRediscuss, structuredQuestionsAvailable: sqAvail });
762
773
  await dispatchWorkflow(pi, prompt, "gsd-discuss", ctx, "discuss-slice");
763
774
  // Wait for the discuss session to finish, then loop back to the picker
@@ -848,7 +859,7 @@ async function dispatchDiscussForMilestone(ctx, pi, basePath, mid, milestoneTitl
848
859
  ].join("\n")
849
860
  : "";
850
861
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
851
- const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
862
+ const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
852
863
  const basePrompt = loadPrompt("guided-discuss-milestone", {
853
864
  milestoneId: mid,
854
865
  milestoneTitle,
@@ -1237,7 +1248,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1237
1248
  });
1238
1249
  if (choice === "discuss_draft") {
1239
1250
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
1240
- const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
1251
+ const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
1241
1252
  const basePrompt = loadPrompt("guided-discuss-milestone", {
1242
1253
  milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
1243
1254
  commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
@@ -1251,7 +1262,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1251
1262
  }
1252
1263
  else if (choice === "discuss_fresh") {
1253
1264
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
1254
- const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
1265
+ const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
1255
1266
  pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
1256
1267
  await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
1257
1268
  milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
@@ -1342,7 +1353,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1342
1353
  }
1343
1354
  else if (choice === "discuss") {
1344
1355
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
1345
- const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
1356
+ const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
1346
1357
  await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
1347
1358
  milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
1348
1359
  commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
@@ -1480,7 +1491,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1480
1491
  }), "gsd-run", ctx, "plan-slice");
1481
1492
  }
1482
1493
  else if (choice === "discuss") {
1483
- const sqAvail = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
1494
+ const sqAvail = getStructuredQuestionsAvailability(pi, ctx);
1484
1495
  await dispatchWorkflow(pi, await buildDiscussSlicePrompt(milestoneId, sliceId, sliceTitle, basePath, { rediscuss: hasContext, structuredQuestionsAvailable: sqAvail }), "gsd-run", ctx, "discuss-slice");
1485
1496
  }
1486
1497
  else if (choice === "research") {