gsd-pi 2.71.0 → 2.72.0-dev.593fa74

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 (465) hide show
  1. package/README.md +57 -17
  2. package/dist/cli.js +29 -3
  3. package/dist/headless-events.d.ts +2 -0
  4. package/dist/headless-events.js +7 -0
  5. package/dist/headless.js +16 -3
  6. package/dist/mcp-server.js +40 -17
  7. package/dist/provider-migrations.d.ts +10 -0
  8. package/dist/provider-migrations.js +12 -0
  9. package/dist/resource-loader.js +139 -13
  10. package/dist/resources/GSD-WORKFLOW.md +1 -1
  11. package/dist/resources/agents/debugger.md +58 -0
  12. package/dist/resources/agents/doc-writer.md +43 -0
  13. package/dist/resources/agents/git-ops.md +56 -0
  14. package/dist/resources/agents/javascript-pro.md +46 -271
  15. package/dist/resources/agents/planner.md +55 -0
  16. package/dist/resources/agents/refactorer.md +47 -0
  17. package/dist/resources/agents/reviewer.md +48 -0
  18. package/dist/resources/agents/security.md +59 -0
  19. package/dist/resources/agents/tester.md +50 -0
  20. package/dist/resources/agents/typescript-pro.md +41 -235
  21. package/dist/resources/extensions/claude-code-cli/partial-builder.js +40 -12
  22. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +113 -10
  23. package/dist/resources/extensions/gsd/auto/infra-errors.js +34 -0
  24. package/dist/resources/extensions/gsd/auto/loop.js +32 -1
  25. package/dist/resources/extensions/gsd/auto/phases.js +5 -1
  26. package/dist/resources/extensions/gsd/auto/session.js +11 -0
  27. package/dist/resources/extensions/gsd/auto-dashboard.js +22 -16
  28. package/dist/resources/extensions/gsd/auto-model-selection.js +10 -2
  29. package/dist/resources/extensions/gsd/auto-prompts.js +88 -33
  30. package/dist/resources/extensions/gsd/auto-start.js +34 -7
  31. package/dist/resources/extensions/gsd/auto-tool-tracking.js +1 -1
  32. package/dist/resources/extensions/gsd/auto-worktree.js +1 -1
  33. package/dist/resources/extensions/gsd/auto.js +56 -0
  34. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -3
  35. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +2 -0
  36. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +63 -51
  37. package/dist/resources/extensions/gsd/bootstrap/system-context.js +6 -0
  38. package/dist/resources/extensions/gsd/commands/context.js +15 -6
  39. package/dist/resources/extensions/gsd/commands/dispatcher.js +12 -2
  40. package/dist/resources/extensions/gsd/commands/handlers/auto.js +10 -33
  41. package/dist/resources/extensions/gsd/commands/handlers/core.js +56 -11
  42. package/dist/resources/extensions/gsd/commands/handlers/notifications-handler.js +15 -6
  43. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +4 -10
  44. package/dist/resources/extensions/gsd/dashboard-overlay.js +8 -3
  45. package/dist/resources/extensions/gsd/dispatch-guard.js +18 -1
  46. package/dist/resources/extensions/gsd/doctor-providers.js +23 -0
  47. package/dist/resources/extensions/gsd/error-classifier.js +5 -2
  48. package/dist/resources/extensions/gsd/forensics.js +19 -6
  49. package/dist/resources/extensions/gsd/gate-registry.js +208 -0
  50. package/dist/resources/extensions/gsd/gsd-db.js +41 -0
  51. package/dist/resources/extensions/gsd/guided-flow.js +5 -10
  52. package/dist/resources/extensions/gsd/metrics.js +1 -0
  53. package/dist/resources/extensions/gsd/milestone-actions.js +10 -4
  54. package/dist/resources/extensions/gsd/milestone-validation-gates.js +11 -12
  55. package/dist/resources/extensions/gsd/notification-overlay.js +42 -13
  56. package/dist/resources/extensions/gsd/notification-store.js +56 -5
  57. package/dist/resources/extensions/gsd/notification-widget.js +5 -13
  58. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +8 -3
  59. package/dist/resources/extensions/gsd/pre-execution-checks.js +35 -2
  60. package/dist/resources/extensions/gsd/prompt-validation.js +126 -0
  61. package/dist/resources/extensions/gsd/prompts/complete-slice.md +5 -3
  62. package/dist/resources/extensions/gsd/prompts/discuss.md +2 -0
  63. package/dist/resources/extensions/gsd/prompts/execute-task.md +22 -19
  64. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  65. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +2 -0
  66. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  67. package/dist/resources/extensions/gsd/prompts/queue.md +3 -2
  68. package/dist/resources/extensions/gsd/prompts/system.md +1 -0
  69. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +4 -1
  70. package/dist/resources/extensions/gsd/session-model-override.js +25 -0
  71. package/dist/resources/extensions/gsd/shortcut-defs.js +40 -0
  72. package/dist/resources/extensions/gsd/state.js +9 -2
  73. package/dist/resources/extensions/gsd/tools/complete-slice.js +52 -1
  74. package/dist/resources/extensions/gsd/tools/complete-task.js +51 -1
  75. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +4 -1
  76. package/dist/resources/extensions/ollama/index.js +13 -5
  77. package/dist/resources/extensions/shared/gsd-phase-state.js +35 -0
  78. package/dist/resources/extensions/subagent/agents.js +8 -0
  79. package/dist/resources/extensions/subagent/index.js +17 -0
  80. package/dist/resources/skills/create-skill/SKILL.md +2 -0
  81. package/dist/startup-model-validation.d.ts +0 -1
  82. package/dist/startup-model-validation.js +6 -2
  83. package/dist/web/standalone/.next/BUILD_ID +1 -1
  84. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  85. package/dist/web/standalone/.next/build-manifest.json +3 -3
  86. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  87. package/dist/web/standalone/.next/required-server-files.json +3 -3
  88. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  89. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  90. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  91. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  99. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  100. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  101. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  102. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  103. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  104. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  105. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  106. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  107. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  108. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  115. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  127. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  137. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  143. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  147. package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  155. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  157. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  159. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  161. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  163. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  164. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  166. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  168. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  169. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  170. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  171. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  172. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  173. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  175. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  176. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
  177. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  178. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  179. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  180. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  181. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  182. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +2 -2
  183. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  184. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  185. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  186. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  187. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  188. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  189. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  190. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  191. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  192. package/dist/web/standalone/.next/server/app/index.html +1 -1
  193. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  194. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  195. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  196. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  197. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  198. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  199. package/dist/web/standalone/.next/server/app/page.js +2 -2
  200. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  201. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  202. package/dist/web/standalone/.next/server/chunks/63.js +3 -3
  203. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  204. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  205. package/dist/web/standalone/.next/server/middleware.js +2 -2
  206. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  207. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  208. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  209. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  210. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  211. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  212. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  213. package/dist/web/standalone/.next/static/chunks/app/page-f1e30ab6bb269149.js +1 -0
  214. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  215. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  216. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  217. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  218. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  219. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  220. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  221. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  222. package/dist/web/standalone/server.js +1 -1
  223. package/package.json +1 -1
  224. package/packages/mcp-server/dist/server.d.ts +12 -1
  225. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  226. package/packages/mcp-server/dist/server.js +90 -42
  227. package/packages/mcp-server/dist/server.js.map +1 -1
  228. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  229. package/packages/mcp-server/dist/workflow-tools.js +22 -12
  230. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  231. package/packages/mcp-server/src/server.ts +110 -38
  232. package/packages/mcp-server/src/workflow-tools.test.ts +110 -0
  233. package/packages/mcp-server/src/workflow-tools.ts +32 -12
  234. package/packages/pi-ai/dist/providers/amazon-bedrock.js +11 -2
  235. package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
  236. package/packages/pi-ai/dist/providers/anthropic-auth.test.d.ts +2 -0
  237. package/packages/pi-ai/dist/providers/anthropic-auth.test.d.ts.map +1 -0
  238. package/packages/pi-ai/dist/providers/anthropic-auth.test.js +20 -0
  239. package/packages/pi-ai/dist/providers/anthropic-auth.test.js.map +1 -0
  240. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +4 -1
  241. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  242. package/packages/pi-ai/dist/providers/anthropic-shared.js +8 -3
  243. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  244. package/packages/pi-ai/dist/providers/anthropic-shared.test.js +44 -1
  245. package/packages/pi-ai/dist/providers/anthropic-shared.test.js.map +1 -1
  246. package/packages/pi-ai/dist/providers/anthropic.d.ts +2 -1
  247. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  248. package/packages/pi-ai/dist/providers/anthropic.js +7 -4
  249. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  250. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  251. package/packages/pi-ai/dist/providers/openai-completions.js +11 -0
  252. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  253. package/packages/pi-ai/src/providers/amazon-bedrock.ts +13 -1
  254. package/packages/pi-ai/src/providers/anthropic-auth.test.ts +32 -0
  255. package/packages/pi-ai/src/providers/anthropic-shared.test.ts +55 -1
  256. package/packages/pi-ai/src/providers/anthropic-shared.ts +14 -3
  257. package/packages/pi-ai/src/providers/anthropic.ts +8 -4
  258. package/packages/pi-ai/src/providers/openai-completions.ts +14 -0
  259. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.d.ts +2 -0
  260. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.d.ts.map +1 -0
  261. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.js +61 -0
  262. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.js.map +1 -0
  263. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  264. package/packages/pi-coding-agent/dist/core/agent-session.js +2 -1
  265. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  266. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +10 -0
  267. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  268. package/packages/pi-coding-agent/dist/core/auth-storage.js +27 -0
  269. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  270. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +85 -0
  271. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  272. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.d.ts +2 -0
  273. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.d.ts.map +1 -0
  274. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.js +64 -0
  275. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.js.map +1 -0
  276. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  277. package/packages/pi-coding-agent/dist/core/model-resolver.js +22 -18
  278. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  279. package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts +8 -0
  280. package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts.map +1 -0
  281. package/packages/pi-coding-agent/dist/core/model-resolver.test.js +75 -0
  282. package/packages/pi-coding-agent/dist/core/model-resolver.test.js.map +1 -0
  283. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts +5 -0
  284. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  285. package/packages/pi-coding-agent/dist/core/retry-handler.js +55 -1
  286. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  287. package/packages/pi-coding-agent/dist/core/retry-handler.test.js +57 -0
  288. package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
  289. package/packages/pi-coding-agent/dist/core/sdk.d.ts +11 -0
  290. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  291. package/packages/pi-coding-agent/dist/core/sdk.js +38 -5
  292. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  293. package/packages/pi-coding-agent/dist/core/sdk.test.d.ts +2 -0
  294. package/packages/pi-coding-agent/dist/core/sdk.test.d.ts.map +1 -0
  295. package/packages/pi-coding-agent/dist/core/sdk.test.js +71 -0
  296. package/packages/pi-coding-agent/dist/core/sdk.test.js.map +1 -0
  297. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  298. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  299. package/packages/pi-coding-agent/dist/index.js +1 -1
  300. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  301. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.d.ts +2 -0
  302. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.d.ts.map +1 -0
  303. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.js +13 -0
  304. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.js.map +1 -0
  305. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +36 -0
  306. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  307. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts +4 -0
  308. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  309. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +24 -2
  310. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js.map +1 -1
  311. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  312. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +9 -2
  313. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
  314. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +4 -0
  315. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  316. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +130 -12
  317. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  318. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  319. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +7 -2
  320. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  321. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts.map +1 -1
  322. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js +6 -1
  323. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js.map +1 -1
  324. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  325. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +4 -3
  326. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  327. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +4 -2
  328. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  329. package/packages/pi-coding-agent/package.json +1 -1
  330. package/packages/pi-coding-agent/src/core/agent-session-renderable-tools.test.ts +70 -0
  331. package/packages/pi-coding-agent/src/core/agent-session.ts +2 -1
  332. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +108 -0
  333. package/packages/pi-coding-agent/src/core/auth-storage.ts +30 -0
  334. package/packages/pi-coding-agent/src/core/model-resolver-initial-model-auth.test.ts +78 -0
  335. package/packages/pi-coding-agent/src/core/model-resolver.test.ts +85 -0
  336. package/packages/pi-coding-agent/src/core/model-resolver.ts +22 -18
  337. package/packages/pi-coding-agent/src/core/retry-handler.test.ts +83 -0
  338. package/packages/pi-coding-agent/src/core/retry-handler.ts +60 -1
  339. package/packages/pi-coding-agent/src/core/sdk.test.ts +89 -0
  340. package/packages/pi-coding-agent/src/core/sdk.ts +45 -9
  341. package/packages/pi-coding-agent/src/index.ts +1 -0
  342. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/login-dialog.test.ts +24 -0
  343. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +72 -0
  344. package/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts +30 -2
  345. package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +15 -6
  346. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +131 -12
  347. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +7 -2
  348. package/packages/pi-coding-agent/src/modes/interactive/controllers/model-controller.ts +6 -1
  349. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +4 -3
  350. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +4 -2
  351. package/pkg/package.json +1 -1
  352. package/src/resources/GSD-WORKFLOW.md +1 -1
  353. package/src/resources/agents/debugger.md +58 -0
  354. package/src/resources/agents/doc-writer.md +43 -0
  355. package/src/resources/agents/git-ops.md +56 -0
  356. package/src/resources/agents/javascript-pro.md +46 -271
  357. package/src/resources/agents/planner.md +55 -0
  358. package/src/resources/agents/refactorer.md +47 -0
  359. package/src/resources/agents/reviewer.md +48 -0
  360. package/src/resources/agents/security.md +59 -0
  361. package/src/resources/agents/tester.md +50 -0
  362. package/src/resources/agents/typescript-pro.md +41 -235
  363. package/src/resources/extensions/claude-code-cli/partial-builder.ts +45 -12
  364. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +122 -8
  365. package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +91 -2
  366. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +189 -6
  367. package/src/resources/extensions/gsd/auto/infra-errors.ts +38 -0
  368. package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -0
  369. package/src/resources/extensions/gsd/auto/loop.ts +45 -1
  370. package/src/resources/extensions/gsd/auto/phases.ts +6 -0
  371. package/src/resources/extensions/gsd/auto/session.ts +11 -0
  372. package/src/resources/extensions/gsd/auto-dashboard.ts +29 -18
  373. package/src/resources/extensions/gsd/auto-model-selection.ts +9 -1
  374. package/src/resources/extensions/gsd/auto-prompts.ts +111 -33
  375. package/src/resources/extensions/gsd/auto-start.ts +41 -7
  376. package/src/resources/extensions/gsd/auto-tool-tracking.ts +1 -1
  377. package/src/resources/extensions/gsd/auto-worktree.ts +1 -1
  378. package/src/resources/extensions/gsd/auto.ts +72 -0
  379. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -3
  380. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +2 -0
  381. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +79 -60
  382. package/src/resources/extensions/gsd/bootstrap/system-context.ts +7 -0
  383. package/src/resources/extensions/gsd/commands/context.ts +16 -5
  384. package/src/resources/extensions/gsd/commands/dispatcher.ts +14 -2
  385. package/src/resources/extensions/gsd/commands/handlers/auto.ts +10 -36
  386. package/src/resources/extensions/gsd/commands/handlers/core.ts +58 -11
  387. package/src/resources/extensions/gsd/commands/handlers/notifications-handler.ts +17 -7
  388. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +4 -10
  389. package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -3
  390. package/src/resources/extensions/gsd/dispatch-guard.ts +18 -1
  391. package/src/resources/extensions/gsd/doctor-providers.ts +24 -0
  392. package/src/resources/extensions/gsd/error-classifier.ts +5 -2
  393. package/src/resources/extensions/gsd/forensics.ts +23 -7
  394. package/src/resources/extensions/gsd/gate-registry.ts +251 -0
  395. package/src/resources/extensions/gsd/gsd-db.ts +51 -0
  396. package/src/resources/extensions/gsd/guided-flow.ts +5 -10
  397. package/src/resources/extensions/gsd/interrupted-session.ts +1 -0
  398. package/src/resources/extensions/gsd/metrics.ts +12 -1
  399. package/src/resources/extensions/gsd/milestone-actions.ts +10 -3
  400. package/src/resources/extensions/gsd/milestone-validation-gates.ts +11 -13
  401. package/src/resources/extensions/gsd/notification-overlay.ts +47 -14
  402. package/src/resources/extensions/gsd/notification-store.ts +54 -5
  403. package/src/resources/extensions/gsd/notification-widget.ts +5 -14
  404. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +10 -3
  405. package/src/resources/extensions/gsd/pre-execution-checks.ts +39 -2
  406. package/src/resources/extensions/gsd/prompt-validation.ts +157 -0
  407. package/src/resources/extensions/gsd/prompts/complete-slice.md +5 -3
  408. package/src/resources/extensions/gsd/prompts/discuss.md +2 -0
  409. package/src/resources/extensions/gsd/prompts/execute-task.md +22 -19
  410. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  411. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +2 -0
  412. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  413. package/src/resources/extensions/gsd/prompts/queue.md +3 -2
  414. package/src/resources/extensions/gsd/prompts/system.md +1 -0
  415. package/src/resources/extensions/gsd/prompts/validate-milestone.md +4 -1
  416. package/src/resources/extensions/gsd/session-model-override.ts +36 -0
  417. package/src/resources/extensions/gsd/shortcut-defs.ts +56 -0
  418. package/src/resources/extensions/gsd/state.ts +13 -2
  419. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +25 -9
  420. package/src/resources/extensions/gsd/tests/auto-start-worktree-db-path.test.ts +28 -0
  421. package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +39 -0
  422. package/src/resources/extensions/gsd/tests/complete-slice-gate-closure.test.ts +167 -0
  423. package/src/resources/extensions/gsd/tests/complete-slice-prompt-task-summary-layout.test.ts +18 -0
  424. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +27 -0
  425. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +36 -0
  426. package/src/resources/extensions/gsd/tests/execute-task-prompt-existing-artifact-guard.test.ts +33 -0
  427. package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +62 -0
  428. package/src/resources/extensions/gsd/tests/format-shortcut.test.ts +31 -0
  429. package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +27 -0
  430. package/src/resources/extensions/gsd/tests/gate-registry.test.ts +140 -0
  431. package/src/resources/extensions/gsd/tests/gsd-no-project-error.test.ts +73 -0
  432. package/src/resources/extensions/gsd/tests/infra-errors-cooldown.test.ts +180 -0
  433. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +66 -1
  434. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +36 -51
  435. package/src/resources/extensions/gsd/tests/notification-store.test.ts +35 -0
  436. package/src/resources/extensions/gsd/tests/notification-widget.test.ts +26 -0
  437. package/src/resources/extensions/gsd/tests/notifications-handler.test.ts +90 -0
  438. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +1 -0
  439. package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +18 -0
  440. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +49 -0
  441. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +19 -0
  442. package/src/resources/extensions/gsd/tests/prompt-system-gate-coverage.test.ts +208 -0
  443. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +16 -0
  444. package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +63 -5
  445. package/src/resources/extensions/gsd/tests/session-model-override.test.ts +35 -0
  446. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +90 -0
  447. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +7 -0
  448. package/src/resources/extensions/gsd/tests/validate-milestone-prompt-verification-classes.test.ts +18 -0
  449. package/src/resources/extensions/gsd/tools/complete-slice.ts +63 -0
  450. package/src/resources/extensions/gsd/tools/complete-task.ts +63 -0
  451. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +4 -1
  452. package/src/resources/extensions/gsd/types.ts +26 -0
  453. package/src/resources/extensions/ollama/index.ts +13 -3
  454. package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +28 -0
  455. package/src/resources/extensions/shared/gsd-phase-state.ts +42 -0
  456. package/src/resources/extensions/shared/tests/gsd-phase-state.test.ts +48 -0
  457. package/src/resources/extensions/subagent/agents.ts +10 -0
  458. package/src/resources/extensions/subagent/index.ts +18 -0
  459. package/src/resources/extensions/subagent/tests/agents-conflicts.test.ts +33 -0
  460. package/src/resources/skills/create-skill/SKILL.md +2 -0
  461. package/dist/web/standalone/.next/static/chunks/app/page-7115e62689b5fd84.js +0 -1
  462. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  463. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  464. /package/dist/web/standalone/.next/static/{nPky_WQC28aBD77eZsRAB → h8B07q4xc-ujHRD7esO6O}/_buildManifest.js +0 -0
  465. /package/dist/web/standalone/.next/static/{nPky_WQC28aBD77eZsRAB → h8B07q4xc-ujHRD7esO6O}/_ssgManifest.js +0 -0
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Regression test for the #unconfigured-models fix: findInitialModel() must
3
+ * skip the saved default when its provider has no working auth, rather than
4
+ * returning an unusable model that every selector surface would display as
5
+ * "current".
6
+ */
7
+
8
+ import test from "node:test";
9
+ import assert from "node:assert/strict";
10
+ import { findInitialModel } from "./model-resolver.js";
11
+
12
+ function fakeRegistry(options: {
13
+ models: Array<{ provider: string; id: string }>;
14
+ readyProviders: Set<string>;
15
+ }) {
16
+ const fullModels = options.models.map((m) => ({
17
+ ...m,
18
+ name: m.id,
19
+ api: "anthropic-messages",
20
+ baseUrl: "",
21
+ reasoning: false,
22
+ input: ["text"],
23
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
24
+ contextWindow: 128_000,
25
+ maxTokens: 4096,
26
+ }));
27
+ const available = fullModels.filter((m) => options.readyProviders.has(m.provider));
28
+ return {
29
+ find(provider: string, id: string) {
30
+ return fullModels.find((m) => m.provider === provider && m.id === id);
31
+ },
32
+ getAvailable() {
33
+ return available;
34
+ },
35
+ isProviderRequestReady(provider: string) {
36
+ return options.readyProviders.has(provider);
37
+ },
38
+ };
39
+ }
40
+
41
+ test("findInitialModel skips saved default when provider has no auth", async () => {
42
+ // User saved xai/grok-4 as default, but XAI_API_KEY is unset so xai is
43
+ // in the registry but not ready. Previously findInitialModel() step 3
44
+ // returned xai anyway — now it must fall through to step 4 and pick
45
+ // an available model.
46
+ const registry = fakeRegistry({
47
+ models: [
48
+ { provider: "xai", id: "grok-4-fast-non-reasoning" },
49
+ { provider: "anthropic", id: "claude-opus-4-6" },
50
+ ],
51
+ readyProviders: new Set(["anthropic"]),
52
+ });
53
+
54
+ const result = await findInitialModel({
55
+ scopedModels: [],
56
+ isContinuing: false,
57
+ defaultProvider: "xai",
58
+ defaultModelId: "grok-4-fast-non-reasoning",
59
+ modelRegistry: registry as any,
60
+ });
61
+
62
+ assert.ok(result.model, "a model must be returned");
63
+ assert.equal(result.model!.provider, "anthropic", "unauth'd saved default must be skipped");
64
+ });
65
+
66
+ test("findInitialModel keeps saved default when provider has auth", async () => {
67
+ const registry = fakeRegistry({
68
+ models: [
69
+ { provider: "anthropic", id: "claude-opus-4-6" },
70
+ { provider: "openai", id: "gpt-5.4" },
71
+ ],
72
+ readyProviders: new Set(["anthropic", "openai"]),
73
+ });
74
+
75
+ const result = await findInitialModel({
76
+ scopedModels: [],
77
+ isContinuing: false,
78
+ defaultProvider: "openai",
79
+ defaultModelId: "gpt-5.4",
80
+ modelRegistry: registry as any,
81
+ });
82
+
83
+ assert.equal(result.model?.provider, "openai");
84
+ assert.equal(result.model?.id, "gpt-5.4");
85
+ });
@@ -504,27 +504,31 @@ export async function findInitialModel(options: {
504
504
 
505
505
  // 3. Try saved default from settings
506
506
  if (defaultProvider && defaultModelId) {
507
- const found = modelRegistry.find(defaultProvider, defaultModelId);
508
- if (found) {
509
- // Check if the provider's recommended default is a higher-capability variant
510
- // of the saved model (e.g. saved "claude-opus-4-6" vs recommended "claude-opus-4-6-extended").
511
- // If so, prefer the recommended variant to avoid using a smaller context window (#1125).
512
- const recommendedId = defaultModelPerProvider[defaultProvider as KnownProvider];
513
- if (recommendedId && recommendedId !== defaultModelId && recommendedId.startsWith(defaultModelId)) {
514
- const recommended = modelRegistry.find(defaultProvider, recommendedId);
515
- if (recommended) {
516
- model = recommended;
517
- if (defaultThinkingLevel) {
518
- thinkingLevel = defaultThinkingLevel;
507
+ // Guard against stale settings defaults: only use the saved provider/model
508
+ // if the provider is actually request-ready (auth/OAuth/CLI ready).
509
+ if (modelRegistry.isProviderRequestReady(defaultProvider)) {
510
+ const found = modelRegistry.find(defaultProvider, defaultModelId);
511
+ if (found) {
512
+ // Check if the provider's recommended default is a higher-capability variant
513
+ // of the saved model (e.g. saved "claude-opus-4-6" vs recommended "claude-opus-4-6-extended").
514
+ // If so, prefer the recommended variant to avoid using a smaller context window (#1125).
515
+ const recommendedId = defaultModelPerProvider[defaultProvider as KnownProvider];
516
+ if (recommendedId && recommendedId !== defaultModelId && recommendedId.startsWith(defaultModelId)) {
517
+ const recommended = modelRegistry.find(defaultProvider, recommendedId);
518
+ if (recommended) {
519
+ model = recommended;
520
+ if (defaultThinkingLevel) {
521
+ thinkingLevel = defaultThinkingLevel;
522
+ }
523
+ return { model, thinkingLevel, fallbackMessage: undefined };
519
524
  }
520
- return { model, thinkingLevel, fallbackMessage: undefined };
521
525
  }
526
+ model = found;
527
+ if (defaultThinkingLevel) {
528
+ thinkingLevel = defaultThinkingLevel;
529
+ }
530
+ return { model, thinkingLevel, fallbackMessage: undefined };
522
531
  }
523
- model = found;
524
- if (defaultThinkingLevel) {
525
- thinkingLevel = defaultThinkingLevel;
526
- }
527
- return { model, thinkingLevel, fallbackMessage: undefined };
528
532
  }
529
533
  }
530
534
 
@@ -171,6 +171,25 @@ describe("RetryHandler — long-context entitlement 429 (#2803)", () => {
171
171
  const retryStart = emittedEvents.find((e) => e.type === "auto_retry_start");
172
172
  assert.ok(retryStart, "Regular 429 should enter backoff retry");
173
173
  });
174
+
175
+ it("classifies OpenRouter credit affordability errors as quota_exhausted", async () => {
176
+ const { deps, emittedEvents } = createMockDeps({
177
+ model: createMockModel("openrouter", "openai/gpt-5-pro"),
178
+ markUsageLimitReachedResult: false,
179
+ fallbackResult: null,
180
+ });
181
+
182
+ const handler = new RetryHandler(deps);
183
+ const msg = errorMessage(
184
+ "402 This request requires more credits, or fewer max_tokens. You requested up to 32000 tokens, but can only afford 329.",
185
+ );
186
+
187
+ const result = await handler.handleRetryableError(msg);
188
+
189
+ assert.equal(result, true, "affordability error should trigger credit-aware retry");
190
+ const retryStart = emittedEvents.find((e) => e.type === "auto_retry_start");
191
+ assert.ok(retryStart, "Expected immediate retry after reducing max tokens");
192
+ });
174
193
  });
175
194
 
176
195
  describe("long-context model downgrade", () => {
@@ -271,6 +290,61 @@ describe("RetryHandler — long-context entitlement 429 (#2803)", () => {
271
290
  });
272
291
  });
273
292
 
293
+ describe("credit-aware maxTokens retry", () => {
294
+ it("reduces maxTokens on same model when provider reports affordable cap", async () => {
295
+ const expensiveModel = createMockModel("openrouter", "openai/gpt-5-pro");
296
+ expensiveModel.maxTokens = 128000;
297
+
298
+ const { deps, emittedEvents, onModelChangeFn } = createMockDeps({
299
+ model: expensiveModel,
300
+ markUsageLimitReachedResult: false,
301
+ fallbackResult: null,
302
+ });
303
+
304
+ const handler = new RetryHandler(deps);
305
+ const msg = errorMessage(
306
+ "402 This request requires more credits, or fewer max_tokens. You requested up to 32000 tokens, but can only afford 329.",
307
+ );
308
+
309
+ const result = await handler.handleRetryableError(msg);
310
+ assert.equal(result, true, "should retry after reducing maxTokens");
311
+
312
+ const setModelCalls = (deps.agent.setModel as any).mock.calls;
313
+ assert.equal(setModelCalls.length, 1, "should apply one model downgrade");
314
+ const downgraded = setModelCalls[0].arguments[0] as Model<Api>;
315
+ assert.equal(downgraded.provider, "openrouter");
316
+ assert.equal(downgraded.id, "openai/gpt-5-pro");
317
+ assert.equal(downgraded.maxTokens, 297, "expected affordability cap with safety buffer");
318
+
319
+ assert.equal(onModelChangeFn.mock.calls.length, 1, "should notify about model update");
320
+ const switchEvent = emittedEvents.find((e) => e.type === "fallback_provider_switch");
321
+ assert.ok(switchEvent, "should emit model-adjustment event");
322
+ assert.ok(
323
+ String(switchEvent?.reason || "").includes("credit-aware retry"),
324
+ "switch reason should mention credit-aware retry",
325
+ );
326
+ });
327
+
328
+ it("does not mark credentials in cooldown for affordability quota errors", async () => {
329
+ const expensiveModel = createMockModel("openrouter", "openai/gpt-5-pro");
330
+ expensiveModel.maxTokens = 128000;
331
+
332
+ const { deps, markUsageLimitReached } = createMockDeps({
333
+ model: expensiveModel,
334
+ markUsageLimitReachedResult: false,
335
+ fallbackResult: null,
336
+ });
337
+
338
+ const handler = new RetryHandler(deps);
339
+ const msg = errorMessage(
340
+ "402 This request requires more credits, or fewer max_tokens. You requested up to 32000 tokens, but can only afford 329.",
341
+ );
342
+
343
+ await handler.handleRetryableError(msg);
344
+ assert.equal(markUsageLimitReached.mock.calls.length, 0, "quota error should skip credential cooldown");
345
+ });
346
+ });
347
+
274
348
  describe("isRetryableError", () => {
275
349
  it("considers long-context entitlement error as retryable", () => {
276
350
  const { deps } = createMockDeps();
@@ -291,6 +365,15 @@ describe("RetryHandler — long-context entitlement 429 (#2803)", () => {
291
365
  );
292
366
  assert.equal(handler.isRetryableError(msg), false);
293
367
  });
368
+
369
+ it("considers OpenRouter affordability credit errors as retryable", () => {
370
+ const { deps } = createMockDeps();
371
+ const handler = new RetryHandler(deps);
372
+ const msg = errorMessage(
373
+ "402 This request requires more credits, or fewer max_tokens. You requested up to 32000 tokens, but can only afford 329.",
374
+ );
375
+ assert.equal(handler.isRetryableError(msg), true);
376
+ });
294
377
  });
295
378
 
296
379
  describe("third-party block claude-code fallback (#3772)", () => {
@@ -116,7 +116,7 @@ export class RetryHandler {
116
116
  // generated error from getApiKey() when credentials are in a backoff window.
117
117
  // Re-entering the retry handler for that message creates a cascade of empty
118
118
  // error entries in the session file, breaking resume (#3429).
119
- return /overloaded|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|connection.?error|connection.?refused|other side closed|fetch failed|upstream.?connect|reset before headers|terminated|retry delay|network.?(?:is\s+)?unavailable|credentials.*expired|extra usage is required|(?:out of|no) extra usage|third.party.*draw from extra|third.party.*not.*available/i.test(
119
+ return /overloaded|rate.?limit|too many requests|402|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|connection.?error|connection.?refused|other side closed|fetch failed|upstream.?connect|reset before headers|terminated|retry delay|network.?(?:is\s+)?unavailable|credentials.*expired|requires more credits|can only afford|insufficient credits|not enough credits|extra usage is required|(?:out of|no) extra usage|third.party.*draw from extra|third.party.*not.*available/i.test(
120
120
  err,
121
121
  );
122
122
  }
@@ -158,6 +158,14 @@ export class RetryHandler {
158
158
  const isRateLimit = errorType === "rate_limit";
159
159
  const isQuotaError = errorType === "quota_exhausted";
160
160
 
161
+ // Credit-aware retry (OpenRouter-style 402 affordability errors):
162
+ // when provider reports "can only afford N", lower maxTokens and retry
163
+ // on the same model before rotating credentials/providers.
164
+ if (isQuotaError) {
165
+ const adjusted = this._tryAffordableMaxTokensRetry(message, retryGeneration);
166
+ if (adjusted) return true;
167
+ }
168
+
161
169
  // Credential rotation — only for transient rate limits (#3430).
162
170
  // Quota errors ("Extra usage is required") are account-level billing
163
171
  // gates; rotating to another credential on the same account won't help
@@ -409,12 +417,63 @@ export class RetryHandler {
409
417
  // Long-context entitlement errors are billing gates, not transient rate limits.
410
418
  // Must be checked before the generic 429/rate_limit regex.
411
419
  if (/extra usage is required|long context required/i.test(err)) return "quota_exhausted";
420
+ if (/requires more credits|can only afford|insufficient credits|not enough credits|credit balance/i.test(err))
421
+ return "quota_exhausted";
412
422
  if (/quota|billing|exceeded.*limit|usage.*limit/i.test(err)) return "quota_exhausted";
413
423
  if (/rate.?limit|too many requests|429/i.test(err)) return "rate_limit";
414
424
  if (/500|502|503|504|server.?error|internal.?error|service.?unavailable/i.test(err)) return "server_error";
415
425
  return "unknown";
416
426
  }
417
427
 
428
+ /**
429
+ * Attempt a same-model retry by reducing maxTokens when provider reports
430
+ * an affordability cap (e.g., "can only afford 329").
431
+ */
432
+ private _tryAffordableMaxTokensRetry(message: AssistantMessage, retryGeneration: number): boolean {
433
+ const currentModel = this._deps.getModel();
434
+ if (!currentModel || !message.errorMessage) return false;
435
+
436
+ // Example: "can only afford 329"
437
+ const match = message.errorMessage.match(/can only afford\s+([\d,]+)/i);
438
+ if (!match?.[1]) return false;
439
+
440
+ const affordable = Number.parseInt(match[1].replace(/,/g, ""), 10);
441
+ if (!Number.isFinite(affordable) || affordable <= 0) return false;
442
+
443
+ // Leave a small buffer so slight input variance doesn't immediately re-fail.
444
+ const safetyBuffer = Math.min(64, Math.max(16, Math.floor(affordable * 0.1)));
445
+ const targetMaxTokens = Math.max(64, affordable - safetyBuffer);
446
+ const downgradedMaxTokens = Math.min(currentModel.maxTokens, targetMaxTokens);
447
+ if (downgradedMaxTokens >= currentModel.maxTokens) return false;
448
+
449
+ const downgradedModel = {
450
+ ...currentModel,
451
+ maxTokens: downgradedMaxTokens,
452
+ };
453
+
454
+ this._deps.agent.setModel(downgradedModel);
455
+ this._deps.onModelChange(downgradedModel);
456
+ this._removeLastAssistantError();
457
+
458
+ this._deps.emit({
459
+ type: "fallback_provider_switch",
460
+ from: `${currentModel.provider}/${currentModel.id} (maxTokens=${currentModel.maxTokens})`,
461
+ to: `${downgradedModel.provider}/${downgradedModel.id} (maxTokens=${downgradedModel.maxTokens})`,
462
+ reason: `credit-aware retry: provider affordable cap ${affordable} tokens`,
463
+ });
464
+
465
+ this._deps.emit({
466
+ type: "auto_retry_start",
467
+ attempt: this._retryAttempt + 1,
468
+ maxAttempts: this._deps.settingsManager.getRetrySettings().maxRetries,
469
+ delayMs: 0,
470
+ errorMessage: `${message.errorMessage} (reducing max tokens)`,
471
+ });
472
+
473
+ this._scheduleContinue(retryGeneration);
474
+ return true;
475
+ }
476
+
418
477
  /**
419
478
  * Attempt to downgrade a long-context model (e.g. claude-opus-4-6[1m]) to its
420
479
  * base model (claude-opus-4-6) when the account lacks the long-context billing
@@ -0,0 +1,89 @@
1
+ // pi-coding-agent / CredentialCooldownError unit tests
2
+ // Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
3
+
4
+ import { describe, it } from "node:test";
5
+ import assert from "node:assert/strict";
6
+ import { CredentialCooldownError } from "./sdk.js";
7
+
8
+ // ─── CredentialCooldownError ──────────────────────────────────────────────────
9
+
10
+ describe("CredentialCooldownError", () => {
11
+ it("is an instance of Error", () => {
12
+ const err = new CredentialCooldownError("anthropic");
13
+ assert.ok(err instanceof Error);
14
+ });
15
+
16
+ it("has name set to CredentialCooldownError", () => {
17
+ const err = new CredentialCooldownError("anthropic");
18
+ assert.equal(err.name, "CredentialCooldownError");
19
+ });
20
+
21
+ it("has code set to AUTH_COOLDOWN", () => {
22
+ const err = new CredentialCooldownError("anthropic");
23
+ assert.equal(err.code, "AUTH_COOLDOWN");
24
+ });
25
+
26
+ it("message includes the provider name", () => {
27
+ const err = new CredentialCooldownError("openai");
28
+ assert.ok(
29
+ err.message.includes("openai"),
30
+ `Expected message to include provider "openai", got: ${err.message}`,
31
+ );
32
+ });
33
+
34
+ it("message mentions cooldown window", () => {
35
+ const err = new CredentialCooldownError("anthropic");
36
+ assert.ok(
37
+ /cooldown window/i.test(err.message),
38
+ `Expected message to mention "cooldown window", got: ${err.message}`,
39
+ );
40
+ });
41
+
42
+ it("retryAfterMs is undefined when not provided", () => {
43
+ const err = new CredentialCooldownError("anthropic");
44
+ assert.equal(err.retryAfterMs, undefined);
45
+ });
46
+
47
+ it("retryAfterMs holds the provided value when specified", () => {
48
+ const err = new CredentialCooldownError("anthropic", 30_000);
49
+ assert.equal(err.retryAfterMs, 30_000);
50
+ });
51
+
52
+ it("retryAfterMs is 0 when explicitly passed as 0", () => {
53
+ const err = new CredentialCooldownError("anthropic", 0);
54
+ assert.equal(err.retryAfterMs, 0);
55
+ });
56
+
57
+ it("code property is readonly and always AUTH_COOLDOWN regardless of provider", () => {
58
+ for (const provider of ["anthropic", "openai", "google", "openrouter"]) {
59
+ const err = new CredentialCooldownError(provider);
60
+ assert.equal(err.code, "AUTH_COOLDOWN", `code should be AUTH_COOLDOWN for provider "${provider}"`);
61
+ }
62
+ });
63
+
64
+ it("different providers produce different messages", () => {
65
+ const err1 = new CredentialCooldownError("anthropic");
66
+ const err2 = new CredentialCooldownError("openai");
67
+ assert.notEqual(err1.message, err2.message);
68
+ });
69
+
70
+ it("can be caught as an Error in a try/catch", () => {
71
+ let caught: unknown;
72
+ try {
73
+ throw new CredentialCooldownError("anthropic", 5_000);
74
+ } catch (e) {
75
+ caught = e;
76
+ }
77
+ assert.ok(caught instanceof Error);
78
+ assert.ok(caught instanceof CredentialCooldownError);
79
+ assert.equal((caught as CredentialCooldownError).retryAfterMs, 5_000);
80
+ });
81
+
82
+ it("code property is detectable via plain object check (cross-process pattern)", () => {
83
+ const err = new CredentialCooldownError("anthropic", 15_000);
84
+ // Simulate cross-process serialization: only plain properties survive JSON round-trip
85
+ const plain = { code: err.code, retryAfterMs: err.retryAfterMs, message: err.message };
86
+ assert.equal(plain.code, "AUTH_COOLDOWN");
87
+ assert.equal(plain.retryAfterMs, 15_000);
88
+ });
89
+ });
@@ -1,4 +1,24 @@
1
1
  import { join } from "node:path";
2
+
3
+ /**
4
+ * Structured error thrown when all credentials for a provider are in a
5
+ * backoff window. Carries typed metadata so callers (e.g. the auto-loop)
6
+ * can make informed retry decisions instead of string-matching the message.
7
+ */
8
+ export class CredentialCooldownError extends Error {
9
+ readonly code = "AUTH_COOLDOWN" as const;
10
+ /** Milliseconds until the earliest credential becomes available, or undefined if unknown. */
11
+ readonly retryAfterMs: number | undefined;
12
+
13
+ constructor(provider: string, retryAfterMs?: number) {
14
+ super(
15
+ `All credentials for "${provider}" are in a cooldown window. ` +
16
+ `Please wait a moment and try again, or switch to a different provider.`,
17
+ );
18
+ this.name = "CredentialCooldownError";
19
+ this.retryAfterMs = retryAfterMs;
20
+ }
21
+ }
2
22
  import { Agent, type AgentMessage, type ThinkingLevel } from "@gsd/pi-agent-core";
3
23
  import type { Message, Model } from "@gsd/pi-ai";
4
24
  import { getAgentDir, getDocsPath } from "../config.js";
@@ -363,8 +383,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
363
383
 
364
384
  // Retry key resolution with backoff to handle transient network failures
365
385
  // (e.g., OAuth token refresh failing due to brief connectivity loss).
386
+ // When credentials are in a cooldown window (e.g., after a 429), wait
387
+ // for the backoff to expire instead of using fixed delays that are
388
+ // shorter than the cooldown duration.
366
389
  const maxAttempts = 3;
367
390
  const baseDelayMs = 2000;
391
+ const maxCooldownWaitMs = 60_000; // Don't wait longer than 60s (skip quota-exhausted 30min backoffs)
368
392
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
369
393
  const key = await modelRegistry.getApiKeyForProvider(resolvedProvider);
370
394
  if (key) return key;
@@ -379,7 +403,21 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
379
403
  const isOAuth = model && modelRegistry.isUsingOAuth(model);
380
404
  if (!hasAuth && !isOAuth) break;
381
405
 
382
- // Wait with exponential backoff before retrying
406
+ // If credentials are in a cooldown window, wait for the earliest
407
+ // one to expire rather than using a fixed delay that's too short.
408
+ const backoffExpiry = modelRegistry.authStorage.getEarliestBackoffExpiry(resolvedProvider);
409
+ if (backoffExpiry !== undefined) {
410
+ const waitMs = backoffExpiry - Date.now() + 500; // 500ms buffer
411
+ if (waitMs > 0 && waitMs <= maxCooldownWaitMs) {
412
+ await new Promise(resolve => setTimeout(resolve, waitMs));
413
+ continue; // Retry immediately after cooldown clears
414
+ }
415
+ if (waitMs > maxCooldownWaitMs) {
416
+ break; // Quota-exhausted or very long backoff — don't block
417
+ }
418
+ }
419
+
420
+ // Standard exponential backoff for non-cooldown transient failures
383
421
  await new Promise(resolve => setTimeout(resolve, baseDelayMs * attempt));
384
422
  }
385
423
 
@@ -390,10 +428,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
390
428
  // the retry handler and creating cascading error entries (#3429).
391
429
  const hasAuth = modelRegistry.authStorage.hasAuth(resolvedProvider);
392
430
  if (hasAuth) {
393
- throw new Error(
394
- `All credentials for "${resolvedProvider}" are in a cooldown window. ` +
395
- `Please wait a moment and try again, or switch to a different provider.`,
396
- );
431
+ const expiry = modelRegistry.authStorage.getEarliestBackoffExpiry(resolvedProvider);
432
+ const retryAfterMs = expiry !== undefined ? Math.max(0, expiry - Date.now()) : undefined;
433
+ throw new CredentialCooldownError(resolvedProvider, retryAfterMs);
397
434
  }
398
435
  const model = agent.state.model;
399
436
  const isOAuth = model && modelRegistry.isUsingOAuth(model);
@@ -401,10 +438,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
401
438
  // If credentials exist but are all in a backoff window (quota / rate-limit),
402
439
  // surface a specific message instead of the misleading "Authentication failed".
403
440
  if (modelRegistry.authStorage.areAllCredentialsBackedOff(resolvedProvider)) {
404
- throw new Error(
405
- `All credentials for "${resolvedProvider}" are in a cooldown window. ` +
406
- `Please wait a moment and try again, or switch to a different provider.`,
407
- );
441
+ const expiry = modelRegistry.authStorage.getEarliestBackoffExpiry(resolvedProvider);
442
+ const retryAfterMs = expiry !== undefined ? Math.max(0, expiry - Date.now()) : undefined;
443
+ throw new CredentialCooldownError(resolvedProvider, retryAfterMs);
408
444
  }
409
445
  throw new Error(
410
446
  `Authentication failed for "${resolvedProvider}". ` +
@@ -176,6 +176,7 @@ export { DefaultResourceLoader } from "./core/resource-loader.js";
176
176
  export {
177
177
  type CreateAgentSessionOptions,
178
178
  type CreateAgentSessionResult,
179
+ CredentialCooldownError,
179
180
  // Factory
180
181
  createAgentSession,
181
182
  createBashTool,
@@ -0,0 +1,24 @@
1
+ import { describe, test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { buildAuthUrlPresentation } from "../login-dialog.js";
4
+
5
+ describe("LoginDialogComponent", () => {
6
+ test("shows the full OAuth URL when the hyperlink label is truncated", () => {
7
+ const presentation = buildAuthUrlPresentation(
8
+ "https://auth.example.com/device?code=ABCD-1234&callback=oauth&state=needs-full-visibility",
9
+ 52,
10
+ );
11
+
12
+ assert.notEqual(
13
+ presentation.displayUrl,
14
+ "https://auth.example.com/device?code=ABCD-1234&callback=oauth&state=needs-full-visibility",
15
+ "narrow terminals should still truncate the hyperlink label",
16
+ );
17
+ assert.ok(presentation.fullUrlLines.length > 1, "truncated URLs should expose wrapped full-url lines");
18
+ assert.match(presentation.fullUrlLines[0] ?? "", /https:\/\/auth\.example\.com\/device\?code=ABCD-1234&/);
19
+ assert.match(
20
+ presentation.fullUrlLines[presentation.fullUrlLines.length - 1] ?? "",
21
+ /state=needs-full-visibility/,
22
+ );
23
+ });
24
+ });
@@ -27,6 +27,26 @@ function renderTool(
27
27
  return stripAnsi(component.render(120).join("\n"));
28
28
  }
29
29
 
30
+ function renderToolCollapsed(
31
+ toolName: string,
32
+ args: Record<string, unknown>,
33
+ result?: {
34
+ content: Array<{ type: string; text?: string }>;
35
+ isError: boolean;
36
+ details?: Record<string, unknown>;
37
+ },
38
+ ): string {
39
+ const component = new ToolExecutionComponent(
40
+ toolName,
41
+ args,
42
+ {},
43
+ undefined,
44
+ { requestRender() {} } as any,
45
+ );
46
+ if (result) component.updateResult(result);
47
+ return stripAnsi(component.render(120).join("\n"));
48
+ }
49
+
30
50
  describe("ToolExecutionComponent", () => {
31
51
  test("renders capitalized Claude Code Bash tool names with bash output instead of generic args JSON", () => {
32
52
  const rendered = renderTool(
@@ -51,4 +71,56 @@ describe("ToolExecutionComponent", () => {
51
71
  assert.match(rendered, /hello/);
52
72
  assert.match(rendered, /world/);
53
73
  });
74
+
75
+ test("generic fallback strips mcp__<server>__ prefix and shows server·tool title", () => {
76
+ const rendered = renderTool(
77
+ "mcp__context7__resolve_library_id",
78
+ { name: "react" },
79
+ { content: [{ type: "text", text: "react@18.3.1" }], isError: false },
80
+ );
81
+
82
+ assert.match(rendered, /context7\u00b7resolve_library_id/);
83
+ assert.doesNotMatch(rendered, /mcp__/);
84
+ assert.match(rendered, /name="react"/);
85
+ assert.match(rendered, /react@18\.3\.1/);
86
+ });
87
+
88
+ test("generic fallback renders compact key=value args for primitive args", () => {
89
+ const rendered = renderTool(
90
+ "some_unknown_tool",
91
+ { count: 3, enabled: true, label: "hello" },
92
+ );
93
+
94
+ assert.match(rendered, /some_unknown_tool/);
95
+ assert.match(rendered, /count=3/);
96
+ assert.match(rendered, /enabled=true/);
97
+ assert.match(rendered, /label="hello"/);
98
+ assert.doesNotMatch(rendered, /^\{$/m);
99
+ });
100
+
101
+ test("generic fallback truncates long output when collapsed", () => {
102
+ const longOutput = Array.from({ length: 25 }, (_, i) => `line ${i + 1}`).join("\n");
103
+ const rendered = renderToolCollapsed(
104
+ "mcp__demo__do_thing",
105
+ { ok: true },
106
+ { content: [{ type: "text", text: longOutput }], isError: false },
107
+ );
108
+
109
+ assert.match(rendered, /line 1\b/);
110
+ assert.match(rendered, /line 10\b/);
111
+ assert.doesNotMatch(rendered, /line 20\b/);
112
+ assert.match(rendered, /\(15 more lines/);
113
+ });
114
+
115
+ test("generic fallback falls back to truncated JSON for complex args", () => {
116
+ const rendered = renderTool(
117
+ "mcp__demo__nested",
118
+ { payload: { nested: { deeply: ["a", "b", "c"] } }, name: "x" },
119
+ );
120
+
121
+ assert.match(rendered, /demo\u00b7nested/);
122
+ // Multi-line JSON dump for the complex payload
123
+ assert.match(rendered, /"payload"/);
124
+ assert.match(rendered, /"nested"/);
125
+ });
54
126
  });