gsd-pi 2.34.0-dev.ed0bfbf → 2.35.0-dev.30eec3f

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 (334) hide show
  1. package/dist/cli.js +7 -2
  2. package/dist/resource-loader.d.ts +1 -1
  3. package/dist/resource-loader.js +13 -1
  4. package/dist/resources/extensions/async-jobs/await-tool.js +0 -2
  5. package/dist/resources/extensions/async-jobs/job-manager.js +0 -6
  6. package/dist/resources/extensions/bg-shell/output-formatter.js +1 -19
  7. package/dist/resources/extensions/bg-shell/process-manager.js +0 -4
  8. package/dist/resources/extensions/bg-shell/types.js +0 -2
  9. package/dist/resources/extensions/context7/index.js +5 -0
  10. package/dist/resources/extensions/get-secrets-from-user.js +2 -30
  11. package/dist/resources/extensions/google-search/index.js +5 -0
  12. package/dist/resources/extensions/gsd/auto-dispatch.js +43 -1
  13. package/dist/resources/extensions/gsd/auto-loop.js +10 -1
  14. package/dist/resources/extensions/gsd/auto-recovery.js +35 -0
  15. package/dist/resources/extensions/gsd/auto-start.js +35 -2
  16. package/dist/resources/extensions/gsd/auto.js +59 -4
  17. package/dist/resources/extensions/gsd/changelog.js +162 -0
  18. package/dist/resources/extensions/gsd/commands-bootstrap.js +1 -0
  19. package/dist/resources/extensions/gsd/commands-handlers.js +2 -2
  20. package/dist/resources/extensions/gsd/commands-inspect.js +10 -3
  21. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +5 -1
  22. package/dist/resources/extensions/gsd/commands.js +8 -1
  23. package/dist/resources/extensions/gsd/docs/preferences-reference.md +10 -0
  24. package/dist/resources/extensions/gsd/doctor-checks.js +113 -5
  25. package/dist/resources/extensions/gsd/doctor-environment.js +26 -17
  26. package/dist/resources/extensions/gsd/doctor-proactive.js +22 -0
  27. package/dist/resources/extensions/gsd/doctor.js +36 -0
  28. package/dist/resources/extensions/gsd/files.js +11 -2
  29. package/dist/resources/extensions/gsd/gitignore.js +54 -7
  30. package/dist/resources/extensions/gsd/guided-flow.js +5 -3
  31. package/dist/resources/extensions/gsd/health-widget-core.js +96 -0
  32. package/dist/resources/extensions/gsd/health-widget.js +97 -46
  33. package/dist/resources/extensions/gsd/index.js +10 -1
  34. package/dist/resources/extensions/gsd/migrate-external.js +55 -2
  35. package/dist/resources/extensions/gsd/paths.js +74 -7
  36. package/dist/resources/extensions/gsd/post-unit-hooks.js +4 -1
  37. package/dist/resources/extensions/gsd/preferences-validation.js +54 -1
  38. package/dist/resources/extensions/gsd/preferences.js +2 -0
  39. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  40. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  41. package/dist/resources/extensions/gsd/roadmap-mutations.js +55 -0
  42. package/dist/resources/extensions/gsd/session-lock.js +26 -2
  43. package/dist/resources/extensions/gsd/templates/plan.md +8 -0
  44. package/dist/resources/extensions/gsd/worktree-resolver.js +12 -0
  45. package/dist/resources/extensions/remote-questions/remote-command.js +2 -22
  46. package/dist/resources/extensions/shared/mod.js +1 -1
  47. package/dist/resources/extensions/shared/sanitize.js +30 -0
  48. package/dist/resources/extensions/subagent/index.js +6 -14
  49. package/dist/resources/skills/create-gsd-extension/references/events-reference.md +4 -4
  50. package/package.json +2 -1
  51. package/packages/pi-agent-core/dist/agent-loop.d.ts +14 -0
  52. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  53. package/packages/pi-agent-core/dist/agent-loop.js +24 -27
  54. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  55. package/packages/pi-agent-core/dist/agent.d.ts +1 -0
  56. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  57. package/packages/pi-agent-core/dist/agent.js +11 -22
  58. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  59. package/packages/pi-agent-core/dist/proxy.d.ts.map +1 -1
  60. package/packages/pi-agent-core/dist/proxy.js +2 -8
  61. package/packages/pi-agent-core/dist/proxy.js.map +1 -1
  62. package/packages/pi-agent-core/src/agent-loop.ts +30 -27
  63. package/packages/pi-agent-core/src/agent.ts +12 -23
  64. package/packages/pi-agent-core/src/proxy.ts +2 -8
  65. package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
  66. package/packages/pi-ai/dist/providers/azure-openai-responses.js +5 -41
  67. package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
  68. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  69. package/packages/pi-ai/dist/providers/openai-completions.js +10 -73
  70. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  71. package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  72. package/packages/pi-ai/dist/providers/openai-responses.js +9 -80
  73. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  74. package/packages/pi-ai/dist/providers/openai-shared.d.ts +65 -0
  75. package/packages/pi-ai/dist/providers/openai-shared.d.ts.map +1 -0
  76. package/packages/pi-ai/dist/providers/openai-shared.js +146 -0
  77. package/packages/pi-ai/dist/providers/openai-shared.js.map +1 -0
  78. package/packages/pi-ai/dist/utils/oauth/google-antigravity.d.ts.map +1 -1
  79. package/packages/pi-ai/dist/utils/oauth/google-antigravity.js +7 -135
  80. package/packages/pi-ai/dist/utils/oauth/google-antigravity.js.map +1 -1
  81. package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.d.ts.map +1 -1
  82. package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.js +7 -135
  83. package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.js.map +1 -1
  84. package/packages/pi-ai/dist/utils/oauth/google-oauth-utils.d.ts +46 -0
  85. package/packages/pi-ai/dist/utils/oauth/google-oauth-utils.d.ts.map +1 -0
  86. package/packages/pi-ai/dist/utils/oauth/google-oauth-utils.js +160 -0
  87. package/packages/pi-ai/dist/utils/oauth/google-oauth-utils.js.map +1 -0
  88. package/packages/pi-ai/src/providers/azure-openai-responses.ts +11 -45
  89. package/packages/pi-ai/src/providers/openai-completions.ts +16 -86
  90. package/packages/pi-ai/src/providers/openai-responses.ts +16 -96
  91. package/packages/pi-ai/src/providers/openai-shared.ts +193 -0
  92. package/packages/pi-ai/src/utils/oauth/google-antigravity.ts +14 -162
  93. package/packages/pi-ai/src/utils/oauth/google-gemini-cli.ts +13 -161
  94. package/packages/pi-ai/src/utils/oauth/google-oauth-utils.ts +201 -0
  95. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +16 -63
  96. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  97. package/packages/pi-coding-agent/dist/core/agent-session.js +104 -641
  98. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  99. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +0 -1
  100. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  101. package/packages/pi-coding-agent/dist/core/auth-storage.js +4 -35
  102. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  103. package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  104. package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.js +5 -43
  105. package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.js.map +1 -1
  106. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
  107. package/packages/pi-coding-agent/dist/core/compaction/compaction.js +11 -69
  108. package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
  109. package/packages/pi-coding-agent/dist/core/compaction/utils.d.ts +40 -0
  110. package/packages/pi-coding-agent/dist/core/compaction/utils.d.ts.map +1 -1
  111. package/packages/pi-coding-agent/dist/core/compaction/utils.js +78 -0
  112. package/packages/pi-coding-agent/dist/core/compaction/utils.js.map +1 -1
  113. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts +77 -0
  114. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts.map +1 -0
  115. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +331 -0
  116. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -0
  117. package/packages/pi-coding-agent/dist/core/extensions/index.d.ts +2 -2
  118. package/packages/pi-coding-agent/dist/core/extensions/index.d.ts.map +1 -1
  119. package/packages/pi-coding-agent/dist/core/extensions/index.js +1 -1
  120. package/packages/pi-coding-agent/dist/core/extensions/index.js.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +15 -0
  122. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/extensions/runner.js +129 -243
  124. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +49 -42
  126. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/extensions/types.js +2 -21
  128. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  129. package/packages/pi-coding-agent/dist/core/lock-utils.d.ts +39 -0
  130. package/packages/pi-coding-agent/dist/core/lock-utils.d.ts.map +1 -0
  131. package/packages/pi-coding-agent/dist/core/lock-utils.js +89 -0
  132. package/packages/pi-coding-agent/dist/core/lock-utils.js.map +1 -0
  133. package/packages/pi-coding-agent/dist/core/lsp/config.d.ts +2 -0
  134. package/packages/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -1
  135. package/packages/pi-coding-agent/dist/core/lsp/config.js +4 -1
  136. package/packages/pi-coding-agent/dist/core/lsp/config.js.map +1 -1
  137. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
  138. package/packages/pi-coding-agent/dist/core/lsp/index.js +52 -107
  139. package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
  140. package/packages/pi-coding-agent/dist/core/lsp/lspmux.d.ts.map +1 -1
  141. package/packages/pi-coding-agent/dist/core/lsp/lspmux.js +2 -21
  142. package/packages/pi-coding-agent/dist/core/lsp/lspmux.js.map +1 -1
  143. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +0 -1
  144. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -1
  145. package/packages/pi-coding-agent/dist/core/lsp/types.js +0 -28
  146. package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -1
  147. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  148. package/packages/pi-coding-agent/dist/core/package-manager.js +2 -4
  149. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  150. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +2 -4
  151. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  152. package/packages/pi-coding-agent/dist/core/resource-loader.js +46 -60
  153. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  154. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts +87 -0
  155. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -0
  156. package/packages/pi-coding-agent/dist/core/retry-handler.js +295 -0
  157. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -0
  158. package/packages/pi-coding-agent/dist/core/session-manager.d.ts +0 -1
  159. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  160. package/packages/pi-coding-agent/dist/core/session-manager.js +3 -28
  161. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  162. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +8 -0
  163. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  164. package/packages/pi-coding-agent/dist/core/settings-manager.js +76 -166
  165. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  166. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  167. package/packages/pi-coding-agent/dist/core/skills.js +1 -3
  168. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  169. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  170. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  171. package/packages/pi-coding-agent/dist/index.js +1 -1
  172. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  173. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.d.ts +1 -1
  174. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  175. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.js +9 -26
  176. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.js.map +1 -1
  177. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  178. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +1 -13
  179. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  180. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-render-utils.d.ts +44 -0
  181. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-render-utils.d.ts.map +1 -0
  182. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-render-utils.js +61 -0
  183. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-render-utils.js.map +1 -0
  184. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  185. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-selector.js +6 -9
  186. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-selector.js.map +1 -1
  187. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts +65 -0
  188. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  189. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +6 -16
  190. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
  191. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts +12 -0
  192. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -0
  193. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +175 -0
  194. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -0
  195. package/packages/pi-coding-agent/dist/modes/interactive/utils/shorten-path.d.ts +6 -0
  196. package/packages/pi-coding-agent/dist/modes/interactive/utils/shorten-path.d.ts.map +1 -0
  197. package/packages/pi-coding-agent/dist/modes/interactive/utils/shorten-path.js +15 -0
  198. package/packages/pi-coding-agent/dist/modes/interactive/utils/shorten-path.js.map +1 -0
  199. package/packages/pi-coding-agent/dist/modes/print-mode.d.ts.map +1 -1
  200. package/packages/pi-coding-agent/dist/modes/print-mode.js +2 -30
  201. package/packages/pi-coding-agent/dist/modes/print-mode.js.map +1 -1
  202. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  203. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +2 -28
  204. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  205. package/packages/pi-coding-agent/dist/modes/shared/command-context-actions.d.ts +19 -0
  206. package/packages/pi-coding-agent/dist/modes/shared/command-context-actions.d.ts.map +1 -0
  207. package/packages/pi-coding-agent/dist/modes/shared/command-context-actions.js +45 -0
  208. package/packages/pi-coding-agent/dist/modes/shared/command-context-actions.js.map +1 -0
  209. package/packages/pi-coding-agent/dist/utils/error.d.ts +5 -0
  210. package/packages/pi-coding-agent/dist/utils/error.d.ts.map +1 -0
  211. package/packages/pi-coding-agent/dist/utils/error.js +7 -0
  212. package/packages/pi-coding-agent/dist/utils/error.js.map +1 -0
  213. package/packages/pi-coding-agent/package.json +1 -1
  214. package/packages/pi-coding-agent/src/core/agent-session.ts +117 -745
  215. package/packages/pi-coding-agent/src/core/auth-storage.ts +4 -38
  216. package/packages/pi-coding-agent/src/core/compaction/branch-summarization.ts +7 -53
  217. package/packages/pi-coding-agent/src/core/compaction/compaction.ts +14 -74
  218. package/packages/pi-coding-agent/src/core/compaction/utils.ts +100 -0
  219. package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +424 -0
  220. package/packages/pi-coding-agent/src/core/extensions/index.ts +1 -21
  221. package/packages/pi-coding-agent/src/core/extensions/runner.ts +119 -243
  222. package/packages/pi-coding-agent/src/core/extensions/types.ts +50 -69
  223. package/packages/pi-coding-agent/src/core/lock-utils.ts +113 -0
  224. package/packages/pi-coding-agent/src/core/lsp/config.ts +4 -1
  225. package/packages/pi-coding-agent/src/core/lsp/index.ts +83 -152
  226. package/packages/pi-coding-agent/src/core/lsp/lspmux.ts +2 -22
  227. package/packages/pi-coding-agent/src/core/lsp/types.ts +0 -29
  228. package/packages/pi-coding-agent/src/core/package-manager.ts +1 -4
  229. package/packages/pi-coding-agent/src/core/resource-loader.ts +56 -69
  230. package/packages/pi-coding-agent/src/core/retry-handler.ts +359 -0
  231. package/packages/pi-coding-agent/src/core/session-manager.ts +3 -30
  232. package/packages/pi-coding-agent/src/core/settings-manager.ts +85 -164
  233. package/packages/pi-coding-agent/src/core/skills.ts +1 -4
  234. package/packages/pi-coding-agent/src/index.ts +1 -7
  235. package/packages/pi-coding-agent/src/modes/interactive/components/session-selector.ts +17 -29
  236. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +1 -13
  237. package/packages/pi-coding-agent/src/modes/interactive/components/tree-render-utils.ts +81 -0
  238. package/packages/pi-coding-agent/src/modes/interactive/components/tree-selector.ts +14 -19
  239. package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +7 -18
  240. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +196 -0
  241. package/packages/pi-coding-agent/src/modes/interactive/utils/shorten-path.ts +14 -0
  242. package/packages/pi-coding-agent/src/modes/print-mode.ts +2 -30
  243. package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +2 -28
  244. package/packages/pi-coding-agent/src/modes/shared/command-context-actions.ts +53 -0
  245. package/packages/pi-coding-agent/src/utils/error.ts +6 -0
  246. package/packages/pi-tui/dist/components/markdown.d.ts +5 -0
  247. package/packages/pi-tui/dist/components/markdown.d.ts.map +1 -1
  248. package/packages/pi-tui/dist/components/markdown.js +25 -31
  249. package/packages/pi-tui/dist/components/markdown.js.map +1 -1
  250. package/packages/pi-tui/dist/keys.d.ts +0 -4
  251. package/packages/pi-tui/dist/keys.d.ts.map +1 -1
  252. package/packages/pi-tui/dist/keys.js +94 -162
  253. package/packages/pi-tui/dist/keys.js.map +1 -1
  254. package/packages/pi-tui/src/components/markdown.ts +25 -29
  255. package/packages/pi-tui/src/keys.ts +94 -173
  256. package/pkg/dist/modes/interactive/theme/theme.d.ts +65 -0
  257. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  258. package/pkg/dist/modes/interactive/theme/theme.js +6 -16
  259. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
  260. package/pkg/dist/modes/interactive/theme/themes.d.ts +12 -0
  261. package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -0
  262. package/pkg/dist/modes/interactive/theme/themes.js +175 -0
  263. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -0
  264. package/pkg/package.json +1 -1
  265. package/src/resources/extensions/async-jobs/await-tool.ts +0 -2
  266. package/src/resources/extensions/async-jobs/job-manager.ts +0 -7
  267. package/src/resources/extensions/bg-shell/output-formatter.ts +0 -17
  268. package/src/resources/extensions/bg-shell/process-manager.ts +0 -4
  269. package/src/resources/extensions/bg-shell/types.ts +0 -12
  270. package/src/resources/extensions/context7/index.ts +7 -0
  271. package/src/resources/extensions/get-secrets-from-user.ts +2 -35
  272. package/src/resources/extensions/google-search/index.ts +7 -0
  273. package/src/resources/extensions/gsd/auto-dispatch.ts +49 -1
  274. package/src/resources/extensions/gsd/auto-loop.ts +11 -1
  275. package/src/resources/extensions/gsd/auto-recovery.ts +39 -0
  276. package/src/resources/extensions/gsd/auto-start.ts +42 -2
  277. package/src/resources/extensions/gsd/auto.ts +61 -3
  278. package/src/resources/extensions/gsd/changelog.ts +213 -0
  279. package/src/resources/extensions/gsd/commands-bootstrap.ts +1 -0
  280. package/src/resources/extensions/gsd/commands-handlers.ts +2 -2
  281. package/src/resources/extensions/gsd/commands-inspect.ts +10 -3
  282. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +5 -1
  283. package/src/resources/extensions/gsd/commands.ts +9 -1
  284. package/src/resources/extensions/gsd/docs/preferences-reference.md +10 -0
  285. package/src/resources/extensions/gsd/doctor-checks.ts +107 -5
  286. package/src/resources/extensions/gsd/doctor-environment.ts +26 -16
  287. package/src/resources/extensions/gsd/doctor-proactive.ts +24 -0
  288. package/src/resources/extensions/gsd/doctor-types.ts +9 -1
  289. package/src/resources/extensions/gsd/doctor.ts +35 -0
  290. package/src/resources/extensions/gsd/files.ts +12 -2
  291. package/src/resources/extensions/gsd/gitignore.ts +54 -7
  292. package/src/resources/extensions/gsd/guided-flow.ts +5 -3
  293. package/src/resources/extensions/gsd/health-widget-core.ts +129 -0
  294. package/src/resources/extensions/gsd/health-widget.ts +103 -59
  295. package/src/resources/extensions/gsd/index.ts +10 -1
  296. package/src/resources/extensions/gsd/migrate-external.ts +47 -2
  297. package/src/resources/extensions/gsd/paths.ts +73 -7
  298. package/src/resources/extensions/gsd/post-unit-hooks.ts +5 -1
  299. package/src/resources/extensions/gsd/preferences-validation.ts +54 -1
  300. package/src/resources/extensions/gsd/preferences.ts +2 -0
  301. package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  302. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  303. package/src/resources/extensions/gsd/roadmap-mutations.ts +66 -0
  304. package/src/resources/extensions/gsd/session-lock.ts +29 -2
  305. package/src/resources/extensions/gsd/templates/plan.md +8 -0
  306. package/src/resources/extensions/gsd/tests/commands-inspect-open-db.test.ts +46 -0
  307. package/src/resources/extensions/gsd/tests/doctor-git.test.ts +98 -2
  308. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +59 -3
  309. package/src/resources/extensions/gsd/tests/files-loadfile-eisdir.test.ts +20 -0
  310. package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +214 -0
  311. package/src/resources/extensions/gsd/tests/health-widget.test.ts +158 -0
  312. package/src/resources/extensions/gsd/tests/paths.test.ts +113 -0
  313. package/src/resources/extensions/gsd/tests/preferences.test.ts +40 -2
  314. package/src/resources/extensions/gsd/tests/test-utils.ts +165 -0
  315. package/src/resources/extensions/gsd/tests/validate-directory.test.ts +15 -0
  316. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +2 -0
  317. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +32 -0
  318. package/src/resources/extensions/gsd/worktree-resolver.ts +11 -0
  319. package/src/resources/extensions/remote-questions/remote-command.ts +2 -23
  320. package/src/resources/extensions/shared/mod.ts +1 -1
  321. package/src/resources/extensions/shared/sanitize.ts +36 -0
  322. package/src/resources/extensions/subagent/index.ts +6 -12
  323. package/src/resources/skills/create-gsd-extension/references/events-reference.md +4 -4
  324. package/dist/resources/extensions/shared/wizard-ui.js +0 -478
  325. package/packages/pi-coding-agent/dist/modes/interactive/theme/dark.json +0 -85
  326. package/packages/pi-coding-agent/dist/modes/interactive/theme/light.json +0 -84
  327. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.json +0 -335
  328. package/packages/pi-coding-agent/src/modes/interactive/theme/dark.json +0 -85
  329. package/packages/pi-coding-agent/src/modes/interactive/theme/light.json +0 -84
  330. package/packages/pi-coding-agent/src/modes/interactive/theme/theme-schema.json +0 -335
  331. package/pkg/dist/modes/interactive/theme/dark.json +0 -85
  332. package/pkg/dist/modes/interactive/theme/light.json +0 -84
  333. package/pkg/dist/modes/interactive/theme/theme-schema.json +0 -335
  334. package/src/resources/extensions/shared/wizard-ui.ts +0 -551
@@ -4,7 +4,7 @@ import { join, resolve, sep } from "node:path";
4
4
  import chalk from "chalk";
5
5
  import { CONFIG_DIR_NAME, getAgentDir } from "../config.js";
6
6
  import { loadThemeFromPath, type Theme } from "../modes/interactive/theme/theme.js";
7
- import type { ResourceDiagnostic } from "./diagnostics.js";
7
+ import type { ResourceCollision, ResourceDiagnostic } from "./diagnostics.js";
8
8
 
9
9
  export type { ResourceCollision, ResourceDiagnostic } from "./diagnostics.js";
10
10
 
@@ -422,12 +422,12 @@ export class DefaultResourceLoader implements ResourceLoader {
422
422
  this.agentsFiles = resolvedAgentsFiles.agentsFiles;
423
423
 
424
424
  const baseSystemPrompt = resolvePromptInput(
425
- this.systemPromptSource ?? this.discoverSystemPromptFile(),
425
+ this.systemPromptSource ?? this.discoverFileInSearchPaths("SYSTEM.md"),
426
426
  "system prompt",
427
427
  );
428
428
  this.systemPrompt = this.systemPromptOverride ? this.systemPromptOverride(baseSystemPrompt) : baseSystemPrompt;
429
429
 
430
- const appendSource = this.appendSystemPromptSource ?? this.discoverAppendSystemPromptFile();
430
+ const appendSource = this.appendSystemPromptSource ?? this.discoverFileInSearchPaths("APPEND_SYSTEM.md");
431
431
  const resolvedAppend = resolvePromptInput(appendSource, "append system prompt");
432
432
  const baseAppend = resolvedAppend ? [resolvedAppend] : [];
433
433
  this.appendSystemPrompt = this.appendSystemPromptOverride
@@ -485,7 +485,13 @@ export class DefaultResourceLoader implements ResourceLoader {
485
485
  promptPaths,
486
486
  includeDefaults: false,
487
487
  });
488
- promptsResult = this.dedupePrompts(allPrompts);
488
+ const deduped = this.dedupeResources(allPrompts, {
489
+ getName: (p) => p.name,
490
+ getPath: (p) => p.filePath,
491
+ resourceType: "prompt",
492
+ namePrefix: "/",
493
+ });
494
+ promptsResult = { prompts: deduped.items, diagnostics: deduped.diagnostics };
489
495
  }
490
496
  const resolvedPrompts = this.promptsOverride ? this.promptsOverride(promptsResult) : promptsResult;
491
497
  this.prompts = resolvedPrompts.prompts;
@@ -508,8 +514,12 @@ export class DefaultResourceLoader implements ResourceLoader {
508
514
  themesResult = { themes: [], diagnostics: [] };
509
515
  } else {
510
516
  const loaded = this.loadThemes(themePaths, false);
511
- const deduped = this.dedupeThemes(loaded.themes);
512
- themesResult = { themes: deduped.themes, diagnostics: [...loaded.diagnostics, ...deduped.diagnostics] };
517
+ const deduped = this.dedupeResources(loaded.themes, {
518
+ getName: (t) => t.name ?? "unnamed",
519
+ getPath: (t) => t.sourcePath,
520
+ resourceType: "theme",
521
+ });
522
+ themesResult = { themes: deduped.items, diagnostics: [...loaded.diagnostics, ...deduped.diagnostics] };
513
523
  }
514
524
  const resolvedThemes = this.themesOverride ? this.themesOverride(themesResult) : themesResult;
515
525
  this.themes = resolvedThemes.themes;
@@ -686,84 +696,50 @@ export class DefaultResourceLoader implements ResourceLoader {
686
696
  return { extensions, errors };
687
697
  }
688
698
 
689
- private dedupePrompts(prompts: PromptTemplate[]): { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[] } {
690
- const seen = new Map<string, PromptTemplate>();
699
+ private dedupeResources<T>(
700
+ items: T[],
701
+ options: {
702
+ getName: (item: T) => string;
703
+ getPath: (item: T) => string | undefined;
704
+ resourceType: ResourceCollision["resourceType"];
705
+ namePrefix?: string;
706
+ },
707
+ ): { items: T[]; diagnostics: ResourceDiagnostic[] } {
708
+ const seen = new Map<string, T>();
691
709
  const diagnostics: ResourceDiagnostic[] = [];
710
+ const { getName, getPath, resourceType, namePrefix = "" } = options;
692
711
 
693
- for (const prompt of prompts) {
694
- const existing = seen.get(prompt.name);
695
- if (existing) {
696
- diagnostics.push({
697
- type: "collision",
698
- message: `name "/${prompt.name}" collision`,
699
- path: prompt.filePath,
700
- collision: {
701
- resourceType: "prompt",
702
- name: prompt.name,
703
- winnerPath: existing.filePath,
704
- loserPath: prompt.filePath,
705
- },
706
- });
707
- } else {
708
- seen.set(prompt.name, prompt);
709
- }
710
- }
711
-
712
- return { prompts: Array.from(seen.values()), diagnostics };
713
- }
714
-
715
- private dedupeThemes(themes: Theme[]): { themes: Theme[]; diagnostics: ResourceDiagnostic[] } {
716
- const seen = new Map<string, Theme>();
717
- const diagnostics: ResourceDiagnostic[] = [];
718
-
719
- for (const t of themes) {
720
- const name = t.name ?? "unnamed";
712
+ for (const item of items) {
713
+ const name = getName(item);
721
714
  const existing = seen.get(name);
722
715
  if (existing) {
723
716
  diagnostics.push({
724
717
  type: "collision",
725
- message: `name "${name}" collision`,
726
- path: t.sourcePath,
718
+ message: `name "${namePrefix}${name}" collision`,
719
+ path: getPath(item),
727
720
  collision: {
728
- resourceType: "theme",
721
+ resourceType,
729
722
  name,
730
- winnerPath: existing.sourcePath ?? "<builtin>",
731
- loserPath: t.sourcePath ?? "<builtin>",
723
+ winnerPath: getPath(existing) ?? "<builtin>",
724
+ loserPath: getPath(item) ?? "<builtin>",
732
725
  },
733
726
  });
734
727
  } else {
735
- seen.set(name, t);
728
+ seen.set(name, item);
736
729
  }
737
730
  }
738
731
 
739
- return { themes: Array.from(seen.values()), diagnostics };
732
+ return { items: Array.from(seen.values()), diagnostics };
740
733
  }
741
734
 
742
- private discoverSystemPromptFile(): string | undefined {
743
- const projectPath = join(this.cwd, CONFIG_DIR_NAME, "SYSTEM.md");
744
- if (existsSync(projectPath)) {
745
- return projectPath;
746
- }
747
-
748
- const globalPath = join(this.agentDir, "SYSTEM.md");
749
- if (existsSync(globalPath)) {
750
- return globalPath;
751
- }
752
-
753
- return undefined;
754
- }
755
-
756
- private discoverAppendSystemPromptFile(): string | undefined {
757
- const projectPath = join(this.cwd, CONFIG_DIR_NAME, "APPEND_SYSTEM.md");
758
- if (existsSync(projectPath)) {
759
- return projectPath;
760
- }
761
-
762
- const globalPath = join(this.agentDir, "APPEND_SYSTEM.md");
763
- if (existsSync(globalPath)) {
764
- return globalPath;
735
+ private discoverFileInSearchPaths(filename: string): string | undefined {
736
+ const searchDirs = [join(this.cwd, CONFIG_DIR_NAME), this.agentDir];
737
+ for (const dir of searchDirs) {
738
+ const filePath = join(dir, filename);
739
+ if (existsSync(filePath)) {
740
+ return filePath;
741
+ }
765
742
  }
766
-
767
743
  return undefined;
768
744
  }
769
745
 
@@ -827,9 +803,15 @@ export class DefaultResourceLoader implements ResourceLoader {
827
803
  for (const toolName of ext.tools.keys()) {
828
804
  const existingOwner = toolOwners.get(toolName);
829
805
  if (existingOwner && existingOwner !== ext.path) {
806
+ // Determine if the existing owner is a built-in (not a user extension)
807
+ const isBuiltIn = !existingOwner.includes("/.gsd/agent/extensions/") &&
808
+ !existingOwner.includes("/.gsd/extensions/");
809
+ const hint = isBuiltIn
810
+ ? ` (built-in tool supersedes — consider removing ${ext.path})`
811
+ : "";
830
812
  conflicts.push({
831
813
  path: ext.path,
832
- message: `Tool "${toolName}" conflicts with ${existingOwner}`,
814
+ message: `Tool "${toolName}" conflicts with ${existingOwner}${hint}`,
833
815
  });
834
816
  } else {
835
817
  toolOwners.set(toolName, ext.path);
@@ -840,9 +822,14 @@ export class DefaultResourceLoader implements ResourceLoader {
840
822
  for (const commandName of ext.commands.keys()) {
841
823
  const existingOwner = commandOwners.get(commandName);
842
824
  if (existingOwner && existingOwner !== ext.path) {
825
+ const isBuiltIn = !existingOwner.includes("/.gsd/agent/extensions/") &&
826
+ !existingOwner.includes("/.gsd/extensions/");
827
+ const hint = isBuiltIn
828
+ ? ` (built-in command supersedes — consider removing ${ext.path})`
829
+ : "";
843
830
  conflicts.push({
844
831
  path: ext.path,
845
- message: `Command "/${commandName}" conflicts with ${existingOwner}`,
832
+ message: `Command "/${commandName}" conflicts with ${existingOwner}${hint}`,
846
833
  });
847
834
  } else {
848
835
  commandOwners.set(commandName, ext.path);
@@ -0,0 +1,359 @@
1
+ /**
2
+ * RetryHandler - Automatic retry logic with exponential backoff and credential/provider fallback.
3
+ *
4
+ * Handles retryable errors (overloaded, rate limit, server errors) by:
5
+ * 1. Trying alternate credentials for the same provider
6
+ * 2. Falling back to other providers via FallbackResolver
7
+ * 3. Exponential backoff with configurable max retries
8
+ *
9
+ * Context overflow errors are NOT handled here (see compaction).
10
+ */
11
+
12
+ import type { Agent } from "@gsd/pi-agent-core";
13
+ import type { AssistantMessage, Model } from "@gsd/pi-ai";
14
+ import { isContextOverflow } from "@gsd/pi-ai";
15
+ import type { UsageLimitErrorType } from "./auth-storage.js";
16
+ import type { FallbackResolver } from "./fallback-resolver.js";
17
+ import type { ModelRegistry } from "./model-registry.js";
18
+ import type { SettingsManager } from "./settings-manager.js";
19
+ import { sleep } from "../utils/sleep.js";
20
+ import type { AgentSessionEvent } from "./agent-session.js";
21
+
22
+ /** Dependencies injected from AgentSession into RetryHandler */
23
+ export interface RetryHandlerDeps {
24
+ readonly agent: Agent;
25
+ readonly settingsManager: SettingsManager;
26
+ readonly modelRegistry: ModelRegistry;
27
+ readonly fallbackResolver: FallbackResolver;
28
+ getModel: () => Model<any> | undefined;
29
+ getSessionId: () => string;
30
+ emit: (event: AgentSessionEvent) => void;
31
+ /** Called when the retry handler switches to a fallback model */
32
+ onModelChange: (model: Model<any>) => void;
33
+ }
34
+
35
+ export class RetryHandler {
36
+ private _retryAbortController: AbortController | undefined = undefined;
37
+ private _retryAttempt = 0;
38
+ private _retryPromise: Promise<void> | undefined = undefined;
39
+ private _retryResolve: (() => void) | undefined = undefined;
40
+
41
+ constructor(private readonly _deps: RetryHandlerDeps) {}
42
+
43
+ /** Current retry attempt (0 if not retrying) */
44
+ get retryAttempt(): number {
45
+ return this._retryAttempt;
46
+ }
47
+
48
+ /** Whether auto-retry is currently in progress */
49
+ get isRetrying(): boolean {
50
+ return this._retryPromise !== undefined;
51
+ }
52
+
53
+ /** Whether auto-retry is enabled */
54
+ get autoRetryEnabled(): boolean {
55
+ return this._deps.settingsManager.getRetryEnabled();
56
+ }
57
+
58
+ /** Toggle auto-retry setting */
59
+ setAutoRetryEnabled(enabled: boolean): void {
60
+ this._deps.settingsManager.setRetryEnabled(enabled);
61
+ }
62
+
63
+ /**
64
+ * Create a retry promise synchronously for agent_end events.
65
+ * Must be called synchronously from the agent event handler before
66
+ * any async processing, so that waitForRetry() doesn't miss in-flight retries.
67
+ */
68
+ createRetryPromiseForAgentEnd(messages: Array<{ role: string } & Record<string, any>>): void {
69
+ if (this._retryPromise) return;
70
+
71
+ const settings = this._deps.settingsManager.getRetrySettings();
72
+ if (!settings.enabled) return;
73
+
74
+ const lastAssistant = this._findLastAssistantInMessages(messages);
75
+ if (!lastAssistant || !this.isRetryableError(lastAssistant)) return;
76
+
77
+ this._retryPromise = new Promise((resolve) => {
78
+ this._retryResolve = resolve;
79
+ });
80
+ }
81
+
82
+ /**
83
+ * Handle a successful assistant response by resetting retry state.
84
+ * Call this when an assistant message completes without error.
85
+ */
86
+ handleSuccessfulResponse(): void {
87
+ if (this._retryAttempt > 0) {
88
+ this._deps.emit({
89
+ type: "auto_retry_end",
90
+ success: true,
91
+ attempt: this._retryAttempt,
92
+ });
93
+ this._retryAttempt = 0;
94
+ this._resolveRetry();
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Check if an error is retryable (overloaded, rate limit, server errors).
100
+ * Context overflow errors are NOT retryable (handled by compaction instead).
101
+ */
102
+ isRetryableError(message: AssistantMessage): boolean {
103
+ if (message.stopReason !== "error" || !message.errorMessage) return false;
104
+
105
+ // Context overflow is handled by compaction, not retry
106
+ const contextWindow = this._deps.getModel()?.contextWindow ?? 0;
107
+ if (isContextOverflow(message, contextWindow)) return false;
108
+
109
+ const err = message.errorMessage;
110
+ return /overloaded|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|connection.?error|connection.?refused|other side closed|fetch failed|upstream.?connect|reset before headers|terminated|retry delay|network.?(?:is\s+)?unavailable|credentials.*expired|temporarily backed off/i.test(
111
+ err,
112
+ );
113
+ }
114
+
115
+ /**
116
+ * Handle retryable errors with exponential backoff.
117
+ * When multiple credentials are available, marks the failing credential
118
+ * as backed off and retries immediately with the next one.
119
+ * @returns true if retry was initiated, false if max retries exceeded or disabled
120
+ */
121
+ async handleRetryableError(message: AssistantMessage): Promise<boolean> {
122
+ const settings = this._deps.settingsManager.getRetrySettings();
123
+ if (!settings.enabled) {
124
+ this._resolveRetry();
125
+ return false;
126
+ }
127
+
128
+ // Retry promise is created synchronously in createRetryPromiseForAgentEnd.
129
+ // Keep a defensive fallback here in case a future refactor bypasses that path.
130
+ if (!this._retryPromise) {
131
+ this._retryPromise = new Promise((resolve) => {
132
+ this._retryResolve = resolve;
133
+ });
134
+ }
135
+
136
+ // Try credential fallback before counting against retry budget.
137
+ if (this._deps.getModel() && message.errorMessage) {
138
+ const errorType = this._classifyErrorType(message.errorMessage);
139
+ const isCredentialError = errorType !== "unknown";
140
+ const hasAlternate =
141
+ isCredentialError &&
142
+ this._deps.modelRegistry.authStorage.markUsageLimitReached(
143
+ this._deps.getModel()!.provider,
144
+ this._deps.getSessionId(),
145
+ { errorType },
146
+ );
147
+
148
+ if (hasAlternate) {
149
+ this._removeLastAssistantError();
150
+
151
+ this._deps.emit({
152
+ type: "auto_retry_start",
153
+ attempt: this._retryAttempt + 1,
154
+ maxAttempts: settings.maxRetries,
155
+ delayMs: 0,
156
+ errorMessage: `${message.errorMessage} (switching credential)`,
157
+ });
158
+
159
+ // Retry immediately with the next credential - don't increment _retryAttempt
160
+ setTimeout(() => {
161
+ this._deps.agent.continue().catch(() => {});
162
+ }, 0);
163
+
164
+ return true;
165
+ }
166
+
167
+ // All credentials are backed off. Try cross-provider fallback before giving up.
168
+ if (isCredentialError) {
169
+ const fallbackResult = await this._deps.fallbackResolver.findFallback(
170
+ this._deps.getModel()!,
171
+ errorType,
172
+ );
173
+
174
+ if (fallbackResult) {
175
+ const previousProvider = this._deps.getModel()!.provider;
176
+ this._deps.agent.setModel(fallbackResult.model);
177
+ this._deps.onModelChange(fallbackResult.model);
178
+ this._removeLastAssistantError();
179
+
180
+ this._deps.emit({
181
+ type: "fallback_provider_switch",
182
+ from: `${previousProvider}/${this._deps.getModel()?.id}`,
183
+ to: `${fallbackResult.model.provider}/${fallbackResult.model.id}`,
184
+ reason: fallbackResult.reason,
185
+ });
186
+
187
+ this._deps.emit({
188
+ type: "auto_retry_start",
189
+ attempt: this._retryAttempt + 1,
190
+ maxAttempts: settings.maxRetries,
191
+ delayMs: 0,
192
+ errorMessage: `${message.errorMessage} (${fallbackResult.reason})`,
193
+ });
194
+
195
+ // Retry immediately with fallback provider - don't increment _retryAttempt
196
+ setTimeout(() => {
197
+ this._deps.agent.continue().catch(() => {});
198
+ }, 0);
199
+
200
+ return true;
201
+ }
202
+
203
+ // No fallback available either
204
+ if (errorType === "quota_exhausted") {
205
+ this._deps.emit({
206
+ type: "fallback_chain_exhausted",
207
+ reason: `All providers exhausted for ${this._deps.getModel()!.provider}/${this._deps.getModel()!.id}`,
208
+ });
209
+ this._deps.emit({
210
+ type: "auto_retry_end",
211
+ success: false,
212
+ attempt: this._retryAttempt,
213
+ finalError: message.errorMessage,
214
+ });
215
+ this._retryAttempt = 0;
216
+ this._resolveRetry();
217
+ return false;
218
+ }
219
+ }
220
+ }
221
+
222
+ this._retryAttempt++;
223
+
224
+ if (this._retryAttempt > settings.maxRetries) {
225
+ this._deps.emit({
226
+ type: "auto_retry_end",
227
+ success: false,
228
+ attempt: this._retryAttempt - 1,
229
+ finalError: message.errorMessage,
230
+ });
231
+ this._retryAttempt = 0;
232
+ this._resolveRetry();
233
+ return false;
234
+ }
235
+
236
+ // Use server-requested delay when available, capped by maxDelayMs.
237
+ // Fall back to exponential backoff when no server hint is present.
238
+ const exponentialDelayMs = settings.baseDelayMs * 2 ** (this._retryAttempt - 1);
239
+ let delayMs: number;
240
+ if (message.retryAfterMs !== undefined) {
241
+ const cap = settings.maxDelayMs > 0 ? settings.maxDelayMs : Infinity;
242
+ if (message.retryAfterMs > cap) {
243
+ this._deps.emit({
244
+ type: "auto_retry_end",
245
+ success: false,
246
+ attempt: this._retryAttempt - 1,
247
+ finalError: `Rate limit reset in ${Math.ceil(message.retryAfterMs / 1000)}s (max: ${Math.ceil(cap / 1000)}s). ${message.errorMessage || ""}`.trim(),
248
+ });
249
+ this._retryAttempt = 0;
250
+ this._resolveRetry();
251
+ return false;
252
+ }
253
+ delayMs = message.retryAfterMs;
254
+ } else {
255
+ delayMs = exponentialDelayMs;
256
+ }
257
+
258
+ this._deps.emit({
259
+ type: "auto_retry_start",
260
+ attempt: this._retryAttempt,
261
+ maxAttempts: settings.maxRetries,
262
+ delayMs,
263
+ errorMessage: message.errorMessage || "Unknown error",
264
+ });
265
+
266
+ this._removeLastAssistantError();
267
+
268
+ // Wait with exponential backoff (abortable)
269
+ this._retryAbortController = new AbortController();
270
+ try {
271
+ await sleep(delayMs, this._retryAbortController.signal);
272
+ } catch {
273
+ // Aborted during sleep
274
+ const attempt = this._retryAttempt;
275
+ this._retryAttempt = 0;
276
+ this._retryAbortController = undefined;
277
+ this._deps.emit({
278
+ type: "auto_retry_end",
279
+ success: false,
280
+ attempt,
281
+ finalError: "Retry cancelled",
282
+ });
283
+ this._resolveRetry();
284
+ return false;
285
+ }
286
+ this._retryAbortController = undefined;
287
+
288
+ // Retry via continue() - use setTimeout to break out of event handler chain
289
+ setTimeout(() => {
290
+ this._deps.agent.continue().catch(() => {});
291
+ }, 0);
292
+
293
+ return true;
294
+ }
295
+
296
+ /** Cancel in-progress retry */
297
+ abortRetry(): void {
298
+ this._retryAbortController?.abort();
299
+ this._resolveRetry();
300
+ }
301
+
302
+ /**
303
+ * Wait for any in-progress retry to complete.
304
+ * Returns immediately if no retry is in progress.
305
+ */
306
+ async waitForRetry(): Promise<void> {
307
+ if (this._retryPromise) {
308
+ await this._retryPromise;
309
+ }
310
+ }
311
+
312
+ /** Resolve the pending retry promise */
313
+ resolveRetry(): void {
314
+ this._resolveRetry();
315
+ }
316
+
317
+ // =========================================================================
318
+ // Private helpers
319
+ // =========================================================================
320
+
321
+ private _resolveRetry(): void {
322
+ if (this._retryResolve) {
323
+ this._retryResolve();
324
+ this._retryResolve = undefined;
325
+ this._retryPromise = undefined;
326
+ }
327
+ }
328
+
329
+ private _findLastAssistantInMessages(
330
+ messages: Array<{ role: string } & Record<string, any>>,
331
+ ): AssistantMessage | undefined {
332
+ for (let i = messages.length - 1; i >= 0; i--) {
333
+ const message = messages[i];
334
+ if (message.role === "assistant") {
335
+ return message as AssistantMessage;
336
+ }
337
+ }
338
+ return undefined;
339
+ }
340
+
341
+ /**
342
+ * Classify an error message into a usage-limit error type for credential backoff.
343
+ */
344
+ private _classifyErrorType(errorMessage: string): UsageLimitErrorType {
345
+ const err = errorMessage.toLowerCase();
346
+ if (/quota|billing|exceeded.*limit|usage.*limit/i.test(err)) return "quota_exhausted";
347
+ if (/rate.?limit|too many requests|429/i.test(err)) return "rate_limit";
348
+ if (/500|502|503|504|server.?error|internal.?error|service.?unavailable/i.test(err)) return "server_error";
349
+ return "unknown";
350
+ }
351
+
352
+ /** Remove the last assistant error message from agent state */
353
+ private _removeLastAssistantError(): void {
354
+ const messages = this._deps.agent.state.messages;
355
+ if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
356
+ this._deps.agent.replaceMessages(messages.slice(0, -1));
357
+ }
358
+ }
359
+ }
@@ -16,8 +16,8 @@ import {
16
16
  import { atomicWriteFileSync } from "./fs-utils.js";
17
17
  import { readdir, readFile, stat } from "fs/promises";
18
18
  import { join, resolve } from "path";
19
- import lockfile from "proper-lockfile";
20
19
  import { getAgentDir as getDefaultAgentDir, getBlobsDir, getSessionsDir } from "../config.js";
20
+ import { tryAcquireLockSync } from "./lock-utils.js";
21
21
  import {
22
22
  type BashExecutionMessage,
23
23
  type CustomMessage,
@@ -953,39 +953,12 @@ export class SessionManager {
953
953
  }
954
954
  }
955
955
 
956
- private acquireSessionLock(path: string): (() => void) | undefined {
957
- const maxAttempts = 10;
958
- const delayMs = 20;
959
- let lastError: unknown;
960
-
961
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
962
- try {
963
- return lockfile.lockSync(path, { realpath: false });
964
- } catch (error) {
965
- const code =
966
- typeof error === "object" && error !== null && "code" in error
967
- ? String((error as { code?: unknown }).code)
968
- : undefined;
969
- if (code !== "ELOCKED" || attempt === maxAttempts) {
970
- // Non-fatal: proceed without lock rather than losing data
971
- return undefined;
972
- }
973
- lastError = error;
974
- const start = Date.now();
975
- while (Date.now() - start < delayMs) {
976
- // Busy-wait to avoid async
977
- }
978
- }
979
- }
980
- return undefined;
981
- }
982
-
983
956
  private _rewriteFile(): void {
984
957
  if (!this.persist || !this.sessionFile) return;
985
958
  const content = `${this.fileEntries.map((e) => JSON.stringify(e)).join("\n")}\n`;
986
959
  let release: (() => void) | undefined;
987
960
  try {
988
- release = this.acquireSessionLock(this.sessionFile);
961
+ release = tryAcquireLockSync(this.sessionFile);
989
962
  atomicWriteFileSync(this.sessionFile, content);
990
963
  } finally {
991
964
  release?.();
@@ -1024,7 +997,7 @@ export class SessionManager {
1024
997
 
1025
998
  let release: (() => void) | undefined;
1026
999
  try {
1027
- release = this.acquireSessionLock(this.sessionFile);
1000
+ release = tryAcquireLockSync(this.sessionFile);
1028
1001
  if (!this.flushed) {
1029
1002
  for (const e of this.fileEntries) {
1030
1003
  const prepared = prepareForPersistence(e, this.blobStore) as FileEntry;