gsd-pi 2.71.0-dev.977c553 → 2.71.0-dev.e17e0ce

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 (258) hide show
  1. package/dist/cli.js +12 -3
  2. package/dist/headless-events.d.ts +2 -0
  3. package/dist/headless-events.js +7 -0
  4. package/dist/headless.js +16 -3
  5. package/dist/mcp-server.js +6 -6
  6. package/dist/provider-migrations.d.ts +10 -0
  7. package/dist/provider-migrations.js +12 -0
  8. package/dist/resource-loader.js +139 -13
  9. package/dist/resources/GSD-WORKFLOW.md +1 -1
  10. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +10 -4
  11. package/dist/resources/extensions/gsd/auto/infra-errors.js +34 -0
  12. package/dist/resources/extensions/gsd/auto/loop.js +32 -1
  13. package/dist/resources/extensions/gsd/auto/phases.js +1 -1
  14. package/dist/resources/extensions/gsd/auto/session.js +11 -0
  15. package/dist/resources/extensions/gsd/auto-dashboard.js +22 -16
  16. package/dist/resources/extensions/gsd/auto-model-selection.js +10 -2
  17. package/dist/resources/extensions/gsd/auto-start.js +13 -6
  18. package/dist/resources/extensions/gsd/auto-tool-tracking.js +1 -1
  19. package/dist/resources/extensions/gsd/auto-worktree.js +1 -1
  20. package/dist/resources/extensions/gsd/auto.js +52 -0
  21. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +2 -0
  22. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +66 -51
  23. package/dist/resources/extensions/gsd/bootstrap/system-context.js +6 -0
  24. package/dist/resources/extensions/gsd/commands/context.js +15 -6
  25. package/dist/resources/extensions/gsd/commands/dispatcher.js +12 -2
  26. package/dist/resources/extensions/gsd/commands/handlers/auto.js +10 -33
  27. package/dist/resources/extensions/gsd/commands/handlers/core.js +56 -11
  28. package/dist/resources/extensions/gsd/commands/handlers/notifications-handler.js +15 -6
  29. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +4 -10
  30. package/dist/resources/extensions/gsd/dashboard-overlay.js +8 -3
  31. package/dist/resources/extensions/gsd/dispatch-guard.js +18 -1
  32. package/dist/resources/extensions/gsd/error-classifier.js +1 -1
  33. package/dist/resources/extensions/gsd/forensics.js +19 -6
  34. package/dist/resources/extensions/gsd/guided-flow.js +5 -10
  35. package/dist/resources/extensions/gsd/metrics.js +1 -0
  36. package/dist/resources/extensions/gsd/milestone-actions.js +10 -4
  37. package/dist/resources/extensions/gsd/notification-overlay.js +20 -5
  38. package/dist/resources/extensions/gsd/notification-store.js +51 -1
  39. package/dist/resources/extensions/gsd/notification-widget.js +5 -13
  40. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +8 -3
  41. package/dist/resources/extensions/gsd/pre-execution-checks.js +35 -2
  42. package/dist/resources/extensions/gsd/prompts/complete-slice.md +2 -2
  43. package/dist/resources/extensions/gsd/prompts/discuss.md +2 -0
  44. package/dist/resources/extensions/gsd/prompts/execute-task.md +20 -19
  45. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  46. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +2 -0
  47. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  48. package/dist/resources/extensions/gsd/prompts/queue.md +3 -2
  49. package/dist/resources/extensions/gsd/prompts/system.md +1 -0
  50. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -1
  51. package/dist/resources/extensions/gsd/session-model-override.js +25 -0
  52. package/dist/resources/extensions/gsd/shortcut-defs.js +34 -0
  53. package/dist/resources/skills/create-skill/SKILL.md +2 -0
  54. package/dist/web/standalone/.next/BUILD_ID +1 -1
  55. package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
  56. package/dist/web/standalone/.next/build-manifest.json +2 -2
  57. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  58. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  59. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  67. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/index.html +1 -1
  75. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app-paths-manifest.json +13 -13
  82. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  83. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  84. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  85. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  86. package/package.json +1 -1
  87. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  88. package/packages/mcp-server/dist/workflow-tools.js +21 -11
  89. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  90. package/packages/mcp-server/src/workflow-tools.test.ts +110 -0
  91. package/packages/mcp-server/src/workflow-tools.ts +31 -11
  92. package/packages/pi-ai/dist/providers/amazon-bedrock.js +11 -2
  93. package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
  94. package/packages/pi-ai/dist/providers/anthropic-auth.test.d.ts +2 -0
  95. package/packages/pi-ai/dist/providers/anthropic-auth.test.d.ts.map +1 -0
  96. package/packages/pi-ai/dist/providers/anthropic-auth.test.js +20 -0
  97. package/packages/pi-ai/dist/providers/anthropic-auth.test.js.map +1 -0
  98. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +4 -1
  99. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  100. package/packages/pi-ai/dist/providers/anthropic-shared.js +8 -3
  101. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  102. package/packages/pi-ai/dist/providers/anthropic-shared.test.js +44 -1
  103. package/packages/pi-ai/dist/providers/anthropic-shared.test.js.map +1 -1
  104. package/packages/pi-ai/dist/providers/anthropic.d.ts +2 -1
  105. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  106. package/packages/pi-ai/dist/providers/anthropic.js +7 -4
  107. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  108. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  109. package/packages/pi-ai/dist/providers/openai-completions.js +11 -0
  110. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  111. package/packages/pi-ai/src/providers/amazon-bedrock.ts +13 -1
  112. package/packages/pi-ai/src/providers/anthropic-auth.test.ts +32 -0
  113. package/packages/pi-ai/src/providers/anthropic-shared.test.ts +55 -1
  114. package/packages/pi-ai/src/providers/anthropic-shared.ts +14 -3
  115. package/packages/pi-ai/src/providers/anthropic.ts +8 -4
  116. package/packages/pi-ai/src/providers/openai-completions.ts +14 -0
  117. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.d.ts +2 -0
  118. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.d.ts.map +1 -0
  119. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.js +61 -0
  120. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.js.map +1 -0
  121. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  122. package/packages/pi-coding-agent/dist/core/agent-session.js +2 -1
  123. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  124. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +10 -0
  125. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  126. package/packages/pi-coding-agent/dist/core/auth-storage.js +27 -0
  127. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  128. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +85 -0
  129. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  130. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.d.ts +2 -0
  131. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.d.ts.map +1 -0
  132. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.js +64 -0
  133. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.js.map +1 -0
  134. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  135. package/packages/pi-coding-agent/dist/core/model-resolver.js +22 -18
  136. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  137. package/packages/pi-coding-agent/dist/core/sdk.d.ts +11 -0
  138. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  139. package/packages/pi-coding-agent/dist/core/sdk.js +38 -5
  140. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  141. package/packages/pi-coding-agent/dist/core/sdk.test.d.ts +2 -0
  142. package/packages/pi-coding-agent/dist/core/sdk.test.d.ts.map +1 -0
  143. package/packages/pi-coding-agent/dist/core/sdk.test.js +71 -0
  144. package/packages/pi-coding-agent/dist/core/sdk.test.js.map +1 -0
  145. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  146. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  147. package/packages/pi-coding-agent/dist/index.js +1 -1
  148. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  149. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.d.ts +2 -0
  150. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.d.ts.map +1 -0
  151. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.js +13 -0
  152. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.js.map +1 -0
  153. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts +4 -0
  154. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  155. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +24 -2
  156. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js.map +1 -1
  157. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +4 -0
  158. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  159. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +43 -0
  160. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  161. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  162. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +7 -2
  163. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  164. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  165. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +4 -3
  166. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  167. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +4 -2
  168. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  169. package/packages/pi-coding-agent/src/core/agent-session-renderable-tools.test.ts +70 -0
  170. package/packages/pi-coding-agent/src/core/agent-session.ts +2 -1
  171. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +108 -0
  172. package/packages/pi-coding-agent/src/core/auth-storage.ts +30 -0
  173. package/packages/pi-coding-agent/src/core/model-resolver-initial-model-auth.test.ts +78 -0
  174. package/packages/pi-coding-agent/src/core/model-resolver.ts +22 -18
  175. package/packages/pi-coding-agent/src/core/sdk.test.ts +89 -0
  176. package/packages/pi-coding-agent/src/core/sdk.ts +45 -9
  177. package/packages/pi-coding-agent/src/index.ts +1 -0
  178. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/login-dialog.test.ts +24 -0
  179. package/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts +30 -2
  180. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +47 -0
  181. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +7 -2
  182. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +4 -3
  183. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +4 -2
  184. package/src/resources/GSD-WORKFLOW.md +1 -1
  185. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +13 -5
  186. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +56 -4
  187. package/src/resources/extensions/gsd/auto/infra-errors.ts +38 -0
  188. package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -0
  189. package/src/resources/extensions/gsd/auto/loop.ts +45 -1
  190. package/src/resources/extensions/gsd/auto/phases.ts +2 -0
  191. package/src/resources/extensions/gsd/auto/session.ts +11 -0
  192. package/src/resources/extensions/gsd/auto-dashboard.ts +29 -18
  193. package/src/resources/extensions/gsd/auto-model-selection.ts +9 -1
  194. package/src/resources/extensions/gsd/auto-start.ts +13 -6
  195. package/src/resources/extensions/gsd/auto-tool-tracking.ts +1 -1
  196. package/src/resources/extensions/gsd/auto-worktree.ts +1 -1
  197. package/src/resources/extensions/gsd/auto.ts +68 -0
  198. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +2 -0
  199. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +82 -60
  200. package/src/resources/extensions/gsd/bootstrap/system-context.ts +7 -0
  201. package/src/resources/extensions/gsd/commands/context.ts +16 -5
  202. package/src/resources/extensions/gsd/commands/dispatcher.ts +14 -2
  203. package/src/resources/extensions/gsd/commands/handlers/auto.ts +10 -36
  204. package/src/resources/extensions/gsd/commands/handlers/core.ts +58 -11
  205. package/src/resources/extensions/gsd/commands/handlers/notifications-handler.ts +17 -7
  206. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +4 -10
  207. package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -3
  208. package/src/resources/extensions/gsd/dispatch-guard.ts +18 -1
  209. package/src/resources/extensions/gsd/error-classifier.ts +1 -1
  210. package/src/resources/extensions/gsd/forensics.ts +23 -7
  211. package/src/resources/extensions/gsd/guided-flow.ts +5 -10
  212. package/src/resources/extensions/gsd/interrupted-session.ts +1 -0
  213. package/src/resources/extensions/gsd/metrics.ts +12 -1
  214. package/src/resources/extensions/gsd/milestone-actions.ts +10 -3
  215. package/src/resources/extensions/gsd/notification-overlay.ts +24 -7
  216. package/src/resources/extensions/gsd/notification-store.ts +49 -1
  217. package/src/resources/extensions/gsd/notification-widget.ts +5 -14
  218. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +10 -3
  219. package/src/resources/extensions/gsd/pre-execution-checks.ts +39 -2
  220. package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -2
  221. package/src/resources/extensions/gsd/prompts/discuss.md +2 -0
  222. package/src/resources/extensions/gsd/prompts/execute-task.md +20 -19
  223. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  224. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +2 -0
  225. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  226. package/src/resources/extensions/gsd/prompts/queue.md +3 -2
  227. package/src/resources/extensions/gsd/prompts/system.md +1 -0
  228. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -1
  229. package/src/resources/extensions/gsd/session-model-override.ts +36 -0
  230. package/src/resources/extensions/gsd/shortcut-defs.ts +49 -0
  231. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +11 -9
  232. package/src/resources/extensions/gsd/tests/auto-start-worktree-db-path.test.ts +28 -0
  233. package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +39 -0
  234. package/src/resources/extensions/gsd/tests/complete-slice-prompt-task-summary-layout.test.ts +18 -0
  235. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +27 -0
  236. package/src/resources/extensions/gsd/tests/execute-task-prompt-existing-artifact-guard.test.ts +33 -0
  237. package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +62 -0
  238. package/src/resources/extensions/gsd/tests/format-shortcut.test.ts +15 -0
  239. package/src/resources/extensions/gsd/tests/gsd-no-project-error.test.ts +73 -0
  240. package/src/resources/extensions/gsd/tests/infra-errors-cooldown.test.ts +180 -0
  241. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +66 -1
  242. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +36 -51
  243. package/src/resources/extensions/gsd/tests/notification-store.test.ts +35 -0
  244. package/src/resources/extensions/gsd/tests/notification-widget.test.ts +26 -0
  245. package/src/resources/extensions/gsd/tests/notifications-handler.test.ts +90 -0
  246. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +1 -0
  247. package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +18 -0
  248. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +49 -0
  249. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +19 -0
  250. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +7 -0
  251. package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +62 -5
  252. package/src/resources/extensions/gsd/tests/session-model-override.test.ts +35 -0
  253. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +90 -0
  254. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +7 -0
  255. package/src/resources/extensions/gsd/tests/validate-milestone-prompt-verification-classes.test.ts +18 -0
  256. package/src/resources/skills/create-skill/SKILL.md +2 -0
  257. /package/dist/web/standalone/.next/static/{4xyaXTn7-shVHaGMcl75o → cYPZv_bAhZk2ms-Pz6vsY}/_buildManifest.js +0 -0
  258. /package/dist/web/standalone/.next/static/{4xyaXTn7-shVHaGMcl75o → cYPZv_bAhZk2ms-Pz6vsY}/_ssgManifest.js +0 -0
package/dist/cli.js CHANGED
@@ -7,6 +7,7 @@ import { ensureManagedTools } from './tool-bootstrap.js';
7
7
  import { loadStoredEnvKeys } from './wizard.js';
8
8
  import { migratePiCredentials } from './pi-migration.js';
9
9
  import { validateConfiguredModel } from './startup-model-validation.js';
10
+ import { shouldMigrateAnthropicToClaudeCode } from './provider-migrations.js';
10
11
  import { shouldRunOnboarding, runOnboarding } from './onboarding.js';
11
12
  import chalk from 'chalk';
12
13
  import { checkForUpdates } from './update-check.js';
@@ -285,7 +286,7 @@ const { resolveModelsJsonPath } = await import('./models-resolver.js');
285
286
  const modelsJsonPath = resolveModelsJsonPath();
286
287
  const modelRegistry = new ModelRegistry(authStorage, modelsJsonPath);
287
288
  markStartup('ModelRegistry');
288
- const settingsManager = SettingsManager.create(agentDir);
289
+ const settingsManager = SettingsManager.create(process.cwd(), agentDir);
289
290
  applySecurityOverrides(settingsManager);
290
291
  markStartup('SettingsManager.create');
291
292
  // Run onboarding wizard on first launch (no LLM provider configured)
@@ -401,7 +402,11 @@ if (isPrintMode) {
401
402
  // Migrate anthropic OAuth users to claude-code provider when CLI is available (#3772).
402
403
  // Anthropic blocks third-party apps from using subscription quotas — routing through
403
404
  // the local claude CLI binary is TOS-compliant.
404
- if (modelRegistry.isProviderRequestReady('claude-code') && settingsManager.getDefaultProvider() === 'anthropic') {
405
+ if (shouldMigrateAnthropicToClaudeCode({
406
+ authStorage,
407
+ isClaudeCodeReady: modelRegistry.isProviderRequestReady('claude-code'),
408
+ defaultProvider: settingsManager.getDefaultProvider(),
409
+ })) {
405
410
  const currentModelId = settingsManager.getDefaultModel();
406
411
  if (currentModelId) {
407
412
  const ccModel = modelRegistry.find('claude-code', currentModelId);
@@ -576,7 +581,11 @@ markStartup('createAgentSession');
576
581
  // Migrate anthropic OAuth users to claude-code provider when CLI is available (#3772).
577
582
  // Anthropic blocks third-party apps from using subscription quotas — routing through
578
583
  // the local claude CLI binary is TOS-compliant.
579
- if (modelRegistry.isProviderRequestReady('claude-code') && settingsManager.getDefaultProvider() === 'anthropic') {
584
+ if (shouldMigrateAnthropicToClaudeCode({
585
+ authStorage,
586
+ isClaudeCodeReady: modelRegistry.isProviderRequestReady('claude-code'),
587
+ defaultProvider: settingsManager.getDefaultProvider(),
588
+ })) {
580
589
  const currentModelId = settingsManager.getDefaultModel();
581
590
  if (currentModelId) {
582
591
  const ccModel = modelRegistry.find('claude-code', currentModelId);
@@ -43,6 +43,8 @@ export declare const NEW_MILESTONE_IDLE_TIMEOUT_MS = 120000;
43
43
  export declare function isTerminalNotification(event: Record<string, unknown>): boolean;
44
44
  export declare function isBlockedNotification(event: Record<string, unknown>): boolean;
45
45
  export declare function isMilestoneReadyNotification(event: Record<string, unknown>): boolean;
46
+ export declare function isInteractiveHeadlessTool(toolName: string | undefined): boolean;
47
+ export declare function shouldArmHeadlessIdleTimeout(toolCallCount: number, interactiveToolCount: number): boolean;
46
48
  export declare const FIRE_AND_FORGET_METHODS: Set<string>;
47
49
  export declare const QUICK_COMMANDS: Set<string>;
48
50
  export declare function isQuickCommand(command: string): boolean;
@@ -65,6 +65,7 @@ export const IDLE_TIMEOUT_MS = 15_000;
65
65
  // between tool calls (e.g. after mkdir, before writing files). Use a
66
66
  // longer idle timeout to avoid killing the session prematurely (#808).
67
67
  export const NEW_MILESTONE_IDLE_TIMEOUT_MS = 120_000;
68
+ const INTERACTIVE_HEADLESS_TOOLS = new Set(['ask_user_questions', 'secure_env_collect']);
68
69
  export function isTerminalNotification(event) {
69
70
  if (event.type !== 'extension_ui_request' || event.method !== 'notify')
70
71
  return false;
@@ -83,6 +84,12 @@ export function isMilestoneReadyNotification(event) {
83
84
  return false;
84
85
  return /milestone\s+m\d+.*ready/i.test(String(event.message ?? ''));
85
86
  }
87
+ export function isInteractiveHeadlessTool(toolName) {
88
+ return INTERACTIVE_HEADLESS_TOOLS.has(String(toolName ?? ''));
89
+ }
90
+ export function shouldArmHeadlessIdleTimeout(toolCallCount, interactiveToolCount) {
91
+ return toolCallCount > 0 && interactiveToolCount === 0;
92
+ }
86
93
  // ---------------------------------------------------------------------------
87
94
  // Quick Command Detection
88
95
  // ---------------------------------------------------------------------------
package/dist/headless.js CHANGED
@@ -17,7 +17,7 @@ import { resolve } from 'node:path';
17
17
  import { RpcClient, SessionManager } from '@gsd/pi-coding-agent';
18
18
  import { getProjectSessionsDir } from './project-sessions.js';
19
19
  import { loadAndValidateAnswerFile, AnswerInjector } from './headless-answers.js';
20
- import { isTerminalNotification, isBlockedNotification, isMilestoneReadyNotification, isQuickCommand, FIRE_AND_FORGET_METHODS, IDLE_TIMEOUT_MS, NEW_MILESTONE_IDLE_TIMEOUT_MS, EXIT_SUCCESS, EXIT_ERROR, EXIT_BLOCKED, EXIT_CANCELLED, mapStatusToExitCode, } from './headless-events.js';
20
+ import { isTerminalNotification, isBlockedNotification, isMilestoneReadyNotification, isQuickCommand, FIRE_AND_FORGET_METHODS, IDLE_TIMEOUT_MS, NEW_MILESTONE_IDLE_TIMEOUT_MS, isInteractiveHeadlessTool, shouldArmHeadlessIdleTimeout, EXIT_SUCCESS, EXIT_ERROR, EXIT_BLOCKED, EXIT_CANCELLED, mapStatusToExitCode, } from './headless-events.js';
21
21
  import { VALID_OUTPUT_FORMATS } from './headless-types.js';
22
22
  import { handleExtensionUIRequest, formatProgress, formatThinkingLine, formatTextStart, formatTextEnd, formatThinkingStart, formatThinkingEnd, startSupervisedStdinReader, } from './headless-ui.js';
23
23
  import { loadContext, bootstrapGsdProject, } from './headless-context.js';
@@ -282,6 +282,7 @@ async function runHeadlessOnce(options, restartCount) {
282
282
  let exitCode = 0;
283
283
  let milestoneReady = false; // tracks "Milestone X ready." for auto-chaining
284
284
  const recentEvents = [];
285
+ const interactiveToolCallIds = new Set();
285
286
  // JSON batch mode: cost aggregation (cumulative-max pattern per K004)
286
287
  let cumulativeCostUsd = 0;
287
288
  let cumulativeInputTokens = 0;
@@ -365,7 +366,7 @@ async function runHeadlessOnce(options, restartCount) {
365
366
  function resetIdleTimer() {
366
367
  if (idleTimer)
367
368
  clearTimeout(idleTimer);
368
- if (toolCallCount > 0) {
369
+ if (shouldArmHeadlessIdleTimeout(toolCallCount, interactiveToolCallIds.size)) {
369
370
  idleTimer = setTimeout(() => {
370
371
  completed = true;
371
372
  resolveCompletion();
@@ -386,13 +387,25 @@ async function runHeadlessOnce(options, restartCount) {
386
387
  client.onEvent((event) => {
387
388
  const eventObj = event;
388
389
  trackEvent(eventObj);
390
+ const eventType = String(eventObj.type ?? '');
391
+ if (eventType === 'tool_execution_start') {
392
+ const toolCallId = String(eventObj.toolCallId ?? eventObj.id ?? '');
393
+ if (toolCallId && isInteractiveHeadlessTool(String(eventObj.toolName ?? ''))) {
394
+ interactiveToolCallIds.add(toolCallId);
395
+ }
396
+ }
397
+ else if (eventType === 'tool_execution_end') {
398
+ const toolCallId = String(eventObj.toolCallId ?? eventObj.id ?? '');
399
+ if (toolCallId) {
400
+ interactiveToolCallIds.delete(toolCallId);
401
+ }
402
+ }
389
403
  resetIdleTimer();
390
404
  // Answer injector: observe events for question metadata
391
405
  injector?.observeEvent(eventObj);
392
406
  // --json / --output-format stream-json: forward events as JSONL to stdout (filtered if --events)
393
407
  // --output-format json (batch mode): suppress streaming, track cost for final result
394
408
  if (options.json && options.outputFormat === 'stream-json') {
395
- const eventType = String(eventObj.type ?? '');
396
409
  if (!options.eventFilter || options.eventFilter.has(eventType)) {
397
410
  process.stdout.write(JSON.stringify(eventObj) + '\n');
398
411
  }
@@ -1,10 +1,10 @@
1
1
  // MCP SDK subpath imports use wildcard exports (./*) that NodeNext resolves
2
2
  // at runtime but TypeScript cannot statically type-check. We construct the
3
3
  // specifiers dynamically so tsc treats them as `any`.
4
- // Use createRequire to resolve wildcard subpaths — CJS resolver auto-appends
5
- // .js, which the ESM wildcard export map does not (#3603).
6
- import { createRequire } from 'node:module';
7
- const _require = createRequire(import.meta.url);
4
+ //
5
+ // Use explicit .js subpaths for modules that are loaded dynamically at runtime.
6
+ // Recent Node / SDK combinations do not reliably resolve the extensionless
7
+ // wildcard targets for `server/stdio` and `types` (#3914).
8
8
  const MCP_PKG = '@modelcontextprotocol/sdk';
9
9
  /**
10
10
  * Starts a native MCP (Model Context Protocol) server over stdin/stdout.
@@ -23,8 +23,8 @@ const MCP_PKG = '@modelcontextprotocol/sdk';
23
23
  export async function startMcpServer(options) {
24
24
  const { tools, version = '0.0.0' } = options;
25
25
  const serverMod = await import(`${MCP_PKG}/server`);
26
- const stdioMod = await import(_require.resolve(`${MCP_PKG}/server/stdio`));
27
- const typesMod = await import(_require.resolve(`${MCP_PKG}/types`));
26
+ const stdioMod = await import(`${MCP_PKG}/server/stdio.js`);
27
+ const typesMod = await import(`${MCP_PKG}/types.js`);
28
28
  const Server = serverMod.Server;
29
29
  const StdioServerTransport = stdioMod.StdioServerTransport;
30
30
  const { ListToolsRequestSchema, CallToolRequestSchema } = typesMod;
@@ -0,0 +1,10 @@
1
+ import type { AuthStorage } from "@gsd/pi-coding-agent";
2
+ type AnthropicMigrationDeps = {
3
+ authStorage: Pick<AuthStorage, "getCredentialsForProvider">;
4
+ isClaudeCodeReady: boolean;
5
+ defaultProvider: string | undefined;
6
+ env?: NodeJS.ProcessEnv;
7
+ };
8
+ export declare function hasDirectAnthropicApiKey(authStorage: Pick<AuthStorage, "getCredentialsForProvider">, env?: NodeJS.ProcessEnv): boolean;
9
+ export declare function shouldMigrateAnthropicToClaudeCode({ authStorage, isClaudeCodeReady, defaultProvider, env, }: AnthropicMigrationDeps): boolean;
10
+ export {};
@@ -0,0 +1,12 @@
1
+ export function hasDirectAnthropicApiKey(authStorage, env = process.env) {
2
+ if ((env.ANTHROPIC_API_KEY ?? "").trim()) {
3
+ return true;
4
+ }
5
+ return authStorage.getCredentialsForProvider("anthropic").some((credential) => credential?.type === "api_key" && typeof credential?.key === "string" && credential.key.trim().length > 0);
6
+ }
7
+ export function shouldMigrateAnthropicToClaudeCode({ authStorage, isClaudeCodeReady, defaultProvider, env = process.env, }) {
8
+ if (!isClaudeCodeReady || defaultProvider !== "anthropic") {
9
+ return false;
10
+ }
11
+ return !hasDirectAnthropicApiKey(authStorage, env);
12
+ }
@@ -2,7 +2,7 @@ import { DefaultResourceLoader, sortExtensionPaths } from '@gsd/pi-coding-agent'
2
2
  import { createHash } from 'node:crypto';
3
3
  import { homedir } from 'node:os';
4
4
  import { chmodSync, copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, openSync, closeSync, readFileSync, readlinkSync, readdirSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from 'node:fs';
5
- import { dirname, join, relative, resolve } from 'node:path';
5
+ import { basename, dirname, join, relative, resolve } from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { compareSemver } from './update-check.js';
8
8
  import { discoverExtensionEntryPaths } from './extension-discovery.js';
@@ -254,34 +254,160 @@ function copyDirRecursive(src, dest) {
254
254
  * ~/.gsd/agent/extensions/ have no ancestor node_modules, so imports of
255
255
  * @gsd/* packages fail. The symlink makes Node's standard resolution find
256
256
  * them without requiring every call site to use jiti.
257
+ *
258
+ * Layout differences by install method:
259
+ * - Source/monorepo: packageRoot/node_modules has everything → simple symlink
260
+ * - npm/bun global: deps hoisted to dirname(packageRoot), including @gsd/* → simple symlink
261
+ * - pnpm global: external deps hoisted, but @gsd/* stays in packageRoot/node_modules
262
+ * → merged directory with symlinks from both roots (#3529, #3564)
257
263
  */
258
264
  function ensureNodeModulesSymlink(agentDir) {
259
265
  const agentNodeModules = join(agentDir, 'node_modules');
260
- const gsdNodeModules = join(packageRoot, 'node_modules');
266
+ const internalNodeModules = join(packageRoot, 'node_modules');
267
+ const hoistedNodeModules = dirname(packageRoot);
268
+ const isGlobalInstall = basename(hoistedNodeModules) === 'node_modules';
269
+ if (!isGlobalInstall) {
270
+ // Source/monorepo: internal node_modules has everything
271
+ reconcileSymlink(agentNodeModules, internalNodeModules);
272
+ return;
273
+ }
274
+ // Global install: check if workspace scopes (@gsd/*) are hoisted.
275
+ // npm/bun hoist everything; pnpm keeps workspace packages internal.
276
+ if (!hasMissingWorkspaceScopes(hoistedNodeModules, internalNodeModules)) {
277
+ // Everything is hoisted — simple symlink to parent node_modules
278
+ reconcileSymlink(agentNodeModules, hoistedNodeModules);
279
+ return;
280
+ }
281
+ // pnpm-style layout: create a real directory merging both roots
282
+ reconcileMergedNodeModules(agentNodeModules, hoistedNodeModules, internalNodeModules);
283
+ }
284
+ /** Check if any @gsd* scopes exist in internal but not in hoisted node_modules */
285
+ function hasMissingWorkspaceScopes(hoisted, internal) {
286
+ if (!existsSync(internal))
287
+ return false;
261
288
  try {
262
- const stat = lstatSync(agentNodeModules);
289
+ for (const entry of readdirSync(internal, { withFileTypes: true })) {
290
+ if (entry.isDirectory() && entry.name.startsWith('@gsd') &&
291
+ !existsSync(join(hoisted, entry.name))) {
292
+ return true;
293
+ }
294
+ }
295
+ }
296
+ catch { /* non-fatal */ }
297
+ return false;
298
+ }
299
+ /** Ensure a symlink at `link` points to `target`, fixing stale/wrong entries */
300
+ function reconcileSymlink(link, target) {
301
+ try {
302
+ const stat = lstatSync(link);
263
303
  if (stat.isSymbolicLink()) {
264
- const existing = readlinkSync(agentNodeModules);
265
- // Symlink exists verify it points to the correct, existing target
266
- if (existing === gsdNodeModules && existsSync(agentNodeModules))
304
+ const existing = readlinkSync(link);
305
+ if (existing === target && existsSync(link))
267
306
  return; // correct and target exists
268
- // Stale or wrong target — remove and recreate
307
+ unlinkSync(link);
308
+ }
309
+ else {
310
+ // Real directory (or merged dir from previous pnpm fix) — remove it
311
+ rmSync(link, { recursive: true, force: true });
312
+ }
313
+ }
314
+ catch {
315
+ // lstatSync throws if path doesn't exist — fine, we'll create below
316
+ }
317
+ try {
318
+ symlinkSync(target, link, 'junction');
319
+ }
320
+ catch (err) {
321
+ console.error(`[gsd] WARN: Failed to symlink ${link} → ${target}: ${err instanceof Error ? err.message : err}`);
322
+ }
323
+ }
324
+ /**
325
+ * Create a real node_modules directory containing symlinks from both the
326
+ * hoisted root (external deps) and internal root (@gsd/* workspace packages).
327
+ * Used for pnpm global installs where @gsd/* isn't hoisted.
328
+ */
329
+ function reconcileMergedNodeModules(agentNodeModules, hoisted, internal) {
330
+ // Fast path: if already merged for this packageRoot + same directory contents, skip.
331
+ // The fingerprint includes entry names from both roots so `pnpm add/remove` triggers rebuild.
332
+ const marker = join(agentNodeModules, '.gsd-merged');
333
+ const fingerprint = mergedFingerprint(hoisted, internal);
334
+ try {
335
+ if (existsSync(marker) && readFileSync(marker, 'utf-8').trim() === fingerprint)
336
+ return;
337
+ }
338
+ catch { /* rebuild */ }
339
+ // Remove any existing symlink or stale merged directory
340
+ try {
341
+ const stat = lstatSync(agentNodeModules);
342
+ if (stat.isSymbolicLink()) {
269
343
  unlinkSync(agentNodeModules);
270
344
  }
271
345
  else {
272
- // Real directory (not a symlink) is blocking — remove it
273
346
  rmSync(agentNodeModules, { recursive: true, force: true });
274
347
  }
275
348
  }
276
- catch {
277
- // lstatSync throws if path doesn't exist — that's fine, we'll create below
349
+ catch { /* doesn't exist */ }
350
+ mkdirSync(agentNodeModules, { recursive: true });
351
+ let linkedCount = 0;
352
+ // Symlink entries from the hoisted node_modules (external deps)
353
+ try {
354
+ for (const entry of readdirSync(hoisted, { withFileTypes: true })) {
355
+ // Skip the gsd-pi package itself and dotfiles
356
+ if (entry.name === basename(packageRoot))
357
+ continue;
358
+ if (entry.name.startsWith('.'))
359
+ continue;
360
+ try {
361
+ symlinkSync(join(hoisted, entry.name), join(agentNodeModules, entry.name));
362
+ linkedCount++;
363
+ }
364
+ catch { /* skip individual */ }
365
+ }
278
366
  }
367
+ catch (err) {
368
+ console.error(`[gsd] WARN: Failed to read hoisted node_modules at ${hoisted}: ${err instanceof Error ? err.message : err}`);
369
+ }
370
+ // Overlay internal node_modules entries that weren't hoisted.
371
+ // This covers @gsd/* workspace packages AND optional deps like
372
+ // @anthropic-ai/claude-agent-sdk that npm keeps internal.
279
373
  try {
280
- symlinkSync(gsdNodeModules, agentNodeModules, 'junction');
374
+ for (const entry of readdirSync(internal, { withFileTypes: true })) {
375
+ if (entry.name.startsWith('.'))
376
+ continue;
377
+ const link = join(agentNodeModules, entry.name);
378
+ // Replace hoisted symlink with internal version (internal takes precedence)
379
+ try {
380
+ lstatSync(link);
381
+ unlinkSync(link);
382
+ }
383
+ catch { /* didn't exist — will create below */ }
384
+ try {
385
+ symlinkSync(join(internal, entry.name), link);
386
+ linkedCount++;
387
+ }
388
+ catch { /* skip individual */ }
389
+ }
281
390
  }
282
391
  catch (err) {
283
- // This failure makes GSD non-functional extensions can't resolve @gsd/* packages
284
- console.error(`[gsd] WARN: Failed to symlink ${agentNodeModules} → ${gsdNodeModules}: ${err instanceof Error ? err.message : err}`);
392
+ console.error(`[gsd] WARN: Failed to read internal node_modules at ${internal}: ${err instanceof Error ? err.message : err}`);
393
+ }
394
+ // Only stamp marker if we actually linked something — avoids caching a broken state
395
+ if (linkedCount > 0) {
396
+ try {
397
+ writeFileSync(marker, fingerprint);
398
+ }
399
+ catch { /* non-fatal */ }
400
+ }
401
+ }
402
+ /** Build a cache fingerprint from packageRoot + sorted entry names of both directories */
403
+ function mergedFingerprint(hoisted, internal) {
404
+ try {
405
+ const h = readdirSync(hoisted).sort().join(',');
406
+ const i = readdirSync(internal).sort().join(',');
407
+ return `${packageRoot}\n${h}\n${i}`;
408
+ }
409
+ catch {
410
+ return packageRoot; // fallback: at least invalidate on version change
285
411
  }
286
412
  }
287
413
  /**
@@ -275,7 +275,7 @@ Work flows through these phases. Each phase produces a file.
275
275
  **How to do it manually:**
276
276
  1. Read the roadmap to understand the scope.
277
277
  2. Identify 3-5 gray areas — implementation decisions the user cares about.
278
- 3. Use `ask_user_questions` to discuss each area.
278
+ 3. Use `ask_user_questions` to discuss each area, one round at a time. Never fabricate user input; wait for the user's actual response before the next round.
279
279
  4. Write decisions to the appropriate context file (`M###-CONTEXT.md` or `S##-CONTEXT.md`).
280
280
  5. Do NOT discuss how to implement — only what the user wants.
281
281
 
@@ -29,6 +29,15 @@ function createAssistantStream() {
29
29
  throw new Error("Unexpected event type for final result");
30
30
  });
31
31
  }
32
+ export function getResultErrorMessage(result) {
33
+ if ("errors" in result && Array.isArray(result.errors) && result.errors.length > 0) {
34
+ return result.errors.join("; ");
35
+ }
36
+ if ("result" in result && typeof result.result === "string" && result.result.trim().length > 0) {
37
+ return result.result.trim();
38
+ }
39
+ return result.subtype === "success" ? "claude_code_request_failed" : result.subtype;
40
+ }
32
41
  // ---------------------------------------------------------------------------
33
42
  // Claude binary resolution
34
43
  // ---------------------------------------------------------------------------
@@ -666,10 +675,7 @@ async function pumpSdkMessages(model, context, options, stream) {
666
675
  timestamp: Date.now(),
667
676
  };
668
677
  if (result.is_error) {
669
- const errText = "errors" in result
670
- ? result.errors?.join("; ")
671
- : result.subtype;
672
- finalMessage.errorMessage = errText;
678
+ finalMessage.errorMessage = getResultErrorMessage(result);
673
679
  stream.push({ type: "error", reason: "error", error: finalMessage });
674
680
  }
675
681
  else {
@@ -47,3 +47,37 @@ export function isInfrastructureError(err) {
47
47
  return "SQLITE_CORRUPT";
48
48
  return null;
49
49
  }
50
+ /**
51
+ * Default wait duration when a cooldown error is detected but no specific
52
+ * expiry is available from AuthStorage (e.g., error propagated across
53
+ * process boundary without structured backoff data).
54
+ */
55
+ export const COOLDOWN_FALLBACK_WAIT_MS = 35_000; // 35s — slightly longer than the 30s rate-limit backoff
56
+ /** Maximum consecutive cooldown retries before the auto-loop gives up. */
57
+ export const MAX_COOLDOWN_RETRIES = 5;
58
+ /**
59
+ * Detect whether an error is a transient credential cooldown that should
60
+ * be waited out rather than counted as a consecutive failure.
61
+ *
62
+ * Prefers the structured `CredentialCooldownError` (code: AUTH_COOLDOWN)
63
+ * thrown by sdk.ts. Falls back to message matching for errors that
64
+ * propagated across process boundaries without the typed class.
65
+ */
66
+ export function isTransientCooldownError(err) {
67
+ if (err && typeof err === "object" && err.code === "AUTH_COOLDOWN") {
68
+ return true;
69
+ }
70
+ // Fallback: message match for cross-process error propagation
71
+ const msg = err instanceof Error ? err.message : String(err);
72
+ return /in a cooldown window/i.test(msg);
73
+ }
74
+ /**
75
+ * Extract retryAfterMs from a CredentialCooldownError, if available.
76
+ * Returns undefined for unstructured errors or when no retry hint exists.
77
+ */
78
+ export function getCooldownRetryAfterMs(err) {
79
+ if (err && typeof err === "object" && err.code === "AUTH_COOLDOWN") {
80
+ return err.retryAfterMs;
81
+ }
82
+ return undefined;
83
+ }
@@ -11,7 +11,7 @@ import { MAX_LOOP_ITERATIONS, } from "./types.js";
11
11
  import { _clearCurrentResolve } from "./resolve.js";
12
12
  import { runPreDispatch, runDispatch, runGuards, runUnitPhase, runFinalize, } from "./phases.js";
13
13
  import { debugLog } from "../debug-logger.js";
14
- import { isInfrastructureError } from "./infra-errors.js";
14
+ import { isInfrastructureError, isTransientCooldownError, getCooldownRetryAfterMs, COOLDOWN_FALLBACK_WAIT_MS, MAX_COOLDOWN_RETRIES } from "./infra-errors.js";
15
15
  import { resolveEngine } from "../engine-resolver.js";
16
16
  /**
17
17
  * Main auto-mode execution loop. Iterates: derive → dispatch → guards →
@@ -26,6 +26,7 @@ export async function autoLoop(ctx, pi, s, deps) {
26
26
  let iteration = 0;
27
27
  const loopState = { recentUnits: [], stuckRecoveryAttempts: 0, consecutiveFinalizeTimeouts: 0 };
28
28
  let consecutiveErrors = 0;
29
+ let consecutiveCooldowns = 0;
29
30
  const recentErrorMessages = [];
30
31
  while (s.active) {
31
32
  iteration++;
@@ -158,6 +159,7 @@ export async function autoLoop(ctx, pi, s, deps) {
158
159
  });
159
160
  deps.clearUnitTimeout();
160
161
  consecutiveErrors = 0;
162
+ consecutiveCooldowns = 0;
161
163
  recentErrorMessages.length = 0;
162
164
  deps.emitJournalEvent({ ts: new Date().toISOString(), flowId, seq: nextSeq(), eventType: "iteration-end", data: { iteration } });
163
165
  debugLog("autoLoop", { phase: "iteration-complete", iteration });
@@ -220,6 +222,7 @@ export async function autoLoop(ctx, pi, s, deps) {
220
222
  if (finalizeResult.action === "continue")
221
223
  continue;
222
224
  consecutiveErrors = 0; // Iteration completed successfully
225
+ consecutiveCooldowns = 0;
223
226
  recentErrorMessages.length = 0;
224
227
  deps.emitJournalEvent({ ts: new Date().toISOString(), flowId, seq: nextSeq(), eventType: "iteration-end", data: { iteration } });
225
228
  debugLog("autoLoop", { phase: "iteration-complete", iteration });
@@ -246,6 +249,34 @@ export async function autoLoop(ctx, pi, s, deps) {
246
249
  await deps.stopAuto(ctx, pi, `Infrastructure error (${infraCode}): not recoverable by retry`);
247
250
  break;
248
251
  }
252
+ // ── Credential cooldown: wait and retry with bounded budget ──
253
+ // A 429 triggers a 30s credential backoff in AuthStorage. If the SDK's
254
+ // getApiKey() retries couldn't outlast the window, the error surfaces
255
+ // here. Wait for the cooldown to clear rather than counting it as a
256
+ // consecutive failure — but cap retries so we don't spin for hours
257
+ // on persistent quota exhaustion.
258
+ if (isTransientCooldownError(loopErr)) {
259
+ consecutiveCooldowns++;
260
+ const retryAfterMs = getCooldownRetryAfterMs(loopErr);
261
+ debugLog("autoLoop", {
262
+ phase: "cooldown-wait",
263
+ iteration,
264
+ consecutiveCooldowns,
265
+ retryAfterMs,
266
+ error: msg,
267
+ });
268
+ if (consecutiveCooldowns > MAX_COOLDOWN_RETRIES) {
269
+ ctx.ui.notify(`Auto-mode stopped: ${consecutiveCooldowns} consecutive credential cooldowns — rate limit or quota may be persistently exhausted.`, "error");
270
+ await deps.stopAuto(ctx, pi, `${consecutiveCooldowns} consecutive credential cooldowns exceeded retry budget`);
271
+ break;
272
+ }
273
+ const waitMs = (retryAfterMs !== undefined && retryAfterMs > 0 && retryAfterMs <= 60_000)
274
+ ? retryAfterMs + 500 // Use structured hint + small buffer
275
+ : COOLDOWN_FALLBACK_WAIT_MS;
276
+ ctx.ui.notify(`Credentials in cooldown (${consecutiveCooldowns}/${MAX_COOLDOWN_RETRIES}) — waiting ${Math.round(waitMs / 1000)}s before retrying.`, "warning");
277
+ await new Promise(resolve => setTimeout(resolve, waitMs));
278
+ continue; // Retry iteration without incrementing consecutiveErrors
279
+ }
249
280
  consecutiveErrors++;
250
281
  recentErrorMessages.push(msg.length > 120 ? msg.slice(0, 120) + "..." : msg);
251
282
  debugLog("autoLoop", {
@@ -857,7 +857,7 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
857
857
  logWarning("engine", "Prompt reorder failed", { error: msg });
858
858
  }
859
859
  // Select and apply model (with tier escalation on retry — normal units only)
860
- const modelResult = await deps.selectAndApplyModel(ctx, pi, unitType, unitId, s.basePath, prefs, s.verbose, s.autoModeStartModel, sidecarItem ? undefined : { isRetry, previousTier });
860
+ const modelResult = await deps.selectAndApplyModel(ctx, pi, unitType, unitId, s.basePath, prefs, s.verbose, s.autoModeStartModel, sidecarItem ? undefined : { isRetry, previousTier }, undefined, s.manualSessionModelOverride);
861
861
  s.currentUnitRouting =
862
862
  modelResult.routing;
863
863
  s.currentUnitModel =
@@ -36,6 +36,10 @@ export class AutoSession {
36
36
  previousProjectRootEnv = null;
37
37
  hadProjectRootEnv = false;
38
38
  projectRootEnvCaptured = false;
39
+ previousMilestoneLockEnv = null;
40
+ hadMilestoneLockEnv = false;
41
+ milestoneLockEnvCaptured = false;
42
+ sessionMilestoneLock = null;
39
43
  gitService = null;
40
44
  // ── Dispatch counters ────────────────────────────────────────────────────
41
45
  unitDispatchCount = new Map();
@@ -52,6 +56,8 @@ export class AutoSession {
52
56
  currentMilestoneId = null;
53
57
  // ── Model state ──────────────────────────────────────────────────────────
54
58
  autoModeStartModel = null;
59
+ /** Explicit /gsd model pin captured at bootstrap (session-scoped policy override). */
60
+ manualSessionModelOverride = null;
55
61
  currentUnitModel = null;
56
62
  /** Fully-qualified model ID (provider/id) set after selectAndApplyModel + hook overrides (#2899). */
57
63
  currentDispatchedModelId = null;
@@ -140,6 +146,10 @@ export class AutoSession {
140
146
  this.previousProjectRootEnv = null;
141
147
  this.hadProjectRootEnv = false;
142
148
  this.projectRootEnvCaptured = false;
149
+ this.previousMilestoneLockEnv = null;
150
+ this.hadMilestoneLockEnv = false;
151
+ this.milestoneLockEnvCaptured = false;
152
+ this.sessionMilestoneLock = null;
143
153
  this.gitService = null;
144
154
  // Dispatch
145
155
  this.unitDispatchCount.clear();
@@ -151,6 +161,7 @@ export class AutoSession {
151
161
  this.currentMilestoneId = null;
152
162
  // Model
153
163
  this.autoModeStartModel = null;
164
+ this.manualSessionModelOverride = null;
154
165
  this.currentUnitModel = null;
155
166
  this.currentDispatchedModelId = null;
156
167
  this.originalModelId = null;
@@ -10,7 +10,6 @@ import { getActiveHook } from "./post-unit-hooks.js";
10
10
  import { getLedger, getProjectTotals } from "./metrics.js";
11
11
  import { getErrorMessage } from "./error-utils.js";
12
12
  import { isDbAvailable, getMilestoneSlices, getSliceTasks } from "./gsd-db.js";
13
- import { formatShortcut } from "./files.js";
14
13
  import { readFileSync, writeFileSync, existsSync } from "node:fs";
15
14
  import { execFileSync } from "node:child_process";
16
15
  import { truncateToWidth, visibleWidth } from "@gsd/pi-tui";
@@ -23,6 +22,7 @@ import { resolveServiceTierIcon, getEffectiveServiceTier } from "./service-tier.
23
22
  import { parseUnitId } from "./unit-id.js";
24
23
  import { formatRtkSavingsLabel, getRtkSessionSavings, } from "../shared/rtk-session-stats.js";
25
24
  import { logWarning } from "./workflow-logger.js";
25
+ import { formattedShortcutPair } from "./shortcut-defs.js";
26
26
  // ─── UAT Slice Extraction ─────────────────────────────────────────────────────
27
27
  /**
28
28
  * Extract the target slice ID from a run-uat unit ID (e.g. "M001/S01" → "S01").
@@ -282,12 +282,23 @@ function getLastCommit(basePath) {
282
282
  }
283
283
  // ─── Footer Factory ───────────────────────────────────────────────────────────
284
284
  /**
285
- * Footer factory that renders zero lines — hides the built-in footer entirely.
286
- * All footer info (pwd, branch, tokens, cost, model) is shown inside the
287
- * progress widget instead, so there's no gap or redundancy.
285
+ * Footer factory used by auto-mode.
286
+ * Keep footer minimal but preserve extension status context from setStatus().
288
287
  */
289
- export const hideFooter = () => ({
290
- render(_width) { return []; },
288
+ function sanitizeFooterStatus(text) {
289
+ return text.replace(/\s+/g, " ").trim();
290
+ }
291
+ export const hideFooter = (_tui, theme, footerData) => ({
292
+ render(width) {
293
+ const extensionStatuses = footerData.getExtensionStatuses();
294
+ if (extensionStatuses.size === 0)
295
+ return [];
296
+ const statusLine = Array.from(extensionStatuses.entries())
297
+ .sort(([a], [b]) => a.localeCompare(b))
298
+ .map(([, text]) => sanitizeFooterStatus(text))
299
+ .join(" ");
300
+ return [truncateToWidth(theme.fg("dim", statusLine), width, theme.fg("dim", "..."))];
301
+ },
291
302
  invalidate() { },
292
303
  dispose() { },
293
304
  });
@@ -522,13 +533,6 @@ export function updateProgressWidget(ctx, unitType, unitId, state, accessors, ti
522
533
  : theme.fg("dim", elapsed))
523
534
  : "";
524
535
  lines.push(rightAlign(headerLeft, headerRight, width));
525
- // Worktree/branch right-aligned below header
526
- const branchLabel = worktreeName && cachedBranch
527
- ? `${worktreeName} (${cachedBranch})`
528
- : cachedBranch ?? "";
529
- if (branchLabel) {
530
- lines.push(rightAlign("", theme.fg("dim", branchLabel), width));
531
- }
532
536
  // Show health signal details when degraded (yellow/red)
533
537
  if (score.level !== "green" && score.signals.length > 0 && widgetMode !== "min") {
534
538
  // Show up to 3 most relevant signals in compact form
@@ -776,16 +780,18 @@ export function updateProgressWidget(ctx, unitType, unitId, state, accessors, ti
776
780
  // Hints line
777
781
  const hintParts = [];
778
782
  hintParts.push("esc pause");
779
- hintParts.push(`${formatShortcut("Ctrl+Alt+G")} dashboard`);
783
+ hintParts.push(`${formattedShortcutPair("dashboard")} dashboard`);
784
+ hintParts.push(`${formattedShortcutPair("parallel")} parallel`);
780
785
  const hintStr = theme.fg("dim", hintParts.join(" | "));
781
786
  const commitStr = lastCommit
782
787
  ? theme.fg("dim", `${lastCommit.timeAgo} ago: ${commitMsg}`)
783
788
  : "";
789
+ const locationStr = theme.fg("dim", widgetPwd);
784
790
  if (commitStr) {
785
- lines.push(rightAlign(`${pad}${commitStr}`, hintStr, width));
791
+ lines.push(rightAlign(`${pad}${locationStr} · ${commitStr}`, hintStr, width));
786
792
  }
787
793
  else {
788
- lines.push(rightAlign("", hintStr, width));
794
+ lines.push(rightAlign(`${pad}${locationStr}`, hintStr, width));
789
795
  }
790
796
  lines.push(...ui.bar());
791
797
  cachedLines = lines;