@code-yeongyu/senpi 2026.7.23 → 2026.7.25-2

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 (900) hide show
  1. package/CHANGELOG.md +162 -6
  2. package/README.md +14 -1
  3. package/dist/beta/omo-local-update.d.ts +207 -0
  4. package/dist/beta/omo-local-update.d.ts.map +1 -0
  5. package/dist/beta/omo-local-update.js +708 -0
  6. package/dist/beta/omo-local-update.js.map +1 -0
  7. package/dist/cli/args.d.ts +2 -0
  8. package/dist/cli/args.d.ts.map +1 -1
  9. package/dist/cli/args.js +6 -0
  10. package/dist/cli/args.js.map +1 -1
  11. package/dist/cli/config-selector.js +1 -1
  12. package/dist/cli/config-selector.js.map +1 -1
  13. package/dist/core/agent-session-runtime.d.ts +15 -1
  14. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  15. package/dist/core/agent-session-runtime.js +12 -2
  16. package/dist/core/agent-session-runtime.js.map +1 -1
  17. package/dist/core/agent-session.d.ts +115 -4
  18. package/dist/core/agent-session.d.ts.map +1 -1
  19. package/dist/core/agent-session.js +1317 -320
  20. package/dist/core/agent-session.js.map +1 -1
  21. package/dist/core/compaction/branch-summarization.d.ts +7 -1
  22. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  23. package/dist/core/compaction/branch-summarization.js +8 -12
  24. package/dist/core/compaction/branch-summarization.js.map +1 -1
  25. package/dist/core/compaction/compaction.d.ts +22 -4
  26. package/dist/core/compaction/compaction.d.ts.map +1 -1
  27. package/dist/core/compaction/compaction.js +114 -51
  28. package/dist/core/compaction/compaction.js.map +1 -1
  29. package/dist/core/compaction/lifecycle.d.ts +69 -0
  30. package/dist/core/compaction/lifecycle.d.ts.map +1 -0
  31. package/dist/core/compaction/lifecycle.js +104 -0
  32. package/dist/core/compaction/lifecycle.js.map +1 -0
  33. package/dist/core/compaction/utils.d.ts +12 -1
  34. package/dist/core/compaction/utils.d.ts.map +1 -1
  35. package/dist/core/compaction/utils.js +22 -17
  36. package/dist/core/compaction/utils.js.map +1 -1
  37. package/dist/core/event-bus.d.ts.map +1 -1
  38. package/dist/core/event-bus.js +11 -1
  39. package/dist/core/event-bus.js.map +1 -1
  40. package/dist/core/extensions/builtin/compaction/index.d.ts +2 -1
  41. package/dist/core/extensions/builtin/compaction/index.d.ts.map +1 -1
  42. package/dist/core/extensions/builtin/compaction/index.js +42 -30
  43. package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
  44. package/dist/core/extensions/builtin/compaction/openai-remote-model.d.ts +9 -2
  45. package/dist/core/extensions/builtin/compaction/openai-remote-model.d.ts.map +1 -1
  46. package/dist/core/extensions/builtin/compaction/openai-remote-model.js +78 -28
  47. package/dist/core/extensions/builtin/compaction/openai-remote-model.js.map +1 -1
  48. package/dist/core/extensions/builtin/compaction/openai-remote-schema.d.ts +3 -1
  49. package/dist/core/extensions/builtin/compaction/openai-remote-schema.d.ts.map +1 -1
  50. package/dist/core/extensions/builtin/compaction/openai-remote-schema.js +19 -1
  51. package/dist/core/extensions/builtin/compaction/openai-remote-schema.js.map +1 -1
  52. package/dist/core/extensions/builtin/compaction/openai-remote.d.ts +20 -6
  53. package/dist/core/extensions/builtin/compaction/openai-remote.d.ts.map +1 -1
  54. package/dist/core/extensions/builtin/compaction/openai-remote.js +207 -22
  55. package/dist/core/extensions/builtin/compaction/openai-remote.js.map +1 -1
  56. package/dist/core/extensions/builtin/compaction/speculative.d.ts +4 -2
  57. package/dist/core/extensions/builtin/compaction/speculative.d.ts.map +1 -1
  58. package/dist/core/extensions/builtin/compaction/speculative.js +25 -13
  59. package/dist/core/extensions/builtin/compaction/speculative.js.map +1 -1
  60. package/dist/core/extensions/builtin/config-reload/index.d.ts +7 -0
  61. package/dist/core/extensions/builtin/config-reload/index.d.ts.map +1 -1
  62. package/dist/core/extensions/builtin/config-reload/index.js +69 -34
  63. package/dist/core/extensions/builtin/config-reload/index.js.map +1 -1
  64. package/dist/core/extensions/builtin/config-reload/log.d.ts +4 -1
  65. package/dist/core/extensions/builtin/config-reload/log.d.ts.map +1 -1
  66. package/dist/core/extensions/builtin/config-reload/log.js +3 -0
  67. package/dist/core/extensions/builtin/config-reload/log.js.map +1 -1
  68. package/dist/core/extensions/builtin/config-reload/routine-settings.d.ts +15 -0
  69. package/dist/core/extensions/builtin/config-reload/routine-settings.d.ts.map +1 -0
  70. package/dist/core/extensions/builtin/config-reload/routine-settings.js +102 -0
  71. package/dist/core/extensions/builtin/config-reload/routine-settings.js.map +1 -0
  72. package/dist/core/extensions/builtin/hooks/index.js +1 -1
  73. package/dist/core/extensions/builtin/hooks/index.js.map +1 -1
  74. package/dist/core/extensions/builtin/hooks/lifecycle-adapter.d.ts +3 -1
  75. package/dist/core/extensions/builtin/hooks/lifecycle-adapter.d.ts.map +1 -1
  76. package/dist/core/extensions/builtin/hooks/lifecycle-adapter.js +2 -1
  77. package/dist/core/extensions/builtin/hooks/lifecycle-adapter.js.map +1 -1
  78. package/dist/core/extensions/builtin/mcp/auth/commands-auth-dispatch.d.ts +2 -1
  79. package/dist/core/extensions/builtin/mcp/auth/commands-auth-dispatch.d.ts.map +1 -1
  80. package/dist/core/extensions/builtin/mcp/auth/commands-auth-dispatch.js +5 -3
  81. package/dist/core/extensions/builtin/mcp/auth/commands-auth-dispatch.js.map +1 -1
  82. package/dist/core/extensions/builtin/mcp/auth/commands-auth.d.ts +5 -0
  83. package/dist/core/extensions/builtin/mcp/auth/commands-auth.d.ts.map +1 -1
  84. package/dist/core/extensions/builtin/mcp/auth/commands-auth.js +8 -3
  85. package/dist/core/extensions/builtin/mcp/auth/commands-auth.js.map +1 -1
  86. package/dist/core/extensions/builtin/mcp/catalog.d.ts +3 -1
  87. package/dist/core/extensions/builtin/mcp/catalog.d.ts.map +1 -1
  88. package/dist/core/extensions/builtin/mcp/catalog.js +2 -0
  89. package/dist/core/extensions/builtin/mcp/catalog.js.map +1 -1
  90. package/dist/core/extensions/builtin/mcp/commands.d.ts +1 -1
  91. package/dist/core/extensions/builtin/mcp/commands.d.ts.map +1 -1
  92. package/dist/core/extensions/builtin/mcp/commands.js +34 -39
  93. package/dist/core/extensions/builtin/mcp/commands.js.map +1 -1
  94. package/dist/core/extensions/builtin/mcp/connection-types.d.ts +2 -0
  95. package/dist/core/extensions/builtin/mcp/connection-types.d.ts.map +1 -1
  96. package/dist/core/extensions/builtin/mcp/connection-types.js.map +1 -1
  97. package/dist/core/extensions/builtin/mcp/connection.d.ts.map +1 -1
  98. package/dist/core/extensions/builtin/mcp/connection.js +3 -0
  99. package/dist/core/extensions/builtin/mcp/connection.js.map +1 -1
  100. package/dist/core/extensions/builtin/mcp/elicitation.d.ts +7 -3
  101. package/dist/core/extensions/builtin/mcp/elicitation.d.ts.map +1 -1
  102. package/dist/core/extensions/builtin/mcp/elicitation.js +5 -5
  103. package/dist/core/extensions/builtin/mcp/elicitation.js.map +1 -1
  104. package/dist/core/extensions/builtin/mcp/expose/register.d.ts.map +1 -1
  105. package/dist/core/extensions/builtin/mcp/expose/register.js +1 -0
  106. package/dist/core/extensions/builtin/mcp/expose/register.js.map +1 -1
  107. package/dist/core/extensions/builtin/mcp/expose/session.d.ts +2 -0
  108. package/dist/core/extensions/builtin/mcp/expose/session.d.ts.map +1 -1
  109. package/dist/core/extensions/builtin/mcp/expose/session.js +3 -0
  110. package/dist/core/extensions/builtin/mcp/expose/session.js.map +1 -1
  111. package/dist/core/extensions/builtin/mcp/guard/output-guard.d.ts +9 -1
  112. package/dist/core/extensions/builtin/mcp/guard/output-guard.d.ts.map +1 -1
  113. package/dist/core/extensions/builtin/mcp/guard/output-guard.js +18 -6
  114. package/dist/core/extensions/builtin/mcp/guard/output-guard.js.map +1 -1
  115. package/dist/core/extensions/builtin/mcp/index.d.ts +4 -2
  116. package/dist/core/extensions/builtin/mcp/index.d.ts.map +1 -1
  117. package/dist/core/extensions/builtin/mcp/index.js +150 -127
  118. package/dist/core/extensions/builtin/mcp/index.js.map +1 -1
  119. package/dist/core/extensions/builtin/mcp/instructions.d.ts +1 -1
  120. package/dist/core/extensions/builtin/mcp/instructions.d.ts.map +1 -1
  121. package/dist/core/extensions/builtin/mcp/instructions.js +6 -6
  122. package/dist/core/extensions/builtin/mcp/instructions.js.map +1 -1
  123. package/dist/core/extensions/builtin/mcp/prompts.d.ts +2 -0
  124. package/dist/core/extensions/builtin/mcp/prompts.d.ts.map +1 -1
  125. package/dist/core/extensions/builtin/mcp/prompts.js +9 -3
  126. package/dist/core/extensions/builtin/mcp/prompts.js.map +1 -1
  127. package/dist/core/extensions/builtin/mcp/resources.d.ts +2 -0
  128. package/dist/core/extensions/builtin/mcp/resources.d.ts.map +1 -1
  129. package/dist/core/extensions/builtin/mcp/resources.js +1 -0
  130. package/dist/core/extensions/builtin/mcp/resources.js.map +1 -1
  131. package/dist/core/extensions/builtin/mcp/service-register.d.ts.map +1 -1
  132. package/dist/core/extensions/builtin/mcp/service-register.js +1 -0
  133. package/dist/core/extensions/builtin/mcp/service-register.js.map +1 -1
  134. package/dist/core/extensions/builtin/mcp/service-types.d.ts +2 -0
  135. package/dist/core/extensions/builtin/mcp/service-types.d.ts.map +1 -1
  136. package/dist/core/extensions/builtin/mcp/service-types.js.map +1 -1
  137. package/dist/core/extensions/builtin/mcp/service.d.ts +12 -1
  138. package/dist/core/extensions/builtin/mcp/service.d.ts.map +1 -1
  139. package/dist/core/extensions/builtin/mcp/service.js +44 -2
  140. package/dist/core/extensions/builtin/mcp/service.js.map +1 -1
  141. package/dist/core/extensions/builtin/mcp/transport.d.ts +2 -0
  142. package/dist/core/extensions/builtin/mcp/transport.d.ts.map +1 -1
  143. package/dist/core/extensions/builtin/mcp/transport.js +7 -7
  144. package/dist/core/extensions/builtin/mcp/transport.js.map +1 -1
  145. package/dist/core/extensions/builtin/prompt-preset/claude-fable-5.d.ts.map +1 -1
  146. package/dist/core/extensions/builtin/prompt-preset/claude-fable-5.js +86 -7
  147. package/dist/core/extensions/builtin/prompt-preset/claude-fable-5.js.map +1 -1
  148. package/dist/core/extensions/builtin/prompt-preset/claude-opus-5.d.ts +3 -0
  149. package/dist/core/extensions/builtin/prompt-preset/claude-opus-5.d.ts.map +1 -0
  150. package/dist/core/extensions/builtin/prompt-preset/claude-opus-5.js +102 -0
  151. package/dist/core/extensions/builtin/prompt-preset/claude-opus-5.js.map +1 -0
  152. package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.d.ts +48 -0
  153. package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.d.ts.map +1 -1
  154. package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.js +88 -64
  155. package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.js.map +1 -1
  156. package/dist/core/extensions/builtin/prompt-preset/kimi-k3.d.ts.map +1 -1
  157. package/dist/core/extensions/builtin/prompt-preset/kimi-k3.js +96 -6
  158. package/dist/core/extensions/builtin/prompt-preset/kimi-k3.js.map +1 -1
  159. package/dist/core/extensions/builtin/prompt-preset/presets.d.ts.map +1 -1
  160. package/dist/core/extensions/builtin/prompt-preset/presets.js +9 -0
  161. package/dist/core/extensions/builtin/prompt-preset/presets.js.map +1 -1
  162. package/dist/core/extensions/builtin/prompt-preset/settings.d.ts +1 -1
  163. package/dist/core/extensions/builtin/prompt-preset/settings.d.ts.map +1 -1
  164. package/dist/core/extensions/builtin/prompt-preset/settings.js +1 -0
  165. package/dist/core/extensions/builtin/prompt-preset/settings.js.map +1 -1
  166. package/dist/core/extensions/builtin/terminal/extension.d.ts.map +1 -1
  167. package/dist/core/extensions/builtin/terminal/extension.js +1 -0
  168. package/dist/core/extensions/builtin/terminal/extension.js.map +1 -1
  169. package/dist/core/extensions/builtin/terminal/tools/bash.d.ts +15 -3
  170. package/dist/core/extensions/builtin/terminal/tools/bash.d.ts.map +1 -1
  171. package/dist/core/extensions/builtin/terminal/tools/bash.js +30 -6
  172. package/dist/core/extensions/builtin/terminal/tools/bash.js.map +1 -1
  173. package/dist/core/extensions/builtin/terminal/tools/context.d.ts +5 -0
  174. package/dist/core/extensions/builtin/terminal/tools/context.d.ts.map +1 -1
  175. package/dist/core/extensions/builtin/terminal/tools/context.js.map +1 -1
  176. package/dist/core/extensions/builtin/websearch/index.d.ts.map +1 -1
  177. package/dist/core/extensions/builtin/websearch/index.js +2 -4
  178. package/dist/core/extensions/builtin/websearch/index.js.map +1 -1
  179. package/dist/core/extensions/builtin/websearch/websearch/config.d.ts.map +1 -1
  180. package/dist/core/extensions/builtin/websearch/websearch/config.js +4 -1
  181. package/dist/core/extensions/builtin/websearch/websearch/config.js.map +1 -1
  182. package/dist/core/extensions/builtin/websearch/websearch/native.d.ts.map +1 -1
  183. package/dist/core/extensions/builtin/websearch/websearch/native.js +2 -4
  184. package/dist/core/extensions/builtin/websearch/websearch/native.js.map +1 -1
  185. package/dist/core/extensions/builtin/websearch/websearch/renderers.d.ts.map +1 -1
  186. package/dist/core/extensions/builtin/websearch/websearch/renderers.js +20 -8
  187. package/dist/core/extensions/builtin/websearch/websearch/renderers.js.map +1 -1
  188. package/dist/core/extensions/builtin/websearch/websearch/search.d.ts +6 -1
  189. package/dist/core/extensions/builtin/websearch/websearch/search.d.ts.map +1 -1
  190. package/dist/core/extensions/builtin/websearch/websearch/search.js +15 -11
  191. package/dist/core/extensions/builtin/websearch/websearch/search.js.map +1 -1
  192. package/dist/core/extensions/builtin/websearch/websearch/tool.d.ts.map +1 -1
  193. package/dist/core/extensions/builtin/websearch/websearch/tool.js +5 -10
  194. package/dist/core/extensions/builtin/websearch/websearch/tool.js.map +1 -1
  195. package/dist/core/extensions/builtin/websearch/websearch/types.d.ts +1 -0
  196. package/dist/core/extensions/builtin/websearch/websearch/types.d.ts.map +1 -1
  197. package/dist/core/extensions/builtin/websearch/websearch/types.js.map +1 -1
  198. package/dist/core/extensions/runner.d.ts +14 -7
  199. package/dist/core/extensions/runner.d.ts.map +1 -1
  200. package/dist/core/extensions/runner.js +64 -37
  201. package/dist/core/extensions/runner.js.map +1 -1
  202. package/dist/core/extensions/types.d.ts +32 -3
  203. package/dist/core/extensions/types.d.ts.map +1 -1
  204. package/dist/core/extensions/types.js.map +1 -1
  205. package/dist/core/http-dispatcher.d.ts.map +1 -1
  206. package/dist/core/http-dispatcher.js +16 -0
  207. package/dist/core/http-dispatcher.js.map +1 -1
  208. package/dist/core/messages.d.ts +1 -1
  209. package/dist/core/messages.d.ts.map +1 -1
  210. package/dist/core/messages.js +10 -8
  211. package/dist/core/messages.js.map +1 -1
  212. package/dist/core/model-registry.d.ts +1 -0
  213. package/dist/core/model-registry.d.ts.map +1 -1
  214. package/dist/core/model-registry.js +3 -2
  215. package/dist/core/model-registry.js.map +1 -1
  216. package/dist/core/model-resolver.d.ts +1 -0
  217. package/dist/core/model-resolver.d.ts.map +1 -1
  218. package/dist/core/model-resolver.js +22 -3
  219. package/dist/core/model-resolver.js.map +1 -1
  220. package/dist/core/model-runtime.d.ts +4 -2
  221. package/dist/core/model-runtime.d.ts.map +1 -1
  222. package/dist/core/model-runtime.js +34 -18
  223. package/dist/core/model-runtime.js.map +1 -1
  224. package/dist/core/remote-catalog-provider.d.ts +1 -1
  225. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  226. package/dist/core/remote-catalog-provider.js +39 -7
  227. package/dist/core/remote-catalog-provider.js.map +1 -1
  228. package/dist/core/resolve-config-value.d.ts.map +1 -1
  229. package/dist/core/resolve-config-value.js +12 -8
  230. package/dist/core/resolve-config-value.js.map +1 -1
  231. package/dist/core/resource-loader.d.ts.map +1 -1
  232. package/dist/core/resource-loader.js +3 -0
  233. package/dist/core/resource-loader.js.map +1 -1
  234. package/dist/core/retry-fallback/controller.d.ts +7 -0
  235. package/dist/core/retry-fallback/controller.d.ts.map +1 -1
  236. package/dist/core/retry-fallback/controller.js +13 -0
  237. package/dist/core/retry-fallback/controller.js.map +1 -1
  238. package/dist/core/sdk.d.ts +1 -1
  239. package/dist/core/sdk.d.ts.map +1 -1
  240. package/dist/core/sdk.js +8 -3
  241. package/dist/core/sdk.js.map +1 -1
  242. package/dist/core/session-manager.d.ts +22 -3
  243. package/dist/core/session-manager.d.ts.map +1 -1
  244. package/dist/core/session-manager.js +155 -58
  245. package/dist/core/session-manager.js.map +1 -1
  246. package/dist/core/settings-manager.d.ts +8 -1
  247. package/dist/core/settings-manager.d.ts.map +1 -1
  248. package/dist/core/settings-manager.js +10 -0
  249. package/dist/core/settings-manager.js.map +1 -1
  250. package/dist/core/system-prompt.js +1 -1
  251. package/dist/core/system-prompt.js.map +1 -1
  252. package/dist/core/thinking-levels.d.ts.map +1 -1
  253. package/dist/core/thinking-levels.js +9 -2
  254. package/dist/core/thinking-levels.js.map +1 -1
  255. package/dist/core/tools/bash.d.ts +2 -0
  256. package/dist/core/tools/bash.d.ts.map +1 -1
  257. package/dist/core/tools/bash.js +34 -5
  258. package/dist/core/tools/bash.js.map +1 -1
  259. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  260. package/dist/core/tools/tool-definition-wrapper.js +3 -1
  261. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  262. package/dist/core/usage-totals.d.ts +19 -0
  263. package/dist/core/usage-totals.d.ts.map +1 -0
  264. package/dist/core/usage-totals.js +52 -0
  265. package/dist/core/usage-totals.js.map +1 -0
  266. package/dist/extensions/llama/client.d.ts.map +1 -1
  267. package/dist/extensions/llama/client.js +3 -1
  268. package/dist/extensions/llama/client.js.map +1 -1
  269. package/dist/extensions/llama/index.js +3 -3
  270. package/dist/extensions/llama/index.js.map +1 -1
  271. package/dist/extensions/llama/provider.d.ts.map +1 -1
  272. package/dist/extensions/llama/provider.js +7 -2
  273. package/dist/extensions/llama/provider.js.map +1 -1
  274. package/dist/index.d.ts +2 -2
  275. package/dist/index.d.ts.map +1 -1
  276. package/dist/index.js +1 -1
  277. package/dist/index.js.map +1 -1
  278. package/dist/main.d.ts.map +1 -1
  279. package/dist/main.js +37 -5
  280. package/dist/main.js.map +1 -1
  281. package/dist/modes/app-server/daemon/occupancy.d.ts +22 -0
  282. package/dist/modes/app-server/daemon/occupancy.d.ts.map +1 -0
  283. package/dist/modes/app-server/daemon/occupancy.js +67 -0
  284. package/dist/modes/app-server/daemon/occupancy.js.map +1 -0
  285. package/dist/modes/app-server/daemon/probe.d.ts +3 -3
  286. package/dist/modes/app-server/daemon/probe.d.ts.map +1 -1
  287. package/dist/modes/app-server/daemon/probe.js +33 -10
  288. package/dist/modes/app-server/daemon/probe.js.map +1 -1
  289. package/dist/modes/app-server/daemon.d.ts.map +1 -1
  290. package/dist/modes/app-server/daemon.js +58 -18
  291. package/dist/modes/app-server/daemon.js.map +1 -1
  292. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  293. package/dist/modes/interactive/components/compaction-summary-message.js +8 -1
  294. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  295. package/dist/modes/interactive/components/custom-message.d.ts +3 -1
  296. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
  297. package/dist/modes/interactive/components/custom-message.js +9 -2
  298. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  299. package/dist/modes/interactive/components/extension-editor.d.ts +1 -2
  300. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  301. package/dist/modes/interactive/components/extension-editor.js +16 -46
  302. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  303. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  304. package/dist/modes/interactive/components/footer.js.map +1 -1
  305. package/dist/modes/interactive/components/status-indicator.d.ts +4 -0
  306. package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -1
  307. package/dist/modes/interactive/components/status-indicator.js +13 -0
  308. package/dist/modes/interactive/components/status-indicator.js.map +1 -1
  309. package/dist/modes/interactive/external-editor.d.ts +12 -0
  310. package/dist/modes/interactive/external-editor.d.ts.map +1 -0
  311. package/dist/modes/interactive/external-editor.js +37 -0
  312. package/dist/modes/interactive/external-editor.js.map +1 -0
  313. package/dist/modes/interactive/interactive-mode.d.ts +3 -1
  314. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  315. package/dist/modes/interactive/interactive-mode.js +105 -80
  316. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  317. package/dist/modes/rpc/connection-handler.d.ts +6 -0
  318. package/dist/modes/rpc/connection-handler.d.ts.map +1 -1
  319. package/dist/modes/rpc/connection-handler.js +34 -9
  320. package/dist/modes/rpc/connection-handler.js.map +1 -1
  321. package/dist/modes/rpc/multi-session-host.d.ts +15 -0
  322. package/dist/modes/rpc/multi-session-host.d.ts.map +1 -0
  323. package/dist/modes/rpc/multi-session-host.js +60 -0
  324. package/dist/modes/rpc/multi-session-host.js.map +1 -0
  325. package/dist/modes/rpc/neo-runtime-options.d.ts.map +1 -1
  326. package/dist/modes/rpc/neo-runtime-options.js +1 -0
  327. package/dist/modes/rpc/neo-runtime-options.js.map +1 -1
  328. package/dist/modes/rpc/rpc-client.d.ts +4 -0
  329. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  330. package/dist/modes/rpc/rpc-client.js +7 -0
  331. package/dist/modes/rpc/rpc-client.js.map +1 -1
  332. package/dist/modes/rpc/rpc-mode.d.ts +49 -0
  333. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  334. package/dist/modes/rpc/rpc-mode.js +49 -0
  335. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  336. package/dist/modes/rpc/rpc-types.d.ts +85 -1
  337. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  338. package/dist/modes/rpc/rpc-types.js +8 -1
  339. package/dist/modes/rpc/rpc-types.js.map +1 -1
  340. package/dist/modes/rpc/session-binding.d.ts +13 -0
  341. package/dist/modes/rpc/session-binding.d.ts.map +1 -0
  342. package/dist/modes/rpc/session-binding.js +33 -0
  343. package/dist/modes/rpc/session-binding.js.map +1 -0
  344. package/dist/modes/rpc/session-command-router.d.ts +19 -0
  345. package/dist/modes/rpc/session-command-router.d.ts.map +1 -0
  346. package/dist/modes/rpc/session-command-router.js +159 -0
  347. package/dist/modes/rpc/session-command-router.js.map +1 -0
  348. package/dist/modes/rpc/session-event-writer.d.ts +34 -0
  349. package/dist/modes/rpc/session-event-writer.d.ts.map +1 -0
  350. package/dist/modes/rpc/session-event-writer.js +92 -0
  351. package/dist/modes/rpc/session-event-writer.js.map +1 -0
  352. package/dist/modes/rpc/session-extension-ui-requests.d.ts +15 -0
  353. package/dist/modes/rpc/session-extension-ui-requests.d.ts.map +1 -0
  354. package/dist/modes/rpc/session-extension-ui-requests.js +27 -0
  355. package/dist/modes/rpc/session-extension-ui-requests.js.map +1 -0
  356. package/dist/modes/rpc/session-registry.d.ts +58 -0
  357. package/dist/modes/rpc/session-registry.d.ts.map +1 -0
  358. package/dist/modes/rpc/session-registry.js +162 -0
  359. package/dist/modes/rpc/session-registry.js.map +1 -0
  360. package/dist/package-manager-cli.d.ts.map +1 -1
  361. package/dist/package-manager-cli.js +11 -0
  362. package/dist/package-manager-cli.js.map +1 -1
  363. package/dist/utils/clipboard.d.ts.map +1 -1
  364. package/dist/utils/clipboard.js +19 -8
  365. package/dist/utils/clipboard.js.map +1 -1
  366. package/dist/utils/version-check.d.ts.map +1 -1
  367. package/dist/utils/version-check.js +3 -1
  368. package/dist/utils/version-check.js.map +1 -1
  369. package/docs/app-server-daemon.md +3 -2
  370. package/docs/compaction.md +9 -4
  371. package/docs/custom-provider.md +5 -2
  372. package/docs/environment-variables.md +88 -0
  373. package/docs/extensions.md +27 -9
  374. package/docs/index.md +1 -0
  375. package/docs/json.md +5 -1
  376. package/docs/models.md +6 -2
  377. package/docs/providers.md +12 -1
  378. package/docs/rpc.md +154 -8
  379. package/docs/sdk.md +3 -0
  380. package/docs/session-format.md +17 -3
  381. package/docs/settings.md +13 -2
  382. package/docs/usage.md +1 -14
  383. package/examples/extensions/custom-compaction.ts +4 -0
  384. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  385. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  386. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  387. package/examples/extensions/gondolin/package-lock.json +2 -2
  388. package/examples/extensions/gondolin/package.json +1 -1
  389. package/examples/extensions/handoff.ts +9 -1
  390. package/examples/extensions/message-renderer.ts +2 -2
  391. package/examples/extensions/sandbox/package-lock.json +2 -2
  392. package/examples/extensions/sandbox/package.json +1 -1
  393. package/examples/extensions/summarize.ts +3 -0
  394. package/examples/extensions/with-deps/package-lock.json +2 -2
  395. package/examples/extensions/with-deps/package.json +1 -1
  396. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +36 -0
  397. package/node_modules/@code-yeongyu/senpi-codemode/package.json +3 -3
  398. package/node_modules/@earendil-works/pi-agent-core/README.md +31 -12
  399. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts +4 -4
  400. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  401. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +8 -7
  402. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
  403. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts +13 -3
  404. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts.map +1 -1
  405. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js +63 -27
  406. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js.map +1 -1
  407. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.d.ts +11 -12
  408. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.d.ts.map +1 -1
  409. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.js +47 -22
  410. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.js.map +1 -1
  411. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.d.ts +5 -1
  412. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.d.ts.map +1 -1
  413. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.js +6 -8
  414. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.js.map +1 -1
  415. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.d.ts +17 -5
  416. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.d.ts.map +1 -1
  417. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +82 -27
  418. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js.map +1 -1
  419. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/utils.d.ts +12 -1
  420. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/utils.d.ts.map +1 -1
  421. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/utils.js +22 -17
  422. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/utils.js.map +1 -1
  423. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.d.ts +3 -9
  424. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.d.ts.map +1 -1
  425. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.js +118 -11
  426. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.js.map +1 -1
  427. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-storage.d.ts +11 -3
  428. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-storage.d.ts.map +1 -1
  429. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-storage.js +56 -4
  430. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-storage.js.map +1 -1
  431. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/memory-storage.d.ts +11 -3
  432. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/memory-storage.d.ts.map +1 -1
  433. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/memory-storage.js +56 -4
  434. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/memory-storage.js.map +1 -1
  435. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo-utils.d.ts.map +1 -1
  436. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo-utils.js +2 -2
  437. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo-utils.js.map +1 -1
  438. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.d.ts +6 -4
  439. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.d.ts.map +1 -1
  440. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.js +30 -14
  441. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.js.map +1 -1
  442. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/bash.d.ts +27 -0
  443. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/bash.d.ts.map +1 -0
  444. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/bash.js +132 -0
  445. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/bash.js.map +1 -0
  446. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit-diff.d.ts +89 -0
  447. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit-diff.d.ts.map +1 -0
  448. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit-diff.js +386 -0
  449. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit-diff.js.map +1 -0
  450. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit.d.ts +19 -0
  451. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit.d.ts.map +1 -0
  452. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit.js +95 -0
  453. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit.js.map +1 -0
  454. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/file-mutation-queue.d.ts +4 -0
  455. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/file-mutation-queue.d.ts.map +1 -0
  456. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/file-mutation-queue.js +46 -0
  457. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/file-mutation-queue.js.map +1 -0
  458. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/image.d.ts +3 -0
  459. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/image.d.ts.map +1 -0
  460. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/image.js +106 -0
  461. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/image.js.map +1 -0
  462. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/index.d.ts +6 -0
  463. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/index.d.ts.map +1 -0
  464. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/index.js +5 -0
  465. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/index.js.map +1 -0
  466. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/path-utils.d.ts +4 -0
  467. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/path-utils.d.ts.map +1 -0
  468. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/path-utils.js +26 -0
  469. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/path-utils.js.map +1 -0
  470. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/read.d.ts +34 -0
  471. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/read.d.ts.map +1 -0
  472. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/read.js +110 -0
  473. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/read.js.map +1 -0
  474. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/tool-context.d.ts +6 -0
  475. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/tool-context.d.ts.map +1 -0
  476. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/tool-context.js +2 -0
  477. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/tool-context.js.map +1 -0
  478. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/write.d.ts +11 -0
  479. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/write.d.ts.map +1 -0
  480. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/write.js +31 -0
  481. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/write.js.map +1 -0
  482. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts +80 -18
  483. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts.map +1 -1
  484. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.js.map +1 -1
  485. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.d.ts +12 -4
  486. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.d.ts.map +1 -1
  487. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.js +90 -35
  488. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.js.map +1 -1
  489. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/truncate.d.ts.map +1 -1
  490. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/truncate.js +10 -4
  491. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/truncate.js.map +1 -1
  492. package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts +4 -2
  493. package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts.map +1 -1
  494. package/node_modules/@earendil-works/pi-agent-core/dist/index.js +5 -2
  495. package/node_modules/@earendil-works/pi-agent-core/dist/index.js.map +1 -1
  496. package/node_modules/@earendil-works/pi-agent-core/dist/stream-fn.d.ts +10 -0
  497. package/node_modules/@earendil-works/pi-agent-core/dist/stream-fn.d.ts.map +1 -0
  498. package/node_modules/@earendil-works/pi-agent-core/dist/stream-fn.js +17 -0
  499. package/node_modules/@earendil-works/pi-agent-core/dist/stream-fn.js.map +1 -0
  500. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts +8 -2
  501. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts.map +1 -1
  502. package/node_modules/@earendil-works/pi-agent-core/dist/types.js.map +1 -1
  503. package/node_modules/@earendil-works/pi-agent-core/package.json +4 -3
  504. package/node_modules/@earendil-works/pi-ai/README.md +44 -4
  505. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts.map +1 -1
  506. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +130 -47
  507. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js.map +1 -1
  508. package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.d.ts.map +1 -1
  509. package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.js +36 -15
  510. package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.js.map +1 -1
  511. package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.d.ts.map +1 -1
  512. package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.js +55 -26
  513. package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.js.map +1 -1
  514. package/node_modules/@earendil-works/pi-ai/dist/api/constrained-sampling.d.ts +17 -0
  515. package/node_modules/@earendil-works/pi-ai/dist/api/constrained-sampling.d.ts.map +1 -0
  516. package/node_modules/@earendil-works/pi-ai/dist/api/constrained-sampling.js +104 -0
  517. package/node_modules/@earendil-works/pi-ai/dist/api/constrained-sampling.js.map +1 -0
  518. package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.d.ts.map +1 -1
  519. package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.js +16 -13
  520. package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.js.map +1 -1
  521. package/node_modules/@earendil-works/pi-ai/dist/api/google-shared.d.ts +4 -3
  522. package/node_modules/@earendil-works/pi-ai/dist/api/google-shared.d.ts.map +1 -1
  523. package/node_modules/@earendil-works/pi-ai/dist/api/google-shared.js +17 -3
  524. package/node_modules/@earendil-works/pi-ai/dist/api/google-shared.js.map +1 -1
  525. package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.d.ts.map +1 -1
  526. package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.js +19 -13
  527. package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.js.map +1 -1
  528. package/node_modules/@earendil-works/pi-ai/dist/api/mistral-conversations.d.ts.map +1 -1
  529. package/node_modules/@earendil-works/pi-ai/dist/api/mistral-conversations.js +13 -9
  530. package/node_modules/@earendil-works/pi-ai/dist/api/mistral-conversations.js.map +1 -1
  531. package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.d.ts.map +1 -1
  532. package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js +92 -58
  533. package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js.map +1 -1
  534. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.d.ts +7 -9
  535. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.d.ts.map +1 -1
  536. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +263 -56
  537. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js.map +1 -1
  538. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.d.ts +7 -0
  539. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.d.ts.map +1 -1
  540. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js +180 -80
  541. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js.map +1 -1
  542. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.d.ts.map +1 -1
  543. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js +48 -21
  544. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js.map +1 -1
  545. package/node_modules/@earendil-works/pi-ai/dist/api/openrouter-images.d.ts.map +1 -1
  546. package/node_modules/@earendil-works/pi-ai/dist/api/openrouter-images.js +8 -3
  547. package/node_modules/@earendil-works/pi-ai/dist/api/openrouter-images.js.map +1 -1
  548. package/node_modules/@earendil-works/pi-ai/dist/api/pi-messages.d.ts.map +1 -1
  549. package/node_modules/@earendil-works/pi-ai/dist/api/pi-messages.js +2 -4
  550. package/node_modules/@earendil-works/pi-ai/dist/api/pi-messages.js.map +1 -1
  551. package/node_modules/@earendil-works/pi-ai/dist/api/simple-options.d.ts.map +1 -1
  552. package/node_modules/@earendil-works/pi-ai/dist/api/simple-options.js +1 -0
  553. package/node_modules/@earendil-works/pi-ai/dist/api/simple-options.js.map +1 -1
  554. package/node_modules/@earendil-works/pi-ai/dist/api-registry.d.ts +15 -0
  555. package/node_modules/@earendil-works/pi-ai/dist/api-registry.d.ts.map +1 -1
  556. package/node_modules/@earendil-works/pi-ai/dist/api-registry.js +52 -6
  557. package/node_modules/@earendil-works/pi-ai/dist/api-registry.js.map +1 -1
  558. package/node_modules/@earendil-works/pi-ai/dist/auth/helpers.d.ts.map +1 -1
  559. package/node_modules/@earendil-works/pi-ai/dist/auth/helpers.js +3 -2
  560. package/node_modules/@earendil-works/pi-ai/dist/auth/helpers.js.map +1 -1
  561. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/kimi-coding.d.ts +10 -0
  562. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/kimi-coding.d.ts.map +1 -0
  563. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/kimi-coding.js +249 -0
  564. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/kimi-coding.js.map +1 -0
  565. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/load.d.ts +4 -0
  566. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/load.d.ts.map +1 -1
  567. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/load.js +10 -0
  568. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/load.js.map +1 -1
  569. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/openai-codex.d.ts.map +1 -1
  570. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/openai-codex.js +2 -18
  571. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/openai-codex.js.map +1 -1
  572. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/openrouter.d.ts +13 -0
  573. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/openrouter.d.ts.map +1 -0
  574. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/openrouter.js +222 -0
  575. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/openrouter.js.map +1 -0
  576. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/radius.d.ts +3 -2
  577. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/radius.d.ts.map +1 -1
  578. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/radius.js +37 -35
  579. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/radius.js.map +1 -1
  580. package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.d.ts.map +1 -1
  581. package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.js +11 -1
  582. package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.js.map +1 -1
  583. package/node_modules/@earendil-works/pi-ai/dist/bun-oauth.d.ts.map +1 -1
  584. package/node_modules/@earendil-works/pi-ai/dist/bun-oauth.js +4 -0
  585. package/node_modules/@earendil-works/pi-ai/dist/bun-oauth.js.map +1 -1
  586. package/node_modules/@earendil-works/pi-ai/dist/compat.d.ts.map +1 -1
  587. package/node_modules/@earendil-works/pi-ai/dist/compat.js +2 -1
  588. package/node_modules/@earendil-works/pi-ai/dist/compat.js.map +1 -1
  589. package/node_modules/@earendil-works/pi-ai/dist/context-provenance.d.ts +16 -0
  590. package/node_modules/@earendil-works/pi-ai/dist/context-provenance.d.ts.map +1 -0
  591. package/node_modules/@earendil-works/pi-ai/dist/context-provenance.js +32 -0
  592. package/node_modules/@earendil-works/pi-ai/dist/context-provenance.js.map +1 -0
  593. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.d.ts +3 -0
  594. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.d.ts.map +1 -1
  595. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js +11 -3
  596. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js.map +1 -1
  597. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts +15 -0
  598. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts.map +1 -1
  599. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js +15 -0
  600. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js.map +1 -1
  601. package/node_modules/@earendil-works/pi-ai/dist/images-api-registry.d.ts +19 -2
  602. package/node_modules/@earendil-works/pi-ai/dist/images-api-registry.d.ts.map +1 -1
  603. package/node_modules/@earendil-works/pi-ai/dist/images-api-registry.js +68 -3
  604. package/node_modules/@earendil-works/pi-ai/dist/images-api-registry.js.map +1 -1
  605. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +6 -0
  606. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
  607. package/node_modules/@earendil-works/pi-ai/dist/index.js +6 -0
  608. package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
  609. package/node_modules/@earendil-works/pi-ai/dist/model-catalog.d.ts +17 -0
  610. package/node_modules/@earendil-works/pi-ai/dist/model-catalog.d.ts.map +1 -0
  611. package/node_modules/@earendil-works/pi-ai/dist/model-catalog.js +4 -0
  612. package/node_modules/@earendil-works/pi-ai/dist/model-catalog.js.map +1 -0
  613. package/node_modules/@earendil-works/pi-ai/dist/model.d.ts +2 -2
  614. package/node_modules/@earendil-works/pi-ai/dist/model.d.ts.map +1 -1
  615. package/node_modules/@earendil-works/pi-ai/dist/model.js.map +1 -1
  616. package/node_modules/@earendil-works/pi-ai/dist/models-store.d.ts +7 -0
  617. package/node_modules/@earendil-works/pi-ai/dist/models-store.d.ts.map +1 -1
  618. package/node_modules/@earendil-works/pi-ai/dist/models-store.js.map +1 -1
  619. package/node_modules/@earendil-works/pi-ai/dist/models.d.ts +0 -6
  620. package/node_modules/@earendil-works/pi-ai/dist/models.d.ts.map +1 -1
  621. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +76 -4448
  622. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
  623. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +4 -0
  624. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js.map +1 -1
  625. package/node_modules/@earendil-works/pi-ai/dist/models.js +5 -54
  626. package/node_modules/@earendil-works/pi-ai/dist/models.js.map +1 -1
  627. package/node_modules/@earendil-works/pi-ai/dist/node/provider-scope.d.ts +12 -0
  628. package/node_modules/@earendil-works/pi-ai/dist/node/provider-scope.d.ts.map +1 -0
  629. package/node_modules/@earendil-works/pi-ai/dist/node/provider-scope.js +34 -0
  630. package/node_modules/@earendil-works/pi-ai/dist/node/provider-scope.js.map +1 -0
  631. package/node_modules/@earendil-works/pi-ai/dist/providers/alibaba-token-plan.models.d.ts +3 -63
  632. package/node_modules/@earendil-works/pi-ai/dist/providers/alibaba-token-plan.models.d.ts.map +1 -1
  633. package/node_modules/@earendil-works/pi-ai/dist/providers/alibaba-token-plan.models.js +2 -1
  634. package/node_modules/@earendil-works/pi-ai/dist/providers/alibaba-token-plan.models.js.map +1 -1
  635. package/node_modules/@earendil-works/pi-ai/dist/providers/all.d.ts +2 -0
  636. package/node_modules/@earendil-works/pi-ai/dist/providers/all.d.ts.map +1 -1
  637. package/node_modules/@earendil-works/pi-ai/dist/providers/all.js +10 -0
  638. package/node_modules/@earendil-works/pi-ai/dist/providers/all.js.map +1 -1
  639. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.d.ts.map +1 -1
  640. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js +3 -2
  641. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
  642. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.d.ts +3 -439
  643. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.d.ts.map +1 -1
  644. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.js +2 -1
  645. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.js.map +1 -1
  646. package/node_modules/@earendil-works/pi-ai/dist/providers/ant-ling.models.d.ts +3 -15
  647. package/node_modules/@earendil-works/pi-ai/dist/providers/ant-ling.models.d.ts.map +1 -1
  648. package/node_modules/@earendil-works/pi-ai/dist/providers/ant-ling.models.js +2 -1
  649. package/node_modules/@earendil-works/pi-ai/dist/providers/ant-ling.models.js.map +1 -1
  650. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  651. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js +30 -3
  652. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js.map +1 -1
  653. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.d.ts +3 -59
  654. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.d.ts.map +1 -1
  655. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.js +2 -1
  656. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.js.map +1 -1
  657. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.models.d.ts +3 -187
  658. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.models.d.ts.map +1 -1
  659. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.models.js +2 -1
  660. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.models.js.map +1 -1
  661. package/node_modules/@earendil-works/pi-ai/dist/providers/cerebras.models.d.ts +3 -15
  662. package/node_modules/@earendil-works/pi-ai/dist/providers/cerebras.models.d.ts.map +1 -1
  663. package/node_modules/@earendil-works/pi-ai/dist/providers/cerebras.models.js +2 -1
  664. package/node_modules/@earendil-works/pi-ai/dist/providers/cerebras.models.js.map +1 -1
  665. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.models.d.ts +3 -171
  666. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.models.d.ts.map +1 -1
  667. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.models.js +2 -1
  668. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.models.js.map +1 -1
  669. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-workers-ai.models.d.ts +3 -55
  670. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-workers-ai.models.d.ts.map +1 -1
  671. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-workers-ai.models.js +2 -1
  672. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-workers-ai.models.js.map +1 -1
  673. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -0
  674. package/node_modules/@earendil-works/pi-ai/dist/providers/data/alibaba-token-plan.json +1 -1
  675. package/node_modules/@earendil-works/pi-ai/dist/providers/data/amazon-bedrock.json +1 -1
  676. package/node_modules/@earendil-works/pi-ai/dist/providers/data/ant-ling.json +1 -1
  677. package/node_modules/@earendil-works/pi-ai/dist/providers/data/anthropic.json +1 -1
  678. package/node_modules/@earendil-works/pi-ai/dist/providers/data/azure-openai-responses.json +1 -1
  679. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cerebras.json +1 -1
  680. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  681. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-workers-ai.json +1 -1
  682. package/node_modules/@earendil-works/pi-ai/dist/providers/data/deepseek.json +1 -1
  683. package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
  684. package/node_modules/@earendil-works/pi-ai/dist/providers/data/github-copilot.json +1 -1
  685. package/node_modules/@earendil-works/pi-ai/dist/providers/data/google-vertex.json +1 -1
  686. package/node_modules/@earendil-works/pi-ai/dist/providers/data/google.json +1 -1
  687. package/node_modules/@earendil-works/pi-ai/dist/providers/data/groq.json +1 -1
  688. package/node_modules/@earendil-works/pi-ai/dist/providers/data/huggingface.json +1 -1
  689. package/node_modules/@earendil-works/pi-ai/dist/providers/data/kimi-coding.json +1 -1
  690. package/node_modules/@earendil-works/pi-ai/dist/providers/data/minimax-cn.json +1 -1
  691. package/node_modules/@earendil-works/pi-ai/dist/providers/data/minimax.json +1 -1
  692. package/node_modules/@earendil-works/pi-ai/dist/providers/data/mistral.json +1 -1
  693. package/node_modules/@earendil-works/pi-ai/dist/providers/data/moonshotai-cn.json +1 -1
  694. package/node_modules/@earendil-works/pi-ai/dist/providers/data/moonshotai.json +1 -1
  695. package/node_modules/@earendil-works/pi-ai/dist/providers/data/nvidia.json +1 -1
  696. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openai-codex.json +1 -1
  697. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openai.json +1 -1
  698. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
  699. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  700. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  701. package/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan-cn.json +1 -0
  702. package/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan.json +1 -0
  703. package/node_modules/@earendil-works/pi-ai/dist/providers/data/together.json +1 -1
  704. package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  705. package/node_modules/@earendil-works/pi-ai/dist/providers/data/xai.json +1 -1
  706. package/node_modules/@earendil-works/pi-ai/dist/providers/data/xiaomi-token-plan-ams.json +1 -1
  707. package/node_modules/@earendil-works/pi-ai/dist/providers/data/xiaomi-token-plan-cn.json +1 -1
  708. package/node_modules/@earendil-works/pi-ai/dist/providers/data/xiaomi-token-plan-sgp.json +1 -1
  709. package/node_modules/@earendil-works/pi-ai/dist/providers/data/xiaomi.json +1 -1
  710. package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai-coding-cn.json +1 -1
  711. package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai.json +1 -1
  712. package/node_modules/@earendil-works/pi-ai/dist/providers/deepseek.models.d.ts +3 -11
  713. package/node_modules/@earendil-works/pi-ai/dist/providers/deepseek.models.d.ts.map +1 -1
  714. package/node_modules/@earendil-works/pi-ai/dist/providers/deepseek.models.js +2 -1
  715. package/node_modules/@earendil-works/pi-ai/dist/providers/deepseek.models.js.map +1 -1
  716. package/node_modules/@earendil-works/pi-ai/dist/providers/fireworks.models.d.ts +3 -67
  717. package/node_modules/@earendil-works/pi-ai/dist/providers/fireworks.models.d.ts.map +1 -1
  718. package/node_modules/@earendil-works/pi-ai/dist/providers/fireworks.models.js +2 -1
  719. package/node_modules/@earendil-works/pi-ai/dist/providers/fireworks.models.js.map +1 -1
  720. package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.d.ts +3 -115
  721. package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.d.ts.map +1 -1
  722. package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.js +2 -1
  723. package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.js.map +1 -1
  724. package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.models.d.ts +3 -51
  725. package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.models.d.ts.map +1 -1
  726. package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.models.js +2 -1
  727. package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.models.js.map +1 -1
  728. package/node_modules/@earendil-works/pi-ai/dist/providers/google.models.d.ts +3 -75
  729. package/node_modules/@earendil-works/pi-ai/dist/providers/google.models.d.ts.map +1 -1
  730. package/node_modules/@earendil-works/pi-ai/dist/providers/google.models.js +2 -1
  731. package/node_modules/@earendil-works/pi-ai/dist/providers/google.models.js.map +1 -1
  732. package/node_modules/@earendil-works/pi-ai/dist/providers/groq.models.d.ts +3 -31
  733. package/node_modules/@earendil-works/pi-ai/dist/providers/groq.models.d.ts.map +1 -1
  734. package/node_modules/@earendil-works/pi-ai/dist/providers/groq.models.js +2 -1
  735. package/node_modules/@earendil-works/pi-ai/dist/providers/groq.models.js.map +1 -1
  736. package/node_modules/@earendil-works/pi-ai/dist/providers/huggingface.models.d.ts +3 -203
  737. package/node_modules/@earendil-works/pi-ai/dist/providers/huggingface.models.d.ts.map +1 -1
  738. package/node_modules/@earendil-works/pi-ai/dist/providers/huggingface.models.js +2 -1
  739. package/node_modules/@earendil-works/pi-ai/dist/providers/huggingface.models.js.map +1 -1
  740. package/node_modules/@earendil-works/pi-ai/dist/providers/images/register-builtins.js +2 -2
  741. package/node_modules/@earendil-works/pi-ai/dist/providers/images/register-builtins.js.map +1 -1
  742. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.d.ts.map +1 -1
  743. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.js +10 -2
  744. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.js.map +1 -1
  745. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.models.d.ts +3 -19
  746. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.models.d.ts.map +1 -1
  747. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.models.js +2 -1
  748. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.models.js.map +1 -1
  749. package/node_modules/@earendil-works/pi-ai/dist/providers/minimax-cn.models.d.ts +3 -15
  750. package/node_modules/@earendil-works/pi-ai/dist/providers/minimax-cn.models.d.ts.map +1 -1
  751. package/node_modules/@earendil-works/pi-ai/dist/providers/minimax-cn.models.js +2 -1
  752. package/node_modules/@earendil-works/pi-ai/dist/providers/minimax-cn.models.js.map +1 -1
  753. package/node_modules/@earendil-works/pi-ai/dist/providers/minimax.models.d.ts +3 -15
  754. package/node_modules/@earendil-works/pi-ai/dist/providers/minimax.models.d.ts.map +1 -1
  755. package/node_modules/@earendil-works/pi-ai/dist/providers/minimax.models.js +2 -1
  756. package/node_modules/@earendil-works/pi-ai/dist/providers/minimax.models.js.map +1 -1
  757. package/node_modules/@earendil-works/pi-ai/dist/providers/mistral.models.d.ts +3 -123
  758. package/node_modules/@earendil-works/pi-ai/dist/providers/mistral.models.d.ts.map +1 -1
  759. package/node_modules/@earendil-works/pi-ai/dist/providers/mistral.models.js +2 -1
  760. package/node_modules/@earendil-works/pi-ai/dist/providers/mistral.models.js.map +1 -1
  761. package/node_modules/@earendil-works/pi-ai/dist/providers/moonshotai-cn.models.d.ts +3 -43
  762. package/node_modules/@earendil-works/pi-ai/dist/providers/moonshotai-cn.models.d.ts.map +1 -1
  763. package/node_modules/@earendil-works/pi-ai/dist/providers/moonshotai-cn.models.js +2 -1
  764. package/node_modules/@earendil-works/pi-ai/dist/providers/moonshotai-cn.models.js.map +1 -1
  765. package/node_modules/@earendil-works/pi-ai/dist/providers/moonshotai.models.d.ts +3 -43
  766. package/node_modules/@earendil-works/pi-ai/dist/providers/moonshotai.models.d.ts.map +1 -1
  767. package/node_modules/@earendil-works/pi-ai/dist/providers/moonshotai.models.js +2 -1
  768. package/node_modules/@earendil-works/pi-ai/dist/providers/moonshotai.models.js.map +1 -1
  769. package/node_modules/@earendil-works/pi-ai/dist/providers/nvidia.models.d.ts +3 -79
  770. package/node_modules/@earendil-works/pi-ai/dist/providers/nvidia.models.d.ts.map +1 -1
  771. package/node_modules/@earendil-works/pi-ai/dist/providers/nvidia.models.js +2 -1
  772. package/node_modules/@earendil-works/pi-ai/dist/providers/nvidia.models.js.map +1 -1
  773. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.models.d.ts +3 -31
  774. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.models.d.ts.map +1 -1
  775. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.models.js +2 -1
  776. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.models.js.map +1 -1
  777. package/node_modules/@earendil-works/pi-ai/dist/providers/openai.models.d.ts +3 -187
  778. package/node_modules/@earendil-works/pi-ai/dist/providers/openai.models.d.ts.map +1 -1
  779. package/node_modules/@earendil-works/pi-ai/dist/providers/openai.models.js +2 -1
  780. package/node_modules/@earendil-works/pi-ai/dist/providers/openai.models.js.map +1 -1
  781. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.models.d.ts +3 -67
  782. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.models.d.ts.map +1 -1
  783. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.models.js +2 -1
  784. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.models.js.map +1 -1
  785. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.d.ts +3 -227
  786. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.d.ts.map +1 -1
  787. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.js +2 -1
  788. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.js.map +1 -1
  789. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter-images.d.ts.map +1 -1
  790. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter-images.js +10 -2
  791. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter-images.js.map +1 -1
  792. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.d.ts.map +1 -1
  793. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.js +10 -2
  794. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.js.map +1 -1
  795. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts +3 -1095
  796. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts.map +1 -1
  797. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js +2 -1
  798. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js.map +1 -1
  799. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-cn.d.ts +3 -0
  800. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-cn.d.ts.map +1 -0
  801. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-cn.js +15 -0
  802. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-cn.js.map +1 -0
  803. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-cn.models.d.ts +4 -0
  804. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-cn.models.d.ts.map +1 -0
  805. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-cn.models.js +6 -0
  806. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-cn.models.js.map +1 -0
  807. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan.d.ts +3 -0
  808. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan.d.ts.map +1 -0
  809. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan.js +15 -0
  810. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan.js.map +1 -0
  811. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan.models.d.ts +4 -0
  812. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan.models.d.ts.map +1 -0
  813. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan.models.js +6 -0
  814. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan.models.js.map +1 -0
  815. package/node_modules/@earendil-works/pi-ai/dist/providers/radius-config.d.ts.map +1 -1
  816. package/node_modules/@earendil-works/pi-ai/dist/providers/radius-config.js +18 -14
  817. package/node_modules/@earendil-works/pi-ai/dist/providers/radius-config.js.map +1 -1
  818. package/node_modules/@earendil-works/pi-ai/dist/providers/together.models.d.ts +3 -67
  819. package/node_modules/@earendil-works/pi-ai/dist/providers/together.models.d.ts.map +1 -1
  820. package/node_modules/@earendil-works/pi-ai/dist/providers/together.models.js +2 -1
  821. package/node_modules/@earendil-works/pi-ai/dist/providers/together.models.js.map +1 -1
  822. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts +3 -775
  823. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts.map +1 -1
  824. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js +2 -1
  825. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js.map +1 -1
  826. package/node_modules/@earendil-works/pi-ai/dist/providers/xai.models.d.ts +3 -15
  827. package/node_modules/@earendil-works/pi-ai/dist/providers/xai.models.d.ts.map +1 -1
  828. package/node_modules/@earendil-works/pi-ai/dist/providers/xai.models.js +2 -1
  829. package/node_modules/@earendil-works/pi-ai/dist/providers/xai.models.js.map +1 -1
  830. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.d.ts +3 -15
  831. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.d.ts.map +1 -1
  832. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.js +2 -1
  833. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.js.map +1 -1
  834. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.d.ts +3 -15
  835. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.d.ts.map +1 -1
  836. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.js +2 -1
  837. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.js.map +1 -1
  838. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.d.ts +3 -15
  839. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.d.ts.map +1 -1
  840. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.js +2 -1
  841. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.js.map +1 -1
  842. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi.models.d.ts +3 -27
  843. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi.models.d.ts.map +1 -1
  844. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi.models.js +2 -1
  845. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi.models.js.map +1 -1
  846. package/node_modules/@earendil-works/pi-ai/dist/providers/zai-coding-cn.models.d.ts +3 -27
  847. package/node_modules/@earendil-works/pi-ai/dist/providers/zai-coding-cn.models.d.ts.map +1 -1
  848. package/node_modules/@earendil-works/pi-ai/dist/providers/zai-coding-cn.models.js +2 -1
  849. package/node_modules/@earendil-works/pi-ai/dist/providers/zai-coding-cn.models.js.map +1 -1
  850. package/node_modules/@earendil-works/pi-ai/dist/providers/zai.models.d.ts +3 -27
  851. package/node_modules/@earendil-works/pi-ai/dist/providers/zai.models.d.ts.map +1 -1
  852. package/node_modules/@earendil-works/pi-ai/dist/providers/zai.models.js +2 -1
  853. package/node_modules/@earendil-works/pi-ai/dist/providers/zai.models.js.map +1 -1
  854. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts +52 -3
  855. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts.map +1 -1
  856. package/node_modules/@earendil-works/pi-ai/dist/types.js.map +1 -1
  857. package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.d.ts.map +1 -1
  858. package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.js +8 -3
  859. package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.js.map +1 -1
  860. package/node_modules/@earendil-works/pi-ai/dist/utils/openai-codex-auth.d.ts +11 -0
  861. package/node_modules/@earendil-works/pi-ai/dist/utils/openai-codex-auth.d.ts.map +1 -0
  862. package/node_modules/@earendil-works/pi-ai/dist/utils/openai-codex-auth.js +30 -0
  863. package/node_modules/@earendil-works/pi-ai/dist/utils/openai-codex-auth.js.map +1 -0
  864. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts +1 -0
  865. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts.map +1 -1
  866. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js +3 -0
  867. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js.map +1 -1
  868. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-retry.d.ts +16 -0
  869. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-retry.d.ts.map +1 -0
  870. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-retry.js +94 -0
  871. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-retry.js.map +1 -0
  872. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts +41 -0
  873. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts.map +1 -1
  874. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +82 -0
  875. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js.map +1 -1
  876. package/node_modules/@earendil-works/pi-ai/dist/utils/server-fallback-receipt.d.ts +30 -0
  877. package/node_modules/@earendil-works/pi-ai/dist/utils/server-fallback-receipt.d.ts.map +1 -0
  878. package/node_modules/@earendil-works/pi-ai/dist/utils/server-fallback-receipt.js +70 -0
  879. package/node_modules/@earendil-works/pi-ai/dist/utils/server-fallback-receipt.js.map +1 -0
  880. package/node_modules/@earendil-works/pi-ai/dist/utils/text.d.ts +6 -0
  881. package/node_modules/@earendil-works/pi-ai/dist/utils/text.d.ts.map +1 -0
  882. package/node_modules/@earendil-works/pi-ai/dist/utils/text.js +10 -0
  883. package/node_modules/@earendil-works/pi-ai/dist/utils/text.js.map +1 -0
  884. package/node_modules/@earendil-works/pi-ai/dist/utils/uuid.d.ts +3 -0
  885. package/node_modules/@earendil-works/pi-ai/dist/utils/uuid.d.ts.map +1 -0
  886. package/node_modules/@earendil-works/{pi-agent-core/dist/harness/session → pi-ai/dist/utils}/uuid.js +2 -2
  887. package/node_modules/@earendil-works/pi-ai/dist/utils/uuid.js.map +1 -0
  888. package/node_modules/@earendil-works/pi-ai/package.json +11 -4
  889. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  890. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +1 -1
  891. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +31 -22
  892. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +1 -1
  893. package/node_modules/@earendil-works/pi-tui/dist/tui.d.ts.map +1 -1
  894. package/node_modules/@earendil-works/pi-tui/dist/tui.js +2 -0
  895. package/node_modules/@earendil-works/pi-tui/dist/tui.js.map +1 -1
  896. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  897. package/package.json +7 -10
  898. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/uuid.d.ts +0 -2
  899. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/uuid.d.ts.map +0 -1
  900. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/uuid.js.map +0 -1
@@ -16,6 +16,7 @@ import { randomUUID } from "node:crypto";
16
16
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
17
17
  import { basename, dirname } from "node:path";
18
18
  import { prepareAgentToolCall } from "@earendil-works/pi-agent-core";
19
+ import { contentText, SERVER_FALLBACK_ABORTED_DIAGNOSTIC } from "@earendil-works/pi-ai";
19
20
  import { cleanupSessionResources, isClassifierRefusal, isContextOverflow, isRetryableAssistantError, modelsAreEqual, resetApiProviders, streamSimple, } from "@earendil-works/pi-ai/compat";
20
21
  import { getAgentDir } from "../config.js";
21
22
  import { getThemeByName, theme } from "../modes/interactive/theme/theme.js";
@@ -25,6 +26,7 @@ import { sleep } from "../utils/sleep.js";
25
26
  import { formatNoApiKeyFoundMessage, formatNoModelSelectedMessage } from "./auth-guidance.js";
26
27
  import { executeBashWithOperations } from "./bash-executor.js";
27
28
  import { calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, estimateTokens, generateBranchSummary, prepareCompaction, shouldCompact, } from "./compaction/index.js";
29
+ import { CompactionLifecycleCoordinator } from "./compaction/lifecycle.js";
28
30
  import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
29
31
  import { buildDynamicSystemPrompt } from "./dynamic-prompt/index.js";
30
32
  import { exportSessionToHtml } from "./export-html/index.js";
@@ -47,6 +49,7 @@ import { getSupportedThinkingLevels, supportsMax, supportsXhigh } from "./thinki
47
49
  import { createLocalBashOperations } from "./tools/bash.js";
48
50
  import { createAllToolDefinitions } from "./tools/index.js";
49
51
  import { createToolDefinitionFromAgentTool } from "./tools/tool-definition-wrapper.js";
52
+ import { addUsageToTotals, createUsageTotals } from "./usage-totals.js";
50
53
  /**
51
54
  * Parse a skill block from message text.
52
55
  * Returns null if the text doesn't contain a skill block.
@@ -70,6 +73,15 @@ function withoutDeletedHeaders(headers) {
70
73
  ? Object.fromEntries(Object.entries(headers).filter((entry) => entry[1] !== null))
71
74
  : undefined;
72
75
  }
76
+ function isCompactionOwnedPreCompactDiagnostic(message, requestId) {
77
+ if (message.role !== "custom" || message.customType !== "senpi.hook")
78
+ return false;
79
+ const details = message.details;
80
+ if (!details || typeof details !== "object")
81
+ return false;
82
+ const diagnostic = details;
83
+ return diagnostic.event === "PreCompact" && diagnostic.compactionRequestId === requestId;
84
+ }
73
85
  /**
74
86
  * Human-readable rejection message paired with a `CompactionRejectionCause`.
75
87
  *
@@ -86,6 +98,8 @@ function describeCompactionRejection(cause) {
86
98
  return "Compaction rejected: the compaction circuit breaker is open after repeated failures. Wait for the cooldown and retry.";
87
99
  case "per-turn-cap":
88
100
  return "Compaction rejected: per-turn compaction cap reached for this turn.";
101
+ case "stale-revision":
102
+ return "Compaction rejected: the session changed while the summary was being prepared. Retry compaction against the latest context.";
89
103
  }
90
104
  }
91
105
  class CompactionRejectedError extends Error {
@@ -97,6 +111,33 @@ class CompactionRejectedError extends Error {
97
111
  this.rejectionCause = rejectionCause;
98
112
  }
99
113
  }
114
+ class CompactionCancelledError extends Error {
115
+ constructor() {
116
+ super("Compaction cancelled");
117
+ this.name = "CompactionCancelledError";
118
+ }
119
+ }
120
+ /**
121
+ * An execution failure annotated with whether this operation still owns its
122
+ * terminal transition. Callers must not publish a terminal event for an
123
+ * operation that a newer compaction generation has superseded.
124
+ */
125
+ class CompactionExecutionError extends Error {
126
+ constructor(error, ownsTerminalTransition, aborted) {
127
+ super(error instanceof Error ? error.message : String(error));
128
+ this.name = "CompactionExecutionError";
129
+ this.ownsTerminalTransition = ownsTerminalTransition;
130
+ this.aborted = aborted;
131
+ }
132
+ }
133
+ function compactionExecutionOwnsTerminalTransition(error) {
134
+ return !(error instanceof CompactionExecutionError) || error.ownsTerminalTransition;
135
+ }
136
+ function isCompactionExecutionAborted(error) {
137
+ return ((error instanceof CompactionExecutionError && error.aborted) ||
138
+ error instanceof CompactionCancelledError ||
139
+ (error instanceof Error && error.name === "AbortError"));
140
+ }
100
141
  class RequiredCompactionError extends Error {
101
142
  constructor() {
102
143
  super("Context remains above the compaction threshold because compaction did not complete");
@@ -135,18 +176,40 @@ export class AgentSession {
135
176
  constructor(config) {
136
177
  this._eventListeners = [];
137
178
  this._agentEventQueue = Promise.resolve();
179
+ /**
180
+ * Exact message objects whose message_end persistence is still queued.
181
+ * Agent core appends messages to agent.state.messages before emitting
182
+ * message_end; until that event settles on _agentEventQueue, compaction must
183
+ * treat these identities as pending persistence, never as stale or droppable.
184
+ */
185
+ this._messageEndsAwaitingPersistence = new Set();
138
186
  this._isAgentRunActive = false;
187
+ this._promptStartPending = false;
139
188
  /** Tracks pending steering messages for UI display. Removed when delivered. */
140
189
  this._steeringMessages = [];
141
190
  /** Tracks pending follow-up messages for UI display. Removed when delivered. */
142
191
  this._followUpMessages = [];
143
192
  /** Messages queued to be included with the next user prompt as context ("asides"). */
144
193
  this._pendingNextTurnMessages = [];
194
+ // Queues held while the first post-compaction response is classified. Agent
195
+ // core otherwise drains steering immediately before AgentSession can consume
196
+ // the stale-usage exemption and schedule the continuation itself.
197
+ this._postCompactionDeferredSteeringMessages = [];
198
+ this._postCompactionDeferredFollowUpMessages = [];
145
199
  // Compaction state
146
200
  this._compactionAbortController = undefined;
147
201
  this._autoCompactionAbortController = undefined;
202
+ this._pendingCompactionAdmission = undefined;
203
+ this._compactionLifecycle = new CompactionLifecycleCoordinator();
148
204
  this._sessionWorkBarrier = new SessionWorkBarrier();
149
205
  this._overflowRecoveryAttempted = false;
206
+ // A retry continuation immediately follows an accepted compaction. Its first
207
+ // response must not retrigger threshold compaction from stale provider usage.
208
+ this._skipNextPostRetryCompactionCheck = false;
209
+ this._skipNextPostCompactionAssistantCheck = false;
210
+ this._scheduledContinuationRecompacted = false;
211
+ this._assistantsPendingAtCompaction = new WeakSet();
212
+ this._postCompactionUsageExemptAssistants = new WeakSet();
150
213
  this._messageRevision = 0;
151
214
  // Branch summarization state
152
215
  this._branchSummaryAbortController = undefined;
@@ -159,6 +222,7 @@ export class AgentSession {
159
222
  this._retryResolve = undefined;
160
223
  this._userAbortPromise = undefined;
161
224
  this._suppressQueuedContinuationAfterUserAbort = false;
225
+ this._extensionEventSignal = undefined;
162
226
  // Bash execution state
163
227
  this._bashAbortController = undefined;
164
228
  this._pendingBashMessages = [];
@@ -176,20 +240,52 @@ export class AgentSession {
176
240
  // Track last assistant message for auto-compaction check
177
241
  this._lastAssistantMessage = undefined;
178
242
  /** Internal handler for agent events - shared by subscribe and reconnect */
179
- this._handleAgentEvent = (event) => {
243
+ this._handleAgentEvent = (event, signal) => {
244
+ // Agent core drains native steer/follow-up queues immediately after its
245
+ // final agent_end. This subscriber intentionally processes its own event
246
+ // queue asynchronously, so a later recovery rejection cannot abort that
247
+ // drain in time. agent_end itself is still an awaited synchronous boundary
248
+ // before that drain: transfer every required overflow or threshold
249
+ // compaction to AgentSession, retaining queues until recovery is accepted.
250
+ if (event.type === "agent_end") {
251
+ const lastAssistant = this._findLastAssistantInMessages(event.messages);
252
+ const requiredAutoCompaction = lastAssistant
253
+ ? this._getRequiredAutoCompactionReason(lastAssistant)
254
+ : undefined;
255
+ if (requiredAutoCompaction) {
256
+ this.agent.suppressQueuedMessageDrain();
257
+ }
258
+ }
180
259
  // Create retry promise synchronously before queueing async processing.
181
260
  // Agent.emit() calls this handler synchronously, and prompt() calls waitForRetry()
182
261
  // as soon as agent.prompt() resolves. If _retryPromise is created only inside
183
262
  // _processAgentEvent, slow earlier queued events can delay agent_end processing
184
263
  // and waitForRetry() can miss the in-flight retry.
185
264
  this._createRetryPromiseForAgentEnd(event);
186
- this._agentEventQueue = this._agentEventQueue.then(() => this._processAgentEvent(event), () => this._processAgentEvent(event));
265
+ // The message object is already in agent.state.messages when message_end
266
+ // fires; track its exact identity until this event's queued processing
267
+ // settles so compaction can distinguish pending persistence from stale state.
268
+ if (event.type === "message_end" && event.message.role === "assistant") {
269
+ this._emitServerFallbackAborted(event.message);
270
+ }
271
+ const pendingMessage = event.type === "message_end" ? event.message : undefined;
272
+ if (pendingMessage !== undefined) {
273
+ this._messageEndsAwaitingPersistence.add(pendingMessage);
274
+ }
275
+ const processing = this._agentEventQueue.then(() => this._processAgentEvent(event, signal), () => this._processAgentEvent(event, signal));
276
+ this._agentEventQueue =
277
+ pendingMessage !== undefined
278
+ ? processing.finally(() => {
279
+ this._messageEndsAwaitingPersistence.delete(pendingMessage);
280
+ })
281
+ : processing;
187
282
  // Keep queue alive if an event handler fails
188
283
  this._agentEventQueue.catch(() => { });
189
284
  };
190
285
  this.agent = config.agent;
191
286
  this.sessionManager = config.sessionManager;
192
287
  this.settingsManager = config.settingsManager;
288
+ this.agent.abortServerSideFallback = this.settingsManager.getAbortServerSideFallback();
193
289
  const noModelFallback = config.resourceLoader.getExtensions().runtime.flagValues.get("no-model-fallback") === true ||
194
290
  process.env.SENPI_NO_FALLBACK === "1";
195
291
  if (noModelFallback) {
@@ -221,8 +317,9 @@ export class AgentSession {
221
317
  persistDefault: false,
222
318
  appendSessionEntry: true,
223
319
  entryReason: reason,
224
- emitModelSelect: reason === "fallback-revert",
225
- invalidateCompaction: false,
320
+ emitModelSelect: true,
321
+ modelSelectSource: reason,
322
+ invalidateCompaction: true,
226
323
  ephemeralThinkingLevel: thinking,
227
324
  });
228
325
  },
@@ -268,7 +365,7 @@ export class AgentSession {
268
365
  }
269
366
  throw error;
270
367
  }
271
- if (result?.auth.apiKey) {
368
+ if (result && (result.auth.apiKey || result.auth.headers)) {
272
369
  return {
273
370
  apiKey: result.auth.apiKey,
274
371
  headers: withoutDeletedHeaders(result.auth.headers),
@@ -284,26 +381,31 @@ export class AgentSession {
284
381
  }
285
382
  throw new Error(formatNoApiKeyFoundMessage(model.provider));
286
383
  }
287
- async _getCompactionRequestAuth(model) {
288
- if (this.agent.streamFn === streamSimple) {
384
+ /**
385
+ * Resolve optional auth for a summarization stream. Native/custom stream
386
+ * functions may provide ambient credentials, unlike streamSimple.
387
+ */
388
+ async _getSummarizationRequestAuth(model) {
389
+ if (this.agent.streamFunction === streamSimple) {
289
390
  return this._getRequiredRequestAuth(model);
290
391
  }
291
- const compatibility = this._modelRuntime.getCompatibilityRequestConfig(model);
292
392
  try {
293
393
  const result = await this._modelRuntime.getAuth(model);
294
394
  return result
295
- ? {
296
- apiKey: result.auth.apiKey,
297
- headers: withoutDeletedHeaders(result.auth.headers),
298
- extraBody: compatibility.extraBody,
299
- env: result.env,
300
- }
301
- : { extraBody: compatibility.extraBody };
395
+ ? { apiKey: result.auth.apiKey, headers: withoutDeletedHeaders(result.auth.headers), env: result.env }
396
+ : {};
302
397
  }
303
398
  catch {
304
- return { extraBody: compatibility.extraBody };
399
+ return {};
305
400
  }
306
401
  }
402
+ async _getCompactionRequestAuth(model) {
403
+ const auth = await this._getSummarizationRequestAuth(model);
404
+ return {
405
+ ...auth,
406
+ extraBody: this._modelRuntime.getCompatibilityRequestConfig(model).extraBody,
407
+ };
408
+ }
307
409
  /**
308
410
  * Install tool hooks once on the Agent instance.
309
411
  *
@@ -312,6 +414,24 @@ export class AgentSession {
312
414
  * registered tool execution to the extension context. Tool call and tool result interception now
313
415
  * happens here instead of in wrappers.
314
416
  */
417
+ /**
418
+ * Surface a provider-level server-fallback abort before retry handling runs so
419
+ * the UI can explain the switch. Emitted synchronously here because retry work
420
+ * for the following agent_end starts before queued message_end processing
421
+ * drains. `chainConfigured` is required because the no-chain refusal path
422
+ * emits no retry_fallback_exhausted, leaving the UI no other signal.
423
+ */
424
+ _emitServerFallbackAborted(message) {
425
+ const details = message.diagnostics?.find((entry) => entry.type === SERVER_FALLBACK_ABORTED_DIAGNOSTIC)?.details;
426
+ if (details === undefined)
427
+ return;
428
+ this._emit({
429
+ type: "server_fallback_aborted",
430
+ from: typeof details.from === "string" ? details.from : message.model,
431
+ to: typeof details.to === "string" ? details.to : message.model,
432
+ chainConfigured: this._retryFallback.hasConfiguredChain(),
433
+ });
434
+ }
315
435
  _installAgentToolHooks() {
316
436
  this.agent.beforeToolCall = async ({ toolCall, args }) => {
317
437
  return this._emitBeforeToolCallHooks(toolCall, args);
@@ -354,6 +474,7 @@ export class AgentSession {
354
474
  content: result.content,
355
475
  details: result.details,
356
476
  isError,
477
+ usage: result.usage,
357
478
  });
358
479
  if (!hookResult) {
359
480
  return undefined;
@@ -362,6 +483,7 @@ export class AgentSession {
362
483
  content: hookResult.content,
363
484
  details: hookResult.details,
364
485
  isError: hookResult.isError ?? isError,
486
+ usage: hookResult.usage,
365
487
  };
366
488
  }
367
489
  _installAgentNextTurnRefresh() {
@@ -370,31 +492,68 @@ export class AgentSession {
370
492
  ? async (_turn, signal) => await this.agent.prepareNextTurn?.(signal)
371
493
  : undefined);
372
494
  this.agent.prepareNextTurnWithContext = async (turn, signal) => {
373
- let messages = turn.context.messages;
374
- if (turn.toolResults.length > 0) {
495
+ // Enforce compaction only when this prepare precedes an actual provider
496
+ // admission: a tool continuation or queued steer/follow-up messages. A
497
+ // completed turn with no continuation keeps pre-PR timing, while the
498
+ // prior prepare callback and context refresh below still run every turn.
499
+ const compactBeforeNextAdmission = async () => {
500
+ if (turn.toolResults.length === 0 && !this.agent.hasQueuedMessages()) {
501
+ return false;
502
+ }
375
503
  await this._agentEventQueue;
376
- const compacted = await this._checkCompaction(turn.message, true, "threshold");
377
- if (compacted) {
378
- messages = this.agent.state.messages.slice();
504
+ // A queue can be cleared while waiting for persistence. Re-sample it
505
+ // immediately before compaction so a completed turn never compacts
506
+ // merely because it once had a possible continuation.
507
+ if (turn.toolResults.length === 0 && !this.agent.hasQueuedMessages()) {
508
+ return false;
379
509
  }
380
- else {
381
- const settings = this.settingsManager.getCompactionSettings();
382
- const contextTokens = estimateContextTokens(filterContextExcludedMessages(this.sessionManager.buildSessionContext().messages)).tokens;
383
- if (settings.enabled && this.model && shouldCompact(contextTokens, this.model.contextWindow, settings)) {
384
- throw new RequiredCompactionError();
510
+ try {
511
+ return await this._enforceCompactionBeforeProvider(turn.message, true, "threshold");
512
+ }
513
+ catch (error) {
514
+ if (error instanceof RequiredCompactionError && this.agent.hasQueuedMessages()) {
515
+ this._requiredCompactionAdmissionError = error;
385
516
  }
517
+ throw error;
386
518
  }
387
- }
519
+ };
520
+ const compactedBeforeCallback = await compactBeforeNextAdmission();
521
+ const messages = compactedBeforeCallback ? this.agent.state.messages.slice() : turn.context.messages;
388
522
  const postCompactionTurn = {
389
523
  ...turn,
390
524
  context: { ...turn.context, messages },
391
525
  };
392
- const previousSnapshot = await previousPrepareNextTurnWithContext?.(postCompactionTurn, signal);
393
- const previousContext = previousSnapshot?.context ?? postCompactionTurn.context;
526
+ let previousSnapshot = await previousPrepareNextTurnWithContext?.(postCompactionTurn, signal);
527
+ let previousContext = previousSnapshot?.context ?? postCompactionTurn.context;
528
+ // The previous callback may await while agent_end extensions enqueue
529
+ // continuation work. Re-sample after it returns so that work cannot
530
+ // slip through with the stale provider snapshot it observed on entry.
531
+ let compactedAfterCallback = false;
532
+ if (!compactedBeforeCallback) {
533
+ compactedAfterCallback = await compactBeforeNextAdmission();
534
+ }
535
+ if (compactedAfterCallback) {
536
+ // The callback's first result describes the stale pre-compaction
537
+ // context. Reapply it once to the compacted context so any host
538
+ // transformation reaches the provider request. Do not re-sample after
539
+ // this invocation: one replay is the bounded admission path.
540
+ const postLateCompactionTurn = {
541
+ ...turn,
542
+ context: { ...turn.context, messages: this.agent.state.messages.slice() },
543
+ };
544
+ const postLateCompactionSnapshot = await previousPrepareNextTurnWithContext?.(postLateCompactionTurn, signal);
545
+ previousSnapshot = {
546
+ ...previousSnapshot,
547
+ ...postLateCompactionSnapshot,
548
+ context: postLateCompactionSnapshot?.context ?? postLateCompactionTurn.context,
549
+ };
550
+ previousContext = previousSnapshot.context ?? postLateCompactionTurn.context;
551
+ }
394
552
  return {
395
553
  ...previousSnapshot,
396
554
  context: {
397
555
  ...previousContext,
556
+ messages: previousContext.messages,
398
557
  systemPrompt: this._systemPromptOverride ?? this._baseSystemPrompt,
399
558
  tools: this.agent.state.tools.slice(),
400
559
  },
@@ -421,6 +580,7 @@ export class AgentSession {
421
580
  }
422
581
  _incrementMessageRevision() {
423
582
  this._messageRevision++;
583
+ this._blockedPostCompactionAssistant = undefined;
424
584
  }
425
585
  getMessageRevision() {
426
586
  return this._messageRevision;
@@ -481,14 +641,46 @@ export class AgentSession {
481
641
  }
482
642
  async _promptAgent(messages) {
483
643
  this._isAgentRunActive = true;
644
+ this._requiredCompactionAdmissionError = undefined;
484
645
  try {
485
646
  await this.agent.prompt(messages);
647
+ // AgentSession's subscriber intentionally queues event work instead of
648
+ // blocking Agent core. Wait for this run's queued recovery decision
649
+ // before reporting prompt completion to the caller.
650
+ await this._agentEventQueue;
651
+ const requiredCompactionError = this._requiredCompactionAdmissionError;
652
+ this._requiredCompactionAdmissionError = undefined;
653
+ if (requiredCompactionError) {
654
+ throw requiredCompactionError;
655
+ }
486
656
  }
487
657
  catch (error) {
658
+ if (error instanceof Error &&
659
+ error.message ===
660
+ "Agent is already processing a prompt. Use steer() or followUp() to queue messages, or wait for completion.") {
661
+ const queuedMessages = Array.isArray(messages) ? messages : [messages];
662
+ for (const message of queuedMessages)
663
+ this.agent.steer(message);
664
+ const userMessage = queuedMessages.find((message) => message.role === "user");
665
+ if (userMessage?.role === "user") {
666
+ this._steeringMessages.push(this._extractUserMessageText(userMessage.content));
667
+ this._emitQueueUpdate();
668
+ }
669
+ return;
670
+ }
488
671
  await this._emitAgentSettled();
489
672
  throw error;
490
673
  }
491
674
  }
675
+ /** Extract text content used to track fork-owned queued user messages. */
676
+ _extractUserMessageText(content) {
677
+ if (typeof content === "string")
678
+ return content;
679
+ return content
680
+ .filter((part) => part.type === "text")
681
+ .map((part) => part.text)
682
+ .join("");
683
+ }
492
684
  _createRetryPromiseForAgentEnd(event) {
493
685
  if (event.type !== "agent_end" || this._retryPromise) {
494
686
  return;
@@ -505,6 +697,10 @@ export class AgentSession {
505
697
  this._retryPromise = new Promise((resolve) => {
506
698
  this._retryResolve = resolve;
507
699
  });
700
+ // Agent core normally drains queued input immediately after agent_end.
701
+ // Retry owns that continuation until its final provider-admission check has
702
+ // either started it or reported a terminal rejection.
703
+ this.agent.suppressQueuedMessageDrain();
508
704
  }
509
705
  _findLastAssistantInMessages(messages) {
510
706
  for (let i = messages.length - 1; i >= 0; i--) {
@@ -515,6 +711,91 @@ export class AgentSession {
515
711
  }
516
712
  return undefined;
517
713
  }
714
+ /**
715
+ * Synchronously mirror the auto-compaction decision that _checkCompaction()
716
+ * will make after agent_end. Agent core drains queues before that async work
717
+ * runs, so only this preflight can transfer required admissions safely.
718
+ */
719
+ _getRequiredAutoCompactionReason(message) {
720
+ const reason = this._getAutoCompactionReason(message);
721
+ // Retry and post-compaction exemptions only cover stale usage estimates.
722
+ // A provider-confirmed overflow must always retain queue ownership and run
723
+ // fail-closed recovery.
724
+ if (reason === "overflow" &&
725
+ message.stopReason === "stop" &&
726
+ this._hasPendingPostCompactionUsageExemption(message)) {
727
+ // A successful post-compaction response can carry stale provider usage.
728
+ // Retain its queues while the asynchronous check consumes the exemption.
729
+ return "threshold";
730
+ }
731
+ if (reason === "overflow")
732
+ return reason;
733
+ // Keep queued continuations under AgentSession ownership while the
734
+ // asynchronous check consumes this post-compaction usage exemption.
735
+ if (this._hasPendingPostCompactionUsageExemption(message))
736
+ return "threshold";
737
+ if (reason === "threshold" &&
738
+ (this._skipNextPostRetryCompactionCheck || this._postCompactionUsageExemptAssistants.has(message))) {
739
+ return undefined;
740
+ }
741
+ return reason;
742
+ }
743
+ _getAutoCompactionReason(message) {
744
+ const settings = this.settingsManager.getCompactionSettings();
745
+ if (!settings.enabled || message.stopReason === "aborted") {
746
+ return undefined;
747
+ }
748
+ const model = this.model;
749
+ if (!model || this._isAssistantFromBeforeLatestCompaction(message)) {
750
+ return undefined;
751
+ }
752
+ const sameModel = isSameOverflowSource(message, model, this._modelRuntime.getCompatibilityRequestConfig(model).upstreamModelId);
753
+ const contextUsage = this.getContextUsage();
754
+ const currentContextNeedsCompaction = contextUsage !== undefined &&
755
+ contextUsage.tokens !== null &&
756
+ shouldCompact(contextUsage.tokens, contextUsage.contextWindow, settings);
757
+ if (isContextOverflow(message, model.contextWindow) && (sameModel || currentContextNeedsCompaction)) {
758
+ return "overflow";
759
+ }
760
+ let contextTokens;
761
+ const directContextTokens = message.usage ? calculateContextTokens(message.usage) : 0;
762
+ if (message.stopReason !== "error" && directContextTokens !== 0) {
763
+ contextTokens = directContextTokens;
764
+ }
765
+ else {
766
+ const messages = filterContextExcludedMessages(this.agent.state.messages);
767
+ const estimate = estimateContextTokens(messages);
768
+ if (estimate.lastUsageIndex === null) {
769
+ return undefined;
770
+ }
771
+ const compactionEntry = getLatestCompactionEntry(this.sessionManager.getBranch());
772
+ const usageMessage = messages[estimate.lastUsageIndex];
773
+ if (compactionEntry &&
774
+ usageMessage?.role === "assistant" &&
775
+ this._isAssistantFromBeforeLatestCompaction(usageMessage)) {
776
+ return undefined;
777
+ }
778
+ contextTokens = estimate.tokens;
779
+ }
780
+ return shouldCompact(contextTokens, model.contextWindow, settings) ? "threshold" : undefined;
781
+ }
782
+ _hasPendingPostCompactionUsageExemption(message) {
783
+ return (this._skipNextPostCompactionAssistantCheck &&
784
+ !this._assistantsPendingAtCompaction.has(message) &&
785
+ !this._overflowRecoveryAttempted);
786
+ }
787
+ _isPostCompactionUsageExempt(message) {
788
+ return (this._postCompactionUsageExemptAssistants.has(message) || this._hasPendingPostCompactionUsageExemption(message));
789
+ }
790
+ _consumePostCompactionUsageExemption(message) {
791
+ if (this._postCompactionUsageExemptAssistants.has(message))
792
+ return true;
793
+ if (!this._isPostCompactionUsageExempt(message))
794
+ return false;
795
+ this._skipNextPostCompactionAssistantCheck = false;
796
+ this._postCompactionUsageExemptAssistants.add(message);
797
+ return true;
798
+ }
518
799
  _agentEndAllowsQueuedContinuation(messages) {
519
800
  let lastAssistantIndex = -1;
520
801
  for (let index = messages.length - 1; index >= 0; index--) {
@@ -533,6 +814,9 @@ export class AgentSession {
533
814
  if (lastAssistant.stopReason === "aborted" || lastAssistant.stopReason === "error") {
534
815
  return false;
535
816
  }
817
+ if (this._getRequiredAutoCompactionReason(lastAssistant)) {
818
+ return false;
819
+ }
536
820
  for (let index = lastAssistantIndex + 1; index < messages.length; index++) {
537
821
  const message = messages[index];
538
822
  if (message?.role === "toolResult" &&
@@ -572,13 +856,13 @@ export class AgentSession {
572
856
  }
573
857
  return providerDelayMs <= this.settingsManager.getProviderRetrySettings().maxRetryDelayMs;
574
858
  }
575
- async _processAgentEvent(event) {
859
+ async _processAgentEvent(event, signal) {
576
860
  // When a user message starts, check if it's from either queue and remove it BEFORE emitting
577
861
  // This ensures the UI sees the updated queue state
578
862
  if (event.type === "message_start" && event.message.role === "user") {
579
863
  this._overflowRecoveryAttempted = false;
580
864
  this._retryFallback.resetTurn();
581
- const messageText = this._getUserMessageText(event.message);
865
+ const messageText = contentText(event.message.content, "");
582
866
  if (messageText) {
583
867
  // Check steering queue first
584
868
  const steeringIndex = this._steeringMessages.indexOf(messageText);
@@ -596,8 +880,15 @@ export class AgentSession {
596
880
  }
597
881
  }
598
882
  }
599
- // Emit to extensions first
600
- await this._emitExtensionEvent(event);
883
+ // Emit to extensions first. Agent event persistence is intentionally
884
+ // asynchronous, so retain the source run signal while dispatching.
885
+ this._extensionEventSignal = signal;
886
+ try {
887
+ await this._emitExtensionEvent(event);
888
+ }
889
+ finally {
890
+ this._extensionEventSignal = undefined;
891
+ }
601
892
  // Notify all listeners
602
893
  this._emit(event.type === "agent_end" ? { ...event, willRetry: this._willRetryAfterAgentEnd(event.messages) } : event);
603
894
  // Handle session persistence
@@ -643,8 +934,10 @@ export class AgentSession {
643
934
  }
644
935
  }
645
936
  }
646
- // Check auto-retry and auto-compaction after agent completes
937
+ // Check auto-retry and auto-compaction after agent completes.
647
938
  let launchedContinuation = false;
939
+ let retryContinuationBlocked = false;
940
+ let allowsPostCompactionUsageExemptContinuation = false;
648
941
  const userAbortSuppressedQueuedContinuation = event.type === "agent_end" && this._suppressQueuedContinuationAfterUserAbort;
649
942
  if (userAbortSuppressedQueuedContinuation) {
650
943
  this._suppressQueuedContinuationAfterUserAbort = false;
@@ -655,23 +948,62 @@ export class AgentSession {
655
948
  if (event.type === "agent_end" && this._lastAssistantMessage) {
656
949
  const msg = this._lastAssistantMessage;
657
950
  this._lastAssistantMessage = undefined;
951
+ this._skipNextPostRetryCompactionCheck = false;
952
+ const requiredAutoCompaction = this._getRequiredAutoCompactionReason(msg);
658
953
  // Retry transient failures normally and eligible hard errors only through a fallback.
659
954
  const retryableError = this._isRetryableError(msg);
660
- let didRetry = false;
661
- if (retryableError) {
662
- didRetry = await this._handleRetryableError(msg);
955
+ const hardErrorFallbackEligible = this._isHardErrorFallbackEligible(msg);
956
+ const retryCanAdmitProvider = this.settingsManager.getRetrySettings().enabled && (retryableError || hardErrorFallbackEligible);
957
+ let compactedBeforeRetry = false;
958
+ if (retryCanAdmitProvider &&
959
+ requiredAutoCompaction &&
960
+ !(requiredAutoCompaction === "threshold" && this._hasPendingPostCompactionUsageExemption(msg))) {
961
+ this._retireFailedRetryAssistant(msg);
962
+ compactedBeforeRetry = await this._runPrePromptCompaction(msg, true, "threshold", true);
963
+ retryContinuationBlocked = !compactedBeforeRetry;
663
964
  }
664
- else if (this._isHardErrorFallbackEligible(msg)) {
665
- didRetry = await this._handleRetryableError(msg, { hardErrorFallback: true });
965
+ let retryOutcome = "not-handled";
966
+ if (!retryContinuationBlocked) {
967
+ if (retryableError) {
968
+ retryOutcome = await this._handleRetryableError(msg);
969
+ }
970
+ else if (hardErrorFallbackEligible) {
971
+ retryOutcome = await this._handleRetryableError(msg, { hardErrorFallback: true });
972
+ }
666
973
  }
667
- if (didRetry)
668
- return; // Retry was initiated, don't proceed to compaction
974
+ if (retryOutcome === "continued")
975
+ return;
669
976
  this._resolveRetry();
670
- launchedContinuation = await this._checkCompaction(msg);
977
+ retryContinuationBlocked ||= retryOutcome === "blocked";
978
+ if (!retryContinuationBlocked) {
979
+ if (compactedBeforeRetry && this.agent.hasQueuedMessages()) {
980
+ this._scheduleContinuationAfterCurrentEvent();
981
+ launchedContinuation = true;
982
+ }
983
+ else {
984
+ launchedContinuation = await this._checkCompaction(msg);
985
+ allowsPostCompactionUsageExemptContinuation = this._postCompactionUsageExemptAssistants.has(msg);
986
+ if (allowsPostCompactionUsageExemptContinuation) {
987
+ this._flushPostCompactionDeferredMessages();
988
+ }
989
+ // _runAutoCompaction() returns false both when recovery was rejected and
990
+ // when an accepted compaction had no queue to continue. Re-sample after
991
+ // it settles so only the still-required (rejected) case fails admission.
992
+ if (requiredAutoCompaction &&
993
+ !launchedContinuation &&
994
+ this.agent.hasQueuedMessages() &&
995
+ this._getRequiredAutoCompactionReason(msg) !== undefined) {
996
+ this._requiredCompactionAdmissionError = new RequiredCompactionError();
997
+ }
998
+ }
999
+ }
671
1000
  }
672
1001
  if (event.type === "agent_end") {
673
1002
  this._flushPendingBashMessages();
674
- if (!launchedContinuation && allowsQueuedContinuation && this.agent.hasQueuedMessages()) {
1003
+ if (!launchedContinuation &&
1004
+ !retryContinuationBlocked &&
1005
+ (allowsQueuedContinuation || allowsPostCompactionUsageExemptContinuation) &&
1006
+ this.agent.hasQueuedMessages()) {
675
1007
  this._scheduleContinuationAfterCurrentEvent();
676
1008
  launchedContinuation = true;
677
1009
  }
@@ -688,16 +1020,6 @@ export class AgentSession {
688
1020
  this._retryPromise = undefined;
689
1021
  }
690
1022
  }
691
- /** Extract text content from a message */
692
- _getUserMessageText(message) {
693
- if (message.role !== "user")
694
- return "";
695
- const content = message.content;
696
- if (typeof content === "string")
697
- return content;
698
- const textBlocks = content.filter((c) => c.type === "text");
699
- return textBlocks.map((c) => c.text).join("");
700
- }
701
1023
  /** Find the last assistant message in agent state (including aborted ones) */
702
1024
  _findLastAssistantMessage() {
703
1025
  const messages = this.agent.state.messages;
@@ -709,9 +1031,47 @@ export class AgentSession {
709
1031
  }
710
1032
  return undefined;
711
1033
  }
1034
+ /**
1035
+ * Retry failures stay in append-only history but must not be retained in the
1036
+ * active context branch. Otherwise split-turn compaction keeps the failed
1037
+ * assistant response verbatim and cannot make progress before a retry.
1038
+ */
1039
+ _retireFailedRetryAssistant(message) {
1040
+ const position = this.sessionManager.getMessageEntryPosition(message);
1041
+ if (!position || this.sessionManager.getLeafId() !== position.entryId)
1042
+ return;
1043
+ const entry = this.sessionManager.getEntry(position.entryId);
1044
+ if (entry?.type !== "message")
1045
+ return;
1046
+ if (entry.parentId === null) {
1047
+ this.sessionManager.resetLeaf();
1048
+ }
1049
+ else {
1050
+ this.sessionManager.branch(entry.parentId);
1051
+ }
1052
+ const messageIndex = this.agent.state.messages.lastIndexOf(message);
1053
+ if (messageIndex !== -1) {
1054
+ this.agent.state.messages = this.agent.state.messages.slice(0, messageIndex);
1055
+ }
1056
+ this._incrementMessageRevision();
1057
+ }
712
1058
  _isAssistantFromBeforeLatestCompaction(assistantMessage) {
713
1059
  const compactionEntry = getLatestCompactionEntry(this.sessionManager.getBranch());
714
- return compactionEntry !== null && assistantMessage.timestamp <= new Date(compactionEntry.timestamp).getTime();
1060
+ if (compactionEntry === null)
1061
+ return false;
1062
+ // An agent message_end can still be awaiting persistence while compaction
1063
+ // commits. It is necessarily a post-boundary message, even when a provider
1064
+ // supplied an older payload timestamp.
1065
+ if (this._messageEndsAwaitingPersistence.has(assistantMessage))
1066
+ return false;
1067
+ const messagePosition = this.sessionManager.getMessageEntryPosition(assistantMessage);
1068
+ const compactionOrder = this.sessionManager.getEntryOrder(compactionEntry.id);
1069
+ if (messagePosition !== undefined && compactionOrder !== undefined) {
1070
+ return messagePosition.order <= compactionOrder;
1071
+ }
1072
+ // Reloaded/reconstructed messages have no runtime identity. Retain the
1073
+ // historical timestamp heuristic only for that compatibility path.
1074
+ return assistantMessage.timestamp <= new Date(compactionEntry.timestamp).getTime();
715
1075
  }
716
1076
  _replaceMessageInPlace(target, replacement) {
717
1077
  // Agent-core stores the finalized message object in its state before emitting message_end.
@@ -1006,17 +1366,28 @@ export class AgentSession {
1006
1366
  validToolNames.push(name);
1007
1367
  }
1008
1368
  }
1369
+ const activeToolNamesChanged = validToolNames.length !== this.agent.state.tools.length ||
1370
+ validToolNames.some((name, index) => name !== this.agent.state.tools[index]?.name);
1009
1371
  this.agent.state.tools = tools;
1010
1372
  // Rebuild base system prompt with new tool set
1011
1373
  this._baseSystemPrompt = this._rebuildSystemPrompt(validToolNames);
1012
1374
  this.agent.state.systemPrompt = this._systemPromptOverride ?? this._baseSystemPrompt;
1375
+ if (activeToolNamesChanged) {
1376
+ this.abortCompaction();
1377
+ this._incrementMessageRevision();
1378
+ }
1013
1379
  }
1014
1380
  /** Whether compaction or branch summarization is currently running */
1015
1381
  get isCompacting() {
1016
- return (this._autoCompactionAbortController !== undefined ||
1382
+ return (this._pendingCompactionAdmission !== undefined ||
1383
+ this._compactionLifecycle.state.status === "running" ||
1384
+ this._autoCompactionAbortController !== undefined ||
1017
1385
  this._compactionAbortController !== undefined ||
1018
1386
  this._branchSummaryAbortController !== undefined);
1019
1387
  }
1388
+ get compactionState() {
1389
+ return this._compactionLifecycle.state;
1390
+ }
1020
1391
  /** All messages including custom types like BashExecutionMessage */
1021
1392
  get messages() {
1022
1393
  return this.agent.state.messages;
@@ -1162,28 +1533,52 @@ export class AgentSession {
1162
1533
  await userAbortPromise;
1163
1534
  throwIfCancelled();
1164
1535
  }
1165
- const shouldWaitForSessionWork = options?.source !== "extension";
1536
+ const ownsPromptStart = !this.isStreaming && !this._promptStartPending && options?.streamingBehavior === undefined;
1537
+ if (ownsPromptStart)
1538
+ this._promptStartPending = true;
1539
+ // Extension bindings deliberately fire-and-forget sendUserMessage(), so an
1540
+ // extension callback cannot deadlock on this wait. The resulting provider
1541
+ // turn must still serialize behind compaction and other session work just
1542
+ // like an interactive prompt does.
1543
+ const shouldWaitForSessionWork = true;
1544
+ const pendingCompactionAdmissionAtExtensionAdmission = options?.source === "extension" ? this._pendingCompactionAdmission : undefined;
1545
+ const compactionGenerationAtExtensionAdmission = options?.source === "extension" &&
1546
+ this._sessionWorkBarrier.hasActiveWork &&
1547
+ this._compactionLifecycle.state.status !== "idle"
1548
+ ? this._compactionLifecycle.state.generation
1549
+ : undefined;
1166
1550
  // A steer/followUp submission during an active run can be queued immediately.
1167
1551
  // Waiting on the session-work barrier here would trap the message inside this
1168
1552
  // call for the rest of the run whenever a queued continuation (e.g. an active
1169
1553
  // goal chain) holds the barrier, making typed input invisible until the run
1170
1554
  // ends or the user aborts.
1171
- const canQueueWhileStreaming = this.isStreaming && !this.isCompacting && options?.streamingBehavior !== undefined;
1555
+ const canQueueWhileStreaming = (this.isStreaming || this._promptStartPending) &&
1556
+ !this.isCompacting &&
1557
+ options?.streamingBehavior !== undefined;
1172
1558
  if (shouldWaitForSessionWork &&
1173
1559
  !canQueueWhileStreaming &&
1174
1560
  (!this.isStreaming || this.isCompacting || this._sessionWorkBarrier.hasActiveWork)) {
1175
1561
  await this._waitForSettledSessionWork();
1176
1562
  throwIfCancelled();
1563
+ options?.onSessionWorkReady?.();
1177
1564
  }
1178
1565
  // Turn boundary: restore the primary model if the fallback cooldown expired.
1179
1566
  if (!this.isStreaming) {
1180
- await this._maybeRestoreFallbackPrimary();
1567
+ try {
1568
+ await this._maybeRestoreFallbackPrimary();
1569
+ }
1570
+ catch (error) {
1571
+ if (ownsPromptStart)
1572
+ this._promptStartPending = false;
1573
+ throw error;
1574
+ }
1181
1575
  }
1182
1576
  const expandPromptTemplates = options?.expandPromptTemplates ?? true;
1183
1577
  const preflightResult = options?.preflightResult;
1184
1578
  const promptDisposition = options?.promptDisposition;
1185
1579
  let messages;
1186
1580
  let titlePrompt;
1581
+ let consumedNextTurnMessages;
1187
1582
  try {
1188
1583
  // Handle extension commands first (execute immediately, even during streaming)
1189
1584
  // Extension commands manage their own LLM interaction via pi.sendMessage()
@@ -1238,6 +1633,23 @@ export class AgentSession {
1238
1633
  preflightResult?.(true);
1239
1634
  return;
1240
1635
  }
1636
+ // Input accepted while a run was active remains queued even if that run
1637
+ // ends while extension input handling or template expansion is pending.
1638
+ // Starting a fresh prompt here would let it overtake the held continuation.
1639
+ if (canQueueWhileStreaming && !this.isStreaming) {
1640
+ if (options?.thinkingLevel !== undefined) {
1641
+ throw new Error("Cannot set thinkingLevel on a queued prompt; set it after the current turn completes.");
1642
+ }
1643
+ if (options?.streamingBehavior === "followUp") {
1644
+ await this._queueFollowUp(expandedText, currentImages);
1645
+ }
1646
+ else {
1647
+ await this._queueSteer(expandedText, currentImages);
1648
+ }
1649
+ promptDisposition?.("queued");
1650
+ preflightResult?.(true);
1651
+ return;
1652
+ }
1241
1653
  // The queue-while-streaming bypass above skipped the settled-work wait. If
1242
1654
  // the run ended while input was being expanded, serialize with remaining
1243
1655
  // session work before sending a fresh prompt, and re-queue if a scheduled
@@ -1262,6 +1674,25 @@ export class AgentSession {
1262
1674
  return;
1263
1675
  }
1264
1676
  }
1677
+ // A background extension prompt that arrived during a manual compaction
1678
+ // must never overtake a rejected or aborted compaction. Keep its normal
1679
+ // queue ownership instead of attempting another provider admission.
1680
+ if ((pendingCompactionAdmissionAtExtensionAdmission !== undefined &&
1681
+ pendingCompactionAdmissionAtExtensionAdmission.outcome !== "completed") ||
1682
+ (compactionGenerationAtExtensionAdmission !== undefined &&
1683
+ this._compactionLifecycle.state.generation === compactionGenerationAtExtensionAdmission &&
1684
+ (this._compactionLifecycle.state.status === "failed" ||
1685
+ this._compactionLifecycle.state.status === "aborted"))) {
1686
+ if (options?.streamingBehavior === "followUp") {
1687
+ await this._queueFollowUp(expandedText, currentImages);
1688
+ }
1689
+ else {
1690
+ await this._queueSteer(expandedText, currentImages);
1691
+ }
1692
+ promptDisposition?.("queued");
1693
+ preflightResult?.(true);
1694
+ return;
1695
+ }
1265
1696
  // Flush any pending bash messages before the new prompt
1266
1697
  this._flushPendingBashMessages();
1267
1698
  // Validate model
@@ -1279,19 +1710,8 @@ export class AgentSession {
1279
1710
  }
1280
1711
  throw new Error(formatNoApiKeyFoundMessage(this.model.provider));
1281
1712
  }
1282
- // Check if we need to compact before sending (catches aborted responses).
1283
1713
  // The user's new prompt is sent below, so do not call agent.continue() here.
1284
- const lastAssistant = this._findLastAssistantMessage();
1285
- if (lastAssistant) {
1286
- const compacted = await this._checkCompaction(lastAssistant, false, "pre_prompt");
1287
- if (!compacted && !this._isAssistantFromBeforeLatestCompaction(lastAssistant)) {
1288
- const settings = this.settingsManager.getCompactionSettings();
1289
- const contextTokens = estimateContextTokens(filterContextExcludedMessages(this.sessionManager.buildSessionContext().messages)).tokens;
1290
- if (settings.enabled && this.model && shouldCompact(contextTokens, this.model.contextWindow, settings)) {
1291
- throw new RequiredCompactionError();
1292
- }
1293
- }
1294
- }
1714
+ await this._enforceCompactionBeforeProvider(this._findLastAssistantMessage(), false, "pre_prompt");
1295
1715
  // Build messages array (custom message if any, then user message)
1296
1716
  messages = [];
1297
1717
  // Add user message
@@ -1304,11 +1724,14 @@ export class AgentSession {
1304
1724
  content: userContent,
1305
1725
  timestamp: Date.now(),
1306
1726
  });
1307
- // Inject any pending "nextTurn" messages as context alongside the user message
1308
- for (const msg of this._pendingNextTurnMessages) {
1727
+ // Consume next-turn messages transactionally: a final admission rejection
1728
+ // restores them in their original order rather than dropping or duplicating
1729
+ // one-shot extension state.
1730
+ consumedNextTurnMessages = this._pendingNextTurnMessages;
1731
+ this._pendingNextTurnMessages = [];
1732
+ for (const msg of consumedNextTurnMessages) {
1309
1733
  messages.push(msg);
1310
1734
  }
1311
- this._pendingNextTurnMessages = [];
1312
1735
  // Emit before_agent_start extension event
1313
1736
  const result = await this._extensionRunner.emitBeforeAgentStart(expandedText, currentImages, this._baseSystemPrompt, this._baseSystemPromptOptions);
1314
1737
  // Add all custom messages from extensions
@@ -1335,11 +1758,22 @@ export class AgentSession {
1335
1758
  this._systemPromptOverride = undefined;
1336
1759
  this.agent.state.systemPrompt = this._baseSystemPrompt;
1337
1760
  }
1761
+ // The preflight above only sees persisted session context. These prompt,
1762
+ // next-turn, and before_agent_start additions are also provider-visible,
1763
+ // so make one final admission decision against the complete request.
1764
+ await this._enforceFinalProviderAdmission(messages);
1338
1765
  }
1339
1766
  catch (error) {
1767
+ if (consumedNextTurnMessages && consumedNextTurnMessages.length > 0) {
1768
+ this._pendingNextTurnMessages = [...consumedNextTurnMessages, ...this._pendingNextTurnMessages];
1769
+ }
1340
1770
  preflightResult?.(false);
1341
1771
  throw error;
1342
1772
  }
1773
+ finally {
1774
+ if (ownsPromptStart)
1775
+ this._promptStartPending = false;
1776
+ }
1343
1777
  if (!messages) {
1344
1778
  return;
1345
1779
  }
@@ -1352,7 +1786,14 @@ export class AgentSession {
1352
1786
  await this._promptAgent(messages);
1353
1787
  await this.waitForRetry();
1354
1788
  await this.waitForIdle();
1355
- if (shouldWaitForSessionWork) {
1789
+ if (options?.onSessionWorkReady) {
1790
+ // This prompt owns a session-work token acquired after waiting for a
1791
+ // prior operation, so waiting for the global barrier here would await
1792
+ // itself. _promptAgent() already drained its event queue; any newly
1793
+ // scheduled continuation retains its own barrier ownership.
1794
+ await this.agent.waitForIdle();
1795
+ }
1796
+ else if (shouldWaitForSessionWork) {
1356
1797
  await this._waitForSettledSessionWork();
1357
1798
  }
1358
1799
  else {
@@ -1479,7 +1920,7 @@ export class AgentSession {
1479
1920
  sessionId: this.sessionId,
1480
1921
  baseOptions: this._buildSessionTitleBaseOptions(),
1481
1922
  signal: abortController.signal,
1482
- streamFn: this.agent.streamFn,
1923
+ streamFn: this.agent.streamFunction,
1483
1924
  });
1484
1925
  if (abortController.signal.aborted) {
1485
1926
  return;
@@ -1532,11 +1973,12 @@ export class AgentSession {
1532
1973
  if (images) {
1533
1974
  content.push(...images);
1534
1975
  }
1535
- this.agent.steer({
1536
- role: "user",
1537
- content,
1538
- timestamp: Date.now(),
1539
- });
1976
+ const message = { role: "user", content, timestamp: Date.now() };
1977
+ if (this._promptStartPending && this._skipNextPostCompactionAssistantCheck) {
1978
+ this._postCompactionDeferredSteeringMessages.push(message);
1979
+ return;
1980
+ }
1981
+ this.agent.steer(message);
1540
1982
  }
1541
1983
  /**
1542
1984
  * Internal: Queue a follow-up message (already expanded, no extension command check).
@@ -1548,11 +1990,22 @@ export class AgentSession {
1548
1990
  if (images) {
1549
1991
  content.push(...images);
1550
1992
  }
1551
- this.agent.followUp({
1552
- role: "user",
1553
- content,
1554
- timestamp: Date.now(),
1555
- });
1993
+ const message = { role: "user", content, timestamp: Date.now() };
1994
+ if (this._promptStartPending && this._skipNextPostCompactionAssistantCheck) {
1995
+ this._postCompactionDeferredFollowUpMessages.push(message);
1996
+ return;
1997
+ }
1998
+ this.agent.followUp(message);
1999
+ }
2000
+ _flushPostCompactionDeferredMessages() {
2001
+ for (const message of this._postCompactionDeferredSteeringMessages) {
2002
+ this.agent.steer(message);
2003
+ }
2004
+ this._postCompactionDeferredSteeringMessages = [];
2005
+ for (const message of this._postCompactionDeferredFollowUpMessages) {
2006
+ this.agent.followUp(message);
2007
+ }
2008
+ this._postCompactionDeferredFollowUpMessages = [];
1556
2009
  }
1557
2010
  /**
1558
2011
  * Throw an error if the text is an extension command.
@@ -1587,26 +2040,56 @@ export class AgentSession {
1587
2040
  details: message.details,
1588
2041
  timestamp: Date.now(),
1589
2042
  };
1590
- if (options?.deliverAs === "nextTurn") {
1591
- this._pendingNextTurnMessages.push(appMessage);
1592
- }
1593
- else if (this.isStreaming) {
1594
- if (options?.deliverAs === "followUp") {
1595
- this.agent.followUp(appMessage);
2043
+ const waitForExistingSessionWork = options?.triggerTurn === true &&
2044
+ options.deliverAs !== "nextTurn" &&
2045
+ !this.isStreaming &&
2046
+ this._sessionWorkBarrier.hasActiveWork;
2047
+ const pendingCompactionAdmission = this._pendingCompactionAdmission;
2048
+ const activeCompactionGeneration = this._compactionLifecycle.state.status === "running" ? this._compactionLifecycle.state.generation : undefined;
2049
+ let finishSessionWork;
2050
+ try {
2051
+ if (waitForExistingSessionWork) {
2052
+ await this._waitForSettledSessionWork();
2053
+ finishSessionWork = this._sessionWorkBarrier.begin();
2054
+ }
2055
+ if (options?.deliverAs === "nextTurn") {
2056
+ this._pendingNextTurnMessages.push(appMessage);
2057
+ }
2058
+ else if (this.isStreaming) {
2059
+ if (options?.deliverAs === "followUp") {
2060
+ this.agent.followUp(appMessage);
2061
+ }
2062
+ else {
2063
+ this.agent.steer(appMessage);
2064
+ }
2065
+ }
2066
+ else if (options?.triggerTurn === true &&
2067
+ ((pendingCompactionAdmission !== undefined && pendingCompactionAdmission.outcome !== "completed") ||
2068
+ (activeCompactionGeneration !== undefined &&
2069
+ this._compactionLifecycle.state.generation === activeCompactionGeneration &&
2070
+ this._compactionLifecycle.state.status !== "completed"))) {
2071
+ if (options?.deliverAs === "followUp") {
2072
+ this.agent.followUp(appMessage);
2073
+ }
2074
+ else {
2075
+ this.agent.steer(appMessage);
2076
+ }
2077
+ }
2078
+ else if (options?.triggerTurn) {
2079
+ await this._enforceCompactionBeforeProvider(this._findLastAssistantMessage(), false, "pre_prompt");
2080
+ await this._enforceFinalProviderAdmission([appMessage]);
2081
+ await this._promptAgent(appMessage);
1596
2082
  }
1597
2083
  else {
1598
- this.agent.steer(appMessage);
2084
+ this.agent.state.messages.push(appMessage);
2085
+ this.sessionManager.appendCustomMessageEntry(message.customType, message.content, message.display, message.details);
2086
+ this._incrementMessageRevision();
2087
+ this._emit({ type: "message_start", message: appMessage });
2088
+ this._emit({ type: "message_end", message: appMessage });
1599
2089
  }
1600
2090
  }
1601
- else if (options?.triggerTurn) {
1602
- await this._promptAgent(appMessage);
1603
- }
1604
- else {
1605
- this.agent.state.messages.push(appMessage);
1606
- this.sessionManager.appendCustomMessageEntry(message.customType, message.content, message.display, message.details);
1607
- this._incrementMessageRevision();
1608
- this._emit({ type: "message_start", message: appMessage });
1609
- this._emit({ type: "message_end", message: appMessage });
2091
+ finally {
2092
+ finishSessionWork?.();
1610
2093
  }
1611
2094
  }
1612
2095
  /**
@@ -1617,6 +2100,14 @@ export class AgentSession {
1617
2100
  * @param options.deliverAs Delivery mode when streaming: "steer" or "followUp"
1618
2101
  */
1619
2102
  async sendUserMessage(content, options) {
2103
+ const bindingPromptReadiness = this._extensionBindingPromptReadiness;
2104
+ let resolveBindingPromptReadiness;
2105
+ if (bindingPromptReadiness) {
2106
+ const readiness = new Promise((resolve) => {
2107
+ resolveBindingPromptReadiness = resolve;
2108
+ });
2109
+ bindingPromptReadiness.add(readiness);
2110
+ }
1620
2111
  // Normalize content to text string + optional images
1621
2112
  let text;
1622
2113
  let images;
@@ -1638,14 +2129,31 @@ export class AgentSession {
1638
2129
  if (images.length === 0)
1639
2130
  images = undefined;
1640
2131
  }
1641
- // Use prompt() with expandPromptTemplates: false to skip command handling and template expansion
1642
- await this.prompt(text, {
1643
- expandPromptTemplates: false,
1644
- streamingBehavior: options?.deliverAs,
1645
- images,
1646
- source: "extension",
1647
- sessionTitlePrompt: false,
1648
- });
2132
+ // An extension binding invokes this method fire-and-forget. When it
2133
+ // arrives during session work, retain a barrier token after the prior work
2134
+ // settles so callers observing session idle cannot race its provider turn.
2135
+ const waitForExistingSessionWork = this._sessionWorkBarrier.hasActiveWork;
2136
+ let finishSessionWork;
2137
+ try {
2138
+ // Use prompt() with expandPromptTemplates: false to skip command handling and template expansion
2139
+ await this.prompt(text, {
2140
+ expandPromptTemplates: false,
2141
+ streamingBehavior: options?.deliverAs,
2142
+ images,
2143
+ source: "extension",
2144
+ promptDisposition: () => resolveBindingPromptReadiness?.(),
2145
+ onSessionWorkReady: waitForExistingSessionWork
2146
+ ? () => {
2147
+ finishSessionWork ??= this._sessionWorkBarrier.begin();
2148
+ }
2149
+ : undefined,
2150
+ sessionTitlePrompt: false,
2151
+ });
2152
+ }
2153
+ finally {
2154
+ resolveBindingPromptReadiness?.();
2155
+ finishSessionWork?.();
2156
+ }
1649
2157
  }
1650
2158
  /**
1651
2159
  * Clear all queued messages and return them.
@@ -1655,8 +2163,13 @@ export class AgentSession {
1655
2163
  clearQueue() {
1656
2164
  const steering = [...this._steeringMessages];
1657
2165
  const followUp = [...this._followUpMessages];
2166
+ // Clear every queue synchronously. Deferred post-compaction messages are
2167
+ // already represented in visible bookkeeping, so they must not be returned
2168
+ // a second time or later resurrected into Agent's native queues.
1658
2169
  this._steeringMessages = [];
1659
2170
  this._followUpMessages = [];
2171
+ this._postCompactionDeferredSteeringMessages = [];
2172
+ this._postCompactionDeferredFollowUpMessages = [];
1660
2173
  this.agent.clearAllQueues();
1661
2174
  this._emitQueueUpdate();
1662
2175
  return { steering, followUp };
@@ -1680,30 +2193,8 @@ export class AgentSession {
1680
2193
  * Abort current operation and wait for agent to become idle.
1681
2194
  */
1682
2195
  async abort() {
1683
- this.abortRetry();
1684
2196
  this.abortCompaction();
1685
- this.abortBranchSummary();
1686
- if (this._userAbortPromise) {
1687
- this.agent.abort();
1688
- await this._userAbortPromise;
1689
- return;
1690
- }
1691
- if (this.isStreaming) {
1692
- this._suppressQueuedContinuationAfterUserAbort = true;
1693
- }
1694
- const abortPromise = (async () => {
1695
- this.agent.abort();
1696
- await this.waitForIdle();
1697
- })();
1698
- this._userAbortPromise = abortPromise;
1699
- try {
1700
- await abortPromise;
1701
- }
1702
- finally {
1703
- if (this._userAbortPromise === abortPromise) {
1704
- this._userAbortPromise = undefined;
1705
- }
1706
- }
2197
+ await this._abortActiveAgentAndRetry();
1707
2198
  }
1708
2199
  async waitForIdle() {
1709
2200
  if (this.isIdle) {
@@ -1779,6 +2270,7 @@ export class AgentSession {
1779
2270
  persistDefault: updateGlobalDefaults,
1780
2271
  appendSessionEntry: true,
1781
2272
  emitModelSelect: true,
2273
+ modelSelectSource: "set",
1782
2274
  invalidateCompaction: true,
1783
2275
  });
1784
2276
  }
@@ -1817,7 +2309,7 @@ export class AgentSession {
1817
2309
  }
1818
2310
  if (!opts.emitModelSelect)
1819
2311
  return undefined;
1820
- return await this._emitModelSelect(model, previousModel, "set");
2312
+ return await this._emitModelSelect(model, previousModel, opts.modelSelectSource);
1821
2313
  }
1822
2314
  _applyEphemeralThinkingLevel(level) {
1823
2315
  const previousLevel = this.agent.state.thinkingLevel;
@@ -2009,29 +2501,133 @@ export class AgentSession {
2009
2501
  // =========================================================================
2010
2502
  // Compaction
2011
2503
  // =========================================================================
2504
+ _claimCompactionController(controller, owner) {
2505
+ const supersedesCurrentOperation = (this._compactionAbortController !== undefined && this._compactionAbortController !== controller) ||
2506
+ (this._autoCompactionAbortController !== undefined && this._autoCompactionAbortController !== controller);
2507
+ if (supersedesCurrentOperation) {
2508
+ // Supersession has no public terminal event: the stale operation no
2509
+ // longer owns the route. Finishing its lifecycle state here prevents a
2510
+ // missing late feedback end from keeping isCompacting true.
2511
+ this._compactionLifecycle.abort(this._messageRevision);
2512
+ }
2513
+ if (this._compactionAbortController && this._compactionAbortController !== controller) {
2514
+ this._compactionAbortController.abort();
2515
+ this._compactionAbortController = undefined;
2516
+ }
2517
+ if (this._autoCompactionAbortController && this._autoCompactionAbortController !== controller) {
2518
+ this._autoCompactionAbortController.abort();
2519
+ this._autoCompactionAbortController = undefined;
2520
+ }
2521
+ if (owner === "auto") {
2522
+ this._autoCompactionAbortController = controller;
2523
+ }
2524
+ else {
2525
+ this._compactionAbortController = controller;
2526
+ }
2527
+ }
2528
+ _ownsCompactionController(controller, owner) {
2529
+ return (!controller.signal.aborted &&
2530
+ (owner === "auto"
2531
+ ? this._autoCompactionAbortController === controller
2532
+ : this._compactionAbortController === controller));
2533
+ }
2534
+ _releaseCompactionController(signal) {
2535
+ if (this._compactionAbortController?.signal === signal) {
2536
+ this._compactionAbortController = undefined;
2537
+ }
2538
+ if (this._autoCompactionAbortController?.signal === signal) {
2539
+ this._autoCompactionAbortController = undefined;
2540
+ }
2541
+ }
2542
+ _claimPendingCompactionAdmission() {
2543
+ const priorAdmission = this._pendingCompactionAdmission;
2544
+ if (priorAdmission) {
2545
+ priorAdmission.controller.abort();
2546
+ priorAdmission.outcome = "aborted";
2547
+ priorAdmission.finishSessionWork();
2548
+ }
2549
+ const controller = new AbortController();
2550
+ const admission = {
2551
+ controller,
2552
+ finishSessionWork: this._sessionWorkBarrier.begin(),
2553
+ };
2554
+ this._pendingCompactionAdmission = admission;
2555
+ this._claimCompactionController(controller, "compaction");
2556
+ return admission;
2557
+ }
2558
+ _releasePendingCompactionAdmission(admission, outcome) {
2559
+ admission.outcome = outcome;
2560
+ if (this._pendingCompactionAdmission !== admission)
2561
+ return;
2562
+ this._pendingCompactionAdmission = undefined;
2563
+ admission.finishSessionWork();
2564
+ }
2565
+ async _abortActiveAgentAndRetry() {
2566
+ this.abortRetry();
2567
+ this.abortBranchSummary();
2568
+ if (this._userAbortPromise) {
2569
+ this.agent.abort();
2570
+ await this._userAbortPromise;
2571
+ return;
2572
+ }
2573
+ if (this.isStreaming) {
2574
+ this._suppressQueuedContinuationAfterUserAbort = true;
2575
+ }
2576
+ const abortPromise = (async () => {
2577
+ this.agent.abort();
2578
+ await this.waitForIdle();
2579
+ })();
2580
+ this._userAbortPromise = abortPromise;
2581
+ try {
2582
+ await abortPromise;
2583
+ }
2584
+ finally {
2585
+ if (this._userAbortPromise === abortPromise) {
2586
+ this._userAbortPromise = undefined;
2587
+ }
2588
+ }
2589
+ }
2012
2590
  /**
2013
2591
  * Manually compact the session context.
2014
2592
  * Aborts current agent operation first.
2015
2593
  * @param customInstructions Optional instructions for the compaction summary
2016
2594
  */
2017
2595
  async compact(customInstructions) {
2018
- this._disconnectFromAgent();
2019
- await this.abort();
2020
- this._compactionAbortController = new AbortController();
2021
- this._emit({ type: "compaction_start", reason: "manual" });
2596
+ const admission = this._claimPendingCompactionAdmission();
2597
+ const controller = admission.controller;
2598
+ let outcome = "failed";
2599
+ let disconnected = false;
2022
2600
  try {
2023
- const execution = await this._executeCompaction({ reason: "manual", customInstructions, willRetry: false });
2601
+ // Keep the session subscriber attached until the aborted run emits
2602
+ // agent_end. That event clears the active-run and retry state that
2603
+ // waitForIdle() depends on.
2604
+ await this._abortActiveAgentAndRetry();
2605
+ this._disconnectFromAgent();
2606
+ disconnected = true;
2607
+ this._emit({ type: "compaction_start", reason: "manual" });
2608
+ const execution = await this._executeCompaction({
2609
+ controller,
2610
+ owner: "compaction",
2611
+ reason: "manual",
2612
+ customInstructions,
2613
+ willRetry: false,
2614
+ });
2024
2615
  if (!execution.accepted) {
2025
2616
  throw new CompactionRejectedError(execution.rejectionCause);
2026
2617
  }
2618
+ outcome = "completed";
2027
2619
  return execution.result;
2028
2620
  }
2029
2621
  catch (error) {
2622
+ outcome = isCompactionExecutionAborted(error) ? "aborted" : "failed";
2030
2623
  if (error instanceof CompactionRejectedError) {
2031
2624
  throw new Error(error.message);
2032
2625
  }
2626
+ if (!compactionExecutionOwnsTerminalTransition(error)) {
2627
+ throw error;
2628
+ }
2033
2629
  const message = error instanceof Error ? error.message : String(error);
2034
- const aborted = message === "Compaction cancelled" || (error instanceof Error && error.name === "AbortError");
2630
+ const aborted = isCompactionExecutionAborted(error);
2035
2631
  this._emit({
2036
2632
  type: "compaction_end",
2037
2633
  reason: "manual",
@@ -2043,21 +2639,34 @@ export class AgentSession {
2043
2639
  throw error;
2044
2640
  }
2045
2641
  finally {
2046
- this._compactionAbortController = undefined;
2047
- this._reconnectToAgent();
2642
+ if (this._compactionAbortController === controller && this._compactionLifecycle.state.status !== "running") {
2643
+ this._compactionAbortController = undefined;
2644
+ }
2645
+ this._releasePendingCompactionAdmission(admission, outcome);
2646
+ if (disconnected && !this.isCompacting)
2647
+ this._reconnectToAgent();
2048
2648
  }
2049
2649
  }
2050
2650
  async applyCompaction(precomputed, options) {
2651
+ if (options.signal !== undefined && options.signal !== this._compactionAbortController?.signal) {
2652
+ return { applied: false, reason: "stale" };
2653
+ }
2051
2654
  if (options.expectedRevision !== undefined && options.expectedRevision !== this._messageRevision) {
2052
2655
  return { applied: false, reason: "stale" };
2053
2656
  }
2054
2657
  const ownsController = this._compactionAbortController === undefined;
2055
2658
  if (ownsController) {
2056
- this._compactionAbortController = new AbortController();
2659
+ this._claimCompactionController(new AbortController(), "compaction");
2057
2660
  this._emit({ type: "compaction_start", reason: options.reason });
2058
2661
  }
2662
+ const controller = this._compactionAbortController;
2663
+ if (!controller)
2664
+ return { applied: false, reason: "rejected" };
2665
+ this._claimCompactionController(controller, "compaction");
2059
2666
  try {
2060
2667
  const execution = await this._executeCompaction({
2668
+ controller,
2669
+ owner: "compaction",
2061
2670
  reason: options.reason,
2062
2671
  willRetry: false,
2063
2672
  precomputed,
@@ -2068,8 +2677,11 @@ export class AgentSession {
2068
2677
  return { applied: true, reason: "ok" };
2069
2678
  }
2070
2679
  catch (error) {
2680
+ if (!compactionExecutionOwnsTerminalTransition(error)) {
2681
+ return { applied: false, reason: "rejected" };
2682
+ }
2071
2683
  const message = error instanceof Error ? error.message : String(error);
2072
- const aborted = message === "Compaction cancelled" || (error instanceof Error && error.name === "AbortError");
2684
+ const aborted = isCompactionExecutionAborted(error);
2073
2685
  this._emit({
2074
2686
  type: "compaction_end",
2075
2687
  reason: options.reason,
@@ -2081,18 +2693,27 @@ export class AgentSession {
2081
2693
  return { applied: false, reason: "rejected" };
2082
2694
  }
2083
2695
  finally {
2084
- this._compactionAbortController = undefined;
2696
+ if (this._compactionAbortController === controller && this._compactionLifecycle.state.status !== "running") {
2697
+ this._compactionAbortController = undefined;
2698
+ }
2085
2699
  }
2086
2700
  }
2087
2701
  _beginExtensionCompactionFeedback(reason) {
2088
- if (!this._compactionAbortController) {
2089
- this._compactionAbortController = new AbortController();
2090
- this._emit({ type: "compaction_start", reason });
2091
- }
2092
- return this._compactionAbortController.signal;
2702
+ const controller = new AbortController();
2703
+ this._claimCompactionController(controller, "compaction");
2704
+ const model = this.model;
2705
+ this._compactionLifecycle.begin({
2706
+ operationId: randomUUID(),
2707
+ stage: "feedback",
2708
+ reason,
2709
+ model: model ? { provider: model.provider, id: model.id } : undefined,
2710
+ startedRevision: this._messageRevision,
2711
+ }, controller);
2712
+ this._emit({ type: "compaction_start", reason });
2713
+ return controller.signal;
2093
2714
  }
2094
2715
  _updateExtensionCompactionFeedback(options) {
2095
- if (!this._compactionAbortController && !this._autoCompactionAbortController)
2716
+ if (!options.signal || !this._compactionLifecycle.hasCurrentSignal(options.signal))
2096
2717
  return;
2097
2718
  this._emit({
2098
2719
  type: "compaction_progress",
@@ -2102,10 +2723,24 @@ export class AgentSession {
2102
2723
  });
2103
2724
  }
2104
2725
  _endExtensionCompactionFeedback(options) {
2105
- const controller = this._compactionAbortController;
2106
- if (!controller)
2726
+ if (!options.signal)
2727
+ return;
2728
+ if (!this._compactionLifecycle.hasCurrentSignal(options.signal)) {
2729
+ this._releaseCompactionController(options.signal);
2107
2730
  return;
2108
- const aborted = options.aborted ?? controller.signal.aborted;
2731
+ }
2732
+ const operation = this._compactionLifecycle.state;
2733
+ if (operation.status !== "running" || operation.stage !== "feedback")
2734
+ return;
2735
+ const aborted = options.aborted ?? options.signal.aborted;
2736
+ this._compactionLifecycle.finish({
2737
+ operationId: operation.operationId,
2738
+ status: aborted ? "aborted" : "failed",
2739
+ endedRevision: this._messageRevision,
2740
+ ...(aborted
2741
+ ? { errorMessage: "Compaction cancelled" }
2742
+ : { errorMessage: options.errorMessage ?? "Compaction did not apply" }),
2743
+ });
2109
2744
  this._emit({
2110
2745
  type: "compaction_end",
2111
2746
  reason: options.reason,
@@ -2114,26 +2749,41 @@ export class AgentSession {
2114
2749
  willRetry: false,
2115
2750
  errorMessage: aborted ? undefined : options.errorMessage,
2116
2751
  });
2117
- this._compactionAbortController = undefined;
2752
+ this._releaseCompactionController(options.signal);
2118
2753
  }
2119
2754
  async _executeCompaction(request) {
2755
+ const model = this.model;
2756
+ if (!model)
2757
+ throw new Error(formatNoModelSelectedMessage());
2758
+ const thinkingLevel = this.thinkingLevel;
2759
+ const controller = request.controller;
2760
+ // Async auth and provider preparation may yield to a newer route. A stale
2761
+ // controller must never promote itself into a lifecycle generation.
2762
+ if (!this._ownsCompactionController(controller, request.owner)) {
2763
+ throw new CompactionExecutionError(new CompactionCancelledError(), false, true);
2764
+ }
2765
+ const requestId = randomUUID();
2766
+ const operationId = this._compactionLifecycle.begin({
2767
+ operationId: requestId,
2768
+ stage: "execution",
2769
+ reason: request.reason,
2770
+ model: { provider: model.provider, id: model.id },
2771
+ startedRevision: this._messageRevision,
2772
+ }, controller);
2120
2773
  const finishCompactionWork = this._sessionWorkBarrier.begin();
2121
2774
  const agentMessagesAtStart = request.agentMessagesAtStart ?? this.agent.state.messages.slice();
2775
+ const compactionOwnedDiagnosticMessages = new Set();
2776
+ const signal = controller.signal;
2122
2777
  try {
2123
- if (!this.model) {
2124
- throw new Error(formatNoModelSelectedMessage());
2778
+ if (signal.aborted) {
2779
+ throw new CompactionCancelledError();
2125
2780
  }
2126
- const requestId = randomUUID();
2127
2781
  const pathEntries = this.sessionManager.getBranch();
2128
2782
  const settings = this.settingsManager.getCompactionSettings();
2129
- const signal = this._compactionAbortController?.signal ?? this._autoCompactionAbortController?.signal;
2130
- if (!signal) {
2131
- throw new Error("Compaction abort controller unavailable");
2132
- }
2133
2783
  let compactionResult = request.precomputed;
2134
2784
  let fromExtension = request.precomputed !== undefined;
2135
2785
  if (!compactionResult) {
2136
- const preparation = prepareCompaction(pathEntries, settings);
2786
+ const preparation = prepareCompaction(pathEntries, settings, request.reason === "overflow", request.allowSummaryOnly);
2137
2787
  if (!preparation) {
2138
2788
  const lastEntry = pathEntries[pathEntries.length - 1];
2139
2789
  if (lastEntry?.type === "compaction") {
@@ -2142,6 +2792,7 @@ export class AgentSession {
2142
2792
  throw new Error("Nothing to compact (session too small)");
2143
2793
  }
2144
2794
  if (this._extensionRunner.hasHandlers("session_before_compact")) {
2795
+ const messagesBeforeExtension = new Set(this.agent.state.messages);
2145
2796
  const extensionResult = (await this._extensionRunner.emit({
2146
2797
  type: "session_before_compact",
2147
2798
  reason: request.reason,
@@ -2152,8 +2803,17 @@ export class AgentSession {
2152
2803
  customInstructions: request.customInstructions,
2153
2804
  signal,
2154
2805
  }));
2806
+ for (const message of this.agent.state.messages) {
2807
+ if (!messagesBeforeExtension.has(message) &&
2808
+ isCompactionOwnedPreCompactDiagnostic(message, requestId)) {
2809
+ compactionOwnedDiagnosticMessages.add(message);
2810
+ }
2811
+ }
2812
+ if (!this._compactionLifecycle.isCurrent(operationId, controller)) {
2813
+ throw new CompactionCancelledError();
2814
+ }
2155
2815
  if (extensionResult?.cancel) {
2156
- return await this._rejectCompaction(request, requestId, extensionResult.rejectionCause ?? "cancelled-by-extension", true, extensionResult.reason);
2816
+ return await this._rejectCompaction(request, requestId, operationId, extensionResult.rejectionCause ?? "cancelled-by-extension", true, extensionResult.reason);
2157
2817
  }
2158
2818
  if (extensionResult?.compaction) {
2159
2819
  compactionResult = extensionResult.compaction;
@@ -2161,17 +2821,38 @@ export class AgentSession {
2161
2821
  }
2162
2822
  }
2163
2823
  if (!compactionResult) {
2164
- const { apiKey, headers, extraBody, env } = await this._getCompactionRequestAuth(this.model);
2165
- compactionResult = await compact(preparation, this.model, apiKey, headers, request.customInstructions, signal, extraBody, this.thinkingLevel, this.agent.streamFn, env, this.agent.transformContext);
2824
+ const { apiKey, headers, extraBody, env } = await this._getCompactionRequestAuth(model);
2825
+ compactionResult = await compact(preparation, model, apiKey, headers, request.customInstructions, signal, extraBody, thinkingLevel, this.agent.streamFunction, env, this.agent.transformContext, this.settingsManager.getRetrySettings(), this._summarizationRetryCallbacks({ source: "compaction", reason: request.reason }));
2166
2826
  }
2167
2827
  }
2168
2828
  if (signal.aborted) {
2169
- throw new Error("Compaction cancelled");
2829
+ throw new CompactionCancelledError();
2830
+ }
2831
+ if (!this._compactionLifecycle.isCurrent(operationId, controller)) {
2832
+ throw new CompactionCancelledError();
2170
2833
  }
2171
- if (this._wouldCompactionOverflow(pathEntries, compactionResult, fromExtension)) {
2172
- return await this._rejectCompaction(request, requestId, "would-overflow", false);
2834
+ const lifecycleState = this._compactionLifecycle.state;
2835
+ const currentMessagesAtCheck = this.agent.state.messages;
2836
+ const startPrefixIntact = agentMessagesAtStart.every((message, index) => currentMessagesAtCheck[index] === message);
2837
+ // Appends after the start snapshot are fresh only while they are exact
2838
+ // message_end identities still awaiting persistence. Any revision change,
2839
+ // other append, replacement, or reorder still makes this compaction stale.
2840
+ const onlyPendingPersistenceAppends = startPrefixIntact &&
2841
+ currentMessagesAtCheck
2842
+ .slice(agentMessagesAtStart.length)
2843
+ .every((message) => this._messageEndsAwaitingPersistence.has(message) ||
2844
+ compactionOwnedDiagnosticMessages.has(message));
2845
+ const sourceChanged = lifecycleState.status !== "running" ||
2846
+ lifecycleState.operationId !== operationId ||
2847
+ lifecycleState.startedRevision + compactionOwnedDiagnosticMessages.size !== this._messageRevision ||
2848
+ !onlyPendingPersistenceAppends;
2849
+ if (sourceChanged) {
2850
+ return await this._rejectCompaction(request, requestId, operationId, "stale-revision", false);
2173
2851
  }
2174
- const compactionEntryId = this.sessionManager.appendCompaction(compactionResult.summary, compactionResult.firstKeptEntryId, compactionResult.tokensBefore, compactionResult.details, fromExtension);
2852
+ if (this._wouldCompactionOverflow(pathEntries, compactionResult, fromExtension, model)) {
2853
+ return await this._rejectCompaction(request, requestId, operationId, "would-overflow", false);
2854
+ }
2855
+ const compactionEntryId = this.sessionManager.appendCompaction(compactionResult.summary, compactionResult.firstKeptEntryId, compactionResult.tokensBefore, compactionResult.details, fromExtension, compactionResult.usage);
2175
2856
  const savedEntry = this.sessionManager.getEntry(compactionEntryId);
2176
2857
  if (savedEntry?.type !== "compaction") {
2177
2858
  throw new Error("Compaction entry was not saved");
@@ -2182,18 +2863,33 @@ export class AgentSession {
2182
2863
  const messagesAppendedDuringCompaction = hasUnchangedPrefix
2183
2864
  ? currentAgentMessages.slice(agentMessagesAtStart.length)
2184
2865
  : [];
2185
- this.agent.state.messages = [...sessionContext.messages, ...messagesAppendedDuringCompaction];
2866
+ // Preserve an identity-deduped, agent-ordered union of the append-during-
2867
+ // compaction suffix and exact messages still awaiting persistence. Their
2868
+ // queued message_end owns exactly-once persistence, so they are kept in
2869
+ // agent state only and never persisted here.
2870
+ const preservedIdentities = new Set(messagesAppendedDuringCompaction);
2871
+ for (const message of currentAgentMessages) {
2872
+ if (this._messageEndsAwaitingPersistence.has(message)) {
2873
+ preservedIdentities.add(message);
2874
+ }
2875
+ }
2876
+ const preservedPendingMessages = currentAgentMessages.filter((message) => preservedIdentities.delete(message));
2877
+ this._skipNextPostCompactionAssistantCheck = true;
2878
+ for (const message of preservedPendingMessages) {
2879
+ if (message.role === "assistant") {
2880
+ this._assistantsPendingAtCompaction.add(message);
2881
+ }
2882
+ }
2883
+ this.agent.state.messages = [...sessionContext.messages, ...preservedPendingMessages];
2186
2884
  compactionResult.estimatedTokensAfter = estimateMessagesTokens(sessionContext.messages);
2187
2885
  this._incrementMessageRevision();
2188
- await this._extensionRunner.emit({
2189
- type: "session_compact",
2190
- reason: request.reason,
2191
- requestId,
2192
- accepted: true,
2193
- compactionEntry: savedEntry,
2194
- fromExtension,
2195
- willRetry: request.willRetry,
2196
- });
2886
+ if (!this._compactionLifecycle.finish({
2887
+ operationId,
2888
+ status: "completed",
2889
+ endedRevision: this._messageRevision,
2890
+ })) {
2891
+ throw new CompactionCancelledError();
2892
+ }
2197
2893
  this._emit({
2198
2894
  type: "compaction_end",
2199
2895
  reason: request.reason,
@@ -2203,15 +2899,41 @@ export class AgentSession {
2203
2899
  requestId,
2204
2900
  accepted: true,
2205
2901
  });
2902
+ await this._extensionRunner.emit({
2903
+ type: "session_compact",
2904
+ reason: request.reason,
2905
+ requestId,
2906
+ accepted: true,
2907
+ compactionEntry: savedEntry,
2908
+ fromExtension,
2909
+ willRetry: request.willRetry,
2910
+ });
2206
2911
  return { accepted: true, requestId, result: compactionResult, compactionEntry: savedEntry, fromExtension };
2207
2912
  }
2913
+ catch (error) {
2914
+ if (error instanceof CompactionExecutionError) {
2915
+ throw error;
2916
+ }
2917
+ const lifecycleState = this._compactionLifecycle.state;
2918
+ const ownsTerminalTransition = lifecycleState.status !== "idle" && lifecycleState.operationId === operationId;
2919
+ const aborted = signal.aborted || isCompactionExecutionAborted(error);
2920
+ if (ownsTerminalTransition && lifecycleState.status === "running") {
2921
+ this._compactionLifecycle.finish({
2922
+ operationId,
2923
+ status: aborted ? "aborted" : "failed",
2924
+ endedRevision: this._messageRevision,
2925
+ errorMessage: error instanceof Error ? error.message : String(error),
2926
+ });
2927
+ }
2928
+ throw new CompactionExecutionError(error, ownsTerminalTransition, aborted);
2929
+ }
2208
2930
  finally {
2209
2931
  finishCompactionWork();
2210
2932
  }
2211
2933
  }
2212
- _wouldCompactionOverflow(pathEntries, compactionResult, fromExtension) {
2934
+ _wouldCompactionOverflow(pathEntries, compactionResult, fromExtension, model) {
2213
2935
  const currentLeaf = pathEntries[pathEntries.length - 1];
2214
- if (!currentLeaf || !this.model)
2936
+ if (!currentLeaf)
2215
2937
  return false;
2216
2938
  const simulatedCompactionEntry = {
2217
2939
  type: "compaction",
@@ -2227,9 +2949,28 @@ export class AgentSession {
2227
2949
  const simulatedMessages = buildSessionContext([...pathEntries, simulatedCompactionEntry], simulatedCompactionEntry.id).messages;
2228
2950
  const contextTokens = estimateMessagesTokens(filterContextExcludedMessages(simulatedMessages));
2229
2951
  const settings = this.settingsManager.getCompactionSettings();
2230
- return contextTokens > this.model.contextWindow - settings.reserveTokens;
2952
+ return contextTokens > model.contextWindow - settings.reserveTokens;
2953
+ }
2954
+ /**
2955
+ * Replaces agent state with the canonical session context while keeping exact
2956
+ * message objects whose message_end persistence is still queued. Identities
2957
+ * already present in the session context are kept from the context only, so
2958
+ * nothing is duplicated; the queued message_end still owns exactly-once
2959
+ * persistence for the rest.
2960
+ */
2961
+ _restoreAgentMessagesFromSession() {
2962
+ const sessionMessages = this.sessionManager.buildSessionContext().messages;
2963
+ const seen = new Set(sessionMessages);
2964
+ const pendingMessages = [];
2965
+ for (const message of this.agent.state.messages) {
2966
+ if (seen.has(message) || !this._messageEndsAwaitingPersistence.has(message))
2967
+ continue;
2968
+ seen.add(message);
2969
+ pendingMessages.push(message);
2970
+ }
2971
+ this.agent.state.messages = [...sessionMessages, ...pendingMessages];
2231
2972
  }
2232
- async _rejectCompaction(request, requestId, rejectionCause, aborted, extensionReason) {
2973
+ async _rejectCompaction(request, requestId, operationId, rejectionCause, aborted, extensionReason) {
2233
2974
  // Per plan Section 1: rejection must never be silent. The compaction_end event
2234
2975
  // carries a non-empty human-readable errorMessage (unless the user aborted, where
2235
2976
  // the aborted branch already renders "Compaction cancelled"). session_compact is
@@ -2241,6 +2982,15 @@ export class AgentSession {
2241
2982
  ? `Compaction rejected: ${trimmedExtensionReason}`
2242
2983
  : describeCompactionRejection(rejectionCause);
2243
2984
  const errorMessage = aborted && !trimmedExtensionReason ? undefined : detailedMessage;
2985
+ if (!this._compactionLifecycle.finish({
2986
+ operationId,
2987
+ status: "failed",
2988
+ endedRevision: this._messageRevision,
2989
+ rejectionCause,
2990
+ ...(trimmedExtensionReason !== undefined ? { errorMessage: trimmedExtensionReason } : {}),
2991
+ })) {
2992
+ throw new CompactionCancelledError();
2993
+ }
2244
2994
  this._emit({
2245
2995
  type: "compaction_end",
2246
2996
  reason: request.reason,
@@ -2267,8 +3017,19 @@ export class AgentSession {
2267
3017
  * Cancel in-progress compaction (manual or auto).
2268
3018
  */
2269
3019
  abortCompaction() {
3020
+ const feedbackOperation = this._compactionLifecycle.abort(this._messageRevision);
2270
3021
  this._compactionAbortController?.abort();
2271
3022
  this._autoCompactionAbortController?.abort();
3023
+ if (feedbackOperation?.stage === "feedback") {
3024
+ this._compactionAbortController = undefined;
3025
+ this._emit({
3026
+ type: "compaction_end",
3027
+ reason: feedbackOperation.reason,
3028
+ result: undefined,
3029
+ aborted: true,
3030
+ willRetry: false,
3031
+ });
3032
+ }
2272
3033
  }
2273
3034
  /**
2274
3035
  * Cancel in-progress branch summarization.
@@ -2287,7 +3048,80 @@ export class AgentSession {
2287
3048
  * @param assistantMessage The assistant message to check
2288
3049
  * @param skipAbortedCheck If false, include aborted messages (for pre-prompt check). Default: true
2289
3050
  */
2290
- async _checkCompaction(assistantMessage, skipAbortedCheck = true, inlineReason) {
3051
+ async _enforceCompactionBeforeProvider(assistantMessage, skipAbortedCheck, inlineReason, retryAfterCompaction = false) {
3052
+ const blockedAdmission = this._blockedPostCompactionAssistant;
3053
+ if (blockedAdmission !== undefined &&
3054
+ blockedAdmission.assistant === assistantMessage &&
3055
+ blockedAdmission.revision === this._messageRevision) {
3056
+ throw new RequiredCompactionError();
3057
+ }
3058
+ const settings = this.settingsManager.getCompactionSettings();
3059
+ const model = this.model;
3060
+ const contextTokens = estimateContextTokens(filterContextExcludedMessages(this.sessionManager.buildSessionContext().messages)).tokens;
3061
+ const compacted = assistantMessage
3062
+ ? await this._checkCompaction(assistantMessage, skipAbortedCheck, inlineReason, retryAfterCompaction)
3063
+ : false;
3064
+ if (compacted || (assistantMessage && this._postCompactionUsageExemptAssistants.has(assistantMessage))) {
3065
+ return compacted;
3066
+ }
3067
+ if (!settings.enabled || !model || !shouldCompact(contextTokens, model.contextWindow, settings)) {
3068
+ return false;
3069
+ }
3070
+ const latestCompaction = getLatestCompactionEntry(this.sessionManager.getBranch());
3071
+ const assistantBeforeLatestCompaction = assistantMessage !== undefined && this._isAssistantFromBeforeLatestCompaction(assistantMessage);
3072
+ const hasPostCompactionCustomState = latestCompaction !== null &&
3073
+ this.agent.state.messages.some((message) => message.role === "custom" && message.timestamp >= new Date(latestCompaction.timestamp).getTime());
3074
+ if (assistantBeforeLatestCompaction && !hasPostCompactionCustomState) {
3075
+ return false;
3076
+ }
3077
+ if (assistantBeforeLatestCompaction && assistantMessage) {
3078
+ const compacted = await this._runPrePromptCompaction(assistantMessage, skipAbortedCheck, inlineReason);
3079
+ if (compacted)
3080
+ return true;
3081
+ }
3082
+ throw new RequiredCompactionError();
3083
+ }
3084
+ /**
3085
+ * The normal pre-prompt check only estimates persisted session context. This
3086
+ * final gate also includes turn-local messages which the provider will see:
3087
+ * the current prompt, next-turn custom messages, and before_agent_start
3088
+ * additions. Compaction rewrites only session context, so callers retain and
3089
+ * reapply their already-assembled one-shot additions after it succeeds.
3090
+ */
3091
+ async _enforceFinalProviderAdmission(messages) {
3092
+ // Normal user-only prompts keep the established provider-overflow recovery
3093
+ // path. This final gate closes the separate gap introduced by turn-local
3094
+ // custom additions, which the normal pre-prompt check cannot observe.
3095
+ if (!messages.some((message) => message.role === "custom"))
3096
+ return;
3097
+ const model = this.model;
3098
+ if (!model)
3099
+ return;
3100
+ const settings = this.settingsManager.getCompactionSettings();
3101
+ const isOversized = () => {
3102
+ const providerMessages = filterContextExcludedMessages([...this.agent.state.messages, ...messages]);
3103
+ const estimate = estimateContextTokens(providerMessages);
3104
+ const usageMessage = estimate.lastUsageIndex === null ? undefined : providerMessages[estimate.lastUsageIndex];
3105
+ // Kept assistant usage can describe the pre-compaction request. Once a
3106
+ // compaction boundary exists, fall back to byte-derived message estimates
3107
+ // until a provider response refreshes that usage.
3108
+ const contextTokens = usageMessage?.role === "assistant" && this._isAssistantFromBeforeLatestCompaction(usageMessage)
3109
+ ? estimateMessagesTokens(providerMessages)
3110
+ : estimate.tokens;
3111
+ return contextTokens > model.contextWindow - settings.reserveTokens;
3112
+ };
3113
+ if (!settings.enabled || !isOversized())
3114
+ return;
3115
+ const lastAssistantMessage = this._findLastAssistantMessage();
3116
+ if (!lastAssistantMessage) {
3117
+ throw new RequiredCompactionError();
3118
+ }
3119
+ const compacted = await this._runPrePromptCompaction(lastAssistantMessage, false, "pre_prompt");
3120
+ if (!compacted || isOversized()) {
3121
+ throw new RequiredCompactionError();
3122
+ }
3123
+ }
3124
+ async _checkCompaction(assistantMessage, skipAbortedCheck = true, inlineReason, retryAfterCompaction = false) {
2291
3125
  const settings = this.settingsManager.getCompactionSettings();
2292
3126
  if (!settings.enabled)
2293
3127
  return false;
@@ -2315,10 +3149,26 @@ export class AgentSession {
2315
3149
  const currentContextNeedsCompaction = contextUsage !== undefined &&
2316
3150
  contextUsage.tokens !== null &&
2317
3151
  shouldCompact(contextUsage.tokens, contextUsage.contextWindow, settings);
2318
- if (isContextOverflow(assistantMessage, contextWindow) && (sameModel || currentContextNeedsCompaction)) {
2319
- const willRetry = assistantMessage.stopReason !== "stop";
3152
+ const isOverflow = isContextOverflow(assistantMessage, contextWindow) && (sameModel || currentContextNeedsCompaction);
3153
+ if (isOverflow &&
3154
+ assistantMessage.stopReason === "stop" &&
3155
+ this._consumePostCompactionUsageExemption(assistantMessage)) {
3156
+ return false;
3157
+ }
3158
+ if (isOverflow) {
3159
+ this._flushPostCompactionDeferredMessages();
3160
+ const willRetry = retryAfterCompaction || assistantMessage.stopReason !== "stop";
2320
3161
  if (!willRetry) {
2321
- return await this._runAutoCompaction("overflow", false);
3162
+ const compacted = await this._runAutoCompaction("overflow", false);
3163
+ if (!compacted &&
3164
+ this._compactionLifecycle.state.status === "failed" &&
3165
+ getLatestCompactionEntry(this.sessionManager.getBranch()) !== null) {
3166
+ this._blockedPostCompactionAssistant = {
3167
+ assistant: assistantMessage,
3168
+ revision: this._messageRevision,
3169
+ };
3170
+ }
3171
+ return compacted;
2322
3172
  }
2323
3173
  if (this._overflowRecoveryAttempted) {
2324
3174
  const errorMessage = "Context overflow recovery failed after one compact-and-retry attempt. Try reducing context or switching to a larger-context model.";
@@ -2339,17 +3189,29 @@ export class AgentSession {
2339
3189
  // Remove the error message from agent state (it IS saved to session for history,
2340
3190
  // but we don't want it in context for the retry)
2341
3191
  const messages = this.agent.state.messages;
3192
+ let removedOverflowAssistant = false;
2342
3193
  if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
2343
3194
  this.agent.state.messages = messages.slice(0, -1);
3195
+ removedOverflowAssistant = true;
2344
3196
  this._incrementMessageRevision();
2345
3197
  }
2346
- if (inlineReason) {
2347
- return await this._runPrePromptCompaction(assistantMessage, skipAbortedCheck, "overflow", willRetry);
3198
+ const compacted = inlineReason
3199
+ ? await this._runPrePromptCompaction(assistantMessage, skipAbortedCheck, "overflow", willRetry)
3200
+ : await this._runAutoCompaction("overflow", willRetry);
3201
+ if (!compacted && removedOverflowAssistant) {
3202
+ this._restoreAgentMessagesFromSession();
3203
+ this._incrementMessageRevision();
2348
3204
  }
2349
- else {
2350
- return await this._runAutoCompaction("overflow", willRetry);
3205
+ if (!compacted && inlineReason) {
3206
+ throw new RequiredCompactionError();
2351
3207
  }
3208
+ return compacted;
2352
3209
  }
3210
+ // The first ordinary response can carry provider usage calculated before
3211
+ // compaction. Consume that exemption only after proving this is not an
3212
+ // overflow, then retain the decision for later admission re-sampling.
3213
+ if (this._consumePostCompactionUsageExemption(assistantMessage))
3214
+ return false;
2353
3215
  // Case 2: Threshold - context is getting large
2354
3216
  // For error messages or all-zero usage messages, estimate from the last valid response.
2355
3217
  // This ensures sessions that hit persistent API errors (e.g. 529) or malformed zero-usage
@@ -2375,7 +3237,7 @@ export class AgentSession {
2375
3237
  const usageMsg = messages[estimate.lastUsageIndex];
2376
3238
  if (compactionEntry &&
2377
3239
  usageMsg.role === "assistant" &&
2378
- usageMsg.timestamp <= new Date(compactionEntry.timestamp).getTime()) {
3240
+ this._isAssistantFromBeforeLatestCompaction(usageMsg)) {
2379
3241
  return false;
2380
3242
  }
2381
3243
  contextTokens = estimate.tokens;
@@ -2383,35 +3245,53 @@ export class AgentSession {
2383
3245
  }
2384
3246
  if (shouldCompact(contextTokens, contextWindow, settings)) {
2385
3247
  if (inlineReason) {
2386
- return await this._runPrePromptCompaction(assistantMessage, skipAbortedCheck, inlineReason);
3248
+ return await this._runPrePromptCompaction(assistantMessage, skipAbortedCheck, inlineReason, retryAfterCompaction);
2387
3249
  }
2388
3250
  else {
2389
- return await this._runAutoCompaction("threshold", false);
3251
+ const compacted = await this._runAutoCompaction("threshold", false);
3252
+ if (!compacted &&
3253
+ this._compactionLifecycle.state.status === "failed" &&
3254
+ getLatestCompactionEntry(this.sessionManager.getBranch()) !== null) {
3255
+ this._blockedPostCompactionAssistant = {
3256
+ assistant: assistantMessage,
3257
+ revision: this._messageRevision,
3258
+ };
3259
+ }
3260
+ return compacted;
2390
3261
  }
2391
3262
  }
2392
3263
  return false;
2393
3264
  }
2394
- async _runPrePromptCompaction(lastAssistantMessage, skipAbortedCheck, reason = "pre_prompt", willRetry = false) {
3265
+ async _runPrePromptCompaction(lastAssistantMessage, skipAbortedCheck, reason = "pre_prompt", willRetry = false, allowSummaryOnly = false) {
3266
+ const controller = new AbortController();
3267
+ this._claimCompactionController(controller, "compaction");
2395
3268
  this._emit({ type: "compaction_start", reason });
2396
- this._compactionAbortController = new AbortController();
2397
3269
  try {
2398
3270
  const execution = await this._executeCompaction({
3271
+ controller,
3272
+ owner: "compaction",
2399
3273
  reason,
2400
3274
  willRetry,
2401
3275
  lastAssistantMessage,
2402
3276
  skipAbortedCheck,
3277
+ allowSummaryOnly,
2403
3278
  });
2404
- if (!execution.accepted && isContextOverflow(lastAssistantMessage, this.model?.contextWindow ?? 0)) {
3279
+ if (!execution.accepted &&
3280
+ lastAssistantMessage &&
3281
+ isContextOverflow(lastAssistantMessage, this.model?.contextWindow ?? 0)) {
2405
3282
  this._overflowRecoveryAttempted = false;
2406
3283
  }
2407
3284
  return execution.accepted;
2408
3285
  }
2409
3286
  catch (error) {
2410
- if (isContextOverflow(lastAssistantMessage, this.model?.contextWindow ?? 0)) {
3287
+ if (!compactionExecutionOwnsTerminalTransition(error)) {
3288
+ return false;
3289
+ }
3290
+ if (lastAssistantMessage && isContextOverflow(lastAssistantMessage, this.model?.contextWindow ?? 0)) {
2411
3291
  this._overflowRecoveryAttempted = false;
2412
3292
  }
2413
3293
  const errorMessage = error instanceof Error ? error.message : "compaction failed";
2414
- const aborted = errorMessage === "Compaction cancelled" || (error instanceof Error && error.name === "AbortError");
3294
+ const aborted = isCompactionExecutionAborted(error);
2415
3295
  this._emit({
2416
3296
  type: "compaction_end",
2417
3297
  reason,
@@ -2423,12 +3303,44 @@ export class AgentSession {
2423
3303
  return false;
2424
3304
  }
2425
3305
  finally {
2426
- this._compactionAbortController = undefined;
3306
+ if (this._compactionAbortController === controller && this._compactionLifecycle.state.status !== "running") {
3307
+ this._compactionAbortController = undefined;
3308
+ }
2427
3309
  }
2428
3310
  }
3311
+ async _revalidateScheduledContinuationAdmission() {
3312
+ const model = this.model;
3313
+ const settings = this.settingsManager.getCompactionSettings();
3314
+ if (!model || !settings.enabled)
3315
+ return;
3316
+ const canonicalMessages = filterContextExcludedMessages(this.sessionManager.buildSessionContext().messages);
3317
+ const estimate = estimateContextTokens(canonicalMessages);
3318
+ const usageMessage = estimate.lastUsageIndex === null ? undefined : canonicalMessages[estimate.lastUsageIndex];
3319
+ const contextTokens = usageMessage?.role === "assistant" && this._isAssistantFromBeforeLatestCompaction(usageMessage)
3320
+ ? estimateMessagesTokens(canonicalMessages)
3321
+ : estimate.tokens;
3322
+ if (!shouldCompact(contextTokens, model.contextWindow, settings))
3323
+ return;
3324
+ const compacted = await this._runPrePromptCompaction(this._findLastAssistantMessage(), true, "pre_prompt");
3325
+ if (!compacted)
3326
+ throw new RequiredCompactionError();
3327
+ this._scheduledContinuationRecompacted = true;
3328
+ }
2429
3329
  async _continueAgentAfterCurrentRun() {
2430
3330
  await this.agent.waitForIdle();
2431
- await this.agent.continue();
3331
+ await this._revalidateScheduledContinuationAdmission();
3332
+ const useQueuedContinuation = this._scheduledContinuationRecompacted;
3333
+ try {
3334
+ if (useQueuedContinuation) {
3335
+ await this.agent.continueWithQueuedMessages();
3336
+ }
3337
+ else {
3338
+ await this.agent.continue();
3339
+ }
3340
+ }
3341
+ finally {
3342
+ this._scheduledContinuationRecompacted = false;
3343
+ }
2432
3344
  }
2433
3345
  _scheduleContinuationAfterCurrentEvent() {
2434
3346
  // Tool hooks wait for queued message persistence, so continue() cannot run inside this event promise.
@@ -2458,49 +3370,53 @@ export class AgentSession {
2458
3370
  async _runAutoCompaction(reason, willRetry) {
2459
3371
  const finishCompactionWork = this._sessionWorkBarrier.begin();
2460
3372
  const agentMessagesAtStart = this.agent.state.messages.slice();
2461
- this._emit({ type: "compaction_start", reason });
2462
3373
  const autoCompactionController = new AbortController();
2463
- this._autoCompactionAbortController = autoCompactionController;
3374
+ this._claimCompactionController(autoCompactionController, "auto");
3375
+ const endBeforeExecution = () => {
3376
+ this._emit({ type: "compaction_start", reason });
3377
+ if (reason === "overflow")
3378
+ this._overflowRecoveryAttempted = false;
3379
+ this._emit({
3380
+ type: "compaction_end",
3381
+ reason,
3382
+ result: undefined,
3383
+ aborted: false,
3384
+ willRetry: false,
3385
+ });
3386
+ return false;
3387
+ };
2464
3388
  try {
2465
3389
  if (!this.model) {
2466
- if (reason === "overflow")
2467
- this._overflowRecoveryAttempted = false;
2468
- this._emit({
2469
- type: "compaction_end",
2470
- reason,
2471
- result: undefined,
2472
- aborted: false,
2473
- willRetry: false,
2474
- });
2475
- return false;
3390
+ return endBeforeExecution();
2476
3391
  }
2477
- const authResult = await this._modelRuntime.getAuth(this.model);
2478
- if (this.agent.streamFn === streamSimple && !authResult?.auth.apiKey) {
2479
- if (reason === "overflow")
2480
- this._overflowRecoveryAttempted = false;
2481
- this._emit({
2482
- type: "compaction_end",
2483
- reason,
2484
- result: undefined,
2485
- aborted: false,
2486
- willRetry: false,
2487
- });
2488
- return false;
3392
+ try {
3393
+ // Resolve once before admission so a pending auth refresh remains a
3394
+ // cancellable boundary. _executeCompaction resolves the policy-specific
3395
+ // auth after extension compaction hooks have had a chance to provide a
3396
+ // summary without credentials.
3397
+ await this._modelRuntime.getAuth(this.model);
2489
3398
  }
2490
- const preparation = prepareCompaction(this.sessionManager.getBranch(), this.settingsManager.getCompactionSettings());
2491
- if (!preparation) {
2492
- if (reason === "overflow")
2493
- this._overflowRecoveryAttempted = false;
2494
- this._emit({
2495
- type: "compaction_end",
2496
- reason,
2497
- result: undefined,
2498
- aborted: false,
2499
- willRetry: false,
2500
- });
3399
+ catch {
3400
+ if (!this._ownsCompactionController(autoCompactionController, "auto"))
3401
+ return false;
3402
+ return endBeforeExecution();
3403
+ }
3404
+ if (!this._ownsCompactionController(autoCompactionController, "auto"))
2501
3405
  return false;
3406
+ const preparation = prepareCompaction(this.sessionManager.getBranch(), this.settingsManager.getCompactionSettings(), reason === "overflow");
3407
+ if (!preparation) {
3408
+ return endBeforeExecution();
2502
3409
  }
2503
- const execution = await this._executeCompaction({ reason, willRetry, agentMessagesAtStart });
3410
+ if (!this._ownsCompactionController(autoCompactionController, "auto"))
3411
+ return false;
3412
+ this._emit({ type: "compaction_start", reason });
3413
+ const execution = await this._executeCompaction({
3414
+ controller: autoCompactionController,
3415
+ owner: "auto",
3416
+ reason,
3417
+ willRetry,
3418
+ agentMessagesAtStart,
3419
+ });
2504
3420
  if (!execution.accepted) {
2505
3421
  if (reason === "overflow")
2506
3422
  this._overflowRecoveryAttempted = false;
@@ -2530,10 +3446,13 @@ export class AgentSession {
2530
3446
  return false;
2531
3447
  }
2532
3448
  catch (error) {
3449
+ if (!compactionExecutionOwnsTerminalTransition(error)) {
3450
+ return false;
3451
+ }
2533
3452
  if (reason === "overflow")
2534
3453
  this._overflowRecoveryAttempted = false;
2535
3454
  const errorMessage = error instanceof Error ? error.message : "compaction failed";
2536
- const aborted = errorMessage === "Compaction cancelled" || (error instanceof Error && error.name === "AbortError");
3455
+ const aborted = isCompactionExecutionAborted(error);
2537
3456
  this._emit({
2538
3457
  type: "compaction_end",
2539
3458
  reason,
@@ -2566,27 +3485,39 @@ export class AgentSession {
2566
3485
  return this.settingsManager.getCompactionEnabled();
2567
3486
  }
2568
3487
  async bindExtensions(bindings) {
2569
- if (bindings.uiContext !== undefined) {
2570
- this._extensionUIContext = bindings.uiContext;
2571
- }
2572
- if (bindings.mode !== undefined) {
2573
- this._extensionMode = bindings.mode;
2574
- }
2575
- if (bindings.commandContextActions !== undefined) {
2576
- this._extensionCommandContextActions = bindings.commandContextActions;
2577
- }
2578
- if (bindings.abortHandler !== undefined) {
2579
- this._extensionAbortHandler = bindings.abortHandler;
2580
- }
2581
- if (bindings.shutdownHandler !== undefined) {
2582
- this._extensionShutdownHandler = bindings.shutdownHandler;
3488
+ const finishBindingWork = this._sessionWorkBarrier.begin();
3489
+ const bindingPromptReadiness = new Set();
3490
+ this._extensionBindingPromptReadiness = bindingPromptReadiness;
3491
+ try {
3492
+ if (bindings.uiContext !== undefined) {
3493
+ this._extensionUIContext = bindings.uiContext;
3494
+ }
3495
+ if (bindings.mode !== undefined) {
3496
+ this._extensionMode = bindings.mode;
3497
+ }
3498
+ if (bindings.commandContextActions !== undefined) {
3499
+ this._extensionCommandContextActions = bindings.commandContextActions;
3500
+ }
3501
+ if (bindings.abortHandler !== undefined) {
3502
+ this._extensionAbortHandler = bindings.abortHandler;
3503
+ }
3504
+ if (bindings.shutdownHandler !== undefined) {
3505
+ this._extensionShutdownHandler = bindings.shutdownHandler;
3506
+ }
3507
+ if (bindings.onError !== undefined) {
3508
+ this._extensionErrorListener = bindings.onError;
3509
+ }
3510
+ this._applyExtensionBindings(this._extensionRunner);
3511
+ await this._extensionRunner.emit(this._sessionStartEvent);
3512
+ await this.extendResourcesFromExtensions(this._sessionStartEvent.reason === "reload" ? "reload" : "startup");
2583
3513
  }
2584
- if (bindings.onError !== undefined) {
2585
- this._extensionErrorListener = bindings.onError;
3514
+ finally {
3515
+ if (this._extensionBindingPromptReadiness === bindingPromptReadiness) {
3516
+ this._extensionBindingPromptReadiness = undefined;
3517
+ }
3518
+ finishBindingWork();
2586
3519
  }
2587
- this._applyExtensionBindings(this._extensionRunner);
2588
- await this._extensionRunner.emit(this._sessionStartEvent);
2589
- await this.extendResourcesFromExtensions(this._sessionStartEvent.reason === "reload" ? "reload" : "startup");
3520
+ await Promise.all(bindingPromptReadiness);
2590
3521
  }
2591
3522
  async extendResourcesFromExtensions(reason) {
2592
3523
  if (!this._extensionRunner.hasHandlers("resources_discover")) {
@@ -2729,7 +3660,7 @@ export class AgentSession {
2729
3660
  getServiceTier: () => this.serviceTier,
2730
3661
  isIdle: () => this.isIdle,
2731
3662
  isProjectTrusted: () => this.settingsManager.isProjectTrusted(),
2732
- getSignal: () => this.agent.signal,
3663
+ getSignal: () => this._extensionEventSignal ?? this.agent.signal,
2733
3664
  abort: () => {
2734
3665
  if (this._extensionAbortHandler) {
2735
3666
  this._extensionAbortHandler();
@@ -2788,27 +3719,39 @@ export class AgentSession {
2788
3719
  },
2789
3720
  },
2790
3721
  compact: (options) => {
3722
+ const admission = this._claimPendingCompactionAdmission();
3723
+ const controller = admission.controller;
2791
3724
  void (async () => {
2792
- this._disconnectFromAgent();
2793
- await this.abort();
2794
- this._compactionAbortController = new AbortController();
2795
- this._emit({ type: "compaction_start", reason: "extension" });
3725
+ let outcome = "failed";
3726
+ let disconnected = false;
2796
3727
  try {
3728
+ await this._abortActiveAgentAndRetry();
3729
+ this._disconnectFromAgent();
3730
+ disconnected = true;
3731
+ this._emit({ type: "compaction_start", reason: "extension" });
2797
3732
  const execution = await this._executeCompaction({
3733
+ controller,
3734
+ owner: "compaction",
2798
3735
  reason: "extension",
2799
3736
  customInstructions: options?.customInstructions,
2800
3737
  willRetry: false,
2801
3738
  });
2802
3739
  if (execution.accepted) {
3740
+ outcome = "completed";
2803
3741
  options?.onComplete?.(execution.result);
2804
3742
  }
2805
3743
  else {
3744
+ outcome = "failed";
2806
3745
  options?.onError?.(new CompactionRejectedError(execution.rejectionCause));
2807
3746
  }
2808
3747
  }
2809
3748
  catch (error) {
3749
+ outcome = isCompactionExecutionAborted(error) ? "aborted" : "failed";
3750
+ if (!compactionExecutionOwnsTerminalTransition(error)) {
3751
+ return;
3752
+ }
2810
3753
  const message = error instanceof Error ? error.message : String(error);
2811
- const aborted = message === "Compaction cancelled" || (error instanceof Error && error.name === "AbortError");
3754
+ const aborted = isCompactionExecutionAborted(error);
2812
3755
  this._emit({
2813
3756
  type: "compaction_end",
2814
3757
  reason: "extension",
@@ -2821,8 +3764,13 @@ export class AgentSession {
2821
3764
  options?.onError?.(err);
2822
3765
  }
2823
3766
  finally {
2824
- this._compactionAbortController = undefined;
2825
- this._reconnectToAgent();
3767
+ if (this._compactionAbortController === controller &&
3768
+ this._compactionLifecycle.state.status !== "running") {
3769
+ this._compactionAbortController = undefined;
3770
+ }
3771
+ this._releasePendingCompactionAdmission(admission, outcome);
3772
+ if (disconnected && !this.isCompacting)
3773
+ this._reconnectToAgent();
2826
3774
  }
2827
3775
  })();
2828
3776
  },
@@ -3056,15 +4004,43 @@ export class AgentSession {
3056
4004
  }
3057
4005
  return Math.ceil(value);
3058
4006
  }
4007
+ /**
4008
+ * Retry policy + callbacks shared by compaction and branch-summary summarization calls.
4009
+ * Uses the same `settings.retry` budget/backoff as agent-turn retries so a single transient
4010
+ * stream drop no longer fails the whole operation. `source` carries the context
4011
+ * the TUI needs to render the retry and recreate the underlying indicator.
4012
+ */
4013
+ _summarizationRetryCallbacks(source) {
4014
+ return {
4015
+ onRetryScheduled: (attempt, maxAttempts, delayMs, errorMessage) => {
4016
+ this._emit({
4017
+ type: "summarization_retry_scheduled",
4018
+ attempt,
4019
+ maxAttempts,
4020
+ delayMs,
4021
+ errorMessage,
4022
+ });
4023
+ },
4024
+ onRetryAttemptStart: () => {
4025
+ this._emit({
4026
+ type: "summarization_retry_attempt_start",
4027
+ ...source,
4028
+ });
4029
+ },
4030
+ onRetryFinished: () => {
4031
+ this._emit({ type: "summarization_retry_finished" });
4032
+ },
4033
+ };
4034
+ }
3059
4035
  /**
3060
4036
  * Handle retryable errors with exponential backoff.
3061
- * @returns true if retry was initiated, false if max retries exceeded or disabled
4037
+ * @returns whether retry continuation started, was blocked by compaction, or was not handled
3062
4038
  */
3063
4039
  async _handleRetryableError(message, options = {}) {
3064
4040
  const settings = this.settingsManager.getRetrySettings();
3065
4041
  if (!settings.enabled) {
3066
4042
  this._resolveRetry();
3067
- return false;
4043
+ return "not-handled";
3068
4044
  }
3069
4045
  // Retry promise is created synchronously in _handleAgentEvent for agent_end.
3070
4046
  // Keep a defensive fallback here in case a future refactor bypasses that path.
@@ -3082,7 +4058,7 @@ export class AgentSession {
3082
4058
  switchedFallback = await this._retryFallback.tryFallback("hard-error", { errorMessage });
3083
4059
  if (!switchedFallback) {
3084
4060
  this._resolveRetry();
3085
- return false;
4061
+ return "not-handled";
3086
4062
  }
3087
4063
  // The fallback starts fresh; the failed model's transient attempts do not carry over.
3088
4064
  this._retryAttempt = 1;
@@ -3092,7 +4068,7 @@ export class AgentSession {
3092
4068
  // same-model retries or the transient over-budget fallback escape hatch.
3093
4069
  if (this._retryAttempt + 1 > settings.maxRetries) {
3094
4070
  this._resolveRetry();
3095
- return false;
4071
+ return "not-handled";
3096
4072
  }
3097
4073
  switchedFallback = await this._retryFallback.tryFallback("refusal", {});
3098
4074
  if (!switchedFallback) {
@@ -3101,7 +4077,7 @@ export class AgentSession {
3101
4077
  this._emit({ type: "retry_fallback_exhausted", chainKey: exhaustedChainKey, lastError: errorMessage });
3102
4078
  }
3103
4079
  this._resolveRetry();
3104
- return false;
4080
+ return "not-handled";
3105
4081
  }
3106
4082
  this._retryAttempt++;
3107
4083
  }
@@ -3133,7 +4109,7 @@ export class AgentSession {
3133
4109
  });
3134
4110
  this._retryAttempt = 0;
3135
4111
  this._resolveRetry();
3136
- return false;
4112
+ return "not-handled";
3137
4113
  }
3138
4114
  }
3139
4115
  }
@@ -3148,7 +4124,7 @@ export class AgentSession {
3148
4124
  });
3149
4125
  this._retryAttempt = 0;
3150
4126
  this._resolveRetry();
3151
- return false;
4127
+ return "not-handled";
3152
4128
  }
3153
4129
  if (!switchedFallback) {
3154
4130
  switchedFallback = await this._retryFallback.tryFallback("transient", {
@@ -3189,19 +4165,52 @@ export class AgentSession {
3189
4165
  finalError: "Retry cancelled",
3190
4166
  });
3191
4167
  this._resolveRetry();
3192
- return false;
4168
+ return "not-handled";
3193
4169
  }
3194
4170
  this._retryAbortController = undefined;
3195
4171
  // Turn boundary: a suppression that expired during the backoff sleep lets
3196
4172
  // the retry continue on the restored primary instead of the fallback model.
3197
4173
  await this._maybeRestoreFallbackPrimary();
3198
- // Retry via continue() - use setTimeout to break out of event handler chain
4174
+ // Model fallback (or reversion) can select a smaller context window after
4175
+ // the prior retry checks. Revalidate canonical session context immediately
4176
+ // before the continuation so a rejected compaction never admits that model.
4177
+ const model = this.model;
4178
+ const compactionSettings = this.settingsManager.getCompactionSettings();
4179
+ const contextTokens = estimateContextTokens(filterContextExcludedMessages(this.sessionManager.buildSessionContext().messages)).tokens;
4180
+ if (compactionSettings.enabled &&
4181
+ model &&
4182
+ shouldCompact(contextTokens, model.contextWindow, compactionSettings)) {
4183
+ if (!(await this._runPrePromptCompaction(message, true, "threshold", true, true))) {
4184
+ const attempt = this._retryAttempt;
4185
+ this._retryAttempt = 0;
4186
+ this._emit({
4187
+ type: "auto_retry_end",
4188
+ success: false,
4189
+ attempt,
4190
+ finalError: new RequiredCompactionError().message,
4191
+ });
4192
+ this._resolveRetry();
4193
+ return "blocked";
4194
+ }
4195
+ this._skipNextPostRetryCompactionCheck = true;
4196
+ }
4197
+ // Retry via the shared continuation path after the event handler chain settles.
4198
+ // Agent core would otherwise drain these queues before the continuation
4199
+ // revalidates its provider admission. The scheduled continuation owns them
4200
+ // until it either starts or reports a terminal admission failure.
4201
+ this.agent.suppressQueuedMessageDrain();
3199
4202
  setTimeout(() => {
3200
- this.agent.continue().catch(() => {
3201
- // Retry failed - will be caught by next agent_end
4203
+ void this._continueAgentAfterCurrentRun().catch(async (error) => {
4204
+ const message = error instanceof Error ? error.message : String(error);
4205
+ this._emit({
4206
+ type: "continuation_error",
4207
+ errorMessage: `Failed to continue queued messages: ${message}`,
4208
+ });
4209
+ await this._emitAgentSettled();
4210
+ this._resolveRetry();
3202
4211
  });
3203
4212
  }, 0);
3204
- return true;
4213
+ return "continued";
3205
4214
  }
3206
4215
  /**
3207
4216
  * Cancel in-progress retry.
@@ -3245,6 +4254,7 @@ export class AgentSession {
3245
4254
  * @param command The bash command to execute
3246
4255
  * @param onChunk Optional streaming callback for output
3247
4256
  * @param options.excludeFromContext If true, command output won't be sent to LLM (!! prefix)
4257
+ * @param options.id Optional identifier included in bash execution update events
3248
4258
  * @param options.operations Custom BashOperations for remote execution
3249
4259
  */
3250
4260
  async executeBash(command, onChunk, options) {
@@ -3255,7 +4265,10 @@ export class AgentSession {
3255
4265
  const resolvedCommand = prefix ? `${prefix}\n${command}` : command;
3256
4266
  try {
3257
4267
  const result = await executeBashWithOperations(resolvedCommand, this.sessionManager.getCwd(), options?.operations ?? createLocalBashOperations({ shellPath }), {
3258
- onChunk,
4268
+ onChunk: (delta) => {
4269
+ onChunk?.(delta);
4270
+ this._emit({ type: "bash_execution_update", id: options?.id, delta });
4271
+ },
3259
4272
  signal: this._bashAbortController.signal,
3260
4273
  });
3261
4274
  this.recordBashResult(command, result, options);
@@ -3413,6 +4426,7 @@ export class AgentSession {
3413
4426
  // Run default summarizer if needed
3414
4427
  let summaryText;
3415
4428
  let summaryDetails;
4429
+ let summaryUsage;
3416
4430
  if (options.summarize && entriesToSummarize.length > 0 && !extensionSummary) {
3417
4431
  const model = this.model;
3418
4432
  const { apiKey, headers, extraBody, env } = await this._getCompactionRequestAuth(model);
@@ -3427,7 +4441,9 @@ export class AgentSession {
3427
4441
  customInstructions,
3428
4442
  replaceInstructions,
3429
4443
  reserveTokens: branchSummarySettings.reserveTokens,
3430
- streamFn: this.agent.streamFn,
4444
+ streamFn: this.agent.streamFunction,
4445
+ retry: this.settingsManager.getRetrySettings(),
4446
+ callbacks: this._summarizationRetryCallbacks({ source: "branchSummary" }),
3431
4447
  });
3432
4448
  if (result.aborted) {
3433
4449
  return { cancelled: true, aborted: true };
@@ -3436,6 +4452,7 @@ export class AgentSession {
3436
4452
  throw new Error(result.error);
3437
4453
  }
3438
4454
  summaryText = result.summary;
4455
+ summaryUsage = result.usage;
3439
4456
  summaryDetails = {
3440
4457
  readFiles: result.readFiles || [],
3441
4458
  modifiedFiles: result.modifiedFiles || [],
@@ -3444,6 +4461,7 @@ export class AgentSession {
3444
4461
  else if (extensionSummary) {
3445
4462
  summaryText = extensionSummary.summary;
3446
4463
  summaryDetails = extensionSummary.details;
4464
+ summaryUsage = extensionSummary.usage;
3447
4465
  }
3448
4466
  // Determine the new leaf position based on target type
3449
4467
  let newLeafId;
@@ -3451,18 +4469,12 @@ export class AgentSession {
3451
4469
  if (targetEntry.type === "message" && targetEntry.message.role === "user") {
3452
4470
  // User message: leaf = parent (null if root), text goes to editor
3453
4471
  newLeafId = targetEntry.parentId;
3454
- editorText = this._extractUserMessageText(targetEntry.message.content);
4472
+ editorText = contentText(targetEntry.message.content, "");
3455
4473
  }
3456
4474
  else if (targetEntry.type === "custom_message") {
3457
4475
  // Custom message: leaf = parent (null if root), text goes to editor
3458
4476
  newLeafId = targetEntry.parentId;
3459
- editorText =
3460
- typeof targetEntry.content === "string"
3461
- ? targetEntry.content
3462
- : targetEntry.content
3463
- .filter((c) => c.type === "text")
3464
- .map((c) => c.text)
3465
- .join("");
4477
+ editorText = contentText(targetEntry.content, "");
3466
4478
  }
3467
4479
  else {
3468
4480
  // Non-user message: leaf = selected node
@@ -3473,7 +4485,7 @@ export class AgentSession {
3473
4485
  let summaryEntry;
3474
4486
  if (summaryText) {
3475
4487
  // Create summary at target position (can be null for root)
3476
- const summaryId = this.sessionManager.branchWithSummary(newLeafId, summaryText, summaryDetails, fromExtension);
4488
+ const summaryId = this.sessionManager.branchWithSummary(newLeafId, summaryText, summaryDetails, fromExtension, summaryUsage);
3477
4489
  summaryEntry = this.sessionManager.getEntry(summaryId);
3478
4490
  // Attach label to the summary entry
3479
4491
  if (label) {
@@ -3492,9 +4504,8 @@ export class AgentSession {
3492
4504
  if (label && !summaryText) {
3493
4505
  this.sessionManager.appendLabelChange(targetId, label);
3494
4506
  }
3495
- // Update agent state
3496
- const sessionContext = this.sessionManager.buildSessionContext();
3497
- this.agent.state.messages = sessionContext.messages;
4507
+ // Update agent state (preserving exact messages still awaiting persistence)
4508
+ this._restoreAgentMessagesFromSession();
3498
4509
  this._incrementMessageRevision();
3499
4510
  // Emit session_tree event
3500
4511
  await this._extensionRunner.emit({
@@ -3522,24 +4533,13 @@ export class AgentSession {
3522
4533
  continue;
3523
4534
  if (entry.message.role !== "user")
3524
4535
  continue;
3525
- const text = this._extractUserMessageText(entry.message.content);
4536
+ const text = contentText(entry.message.content, "");
3526
4537
  if (text) {
3527
4538
  result.push({ entryId: entry.id, text });
3528
4539
  }
3529
4540
  }
3530
4541
  return result;
3531
4542
  }
3532
- _extractUserMessageText(content) {
3533
- if (typeof content === "string")
3534
- return content;
3535
- if (Array.isArray(content)) {
3536
- return content
3537
- .filter((c) => c.type === "text")
3538
- .map((c) => c.text)
3539
- .join("");
3540
- }
3541
- return "";
3542
- }
3543
4543
  /**
3544
4544
  * Get session statistics. Aggregates over ALL session entries (including
3545
4545
  * history that was compacted away), so token/cost totals reflect what was
@@ -3551,12 +4551,11 @@ export class AgentSession {
3551
4551
  let toolResults = 0;
3552
4552
  let totalMessages = 0;
3553
4553
  let toolCalls = 0;
3554
- let totalInput = 0;
3555
- let totalOutput = 0;
3556
- let totalCacheRead = 0;
3557
- let totalCacheWrite = 0;
3558
- let totalCost = 0;
4554
+ const usageTotals = createUsageTotals();
3559
4555
  for (const entry of this.sessionManager.getEntries()) {
4556
+ if ((entry.type === "branch_summary" || entry.type === "compaction") && entry.usage) {
4557
+ addUsageToTotals(usageTotals, entry.usage);
4558
+ }
3560
4559
  if (entry.type !== "message")
3561
4560
  continue;
3562
4561
  totalMessages++;
@@ -3566,6 +4565,9 @@ export class AgentSession {
3566
4565
  }
3567
4566
  else if (message.role === "toolResult") {
3568
4567
  toolResults++;
4568
+ if (message.usage) {
4569
+ addUsageToTotals(usageTotals, message.usage);
4570
+ }
3569
4571
  }
3570
4572
  else if (message.role === "assistant") {
3571
4573
  assistantMessages++;
@@ -3573,12 +4575,7 @@ export class AgentSession {
3573
4575
  if (Array.isArray(assistantMsg.content)) {
3574
4576
  toolCalls += assistantMsg.content.filter((c) => c.type === "toolCall").length;
3575
4577
  }
3576
- const usage = assistantMsg.usage;
3577
- totalInput += usage.input;
3578
- totalOutput += usage.output;
3579
- totalCacheRead += usage.cacheRead;
3580
- totalCacheWrite += usage.cacheWrite;
3581
- totalCost += usage.cost.total;
4578
+ addUsageToTotals(usageTotals, assistantMsg.usage);
3582
4579
  }
3583
4580
  }
3584
4581
  return {
@@ -3590,13 +4587,13 @@ export class AgentSession {
3590
4587
  toolResults,
3591
4588
  totalMessages,
3592
4589
  tokens: {
3593
- input: totalInput,
3594
- output: totalOutput,
3595
- cacheRead: totalCacheRead,
3596
- cacheWrite: totalCacheWrite,
3597
- total: totalInput + totalOutput + totalCacheRead + totalCacheWrite,
4590
+ input: usageTotals.input,
4591
+ output: usageTotals.output,
4592
+ cacheRead: usageTotals.cacheRead,
4593
+ cacheWrite: usageTotals.cacheWrite,
4594
+ total: usageTotals.input + usageTotals.output + usageTotals.cacheRead + usageTotals.cacheWrite,
3598
4595
  },
3599
- cost: totalCost,
4596
+ cost: usageTotals.cost,
3600
4597
  contextUsage: this.getContextUsage(),
3601
4598
  };
3602
4599
  }