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
@@ -9,7 +9,7 @@ import { readUnitRuntimeRecord, writeUnitRuntimeRecord } from "./unit-runtime.js
9
9
  import { isDbAvailable, getMilestoneSlices, getSliceTasks } from "./gsd-db.js";
10
10
  import { resolveAutoSupervisorConfig } from "./preferences.js";
11
11
  import { computeBudgets, resolveExecutorContextWindow } from "./context-budget.js";
12
- import { getInFlightToolCount, getOldestInFlightToolStart, } from "./auto-tool-tracking.js";
12
+ import { getInFlightToolCount, getOldestInFlightToolStart, clearInFlightTools, hasInteractiveToolInFlight, } from "./auto-tool-tracking.js";
13
13
  import { detectWorkingTreeActivity } from "./auto-supervisor.js";
14
14
  import { closeoutUnit } from "./auto-unit-closeout.js";
15
15
  import { saveActivityLog } from "./activity-log.js";
@@ -116,7 +116,17 @@ export function startUnitSupervision(sctx) {
116
116
  return;
117
117
  // Agent has tool calls currently executing — not idle, just waiting.
118
118
  // But only suppress recovery if the tool started recently.
119
+ let stalledToolDetected = false;
119
120
  if (getInFlightToolCount() > 0) {
121
+ // User-interactive tools (ask_user_questions, secure_env_collect) block
122
+ // waiting for human input by design — never treat them as stalled (#2676).
123
+ if (hasInteractiveToolInFlight()) {
124
+ writeUnitRuntimeRecord(s.basePath, unitType, unitId, s.currentUnit.startedAt, {
125
+ lastProgressAt: Date.now(),
126
+ lastProgressKind: "interactive-tool-waiting",
127
+ });
128
+ return;
129
+ }
120
130
  const oldestStart = getOldestInFlightToolStart();
121
131
  const toolAgeMs = Date.now() - oldestStart;
122
132
  if (toolAgeMs < idleTimeoutMs) {
@@ -126,10 +136,18 @@ export function startUnitSupervision(sctx) {
126
136
  });
127
137
  return;
128
138
  }
139
+ // Tool has been in-flight longer than idle timeout — treat as hung.
140
+ // Clear the stale entries so subsequent ticks don't re-detect them,
141
+ // and set the flag so the filesystem-activity check below does not
142
+ // override the stall verdict (#2527).
143
+ stalledToolDetected = true;
144
+ clearInFlightTools();
129
145
  ctx.ui.notify(`Stalled tool detected: a tool has been in-flight for ${Math.round(toolAgeMs / 60000)}min. Treating as hung — attempting idle recovery.`, "warning");
130
146
  }
131
147
  // Check if the agent is producing work on disk.
132
- if (detectWorkingTreeActivity(s.basePath)) {
148
+ // Skip this when a stalled tool was just detected — filesystem changes
149
+ // from earlier in the task should not override the stall verdict (#2527).
150
+ if (!stalledToolDetected && detectWorkingTreeActivity(s.basePath)) {
133
151
  writeUnitRuntimeRecord(s.basePath, unitType, unitId, s.currentUnit.startedAt, {
134
152
  lastProgressAt: Date.now(),
135
153
  lastProgressKind: "filesystem-activity",
@@ -145,6 +163,10 @@ export function startUnitSupervision(sctx) {
145
163
  const recovery = await recoverTimedOutUnit(ctx, pi, unitType, unitId, "idle", buildRecoveryContext());
146
164
  if (recovery === "recovered")
147
165
  return;
166
+ // Guard: recoverTimedOutUnit is async — pauseAuto/stopAuto may have
167
+ // set s.currentUnit = null during the await (#2527).
168
+ if (!s.currentUnit)
169
+ return;
148
170
  writeUnitRuntimeRecord(s.basePath, unitType, unitId, s.currentUnit.startedAt, {
149
171
  phase: "paused",
150
172
  });
@@ -4,14 +4,20 @@
4
4
  * can distinguish "waiting for tool completion" from "truly idle".
5
5
  */
6
6
  const inFlightTools = new Map();
7
+ /**
8
+ * Tools that block waiting for human input by design.
9
+ * The idle watchdog must not treat these as stalled.
10
+ */
11
+ const INTERACTIVE_TOOLS = new Set(["ask_user_questions", "secure_env_collect"]);
7
12
  /**
8
13
  * Mark a tool execution as in-flight.
9
- * Records start time so the idle watchdog can detect tools hung longer than the idle timeout.
14
+ * Records start time and tool name so the idle watchdog can detect tools
15
+ * hung longer than the idle timeout while exempting interactive tools.
10
16
  */
11
- export function markToolStart(toolCallId, isActive) {
17
+ export function markToolStart(toolCallId, isActive, toolName) {
12
18
  if (!isActive)
13
19
  return;
14
- inFlightTools.set(toolCallId, Date.now());
20
+ inFlightTools.set(toolCallId, { startedAt: Date.now(), toolName: toolName ?? "unknown" });
15
21
  }
16
22
  /**
17
23
  * Mark a tool execution as completed.
@@ -27,8 +33,8 @@ export function getOldestInFlightToolAgeMs() {
27
33
  return 0;
28
34
  let oldestStart = Infinity;
29
35
  for (const t of inFlightTools.values()) {
30
- if (t < oldestStart)
31
- oldestStart = t;
36
+ if (t.startedAt < oldestStart)
37
+ oldestStart = t.startedAt;
32
38
  }
33
39
  return Date.now() - oldestStart;
34
40
  }
@@ -46,11 +52,23 @@ export function getOldestInFlightToolStart() {
46
52
  return undefined;
47
53
  let oldest = Infinity;
48
54
  for (const t of inFlightTools.values()) {
49
- if (t < oldest)
50
- oldest = t;
55
+ if (t.startedAt < oldest)
56
+ oldest = t.startedAt;
51
57
  }
52
58
  return oldest;
53
59
  }
60
+ /**
61
+ * Returns true if any currently in-flight tool is a user-interactive tool
62
+ * (e.g. ask_user_questions, secure_env_collect) that blocks waiting for
63
+ * human input. These must be exempt from idle stall detection.
64
+ */
65
+ export function hasInteractiveToolInFlight() {
66
+ for (const { toolName } of inFlightTools.values()) {
67
+ if (INTERACTIVE_TOOLS.has(toolName))
68
+ return true;
69
+ }
70
+ return false;
71
+ }
54
72
  /**
55
73
  * Clear all in-flight tool tracking state.
56
74
  */
@@ -37,6 +37,10 @@ const ROOT_STATE_FILES = [
37
37
  "QUEUE.md",
38
38
  "completed-units.json",
39
39
  "metrics.json",
40
+ // NOTE: preferences.md is intentionally NOT in ROOT_STATE_FILES.
41
+ // Forward-sync (main → worktree) is handled explicitly in syncGsdStateToWorktree().
42
+ // Back-sync (worktree → main) must NEVER overwrite the project root's copy
43
+ // because the project root is authoritative for preferences (#2684).
40
44
  ];
41
45
  /**
42
46
  * Check if two filesystem paths resolve to the same real location.
@@ -319,6 +323,22 @@ export function syncGsdStateToWorktree(mainBasePath, worktreePath_) {
319
323
  }
320
324
  }
321
325
  }
326
+ // Forward-sync preferences.md from project root to worktree (additive only).
327
+ // NOT in ROOT_STATE_FILES because syncWorktreeStateBack() must never overwrite
328
+ // the project root's preferences — the project root is authoritative (#2684).
329
+ {
330
+ const src = join(mainGsd, "preferences.md");
331
+ const dst = join(wtGsd, "preferences.md");
332
+ if (existsSync(src) && !existsSync(dst)) {
333
+ try {
334
+ cpSync(src, dst);
335
+ synced.push("preferences.md");
336
+ }
337
+ catch {
338
+ /* non-fatal */
339
+ }
340
+ }
341
+ }
322
342
  // Sync milestones: copy entire milestone directories that are missing
323
343
  const mainMilestonesDir = join(mainGsd, "milestones");
324
344
  const wtMilestonesDir = join(wtGsd, "milestones");
@@ -801,6 +821,7 @@ function copyPlanningArtifacts(srcBase, wtPath) {
801
821
  "STATE.md",
802
822
  "KNOWLEDGE.md",
803
823
  "OVERRIDES.md",
824
+ "preferences.md",
804
825
  ]) {
805
826
  safeCopy(join(srcGsd, file), join(dstGsd, file), { force: true });
806
827
  }
@@ -34,6 +34,7 @@ import { clearSkillSnapshot } from "./skill-discovery.js";
34
34
  import { captureAvailableSkills, resetSkillTelemetry, } from "./skill-telemetry.js";
35
35
  import { getRtkSessionSavings } from "../shared/rtk-session-stats.js";
36
36
  import { initMetrics, resetMetrics, getLedger, getProjectTotals, formatCost, formatTokenCount, } from "./metrics.js";
37
+ import { setLogBasePath } from "./workflow-logger.js";
37
38
  import { join } from "node:path";
38
39
  import { readFileSync, existsSync, mkdirSync, writeFileSync, unlinkSync } from "node:fs";
39
40
  import { atomicWriteSync } from "./atomic-write.js";
@@ -184,8 +185,8 @@ export function getAutoModeStartModel() {
184
185
  return s.autoModeStartModel;
185
186
  }
186
187
  // Tool tracking — delegates to auto-tool-tracking.ts
187
- export function markToolStart(toolCallId) {
188
- _markToolStart(toolCallId, s.active);
188
+ export function markToolStart(toolCallId, toolName) {
189
+ _markToolStart(toolCallId, s.active, toolName);
189
190
  }
190
191
  export function markToolEnd(toolCallId) {
191
192
  _markToolEnd(toolCallId);
@@ -815,6 +816,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
815
816
  s.stepMode = requestedStepMode;
816
817
  s.cmdCtx = ctx;
817
818
  s.basePath = base;
819
+ setLogBasePath(base);
818
820
  s.unitDispatchCount.clear();
819
821
  s.unitLifetimeDispatches.clear();
820
822
  if (!getLedger())
@@ -1,12 +1,33 @@
1
1
  import { checkAutoStartAfterDiscuss } from "../guided-flow.js";
2
2
  import { getAutoDashboardData, getAutoModeStartModel, isAutoActive, pauseAuto } from "../auto.js";
3
- import { getNextFallbackModel, isTransientNetworkError, resolveModelWithFallbacksForUnit } from "../preferences.js";
4
- import { classifyProviderError, pauseAutoForProviderError } from "../provider-error-pause.js";
3
+ import { getNextFallbackModel, resolveModelWithFallbacksForUnit } from "../preferences.js";
4
+ import { pauseAutoForProviderError } from "../provider-error-pause.js";
5
5
  import { isSessionSwitchInFlight, resolveAgentEnd } from "../auto-loop.js";
6
+ import { resolveModelId } from "../auto-model-selection.js";
6
7
  import { clearDiscussionFlowState } from "./write-gate.js";
7
- const networkRetryCounters = new Map();
8
+ import { classifyError, createRetryState, resetRetryState, isTransient, } from "../error-classifier.js";
9
+ const retryState = createRetryState();
10
+ const MAX_NETWORK_RETRIES = 2;
8
11
  const MAX_TRANSIENT_AUTO_RESUMES = 3;
9
- let consecutiveTransientErrors = 0;
12
+ async function pauseTransientWithBackoff(cls, pi, ctx, errorDetail, isRateLimit) {
13
+ retryState.consecutiveTransientCount += 1;
14
+ const baseRetryAfterMs = "retryAfterMs" in cls ? cls.retryAfterMs : 15_000;
15
+ const retryAfterMs = baseRetryAfterMs * 2 ** Math.max(0, retryState.consecutiveTransientCount - 1);
16
+ const allowAutoResume = retryState.consecutiveTransientCount <= MAX_TRANSIENT_AUTO_RESUMES;
17
+ if (!allowAutoResume) {
18
+ ctx.ui.notify(`Transient provider errors persisted after ${MAX_TRANSIENT_AUTO_RESUMES} auto-resume attempts. Pausing for manual review.`, "warning");
19
+ }
20
+ await pauseAutoForProviderError(ctx.ui, errorDetail, () => pauseAuto(ctx, pi), {
21
+ isRateLimit,
22
+ isTransient: allowAutoResume,
23
+ retryAfterMs,
24
+ resume: allowAutoResume
25
+ ? () => {
26
+ pi.sendMessage({ customType: "gsd-auto-timeout-recovery", content: "Continue execution — provider error recovery delay elapsed.", display: false }, { triggerTurn: true });
27
+ }
28
+ : undefined,
29
+ });
30
+ }
10
31
  export async function handleAgentEnd(pi, event, ctx) {
11
32
  if (checkAutoStartAfterDiscuss()) {
12
33
  clearDiscussionFlowState();
@@ -24,93 +45,98 @@ export async function handleAgentEnd(pi, event, ctx) {
24
45
  if (lastMsg && "stopReason" in lastMsg && lastMsg.stopReason === "error") {
25
46
  const errorDetail = "errorMessage" in lastMsg && lastMsg.errorMessage ? `: ${lastMsg.errorMessage}` : "";
26
47
  const errorMsg = ("errorMessage" in lastMsg && lastMsg.errorMessage) ? String(lastMsg.errorMessage) : "";
27
- if (isTransientNetworkError(errorMsg)) {
48
+ const explicitRetryAfterMs = ("retryAfterMs" in lastMsg && typeof lastMsg.retryAfterMs === "number") ? lastMsg.retryAfterMs : undefined;
49
+ // ── 1. Classify ──────────────────────────────────────────────────────
50
+ const cls = classifyError(errorMsg, explicitRetryAfterMs);
51
+ // ── 2. Decide & Act ──────────────────────────────────────────────────
52
+ // --- Network errors: same-model retry with backoff ---
53
+ if (cls.kind === "network") {
28
54
  const currentModelId = ctx.model?.id ?? "unknown";
29
- const retryKey = `network-retry:${currentModelId}`;
30
- const currentRetries = networkRetryCounters.get(retryKey) ?? 0;
31
- const maxRetries = 2;
32
- if (currentRetries < maxRetries) {
33
- networkRetryCounters.set(retryKey, currentRetries + 1);
34
- const attempt = currentRetries + 1;
35
- const delayMs = attempt * 3000;
36
- ctx.ui.notify(`Network error on ${currentModelId}${errorDetail}. Retry ${attempt}/${maxRetries} in ${delayMs / 1000}s...`, "warning");
55
+ if (retryState.currentRetryModelId !== currentModelId) {
56
+ retryState.networkRetryCount = 0;
57
+ retryState.currentRetryModelId = currentModelId;
58
+ }
59
+ if (retryState.networkRetryCount < MAX_NETWORK_RETRIES) {
60
+ retryState.networkRetryCount += 1;
61
+ retryState.consecutiveTransientCount += 1;
62
+ const attempt = retryState.networkRetryCount;
63
+ const delayMs = attempt * cls.retryAfterMs;
64
+ ctx.ui.notify(`Network error on ${currentModelId}${errorDetail}. Retry ${attempt}/${MAX_NETWORK_RETRIES} in ${delayMs / 1000}s...`, "warning");
37
65
  setTimeout(() => {
38
66
  pi.sendMessage({ customType: "gsd-auto-timeout-recovery", content: "Continue execution — retrying after transient network error.", display: false }, { triggerTurn: true });
39
67
  }, delayMs);
40
68
  return;
41
69
  }
42
- networkRetryCounters.delete(retryKey);
70
+ // Network retries exhausted — fall through to model fallback
71
+ retryState.networkRetryCount = 0;
72
+ retryState.currentRetryModelId = undefined;
43
73
  ctx.ui.notify(`Network retries exhausted for ${currentModelId}. Attempting model fallback.`, "warning");
44
74
  }
45
- const dash = getAutoDashboardData();
46
- if (dash.currentUnit) {
47
- const modelConfig = resolveModelWithFallbacksForUnit(dash.currentUnit.type);
48
- if (modelConfig && modelConfig.fallbacks.length > 0) {
49
- const availableModels = ctx.modelRegistry.getAvailable();
50
- const nextModelId = getNextFallbackModel(ctx.model?.id, modelConfig);
51
- if (nextModelId) {
52
- networkRetryCounters.clear();
53
- const slashIdx = nextModelId.indexOf("/");
54
- const modelToSet = slashIdx !== -1
55
- ? availableModels.find((m) => m.provider.toLowerCase() === nextModelId.substring(0, slashIdx).toLowerCase() && m.id.toLowerCase() === nextModelId.substring(slashIdx + 1).toLowerCase())
56
- : (availableModels.find((m) => m.id === nextModelId && m.provider === ctx.model?.provider) ?? availableModels.find((m) => m.id === nextModelId));
57
- if (modelToSet) {
58
- const ok = await pi.setModel(modelToSet, { persist: false });
59
- if (ok) {
60
- ctx.ui.notify(`Model error${errorDetail}. Switched to fallback: ${nextModelId} and resuming.`, "warning");
61
- pi.sendMessage({ customType: "gsd-auto-timeout-recovery", content: "Continue execution.", display: false }, { triggerTurn: true });
62
- return;
75
+ // --- Rate limit: skip model fallback, go straight to pause ---
76
+ // Rate-limiting is a provider issue, not a model issue.
77
+ // Switching models won't help if the provider is throttling you.
78
+ if (cls.kind === "rate-limit") {
79
+ await pauseTransientWithBackoff(cls, pi, ctx, errorDetail, true);
80
+ return;
81
+ }
82
+ // --- Server/connection/stream errors: try model fallback first ---
83
+ if (cls.kind === "network" || cls.kind === "server" || cls.kind === "connection" || cls.kind === "stream") {
84
+ // Try model fallback
85
+ const dash = getAutoDashboardData();
86
+ if (dash.currentUnit) {
87
+ const modelConfig = resolveModelWithFallbacksForUnit(dash.currentUnit.type);
88
+ if (modelConfig && modelConfig.fallbacks.length > 0) {
89
+ const availableModels = ctx.modelRegistry.getAvailable();
90
+ const nextModelId = getNextFallbackModel(ctx.model?.id, modelConfig);
91
+ if (nextModelId) {
92
+ retryState.networkRetryCount = 0;
93
+ retryState.currentRetryModelId = undefined;
94
+ const modelToSet = resolveModelId(nextModelId, availableModels, ctx.model?.provider);
95
+ if (modelToSet) {
96
+ const ok = await pi.setModel(modelToSet, { persist: false });
97
+ if (ok) {
98
+ ctx.ui.notify(`Model error${errorDetail}. Switched to fallback: ${nextModelId} and resuming.`, "warning");
99
+ pi.sendMessage({ customType: "gsd-auto-timeout-recovery", content: "Continue execution.", display: false }, { triggerTurn: true });
100
+ return;
101
+ }
63
102
  }
64
103
  }
65
104
  }
66
105
  }
67
- }
68
- const sessionModel = getAutoModeStartModel();
69
- if (sessionModel) {
70
- if (ctx.model?.id !== sessionModel.id || ctx.model?.provider !== sessionModel.provider) {
71
- const startModel = ctx.modelRegistry.getAvailable().find((m) => m.provider === sessionModel.provider && m.id === sessionModel.id);
72
- if (startModel) {
73
- const ok = await pi.setModel(startModel, { persist: false });
74
- if (ok) {
75
- networkRetryCounters.clear();
76
- ctx.ui.notify(`Model error${errorDetail}. Restored session model: ${sessionModel.provider}/${sessionModel.id} and resuming.`, "warning");
77
- pi.sendMessage({ customType: "gsd-auto-timeout-recovery", content: "Continue execution.", display: false }, { triggerTurn: true });
78
- return;
106
+ // Try restoring session model
107
+ const sessionModel = getAutoModeStartModel();
108
+ if (sessionModel) {
109
+ if (ctx.model?.id !== sessionModel.id || ctx.model?.provider !== sessionModel.provider) {
110
+ const startModel = ctx.modelRegistry.getAvailable().find((m) => m.provider === sessionModel.provider && m.id === sessionModel.id);
111
+ if (startModel) {
112
+ const ok = await pi.setModel(startModel, { persist: false });
113
+ if (ok) {
114
+ retryState.networkRetryCount = 0;
115
+ retryState.currentRetryModelId = undefined;
116
+ ctx.ui.notify(`Model error${errorDetail}. Restored session model: ${sessionModel.provider}/${sessionModel.id} and resuming.`, "warning");
117
+ pi.sendMessage({ customType: "gsd-auto-timeout-recovery", content: "Continue execution.", display: false }, { triggerTurn: true });
118
+ return;
119
+ }
79
120
  }
80
121
  }
81
122
  }
82
123
  }
83
- const classification = classifyProviderError(errorMsg);
84
- const explicitRetryAfterMs = ("retryAfterMs" in lastMsg && typeof lastMsg.retryAfterMs === "number") ? lastMsg.retryAfterMs : undefined;
85
- if (classification.isTransient) {
86
- consecutiveTransientErrors += 1;
87
- }
88
- else {
89
- consecutiveTransientErrors = 0;
90
- }
91
- const baseRetryAfterMs = explicitRetryAfterMs ?? classification.suggestedDelayMs;
92
- const retryAfterMs = classification.isTransient
93
- ? baseRetryAfterMs * 2 ** Math.max(0, consecutiveTransientErrors - 1)
94
- : baseRetryAfterMs;
95
- const allowAutoResume = classification.isTransient && consecutiveTransientErrors <= MAX_TRANSIENT_AUTO_RESUMES;
96
- if (classification.isTransient && !allowAutoResume) {
97
- ctx.ui.notify(`Transient provider errors persisted after ${MAX_TRANSIENT_AUTO_RESUMES} auto-resume attempts. Pausing for manual review.`, "warning");
124
+ // --- Transient fallback: pause with auto-resume ---
125
+ if (isTransient(cls)) {
126
+ await pauseTransientWithBackoff(cls, pi, ctx, errorDetail, false);
127
+ return;
98
128
  }
129
+ // --- Permanent / unknown: pause indefinitely ---
99
130
  await pauseAutoForProviderError(ctx.ui, errorDetail, () => pauseAuto(ctx, pi), {
100
- isRateLimit: classification.isRateLimit,
101
- isTransient: allowAutoResume,
102
- retryAfterMs,
103
- resume: allowAutoResume
104
- ? () => {
105
- pi.sendMessage({ customType: "gsd-auto-timeout-recovery", content: "Continue execution — provider error recovery delay elapsed.", display: false }, { triggerTurn: true });
106
- }
107
- : undefined,
131
+ isRateLimit: false,
132
+ isTransient: false,
133
+ retryAfterMs: 0,
108
134
  });
109
135
  return;
110
136
  }
137
+ // ── Success path ─────────────────────────────────────────────────────────
111
138
  try {
112
- consecutiveTransientErrors = 0;
113
- networkRetryCounters.clear();
139
+ resetRetryState(retryState);
114
140
  resolveAgentEnd(event);
115
141
  }
116
142
  catch (err) {
@@ -2,6 +2,7 @@ import { existsSync } from "node:fs";
2
2
  import { join, sep } from "node:path";
3
3
  import { createBashTool, createEditTool, createReadTool, createWriteTool } from "@gsd/pi-coding-agent";
4
4
  import { DEFAULT_BASH_TIMEOUT_SECS } from "../constants.js";
5
+ import { setLogBasePath } from "../workflow-logger.js";
5
6
  /**
6
7
  * Resolve the correct DB path for the current working directory.
7
8
  * If `basePath` is inside a `.gsd/worktrees/<MID>/` directory, returns
@@ -35,9 +36,14 @@ export async function ensureDbOpen() {
35
36
  const basePath = process.cwd();
36
37
  const dbPath = resolveProjectRootDbPath(basePath);
37
38
  const gsdDir = join(basePath, ".gsd");
39
+ // Derive the project root from the DB path (strip .gsd/gsd.db)
40
+ const projectRoot = join(dbPath, "..", "..");
38
41
  // Open existing DB file (may be at project root for worktrees)
39
42
  if (existsSync(dbPath)) {
40
- return db.openDatabase(dbPath);
43
+ const opened = db.openDatabase(dbPath);
44
+ if (opened)
45
+ setLogBasePath(projectRoot);
46
+ return opened;
41
47
  }
42
48
  // No DB file — create + migrate from Markdown if .gsd/ has content
43
49
  if (existsSync(gsdDir)) {
@@ -47,6 +53,7 @@ export async function ensureDbOpen() {
47
53
  if (hasDecisions || hasRequirements || hasMilestones) {
48
54
  const opened = db.openDatabase(dbPath);
49
55
  if (opened) {
56
+ setLogBasePath(projectRoot);
50
57
  try {
51
58
  const { migrateFromMarkdown } = await import("../md-importer.js");
52
59
  migrateFromMarkdown(basePath);
@@ -58,7 +65,10 @@ export async function ensureDbOpen() {
58
65
  return opened;
59
66
  }
60
67
  // .gsd/ exists but has no Markdown content (fresh project) — create empty DB
61
- return db.openDatabase(dbPath);
68
+ const opened = db.openDatabase(dbPath);
69
+ if (opened)
70
+ setLogBasePath(projectRoot);
71
+ return opened;
62
72
  }
63
73
  return false;
64
74
  }
@@ -227,7 +227,7 @@ export function registerHooks(pi) {
227
227
  pi.on("tool_execution_start", async (event) => {
228
228
  if (!isAutoActive())
229
229
  return;
230
- markToolStart(event.toolCallId);
230
+ markToolStart(event.toolCallId, event.toolName);
231
231
  });
232
232
  pi.on("tool_execution_end", async (event) => {
233
233
  markToolEnd(event.toolCallId);
@@ -79,16 +79,50 @@ function isMarketplacePath(pluginPath) {
79
79
  }
80
80
  /**
81
81
  * Detect which plugin roots are marketplaces and which are legacy flat paths.
82
+ *
83
+ * Claude Code stores marketplace sources under ~/.claude/plugins/marketplaces/.
84
+ * Each subdirectory (e.g. marketplaces/confluent/) is a marketplace repo that
85
+ * contains .claude-plugin/marketplace.json. The parent directory itself does not
86
+ * have a marketplace.json, so we scan one level deeper when the root isn't
87
+ * directly a marketplace.
82
88
  */
83
- function categorizePluginRoots(pluginRoots) {
89
+ export function categorizePluginRoots(pluginRoots) {
84
90
  const marketplaces = [];
85
91
  const flat = [];
92
+ const seen = new Set();
86
93
  for (const root of pluginRoots) {
87
94
  if (isMarketplacePath(root)) {
88
- marketplaces.push(root);
95
+ if (!seen.has(root)) {
96
+ marketplaces.push(root);
97
+ seen.add(root);
98
+ }
89
99
  }
90
100
  else {
91
- flat.push(root);
101
+ // The root itself isn't a marketplace — check if it's a container of
102
+ // marketplaces (e.g. ~/.claude/plugins/marketplaces/ contains subdirs
103
+ // like confluent/, claude-hud/, each with their own marketplace.json).
104
+ let foundChild = false;
105
+ try {
106
+ const entries = readdirSync(root, { withFileTypes: true });
107
+ for (const entry of entries) {
108
+ if (!entry.isDirectory())
109
+ continue;
110
+ if (SKIP_DIRS.has(entry.name))
111
+ continue;
112
+ const childPath = join(root, entry.name);
113
+ if (isMarketplacePath(childPath) && !seen.has(childPath)) {
114
+ marketplaces.push(childPath);
115
+ seen.add(childPath);
116
+ foundChild = true;
117
+ }
118
+ }
119
+ }
120
+ catch {
121
+ // Can't read directory — fall through to flat
122
+ }
123
+ if (!foundChild) {
124
+ flat.push(root);
125
+ }
92
126
  }
93
127
  }
94
128
  return { marketplaces, flat };
@@ -145,20 +179,37 @@ export function discoverClaudePlugins(cwd) {
145
179
  const seen = new Set();
146
180
  for (const root of pluginRoots) {
147
181
  walkDirs(root, (dir) => {
182
+ // Recognize both npm-style plugins (package.json) and Claude Code plugins
183
+ // (.claude-plugin/plugin.json). Claude marketplace-installed plugins use
184
+ // the latter format exclusively.
148
185
  const pkgPath = join(dir, "package.json");
149
- if (!existsSync(pkgPath))
186
+ const claudePluginPath = join(dir, ".claude-plugin", "plugin.json");
187
+ const hasPkg = existsSync(pkgPath);
188
+ const hasClaudePlugin = existsSync(claudePluginPath);
189
+ if (!hasPkg && !hasClaudePlugin)
150
190
  return;
151
191
  const resolvedDir = resolve(dir);
152
192
  if (seen.has(resolvedDir))
153
193
  return;
154
194
  seen.add(resolvedDir);
155
195
  let packageName;
156
- try {
157
- const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
158
- packageName = pkg.name;
196
+ if (hasPkg) {
197
+ try {
198
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
199
+ packageName = pkg.name;
200
+ }
201
+ catch {
202
+ packageName = undefined;
203
+ }
159
204
  }
160
- catch {
161
- packageName = undefined;
205
+ else if (hasClaudePlugin) {
206
+ try {
207
+ const manifest = JSON.parse(readFileSync(claudePluginPath, "utf8"));
208
+ packageName = manifest.name;
209
+ }
210
+ catch {
211
+ packageName = undefined;
212
+ }
162
213
  }
163
214
  results.push({
164
215
  type: "plugin",