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
@@ -24,22 +24,20 @@ import type {
24
24
  ThinkingLevel,
25
25
  } from "@gsd/pi-agent-core";
26
26
  import type { AssistantMessage, ImageContent, Message, Model, TextContent } from "@gsd/pi-ai";
27
- import { isContextOverflow, modelsAreEqual, resetApiProviders, supportsXhigh } from "@gsd/pi-ai";
27
+ import { modelsAreEqual, resetApiProviders, supportsXhigh } from "@gsd/pi-ai";
28
28
  import { getDocsPath } from "../config.js";
29
+ import { getErrorMessage } from "../utils/error.js";
29
30
  import { theme } from "../modes/interactive/theme/theme.js";
30
31
  import { stripFrontmatter } from "../utils/frontmatter.js";
31
- import { sleep } from "../utils/sleep.js";
32
32
  import { type BashResult, executeBash as executeBashCommand, executeBashWithOperations } from "./bash-executor.js";
33
33
  import {
34
34
  type CompactionResult,
35
35
  calculateContextTokens,
36
36
  collectEntriesForBranchSummary,
37
- compact,
38
37
  estimateContextTokens,
39
38
  generateBranchSummary,
40
- prepareCompaction,
41
- shouldCompact,
42
39
  } from "./compaction/index.js";
40
+ import { CompactionOrchestrator } from "./compaction-orchestrator.js";
43
41
  import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
44
42
  import { exportSessionToHtml, type ToolHtmlRenderer } from "./export-html/index.js";
45
43
  import { createToolHtmlRenderer } from "./export-html/tool-renderer.js";
@@ -53,7 +51,6 @@ import {
53
51
  type MessageEndEvent,
54
52
  type MessageStartEvent,
55
53
  type MessageUpdateEvent,
56
- type SessionBeforeCompactResult,
57
54
  type SessionBeforeForkResult,
58
55
  type SessionBeforeSwitchResult,
59
56
  type SessionBeforeTreeResult,
@@ -73,7 +70,8 @@ import { FallbackResolver } from "./fallback-resolver.js";
73
70
  import type { ModelRegistry } from "./model-registry.js";
74
71
  import { expandPromptTemplate, type PromptTemplate } from "./prompt-templates.js";
75
72
  import type { ResourceExtensionPaths, ResourceLoader } from "./resource-loader.js";
76
- import type { BranchSummaryEntry, CompactionEntry, SessionManager } from "./session-manager.js";
73
+ import { RetryHandler } from "./retry-handler.js";
74
+ import type { BranchSummaryEntry, SessionManager } from "./session-manager.js";
77
75
  import { getLatestCompactionEntry } from "./session-manager.js";
78
76
  import type { SettingsManager } from "./settings-manager.js";
79
77
  import { BUILTIN_SLASH_COMMANDS, type SlashCommandInfo, type SlashCommandLocation } from "./slash-commands.js";
@@ -232,19 +230,15 @@ export class AgentSession {
232
230
  /** Messages queued to be included with the next user prompt as context ("asides"). */
233
231
  private _pendingNextTurnMessages: CustomMessage[] = [];
234
232
 
235
- // Compaction state
236
- private _compactionAbortController: AbortController | undefined = undefined;
237
- private _autoCompactionAbortController: AbortController | undefined = undefined;
238
- private _overflowRecoveryAttempted = false;
233
+ // Delegated subsystems
234
+ private _retryHandler: RetryHandler;
235
+ private _compactionOrchestrator: CompactionOrchestrator;
239
236
 
240
- // Branch summarization state
241
- private _branchSummaryAbortController: AbortController | undefined = undefined;
242
-
243
- // Retry state
244
- private _retryAbortController: AbortController | undefined = undefined;
245
- private _retryAttempt = 0;
246
- private _retryPromise: Promise<void> | undefined = undefined;
247
- private _retryResolve: (() => void) | undefined = undefined;
237
+ // Cumulative session stats — survives compaction (#1423)
238
+ private _cumulativeCost = 0;
239
+ private _cumulativeInputTokens = 0;
240
+ private _cumulativeOutputTokens = 0;
241
+ private _cumulativeToolCalls = 0;
248
242
 
249
243
  // Bash execution state
250
244
  private _bashAbortController: AbortController | undefined = undefined;
@@ -299,6 +293,32 @@ export class AgentSession {
299
293
  this._initialActiveToolNames = config.initialActiveToolNames;
300
294
  this._baseToolsOverride = config.baseToolsOverride;
301
295
 
296
+ // Initialize delegated subsystems
297
+ this._retryHandler = new RetryHandler({
298
+ agent: this.agent,
299
+ settingsManager: this.settingsManager,
300
+ modelRegistry: this._modelRegistry,
301
+ fallbackResolver: this._fallbackResolver,
302
+ getModel: () => this.model,
303
+ getSessionId: () => this.sessionId,
304
+ emit: (event) => this._emit(event),
305
+ onModelChange: (model) => this.sessionManager.appendModelChange(model.provider, model.id),
306
+ });
307
+
308
+ this._compactionOrchestrator = new CompactionOrchestrator({
309
+ agent: this.agent,
310
+ sessionManager: this.sessionManager,
311
+ settingsManager: this.settingsManager,
312
+ modelRegistry: this._modelRegistry,
313
+ getModel: () => this.model,
314
+ getSessionId: () => this.sessionId,
315
+ getExtensionRunner: () => this._extensionRunner,
316
+ emit: (event) => this._emit(event),
317
+ disconnectFromAgent: () => this._disconnectFromAgent(),
318
+ reconnectToAgent: () => this._reconnectToAgent(),
319
+ abort: () => this.abort(),
320
+ });
321
+
302
322
  // Always subscribe to agent events for internal handling
303
323
  // (session persistence, extensions, auto-compaction, retry logic)
304
324
  this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);
@@ -342,7 +362,7 @@ export class AgentSession {
342
362
  private _handleAgentEvent = (event: AgentEvent): void => {
343
363
  // Create retry promise synchronously before queueing async processing.
344
364
  // Agent.emit() calls this handler synchronously, and prompt() calls waitForRetry()
345
- // as soon as agent.prompt() resolves. If _retryPromise is created only inside
365
+ // as soon as agent.prompt() resolves. If the retry promise is created only inside
346
366
  // _processAgentEvent, slow earlier queued events can delay agent_end processing
347
367
  // and waitForRetry() can miss the in-flight retry.
348
368
  this._createRetryPromiseForAgentEnd(event);
@@ -357,40 +377,15 @@ export class AgentSession {
357
377
  };
358
378
 
359
379
  private _createRetryPromiseForAgentEnd(event: AgentEvent): void {
360
- if (event.type !== "agent_end" || this._retryPromise) {
361
- return;
362
- }
363
-
364
- const settings = this.settingsManager.getRetrySettings();
365
- if (!settings.enabled) {
366
- return;
367
- }
368
-
369
- const lastAssistant = this._findLastAssistantInMessages(event.messages);
370
- if (!lastAssistant || !this._isRetryableError(lastAssistant)) {
371
- return;
372
- }
373
-
374
- this._retryPromise = new Promise((resolve) => {
375
- this._retryResolve = resolve;
376
- });
377
- }
378
-
379
- private _findLastAssistantInMessages(messages: AgentMessage[]): AssistantMessage | undefined {
380
- for (let i = messages.length - 1; i >= 0; i--) {
381
- const message = messages[i];
382
- if (message.role === "assistant") {
383
- return message as AssistantMessage;
384
- }
385
- }
386
- return undefined;
380
+ if (event.type !== "agent_end") return;
381
+ this._retryHandler.createRetryPromiseForAgentEnd(event.messages);
387
382
  }
388
383
 
389
384
  private async _processAgentEvent(event: AgentEvent): Promise<void> {
390
385
  // When a user message starts, check if it's from either queue and remove it BEFORE emitting
391
386
  // This ensures the UI sees the updated queue state
392
387
  if (event.type === "message_start" && event.message.role === "user") {
393
- this._overflowRecoveryAttempted = false;
388
+ this._compactionOrchestrator.resetOverflowRecovery();
394
389
  const messageText = this._getUserMessageText(event.message);
395
390
  if (messageText) {
396
391
  // Check steering queue first
@@ -438,21 +433,21 @@ export class AgentSession {
438
433
  if (event.message.role === "assistant") {
439
434
  this._lastAssistantMessage = event.message;
440
435
 
436
+ // Accumulate session stats that survive compaction (#1423)
441
437
  const assistantMsg = event.message as AssistantMessage;
438
+ this._cumulativeCost += assistantMsg.usage?.cost?.total ?? 0;
439
+ this._cumulativeInputTokens += assistantMsg.usage?.input ?? 0;
440
+ this._cumulativeOutputTokens += assistantMsg.usage?.output ?? 0;
441
+ this._cumulativeToolCalls += assistantMsg.content.filter((c) => c.type === "toolCall").length;
442
+
442
443
  if (assistantMsg.stopReason !== "error") {
443
- this._overflowRecoveryAttempted = false;
444
+ this._compactionOrchestrator.clearOverflowRecovery();
444
445
  }
445
446
 
446
447
  // Reset retry counter immediately on successful assistant response
447
448
  // This prevents accumulation across multiple LLM calls within a turn
448
- if (assistantMsg.stopReason !== "error" && this._retryAttempt > 0) {
449
- this._emit({
450
- type: "auto_retry_end",
451
- success: true,
452
- attempt: this._retryAttempt,
453
- });
454
- this._retryAttempt = 0;
455
- this._resolveRetry();
449
+ if (assistantMsg.stopReason !== "error") {
450
+ this._retryHandler.handleSuccessfulResponse();
456
451
  }
457
452
  }
458
453
  }
@@ -463,21 +458,12 @@ export class AgentSession {
463
458
  this._lastAssistantMessage = undefined;
464
459
 
465
460
  // Check for retryable errors first (overloaded, rate limit, server errors)
466
- if (this._isRetryableError(msg)) {
467
- const didRetry = await this._handleRetryableError(msg);
461
+ if (this._retryHandler.isRetryableError(msg)) {
462
+ const didRetry = await this._retryHandler.handleRetryableError(msg);
468
463
  if (didRetry) return; // Retry was initiated, don't proceed to compaction
469
464
  }
470
465
 
471
- await this._checkCompaction(msg);
472
- }
473
- }
474
-
475
- /** Resolve the pending retry promise */
476
- private _resolveRetry(): void {
477
- if (this._retryResolve) {
478
- this._retryResolve();
479
- this._retryResolve = undefined;
480
- this._retryPromise = undefined;
466
+ await this._compactionOrchestrator.checkCompaction(msg);
481
467
  }
482
468
  }
483
469
 
@@ -512,10 +498,7 @@ export class AgentSession {
512
498
  };
513
499
  }
514
500
  } catch (err) {
515
- if (err instanceof Error) {
516
- return { block: true, reason: err.message };
517
- }
518
- return { block: true, reason: `Extension failed, blocking execution: ${String(err)}` };
501
+ return { block: true, reason: err instanceof Error ? err.message : `Extension failed, blocking execution: ${String(err)}` };
519
502
  }
520
503
 
521
504
  return undefined;
@@ -720,7 +703,7 @@ export class AgentSession {
720
703
 
721
704
  /** Current retry attempt (0 if not retrying) */
722
705
  get retryAttempt(): number {
723
- return this._retryAttempt;
706
+ return this._retryHandler.retryAttempt;
724
707
  }
725
708
 
726
709
  /**
@@ -767,11 +750,7 @@ export class AgentSession {
767
750
 
768
751
  /** Whether compaction or branch summarization is currently running */
769
752
  get isCompacting(): boolean {
770
- return (
771
- this._autoCompactionAbortController !== undefined ||
772
- this._compactionAbortController !== undefined ||
773
- this._branchSummaryAbortController !== undefined
774
- );
753
+ return this._compactionOrchestrator.isCompacting;
775
754
  }
776
755
 
777
756
  /**
@@ -1025,7 +1004,7 @@ export class AgentSession {
1025
1004
  // Check if we need to compact before sending (catches aborted responses)
1026
1005
  const lastAssistant = this._findLastAssistantMessage();
1027
1006
  if (lastAssistant) {
1028
- await this._checkCompaction(lastAssistant, false);
1007
+ await this._compactionOrchestrator.checkCompaction(lastAssistant, false);
1029
1008
  }
1030
1009
 
1031
1010
  // Build messages array (custom message if any, then user message)
@@ -1078,7 +1057,7 @@ export class AgentSession {
1078
1057
  }
1079
1058
 
1080
1059
  await this.agent.prompt(messages);
1081
- await this.waitForRetry();
1060
+ await this._retryHandler.waitForRetry();
1082
1061
  }
1083
1062
 
1084
1063
  /**
@@ -1106,7 +1085,7 @@ export class AgentSession {
1106
1085
  this._extensionRunner.emitError({
1107
1086
  extensionPath: `command:${commandName}`,
1108
1087
  event: "command",
1109
- error: err instanceof Error ? err.message : String(err),
1088
+ error: getErrorMessage(err),
1110
1089
  });
1111
1090
  return true;
1112
1091
  }
@@ -1137,7 +1116,7 @@ export class AgentSession {
1137
1116
  this._extensionRunner?.emitError({
1138
1117
  extensionPath: skill.filePath,
1139
1118
  event: "skill_expansion",
1140
- error: err instanceof Error ? err.message : String(err),
1119
+ error: getErrorMessage(err),
1141
1120
  });
1142
1121
  return text; // Return original on error
1143
1122
  }
@@ -1356,7 +1335,7 @@ export class AgentSession {
1356
1335
  * Abort current operation and wait for agent to become idle.
1357
1336
  */
1358
1337
  async abort(): Promise<void> {
1359
- this.abortRetry();
1338
+ this._retryHandler.abortRetry();
1360
1339
  this.agent.abort();
1361
1340
  await this.agent.waitForIdle();
1362
1341
  // Ensure agent_end is emitted even when abort interrupts a tool call (#1414).
@@ -1464,6 +1443,26 @@ export class AgentSession {
1464
1443
  });
1465
1444
  }
1466
1445
 
1446
+ /**
1447
+ * Apply a model change: set the model on the agent, persist to session/settings,
1448
+ * re-clamp thinking level, and emit the model_select event.
1449
+ */
1450
+ private async _applyModelChange(
1451
+ model: Model<any>,
1452
+ thinkingLevel: ThinkingLevel,
1453
+ source: "set" | "cycle" | "restore",
1454
+ options?: { persist?: boolean },
1455
+ ): Promise<void> {
1456
+ const previousModel = this.model;
1457
+ this.agent.setModel(model);
1458
+ this.sessionManager.appendModelChange(model.provider, model.id);
1459
+ if (options?.persist !== false) {
1460
+ this.settingsManager.setDefaultModelAndProvider(model.provider, model.id);
1461
+ }
1462
+ this.setThinkingLevel(thinkingLevel);
1463
+ await this._emitModelSelect(model, previousModel, source);
1464
+ }
1465
+
1467
1466
  /**
1468
1467
  * Set model directly.
1469
1468
  * Validates API key, saves to session and settings.
@@ -1475,18 +1474,8 @@ export class AgentSession {
1475
1474
  throw new Error(`No API key for ${model.provider}/${model.id}`);
1476
1475
  }
1477
1476
 
1478
- const previousModel = this.model;
1479
1477
  const thinkingLevel = this._getThinkingLevelForModelSwitch();
1480
- this.agent.setModel(model);
1481
- this.sessionManager.appendModelChange(model.provider, model.id);
1482
- if (options?.persist !== false) {
1483
- this.settingsManager.setDefaultModelAndProvider(model.provider, model.id);
1484
- }
1485
-
1486
- // Re-clamp thinking level for new model's capabilities
1487
- this.setThinkingLevel(thinkingLevel);
1488
-
1489
- await this._emitModelSelect(model, previousModel, "set");
1478
+ await this._applyModelChange(model, thinkingLevel, "set", options);
1490
1479
  }
1491
1480
 
1492
1481
  /**
@@ -1535,22 +1524,11 @@ export class AgentSession {
1535
1524
  const len = scopedModels.length;
1536
1525
  const nextIndex = direction === "forward" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;
1537
1526
  const next = scopedModels[nextIndex];
1538
- const thinkingLevel = this._getThinkingLevelForModelSwitch(next.thinkingLevel);
1539
-
1540
- // Apply model
1541
- this.agent.setModel(next.model);
1542
- this.sessionManager.appendModelChange(next.model.provider, next.model.id);
1543
- if (options?.persist !== false) {
1544
- this.settingsManager.setDefaultModelAndProvider(next.model.provider, next.model.id);
1545
- }
1546
1527
 
1547
- // Apply thinking level.
1548
- // - Explicit scoped model thinking level overrides current session level
1549
- // - Undefined scoped model thinking level inherits the current session preference
1550
- // setThinkingLevel clamps to model capabilities.
1551
- this.setThinkingLevel(thinkingLevel);
1552
-
1553
- await this._emitModelSelect(next.model, currentModel, "cycle");
1528
+ // Explicit scoped model thinking level overrides current session level;
1529
+ // undefined scoped model thinking level inherits the current session preference.
1530
+ const thinkingLevel = this._getThinkingLevelForModelSwitch(next.thinkingLevel);
1531
+ await this._applyModelChange(next.model, thinkingLevel, "cycle", options);
1554
1532
 
1555
1533
  return { model: next.model, thinkingLevel: this.thinkingLevel, isScoped: true };
1556
1534
  }
@@ -1573,16 +1551,7 @@ export class AgentSession {
1573
1551
  }
1574
1552
 
1575
1553
  const thinkingLevel = this._getThinkingLevelForModelSwitch();
1576
- this.agent.setModel(nextModel);
1577
- this.sessionManager.appendModelChange(nextModel.provider, nextModel.id);
1578
- if (options?.persist !== false) {
1579
- this.settingsManager.setDefaultModelAndProvider(nextModel.provider, nextModel.id);
1580
- }
1581
-
1582
- // Re-clamp thinking level for new model's capabilities
1583
- this.setThinkingLevel(thinkingLevel);
1584
-
1585
- await this._emitModelSelect(nextModel, currentModel, "cycle");
1554
+ await this._applyModelChange(nextModel, thinkingLevel, "cycle", options);
1586
1555
 
1587
1556
  return { model: nextModel, thinkingLevel: this.thinkingLevel, isScoped: false };
1588
1557
  }
@@ -1712,373 +1681,27 @@ export class AgentSession {
1712
1681
  * @param customInstructions Optional instructions for the compaction summary
1713
1682
  */
1714
1683
  async compact(customInstructions?: string): Promise<CompactionResult> {
1715
- this._disconnectFromAgent();
1716
- await this.abort();
1717
- this._compactionAbortController = new AbortController();
1718
-
1719
- try {
1720
- if (!this.model) {
1721
- throw new Error("No model selected");
1722
- }
1723
-
1724
- const apiKey = await this._modelRegistry.getApiKey(this.model, this.sessionId);
1725
- if (!apiKey) {
1726
- throw new Error(`No API key for ${this.model.provider}`);
1727
- }
1728
-
1729
- const pathEntries = this.sessionManager.getBranch();
1730
- const settings = this.settingsManager.getCompactionSettings();
1731
-
1732
- const preparation = prepareCompaction(pathEntries, settings);
1733
- if (!preparation) {
1734
- // Check why we can't compact
1735
- const lastEntry = pathEntries[pathEntries.length - 1];
1736
- if (lastEntry?.type === "compaction") {
1737
- throw new Error("Already compacted");
1738
- }
1739
- throw new Error("Nothing to compact (session too small)");
1740
- }
1741
-
1742
- let extensionCompaction: CompactionResult | undefined;
1743
- let fromExtension = false;
1744
-
1745
- if (this._extensionRunner?.hasHandlers("session_before_compact")) {
1746
- const result = (await this._extensionRunner.emit({
1747
- type: "session_before_compact",
1748
- preparation,
1749
- branchEntries: pathEntries,
1750
- customInstructions,
1751
- signal: this._compactionAbortController.signal,
1752
- })) as SessionBeforeCompactResult | undefined;
1753
-
1754
- if (result?.cancel) {
1755
- throw new Error("Compaction cancelled");
1756
- }
1757
-
1758
- if (result?.compaction) {
1759
- extensionCompaction = result.compaction;
1760
- fromExtension = true;
1761
- }
1762
- }
1763
-
1764
- let summary: string;
1765
- let firstKeptEntryId: string;
1766
- let tokensBefore: number;
1767
- let details: unknown;
1768
-
1769
- if (extensionCompaction) {
1770
- // Extension provided compaction content
1771
- summary = extensionCompaction.summary;
1772
- firstKeptEntryId = extensionCompaction.firstKeptEntryId;
1773
- tokensBefore = extensionCompaction.tokensBefore;
1774
- details = extensionCompaction.details;
1775
- } else {
1776
- // Generate compaction result
1777
- const result = await compact(
1778
- preparation,
1779
- this.model,
1780
- apiKey,
1781
- customInstructions,
1782
- this._compactionAbortController.signal,
1783
- );
1784
- summary = result.summary;
1785
- firstKeptEntryId = result.firstKeptEntryId;
1786
- tokensBefore = result.tokensBefore;
1787
- details = result.details;
1788
- }
1789
-
1790
- if (this._compactionAbortController.signal.aborted) {
1791
- throw new Error("Compaction cancelled");
1792
- }
1793
-
1794
- this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
1795
- const newEntries = this.sessionManager.getEntries();
1796
- const sessionContext = this.sessionManager.buildSessionContext();
1797
- this.agent.replaceMessages(sessionContext.messages);
1798
-
1799
- // Get the saved compaction entry for the extension event
1800
- const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary) as
1801
- | CompactionEntry
1802
- | undefined;
1803
-
1804
- if (this._extensionRunner && savedCompactionEntry) {
1805
- await this._extensionRunner.emit({
1806
- type: "session_compact",
1807
- compactionEntry: savedCompactionEntry,
1808
- fromExtension,
1809
- });
1810
- }
1811
-
1812
- return {
1813
- summary,
1814
- firstKeptEntryId,
1815
- tokensBefore,
1816
- details,
1817
- };
1818
- } finally {
1819
- this._compactionAbortController = undefined;
1820
- this._reconnectToAgent();
1821
- }
1684
+ return this._compactionOrchestrator.compact(customInstructions);
1822
1685
  }
1823
1686
 
1824
- /**
1825
- * Cancel in-progress compaction (manual or auto).
1826
- */
1687
+ /** Cancel in-progress compaction (manual or auto) */
1827
1688
  abortCompaction(): void {
1828
- this._compactionAbortController?.abort();
1829
- this._autoCompactionAbortController?.abort();
1689
+ this._compactionOrchestrator.abortCompaction();
1830
1690
  }
1831
1691
 
1832
- /**
1833
- * Cancel in-progress branch summarization.
1834
- */
1692
+ /** Cancel in-progress branch summarization */
1835
1693
  abortBranchSummary(): void {
1836
- this._branchSummaryAbortController?.abort();
1837
- }
1838
-
1839
- /**
1840
- * Check if compaction is needed and run it.
1841
- * Called after agent_end and before prompt submission.
1842
- *
1843
- * Two cases:
1844
- * 1. Overflow: LLM returned context overflow error, remove error message from agent state, compact, auto-retry
1845
- * 2. Threshold: Context over threshold, compact, NO auto-retry (user continues manually)
1846
- *
1847
- * @param assistantMessage The assistant message to check
1848
- * @param skipAbortedCheck If false, include aborted messages (for pre-prompt check). Default: true
1849
- */
1850
- private async _checkCompaction(assistantMessage: AssistantMessage, skipAbortedCheck = true): Promise<void> {
1851
- const settings = this.settingsManager.getCompactionSettings();
1852
- if (!settings.enabled) return;
1853
-
1854
- // Skip if message was aborted (user cancelled) - unless skipAbortedCheck is false
1855
- if (skipAbortedCheck && assistantMessage.stopReason === "aborted") return;
1856
-
1857
- const contextWindow = this.model?.contextWindow ?? 0;
1858
-
1859
- // Skip overflow check if the message came from a different model.
1860
- // This handles the case where user switched from a smaller-context model (e.g. opus)
1861
- // to a larger-context model (e.g. codex) - the overflow error from the old model
1862
- // shouldn't trigger compaction for the new model.
1863
- const sameModel =
1864
- this.model && assistantMessage.provider === this.model.provider && assistantMessage.model === this.model.id;
1865
-
1866
- // Skip compaction checks if this assistant message is older than the latest
1867
- // compaction boundary. This prevents a stale pre-compaction usage/error
1868
- // from retriggering compaction on the first prompt after compaction.
1869
- const compactionEntry = getLatestCompactionEntry(this.sessionManager.getBranch());
1870
- const assistantIsFromBeforeCompaction =
1871
- compactionEntry !== null && assistantMessage.timestamp <= new Date(compactionEntry.timestamp).getTime();
1872
- if (assistantIsFromBeforeCompaction) {
1873
- return;
1874
- }
1875
-
1876
- // Case 1: Overflow - LLM returned context overflow error
1877
- if (sameModel && isContextOverflow(assistantMessage, contextWindow)) {
1878
- if (this._overflowRecoveryAttempted) {
1879
- this._emit({
1880
- type: "auto_compaction_end",
1881
- result: undefined,
1882
- aborted: false,
1883
- willRetry: false,
1884
- errorMessage:
1885
- "Context overflow recovery failed after one compact-and-retry attempt. Try reducing context or switching to a larger-context model.",
1886
- });
1887
- return;
1888
- }
1889
-
1890
- this._overflowRecoveryAttempted = true;
1891
- // Remove the error message from agent state (it IS saved to session for history,
1892
- // but we don't want it in context for the retry)
1893
- const messages = this.agent.state.messages;
1894
- if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
1895
- this.agent.replaceMessages(messages.slice(0, -1));
1896
- }
1897
- await this._runAutoCompaction("overflow", true);
1898
- return;
1899
- }
1900
-
1901
- // Case 2: Threshold - context is getting large
1902
- // For error messages (no usage data), estimate from last successful response.
1903
- // This ensures sessions that hit persistent API errors (e.g. 529) can still compact.
1904
- let contextTokens: number;
1905
- if (assistantMessage.stopReason === "error") {
1906
- const messages = this.agent.state.messages;
1907
- const estimate = estimateContextTokens(messages);
1908
- if (estimate.lastUsageIndex === null) return; // No usage data at all
1909
- // Verify the usage source is post-compaction. Kept pre-compaction messages
1910
- // have stale usage reflecting the old (larger) context and would falsely
1911
- // trigger compaction right after one just finished.
1912
- const usageMsg = messages[estimate.lastUsageIndex];
1913
- if (
1914
- compactionEntry &&
1915
- usageMsg.role === "assistant" &&
1916
- (usageMsg as AssistantMessage).timestamp <= new Date(compactionEntry.timestamp).getTime()
1917
- ) {
1918
- return;
1919
- }
1920
- contextTokens = estimate.tokens;
1921
- } else {
1922
- contextTokens = calculateContextTokens(assistantMessage.usage);
1923
- }
1924
- if (shouldCompact(contextTokens, contextWindow, settings)) {
1925
- await this._runAutoCompaction("threshold", false);
1926
- }
1694
+ this._compactionOrchestrator.abortBranchSummary();
1927
1695
  }
1928
1696
 
1929
- /**
1930
- * Internal: Run auto-compaction with events.
1931
- */
1932
- private async _runAutoCompaction(reason: "overflow" | "threshold", willRetry: boolean): Promise<void> {
1933
- const settings = this.settingsManager.getCompactionSettings();
1934
-
1935
- this._emit({ type: "auto_compaction_start", reason });
1936
- this._autoCompactionAbortController = new AbortController();
1937
-
1938
- try {
1939
- if (!this.model) {
1940
- this._emit({ type: "auto_compaction_end", result: undefined, aborted: false, willRetry: false });
1941
- return;
1942
- }
1943
-
1944
- const apiKey = await this._modelRegistry.getApiKey(this.model, this.sessionId);
1945
- if (!apiKey) {
1946
- this._emit({ type: "auto_compaction_end", result: undefined, aborted: false, willRetry: false });
1947
- return;
1948
- }
1949
-
1950
- const pathEntries = this.sessionManager.getBranch();
1951
-
1952
- const preparation = prepareCompaction(pathEntries, settings);
1953
- if (!preparation) {
1954
- this._emit({ type: "auto_compaction_end", result: undefined, aborted: false, willRetry: false });
1955
- return;
1956
- }
1957
-
1958
- let extensionCompaction: CompactionResult | undefined;
1959
- let fromExtension = false;
1960
-
1961
- if (this._extensionRunner?.hasHandlers("session_before_compact")) {
1962
- const extensionResult = (await this._extensionRunner.emit({
1963
- type: "session_before_compact",
1964
- preparation,
1965
- branchEntries: pathEntries,
1966
- customInstructions: undefined,
1967
- signal: this._autoCompactionAbortController.signal,
1968
- })) as SessionBeforeCompactResult | undefined;
1969
-
1970
- if (extensionResult?.cancel) {
1971
- this._emit({ type: "auto_compaction_end", result: undefined, aborted: true, willRetry: false });
1972
- return;
1973
- }
1974
-
1975
- if (extensionResult?.compaction) {
1976
- extensionCompaction = extensionResult.compaction;
1977
- fromExtension = true;
1978
- }
1979
- }
1980
-
1981
- let summary: string;
1982
- let firstKeptEntryId: string;
1983
- let tokensBefore: number;
1984
- let details: unknown;
1985
-
1986
- if (extensionCompaction) {
1987
- // Extension provided compaction content
1988
- summary = extensionCompaction.summary;
1989
- firstKeptEntryId = extensionCompaction.firstKeptEntryId;
1990
- tokensBefore = extensionCompaction.tokensBefore;
1991
- details = extensionCompaction.details;
1992
- } else {
1993
- // Generate compaction result
1994
- const compactResult = await compact(
1995
- preparation,
1996
- this.model,
1997
- apiKey,
1998
- undefined,
1999
- this._autoCompactionAbortController.signal,
2000
- );
2001
- summary = compactResult.summary;
2002
- firstKeptEntryId = compactResult.firstKeptEntryId;
2003
- tokensBefore = compactResult.tokensBefore;
2004
- details = compactResult.details;
2005
- }
2006
-
2007
- if (this._autoCompactionAbortController.signal.aborted) {
2008
- this._emit({ type: "auto_compaction_end", result: undefined, aborted: true, willRetry: false });
2009
- return;
2010
- }
2011
-
2012
- this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
2013
- const newEntries = this.sessionManager.getEntries();
2014
- const sessionContext = this.sessionManager.buildSessionContext();
2015
- this.agent.replaceMessages(sessionContext.messages);
2016
-
2017
- // Get the saved compaction entry for the extension event
2018
- const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary) as
2019
- | CompactionEntry
2020
- | undefined;
2021
-
2022
- if (this._extensionRunner && savedCompactionEntry) {
2023
- await this._extensionRunner.emit({
2024
- type: "session_compact",
2025
- compactionEntry: savedCompactionEntry,
2026
- fromExtension,
2027
- });
2028
- }
2029
-
2030
- const result: CompactionResult = {
2031
- summary,
2032
- firstKeptEntryId,
2033
- tokensBefore,
2034
- details,
2035
- };
2036
- this._emit({ type: "auto_compaction_end", result, aborted: false, willRetry });
2037
-
2038
- if (willRetry) {
2039
- const messages = this.agent.state.messages;
2040
- const lastMsg = messages[messages.length - 1];
2041
- if (lastMsg?.role === "assistant" && (lastMsg as AssistantMessage).stopReason === "error") {
2042
- this.agent.replaceMessages(messages.slice(0, -1));
2043
- }
2044
-
2045
- setTimeout(() => {
2046
- this.agent.continue().catch(() => {});
2047
- }, 100);
2048
- } else if (this.agent.hasQueuedMessages()) {
2049
- // Auto-compaction can complete while follow-up/steering/custom messages are waiting.
2050
- // Kick the loop so queued messages are actually delivered.
2051
- setTimeout(() => {
2052
- this.agent.continue().catch(() => {});
2053
- }, 100);
2054
- }
2055
- } catch (error) {
2056
- const errorMessage = error instanceof Error ? error.message : "compaction failed";
2057
- this._emit({
2058
- type: "auto_compaction_end",
2059
- result: undefined,
2060
- aborted: false,
2061
- willRetry: false,
2062
- errorMessage:
2063
- reason === "overflow"
2064
- ? `Context overflow recovery failed: ${errorMessage}`
2065
- : `Auto-compaction failed: ${errorMessage}`,
2066
- });
2067
- } finally {
2068
- this._autoCompactionAbortController = undefined;
2069
- }
2070
- }
2071
-
2072
- /**
2073
- * Toggle auto-compaction setting.
2074
- */
1697
+ /** Toggle auto-compaction setting */
2075
1698
  setAutoCompactionEnabled(enabled: boolean): void {
2076
- this.settingsManager.setCompactionEnabled(enabled);
1699
+ this._compactionOrchestrator.setAutoCompactionEnabled(enabled);
2077
1700
  }
2078
1701
 
2079
1702
  /** Whether auto-compaction is enabled */
2080
1703
  get autoCompactionEnabled(): boolean {
2081
- return this.settingsManager.getCompactionEnabled();
1704
+ return this._compactionOrchestrator.autoCompactionEnabled;
2082
1705
  }
2083
1706
 
2084
1707
  async bindExtensions(bindings: ExtensionBindings): Promise<void> {
@@ -2212,7 +1835,7 @@ export class AgentSession {
2212
1835
  runner.emitError({
2213
1836
  extensionPath: "<runtime>",
2214
1837
  event: "send_message",
2215
- error: err instanceof Error ? err.message : String(err),
1838
+ error: getErrorMessage(err),
2216
1839
  });
2217
1840
  });
2218
1841
  },
@@ -2221,7 +1844,7 @@ export class AgentSession {
2221
1844
  runner.emitError({
2222
1845
  extensionPath: "<runtime>",
2223
1846
  event: "send_user_message",
2224
- error: err instanceof Error ? err.message : String(err),
1847
+ error: getErrorMessage(err),
2225
1848
  });
2226
1849
  });
2227
1850
  },
@@ -2234,7 +1857,7 @@ export class AgentSession {
2234
1857
  runner.emitError({
2235
1858
  extensionPath: "<runtime>",
2236
1859
  event: "retry_last_turn",
2237
- error: err instanceof Error ? err.message : String(err),
1860
+ error: getErrorMessage(err),
2238
1861
  });
2239
1862
  });
2240
1863
  }
@@ -2434,278 +2057,27 @@ export class AgentSession {
2434
2057
  }
2435
2058
 
2436
2059
  // =========================================================================
2437
- // Auto-Retry
2060
+ // Auto-Retry (delegated to RetryHandler)
2438
2061
  // =========================================================================
2439
2062
 
2440
- /**
2441
- * Check if an error is retryable (overloaded, rate limit, server errors).
2442
- * Context overflow errors are NOT retryable (handled by compaction instead).
2443
- */
2444
- private _isRetryableError(message: AssistantMessage): boolean {
2445
- if (message.stopReason !== "error" || !message.errorMessage) return false;
2446
-
2447
- // Context overflow is handled by compaction, not retry
2448
- const contextWindow = this.model?.contextWindow ?? 0;
2449
- if (isContextOverflow(message, contextWindow)) return false;
2450
-
2451
- const err = message.errorMessage;
2452
- // Match: overloaded_error, rate limit, 429, 500, 502, 503, 504, service unavailable, connection errors, fetch failed, terminated, retry delay exceeded, network unavailable / auth expired (transient network failures)
2453
- 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(
2454
- err,
2455
- );
2456
- }
2457
-
2458
- /**
2459
- * Classify an error message into a usage-limit error type for credential backoff.
2460
- */
2461
- private _classifyErrorType(errorMessage: string): import("./auth-storage.js").UsageLimitErrorType {
2462
- const err = errorMessage.toLowerCase();
2463
- if (/quota|billing|exceeded.*limit|usage.*limit/i.test(err)) return "quota_exhausted";
2464
- if (/rate.?limit|too many requests|429/i.test(err)) return "rate_limit";
2465
- if (/500|502|503|504|server.?error|internal.?error|service.?unavailable/i.test(err)) return "server_error";
2466
- return "unknown";
2467
- }
2468
-
2469
- /**
2470
- * Handle retryable errors with exponential backoff.
2471
- * When multiple credentials are available, marks the failing credential
2472
- * as backed off and retries immediately with the next one.
2473
- * @returns true if retry was initiated, false if max retries exceeded or disabled
2474
- */
2475
- private async _handleRetryableError(message: AssistantMessage): Promise<boolean> {
2476
- const settings = this.settingsManager.getRetrySettings();
2477
- if (!settings.enabled) {
2478
- this._resolveRetry();
2479
- return false;
2480
- }
2481
-
2482
- // Retry promise is created synchronously in _handleAgentEvent for agent_end.
2483
- // Keep a defensive fallback here in case a future refactor bypasses that path.
2484
- if (!this._retryPromise) {
2485
- this._retryPromise = new Promise((resolve) => {
2486
- this._retryResolve = resolve;
2487
- });
2488
- }
2489
-
2490
- // Try credential fallback before counting against retry budget.
2491
- // If another credential is available, switch to it and retry immediately.
2492
- // Only attempt credential rotation for errors that indicate a credential-level
2493
- // problem (rate limit, quota exhaustion, server error). Transport failures
2494
- // ("unknown") like connection resets are not credential-specific — rotating
2495
- // won't help and backing off the only credential causes "Authentication failed".
2496
- if (this.model && message.errorMessage) {
2497
- const errorType = this._classifyErrorType(message.errorMessage);
2498
- const isCredentialError = errorType !== "unknown";
2499
- const hasAlternate = isCredentialError && this._modelRegistry.authStorage.markUsageLimitReached(
2500
- this.model.provider,
2501
- this.sessionId,
2502
- { errorType },
2503
- );
2504
-
2505
- if (hasAlternate) {
2506
- // Remove error message from agent state
2507
- const messages = this.agent.state.messages;
2508
- if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
2509
- this.agent.replaceMessages(messages.slice(0, -1));
2510
- }
2511
-
2512
- this._emit({
2513
- type: "auto_retry_start",
2514
- attempt: this._retryAttempt + 1,
2515
- maxAttempts: settings.maxRetries,
2516
- delayMs: 0,
2517
- errorMessage: `${message.errorMessage} (switching credential)`,
2518
- });
2519
-
2520
- // Retry immediately with the next credential - don't increment _retryAttempt
2521
- setTimeout(() => {
2522
- this.agent.continue().catch(() => {
2523
- // Retry failed - will be caught by next agent_end
2524
- });
2525
- }, 0);
2526
-
2527
- return true;
2528
- }
2529
-
2530
- // All credentials are backed off. Try cross-provider fallback before giving up.
2531
- if (isCredentialError) {
2532
- const fallbackResult = await this._fallbackResolver.findFallback(
2533
- this.model,
2534
- errorType,
2535
- );
2536
-
2537
- if (fallbackResult) {
2538
- // Swap to fallback model — don't persist to settings
2539
- const previousProvider = this.model.provider;
2540
- this.agent.setModel(fallbackResult.model);
2541
- this.sessionManager.appendModelChange(fallbackResult.model.provider, fallbackResult.model.id);
2542
-
2543
- // Remove error message from agent state
2544
- const msgs = this.agent.state.messages;
2545
- if (msgs.length > 0 && msgs[msgs.length - 1].role === "assistant") {
2546
- this.agent.replaceMessages(msgs.slice(0, -1));
2547
- }
2548
-
2549
- this._emit({
2550
- type: "fallback_provider_switch",
2551
- from: `${previousProvider}/${this.model?.id}`,
2552
- to: `${fallbackResult.model.provider}/${fallbackResult.model.id}`,
2553
- reason: fallbackResult.reason,
2554
- });
2555
-
2556
- this._emit({
2557
- type: "auto_retry_start",
2558
- attempt: this._retryAttempt + 1,
2559
- maxAttempts: settings.maxRetries,
2560
- delayMs: 0,
2561
- errorMessage: `${message.errorMessage} (${fallbackResult.reason})`,
2562
- });
2563
-
2564
- // Retry immediately with fallback provider - don't increment _retryAttempt
2565
- setTimeout(() => {
2566
- this.agent.continue().catch(() => {
2567
- // Retry failed - will be caught by next agent_end
2568
- });
2569
- }, 0);
2570
-
2571
- return true;
2572
- }
2573
-
2574
- // No fallback available either
2575
- if (errorType === "quota_exhausted") {
2576
- this._emit({
2577
- type: "fallback_chain_exhausted",
2578
- reason: `All providers exhausted for ${this.model.provider}/${this.model.id}`,
2579
- });
2580
- this._emit({
2581
- type: "auto_retry_end",
2582
- success: false,
2583
- attempt: this._retryAttempt,
2584
- finalError: message.errorMessage,
2585
- });
2586
- this._retryAttempt = 0;
2587
- this._resolveRetry();
2588
- return false;
2589
- }
2590
- }
2591
- }
2592
-
2593
- this._retryAttempt++;
2594
-
2595
- if (this._retryAttempt > settings.maxRetries) {
2596
- // Max retries exceeded, emit final failure and reset
2597
- this._emit({
2598
- type: "auto_retry_end",
2599
- success: false,
2600
- attempt: this._retryAttempt - 1,
2601
- finalError: message.errorMessage,
2602
- });
2603
- this._retryAttempt = 0;
2604
- this._resolveRetry(); // Resolve so waitForRetry() completes
2605
- return false;
2606
- }
2607
-
2608
- // Use server-requested delay when available (rate limit headers), capped by maxDelayMs.
2609
- // Fall back to exponential backoff when no server hint is present.
2610
- const exponentialDelayMs = settings.baseDelayMs * 2 ** (this._retryAttempt - 1);
2611
- let delayMs: number;
2612
- if (message.retryAfterMs !== undefined) {
2613
- const cap = settings.maxDelayMs > 0 ? settings.maxDelayMs : Infinity;
2614
- if (message.retryAfterMs > cap) {
2615
- // Server wants us to wait longer than maxDelayMs — give up to let auto-mode handle recovery
2616
- this._emit({
2617
- type: "auto_retry_end",
2618
- success: false,
2619
- attempt: this._retryAttempt - 1,
2620
- finalError: `Rate limit reset in ${Math.ceil(message.retryAfterMs / 1000)}s (max: ${Math.ceil(cap / 1000)}s). ${message.errorMessage || ""}`.trim(),
2621
- });
2622
- this._retryAttempt = 0;
2623
- this._resolveRetry();
2624
- return false;
2625
- }
2626
- delayMs = message.retryAfterMs;
2627
- } else {
2628
- delayMs = exponentialDelayMs;
2629
- }
2630
-
2631
- this._emit({
2632
- type: "auto_retry_start",
2633
- attempt: this._retryAttempt,
2634
- maxAttempts: settings.maxRetries,
2635
- delayMs,
2636
- errorMessage: message.errorMessage || "Unknown error",
2637
- });
2638
-
2639
- // Remove error message from agent state (keep in session for history)
2640
- const messages = this.agent.state.messages;
2641
- if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
2642
- this.agent.replaceMessages(messages.slice(0, -1));
2643
- }
2644
-
2645
- // Wait with exponential backoff (abortable)
2646
- this._retryAbortController = new AbortController();
2647
- try {
2648
- await sleep(delayMs, this._retryAbortController.signal);
2649
- } catch {
2650
- // Aborted during sleep - emit end event so UI can clean up
2651
- const attempt = this._retryAttempt;
2652
- this._retryAttempt = 0;
2653
- this._retryAbortController = undefined;
2654
- this._emit({
2655
- type: "auto_retry_end",
2656
- success: false,
2657
- attempt,
2658
- finalError: "Retry cancelled",
2659
- });
2660
- this._resolveRetry();
2661
- return false;
2662
- }
2663
- this._retryAbortController = undefined;
2664
-
2665
- // Retry via continue() - use setTimeout to break out of event handler chain
2666
- setTimeout(() => {
2667
- this.agent.continue().catch(() => {
2668
- // Retry failed - will be caught by next agent_end
2669
- });
2670
- }, 0);
2671
-
2672
- return true;
2673
- }
2674
-
2675
- /**
2676
- * Cancel in-progress retry.
2677
- */
2063
+ /** Cancel in-progress retry */
2678
2064
  abortRetry(): void {
2679
- this._retryAbortController?.abort();
2680
- // Note: _retryAttempt is reset in the catch block of _autoRetry
2681
- this._resolveRetry();
2682
- }
2683
-
2684
- /**
2685
- * Wait for any in-progress retry to complete.
2686
- * Returns immediately if no retry is in progress.
2687
- */
2688
- private async waitForRetry(): Promise<void> {
2689
- if (this._retryPromise) {
2690
- await this._retryPromise;
2691
- }
2065
+ this._retryHandler.abortRetry();
2692
2066
  }
2693
2067
 
2694
2068
  /** Whether auto-retry is currently in progress */
2695
2069
  get isRetrying(): boolean {
2696
- return this._retryPromise !== undefined;
2070
+ return this._retryHandler.isRetrying;
2697
2071
  }
2698
2072
 
2699
2073
  /** Whether auto-retry is enabled */
2700
2074
  get autoRetryEnabled(): boolean {
2701
- return this.settingsManager.getRetryEnabled();
2075
+ return this._retryHandler.autoRetryEnabled;
2702
2076
  }
2703
2077
 
2704
- /**
2705
- * Toggle auto-retry setting.
2706
- */
2078
+ /** Toggle auto-retry setting */
2707
2079
  setAutoRetryEnabled(enabled: boolean): void {
2708
- this.settingsManager.setRetryEnabled(enabled);
2080
+ this._retryHandler.setAutoRetryEnabled(enabled);
2709
2081
  }
2710
2082
 
2711
2083
  // =========================================================================
@@ -3029,7 +2401,7 @@ export class AgentSession {
3029
2401
  };
3030
2402
 
3031
2403
  // Set up abort controller for summarization
3032
- this._branchSummaryAbortController = new AbortController();
2404
+ this._compactionOrchestrator.branchSummaryAbortController = new AbortController();
3033
2405
  let extensionSummary: { summary: string; details?: unknown } | undefined;
3034
2406
  let fromExtension = false;
3035
2407
 
@@ -3038,7 +2410,7 @@ export class AgentSession {
3038
2410
  const result = (await this._extensionRunner.emit({
3039
2411
  type: "session_before_tree",
3040
2412
  preparation,
3041
- signal: this._branchSummaryAbortController.signal,
2413
+ signal: this._compactionOrchestrator.branchSummaryAbortController.signal,
3042
2414
  })) as SessionBeforeTreeResult | undefined;
3043
2415
 
3044
2416
  if (result?.cancel) {
@@ -3075,12 +2447,12 @@ export class AgentSession {
3075
2447
  const result = await generateBranchSummary(entriesToSummarize, {
3076
2448
  model,
3077
2449
  apiKey,
3078
- signal: this._branchSummaryAbortController.signal,
2450
+ signal: this._compactionOrchestrator.branchSummaryAbortController.signal,
3079
2451
  customInstructions,
3080
2452
  replaceInstructions,
3081
2453
  reserveTokens: branchSummarySettings.reserveTokens,
3082
2454
  });
3083
- this._branchSummaryAbortController = undefined;
2455
+ this._compactionOrchestrator.branchSummaryAbortController = undefined;
3084
2456
  if (result.aborted) {
3085
2457
  return { cancelled: true, aborted: true };
3086
2458
  }
@@ -3162,7 +2534,7 @@ export class AgentSession {
3162
2534
 
3163
2535
  // Emit to custom tools
3164
2536
 
3165
- this._branchSummaryAbortController = undefined;
2537
+ this._compactionOrchestrator.branchSummaryAbortController = undefined;
3166
2538
  return { editorText, cancelled: false, summaryEntry };
3167
2539
  }
3168
2540
 
@@ -3230,17 +2602,17 @@ export class AgentSession {
3230
2602
  sessionId: this.sessionId,
3231
2603
  userMessages,
3232
2604
  assistantMessages,
3233
- toolCalls,
2605
+ toolCalls: Math.max(toolCalls, this._cumulativeToolCalls),
3234
2606
  toolResults,
3235
2607
  totalMessages: state.messages.length,
3236
2608
  tokens: {
3237
- input: totalInput,
3238
- output: totalOutput,
2609
+ input: Math.max(totalInput, this._cumulativeInputTokens),
2610
+ output: Math.max(totalOutput, this._cumulativeOutputTokens),
3239
2611
  cacheRead: totalCacheRead,
3240
2612
  cacheWrite: totalCacheWrite,
3241
- total: totalInput + totalOutput + totalCacheRead + totalCacheWrite,
2613
+ total: Math.max(totalInput + totalOutput, this._cumulativeInputTokens + this._cumulativeOutputTokens) + totalCacheRead + totalCacheWrite,
3242
2614
  },
3243
- cost: totalCost,
2615
+ cost: Math.max(totalCost, this._cumulativeCost),
3244
2616
  };
3245
2617
  }
3246
2618