gsd-pi 2.50.0-dev.d210a87 → 2.51.0-dev.7d435fe

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 (302) hide show
  1. package/README.md +4 -4
  2. package/dist/headless-events.d.ts +18 -0
  3. package/dist/headless-events.js +36 -0
  4. package/dist/headless-types.d.ts +28 -0
  5. package/dist/headless-types.js +7 -0
  6. package/dist/headless.d.ts +8 -3
  7. package/dist/headless.js +47 -16
  8. package/dist/help-text.js +16 -5
  9. package/dist/onboarding.js +5 -4
  10. package/dist/remote-questions-config.js +1 -1
  11. package/dist/resources/extensions/async-jobs/async-bash-tool.js +29 -17
  12. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +18 -19
  13. package/dist/resources/extensions/gsd/auto-dispatch.js +18 -0
  14. package/dist/resources/extensions/gsd/auto-start.js +2 -0
  15. package/dist/resources/extensions/gsd/auto-timers.js +24 -2
  16. package/dist/resources/extensions/gsd/auto-tool-tracking.js +25 -7
  17. package/dist/resources/extensions/gsd/auto-worktree.js +21 -0
  18. package/dist/resources/extensions/gsd/auto.js +4 -2
  19. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +95 -69
  20. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +12 -2
  21. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +1 -1
  22. package/dist/resources/extensions/gsd/claude-import.js +60 -9
  23. package/dist/resources/extensions/gsd/commands/handlers/auto.js +69 -6
  24. package/dist/resources/extensions/gsd/commands-config.js +10 -5
  25. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  26. package/dist/resources/extensions/gsd/detection.js +6 -6
  27. package/dist/resources/extensions/gsd/docs/preferences-reference.md +3 -3
  28. package/dist/resources/extensions/gsd/error-classifier.js +105 -0
  29. package/dist/resources/extensions/gsd/gitignore.js +7 -7
  30. package/dist/resources/extensions/gsd/gsd-db.js +298 -45
  31. package/dist/resources/extensions/gsd/init-wizard.js +2 -2
  32. package/dist/resources/extensions/gsd/key-manager.js +7 -16
  33. package/dist/resources/extensions/gsd/memory-store.js +28 -13
  34. package/dist/resources/extensions/gsd/milestone-actions.js +19 -0
  35. package/dist/resources/extensions/gsd/preferences-models.js +1 -13
  36. package/dist/resources/extensions/gsd/preferences.js +13 -13
  37. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  38. package/dist/resources/extensions/gsd/provider-error-pause.js +0 -44
  39. package/dist/resources/extensions/gsd/rule-registry.js +1 -1
  40. package/dist/resources/extensions/gsd/service-tier.js +13 -2
  41. package/dist/resources/extensions/gsd/state.js +21 -2
  42. package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -10
  43. package/dist/resources/extensions/gsd/tools/complete-slice.js +3 -17
  44. package/dist/resources/extensions/gsd/tools/complete-task.js +7 -18
  45. package/dist/resources/extensions/gsd/tools/plan-milestone.js +26 -17
  46. package/dist/resources/extensions/gsd/tools/plan-slice.js +25 -14
  47. package/dist/resources/extensions/gsd/tools/plan-task.js +21 -11
  48. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +47 -37
  49. package/dist/resources/extensions/gsd/tools/replan-slice.js +49 -38
  50. package/dist/resources/extensions/gsd/tools/validate-milestone.js +23 -16
  51. package/dist/resources/extensions/gsd/workflow-logger.js +0 -1
  52. package/dist/resources/extensions/remote-questions/config.js +1 -1
  53. package/dist/resources/extensions/remote-questions/remote-command.js +1 -1
  54. package/dist/resources/extensions/search-the-web/native-search.js +1 -1
  55. package/dist/resources/extensions/search-the-web/provider.js +1 -1
  56. package/dist/web/standalone/.next/BUILD_ID +1 -1
  57. package/dist/web/standalone/.next/app-path-routes-manifest.json +21 -21
  58. package/dist/web/standalone/.next/build-manifest.json +3 -3
  59. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  60. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  61. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  63. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  71. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.rsc +2 -2
  73. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  74. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  76. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  79. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  80. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/experimental/route.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  122. package/dist/web/standalone/.next/server/app/index.html +1 -1
  123. package/dist/web/standalone/.next/server/app/index.rsc +2 -2
  124. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  125. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
  126. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  127. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +2 -2
  128. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  129. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  130. package/dist/web/standalone/.next/server/app-paths-manifest.json +21 -21
  131. package/dist/web/standalone/.next/server/chunks/2229.js +2 -2
  132. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  133. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  134. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  135. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  136. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  137. package/dist/web/standalone/.next/static/chunks/4024.21054f459af5cc78.js +9 -0
  138. package/dist/web/standalone/.next/static/chunks/{webpack-cfc9a116e6450a6b.js → webpack-024d82be84800e52.js} +1 -1
  139. package/dist/web/standalone/.next/static/css/a58ef8a151aa0493.css +1 -0
  140. package/dist/wizard.js +4 -1
  141. package/package.json +2 -2
  142. package/packages/pi-ai/dist/models.d.ts +14 -3
  143. package/packages/pi-ai/dist/models.d.ts.map +1 -1
  144. package/packages/pi-ai/dist/models.js +53 -10
  145. package/packages/pi-ai/dist/models.js.map +1 -1
  146. package/packages/pi-ai/dist/models.test.js +102 -1
  147. package/packages/pi-ai/dist/models.test.js.map +1 -1
  148. package/packages/pi-ai/dist/types.d.ts +30 -0
  149. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  150. package/packages/pi-ai/dist/types.js.map +1 -1
  151. package/packages/pi-ai/src/models.test.ts +114 -1
  152. package/packages/pi-ai/src/models.ts +70 -13
  153. package/packages/pi-ai/src/types.ts +31 -0
  154. package/packages/pi-coding-agent/dist/cli/args.d.ts +2 -0
  155. package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
  156. package/packages/pi-coding-agent/dist/cli/args.js +3 -0
  157. package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
  158. package/packages/pi-coding-agent/dist/core/bash-executor.d.ts.map +1 -1
  159. package/packages/pi-coding-agent/dist/core/bash-executor.js +5 -1
  160. package/packages/pi-coding-agent/dist/core/bash-executor.js.map +1 -1
  161. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  162. package/packages/pi-coding-agent/dist/core/model-registry.js +9 -4
  163. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  164. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.d.ts +19 -0
  165. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.d.ts.map +1 -0
  166. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.js +83 -0
  167. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.js.map +1 -0
  168. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
  169. package/packages/pi-coding-agent/dist/core/tools/bash.js +5 -1
  170. package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  171. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  172. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  173. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  174. package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
  175. package/packages/pi-coding-agent/dist/main.js +5 -3
  176. package/packages/pi-coding-agent/dist/main.js.map +1 -1
  177. package/packages/pi-coding-agent/dist/modes/index.d.ts +1 -1
  178. package/packages/pi-coding-agent/dist/modes/index.d.ts.map +1 -1
  179. package/packages/pi-coding-agent/dist/modes/index.js.map +1 -1
  180. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  181. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +0 -2
  182. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  183. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts +28 -1
  184. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  185. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js +49 -0
  186. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js.map +1 -1
  187. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts +1 -1
  188. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  189. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +114 -6
  190. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  191. package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.d.ts +9 -0
  192. package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.d.ts.map +1 -0
  193. package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.js +831 -0
  194. package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.js.map +1 -0
  195. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts +66 -0
  196. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  197. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.js.map +1 -1
  198. package/packages/pi-coding-agent/dist/utils/shell.d.ts.map +1 -1
  199. package/packages/pi-coding-agent/dist/utils/shell.js +0 -1
  200. package/packages/pi-coding-agent/dist/utils/shell.js.map +1 -1
  201. package/packages/pi-coding-agent/package.json +1 -1
  202. package/packages/pi-coding-agent/src/cli/args.ts +4 -0
  203. package/packages/pi-coding-agent/src/core/bash-executor.ts +5 -1
  204. package/packages/pi-coding-agent/src/core/model-registry.ts +10 -3
  205. package/packages/pi-coding-agent/src/core/tools/bash-spawn-windows.test.ts +101 -0
  206. package/packages/pi-coding-agent/src/core/tools/bash.ts +5 -1
  207. package/packages/pi-coding-agent/src/index.ts +3 -0
  208. package/packages/pi-coding-agent/src/main.ts +5 -3
  209. package/packages/pi-coding-agent/src/modes/index.ts +8 -1
  210. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +0 -2
  211. package/packages/pi-coding-agent/src/modes/rpc/rpc-client.ts +54 -1
  212. package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +124 -6
  213. package/packages/pi-coding-agent/src/modes/rpc/rpc-protocol-v2.test.ts +971 -0
  214. package/packages/pi-coding-agent/src/modes/rpc/rpc-types.ts +61 -4
  215. package/packages/pi-coding-agent/src/utils/shell.ts +0 -1
  216. package/pkg/package.json +1 -1
  217. package/src/resources/extensions/async-jobs/async-bash-tool.ts +22 -11
  218. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +19 -20
  219. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +21 -0
  220. package/src/resources/extensions/gsd/auto-dispatch.ts +19 -0
  221. package/src/resources/extensions/gsd/auto-start.ts +2 -0
  222. package/src/resources/extensions/gsd/auto-timers.ts +25 -1
  223. package/src/resources/extensions/gsd/auto-tool-tracking.ts +30 -6
  224. package/src/resources/extensions/gsd/auto-worktree.ts +21 -0
  225. package/src/resources/extensions/gsd/auto.ts +5 -2
  226. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +115 -72
  227. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +11 -2
  228. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +1 -1
  229. package/src/resources/extensions/gsd/claude-import.ts +58 -9
  230. package/src/resources/extensions/gsd/commands/handlers/auto.ts +73 -6
  231. package/src/resources/extensions/gsd/commands-config.ts +11 -5
  232. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  233. package/src/resources/extensions/gsd/detection.ts +6 -6
  234. package/src/resources/extensions/gsd/docs/preferences-reference.md +3 -3
  235. package/src/resources/extensions/gsd/error-classifier.ts +139 -0
  236. package/src/resources/extensions/gsd/gitignore.ts +7 -7
  237. package/src/resources/extensions/gsd/gsd-db.ts +355 -63
  238. package/src/resources/extensions/gsd/init-wizard.ts +2 -2
  239. package/src/resources/extensions/gsd/key-manager.ts +7 -16
  240. package/src/resources/extensions/gsd/memory-store.ts +29 -18
  241. package/src/resources/extensions/gsd/milestone-actions.ts +17 -0
  242. package/src/resources/extensions/gsd/preferences-models.ts +1 -13
  243. package/src/resources/extensions/gsd/preferences.ts +12 -13
  244. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  245. package/src/resources/extensions/gsd/provider-error-pause.ts +0 -57
  246. package/src/resources/extensions/gsd/rule-registry.ts +1 -1
  247. package/src/resources/extensions/gsd/service-tier.ts +14 -2
  248. package/src/resources/extensions/gsd/state.ts +22 -2
  249. package/src/resources/extensions/gsd/tests/auto-milestone-target.test.ts +61 -0
  250. package/src/resources/extensions/gsd/tests/claude-import-marketplace-discovery.test.ts +191 -0
  251. package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +1 -1
  252. package/src/resources/extensions/gsd/tests/commands-config.test.ts +24 -0
  253. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
  254. package/src/resources/extensions/gsd/tests/complete-task-rollback-evidence.test.ts +106 -0
  255. package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
  256. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +35 -7
  257. package/src/resources/extensions/gsd/tests/detection.test.ts +1 -1
  258. package/src/resources/extensions/gsd/tests/doctor-git.test.ts +4 -4
  259. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +1 -1
  260. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +2 -2
  261. package/src/resources/extensions/gsd/tests/empty-db-reconciliation.test.ts +79 -0
  262. package/src/resources/extensions/gsd/tests/git-service.test.ts +1 -1
  263. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
  264. package/src/resources/extensions/gsd/tests/idle-watchdog-stall-override.test.ts +125 -0
  265. package/src/resources/extensions/gsd/tests/init-wizard.test.ts +1 -1
  266. package/src/resources/extensions/gsd/tests/interactive-tool-idle-exemption.test.ts +119 -0
  267. package/src/resources/extensions/gsd/tests/key-manager.test.ts +16 -1
  268. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
  269. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  270. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +7 -7
  271. package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +85 -0
  272. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +91 -0
  273. package/src/resources/extensions/gsd/tests/preferences.test.ts +1 -1
  274. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +77 -70
  275. package/src/resources/extensions/gsd/tests/remediation-completion-guard.test.ts +110 -0
  276. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +29 -0
  277. package/src/resources/extensions/gsd/tests/terminated-transient.test.ts +42 -31
  278. package/src/resources/extensions/gsd/tests/token-cost-display.test.ts +2 -2
  279. package/src/resources/extensions/gsd/tests/vacuous-truth-slices.test.ts +115 -0
  280. package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +90 -0
  281. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +81 -1
  282. package/src/resources/extensions/gsd/tests/worktree-preferences-sync.test.ts +130 -0
  283. package/src/resources/extensions/gsd/tools/complete-milestone.ts +3 -14
  284. package/src/resources/extensions/gsd/tools/complete-slice.ts +3 -21
  285. package/src/resources/extensions/gsd/tools/complete-task.ts +9 -22
  286. package/src/resources/extensions/gsd/tools/plan-milestone.ts +28 -18
  287. package/src/resources/extensions/gsd/tools/plan-slice.ts +28 -16
  288. package/src/resources/extensions/gsd/tools/plan-task.ts +24 -12
  289. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +54 -42
  290. package/src/resources/extensions/gsd/tools/replan-slice.ts +53 -40
  291. package/src/resources/extensions/gsd/tools/validate-milestone.ts +26 -20
  292. package/src/resources/extensions/gsd/workflow-logger.ts +0 -1
  293. package/src/resources/extensions/remote-questions/config.ts +1 -1
  294. package/src/resources/extensions/remote-questions/remote-command.ts +1 -1
  295. package/src/resources/extensions/search-the-web/native-search.ts +1 -1
  296. package/src/resources/extensions/search-the-web/provider.ts +1 -1
  297. package/dist/web/standalone/.next/static/chunks/4024.9ad5def014d90ce4.js +0 -9
  298. package/dist/web/standalone/.next/static/css/de141508b083f922.css +0 -1
  299. /package/dist/resources/extensions/gsd/templates/{preferences.md → PREFERENCES.md} +0 -0
  300. /package/dist/web/standalone/.next/static/{yJIyd5cXPNpmXTv18ZlyC → RqOU-jOv9uZ1Q03P6L6nn}/_buildManifest.js +0 -0
  301. /package/dist/web/standalone/.next/static/{yJIyd5cXPNpmXTv18ZlyC → RqOU-jOv9uZ1Q03P6L6nn}/_ssgManifest.js +0 -0
  302. /package/src/resources/extensions/gsd/templates/{preferences.md → PREFERENCES.md} +0 -0
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Tests for #2676: idle watchdog must exempt user-interactive tools
3
+ * (ask_user_questions, secure_env_collect) from stall detection.
4
+ */
5
+ import { describe, test, beforeEach } from "node:test";
6
+ import assert from "node:assert/strict";
7
+ import {
8
+ markToolStart,
9
+ markToolEnd,
10
+ hasInteractiveToolInFlight,
11
+ getInFlightToolCount,
12
+ getOldestInFlightToolStart,
13
+ getOldestInFlightToolAgeMs,
14
+ clearInFlightTools,
15
+ } from "../auto-tool-tracking.ts";
16
+
17
+ // These tests call the tracking module directly (bypassing the auto.ts
18
+ // wrapper which guards on s.active) so we always pass isActive=true.
19
+
20
+ beforeEach(() => {
21
+ clearInFlightTools();
22
+ });
23
+
24
+ describe("hasInteractiveToolInFlight", () => {
25
+ test("returns false when no tools are in-flight", () => {
26
+ assert.equal(hasInteractiveToolInFlight(), false);
27
+ });
28
+
29
+ test("returns false when only non-interactive tools are in-flight", () => {
30
+ markToolStart("call-1", true, "bash");
31
+ markToolStart("call-2", true, "read");
32
+ assert.equal(hasInteractiveToolInFlight(), false);
33
+ });
34
+
35
+ test("returns true when ask_user_questions is in-flight", () => {
36
+ markToolStart("call-1", true, "bash");
37
+ markToolStart("call-2", true, "ask_user_questions");
38
+ assert.equal(hasInteractiveToolInFlight(), true);
39
+ });
40
+
41
+ test("returns true when secure_env_collect is in-flight", () => {
42
+ markToolStart("call-1", true, "secure_env_collect");
43
+ assert.equal(hasInteractiveToolInFlight(), true);
44
+ });
45
+
46
+ test("returns false after interactive tool completes", () => {
47
+ markToolStart("call-1", true, "ask_user_questions");
48
+ assert.equal(hasInteractiveToolInFlight(), true);
49
+ markToolEnd("call-1");
50
+ assert.equal(hasInteractiveToolInFlight(), false);
51
+ });
52
+
53
+ test("returns true if one of multiple tools is interactive", () => {
54
+ markToolStart("call-1", true, "bash");
55
+ markToolStart("call-2", true, "edit");
56
+ markToolStart("call-3", true, "ask_user_questions");
57
+ markToolStart("call-4", true, "write");
58
+ assert.equal(hasInteractiveToolInFlight(), true);
59
+ });
60
+ });
61
+
62
+ describe("toolName tracking in markToolStart", () => {
63
+ test("defaults toolName to 'unknown' when not provided", () => {
64
+ markToolStart("call-1", true);
65
+ // unknown tool should not be treated as interactive
66
+ assert.equal(hasInteractiveToolInFlight(), false);
67
+ assert.equal(getInFlightToolCount(), 1);
68
+ });
69
+
70
+ test("no-ops when isActive is false", () => {
71
+ markToolStart("call-1", false, "ask_user_questions");
72
+ assert.equal(getInFlightToolCount(), 0);
73
+ assert.equal(hasInteractiveToolInFlight(), false);
74
+ });
75
+ });
76
+
77
+ describe("existing tracking behavior preserved with toolName", () => {
78
+ test("getInFlightToolCount tracks correctly", () => {
79
+ assert.equal(getInFlightToolCount(), 0);
80
+ markToolStart("call-1", true, "bash");
81
+ assert.equal(getInFlightToolCount(), 1);
82
+ markToolStart("call-2", true, "ask_user_questions");
83
+ assert.equal(getInFlightToolCount(), 2);
84
+ markToolEnd("call-1");
85
+ assert.equal(getInFlightToolCount(), 1);
86
+ markToolEnd("call-2");
87
+ assert.equal(getInFlightToolCount(), 0);
88
+ });
89
+
90
+ test("getOldestInFlightToolStart returns correct timestamp", () => {
91
+ assert.equal(getOldestInFlightToolStart(), undefined);
92
+ const before = Date.now();
93
+ markToolStart("call-1", true, "bash");
94
+ const after = Date.now();
95
+ const oldest = getOldestInFlightToolStart();
96
+ assert.ok(oldest !== undefined);
97
+ assert.ok(oldest! >= before && oldest! <= after);
98
+ });
99
+
100
+ test("getOldestInFlightToolAgeMs returns 0 with no tools", () => {
101
+ assert.equal(getOldestInFlightToolAgeMs(), 0);
102
+ });
103
+
104
+ test("getOldestInFlightToolAgeMs returns positive value with tools", () => {
105
+ markToolStart("call-1", true, "read");
106
+ const age = getOldestInFlightToolAgeMs();
107
+ assert.ok(age >= 0, `age should be non-negative, got ${age}`);
108
+ });
109
+
110
+ test("clearInFlightTools resets all state", () => {
111
+ markToolStart("call-1", true, "ask_user_questions");
112
+ markToolStart("call-2", true, "bash");
113
+ assert.equal(getInFlightToolCount(), 2);
114
+ assert.equal(hasInteractiveToolInFlight(), true);
115
+ clearInFlightTools();
116
+ assert.equal(getInFlightToolCount(), 0);
117
+ assert.equal(hasInteractiveToolInFlight(), false);
118
+ });
119
+ });
@@ -189,7 +189,22 @@ test("getAllKeyStatuses detects empty keys as not configured", () => {
189
189
  const statuses = getAllKeyStatuses(auth);
190
190
  const groq = statuses.find((s) => s.provider.id === "groq");
191
191
  assert.equal(groq?.configured, false);
192
- assert.ok(groq?.description.includes("empty"));
192
+ // Empty-key entries are filtered out, so provider appears unconfigured
193
+ assert.equal(groq?.source, "none");
194
+ });
195
+
196
+ test("getAllKeyStatuses finds valid keys even when empty-key entry exists at index 0", () => {
197
+ const auth = makeAuth({
198
+ groq: [
199
+ { type: "api_key", key: "" },
200
+ { type: "api_key", key: "gsk-real-key" },
201
+ ],
202
+ });
203
+ const statuses = getAllKeyStatuses(auth);
204
+ const groq = statuses.find((s) => s.provider.id === "groq");
205
+ assert.equal(groq?.configured, true);
206
+ assert.equal(groq?.source, "auth.json");
207
+ assert.equal(groq?.credentialCount, 1); // only the valid key counts
193
208
  });
194
209
 
195
210
  test("getAllKeyStatuses detects env var keys", () => {
@@ -363,7 +363,7 @@ test('md-importer: schema v1→v2 migration', () => {
363
363
  openDatabase(':memory:');
364
364
  const adapter = _getAdapter();
365
365
  const version = adapter?.prepare('SELECT MAX(version) as v FROM schema_version').get();
366
- assert.deepStrictEqual(version?.v, 12, 'new DB should be at schema version 12');
366
+ assert.deepStrictEqual(version?.v, 14, 'new DB should be at schema version 14');
367
367
 
368
368
  // Artifacts table should exist
369
369
  const tableCheck = adapter?.prepare("SELECT count(*) as c FROM sqlite_master WHERE type='table' AND name='artifacts'").get();
@@ -323,9 +323,9 @@ test('memory-store: schema includes memories table', () => {
323
323
  const viewCount = adapter.prepare('SELECT count(*) as cnt FROM active_memories').get();
324
324
  assert.deepStrictEqual(viewCount?.['cnt'], 0, 'active_memories view should exist');
325
325
 
326
- // Verify schema version is 12 (after quality gates table)
326
+ // Verify schema version is 14 (after indexes + slice_dependencies)
327
327
  const version = adapter.prepare('SELECT MAX(version) as v FROM schema_version').get();
328
- assert.deepStrictEqual(version?.['v'], 12, 'schema version should be 12');
328
+ assert.deepStrictEqual(version?.['v'], 14, 'schema version should be 14');
329
329
 
330
330
  closeDatabase();
331
331
  });
@@ -8,7 +8,7 @@
8
8
  * Uses the writeRunnerPreferences pattern from doctor-git.test.ts:
9
9
  * PROJECT_PREFERENCES_PATH is a module-level constant frozen at import
10
10
  * time, so process.chdir() won't redirect preference loading. We write
11
- * prefs to the runner's cwd .gsd/preferences.md and clean up in finally.
11
+ * prefs to the runner's cwd .gsd/PREFERENCES.md and clean up in finally.
12
12
  */
13
13
 
14
14
  import { mkdirSync, writeFileSync, rmSync, existsSync } from "node:fs";
@@ -24,7 +24,7 @@ import assert from 'node:assert/strict';
24
24
 
25
25
  // --- Preferences helpers (same pattern as doctor-git.test.ts K001) ---
26
26
 
27
- const RUNNER_PREFS_PATH = join(process.cwd(), ".gsd", "preferences.md");
27
+ const RUNNER_PREFS_PATH = join(process.cwd(), ".gsd", "PREFERENCES.md");
28
28
 
29
29
  function writeRunnerPreferences(isolation: "none" | "worktree" | "branch"): void {
30
30
  mkdirSync(join(process.cwd(), ".gsd"), { recursive: true });
@@ -72,12 +72,12 @@ try {
72
72
 
73
73
  // Test 4: shouldUseWorktreeIsolation returns false for no prefs (default: none)
74
74
  // Worktree isolation requires explicit opt-in — default is "none" so GSD
75
- // works out of the box without preferences.md (#2480).
75
+ // works out of the box without PREFERENCES.md (#2480).
76
76
  // Skip if global prefs exist — they override the default and this test
77
- // cannot control ~/.gsd/preferences.md.
77
+ // cannot control ~/.gsd/PREFERENCES.md.
78
78
 
79
79
  test('shouldUseWorktreeIsolation returns false for no prefs (default: none)', () => {
80
- const globalPrefsExist = existsSync(join(homedir(), ".gsd", "preferences.md"))
80
+ const globalPrefsExist = existsSync(join(homedir(), ".gsd", "PREFERENCES.md"))
81
81
  || existsSync(join(homedir(), ".gsd", "PREFERENCES.md"));
82
82
  if (!globalPrefsExist) {
83
83
  try {
@@ -91,9 +91,9 @@ test('shouldUseWorktreeIsolation returns false for no prefs (default: none)', ()
91
91
  }
92
92
  });
93
93
 
94
- // Test 5: getIsolationMode returns "none" when no preferences.md exists (#2480)
94
+ // Test 5: getIsolationMode returns "none" when no PREFERENCES.md exists (#2480)
95
95
  test('getIsolationMode returns "none" with no prefs (default)', () => {
96
- const globalPrefsExist = existsSync(join(homedir(), ".gsd", "preferences.md"))
96
+ const globalPrefsExist = existsSync(join(homedir(), ".gsd", "PREFERENCES.md"))
97
97
  || existsSync(join(homedir(), ".gsd", "PREFERENCES.md"));
98
98
  if (!globalPrefsExist) {
99
99
  try {
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Regression test for #2694: parkMilestone and unparkMilestone must
3
+ * update the DB milestone status alongside the filesystem marker.
4
+ *
5
+ * Without this, deriveStateFromDb skips unparked milestones because
6
+ * the DB still has status='parked', causing "All milestones complete".
7
+ */
8
+ import { test } from "node:test";
9
+ import assert from "node:assert/strict";
10
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import { tmpdir } from "node:os";
13
+
14
+ import { parkMilestone, unparkMilestone } from "../milestone-actions.ts";
15
+ import {
16
+ openDatabase,
17
+ closeDatabase,
18
+ insertMilestone,
19
+ getMilestone,
20
+ } from "../gsd-db.ts";
21
+
22
+ function createBase(): string {
23
+ const base = mkdtempSync(join(tmpdir(), "gsd-park-db-"));
24
+ mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
25
+ writeFileSync(
26
+ join(base, ".gsd", "milestones", "M001", "M001-CONTEXT.md"),
27
+ "# M001\n\nContext.",
28
+ );
29
+ return base;
30
+ }
31
+
32
+ test("parkMilestone updates DB status to 'parked' (#2694)", () => {
33
+ const base = createBase();
34
+ try {
35
+ openDatabase(":memory:");
36
+ insertMilestone({ id: "M001", title: "Test", status: "active" });
37
+
38
+ assert.equal(getMilestone("M001")!.status, "active", "starts active");
39
+
40
+ parkMilestone(base, "M001", "deprioritized");
41
+
42
+ assert.equal(getMilestone("M001")!.status, "parked", "DB status should be parked");
43
+
44
+ closeDatabase();
45
+ } finally {
46
+ closeDatabase();
47
+ rmSync(base, { recursive: true, force: true });
48
+ }
49
+ });
50
+
51
+ test("unparkMilestone updates DB status to 'active' (#2694)", () => {
52
+ const base = createBase();
53
+ try {
54
+ openDatabase(":memory:");
55
+ insertMilestone({ id: "M001", title: "Test", status: "active" });
56
+
57
+ // Park first
58
+ parkMilestone(base, "M001", "deprioritized");
59
+ assert.equal(getMilestone("M001")!.status, "parked");
60
+
61
+ // Unpark
62
+ unparkMilestone(base, "M001");
63
+ assert.equal(getMilestone("M001")!.status, "active", "DB status should be active after unpark");
64
+
65
+ closeDatabase();
66
+ } finally {
67
+ closeDatabase();
68
+ rmSync(base, { recursive: true, force: true });
69
+ }
70
+ });
71
+
72
+ test("park/unpark are safe when DB is not available (#2694 guard)", () => {
73
+ const base = createBase();
74
+ try {
75
+ // No openDatabase — DB not available
76
+ // park/unpark should still work (filesystem-only, no throw)
77
+ const parked = parkMilestone(base, "M001", "test");
78
+ assert.ok(parked, "parkMilestone succeeds without DB");
79
+
80
+ const unparked = unparkMilestone(base, "M001");
81
+ assert.ok(unparked, "unparkMilestone succeeds without DB");
82
+ } finally {
83
+ rmSync(base, { recursive: true, force: true });
84
+ }
85
+ });
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Regression tests for #2684: preferences.md must be included in both
3
+ * ROOT_STATE_FILES (sync) and copyPlanningArtifacts (initial seed).
4
+ *
5
+ * Without this, post_unit_hooks and all preference-driven config silently
6
+ * stop working inside auto-mode worktrees.
7
+ */
8
+ import { test } from "node:test";
9
+ import assert from "node:assert/strict";
10
+ import { readFileSync, mkdtempSync, mkdirSync, writeFileSync, existsSync, rmSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import { tmpdir } from "node:os";
13
+
14
+ test("#2684: preferences.md is NOT in ROOT_STATE_FILES (forward-only sync)", () => {
15
+ const srcPath = join(import.meta.dirname, "..", "auto-worktree.ts");
16
+ const src = readFileSync(srcPath, "utf-8");
17
+
18
+ const constIdx = src.indexOf("ROOT_STATE_FILES");
19
+ assert.ok(constIdx !== -1, "ROOT_STATE_FILES constant exists");
20
+
21
+ const arrayStart = src.indexOf("[", constIdx);
22
+ const arrayEnd = src.indexOf("] as const", arrayStart);
23
+ const block = src.slice(arrayStart, arrayEnd);
24
+
25
+ // preferences.md must NOT be in ROOT_STATE_FILES — it is handled separately
26
+ // in syncGsdStateToWorktree() (forward-only, additive). Including it in
27
+ // ROOT_STATE_FILES would cause syncWorktreeStateBack() to overwrite the
28
+ // authoritative project root copy (#2684).
29
+ const entries = block.split("\n")
30
+ .map(l => l.trim())
31
+ .filter(l => l.startsWith('"') && l.includes(".md"));
32
+ const hasPrefs = entries.some(l => l.includes("preferences.md"));
33
+ assert.ok(
34
+ !hasPrefs,
35
+ "preferences.md must NOT be in ROOT_STATE_FILES (back-sync would overwrite root)",
36
+ );
37
+ });
38
+
39
+ test("#2684: copyPlanningArtifacts file list includes preferences.md", () => {
40
+ const srcPath = join(import.meta.dirname, "..", "auto-worktree.ts");
41
+ const src = readFileSync(srcPath, "utf-8");
42
+
43
+ // Find the copyPlanningArtifacts function body
44
+ const fnIdx = src.indexOf("function copyPlanningArtifacts");
45
+ assert.ok(fnIdx !== -1, "copyPlanningArtifacts function exists");
46
+
47
+ // Extract function body (up to the next top-level function)
48
+ const fnBody = src.slice(fnIdx, fnIdx + 1500);
49
+
50
+ assert.ok(
51
+ fnBody.includes('"preferences.md"'),
52
+ "preferences.md should be in copyPlanningArtifacts file list",
53
+ );
54
+ });
55
+
56
+ test("#2684: syncGsdStateToWorktree copies preferences.md", async () => {
57
+ // Functional test: create a mock source and destination, call the sync
58
+ const srcBase = mkdtempSync(join(tmpdir(), "gsd-wt-prefs-src-"));
59
+ const dstBase = mkdtempSync(join(tmpdir(), "gsd-wt-prefs-dst-"));
60
+ const srcGsd = join(srcBase, ".gsd");
61
+ const dstGsd = join(dstBase, ".gsd");
62
+ mkdirSync(srcGsd, { recursive: true });
63
+ mkdirSync(dstGsd, { recursive: true });
64
+
65
+ try {
66
+ // Write a preferences.md in source
67
+ writeFileSync(
68
+ join(srcGsd, "preferences.md"),
69
+ "---\nversion: 1\n---\n\npost_unit_hooks:\n - name: notify\n command: echo done\n",
70
+ );
71
+
72
+ // Import and call syncGsdStateToWorktree
73
+ const { syncGsdStateToWorktree } = await import("../auto-worktree.ts");
74
+ syncGsdStateToWorktree(srcBase, dstBase);
75
+
76
+ // Verify preferences.md was copied
77
+ assert.ok(
78
+ existsSync(join(dstGsd, "preferences.md")),
79
+ "preferences.md should be copied to worktree",
80
+ );
81
+
82
+ const content = readFileSync(join(dstGsd, "preferences.md"), "utf-8");
83
+ assert.ok(
84
+ content.includes("post_unit_hooks"),
85
+ "copied preferences.md should contain the hooks config",
86
+ );
87
+ } finally {
88
+ rmSync(srcBase, { recursive: true, force: true });
89
+ rmSync(dstBase, { recursive: true, force: true });
90
+ }
91
+ });
@@ -45,7 +45,7 @@ test("getIsolationMode defaults to none when preferences have no isolation setti
45
45
  // Validate the default via validatePreferences: when no isolation is set,
46
46
  // preferences.git.isolation is undefined, and getIsolationMode returns "none".
47
47
  // Default changed from "worktree" to "none" so GSD works out of the box
48
- // without preferences.md (#2480).
48
+ // without PREFERENCES.md (#2480).
49
49
  const { preferences } = validatePreferences({});
50
50
  assert.equal(preferences.git?.isolation, undefined, "no isolation in empty prefs");
51
51
  const isolation = preferences.git?.isolation;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Provider error handling tests — consolidated from:
3
- * - provider-error-classify.test.ts (classifyProviderError)
3
+ * - provider-error-classify.test.ts (classifyError)
4
4
  * - network-error-fallback.test.ts (isTransientNetworkError, getNextFallbackModel)
5
5
  * - agent-end-provider-error.test.ts (pauseAutoForProviderError)
6
6
  */
@@ -10,102 +10,111 @@ import assert from "node:assert/strict";
10
10
  import { readFileSync } from "node:fs";
11
11
  import { join, dirname } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
- import { classifyProviderError, pauseAutoForProviderError } from "../provider-error-pause.ts";
14
- import { getNextFallbackModel, isTransientNetworkError } from "../preferences.ts";
13
+ import { classifyError, isTransient, isTransientNetworkError } from "../error-classifier.ts";
14
+ import { pauseAutoForProviderError } from "../provider-error-pause.ts";
15
+ import { getNextFallbackModel } from "../preferences.ts";
15
16
 
16
17
  const __dirname = dirname(fileURLToPath(import.meta.url));
17
18
 
18
- // ── classifyProviderError ────────────────────────────────────────────────────
19
+ // ── classifyError ────────────────────────────────────────────────────────────
19
20
 
20
- test("classifyProviderError detects rate limit from 429", () => {
21
- const result = classifyProviderError("HTTP 429 Too Many Requests");
22
- assert.ok(result.isTransient);
23
- assert.ok(result.isRateLimit);
24
- assert.ok(result.suggestedDelayMs > 0);
21
+ test("classifyError detects rate limit from 429", () => {
22
+ const result = classifyError("HTTP 429 Too Many Requests");
23
+ assert.ok(isTransient(result));
24
+ assert.equal(result.kind, "rate-limit");
25
+ assert.ok("retryAfterMs" in result && result.retryAfterMs > 0);
25
26
  });
26
27
 
27
- test("classifyProviderError detects rate limit from message", () => {
28
- const result = classifyProviderError("rate limit exceeded");
29
- assert.ok(result.isTransient);
30
- assert.ok(result.isRateLimit);
28
+ test("classifyError detects rate limit from message", () => {
29
+ const result = classifyError("rate limit exceeded");
30
+ assert.ok(isTransient(result));
31
+ assert.equal(result.kind, "rate-limit");
31
32
  });
32
33
 
33
- test("classifyProviderError extracts reset delay from message", () => {
34
- const result = classifyProviderError("rate limit exceeded, reset in 45s");
35
- assert.ok(result.isRateLimit);
36
- assert.equal(result.suggestedDelayMs, 45000);
34
+ test("classifyError extracts reset delay from message", () => {
35
+ const result = classifyError("rate limit exceeded, reset in 45s");
36
+ assert.equal(result.kind, "rate-limit");
37
+ assert.ok("retryAfterMs" in result && result.retryAfterMs === 45000);
37
38
  });
38
39
 
39
- test("classifyProviderError defaults to 60s for rate limit without reset", () => {
40
- const result = classifyProviderError("429 too many requests");
41
- assert.ok(result.isRateLimit);
42
- assert.equal(result.suggestedDelayMs, 60_000);
40
+ test("classifyError defaults to 60s for rate limit without reset", () => {
41
+ const result = classifyError("429 too many requests");
42
+ assert.equal(result.kind, "rate-limit");
43
+ assert.ok("retryAfterMs" in result && result.retryAfterMs === 60_000);
43
44
  });
44
45
 
45
- test("classifyProviderError detects Anthropic internal server error", () => {
46
+ test("classifyError treats stream_exhausted_without_result as transient connection failure", () => {
47
+ const result = classifyError("stream_exhausted_without_result");
48
+ assert.ok(isTransient(result));
49
+ assert.equal(result.kind, "connection");
50
+ assert.ok("retryAfterMs" in result && result.retryAfterMs === 15_000);
51
+ });
52
+
53
+ test("classifyError detects Anthropic internal server error", () => {
46
54
  const msg = '{"type":"error","error":{"details":null,"type":"api_error","message":"Internal server error"}}';
47
- const result = classifyProviderError(msg);
48
- assert.ok(result.isTransient);
49
- assert.ok(!result.isRateLimit);
50
- assert.equal(result.suggestedDelayMs, 30_000);
55
+ const result = classifyError(msg);
56
+ assert.ok(isTransient(result));
57
+ assert.equal(result.kind, "server");
58
+ assert.ok("retryAfterMs" in result && result.retryAfterMs === 30_000);
51
59
  });
52
60
 
53
- test("classifyProviderError detects Codex server_error from extracted message", () => {
61
+ test("classifyError detects Codex server_error from extracted message", () => {
54
62
  // After fix, mapCodexEvents extracts the nested error type and produces
55
63
  // "Codex server_error: <message>" instead of raw JSON.
56
64
  const msg = "Codex server_error: An error occurred while processing your request.";
57
- const result = classifyProviderError(msg);
58
- assert.ok(result.isTransient);
59
- assert.ok(!result.isRateLimit);
60
- assert.equal(result.suggestedDelayMs, 30_000);
65
+ const result = classifyError(msg);
66
+ assert.ok(isTransient(result));
67
+ assert.equal(result.kind, "server");
68
+ assert.ok("retryAfterMs" in result && result.retryAfterMs === 30_000);
61
69
  });
62
70
 
63
- test("classifyProviderError detects overloaded error", () => {
64
- const result = classifyProviderError("overloaded_error: Overloaded");
65
- assert.ok(result.isTransient);
66
- assert.equal(result.suggestedDelayMs, 30_000);
71
+ test("classifyError detects overloaded error", () => {
72
+ const result = classifyError("overloaded_error: Overloaded");
73
+ assert.ok(isTransient(result));
74
+ assert.ok("retryAfterMs" in result && result.retryAfterMs === 30_000);
67
75
  });
68
76
 
69
- test("classifyProviderError detects 503 service unavailable", () => {
70
- const result = classifyProviderError("HTTP 503 Service Unavailable");
71
- assert.ok(result.isTransient);
77
+ test("classifyError detects 503 service unavailable", () => {
78
+ const result = classifyError("HTTP 503 Service Unavailable");
79
+ assert.ok(isTransient(result));
72
80
  });
73
81
 
74
- test("classifyProviderError detects 502 bad gateway", () => {
75
- const result = classifyProviderError("HTTP 502 Bad Gateway");
76
- assert.ok(result.isTransient);
82
+ test("classifyError detects 502 bad gateway", () => {
83
+ const result = classifyError("HTTP 502 Bad Gateway");
84
+ assert.ok(isTransient(result));
77
85
  });
78
86
 
79
- test("classifyProviderError detects auth error as permanent", () => {
80
- const result = classifyProviderError("unauthorized: invalid API key");
81
- assert.ok(!result.isTransient);
82
- assert.ok(!result.isRateLimit);
87
+ test("classifyError detects auth error as permanent", () => {
88
+ const result = classifyError("unauthorized: invalid API key");
89
+ assert.ok(!isTransient(result));
90
+ assert.equal(result.kind, "permanent");
83
91
  });
84
92
 
85
- test("classifyProviderError detects billing error as permanent", () => {
86
- const result = classifyProviderError("billing issue: payment required");
87
- assert.ok(!result.isTransient);
93
+ test("classifyError detects billing error as permanent", () => {
94
+ const result = classifyError("billing issue: payment required");
95
+ assert.ok(!isTransient(result));
88
96
  });
89
97
 
90
- test("classifyProviderError detects quota exceeded as permanent", () => {
91
- const result = classifyProviderError("quota exceeded for this month");
92
- assert.ok(!result.isTransient);
98
+ test("classifyError detects quota exceeded as permanent", () => {
99
+ const result = classifyError("quota exceeded for this month");
100
+ assert.ok(!isTransient(result));
93
101
  });
94
102
 
95
- test("classifyProviderError treats unknown error as permanent", () => {
96
- const result = classifyProviderError("something went wrong");
97
- assert.ok(!result.isTransient);
103
+ test("classifyError treats unknown error as not transient", () => {
104
+ const result = classifyError("something went wrong");
105
+ assert.ok(!isTransient(result));
106
+ assert.equal(result.kind, "unknown");
98
107
  });
99
108
 
100
- test("classifyProviderError treats empty string as permanent", () => {
101
- const result = classifyProviderError("");
102
- assert.ok(!result.isTransient);
109
+ test("classifyError treats empty string as not transient", () => {
110
+ const result = classifyError("");
111
+ assert.ok(!isTransient(result));
103
112
  });
104
113
 
105
- test("classifyProviderError: rate limit takes precedence over auth keywords", () => {
106
- const result = classifyProviderError("429 unauthorized rate limit");
107
- assert.ok(result.isRateLimit);
108
- assert.ok(result.isTransient);
114
+ test("classifyError: rate limit takes precedence over auth keywords", () => {
115
+ const result = classifyError("429 unauthorized rate limit");
116
+ assert.equal(result.kind, "rate-limit");
117
+ assert.ok(isTransient(result));
109
118
  });
110
119
 
111
120
  // ── isTransientNetworkError ──────────────────────────────────────────────────
@@ -265,8 +274,8 @@ test("agent-end-recovery.ts tracks consecutive transient errors for escalating b
265
274
  const src = readFileSync(join(__dirname, "..", "bootstrap", "agent-end-recovery.ts"), "utf-8");
266
275
 
267
276
  assert.ok(
268
- src.includes("consecutiveTransientErrors"),
269
- "agent-end-recovery.ts must track consecutiveTransientErrors for escalating backoff (#1166)",
277
+ src.includes("consecutiveTransientCount"),
278
+ "agent-end-recovery.ts must track consecutiveTransientCount for escalating backoff (#1166)",
270
279
  );
271
280
  assert.ok(
272
281
  src.includes("MAX_TRANSIENT_AUTO_RESUMES"),
@@ -274,15 +283,13 @@ test("agent-end-recovery.ts tracks consecutive transient errors for escalating b
274
283
  );
275
284
  });
276
285
 
277
- test("agent-end-recovery.ts resets consecutive transient error counter on success", () => {
286
+ test("agent-end-recovery.ts resets retry state before resolveAgentEnd on success", () => {
278
287
  const src = readFileSync(join(__dirname, "..", "bootstrap", "agent-end-recovery.ts"), "utf-8");
279
288
 
280
- // After successful agent_end (before resolveAgentEnd), the counter must be reset.
281
- // Use a regex across the success block so CRLF checkouts on Windows do not
282
- // push the reset line outside a fixed substring window.
289
+ // After successful agent_end, resetRetryState must be called before resolveAgentEnd.
283
290
  assert.ok(
284
- /consecutiveTransientErrors\s*=\s*0\s*;[\s\S]{0,250}resolveAgentEnd/.test(src),
285
- "consecutive transient error counter must be reset before resolveAgentEnd on the success path (#1166)",
291
+ /resetRetryState[\s\S]{0,250}resolveAgentEnd/.test(src),
292
+ "resetRetryState must be called before resolveAgentEnd on the success path (#1166)",
286
293
  );
287
294
  });
288
295
 
@@ -291,7 +298,7 @@ test("agent-end-recovery.ts applies escalating delay for repeated transient erro
291
298
 
292
299
  // Must contain the exponential backoff formula (may span multiple lines)
293
300
  assert.ok(
294
- src.includes("2 ** Math.max(0, consecutiveTransientErrors"),
301
+ src.includes("2 ** Math.max(0, retryState.consecutiveTransientCount"),
295
302
  "agent-end-recovery.ts must escalate retryAfterMs exponentially for consecutive transient errors (#1166)",
296
303
  );
297
304
  });