@code-yeongyu/senpi 2026.7.23 → 2026.7.24

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 (825) hide show
  1. package/CHANGELOG.md +131 -6
  2. package/README.md +14 -1
  3. package/dist/cli/args.d.ts +2 -0
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +6 -0
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/cli/config-selector.js +1 -1
  8. package/dist/cli/config-selector.js.map +1 -1
  9. package/dist/core/agent-session-runtime.d.ts +15 -1
  10. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  11. package/dist/core/agent-session-runtime.js +12 -2
  12. package/dist/core/agent-session-runtime.js.map +1 -1
  13. package/dist/core/agent-session.d.ts +102 -4
  14. package/dist/core/agent-session.d.ts.map +1 -1
  15. package/dist/core/agent-session.js +1295 -320
  16. package/dist/core/agent-session.js.map +1 -1
  17. package/dist/core/compaction/branch-summarization.d.ts +7 -1
  18. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  19. package/dist/core/compaction/branch-summarization.js +8 -12
  20. package/dist/core/compaction/branch-summarization.js.map +1 -1
  21. package/dist/core/compaction/compaction.d.ts +22 -4
  22. package/dist/core/compaction/compaction.d.ts.map +1 -1
  23. package/dist/core/compaction/compaction.js +114 -51
  24. package/dist/core/compaction/compaction.js.map +1 -1
  25. package/dist/core/compaction/lifecycle.d.ts +69 -0
  26. package/dist/core/compaction/lifecycle.d.ts.map +1 -0
  27. package/dist/core/compaction/lifecycle.js +104 -0
  28. package/dist/core/compaction/lifecycle.js.map +1 -0
  29. package/dist/core/compaction/utils.d.ts +12 -1
  30. package/dist/core/compaction/utils.d.ts.map +1 -1
  31. package/dist/core/compaction/utils.js +22 -17
  32. package/dist/core/compaction/utils.js.map +1 -1
  33. package/dist/core/event-bus.d.ts.map +1 -1
  34. package/dist/core/event-bus.js +11 -1
  35. package/dist/core/event-bus.js.map +1 -1
  36. package/dist/core/extensions/builtin/compaction/index.d.ts +2 -1
  37. package/dist/core/extensions/builtin/compaction/index.d.ts.map +1 -1
  38. package/dist/core/extensions/builtin/compaction/index.js +42 -30
  39. package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
  40. package/dist/core/extensions/builtin/compaction/openai-remote-model.d.ts +9 -2
  41. package/dist/core/extensions/builtin/compaction/openai-remote-model.d.ts.map +1 -1
  42. package/dist/core/extensions/builtin/compaction/openai-remote-model.js +78 -28
  43. package/dist/core/extensions/builtin/compaction/openai-remote-model.js.map +1 -1
  44. package/dist/core/extensions/builtin/compaction/openai-remote-schema.d.ts +3 -1
  45. package/dist/core/extensions/builtin/compaction/openai-remote-schema.d.ts.map +1 -1
  46. package/dist/core/extensions/builtin/compaction/openai-remote-schema.js +19 -1
  47. package/dist/core/extensions/builtin/compaction/openai-remote-schema.js.map +1 -1
  48. package/dist/core/extensions/builtin/compaction/openai-remote.d.ts +20 -6
  49. package/dist/core/extensions/builtin/compaction/openai-remote.d.ts.map +1 -1
  50. package/dist/core/extensions/builtin/compaction/openai-remote.js +207 -22
  51. package/dist/core/extensions/builtin/compaction/openai-remote.js.map +1 -1
  52. package/dist/core/extensions/builtin/compaction/speculative.d.ts +4 -2
  53. package/dist/core/extensions/builtin/compaction/speculative.d.ts.map +1 -1
  54. package/dist/core/extensions/builtin/compaction/speculative.js +25 -13
  55. package/dist/core/extensions/builtin/compaction/speculative.js.map +1 -1
  56. package/dist/core/extensions/builtin/config-reload/index.d.ts +7 -0
  57. package/dist/core/extensions/builtin/config-reload/index.d.ts.map +1 -1
  58. package/dist/core/extensions/builtin/config-reload/index.js +49 -17
  59. package/dist/core/extensions/builtin/config-reload/index.js.map +1 -1
  60. package/dist/core/extensions/builtin/hooks/index.js +1 -1
  61. package/dist/core/extensions/builtin/hooks/index.js.map +1 -1
  62. package/dist/core/extensions/builtin/hooks/lifecycle-adapter.d.ts +3 -1
  63. package/dist/core/extensions/builtin/hooks/lifecycle-adapter.d.ts.map +1 -1
  64. package/dist/core/extensions/builtin/hooks/lifecycle-adapter.js +2 -1
  65. package/dist/core/extensions/builtin/hooks/lifecycle-adapter.js.map +1 -1
  66. package/dist/core/extensions/builtin/mcp/auth/commands-auth-dispatch.d.ts +2 -1
  67. package/dist/core/extensions/builtin/mcp/auth/commands-auth-dispatch.d.ts.map +1 -1
  68. package/dist/core/extensions/builtin/mcp/auth/commands-auth-dispatch.js +5 -3
  69. package/dist/core/extensions/builtin/mcp/auth/commands-auth-dispatch.js.map +1 -1
  70. package/dist/core/extensions/builtin/mcp/auth/commands-auth.d.ts +5 -0
  71. package/dist/core/extensions/builtin/mcp/auth/commands-auth.d.ts.map +1 -1
  72. package/dist/core/extensions/builtin/mcp/auth/commands-auth.js +8 -3
  73. package/dist/core/extensions/builtin/mcp/auth/commands-auth.js.map +1 -1
  74. package/dist/core/extensions/builtin/mcp/catalog.d.ts +3 -1
  75. package/dist/core/extensions/builtin/mcp/catalog.d.ts.map +1 -1
  76. package/dist/core/extensions/builtin/mcp/catalog.js +2 -0
  77. package/dist/core/extensions/builtin/mcp/catalog.js.map +1 -1
  78. package/dist/core/extensions/builtin/mcp/commands.d.ts +1 -1
  79. package/dist/core/extensions/builtin/mcp/commands.d.ts.map +1 -1
  80. package/dist/core/extensions/builtin/mcp/commands.js +34 -39
  81. package/dist/core/extensions/builtin/mcp/commands.js.map +1 -1
  82. package/dist/core/extensions/builtin/mcp/connection-types.d.ts +2 -0
  83. package/dist/core/extensions/builtin/mcp/connection-types.d.ts.map +1 -1
  84. package/dist/core/extensions/builtin/mcp/connection-types.js.map +1 -1
  85. package/dist/core/extensions/builtin/mcp/connection.d.ts.map +1 -1
  86. package/dist/core/extensions/builtin/mcp/connection.js +3 -0
  87. package/dist/core/extensions/builtin/mcp/connection.js.map +1 -1
  88. package/dist/core/extensions/builtin/mcp/elicitation.d.ts +7 -3
  89. package/dist/core/extensions/builtin/mcp/elicitation.d.ts.map +1 -1
  90. package/dist/core/extensions/builtin/mcp/elicitation.js +5 -5
  91. package/dist/core/extensions/builtin/mcp/elicitation.js.map +1 -1
  92. package/dist/core/extensions/builtin/mcp/expose/register.d.ts.map +1 -1
  93. package/dist/core/extensions/builtin/mcp/expose/register.js +1 -0
  94. package/dist/core/extensions/builtin/mcp/expose/register.js.map +1 -1
  95. package/dist/core/extensions/builtin/mcp/expose/session.d.ts +2 -0
  96. package/dist/core/extensions/builtin/mcp/expose/session.d.ts.map +1 -1
  97. package/dist/core/extensions/builtin/mcp/expose/session.js +3 -0
  98. package/dist/core/extensions/builtin/mcp/expose/session.js.map +1 -1
  99. package/dist/core/extensions/builtin/mcp/guard/output-guard.d.ts +9 -1
  100. package/dist/core/extensions/builtin/mcp/guard/output-guard.d.ts.map +1 -1
  101. package/dist/core/extensions/builtin/mcp/guard/output-guard.js +18 -6
  102. package/dist/core/extensions/builtin/mcp/guard/output-guard.js.map +1 -1
  103. package/dist/core/extensions/builtin/mcp/index.d.ts +4 -2
  104. package/dist/core/extensions/builtin/mcp/index.d.ts.map +1 -1
  105. package/dist/core/extensions/builtin/mcp/index.js +150 -127
  106. package/dist/core/extensions/builtin/mcp/index.js.map +1 -1
  107. package/dist/core/extensions/builtin/mcp/instructions.d.ts +1 -1
  108. package/dist/core/extensions/builtin/mcp/instructions.d.ts.map +1 -1
  109. package/dist/core/extensions/builtin/mcp/instructions.js +6 -6
  110. package/dist/core/extensions/builtin/mcp/instructions.js.map +1 -1
  111. package/dist/core/extensions/builtin/mcp/prompts.d.ts +2 -0
  112. package/dist/core/extensions/builtin/mcp/prompts.d.ts.map +1 -1
  113. package/dist/core/extensions/builtin/mcp/prompts.js +9 -3
  114. package/dist/core/extensions/builtin/mcp/prompts.js.map +1 -1
  115. package/dist/core/extensions/builtin/mcp/resources.d.ts +2 -0
  116. package/dist/core/extensions/builtin/mcp/resources.d.ts.map +1 -1
  117. package/dist/core/extensions/builtin/mcp/resources.js +1 -0
  118. package/dist/core/extensions/builtin/mcp/resources.js.map +1 -1
  119. package/dist/core/extensions/builtin/mcp/service-register.d.ts.map +1 -1
  120. package/dist/core/extensions/builtin/mcp/service-register.js +1 -0
  121. package/dist/core/extensions/builtin/mcp/service-register.js.map +1 -1
  122. package/dist/core/extensions/builtin/mcp/service-types.d.ts +2 -0
  123. package/dist/core/extensions/builtin/mcp/service-types.d.ts.map +1 -1
  124. package/dist/core/extensions/builtin/mcp/service-types.js.map +1 -1
  125. package/dist/core/extensions/builtin/mcp/service.d.ts +12 -1
  126. package/dist/core/extensions/builtin/mcp/service.d.ts.map +1 -1
  127. package/dist/core/extensions/builtin/mcp/service.js +44 -2
  128. package/dist/core/extensions/builtin/mcp/service.js.map +1 -1
  129. package/dist/core/extensions/builtin/mcp/transport.d.ts +2 -0
  130. package/dist/core/extensions/builtin/mcp/transport.d.ts.map +1 -1
  131. package/dist/core/extensions/builtin/mcp/transport.js +7 -7
  132. package/dist/core/extensions/builtin/mcp/transport.js.map +1 -1
  133. package/dist/core/extensions/builtin/prompt-preset/claude-fable-5.d.ts.map +1 -1
  134. package/dist/core/extensions/builtin/prompt-preset/claude-fable-5.js +86 -7
  135. package/dist/core/extensions/builtin/prompt-preset/claude-fable-5.js.map +1 -1
  136. package/dist/core/extensions/builtin/prompt-preset/claude-opus-5.d.ts +3 -0
  137. package/dist/core/extensions/builtin/prompt-preset/claude-opus-5.d.ts.map +1 -0
  138. package/dist/core/extensions/builtin/prompt-preset/claude-opus-5.js +102 -0
  139. package/dist/core/extensions/builtin/prompt-preset/claude-opus-5.js.map +1 -0
  140. package/dist/core/extensions/builtin/prompt-preset/kimi-k3.d.ts.map +1 -1
  141. package/dist/core/extensions/builtin/prompt-preset/kimi-k3.js +96 -6
  142. package/dist/core/extensions/builtin/prompt-preset/kimi-k3.js.map +1 -1
  143. package/dist/core/extensions/builtin/prompt-preset/presets.d.ts.map +1 -1
  144. package/dist/core/extensions/builtin/prompt-preset/presets.js +9 -0
  145. package/dist/core/extensions/builtin/prompt-preset/presets.js.map +1 -1
  146. package/dist/core/extensions/builtin/prompt-preset/settings.d.ts +1 -1
  147. package/dist/core/extensions/builtin/prompt-preset/settings.d.ts.map +1 -1
  148. package/dist/core/extensions/builtin/prompt-preset/settings.js +1 -0
  149. package/dist/core/extensions/builtin/prompt-preset/settings.js.map +1 -1
  150. package/dist/core/extensions/builtin/terminal/extension.d.ts.map +1 -1
  151. package/dist/core/extensions/builtin/terminal/extension.js +1 -0
  152. package/dist/core/extensions/builtin/terminal/extension.js.map +1 -1
  153. package/dist/core/extensions/builtin/terminal/tools/bash.d.ts +15 -3
  154. package/dist/core/extensions/builtin/terminal/tools/bash.d.ts.map +1 -1
  155. package/dist/core/extensions/builtin/terminal/tools/bash.js +30 -6
  156. package/dist/core/extensions/builtin/terminal/tools/bash.js.map +1 -1
  157. package/dist/core/extensions/builtin/terminal/tools/context.d.ts +5 -0
  158. package/dist/core/extensions/builtin/terminal/tools/context.d.ts.map +1 -1
  159. package/dist/core/extensions/builtin/terminal/tools/context.js.map +1 -1
  160. package/dist/core/extensions/runner.d.ts +14 -7
  161. package/dist/core/extensions/runner.d.ts.map +1 -1
  162. package/dist/core/extensions/runner.js +64 -37
  163. package/dist/core/extensions/runner.js.map +1 -1
  164. package/dist/core/extensions/types.d.ts +30 -3
  165. package/dist/core/extensions/types.d.ts.map +1 -1
  166. package/dist/core/extensions/types.js.map +1 -1
  167. package/dist/core/http-dispatcher.d.ts.map +1 -1
  168. package/dist/core/http-dispatcher.js +16 -0
  169. package/dist/core/http-dispatcher.js.map +1 -1
  170. package/dist/core/messages.d.ts +1 -1
  171. package/dist/core/messages.d.ts.map +1 -1
  172. package/dist/core/messages.js +10 -8
  173. package/dist/core/messages.js.map +1 -1
  174. package/dist/core/model-registry.d.ts +1 -0
  175. package/dist/core/model-registry.d.ts.map +1 -1
  176. package/dist/core/model-registry.js +3 -2
  177. package/dist/core/model-registry.js.map +1 -1
  178. package/dist/core/model-resolver.d.ts.map +1 -1
  179. package/dist/core/model-resolver.js +9 -0
  180. package/dist/core/model-resolver.js.map +1 -1
  181. package/dist/core/model-runtime.d.ts +4 -2
  182. package/dist/core/model-runtime.d.ts.map +1 -1
  183. package/dist/core/model-runtime.js +34 -18
  184. package/dist/core/model-runtime.js.map +1 -1
  185. package/dist/core/remote-catalog-provider.d.ts +1 -1
  186. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  187. package/dist/core/remote-catalog-provider.js +21 -7
  188. package/dist/core/remote-catalog-provider.js.map +1 -1
  189. package/dist/core/resolve-config-value.d.ts.map +1 -1
  190. package/dist/core/resolve-config-value.js +12 -8
  191. package/dist/core/resolve-config-value.js.map +1 -1
  192. package/dist/core/sdk.d.ts +1 -1
  193. package/dist/core/sdk.d.ts.map +1 -1
  194. package/dist/core/sdk.js +8 -3
  195. package/dist/core/sdk.js.map +1 -1
  196. package/dist/core/session-manager.d.ts +22 -3
  197. package/dist/core/session-manager.d.ts.map +1 -1
  198. package/dist/core/session-manager.js +155 -58
  199. package/dist/core/session-manager.js.map +1 -1
  200. package/dist/core/settings-manager.d.ts +1 -1
  201. package/dist/core/settings-manager.d.ts.map +1 -1
  202. package/dist/core/settings-manager.js.map +1 -1
  203. package/dist/core/system-prompt.js +1 -1
  204. package/dist/core/system-prompt.js.map +1 -1
  205. package/dist/core/tools/bash.d.ts +2 -0
  206. package/dist/core/tools/bash.d.ts.map +1 -1
  207. package/dist/core/tools/bash.js +34 -5
  208. package/dist/core/tools/bash.js.map +1 -1
  209. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  210. package/dist/core/tools/tool-definition-wrapper.js +3 -1
  211. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  212. package/dist/core/usage-totals.d.ts +19 -0
  213. package/dist/core/usage-totals.d.ts.map +1 -0
  214. package/dist/core/usage-totals.js +52 -0
  215. package/dist/core/usage-totals.js.map +1 -0
  216. package/dist/extensions/llama/client.d.ts.map +1 -1
  217. package/dist/extensions/llama/client.js +3 -1
  218. package/dist/extensions/llama/client.js.map +1 -1
  219. package/dist/extensions/llama/index.js +3 -3
  220. package/dist/extensions/llama/index.js.map +1 -1
  221. package/dist/extensions/llama/provider.d.ts.map +1 -1
  222. package/dist/extensions/llama/provider.js +1 -2
  223. package/dist/extensions/llama/provider.js.map +1 -1
  224. package/dist/index.d.ts +2 -2
  225. package/dist/index.d.ts.map +1 -1
  226. package/dist/index.js +1 -1
  227. package/dist/index.js.map +1 -1
  228. package/dist/main.d.ts.map +1 -1
  229. package/dist/main.js +37 -5
  230. package/dist/main.js.map +1 -1
  231. package/dist/modes/app-server/daemon/occupancy.d.ts +22 -0
  232. package/dist/modes/app-server/daemon/occupancy.d.ts.map +1 -0
  233. package/dist/modes/app-server/daemon/occupancy.js +67 -0
  234. package/dist/modes/app-server/daemon/occupancy.js.map +1 -0
  235. package/dist/modes/app-server/daemon/probe.d.ts +3 -3
  236. package/dist/modes/app-server/daemon/probe.d.ts.map +1 -1
  237. package/dist/modes/app-server/daemon/probe.js +33 -10
  238. package/dist/modes/app-server/daemon/probe.js.map +1 -1
  239. package/dist/modes/app-server/daemon.d.ts.map +1 -1
  240. package/dist/modes/app-server/daemon.js +58 -18
  241. package/dist/modes/app-server/daemon.js.map +1 -1
  242. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  243. package/dist/modes/interactive/components/compaction-summary-message.js +8 -1
  244. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  245. package/dist/modes/interactive/components/extension-editor.d.ts +1 -2
  246. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  247. package/dist/modes/interactive/components/extension-editor.js +16 -46
  248. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  249. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  250. package/dist/modes/interactive/components/footer.js.map +1 -1
  251. package/dist/modes/interactive/components/status-indicator.d.ts +4 -0
  252. package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -1
  253. package/dist/modes/interactive/components/status-indicator.js +13 -0
  254. package/dist/modes/interactive/components/status-indicator.js.map +1 -1
  255. package/dist/modes/interactive/external-editor.d.ts +12 -0
  256. package/dist/modes/interactive/external-editor.d.ts.map +1 -0
  257. package/dist/modes/interactive/external-editor.js +37 -0
  258. package/dist/modes/interactive/external-editor.js.map +1 -0
  259. package/dist/modes/interactive/interactive-mode.d.ts +3 -1
  260. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  261. package/dist/modes/interactive/interactive-mode.js +96 -78
  262. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  263. package/dist/modes/rpc/connection-handler.d.ts +6 -0
  264. package/dist/modes/rpc/connection-handler.d.ts.map +1 -1
  265. package/dist/modes/rpc/connection-handler.js +34 -9
  266. package/dist/modes/rpc/connection-handler.js.map +1 -1
  267. package/dist/modes/rpc/multi-session-host.d.ts +15 -0
  268. package/dist/modes/rpc/multi-session-host.d.ts.map +1 -0
  269. package/dist/modes/rpc/multi-session-host.js +60 -0
  270. package/dist/modes/rpc/multi-session-host.js.map +1 -0
  271. package/dist/modes/rpc/neo-runtime-options.d.ts.map +1 -1
  272. package/dist/modes/rpc/neo-runtime-options.js +1 -0
  273. package/dist/modes/rpc/neo-runtime-options.js.map +1 -1
  274. package/dist/modes/rpc/rpc-client.d.ts +4 -0
  275. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  276. package/dist/modes/rpc/rpc-client.js +7 -0
  277. package/dist/modes/rpc/rpc-client.js.map +1 -1
  278. package/dist/modes/rpc/rpc-mode.d.ts +49 -0
  279. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  280. package/dist/modes/rpc/rpc-mode.js +49 -0
  281. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  282. package/dist/modes/rpc/rpc-types.d.ts +85 -1
  283. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  284. package/dist/modes/rpc/rpc-types.js +8 -1
  285. package/dist/modes/rpc/rpc-types.js.map +1 -1
  286. package/dist/modes/rpc/session-binding.d.ts +13 -0
  287. package/dist/modes/rpc/session-binding.d.ts.map +1 -0
  288. package/dist/modes/rpc/session-binding.js +33 -0
  289. package/dist/modes/rpc/session-binding.js.map +1 -0
  290. package/dist/modes/rpc/session-command-router.d.ts +19 -0
  291. package/dist/modes/rpc/session-command-router.d.ts.map +1 -0
  292. package/dist/modes/rpc/session-command-router.js +159 -0
  293. package/dist/modes/rpc/session-command-router.js.map +1 -0
  294. package/dist/modes/rpc/session-event-writer.d.ts +34 -0
  295. package/dist/modes/rpc/session-event-writer.d.ts.map +1 -0
  296. package/dist/modes/rpc/session-event-writer.js +92 -0
  297. package/dist/modes/rpc/session-event-writer.js.map +1 -0
  298. package/dist/modes/rpc/session-extension-ui-requests.d.ts +15 -0
  299. package/dist/modes/rpc/session-extension-ui-requests.d.ts.map +1 -0
  300. package/dist/modes/rpc/session-extension-ui-requests.js +27 -0
  301. package/dist/modes/rpc/session-extension-ui-requests.js.map +1 -0
  302. package/dist/modes/rpc/session-registry.d.ts +58 -0
  303. package/dist/modes/rpc/session-registry.d.ts.map +1 -0
  304. package/dist/modes/rpc/session-registry.js +162 -0
  305. package/dist/modes/rpc/session-registry.js.map +1 -0
  306. package/dist/utils/clipboard.d.ts.map +1 -1
  307. package/dist/utils/clipboard.js +19 -8
  308. package/dist/utils/clipboard.js.map +1 -1
  309. package/dist/utils/version-check.d.ts.map +1 -1
  310. package/dist/utils/version-check.js +3 -1
  311. package/dist/utils/version-check.js.map +1 -1
  312. package/docs/app-server-daemon.md +3 -2
  313. package/docs/compaction.md +9 -4
  314. package/docs/custom-provider.md +5 -2
  315. package/docs/environment-variables.md +88 -0
  316. package/docs/extensions.md +25 -7
  317. package/docs/index.md +1 -0
  318. package/docs/json.md +5 -1
  319. package/docs/models.md +6 -2
  320. package/docs/providers.md +12 -1
  321. package/docs/rpc.md +154 -8
  322. package/docs/sdk.md +3 -0
  323. package/docs/session-format.md +17 -3
  324. package/docs/settings.md +2 -2
  325. package/docs/usage.md +1 -14
  326. package/examples/extensions/custom-compaction.ts +4 -0
  327. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  328. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  329. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  330. package/examples/extensions/gondolin/package-lock.json +2 -2
  331. package/examples/extensions/gondolin/package.json +1 -1
  332. package/examples/extensions/handoff.ts +9 -1
  333. package/examples/extensions/sandbox/package-lock.json +2 -2
  334. package/examples/extensions/sandbox/package.json +1 -1
  335. package/examples/extensions/summarize.ts +3 -0
  336. package/examples/extensions/with-deps/package-lock.json +2 -2
  337. package/examples/extensions/with-deps/package.json +1 -1
  338. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +24 -0
  339. package/node_modules/@code-yeongyu/senpi-codemode/package.json +3 -3
  340. package/node_modules/@earendil-works/pi-agent-core/README.md +31 -12
  341. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts +4 -4
  342. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  343. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +8 -7
  344. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
  345. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts +10 -3
  346. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts.map +1 -1
  347. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js +61 -27
  348. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js.map +1 -1
  349. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.d.ts +11 -12
  350. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.d.ts.map +1 -1
  351. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.js +47 -22
  352. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.js.map +1 -1
  353. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.d.ts +5 -1
  354. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.d.ts.map +1 -1
  355. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.js +6 -8
  356. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.js.map +1 -1
  357. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.d.ts +17 -5
  358. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.d.ts.map +1 -1
  359. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +82 -27
  360. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js.map +1 -1
  361. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/utils.d.ts +12 -1
  362. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/utils.d.ts.map +1 -1
  363. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/utils.js +22 -17
  364. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/utils.js.map +1 -1
  365. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.d.ts +3 -9
  366. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.d.ts.map +1 -1
  367. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.js +118 -11
  368. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.js.map +1 -1
  369. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-storage.d.ts +11 -3
  370. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-storage.d.ts.map +1 -1
  371. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-storage.js +56 -4
  372. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-storage.js.map +1 -1
  373. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/memory-storage.d.ts +11 -3
  374. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/memory-storage.d.ts.map +1 -1
  375. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/memory-storage.js +56 -4
  376. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/memory-storage.js.map +1 -1
  377. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo-utils.d.ts.map +1 -1
  378. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo-utils.js +2 -2
  379. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo-utils.js.map +1 -1
  380. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.d.ts +6 -4
  381. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.d.ts.map +1 -1
  382. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.js +30 -14
  383. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.js.map +1 -1
  384. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/bash.d.ts +27 -0
  385. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/bash.d.ts.map +1 -0
  386. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/bash.js +132 -0
  387. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/bash.js.map +1 -0
  388. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit-diff.d.ts +89 -0
  389. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit-diff.d.ts.map +1 -0
  390. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit-diff.js +386 -0
  391. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit-diff.js.map +1 -0
  392. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit.d.ts +19 -0
  393. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit.d.ts.map +1 -0
  394. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit.js +95 -0
  395. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit.js.map +1 -0
  396. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/file-mutation-queue.d.ts +4 -0
  397. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/file-mutation-queue.d.ts.map +1 -0
  398. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/file-mutation-queue.js +46 -0
  399. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/file-mutation-queue.js.map +1 -0
  400. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/image.d.ts +3 -0
  401. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/image.d.ts.map +1 -0
  402. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/image.js +106 -0
  403. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/image.js.map +1 -0
  404. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/index.d.ts +6 -0
  405. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/index.d.ts.map +1 -0
  406. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/index.js +5 -0
  407. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/index.js.map +1 -0
  408. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/path-utils.d.ts +4 -0
  409. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/path-utils.d.ts.map +1 -0
  410. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/path-utils.js +26 -0
  411. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/path-utils.js.map +1 -0
  412. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/read.d.ts +34 -0
  413. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/read.d.ts.map +1 -0
  414. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/read.js +110 -0
  415. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/read.js.map +1 -0
  416. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/tool-context.d.ts +6 -0
  417. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/tool-context.d.ts.map +1 -0
  418. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/tool-context.js +2 -0
  419. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/tool-context.js.map +1 -0
  420. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/write.d.ts +11 -0
  421. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/write.d.ts.map +1 -0
  422. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/write.js +31 -0
  423. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/write.js.map +1 -0
  424. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts +80 -18
  425. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts.map +1 -1
  426. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.js.map +1 -1
  427. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.d.ts +12 -4
  428. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.d.ts.map +1 -1
  429. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.js +90 -35
  430. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.js.map +1 -1
  431. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/truncate.d.ts.map +1 -1
  432. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/truncate.js +10 -4
  433. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/truncate.js.map +1 -1
  434. package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts +4 -2
  435. package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts.map +1 -1
  436. package/node_modules/@earendil-works/pi-agent-core/dist/index.js +5 -2
  437. package/node_modules/@earendil-works/pi-agent-core/dist/index.js.map +1 -1
  438. package/node_modules/@earendil-works/pi-agent-core/dist/stream-fn.d.ts +10 -0
  439. package/node_modules/@earendil-works/pi-agent-core/dist/stream-fn.d.ts.map +1 -0
  440. package/node_modules/@earendil-works/pi-agent-core/dist/stream-fn.js +17 -0
  441. package/node_modules/@earendil-works/pi-agent-core/dist/stream-fn.js.map +1 -0
  442. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts +8 -2
  443. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts.map +1 -1
  444. package/node_modules/@earendil-works/pi-agent-core/dist/types.js.map +1 -1
  445. package/node_modules/@earendil-works/pi-agent-core/package.json +4 -3
  446. package/node_modules/@earendil-works/pi-ai/README.md +44 -4
  447. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts.map +1 -1
  448. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +45 -27
  449. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js.map +1 -1
  450. package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.d.ts.map +1 -1
  451. package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.js +21 -8
  452. package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.js.map +1 -1
  453. package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.d.ts.map +1 -1
  454. package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.js +17 -10
  455. package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.js.map +1 -1
  456. package/node_modules/@earendil-works/pi-ai/dist/api/constrained-sampling.d.ts +17 -0
  457. package/node_modules/@earendil-works/pi-ai/dist/api/constrained-sampling.d.ts.map +1 -0
  458. package/node_modules/@earendil-works/pi-ai/dist/api/constrained-sampling.js +104 -0
  459. package/node_modules/@earendil-works/pi-ai/dist/api/constrained-sampling.js.map +1 -0
  460. package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.d.ts.map +1 -1
  461. package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.js +7 -11
  462. package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.js.map +1 -1
  463. package/node_modules/@earendil-works/pi-ai/dist/api/google-shared.d.ts +4 -3
  464. package/node_modules/@earendil-works/pi-ai/dist/api/google-shared.d.ts.map +1 -1
  465. package/node_modules/@earendil-works/pi-ai/dist/api/google-shared.js +17 -3
  466. package/node_modules/@earendil-works/pi-ai/dist/api/google-shared.js.map +1 -1
  467. package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.d.ts.map +1 -1
  468. package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.js +7 -11
  469. package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.js.map +1 -1
  470. package/node_modules/@earendil-works/pi-ai/dist/api/mistral-conversations.d.ts.map +1 -1
  471. package/node_modules/@earendil-works/pi-ai/dist/api/mistral-conversations.js +13 -9
  472. package/node_modules/@earendil-works/pi-ai/dist/api/mistral-conversations.js.map +1 -1
  473. package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.d.ts.map +1 -1
  474. package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js +73 -47
  475. package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js.map +1 -1
  476. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.d.ts +7 -9
  477. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.d.ts.map +1 -1
  478. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +142 -33
  479. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js.map +1 -1
  480. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.d.ts +7 -0
  481. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.d.ts.map +1 -1
  482. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js +180 -80
  483. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js.map +1 -1
  484. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.d.ts.map +1 -1
  485. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js +37 -15
  486. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js.map +1 -1
  487. package/node_modules/@earendil-works/pi-ai/dist/api/openrouter-images.d.ts.map +1 -1
  488. package/node_modules/@earendil-works/pi-ai/dist/api/openrouter-images.js +8 -3
  489. package/node_modules/@earendil-works/pi-ai/dist/api/openrouter-images.js.map +1 -1
  490. package/node_modules/@earendil-works/pi-ai/dist/api/pi-messages.d.ts.map +1 -1
  491. package/node_modules/@earendil-works/pi-ai/dist/api/pi-messages.js +2 -4
  492. package/node_modules/@earendil-works/pi-ai/dist/api/pi-messages.js.map +1 -1
  493. package/node_modules/@earendil-works/pi-ai/dist/api-registry.d.ts +15 -0
  494. package/node_modules/@earendil-works/pi-ai/dist/api-registry.d.ts.map +1 -1
  495. package/node_modules/@earendil-works/pi-ai/dist/api-registry.js +52 -6
  496. package/node_modules/@earendil-works/pi-ai/dist/api-registry.js.map +1 -1
  497. package/node_modules/@earendil-works/pi-ai/dist/auth/helpers.d.ts.map +1 -1
  498. package/node_modules/@earendil-works/pi-ai/dist/auth/helpers.js +3 -2
  499. package/node_modules/@earendil-works/pi-ai/dist/auth/helpers.js.map +1 -1
  500. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/kimi-coding.d.ts +10 -0
  501. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/kimi-coding.d.ts.map +1 -0
  502. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/kimi-coding.js +249 -0
  503. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/kimi-coding.js.map +1 -0
  504. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/load.d.ts +4 -0
  505. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/load.d.ts.map +1 -1
  506. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/load.js +10 -0
  507. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/load.js.map +1 -1
  508. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/openai-codex.d.ts.map +1 -1
  509. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/openai-codex.js +2 -18
  510. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/openai-codex.js.map +1 -1
  511. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/openrouter.d.ts +13 -0
  512. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/openrouter.d.ts.map +1 -0
  513. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/openrouter.js +222 -0
  514. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/openrouter.js.map +1 -0
  515. package/node_modules/@earendil-works/pi-ai/dist/bun-oauth.d.ts.map +1 -1
  516. package/node_modules/@earendil-works/pi-ai/dist/bun-oauth.js +4 -0
  517. package/node_modules/@earendil-works/pi-ai/dist/bun-oauth.js.map +1 -1
  518. package/node_modules/@earendil-works/pi-ai/dist/compat.d.ts.map +1 -1
  519. package/node_modules/@earendil-works/pi-ai/dist/compat.js +2 -1
  520. package/node_modules/@earendil-works/pi-ai/dist/compat.js.map +1 -1
  521. package/node_modules/@earendil-works/pi-ai/dist/context-provenance.d.ts +16 -0
  522. package/node_modules/@earendil-works/pi-ai/dist/context-provenance.d.ts.map +1 -0
  523. package/node_modules/@earendil-works/pi-ai/dist/context-provenance.js +32 -0
  524. package/node_modules/@earendil-works/pi-ai/dist/context-provenance.js.map +1 -0
  525. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.d.ts +3 -0
  526. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.d.ts.map +1 -1
  527. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js +11 -3
  528. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js.map +1 -1
  529. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts +15 -0
  530. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts.map +1 -1
  531. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js +15 -0
  532. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js.map +1 -1
  533. package/node_modules/@earendil-works/pi-ai/dist/images-api-registry.d.ts +19 -2
  534. package/node_modules/@earendil-works/pi-ai/dist/images-api-registry.d.ts.map +1 -1
  535. package/node_modules/@earendil-works/pi-ai/dist/images-api-registry.js +68 -3
  536. package/node_modules/@earendil-works/pi-ai/dist/images-api-registry.js.map +1 -1
  537. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +5 -0
  538. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
  539. package/node_modules/@earendil-works/pi-ai/dist/index.js +5 -0
  540. package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
  541. package/node_modules/@earendil-works/pi-ai/dist/model-catalog.d.ts +17 -0
  542. package/node_modules/@earendil-works/pi-ai/dist/model-catalog.d.ts.map +1 -0
  543. package/node_modules/@earendil-works/pi-ai/dist/model-catalog.js +4 -0
  544. package/node_modules/@earendil-works/pi-ai/dist/model-catalog.js.map +1 -0
  545. package/node_modules/@earendil-works/pi-ai/dist/model.d.ts +2 -2
  546. package/node_modules/@earendil-works/pi-ai/dist/model.d.ts.map +1 -1
  547. package/node_modules/@earendil-works/pi-ai/dist/model.js.map +1 -1
  548. package/node_modules/@earendil-works/pi-ai/dist/models-store.d.ts +2 -0
  549. package/node_modules/@earendil-works/pi-ai/dist/models-store.d.ts.map +1 -1
  550. package/node_modules/@earendil-works/pi-ai/dist/models-store.js.map +1 -1
  551. package/node_modules/@earendil-works/pi-ai/dist/models.d.ts +0 -6
  552. package/node_modules/@earendil-works/pi-ai/dist/models.d.ts.map +1 -1
  553. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +76 -4448
  554. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
  555. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +4 -0
  556. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js.map +1 -1
  557. package/node_modules/@earendil-works/pi-ai/dist/models.js +0 -53
  558. package/node_modules/@earendil-works/pi-ai/dist/models.js.map +1 -1
  559. package/node_modules/@earendil-works/pi-ai/dist/node/provider-scope.d.ts +12 -0
  560. package/node_modules/@earendil-works/pi-ai/dist/node/provider-scope.d.ts.map +1 -0
  561. package/node_modules/@earendil-works/pi-ai/dist/node/provider-scope.js +34 -0
  562. package/node_modules/@earendil-works/pi-ai/dist/node/provider-scope.js.map +1 -0
  563. package/node_modules/@earendil-works/pi-ai/dist/providers/alibaba-token-plan.models.d.ts +3 -63
  564. package/node_modules/@earendil-works/pi-ai/dist/providers/alibaba-token-plan.models.d.ts.map +1 -1
  565. package/node_modules/@earendil-works/pi-ai/dist/providers/alibaba-token-plan.models.js +2 -1
  566. package/node_modules/@earendil-works/pi-ai/dist/providers/alibaba-token-plan.models.js.map +1 -1
  567. package/node_modules/@earendil-works/pi-ai/dist/providers/all.d.ts +2 -0
  568. package/node_modules/@earendil-works/pi-ai/dist/providers/all.d.ts.map +1 -1
  569. package/node_modules/@earendil-works/pi-ai/dist/providers/all.js +10 -0
  570. package/node_modules/@earendil-works/pi-ai/dist/providers/all.js.map +1 -1
  571. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.d.ts.map +1 -1
  572. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js +3 -2
  573. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
  574. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.d.ts +3 -439
  575. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.d.ts.map +1 -1
  576. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.js +2 -1
  577. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.js.map +1 -1
  578. package/node_modules/@earendil-works/pi-ai/dist/providers/ant-ling.models.d.ts +3 -15
  579. package/node_modules/@earendil-works/pi-ai/dist/providers/ant-ling.models.d.ts.map +1 -1
  580. package/node_modules/@earendil-works/pi-ai/dist/providers/ant-ling.models.js +2 -1
  581. package/node_modules/@earendil-works/pi-ai/dist/providers/ant-ling.models.js.map +1 -1
  582. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  583. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js +30 -3
  584. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js.map +1 -1
  585. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.d.ts +3 -59
  586. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.d.ts.map +1 -1
  587. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.js +2 -1
  588. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.js.map +1 -1
  589. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.models.d.ts +3 -187
  590. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.models.d.ts.map +1 -1
  591. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.models.js +2 -1
  592. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.models.js.map +1 -1
  593. package/node_modules/@earendil-works/pi-ai/dist/providers/cerebras.models.d.ts +3 -15
  594. package/node_modules/@earendil-works/pi-ai/dist/providers/cerebras.models.d.ts.map +1 -1
  595. package/node_modules/@earendil-works/pi-ai/dist/providers/cerebras.models.js +2 -1
  596. package/node_modules/@earendil-works/pi-ai/dist/providers/cerebras.models.js.map +1 -1
  597. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.models.d.ts +3 -171
  598. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.models.d.ts.map +1 -1
  599. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.models.js +2 -1
  600. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.models.js.map +1 -1
  601. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-workers-ai.models.d.ts +3 -55
  602. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-workers-ai.models.d.ts.map +1 -1
  603. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-workers-ai.models.js +2 -1
  604. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-workers-ai.models.js.map +1 -1
  605. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -0
  606. package/node_modules/@earendil-works/pi-ai/dist/providers/data/alibaba-token-plan.json +1 -1
  607. package/node_modules/@earendil-works/pi-ai/dist/providers/data/amazon-bedrock.json +1 -1
  608. package/node_modules/@earendil-works/pi-ai/dist/providers/data/ant-ling.json +1 -1
  609. package/node_modules/@earendil-works/pi-ai/dist/providers/data/anthropic.json +1 -1
  610. package/node_modules/@earendil-works/pi-ai/dist/providers/data/azure-openai-responses.json +1 -1
  611. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cerebras.json +1 -1
  612. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  613. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-workers-ai.json +1 -1
  614. package/node_modules/@earendil-works/pi-ai/dist/providers/data/deepseek.json +1 -1
  615. package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
  616. package/node_modules/@earendil-works/pi-ai/dist/providers/data/github-copilot.json +1 -1
  617. package/node_modules/@earendil-works/pi-ai/dist/providers/data/google-vertex.json +1 -1
  618. package/node_modules/@earendil-works/pi-ai/dist/providers/data/google.json +1 -1
  619. package/node_modules/@earendil-works/pi-ai/dist/providers/data/groq.json +1 -1
  620. package/node_modules/@earendil-works/pi-ai/dist/providers/data/huggingface.json +1 -1
  621. package/node_modules/@earendil-works/pi-ai/dist/providers/data/kimi-coding.json +1 -1
  622. package/node_modules/@earendil-works/pi-ai/dist/providers/data/minimax-cn.json +1 -1
  623. package/node_modules/@earendil-works/pi-ai/dist/providers/data/minimax.json +1 -1
  624. package/node_modules/@earendil-works/pi-ai/dist/providers/data/mistral.json +1 -1
  625. package/node_modules/@earendil-works/pi-ai/dist/providers/data/moonshotai-cn.json +1 -1
  626. package/node_modules/@earendil-works/pi-ai/dist/providers/data/moonshotai.json +1 -1
  627. package/node_modules/@earendil-works/pi-ai/dist/providers/data/nvidia.json +1 -1
  628. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openai-codex.json +1 -1
  629. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openai.json +1 -1
  630. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
  631. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  632. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  633. package/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan-cn.json +1 -0
  634. package/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan.json +1 -0
  635. package/node_modules/@earendil-works/pi-ai/dist/providers/data/together.json +1 -1
  636. package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  637. package/node_modules/@earendil-works/pi-ai/dist/providers/data/xai.json +1 -1
  638. package/node_modules/@earendil-works/pi-ai/dist/providers/data/xiaomi-token-plan-ams.json +1 -1
  639. package/node_modules/@earendil-works/pi-ai/dist/providers/data/xiaomi-token-plan-cn.json +1 -1
  640. package/node_modules/@earendil-works/pi-ai/dist/providers/data/xiaomi-token-plan-sgp.json +1 -1
  641. package/node_modules/@earendil-works/pi-ai/dist/providers/data/xiaomi.json +1 -1
  642. package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai-coding-cn.json +1 -1
  643. package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai.json +1 -1
  644. package/node_modules/@earendil-works/pi-ai/dist/providers/deepseek.models.d.ts +3 -11
  645. package/node_modules/@earendil-works/pi-ai/dist/providers/deepseek.models.d.ts.map +1 -1
  646. package/node_modules/@earendil-works/pi-ai/dist/providers/deepseek.models.js +2 -1
  647. package/node_modules/@earendil-works/pi-ai/dist/providers/deepseek.models.js.map +1 -1
  648. package/node_modules/@earendil-works/pi-ai/dist/providers/fireworks.models.d.ts +3 -67
  649. package/node_modules/@earendil-works/pi-ai/dist/providers/fireworks.models.d.ts.map +1 -1
  650. package/node_modules/@earendil-works/pi-ai/dist/providers/fireworks.models.js +2 -1
  651. package/node_modules/@earendil-works/pi-ai/dist/providers/fireworks.models.js.map +1 -1
  652. package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.d.ts +3 -115
  653. package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.d.ts.map +1 -1
  654. package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.js +2 -1
  655. package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.js.map +1 -1
  656. package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.models.d.ts +3 -51
  657. package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.models.d.ts.map +1 -1
  658. package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.models.js +2 -1
  659. package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.models.js.map +1 -1
  660. package/node_modules/@earendil-works/pi-ai/dist/providers/google.models.d.ts +3 -75
  661. package/node_modules/@earendil-works/pi-ai/dist/providers/google.models.d.ts.map +1 -1
  662. package/node_modules/@earendil-works/pi-ai/dist/providers/google.models.js +2 -1
  663. package/node_modules/@earendil-works/pi-ai/dist/providers/google.models.js.map +1 -1
  664. package/node_modules/@earendil-works/pi-ai/dist/providers/groq.models.d.ts +3 -31
  665. package/node_modules/@earendil-works/pi-ai/dist/providers/groq.models.d.ts.map +1 -1
  666. package/node_modules/@earendil-works/pi-ai/dist/providers/groq.models.js +2 -1
  667. package/node_modules/@earendil-works/pi-ai/dist/providers/groq.models.js.map +1 -1
  668. package/node_modules/@earendil-works/pi-ai/dist/providers/huggingface.models.d.ts +3 -203
  669. package/node_modules/@earendil-works/pi-ai/dist/providers/huggingface.models.d.ts.map +1 -1
  670. package/node_modules/@earendil-works/pi-ai/dist/providers/huggingface.models.js +2 -1
  671. package/node_modules/@earendil-works/pi-ai/dist/providers/huggingface.models.js.map +1 -1
  672. package/node_modules/@earendil-works/pi-ai/dist/providers/images/register-builtins.js +2 -2
  673. package/node_modules/@earendil-works/pi-ai/dist/providers/images/register-builtins.js.map +1 -1
  674. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.d.ts.map +1 -1
  675. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.js +10 -2
  676. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.js.map +1 -1
  677. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.models.d.ts +3 -19
  678. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.models.d.ts.map +1 -1
  679. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.models.js +2 -1
  680. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.models.js.map +1 -1
  681. package/node_modules/@earendil-works/pi-ai/dist/providers/minimax-cn.models.d.ts +3 -15
  682. package/node_modules/@earendil-works/pi-ai/dist/providers/minimax-cn.models.d.ts.map +1 -1
  683. package/node_modules/@earendil-works/pi-ai/dist/providers/minimax-cn.models.js +2 -1
  684. package/node_modules/@earendil-works/pi-ai/dist/providers/minimax-cn.models.js.map +1 -1
  685. package/node_modules/@earendil-works/pi-ai/dist/providers/minimax.models.d.ts +3 -15
  686. package/node_modules/@earendil-works/pi-ai/dist/providers/minimax.models.d.ts.map +1 -1
  687. package/node_modules/@earendil-works/pi-ai/dist/providers/minimax.models.js +2 -1
  688. package/node_modules/@earendil-works/pi-ai/dist/providers/minimax.models.js.map +1 -1
  689. package/node_modules/@earendil-works/pi-ai/dist/providers/mistral.models.d.ts +3 -123
  690. package/node_modules/@earendil-works/pi-ai/dist/providers/mistral.models.d.ts.map +1 -1
  691. package/node_modules/@earendil-works/pi-ai/dist/providers/mistral.models.js +2 -1
  692. package/node_modules/@earendil-works/pi-ai/dist/providers/mistral.models.js.map +1 -1
  693. package/node_modules/@earendil-works/pi-ai/dist/providers/moonshotai-cn.models.d.ts +3 -43
  694. package/node_modules/@earendil-works/pi-ai/dist/providers/moonshotai-cn.models.d.ts.map +1 -1
  695. package/node_modules/@earendil-works/pi-ai/dist/providers/moonshotai-cn.models.js +2 -1
  696. package/node_modules/@earendil-works/pi-ai/dist/providers/moonshotai-cn.models.js.map +1 -1
  697. package/node_modules/@earendil-works/pi-ai/dist/providers/moonshotai.models.d.ts +3 -43
  698. package/node_modules/@earendil-works/pi-ai/dist/providers/moonshotai.models.d.ts.map +1 -1
  699. package/node_modules/@earendil-works/pi-ai/dist/providers/moonshotai.models.js +2 -1
  700. package/node_modules/@earendil-works/pi-ai/dist/providers/moonshotai.models.js.map +1 -1
  701. package/node_modules/@earendil-works/pi-ai/dist/providers/nvidia.models.d.ts +3 -79
  702. package/node_modules/@earendil-works/pi-ai/dist/providers/nvidia.models.d.ts.map +1 -1
  703. package/node_modules/@earendil-works/pi-ai/dist/providers/nvidia.models.js +2 -1
  704. package/node_modules/@earendil-works/pi-ai/dist/providers/nvidia.models.js.map +1 -1
  705. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.models.d.ts +3 -31
  706. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.models.d.ts.map +1 -1
  707. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.models.js +2 -1
  708. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.models.js.map +1 -1
  709. package/node_modules/@earendil-works/pi-ai/dist/providers/openai.models.d.ts +3 -187
  710. package/node_modules/@earendil-works/pi-ai/dist/providers/openai.models.d.ts.map +1 -1
  711. package/node_modules/@earendil-works/pi-ai/dist/providers/openai.models.js +2 -1
  712. package/node_modules/@earendil-works/pi-ai/dist/providers/openai.models.js.map +1 -1
  713. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.models.d.ts +3 -67
  714. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.models.d.ts.map +1 -1
  715. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.models.js +2 -1
  716. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.models.js.map +1 -1
  717. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.d.ts +3 -227
  718. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.d.ts.map +1 -1
  719. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.js +2 -1
  720. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.js.map +1 -1
  721. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter-images.d.ts.map +1 -1
  722. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter-images.js +10 -2
  723. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter-images.js.map +1 -1
  724. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.d.ts.map +1 -1
  725. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.js +10 -2
  726. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.js.map +1 -1
  727. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts +3 -1095
  728. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts.map +1 -1
  729. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js +2 -1
  730. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js.map +1 -1
  731. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-cn.d.ts +3 -0
  732. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-cn.d.ts.map +1 -0
  733. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-cn.js +15 -0
  734. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-cn.js.map +1 -0
  735. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-cn.models.d.ts +4 -0
  736. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-cn.models.d.ts.map +1 -0
  737. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-cn.models.js +6 -0
  738. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-cn.models.js.map +1 -0
  739. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan.d.ts +3 -0
  740. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan.d.ts.map +1 -0
  741. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan.js +15 -0
  742. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan.js.map +1 -0
  743. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan.models.d.ts +4 -0
  744. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan.models.d.ts.map +1 -0
  745. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan.models.js +6 -0
  746. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan.models.js.map +1 -0
  747. package/node_modules/@earendil-works/pi-ai/dist/providers/radius-config.d.ts.map +1 -1
  748. package/node_modules/@earendil-works/pi-ai/dist/providers/radius-config.js +18 -14
  749. package/node_modules/@earendil-works/pi-ai/dist/providers/radius-config.js.map +1 -1
  750. package/node_modules/@earendil-works/pi-ai/dist/providers/together.models.d.ts +3 -67
  751. package/node_modules/@earendil-works/pi-ai/dist/providers/together.models.d.ts.map +1 -1
  752. package/node_modules/@earendil-works/pi-ai/dist/providers/together.models.js +2 -1
  753. package/node_modules/@earendil-works/pi-ai/dist/providers/together.models.js.map +1 -1
  754. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts +3 -775
  755. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts.map +1 -1
  756. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js +2 -1
  757. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js.map +1 -1
  758. package/node_modules/@earendil-works/pi-ai/dist/providers/xai.models.d.ts +3 -15
  759. package/node_modules/@earendil-works/pi-ai/dist/providers/xai.models.d.ts.map +1 -1
  760. package/node_modules/@earendil-works/pi-ai/dist/providers/xai.models.js +2 -1
  761. package/node_modules/@earendil-works/pi-ai/dist/providers/xai.models.js.map +1 -1
  762. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.d.ts +3 -15
  763. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.d.ts.map +1 -1
  764. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.js +2 -1
  765. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.js.map +1 -1
  766. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.d.ts +3 -15
  767. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.d.ts.map +1 -1
  768. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.js +2 -1
  769. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.js.map +1 -1
  770. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.d.ts +3 -15
  771. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.d.ts.map +1 -1
  772. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.js +2 -1
  773. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.js.map +1 -1
  774. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi.models.d.ts +3 -27
  775. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi.models.d.ts.map +1 -1
  776. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi.models.js +2 -1
  777. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi.models.js.map +1 -1
  778. package/node_modules/@earendil-works/pi-ai/dist/providers/zai-coding-cn.models.d.ts +3 -27
  779. package/node_modules/@earendil-works/pi-ai/dist/providers/zai-coding-cn.models.d.ts.map +1 -1
  780. package/node_modules/@earendil-works/pi-ai/dist/providers/zai-coding-cn.models.js +2 -1
  781. package/node_modules/@earendil-works/pi-ai/dist/providers/zai-coding-cn.models.js.map +1 -1
  782. package/node_modules/@earendil-works/pi-ai/dist/providers/zai.models.d.ts +3 -27
  783. package/node_modules/@earendil-works/pi-ai/dist/providers/zai.models.d.ts.map +1 -1
  784. package/node_modules/@earendil-works/pi-ai/dist/providers/zai.models.js +2 -1
  785. package/node_modules/@earendil-works/pi-ai/dist/providers/zai.models.js.map +1 -1
  786. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts +43 -3
  787. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts.map +1 -1
  788. package/node_modules/@earendil-works/pi-ai/dist/types.js.map +1 -1
  789. package/node_modules/@earendil-works/pi-ai/dist/utils/openai-codex-auth.d.ts +11 -0
  790. package/node_modules/@earendil-works/pi-ai/dist/utils/openai-codex-auth.d.ts.map +1 -0
  791. package/node_modules/@earendil-works/pi-ai/dist/utils/openai-codex-auth.js +30 -0
  792. package/node_modules/@earendil-works/pi-ai/dist/utils/openai-codex-auth.js.map +1 -0
  793. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts +1 -0
  794. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts.map +1 -1
  795. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js +3 -0
  796. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js.map +1 -1
  797. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-retry.d.ts +16 -0
  798. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-retry.d.ts.map +1 -0
  799. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-retry.js +94 -0
  800. package/node_modules/@earendil-works/pi-ai/dist/utils/provider-retry.js.map +1 -0
  801. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts +41 -0
  802. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts.map +1 -1
  803. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +82 -0
  804. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js.map +1 -1
  805. package/node_modules/@earendil-works/pi-ai/dist/utils/text.d.ts +6 -0
  806. package/node_modules/@earendil-works/pi-ai/dist/utils/text.d.ts.map +1 -0
  807. package/node_modules/@earendil-works/pi-ai/dist/utils/text.js +10 -0
  808. package/node_modules/@earendil-works/pi-ai/dist/utils/text.js.map +1 -0
  809. package/node_modules/@earendil-works/pi-ai/dist/utils/uuid.d.ts +3 -0
  810. package/node_modules/@earendil-works/pi-ai/dist/utils/uuid.d.ts.map +1 -0
  811. package/node_modules/@earendil-works/{pi-agent-core/dist/harness/session → pi-ai/dist/utils}/uuid.js +2 -2
  812. package/node_modules/@earendil-works/pi-ai/dist/utils/uuid.js.map +1 -0
  813. package/node_modules/@earendil-works/pi-ai/package.json +11 -4
  814. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  815. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +1 -1
  816. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +31 -22
  817. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +1 -1
  818. package/node_modules/@earendil-works/pi-tui/dist/tui.d.ts.map +1 -1
  819. package/node_modules/@earendil-works/pi-tui/dist/tui.js +2 -0
  820. package/node_modules/@earendil-works/pi-tui/dist/tui.js.map +1 -1
  821. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  822. package/package.json +7 -6
  823. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/uuid.d.ts +0 -2
  824. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/uuid.d.ts.map +0 -1
  825. 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 } 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,14 +240,42 @@ 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
+ const pendingMessage = event.type === "message_end" ? event.message : undefined;
269
+ if (pendingMessage !== undefined) {
270
+ this._messageEndsAwaitingPersistence.add(pendingMessage);
271
+ }
272
+ const processing = this._agentEventQueue.then(() => this._processAgentEvent(event, signal), () => this._processAgentEvent(event, signal));
273
+ this._agentEventQueue =
274
+ pendingMessage !== undefined
275
+ ? processing.finally(() => {
276
+ this._messageEndsAwaitingPersistence.delete(pendingMessage);
277
+ })
278
+ : processing;
187
279
  // Keep queue alive if an event handler fails
188
280
  this._agentEventQueue.catch(() => { });
189
281
  };
@@ -221,8 +313,9 @@ export class AgentSession {
221
313
  persistDefault: false,
222
314
  appendSessionEntry: true,
223
315
  entryReason: reason,
224
- emitModelSelect: reason === "fallback-revert",
225
- invalidateCompaction: false,
316
+ emitModelSelect: true,
317
+ modelSelectSource: reason,
318
+ invalidateCompaction: true,
226
319
  ephemeralThinkingLevel: thinking,
227
320
  });
228
321
  },
@@ -268,7 +361,7 @@ export class AgentSession {
268
361
  }
269
362
  throw error;
270
363
  }
271
- if (result?.auth.apiKey) {
364
+ if (result && (result.auth.apiKey || result.auth.headers)) {
272
365
  return {
273
366
  apiKey: result.auth.apiKey,
274
367
  headers: withoutDeletedHeaders(result.auth.headers),
@@ -284,26 +377,31 @@ export class AgentSession {
284
377
  }
285
378
  throw new Error(formatNoApiKeyFoundMessage(model.provider));
286
379
  }
287
- async _getCompactionRequestAuth(model) {
288
- if (this.agent.streamFn === streamSimple) {
380
+ /**
381
+ * Resolve optional auth for a summarization stream. Native/custom stream
382
+ * functions may provide ambient credentials, unlike streamSimple.
383
+ */
384
+ async _getSummarizationRequestAuth(model) {
385
+ if (this.agent.streamFunction === streamSimple) {
289
386
  return this._getRequiredRequestAuth(model);
290
387
  }
291
- const compatibility = this._modelRuntime.getCompatibilityRequestConfig(model);
292
388
  try {
293
389
  const result = await this._modelRuntime.getAuth(model);
294
390
  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 };
391
+ ? { apiKey: result.auth.apiKey, headers: withoutDeletedHeaders(result.auth.headers), env: result.env }
392
+ : {};
302
393
  }
303
394
  catch {
304
- return { extraBody: compatibility.extraBody };
395
+ return {};
305
396
  }
306
397
  }
398
+ async _getCompactionRequestAuth(model) {
399
+ const auth = await this._getSummarizationRequestAuth(model);
400
+ return {
401
+ ...auth,
402
+ extraBody: this._modelRuntime.getCompatibilityRequestConfig(model).extraBody,
403
+ };
404
+ }
307
405
  /**
308
406
  * Install tool hooks once on the Agent instance.
309
407
  *
@@ -354,6 +452,7 @@ export class AgentSession {
354
452
  content: result.content,
355
453
  details: result.details,
356
454
  isError,
455
+ usage: result.usage,
357
456
  });
358
457
  if (!hookResult) {
359
458
  return undefined;
@@ -362,6 +461,7 @@ export class AgentSession {
362
461
  content: hookResult.content,
363
462
  details: hookResult.details,
364
463
  isError: hookResult.isError ?? isError,
464
+ usage: hookResult.usage,
365
465
  };
366
466
  }
367
467
  _installAgentNextTurnRefresh() {
@@ -370,31 +470,68 @@ export class AgentSession {
370
470
  ? async (_turn, signal) => await this.agent.prepareNextTurn?.(signal)
371
471
  : undefined);
372
472
  this.agent.prepareNextTurnWithContext = async (turn, signal) => {
373
- let messages = turn.context.messages;
374
- if (turn.toolResults.length > 0) {
473
+ // Enforce compaction only when this prepare precedes an actual provider
474
+ // admission: a tool continuation or queued steer/follow-up messages. A
475
+ // completed turn with no continuation keeps pre-PR timing, while the
476
+ // prior prepare callback and context refresh below still run every turn.
477
+ const compactBeforeNextAdmission = async () => {
478
+ if (turn.toolResults.length === 0 && !this.agent.hasQueuedMessages()) {
479
+ return false;
480
+ }
375
481
  await this._agentEventQueue;
376
- const compacted = await this._checkCompaction(turn.message, true, "threshold");
377
- if (compacted) {
378
- messages = this.agent.state.messages.slice();
482
+ // A queue can be cleared while waiting for persistence. Re-sample it
483
+ // immediately before compaction so a completed turn never compacts
484
+ // merely because it once had a possible continuation.
485
+ if (turn.toolResults.length === 0 && !this.agent.hasQueuedMessages()) {
486
+ return false;
379
487
  }
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();
488
+ try {
489
+ return await this._enforceCompactionBeforeProvider(turn.message, true, "threshold");
490
+ }
491
+ catch (error) {
492
+ if (error instanceof RequiredCompactionError && this.agent.hasQueuedMessages()) {
493
+ this._requiredCompactionAdmissionError = error;
385
494
  }
495
+ throw error;
386
496
  }
387
- }
497
+ };
498
+ const compactedBeforeCallback = await compactBeforeNextAdmission();
499
+ const messages = compactedBeforeCallback ? this.agent.state.messages.slice() : turn.context.messages;
388
500
  const postCompactionTurn = {
389
501
  ...turn,
390
502
  context: { ...turn.context, messages },
391
503
  };
392
- const previousSnapshot = await previousPrepareNextTurnWithContext?.(postCompactionTurn, signal);
393
- const previousContext = previousSnapshot?.context ?? postCompactionTurn.context;
504
+ let previousSnapshot = await previousPrepareNextTurnWithContext?.(postCompactionTurn, signal);
505
+ let previousContext = previousSnapshot?.context ?? postCompactionTurn.context;
506
+ // The previous callback may await while agent_end extensions enqueue
507
+ // continuation work. Re-sample after it returns so that work cannot
508
+ // slip through with the stale provider snapshot it observed on entry.
509
+ let compactedAfterCallback = false;
510
+ if (!compactedBeforeCallback) {
511
+ compactedAfterCallback = await compactBeforeNextAdmission();
512
+ }
513
+ if (compactedAfterCallback) {
514
+ // The callback's first result describes the stale pre-compaction
515
+ // context. Reapply it once to the compacted context so any host
516
+ // transformation reaches the provider request. Do not re-sample after
517
+ // this invocation: one replay is the bounded admission path.
518
+ const postLateCompactionTurn = {
519
+ ...turn,
520
+ context: { ...turn.context, messages: this.agent.state.messages.slice() },
521
+ };
522
+ const postLateCompactionSnapshot = await previousPrepareNextTurnWithContext?.(postLateCompactionTurn, signal);
523
+ previousSnapshot = {
524
+ ...previousSnapshot,
525
+ ...postLateCompactionSnapshot,
526
+ context: postLateCompactionSnapshot?.context ?? postLateCompactionTurn.context,
527
+ };
528
+ previousContext = previousSnapshot.context ?? postLateCompactionTurn.context;
529
+ }
394
530
  return {
395
531
  ...previousSnapshot,
396
532
  context: {
397
533
  ...previousContext,
534
+ messages: previousContext.messages,
398
535
  systemPrompt: this._systemPromptOverride ?? this._baseSystemPrompt,
399
536
  tools: this.agent.state.tools.slice(),
400
537
  },
@@ -421,6 +558,7 @@ export class AgentSession {
421
558
  }
422
559
  _incrementMessageRevision() {
423
560
  this._messageRevision++;
561
+ this._blockedPostCompactionAssistant = undefined;
424
562
  }
425
563
  getMessageRevision() {
426
564
  return this._messageRevision;
@@ -481,14 +619,46 @@ export class AgentSession {
481
619
  }
482
620
  async _promptAgent(messages) {
483
621
  this._isAgentRunActive = true;
622
+ this._requiredCompactionAdmissionError = undefined;
484
623
  try {
485
624
  await this.agent.prompt(messages);
625
+ // AgentSession's subscriber intentionally queues event work instead of
626
+ // blocking Agent core. Wait for this run's queued recovery decision
627
+ // before reporting prompt completion to the caller.
628
+ await this._agentEventQueue;
629
+ const requiredCompactionError = this._requiredCompactionAdmissionError;
630
+ this._requiredCompactionAdmissionError = undefined;
631
+ if (requiredCompactionError) {
632
+ throw requiredCompactionError;
633
+ }
486
634
  }
487
635
  catch (error) {
636
+ if (error instanceof Error &&
637
+ error.message ===
638
+ "Agent is already processing a prompt. Use steer() or followUp() to queue messages, or wait for completion.") {
639
+ const queuedMessages = Array.isArray(messages) ? messages : [messages];
640
+ for (const message of queuedMessages)
641
+ this.agent.steer(message);
642
+ const userMessage = queuedMessages.find((message) => message.role === "user");
643
+ if (userMessage?.role === "user") {
644
+ this._steeringMessages.push(this._extractUserMessageText(userMessage.content));
645
+ this._emitQueueUpdate();
646
+ }
647
+ return;
648
+ }
488
649
  await this._emitAgentSettled();
489
650
  throw error;
490
651
  }
491
652
  }
653
+ /** Extract text content used to track fork-owned queued user messages. */
654
+ _extractUserMessageText(content) {
655
+ if (typeof content === "string")
656
+ return content;
657
+ return content
658
+ .filter((part) => part.type === "text")
659
+ .map((part) => part.text)
660
+ .join("");
661
+ }
492
662
  _createRetryPromiseForAgentEnd(event) {
493
663
  if (event.type !== "agent_end" || this._retryPromise) {
494
664
  return;
@@ -505,6 +675,10 @@ export class AgentSession {
505
675
  this._retryPromise = new Promise((resolve) => {
506
676
  this._retryResolve = resolve;
507
677
  });
678
+ // Agent core normally drains queued input immediately after agent_end.
679
+ // Retry owns that continuation until its final provider-admission check has
680
+ // either started it or reported a terminal rejection.
681
+ this.agent.suppressQueuedMessageDrain();
508
682
  }
509
683
  _findLastAssistantInMessages(messages) {
510
684
  for (let i = messages.length - 1; i >= 0; i--) {
@@ -515,6 +689,91 @@ export class AgentSession {
515
689
  }
516
690
  return undefined;
517
691
  }
692
+ /**
693
+ * Synchronously mirror the auto-compaction decision that _checkCompaction()
694
+ * will make after agent_end. Agent core drains queues before that async work
695
+ * runs, so only this preflight can transfer required admissions safely.
696
+ */
697
+ _getRequiredAutoCompactionReason(message) {
698
+ const reason = this._getAutoCompactionReason(message);
699
+ // Retry and post-compaction exemptions only cover stale usage estimates.
700
+ // A provider-confirmed overflow must always retain queue ownership and run
701
+ // fail-closed recovery.
702
+ if (reason === "overflow" &&
703
+ message.stopReason === "stop" &&
704
+ this._hasPendingPostCompactionUsageExemption(message)) {
705
+ // A successful post-compaction response can carry stale provider usage.
706
+ // Retain its queues while the asynchronous check consumes the exemption.
707
+ return "threshold";
708
+ }
709
+ if (reason === "overflow")
710
+ return reason;
711
+ // Keep queued continuations under AgentSession ownership while the
712
+ // asynchronous check consumes this post-compaction usage exemption.
713
+ if (this._hasPendingPostCompactionUsageExemption(message))
714
+ return "threshold";
715
+ if (reason === "threshold" &&
716
+ (this._skipNextPostRetryCompactionCheck || this._postCompactionUsageExemptAssistants.has(message))) {
717
+ return undefined;
718
+ }
719
+ return reason;
720
+ }
721
+ _getAutoCompactionReason(message) {
722
+ const settings = this.settingsManager.getCompactionSettings();
723
+ if (!settings.enabled || message.stopReason === "aborted") {
724
+ return undefined;
725
+ }
726
+ const model = this.model;
727
+ if (!model || this._isAssistantFromBeforeLatestCompaction(message)) {
728
+ return undefined;
729
+ }
730
+ const sameModel = isSameOverflowSource(message, model, this._modelRuntime.getCompatibilityRequestConfig(model).upstreamModelId);
731
+ const contextUsage = this.getContextUsage();
732
+ const currentContextNeedsCompaction = contextUsage !== undefined &&
733
+ contextUsage.tokens !== null &&
734
+ shouldCompact(contextUsage.tokens, contextUsage.contextWindow, settings);
735
+ if (isContextOverflow(message, model.contextWindow) && (sameModel || currentContextNeedsCompaction)) {
736
+ return "overflow";
737
+ }
738
+ let contextTokens;
739
+ const directContextTokens = message.usage ? calculateContextTokens(message.usage) : 0;
740
+ if (message.stopReason !== "error" && directContextTokens !== 0) {
741
+ contextTokens = directContextTokens;
742
+ }
743
+ else {
744
+ const messages = filterContextExcludedMessages(this.agent.state.messages);
745
+ const estimate = estimateContextTokens(messages);
746
+ if (estimate.lastUsageIndex === null) {
747
+ return undefined;
748
+ }
749
+ const compactionEntry = getLatestCompactionEntry(this.sessionManager.getBranch());
750
+ const usageMessage = messages[estimate.lastUsageIndex];
751
+ if (compactionEntry &&
752
+ usageMessage?.role === "assistant" &&
753
+ this._isAssistantFromBeforeLatestCompaction(usageMessage)) {
754
+ return undefined;
755
+ }
756
+ contextTokens = estimate.tokens;
757
+ }
758
+ return shouldCompact(contextTokens, model.contextWindow, settings) ? "threshold" : undefined;
759
+ }
760
+ _hasPendingPostCompactionUsageExemption(message) {
761
+ return (this._skipNextPostCompactionAssistantCheck &&
762
+ !this._assistantsPendingAtCompaction.has(message) &&
763
+ !this._overflowRecoveryAttempted);
764
+ }
765
+ _isPostCompactionUsageExempt(message) {
766
+ return (this._postCompactionUsageExemptAssistants.has(message) || this._hasPendingPostCompactionUsageExemption(message));
767
+ }
768
+ _consumePostCompactionUsageExemption(message) {
769
+ if (this._postCompactionUsageExemptAssistants.has(message))
770
+ return true;
771
+ if (!this._isPostCompactionUsageExempt(message))
772
+ return false;
773
+ this._skipNextPostCompactionAssistantCheck = false;
774
+ this._postCompactionUsageExemptAssistants.add(message);
775
+ return true;
776
+ }
518
777
  _agentEndAllowsQueuedContinuation(messages) {
519
778
  let lastAssistantIndex = -1;
520
779
  for (let index = messages.length - 1; index >= 0; index--) {
@@ -533,6 +792,9 @@ export class AgentSession {
533
792
  if (lastAssistant.stopReason === "aborted" || lastAssistant.stopReason === "error") {
534
793
  return false;
535
794
  }
795
+ if (this._getRequiredAutoCompactionReason(lastAssistant)) {
796
+ return false;
797
+ }
536
798
  for (let index = lastAssistantIndex + 1; index < messages.length; index++) {
537
799
  const message = messages[index];
538
800
  if (message?.role === "toolResult" &&
@@ -572,13 +834,13 @@ export class AgentSession {
572
834
  }
573
835
  return providerDelayMs <= this.settingsManager.getProviderRetrySettings().maxRetryDelayMs;
574
836
  }
575
- async _processAgentEvent(event) {
837
+ async _processAgentEvent(event, signal) {
576
838
  // When a user message starts, check if it's from either queue and remove it BEFORE emitting
577
839
  // This ensures the UI sees the updated queue state
578
840
  if (event.type === "message_start" && event.message.role === "user") {
579
841
  this._overflowRecoveryAttempted = false;
580
842
  this._retryFallback.resetTurn();
581
- const messageText = this._getUserMessageText(event.message);
843
+ const messageText = contentText(event.message.content, "");
582
844
  if (messageText) {
583
845
  // Check steering queue first
584
846
  const steeringIndex = this._steeringMessages.indexOf(messageText);
@@ -596,8 +858,15 @@ export class AgentSession {
596
858
  }
597
859
  }
598
860
  }
599
- // Emit to extensions first
600
- await this._emitExtensionEvent(event);
861
+ // Emit to extensions first. Agent event persistence is intentionally
862
+ // asynchronous, so retain the source run signal while dispatching.
863
+ this._extensionEventSignal = signal;
864
+ try {
865
+ await this._emitExtensionEvent(event);
866
+ }
867
+ finally {
868
+ this._extensionEventSignal = undefined;
869
+ }
601
870
  // Notify all listeners
602
871
  this._emit(event.type === "agent_end" ? { ...event, willRetry: this._willRetryAfterAgentEnd(event.messages) } : event);
603
872
  // Handle session persistence
@@ -643,8 +912,10 @@ export class AgentSession {
643
912
  }
644
913
  }
645
914
  }
646
- // Check auto-retry and auto-compaction after agent completes
915
+ // Check auto-retry and auto-compaction after agent completes.
647
916
  let launchedContinuation = false;
917
+ let retryContinuationBlocked = false;
918
+ let allowsPostCompactionUsageExemptContinuation = false;
648
919
  const userAbortSuppressedQueuedContinuation = event.type === "agent_end" && this._suppressQueuedContinuationAfterUserAbort;
649
920
  if (userAbortSuppressedQueuedContinuation) {
650
921
  this._suppressQueuedContinuationAfterUserAbort = false;
@@ -655,23 +926,62 @@ export class AgentSession {
655
926
  if (event.type === "agent_end" && this._lastAssistantMessage) {
656
927
  const msg = this._lastAssistantMessage;
657
928
  this._lastAssistantMessage = undefined;
929
+ this._skipNextPostRetryCompactionCheck = false;
930
+ const requiredAutoCompaction = this._getRequiredAutoCompactionReason(msg);
658
931
  // Retry transient failures normally and eligible hard errors only through a fallback.
659
932
  const retryableError = this._isRetryableError(msg);
660
- let didRetry = false;
661
- if (retryableError) {
662
- didRetry = await this._handleRetryableError(msg);
933
+ const hardErrorFallbackEligible = this._isHardErrorFallbackEligible(msg);
934
+ const retryCanAdmitProvider = this.settingsManager.getRetrySettings().enabled && (retryableError || hardErrorFallbackEligible);
935
+ let compactedBeforeRetry = false;
936
+ if (retryCanAdmitProvider &&
937
+ requiredAutoCompaction &&
938
+ !(requiredAutoCompaction === "threshold" && this._hasPendingPostCompactionUsageExemption(msg))) {
939
+ this._retireFailedRetryAssistant(msg);
940
+ compactedBeforeRetry = await this._runPrePromptCompaction(msg, true, "threshold", true);
941
+ retryContinuationBlocked = !compactedBeforeRetry;
663
942
  }
664
- else if (this._isHardErrorFallbackEligible(msg)) {
665
- didRetry = await this._handleRetryableError(msg, { hardErrorFallback: true });
943
+ let retryOutcome = "not-handled";
944
+ if (!retryContinuationBlocked) {
945
+ if (retryableError) {
946
+ retryOutcome = await this._handleRetryableError(msg);
947
+ }
948
+ else if (hardErrorFallbackEligible) {
949
+ retryOutcome = await this._handleRetryableError(msg, { hardErrorFallback: true });
950
+ }
666
951
  }
667
- if (didRetry)
668
- return; // Retry was initiated, don't proceed to compaction
952
+ if (retryOutcome === "continued")
953
+ return;
669
954
  this._resolveRetry();
670
- launchedContinuation = await this._checkCompaction(msg);
955
+ retryContinuationBlocked ||= retryOutcome === "blocked";
956
+ if (!retryContinuationBlocked) {
957
+ if (compactedBeforeRetry && this.agent.hasQueuedMessages()) {
958
+ this._scheduleContinuationAfterCurrentEvent();
959
+ launchedContinuation = true;
960
+ }
961
+ else {
962
+ launchedContinuation = await this._checkCompaction(msg);
963
+ allowsPostCompactionUsageExemptContinuation = this._postCompactionUsageExemptAssistants.has(msg);
964
+ if (allowsPostCompactionUsageExemptContinuation) {
965
+ this._flushPostCompactionDeferredMessages();
966
+ }
967
+ // _runAutoCompaction() returns false both when recovery was rejected and
968
+ // when an accepted compaction had no queue to continue. Re-sample after
969
+ // it settles so only the still-required (rejected) case fails admission.
970
+ if (requiredAutoCompaction &&
971
+ !launchedContinuation &&
972
+ this.agent.hasQueuedMessages() &&
973
+ this._getRequiredAutoCompactionReason(msg) !== undefined) {
974
+ this._requiredCompactionAdmissionError = new RequiredCompactionError();
975
+ }
976
+ }
977
+ }
671
978
  }
672
979
  if (event.type === "agent_end") {
673
980
  this._flushPendingBashMessages();
674
- if (!launchedContinuation && allowsQueuedContinuation && this.agent.hasQueuedMessages()) {
981
+ if (!launchedContinuation &&
982
+ !retryContinuationBlocked &&
983
+ (allowsQueuedContinuation || allowsPostCompactionUsageExemptContinuation) &&
984
+ this.agent.hasQueuedMessages()) {
675
985
  this._scheduleContinuationAfterCurrentEvent();
676
986
  launchedContinuation = true;
677
987
  }
@@ -688,16 +998,6 @@ export class AgentSession {
688
998
  this._retryPromise = undefined;
689
999
  }
690
1000
  }
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
1001
  /** Find the last assistant message in agent state (including aborted ones) */
702
1002
  _findLastAssistantMessage() {
703
1003
  const messages = this.agent.state.messages;
@@ -709,9 +1009,47 @@ export class AgentSession {
709
1009
  }
710
1010
  return undefined;
711
1011
  }
1012
+ /**
1013
+ * Retry failures stay in append-only history but must not be retained in the
1014
+ * active context branch. Otherwise split-turn compaction keeps the failed
1015
+ * assistant response verbatim and cannot make progress before a retry.
1016
+ */
1017
+ _retireFailedRetryAssistant(message) {
1018
+ const position = this.sessionManager.getMessageEntryPosition(message);
1019
+ if (!position || this.sessionManager.getLeafId() !== position.entryId)
1020
+ return;
1021
+ const entry = this.sessionManager.getEntry(position.entryId);
1022
+ if (entry?.type !== "message")
1023
+ return;
1024
+ if (entry.parentId === null) {
1025
+ this.sessionManager.resetLeaf();
1026
+ }
1027
+ else {
1028
+ this.sessionManager.branch(entry.parentId);
1029
+ }
1030
+ const messageIndex = this.agent.state.messages.lastIndexOf(message);
1031
+ if (messageIndex !== -1) {
1032
+ this.agent.state.messages = this.agent.state.messages.slice(0, messageIndex);
1033
+ }
1034
+ this._incrementMessageRevision();
1035
+ }
712
1036
  _isAssistantFromBeforeLatestCompaction(assistantMessage) {
713
1037
  const compactionEntry = getLatestCompactionEntry(this.sessionManager.getBranch());
714
- return compactionEntry !== null && assistantMessage.timestamp <= new Date(compactionEntry.timestamp).getTime();
1038
+ if (compactionEntry === null)
1039
+ return false;
1040
+ // An agent message_end can still be awaiting persistence while compaction
1041
+ // commits. It is necessarily a post-boundary message, even when a provider
1042
+ // supplied an older payload timestamp.
1043
+ if (this._messageEndsAwaitingPersistence.has(assistantMessage))
1044
+ return false;
1045
+ const messagePosition = this.sessionManager.getMessageEntryPosition(assistantMessage);
1046
+ const compactionOrder = this.sessionManager.getEntryOrder(compactionEntry.id);
1047
+ if (messagePosition !== undefined && compactionOrder !== undefined) {
1048
+ return messagePosition.order <= compactionOrder;
1049
+ }
1050
+ // Reloaded/reconstructed messages have no runtime identity. Retain the
1051
+ // historical timestamp heuristic only for that compatibility path.
1052
+ return assistantMessage.timestamp <= new Date(compactionEntry.timestamp).getTime();
715
1053
  }
716
1054
  _replaceMessageInPlace(target, replacement) {
717
1055
  // Agent-core stores the finalized message object in its state before emitting message_end.
@@ -1006,17 +1344,28 @@ export class AgentSession {
1006
1344
  validToolNames.push(name);
1007
1345
  }
1008
1346
  }
1347
+ const activeToolNamesChanged = validToolNames.length !== this.agent.state.tools.length ||
1348
+ validToolNames.some((name, index) => name !== this.agent.state.tools[index]?.name);
1009
1349
  this.agent.state.tools = tools;
1010
1350
  // Rebuild base system prompt with new tool set
1011
1351
  this._baseSystemPrompt = this._rebuildSystemPrompt(validToolNames);
1012
1352
  this.agent.state.systemPrompt = this._systemPromptOverride ?? this._baseSystemPrompt;
1353
+ if (activeToolNamesChanged) {
1354
+ this.abortCompaction();
1355
+ this._incrementMessageRevision();
1356
+ }
1013
1357
  }
1014
1358
  /** Whether compaction or branch summarization is currently running */
1015
1359
  get isCompacting() {
1016
- return (this._autoCompactionAbortController !== undefined ||
1360
+ return (this._pendingCompactionAdmission !== undefined ||
1361
+ this._compactionLifecycle.state.status === "running" ||
1362
+ this._autoCompactionAbortController !== undefined ||
1017
1363
  this._compactionAbortController !== undefined ||
1018
1364
  this._branchSummaryAbortController !== undefined);
1019
1365
  }
1366
+ get compactionState() {
1367
+ return this._compactionLifecycle.state;
1368
+ }
1020
1369
  /** All messages including custom types like BashExecutionMessage */
1021
1370
  get messages() {
1022
1371
  return this.agent.state.messages;
@@ -1162,28 +1511,52 @@ export class AgentSession {
1162
1511
  await userAbortPromise;
1163
1512
  throwIfCancelled();
1164
1513
  }
1165
- const shouldWaitForSessionWork = options?.source !== "extension";
1514
+ const ownsPromptStart = !this.isStreaming && !this._promptStartPending && options?.streamingBehavior === undefined;
1515
+ if (ownsPromptStart)
1516
+ this._promptStartPending = true;
1517
+ // Extension bindings deliberately fire-and-forget sendUserMessage(), so an
1518
+ // extension callback cannot deadlock on this wait. The resulting provider
1519
+ // turn must still serialize behind compaction and other session work just
1520
+ // like an interactive prompt does.
1521
+ const shouldWaitForSessionWork = true;
1522
+ const pendingCompactionAdmissionAtExtensionAdmission = options?.source === "extension" ? this._pendingCompactionAdmission : undefined;
1523
+ const compactionGenerationAtExtensionAdmission = options?.source === "extension" &&
1524
+ this._sessionWorkBarrier.hasActiveWork &&
1525
+ this._compactionLifecycle.state.status !== "idle"
1526
+ ? this._compactionLifecycle.state.generation
1527
+ : undefined;
1166
1528
  // A steer/followUp submission during an active run can be queued immediately.
1167
1529
  // Waiting on the session-work barrier here would trap the message inside this
1168
1530
  // call for the rest of the run whenever a queued continuation (e.g. an active
1169
1531
  // goal chain) holds the barrier, making typed input invisible until the run
1170
1532
  // ends or the user aborts.
1171
- const canQueueWhileStreaming = this.isStreaming && !this.isCompacting && options?.streamingBehavior !== undefined;
1533
+ const canQueueWhileStreaming = (this.isStreaming || this._promptStartPending) &&
1534
+ !this.isCompacting &&
1535
+ options?.streamingBehavior !== undefined;
1172
1536
  if (shouldWaitForSessionWork &&
1173
1537
  !canQueueWhileStreaming &&
1174
1538
  (!this.isStreaming || this.isCompacting || this._sessionWorkBarrier.hasActiveWork)) {
1175
1539
  await this._waitForSettledSessionWork();
1176
1540
  throwIfCancelled();
1541
+ options?.onSessionWorkReady?.();
1177
1542
  }
1178
1543
  // Turn boundary: restore the primary model if the fallback cooldown expired.
1179
1544
  if (!this.isStreaming) {
1180
- await this._maybeRestoreFallbackPrimary();
1545
+ try {
1546
+ await this._maybeRestoreFallbackPrimary();
1547
+ }
1548
+ catch (error) {
1549
+ if (ownsPromptStart)
1550
+ this._promptStartPending = false;
1551
+ throw error;
1552
+ }
1181
1553
  }
1182
1554
  const expandPromptTemplates = options?.expandPromptTemplates ?? true;
1183
1555
  const preflightResult = options?.preflightResult;
1184
1556
  const promptDisposition = options?.promptDisposition;
1185
1557
  let messages;
1186
1558
  let titlePrompt;
1559
+ let consumedNextTurnMessages;
1187
1560
  try {
1188
1561
  // Handle extension commands first (execute immediately, even during streaming)
1189
1562
  // Extension commands manage their own LLM interaction via pi.sendMessage()
@@ -1238,6 +1611,23 @@ export class AgentSession {
1238
1611
  preflightResult?.(true);
1239
1612
  return;
1240
1613
  }
1614
+ // Input accepted while a run was active remains queued even if that run
1615
+ // ends while extension input handling or template expansion is pending.
1616
+ // Starting a fresh prompt here would let it overtake the held continuation.
1617
+ if (canQueueWhileStreaming && !this.isStreaming) {
1618
+ if (options?.thinkingLevel !== undefined) {
1619
+ throw new Error("Cannot set thinkingLevel on a queued prompt; set it after the current turn completes.");
1620
+ }
1621
+ if (options?.streamingBehavior === "followUp") {
1622
+ await this._queueFollowUp(expandedText, currentImages);
1623
+ }
1624
+ else {
1625
+ await this._queueSteer(expandedText, currentImages);
1626
+ }
1627
+ promptDisposition?.("queued");
1628
+ preflightResult?.(true);
1629
+ return;
1630
+ }
1241
1631
  // The queue-while-streaming bypass above skipped the settled-work wait. If
1242
1632
  // the run ended while input was being expanded, serialize with remaining
1243
1633
  // session work before sending a fresh prompt, and re-queue if a scheduled
@@ -1262,6 +1652,25 @@ export class AgentSession {
1262
1652
  return;
1263
1653
  }
1264
1654
  }
1655
+ // A background extension prompt that arrived during a manual compaction
1656
+ // must never overtake a rejected or aborted compaction. Keep its normal
1657
+ // queue ownership instead of attempting another provider admission.
1658
+ if ((pendingCompactionAdmissionAtExtensionAdmission !== undefined &&
1659
+ pendingCompactionAdmissionAtExtensionAdmission.outcome !== "completed") ||
1660
+ (compactionGenerationAtExtensionAdmission !== undefined &&
1661
+ this._compactionLifecycle.state.generation === compactionGenerationAtExtensionAdmission &&
1662
+ (this._compactionLifecycle.state.status === "failed" ||
1663
+ this._compactionLifecycle.state.status === "aborted"))) {
1664
+ if (options?.streamingBehavior === "followUp") {
1665
+ await this._queueFollowUp(expandedText, currentImages);
1666
+ }
1667
+ else {
1668
+ await this._queueSteer(expandedText, currentImages);
1669
+ }
1670
+ promptDisposition?.("queued");
1671
+ preflightResult?.(true);
1672
+ return;
1673
+ }
1265
1674
  // Flush any pending bash messages before the new prompt
1266
1675
  this._flushPendingBashMessages();
1267
1676
  // Validate model
@@ -1279,19 +1688,8 @@ export class AgentSession {
1279
1688
  }
1280
1689
  throw new Error(formatNoApiKeyFoundMessage(this.model.provider));
1281
1690
  }
1282
- // Check if we need to compact before sending (catches aborted responses).
1283
1691
  // 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
- }
1692
+ await this._enforceCompactionBeforeProvider(this._findLastAssistantMessage(), false, "pre_prompt");
1295
1693
  // Build messages array (custom message if any, then user message)
1296
1694
  messages = [];
1297
1695
  // Add user message
@@ -1304,11 +1702,14 @@ export class AgentSession {
1304
1702
  content: userContent,
1305
1703
  timestamp: Date.now(),
1306
1704
  });
1307
- // Inject any pending "nextTurn" messages as context alongside the user message
1308
- for (const msg of this._pendingNextTurnMessages) {
1705
+ // Consume next-turn messages transactionally: a final admission rejection
1706
+ // restores them in their original order rather than dropping or duplicating
1707
+ // one-shot extension state.
1708
+ consumedNextTurnMessages = this._pendingNextTurnMessages;
1709
+ this._pendingNextTurnMessages = [];
1710
+ for (const msg of consumedNextTurnMessages) {
1309
1711
  messages.push(msg);
1310
1712
  }
1311
- this._pendingNextTurnMessages = [];
1312
1713
  // Emit before_agent_start extension event
1313
1714
  const result = await this._extensionRunner.emitBeforeAgentStart(expandedText, currentImages, this._baseSystemPrompt, this._baseSystemPromptOptions);
1314
1715
  // Add all custom messages from extensions
@@ -1335,11 +1736,22 @@ export class AgentSession {
1335
1736
  this._systemPromptOverride = undefined;
1336
1737
  this.agent.state.systemPrompt = this._baseSystemPrompt;
1337
1738
  }
1739
+ // The preflight above only sees persisted session context. These prompt,
1740
+ // next-turn, and before_agent_start additions are also provider-visible,
1741
+ // so make one final admission decision against the complete request.
1742
+ await this._enforceFinalProviderAdmission(messages);
1338
1743
  }
1339
1744
  catch (error) {
1745
+ if (consumedNextTurnMessages && consumedNextTurnMessages.length > 0) {
1746
+ this._pendingNextTurnMessages = [...consumedNextTurnMessages, ...this._pendingNextTurnMessages];
1747
+ }
1340
1748
  preflightResult?.(false);
1341
1749
  throw error;
1342
1750
  }
1751
+ finally {
1752
+ if (ownsPromptStart)
1753
+ this._promptStartPending = false;
1754
+ }
1343
1755
  if (!messages) {
1344
1756
  return;
1345
1757
  }
@@ -1352,7 +1764,14 @@ export class AgentSession {
1352
1764
  await this._promptAgent(messages);
1353
1765
  await this.waitForRetry();
1354
1766
  await this.waitForIdle();
1355
- if (shouldWaitForSessionWork) {
1767
+ if (options?.onSessionWorkReady) {
1768
+ // This prompt owns a session-work token acquired after waiting for a
1769
+ // prior operation, so waiting for the global barrier here would await
1770
+ // itself. _promptAgent() already drained its event queue; any newly
1771
+ // scheduled continuation retains its own barrier ownership.
1772
+ await this.agent.waitForIdle();
1773
+ }
1774
+ else if (shouldWaitForSessionWork) {
1356
1775
  await this._waitForSettledSessionWork();
1357
1776
  }
1358
1777
  else {
@@ -1479,7 +1898,7 @@ export class AgentSession {
1479
1898
  sessionId: this.sessionId,
1480
1899
  baseOptions: this._buildSessionTitleBaseOptions(),
1481
1900
  signal: abortController.signal,
1482
- streamFn: this.agent.streamFn,
1901
+ streamFn: this.agent.streamFunction,
1483
1902
  });
1484
1903
  if (abortController.signal.aborted) {
1485
1904
  return;
@@ -1532,11 +1951,12 @@ export class AgentSession {
1532
1951
  if (images) {
1533
1952
  content.push(...images);
1534
1953
  }
1535
- this.agent.steer({
1536
- role: "user",
1537
- content,
1538
- timestamp: Date.now(),
1539
- });
1954
+ const message = { role: "user", content, timestamp: Date.now() };
1955
+ if (this._promptStartPending && this._skipNextPostCompactionAssistantCheck) {
1956
+ this._postCompactionDeferredSteeringMessages.push(message);
1957
+ return;
1958
+ }
1959
+ this.agent.steer(message);
1540
1960
  }
1541
1961
  /**
1542
1962
  * Internal: Queue a follow-up message (already expanded, no extension command check).
@@ -1548,11 +1968,22 @@ export class AgentSession {
1548
1968
  if (images) {
1549
1969
  content.push(...images);
1550
1970
  }
1551
- this.agent.followUp({
1552
- role: "user",
1553
- content,
1554
- timestamp: Date.now(),
1555
- });
1971
+ const message = { role: "user", content, timestamp: Date.now() };
1972
+ if (this._promptStartPending && this._skipNextPostCompactionAssistantCheck) {
1973
+ this._postCompactionDeferredFollowUpMessages.push(message);
1974
+ return;
1975
+ }
1976
+ this.agent.followUp(message);
1977
+ }
1978
+ _flushPostCompactionDeferredMessages() {
1979
+ for (const message of this._postCompactionDeferredSteeringMessages) {
1980
+ this.agent.steer(message);
1981
+ }
1982
+ this._postCompactionDeferredSteeringMessages = [];
1983
+ for (const message of this._postCompactionDeferredFollowUpMessages) {
1984
+ this.agent.followUp(message);
1985
+ }
1986
+ this._postCompactionDeferredFollowUpMessages = [];
1556
1987
  }
1557
1988
  /**
1558
1989
  * Throw an error if the text is an extension command.
@@ -1587,26 +2018,56 @@ export class AgentSession {
1587
2018
  details: message.details,
1588
2019
  timestamp: Date.now(),
1589
2020
  };
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);
2021
+ const waitForExistingSessionWork = options?.triggerTurn === true &&
2022
+ options.deliverAs !== "nextTurn" &&
2023
+ !this.isStreaming &&
2024
+ this._sessionWorkBarrier.hasActiveWork;
2025
+ const pendingCompactionAdmission = this._pendingCompactionAdmission;
2026
+ const activeCompactionGeneration = this._compactionLifecycle.state.status === "running" ? this._compactionLifecycle.state.generation : undefined;
2027
+ let finishSessionWork;
2028
+ try {
2029
+ if (waitForExistingSessionWork) {
2030
+ await this._waitForSettledSessionWork();
2031
+ finishSessionWork = this._sessionWorkBarrier.begin();
2032
+ }
2033
+ if (options?.deliverAs === "nextTurn") {
2034
+ this._pendingNextTurnMessages.push(appMessage);
2035
+ }
2036
+ else if (this.isStreaming) {
2037
+ if (options?.deliverAs === "followUp") {
2038
+ this.agent.followUp(appMessage);
2039
+ }
2040
+ else {
2041
+ this.agent.steer(appMessage);
2042
+ }
2043
+ }
2044
+ else if (options?.triggerTurn === true &&
2045
+ ((pendingCompactionAdmission !== undefined && pendingCompactionAdmission.outcome !== "completed") ||
2046
+ (activeCompactionGeneration !== undefined &&
2047
+ this._compactionLifecycle.state.generation === activeCompactionGeneration &&
2048
+ this._compactionLifecycle.state.status !== "completed"))) {
2049
+ if (options?.deliverAs === "followUp") {
2050
+ this.agent.followUp(appMessage);
2051
+ }
2052
+ else {
2053
+ this.agent.steer(appMessage);
2054
+ }
2055
+ }
2056
+ else if (options?.triggerTurn) {
2057
+ await this._enforceCompactionBeforeProvider(this._findLastAssistantMessage(), false, "pre_prompt");
2058
+ await this._enforceFinalProviderAdmission([appMessage]);
2059
+ await this._promptAgent(appMessage);
1596
2060
  }
1597
2061
  else {
1598
- this.agent.steer(appMessage);
2062
+ this.agent.state.messages.push(appMessage);
2063
+ this.sessionManager.appendCustomMessageEntry(message.customType, message.content, message.display, message.details);
2064
+ this._incrementMessageRevision();
2065
+ this._emit({ type: "message_start", message: appMessage });
2066
+ this._emit({ type: "message_end", message: appMessage });
1599
2067
  }
1600
2068
  }
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 });
2069
+ finally {
2070
+ finishSessionWork?.();
1610
2071
  }
1611
2072
  }
1612
2073
  /**
@@ -1617,6 +2078,14 @@ export class AgentSession {
1617
2078
  * @param options.deliverAs Delivery mode when streaming: "steer" or "followUp"
1618
2079
  */
1619
2080
  async sendUserMessage(content, options) {
2081
+ const bindingPromptReadiness = this._extensionBindingPromptReadiness;
2082
+ let resolveBindingPromptReadiness;
2083
+ if (bindingPromptReadiness) {
2084
+ const readiness = new Promise((resolve) => {
2085
+ resolveBindingPromptReadiness = resolve;
2086
+ });
2087
+ bindingPromptReadiness.add(readiness);
2088
+ }
1620
2089
  // Normalize content to text string + optional images
1621
2090
  let text;
1622
2091
  let images;
@@ -1638,14 +2107,31 @@ export class AgentSession {
1638
2107
  if (images.length === 0)
1639
2108
  images = undefined;
1640
2109
  }
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
- });
2110
+ // An extension binding invokes this method fire-and-forget. When it
2111
+ // arrives during session work, retain a barrier token after the prior work
2112
+ // settles so callers observing session idle cannot race its provider turn.
2113
+ const waitForExistingSessionWork = this._sessionWorkBarrier.hasActiveWork;
2114
+ let finishSessionWork;
2115
+ try {
2116
+ // Use prompt() with expandPromptTemplates: false to skip command handling and template expansion
2117
+ await this.prompt(text, {
2118
+ expandPromptTemplates: false,
2119
+ streamingBehavior: options?.deliverAs,
2120
+ images,
2121
+ source: "extension",
2122
+ promptDisposition: () => resolveBindingPromptReadiness?.(),
2123
+ onSessionWorkReady: waitForExistingSessionWork
2124
+ ? () => {
2125
+ finishSessionWork ??= this._sessionWorkBarrier.begin();
2126
+ }
2127
+ : undefined,
2128
+ sessionTitlePrompt: false,
2129
+ });
2130
+ }
2131
+ finally {
2132
+ resolveBindingPromptReadiness?.();
2133
+ finishSessionWork?.();
2134
+ }
1649
2135
  }
1650
2136
  /**
1651
2137
  * Clear all queued messages and return them.
@@ -1655,8 +2141,13 @@ export class AgentSession {
1655
2141
  clearQueue() {
1656
2142
  const steering = [...this._steeringMessages];
1657
2143
  const followUp = [...this._followUpMessages];
2144
+ // Clear every queue synchronously. Deferred post-compaction messages are
2145
+ // already represented in visible bookkeeping, so they must not be returned
2146
+ // a second time or later resurrected into Agent's native queues.
1658
2147
  this._steeringMessages = [];
1659
2148
  this._followUpMessages = [];
2149
+ this._postCompactionDeferredSteeringMessages = [];
2150
+ this._postCompactionDeferredFollowUpMessages = [];
1660
2151
  this.agent.clearAllQueues();
1661
2152
  this._emitQueueUpdate();
1662
2153
  return { steering, followUp };
@@ -1680,30 +2171,8 @@ export class AgentSession {
1680
2171
  * Abort current operation and wait for agent to become idle.
1681
2172
  */
1682
2173
  async abort() {
1683
- this.abortRetry();
1684
2174
  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
- }
2175
+ await this._abortActiveAgentAndRetry();
1707
2176
  }
1708
2177
  async waitForIdle() {
1709
2178
  if (this.isIdle) {
@@ -1779,6 +2248,7 @@ export class AgentSession {
1779
2248
  persistDefault: updateGlobalDefaults,
1780
2249
  appendSessionEntry: true,
1781
2250
  emitModelSelect: true,
2251
+ modelSelectSource: "set",
1782
2252
  invalidateCompaction: true,
1783
2253
  });
1784
2254
  }
@@ -1817,7 +2287,7 @@ export class AgentSession {
1817
2287
  }
1818
2288
  if (!opts.emitModelSelect)
1819
2289
  return undefined;
1820
- return await this._emitModelSelect(model, previousModel, "set");
2290
+ return await this._emitModelSelect(model, previousModel, opts.modelSelectSource);
1821
2291
  }
1822
2292
  _applyEphemeralThinkingLevel(level) {
1823
2293
  const previousLevel = this.agent.state.thinkingLevel;
@@ -2009,29 +2479,133 @@ export class AgentSession {
2009
2479
  // =========================================================================
2010
2480
  // Compaction
2011
2481
  // =========================================================================
2482
+ _claimCompactionController(controller, owner) {
2483
+ const supersedesCurrentOperation = (this._compactionAbortController !== undefined && this._compactionAbortController !== controller) ||
2484
+ (this._autoCompactionAbortController !== undefined && this._autoCompactionAbortController !== controller);
2485
+ if (supersedesCurrentOperation) {
2486
+ // Supersession has no public terminal event: the stale operation no
2487
+ // longer owns the route. Finishing its lifecycle state here prevents a
2488
+ // missing late feedback end from keeping isCompacting true.
2489
+ this._compactionLifecycle.abort(this._messageRevision);
2490
+ }
2491
+ if (this._compactionAbortController && this._compactionAbortController !== controller) {
2492
+ this._compactionAbortController.abort();
2493
+ this._compactionAbortController = undefined;
2494
+ }
2495
+ if (this._autoCompactionAbortController && this._autoCompactionAbortController !== controller) {
2496
+ this._autoCompactionAbortController.abort();
2497
+ this._autoCompactionAbortController = undefined;
2498
+ }
2499
+ if (owner === "auto") {
2500
+ this._autoCompactionAbortController = controller;
2501
+ }
2502
+ else {
2503
+ this._compactionAbortController = controller;
2504
+ }
2505
+ }
2506
+ _ownsCompactionController(controller, owner) {
2507
+ return (!controller.signal.aborted &&
2508
+ (owner === "auto"
2509
+ ? this._autoCompactionAbortController === controller
2510
+ : this._compactionAbortController === controller));
2511
+ }
2512
+ _releaseCompactionController(signal) {
2513
+ if (this._compactionAbortController?.signal === signal) {
2514
+ this._compactionAbortController = undefined;
2515
+ }
2516
+ if (this._autoCompactionAbortController?.signal === signal) {
2517
+ this._autoCompactionAbortController = undefined;
2518
+ }
2519
+ }
2520
+ _claimPendingCompactionAdmission() {
2521
+ const priorAdmission = this._pendingCompactionAdmission;
2522
+ if (priorAdmission) {
2523
+ priorAdmission.controller.abort();
2524
+ priorAdmission.outcome = "aborted";
2525
+ priorAdmission.finishSessionWork();
2526
+ }
2527
+ const controller = new AbortController();
2528
+ const admission = {
2529
+ controller,
2530
+ finishSessionWork: this._sessionWorkBarrier.begin(),
2531
+ };
2532
+ this._pendingCompactionAdmission = admission;
2533
+ this._claimCompactionController(controller, "compaction");
2534
+ return admission;
2535
+ }
2536
+ _releasePendingCompactionAdmission(admission, outcome) {
2537
+ admission.outcome = outcome;
2538
+ if (this._pendingCompactionAdmission !== admission)
2539
+ return;
2540
+ this._pendingCompactionAdmission = undefined;
2541
+ admission.finishSessionWork();
2542
+ }
2543
+ async _abortActiveAgentAndRetry() {
2544
+ this.abortRetry();
2545
+ this.abortBranchSummary();
2546
+ if (this._userAbortPromise) {
2547
+ this.agent.abort();
2548
+ await this._userAbortPromise;
2549
+ return;
2550
+ }
2551
+ if (this.isStreaming) {
2552
+ this._suppressQueuedContinuationAfterUserAbort = true;
2553
+ }
2554
+ const abortPromise = (async () => {
2555
+ this.agent.abort();
2556
+ await this.waitForIdle();
2557
+ })();
2558
+ this._userAbortPromise = abortPromise;
2559
+ try {
2560
+ await abortPromise;
2561
+ }
2562
+ finally {
2563
+ if (this._userAbortPromise === abortPromise) {
2564
+ this._userAbortPromise = undefined;
2565
+ }
2566
+ }
2567
+ }
2012
2568
  /**
2013
2569
  * Manually compact the session context.
2014
2570
  * Aborts current agent operation first.
2015
2571
  * @param customInstructions Optional instructions for the compaction summary
2016
2572
  */
2017
2573
  async compact(customInstructions) {
2018
- this._disconnectFromAgent();
2019
- await this.abort();
2020
- this._compactionAbortController = new AbortController();
2021
- this._emit({ type: "compaction_start", reason: "manual" });
2574
+ const admission = this._claimPendingCompactionAdmission();
2575
+ const controller = admission.controller;
2576
+ let outcome = "failed";
2577
+ let disconnected = false;
2022
2578
  try {
2023
- const execution = await this._executeCompaction({ reason: "manual", customInstructions, willRetry: false });
2579
+ // Keep the session subscriber attached until the aborted run emits
2580
+ // agent_end. That event clears the active-run and retry state that
2581
+ // waitForIdle() depends on.
2582
+ await this._abortActiveAgentAndRetry();
2583
+ this._disconnectFromAgent();
2584
+ disconnected = true;
2585
+ this._emit({ type: "compaction_start", reason: "manual" });
2586
+ const execution = await this._executeCompaction({
2587
+ controller,
2588
+ owner: "compaction",
2589
+ reason: "manual",
2590
+ customInstructions,
2591
+ willRetry: false,
2592
+ });
2024
2593
  if (!execution.accepted) {
2025
2594
  throw new CompactionRejectedError(execution.rejectionCause);
2026
2595
  }
2596
+ outcome = "completed";
2027
2597
  return execution.result;
2028
2598
  }
2029
2599
  catch (error) {
2600
+ outcome = isCompactionExecutionAborted(error) ? "aborted" : "failed";
2030
2601
  if (error instanceof CompactionRejectedError) {
2031
2602
  throw new Error(error.message);
2032
2603
  }
2604
+ if (!compactionExecutionOwnsTerminalTransition(error)) {
2605
+ throw error;
2606
+ }
2033
2607
  const message = error instanceof Error ? error.message : String(error);
2034
- const aborted = message === "Compaction cancelled" || (error instanceof Error && error.name === "AbortError");
2608
+ const aborted = isCompactionExecutionAborted(error);
2035
2609
  this._emit({
2036
2610
  type: "compaction_end",
2037
2611
  reason: "manual",
@@ -2043,21 +2617,34 @@ export class AgentSession {
2043
2617
  throw error;
2044
2618
  }
2045
2619
  finally {
2046
- this._compactionAbortController = undefined;
2047
- this._reconnectToAgent();
2620
+ if (this._compactionAbortController === controller && this._compactionLifecycle.state.status !== "running") {
2621
+ this._compactionAbortController = undefined;
2622
+ }
2623
+ this._releasePendingCompactionAdmission(admission, outcome);
2624
+ if (disconnected && !this.isCompacting)
2625
+ this._reconnectToAgent();
2048
2626
  }
2049
2627
  }
2050
2628
  async applyCompaction(precomputed, options) {
2629
+ if (options.signal !== undefined && options.signal !== this._compactionAbortController?.signal) {
2630
+ return { applied: false, reason: "stale" };
2631
+ }
2051
2632
  if (options.expectedRevision !== undefined && options.expectedRevision !== this._messageRevision) {
2052
2633
  return { applied: false, reason: "stale" };
2053
2634
  }
2054
2635
  const ownsController = this._compactionAbortController === undefined;
2055
2636
  if (ownsController) {
2056
- this._compactionAbortController = new AbortController();
2637
+ this._claimCompactionController(new AbortController(), "compaction");
2057
2638
  this._emit({ type: "compaction_start", reason: options.reason });
2058
2639
  }
2640
+ const controller = this._compactionAbortController;
2641
+ if (!controller)
2642
+ return { applied: false, reason: "rejected" };
2643
+ this._claimCompactionController(controller, "compaction");
2059
2644
  try {
2060
2645
  const execution = await this._executeCompaction({
2646
+ controller,
2647
+ owner: "compaction",
2061
2648
  reason: options.reason,
2062
2649
  willRetry: false,
2063
2650
  precomputed,
@@ -2068,8 +2655,11 @@ export class AgentSession {
2068
2655
  return { applied: true, reason: "ok" };
2069
2656
  }
2070
2657
  catch (error) {
2658
+ if (!compactionExecutionOwnsTerminalTransition(error)) {
2659
+ return { applied: false, reason: "rejected" };
2660
+ }
2071
2661
  const message = error instanceof Error ? error.message : String(error);
2072
- const aborted = message === "Compaction cancelled" || (error instanceof Error && error.name === "AbortError");
2662
+ const aborted = isCompactionExecutionAborted(error);
2073
2663
  this._emit({
2074
2664
  type: "compaction_end",
2075
2665
  reason: options.reason,
@@ -2081,18 +2671,27 @@ export class AgentSession {
2081
2671
  return { applied: false, reason: "rejected" };
2082
2672
  }
2083
2673
  finally {
2084
- this._compactionAbortController = undefined;
2674
+ if (this._compactionAbortController === controller && this._compactionLifecycle.state.status !== "running") {
2675
+ this._compactionAbortController = undefined;
2676
+ }
2085
2677
  }
2086
2678
  }
2087
2679
  _beginExtensionCompactionFeedback(reason) {
2088
- if (!this._compactionAbortController) {
2089
- this._compactionAbortController = new AbortController();
2090
- this._emit({ type: "compaction_start", reason });
2091
- }
2092
- return this._compactionAbortController.signal;
2680
+ const controller = new AbortController();
2681
+ this._claimCompactionController(controller, "compaction");
2682
+ const model = this.model;
2683
+ this._compactionLifecycle.begin({
2684
+ operationId: randomUUID(),
2685
+ stage: "feedback",
2686
+ reason,
2687
+ model: model ? { provider: model.provider, id: model.id } : undefined,
2688
+ startedRevision: this._messageRevision,
2689
+ }, controller);
2690
+ this._emit({ type: "compaction_start", reason });
2691
+ return controller.signal;
2093
2692
  }
2094
2693
  _updateExtensionCompactionFeedback(options) {
2095
- if (!this._compactionAbortController && !this._autoCompactionAbortController)
2694
+ if (!options.signal || !this._compactionLifecycle.hasCurrentSignal(options.signal))
2096
2695
  return;
2097
2696
  this._emit({
2098
2697
  type: "compaction_progress",
@@ -2102,10 +2701,24 @@ export class AgentSession {
2102
2701
  });
2103
2702
  }
2104
2703
  _endExtensionCompactionFeedback(options) {
2105
- const controller = this._compactionAbortController;
2106
- if (!controller)
2704
+ if (!options.signal)
2107
2705
  return;
2108
- const aborted = options.aborted ?? controller.signal.aborted;
2706
+ if (!this._compactionLifecycle.hasCurrentSignal(options.signal)) {
2707
+ this._releaseCompactionController(options.signal);
2708
+ return;
2709
+ }
2710
+ const operation = this._compactionLifecycle.state;
2711
+ if (operation.status !== "running" || operation.stage !== "feedback")
2712
+ return;
2713
+ const aborted = options.aborted ?? options.signal.aborted;
2714
+ this._compactionLifecycle.finish({
2715
+ operationId: operation.operationId,
2716
+ status: aborted ? "aborted" : "failed",
2717
+ endedRevision: this._messageRevision,
2718
+ ...(aborted
2719
+ ? { errorMessage: "Compaction cancelled" }
2720
+ : { errorMessage: options.errorMessage ?? "Compaction did not apply" }),
2721
+ });
2109
2722
  this._emit({
2110
2723
  type: "compaction_end",
2111
2724
  reason: options.reason,
@@ -2114,26 +2727,41 @@ export class AgentSession {
2114
2727
  willRetry: false,
2115
2728
  errorMessage: aborted ? undefined : options.errorMessage,
2116
2729
  });
2117
- this._compactionAbortController = undefined;
2730
+ this._releaseCompactionController(options.signal);
2118
2731
  }
2119
2732
  async _executeCompaction(request) {
2733
+ const model = this.model;
2734
+ if (!model)
2735
+ throw new Error(formatNoModelSelectedMessage());
2736
+ const thinkingLevel = this.thinkingLevel;
2737
+ const controller = request.controller;
2738
+ // Async auth and provider preparation may yield to a newer route. A stale
2739
+ // controller must never promote itself into a lifecycle generation.
2740
+ if (!this._ownsCompactionController(controller, request.owner)) {
2741
+ throw new CompactionExecutionError(new CompactionCancelledError(), false, true);
2742
+ }
2743
+ const requestId = randomUUID();
2744
+ const operationId = this._compactionLifecycle.begin({
2745
+ operationId: requestId,
2746
+ stage: "execution",
2747
+ reason: request.reason,
2748
+ model: { provider: model.provider, id: model.id },
2749
+ startedRevision: this._messageRevision,
2750
+ }, controller);
2120
2751
  const finishCompactionWork = this._sessionWorkBarrier.begin();
2121
2752
  const agentMessagesAtStart = request.agentMessagesAtStart ?? this.agent.state.messages.slice();
2753
+ const compactionOwnedDiagnosticMessages = new Set();
2754
+ const signal = controller.signal;
2122
2755
  try {
2123
- if (!this.model) {
2124
- throw new Error(formatNoModelSelectedMessage());
2756
+ if (signal.aborted) {
2757
+ throw new CompactionCancelledError();
2125
2758
  }
2126
- const requestId = randomUUID();
2127
2759
  const pathEntries = this.sessionManager.getBranch();
2128
2760
  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
2761
  let compactionResult = request.precomputed;
2134
2762
  let fromExtension = request.precomputed !== undefined;
2135
2763
  if (!compactionResult) {
2136
- const preparation = prepareCompaction(pathEntries, settings);
2764
+ const preparation = prepareCompaction(pathEntries, settings, request.reason === "overflow", request.allowSummaryOnly);
2137
2765
  if (!preparation) {
2138
2766
  const lastEntry = pathEntries[pathEntries.length - 1];
2139
2767
  if (lastEntry?.type === "compaction") {
@@ -2142,6 +2770,7 @@ export class AgentSession {
2142
2770
  throw new Error("Nothing to compact (session too small)");
2143
2771
  }
2144
2772
  if (this._extensionRunner.hasHandlers("session_before_compact")) {
2773
+ const messagesBeforeExtension = new Set(this.agent.state.messages);
2145
2774
  const extensionResult = (await this._extensionRunner.emit({
2146
2775
  type: "session_before_compact",
2147
2776
  reason: request.reason,
@@ -2152,8 +2781,17 @@ export class AgentSession {
2152
2781
  customInstructions: request.customInstructions,
2153
2782
  signal,
2154
2783
  }));
2784
+ for (const message of this.agent.state.messages) {
2785
+ if (!messagesBeforeExtension.has(message) &&
2786
+ isCompactionOwnedPreCompactDiagnostic(message, requestId)) {
2787
+ compactionOwnedDiagnosticMessages.add(message);
2788
+ }
2789
+ }
2790
+ if (!this._compactionLifecycle.isCurrent(operationId, controller)) {
2791
+ throw new CompactionCancelledError();
2792
+ }
2155
2793
  if (extensionResult?.cancel) {
2156
- return await this._rejectCompaction(request, requestId, extensionResult.rejectionCause ?? "cancelled-by-extension", true, extensionResult.reason);
2794
+ return await this._rejectCompaction(request, requestId, operationId, extensionResult.rejectionCause ?? "cancelled-by-extension", true, extensionResult.reason);
2157
2795
  }
2158
2796
  if (extensionResult?.compaction) {
2159
2797
  compactionResult = extensionResult.compaction;
@@ -2161,17 +2799,38 @@ export class AgentSession {
2161
2799
  }
2162
2800
  }
2163
2801
  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);
2802
+ const { apiKey, headers, extraBody, env } = await this._getCompactionRequestAuth(model);
2803
+ 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
2804
  }
2167
2805
  }
2168
2806
  if (signal.aborted) {
2169
- throw new Error("Compaction cancelled");
2807
+ throw new CompactionCancelledError();
2170
2808
  }
2171
- if (this._wouldCompactionOverflow(pathEntries, compactionResult, fromExtension)) {
2172
- return await this._rejectCompaction(request, requestId, "would-overflow", false);
2809
+ if (!this._compactionLifecycle.isCurrent(operationId, controller)) {
2810
+ throw new CompactionCancelledError();
2173
2811
  }
2174
- const compactionEntryId = this.sessionManager.appendCompaction(compactionResult.summary, compactionResult.firstKeptEntryId, compactionResult.tokensBefore, compactionResult.details, fromExtension);
2812
+ const lifecycleState = this._compactionLifecycle.state;
2813
+ const currentMessagesAtCheck = this.agent.state.messages;
2814
+ const startPrefixIntact = agentMessagesAtStart.every((message, index) => currentMessagesAtCheck[index] === message);
2815
+ // Appends after the start snapshot are fresh only while they are exact
2816
+ // message_end identities still awaiting persistence. Any revision change,
2817
+ // other append, replacement, or reorder still makes this compaction stale.
2818
+ const onlyPendingPersistenceAppends = startPrefixIntact &&
2819
+ currentMessagesAtCheck
2820
+ .slice(agentMessagesAtStart.length)
2821
+ .every((message) => this._messageEndsAwaitingPersistence.has(message) ||
2822
+ compactionOwnedDiagnosticMessages.has(message));
2823
+ const sourceChanged = lifecycleState.status !== "running" ||
2824
+ lifecycleState.operationId !== operationId ||
2825
+ lifecycleState.startedRevision + compactionOwnedDiagnosticMessages.size !== this._messageRevision ||
2826
+ !onlyPendingPersistenceAppends;
2827
+ if (sourceChanged) {
2828
+ return await this._rejectCompaction(request, requestId, operationId, "stale-revision", false);
2829
+ }
2830
+ if (this._wouldCompactionOverflow(pathEntries, compactionResult, fromExtension, model)) {
2831
+ return await this._rejectCompaction(request, requestId, operationId, "would-overflow", false);
2832
+ }
2833
+ const compactionEntryId = this.sessionManager.appendCompaction(compactionResult.summary, compactionResult.firstKeptEntryId, compactionResult.tokensBefore, compactionResult.details, fromExtension, compactionResult.usage);
2175
2834
  const savedEntry = this.sessionManager.getEntry(compactionEntryId);
2176
2835
  if (savedEntry?.type !== "compaction") {
2177
2836
  throw new Error("Compaction entry was not saved");
@@ -2182,18 +2841,33 @@ export class AgentSession {
2182
2841
  const messagesAppendedDuringCompaction = hasUnchangedPrefix
2183
2842
  ? currentAgentMessages.slice(agentMessagesAtStart.length)
2184
2843
  : [];
2185
- this.agent.state.messages = [...sessionContext.messages, ...messagesAppendedDuringCompaction];
2844
+ // Preserve an identity-deduped, agent-ordered union of the append-during-
2845
+ // compaction suffix and exact messages still awaiting persistence. Their
2846
+ // queued message_end owns exactly-once persistence, so they are kept in
2847
+ // agent state only and never persisted here.
2848
+ const preservedIdentities = new Set(messagesAppendedDuringCompaction);
2849
+ for (const message of currentAgentMessages) {
2850
+ if (this._messageEndsAwaitingPersistence.has(message)) {
2851
+ preservedIdentities.add(message);
2852
+ }
2853
+ }
2854
+ const preservedPendingMessages = currentAgentMessages.filter((message) => preservedIdentities.delete(message));
2855
+ this._skipNextPostCompactionAssistantCheck = true;
2856
+ for (const message of preservedPendingMessages) {
2857
+ if (message.role === "assistant") {
2858
+ this._assistantsPendingAtCompaction.add(message);
2859
+ }
2860
+ }
2861
+ this.agent.state.messages = [...sessionContext.messages, ...preservedPendingMessages];
2186
2862
  compactionResult.estimatedTokensAfter = estimateMessagesTokens(sessionContext.messages);
2187
2863
  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
- });
2864
+ if (!this._compactionLifecycle.finish({
2865
+ operationId,
2866
+ status: "completed",
2867
+ endedRevision: this._messageRevision,
2868
+ })) {
2869
+ throw new CompactionCancelledError();
2870
+ }
2197
2871
  this._emit({
2198
2872
  type: "compaction_end",
2199
2873
  reason: request.reason,
@@ -2203,15 +2877,41 @@ export class AgentSession {
2203
2877
  requestId,
2204
2878
  accepted: true,
2205
2879
  });
2880
+ await this._extensionRunner.emit({
2881
+ type: "session_compact",
2882
+ reason: request.reason,
2883
+ requestId,
2884
+ accepted: true,
2885
+ compactionEntry: savedEntry,
2886
+ fromExtension,
2887
+ willRetry: request.willRetry,
2888
+ });
2206
2889
  return { accepted: true, requestId, result: compactionResult, compactionEntry: savedEntry, fromExtension };
2207
2890
  }
2891
+ catch (error) {
2892
+ if (error instanceof CompactionExecutionError) {
2893
+ throw error;
2894
+ }
2895
+ const lifecycleState = this._compactionLifecycle.state;
2896
+ const ownsTerminalTransition = lifecycleState.status !== "idle" && lifecycleState.operationId === operationId;
2897
+ const aborted = signal.aborted || isCompactionExecutionAborted(error);
2898
+ if (ownsTerminalTransition && lifecycleState.status === "running") {
2899
+ this._compactionLifecycle.finish({
2900
+ operationId,
2901
+ status: aborted ? "aborted" : "failed",
2902
+ endedRevision: this._messageRevision,
2903
+ errorMessage: error instanceof Error ? error.message : String(error),
2904
+ });
2905
+ }
2906
+ throw new CompactionExecutionError(error, ownsTerminalTransition, aborted);
2907
+ }
2208
2908
  finally {
2209
2909
  finishCompactionWork();
2210
2910
  }
2211
2911
  }
2212
- _wouldCompactionOverflow(pathEntries, compactionResult, fromExtension) {
2912
+ _wouldCompactionOverflow(pathEntries, compactionResult, fromExtension, model) {
2213
2913
  const currentLeaf = pathEntries[pathEntries.length - 1];
2214
- if (!currentLeaf || !this.model)
2914
+ if (!currentLeaf)
2215
2915
  return false;
2216
2916
  const simulatedCompactionEntry = {
2217
2917
  type: "compaction",
@@ -2227,9 +2927,28 @@ export class AgentSession {
2227
2927
  const simulatedMessages = buildSessionContext([...pathEntries, simulatedCompactionEntry], simulatedCompactionEntry.id).messages;
2228
2928
  const contextTokens = estimateMessagesTokens(filterContextExcludedMessages(simulatedMessages));
2229
2929
  const settings = this.settingsManager.getCompactionSettings();
2230
- return contextTokens > this.model.contextWindow - settings.reserveTokens;
2930
+ return contextTokens > model.contextWindow - settings.reserveTokens;
2231
2931
  }
2232
- async _rejectCompaction(request, requestId, rejectionCause, aborted, extensionReason) {
2932
+ /**
2933
+ * Replaces agent state with the canonical session context while keeping exact
2934
+ * message objects whose message_end persistence is still queued. Identities
2935
+ * already present in the session context are kept from the context only, so
2936
+ * nothing is duplicated; the queued message_end still owns exactly-once
2937
+ * persistence for the rest.
2938
+ */
2939
+ _restoreAgentMessagesFromSession() {
2940
+ const sessionMessages = this.sessionManager.buildSessionContext().messages;
2941
+ const seen = new Set(sessionMessages);
2942
+ const pendingMessages = [];
2943
+ for (const message of this.agent.state.messages) {
2944
+ if (seen.has(message) || !this._messageEndsAwaitingPersistence.has(message))
2945
+ continue;
2946
+ seen.add(message);
2947
+ pendingMessages.push(message);
2948
+ }
2949
+ this.agent.state.messages = [...sessionMessages, ...pendingMessages];
2950
+ }
2951
+ async _rejectCompaction(request, requestId, operationId, rejectionCause, aborted, extensionReason) {
2233
2952
  // Per plan Section 1: rejection must never be silent. The compaction_end event
2234
2953
  // carries a non-empty human-readable errorMessage (unless the user aborted, where
2235
2954
  // the aborted branch already renders "Compaction cancelled"). session_compact is
@@ -2241,6 +2960,15 @@ export class AgentSession {
2241
2960
  ? `Compaction rejected: ${trimmedExtensionReason}`
2242
2961
  : describeCompactionRejection(rejectionCause);
2243
2962
  const errorMessage = aborted && !trimmedExtensionReason ? undefined : detailedMessage;
2963
+ if (!this._compactionLifecycle.finish({
2964
+ operationId,
2965
+ status: "failed",
2966
+ endedRevision: this._messageRevision,
2967
+ rejectionCause,
2968
+ ...(trimmedExtensionReason !== undefined ? { errorMessage: trimmedExtensionReason } : {}),
2969
+ })) {
2970
+ throw new CompactionCancelledError();
2971
+ }
2244
2972
  this._emit({
2245
2973
  type: "compaction_end",
2246
2974
  reason: request.reason,
@@ -2267,8 +2995,19 @@ export class AgentSession {
2267
2995
  * Cancel in-progress compaction (manual or auto).
2268
2996
  */
2269
2997
  abortCompaction() {
2998
+ const feedbackOperation = this._compactionLifecycle.abort(this._messageRevision);
2270
2999
  this._compactionAbortController?.abort();
2271
3000
  this._autoCompactionAbortController?.abort();
3001
+ if (feedbackOperation?.stage === "feedback") {
3002
+ this._compactionAbortController = undefined;
3003
+ this._emit({
3004
+ type: "compaction_end",
3005
+ reason: feedbackOperation.reason,
3006
+ result: undefined,
3007
+ aborted: true,
3008
+ willRetry: false,
3009
+ });
3010
+ }
2272
3011
  }
2273
3012
  /**
2274
3013
  * Cancel in-progress branch summarization.
@@ -2287,7 +3026,80 @@ export class AgentSession {
2287
3026
  * @param assistantMessage The assistant message to check
2288
3027
  * @param skipAbortedCheck If false, include aborted messages (for pre-prompt check). Default: true
2289
3028
  */
2290
- async _checkCompaction(assistantMessage, skipAbortedCheck = true, inlineReason) {
3029
+ async _enforceCompactionBeforeProvider(assistantMessage, skipAbortedCheck, inlineReason, retryAfterCompaction = false) {
3030
+ const blockedAdmission = this._blockedPostCompactionAssistant;
3031
+ if (blockedAdmission !== undefined &&
3032
+ blockedAdmission.assistant === assistantMessage &&
3033
+ blockedAdmission.revision === this._messageRevision) {
3034
+ throw new RequiredCompactionError();
3035
+ }
3036
+ const settings = this.settingsManager.getCompactionSettings();
3037
+ const model = this.model;
3038
+ const contextTokens = estimateContextTokens(filterContextExcludedMessages(this.sessionManager.buildSessionContext().messages)).tokens;
3039
+ const compacted = assistantMessage
3040
+ ? await this._checkCompaction(assistantMessage, skipAbortedCheck, inlineReason, retryAfterCompaction)
3041
+ : false;
3042
+ if (compacted || (assistantMessage && this._postCompactionUsageExemptAssistants.has(assistantMessage))) {
3043
+ return compacted;
3044
+ }
3045
+ if (!settings.enabled || !model || !shouldCompact(contextTokens, model.contextWindow, settings)) {
3046
+ return false;
3047
+ }
3048
+ const latestCompaction = getLatestCompactionEntry(this.sessionManager.getBranch());
3049
+ const assistantBeforeLatestCompaction = assistantMessage !== undefined && this._isAssistantFromBeforeLatestCompaction(assistantMessage);
3050
+ const hasPostCompactionCustomState = latestCompaction !== null &&
3051
+ this.agent.state.messages.some((message) => message.role === "custom" && message.timestamp >= new Date(latestCompaction.timestamp).getTime());
3052
+ if (assistantBeforeLatestCompaction && !hasPostCompactionCustomState) {
3053
+ return false;
3054
+ }
3055
+ if (assistantBeforeLatestCompaction && assistantMessage) {
3056
+ const compacted = await this._runPrePromptCompaction(assistantMessage, skipAbortedCheck, inlineReason);
3057
+ if (compacted)
3058
+ return true;
3059
+ }
3060
+ throw new RequiredCompactionError();
3061
+ }
3062
+ /**
3063
+ * The normal pre-prompt check only estimates persisted session context. This
3064
+ * final gate also includes turn-local messages which the provider will see:
3065
+ * the current prompt, next-turn custom messages, and before_agent_start
3066
+ * additions. Compaction rewrites only session context, so callers retain and
3067
+ * reapply their already-assembled one-shot additions after it succeeds.
3068
+ */
3069
+ async _enforceFinalProviderAdmission(messages) {
3070
+ // Normal user-only prompts keep the established provider-overflow recovery
3071
+ // path. This final gate closes the separate gap introduced by turn-local
3072
+ // custom additions, which the normal pre-prompt check cannot observe.
3073
+ if (!messages.some((message) => message.role === "custom"))
3074
+ return;
3075
+ const model = this.model;
3076
+ if (!model)
3077
+ return;
3078
+ const settings = this.settingsManager.getCompactionSettings();
3079
+ const isOversized = () => {
3080
+ const providerMessages = filterContextExcludedMessages([...this.agent.state.messages, ...messages]);
3081
+ const estimate = estimateContextTokens(providerMessages);
3082
+ const usageMessage = estimate.lastUsageIndex === null ? undefined : providerMessages[estimate.lastUsageIndex];
3083
+ // Kept assistant usage can describe the pre-compaction request. Once a
3084
+ // compaction boundary exists, fall back to byte-derived message estimates
3085
+ // until a provider response refreshes that usage.
3086
+ const contextTokens = usageMessage?.role === "assistant" && this._isAssistantFromBeforeLatestCompaction(usageMessage)
3087
+ ? estimateMessagesTokens(providerMessages)
3088
+ : estimate.tokens;
3089
+ return contextTokens > model.contextWindow - settings.reserveTokens;
3090
+ };
3091
+ if (!settings.enabled || !isOversized())
3092
+ return;
3093
+ const lastAssistantMessage = this._findLastAssistantMessage();
3094
+ if (!lastAssistantMessage) {
3095
+ throw new RequiredCompactionError();
3096
+ }
3097
+ const compacted = await this._runPrePromptCompaction(lastAssistantMessage, false, "pre_prompt");
3098
+ if (!compacted || isOversized()) {
3099
+ throw new RequiredCompactionError();
3100
+ }
3101
+ }
3102
+ async _checkCompaction(assistantMessage, skipAbortedCheck = true, inlineReason, retryAfterCompaction = false) {
2291
3103
  const settings = this.settingsManager.getCompactionSettings();
2292
3104
  if (!settings.enabled)
2293
3105
  return false;
@@ -2315,10 +3127,26 @@ export class AgentSession {
2315
3127
  const currentContextNeedsCompaction = contextUsage !== undefined &&
2316
3128
  contextUsage.tokens !== null &&
2317
3129
  shouldCompact(contextUsage.tokens, contextUsage.contextWindow, settings);
2318
- if (isContextOverflow(assistantMessage, contextWindow) && (sameModel || currentContextNeedsCompaction)) {
2319
- const willRetry = assistantMessage.stopReason !== "stop";
3130
+ const isOverflow = isContextOverflow(assistantMessage, contextWindow) && (sameModel || currentContextNeedsCompaction);
3131
+ if (isOverflow &&
3132
+ assistantMessage.stopReason === "stop" &&
3133
+ this._consumePostCompactionUsageExemption(assistantMessage)) {
3134
+ return false;
3135
+ }
3136
+ if (isOverflow) {
3137
+ this._flushPostCompactionDeferredMessages();
3138
+ const willRetry = retryAfterCompaction || assistantMessage.stopReason !== "stop";
2320
3139
  if (!willRetry) {
2321
- return await this._runAutoCompaction("overflow", false);
3140
+ const compacted = await this._runAutoCompaction("overflow", false);
3141
+ if (!compacted &&
3142
+ this._compactionLifecycle.state.status === "failed" &&
3143
+ getLatestCompactionEntry(this.sessionManager.getBranch()) !== null) {
3144
+ this._blockedPostCompactionAssistant = {
3145
+ assistant: assistantMessage,
3146
+ revision: this._messageRevision,
3147
+ };
3148
+ }
3149
+ return compacted;
2322
3150
  }
2323
3151
  if (this._overflowRecoveryAttempted) {
2324
3152
  const errorMessage = "Context overflow recovery failed after one compact-and-retry attempt. Try reducing context or switching to a larger-context model.";
@@ -2339,17 +3167,29 @@ export class AgentSession {
2339
3167
  // Remove the error message from agent state (it IS saved to session for history,
2340
3168
  // but we don't want it in context for the retry)
2341
3169
  const messages = this.agent.state.messages;
3170
+ let removedOverflowAssistant = false;
2342
3171
  if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
2343
3172
  this.agent.state.messages = messages.slice(0, -1);
3173
+ removedOverflowAssistant = true;
2344
3174
  this._incrementMessageRevision();
2345
3175
  }
2346
- if (inlineReason) {
2347
- return await this._runPrePromptCompaction(assistantMessage, skipAbortedCheck, "overflow", willRetry);
3176
+ const compacted = inlineReason
3177
+ ? await this._runPrePromptCompaction(assistantMessage, skipAbortedCheck, "overflow", willRetry)
3178
+ : await this._runAutoCompaction("overflow", willRetry);
3179
+ if (!compacted && removedOverflowAssistant) {
3180
+ this._restoreAgentMessagesFromSession();
3181
+ this._incrementMessageRevision();
2348
3182
  }
2349
- else {
2350
- return await this._runAutoCompaction("overflow", willRetry);
3183
+ if (!compacted && inlineReason) {
3184
+ throw new RequiredCompactionError();
2351
3185
  }
3186
+ return compacted;
2352
3187
  }
3188
+ // The first ordinary response can carry provider usage calculated before
3189
+ // compaction. Consume that exemption only after proving this is not an
3190
+ // overflow, then retain the decision for later admission re-sampling.
3191
+ if (this._consumePostCompactionUsageExemption(assistantMessage))
3192
+ return false;
2353
3193
  // Case 2: Threshold - context is getting large
2354
3194
  // For error messages or all-zero usage messages, estimate from the last valid response.
2355
3195
  // This ensures sessions that hit persistent API errors (e.g. 529) or malformed zero-usage
@@ -2375,7 +3215,7 @@ export class AgentSession {
2375
3215
  const usageMsg = messages[estimate.lastUsageIndex];
2376
3216
  if (compactionEntry &&
2377
3217
  usageMsg.role === "assistant" &&
2378
- usageMsg.timestamp <= new Date(compactionEntry.timestamp).getTime()) {
3218
+ this._isAssistantFromBeforeLatestCompaction(usageMsg)) {
2379
3219
  return false;
2380
3220
  }
2381
3221
  contextTokens = estimate.tokens;
@@ -2383,35 +3223,53 @@ export class AgentSession {
2383
3223
  }
2384
3224
  if (shouldCompact(contextTokens, contextWindow, settings)) {
2385
3225
  if (inlineReason) {
2386
- return await this._runPrePromptCompaction(assistantMessage, skipAbortedCheck, inlineReason);
3226
+ return await this._runPrePromptCompaction(assistantMessage, skipAbortedCheck, inlineReason, retryAfterCompaction);
2387
3227
  }
2388
3228
  else {
2389
- return await this._runAutoCompaction("threshold", false);
3229
+ const compacted = await this._runAutoCompaction("threshold", false);
3230
+ if (!compacted &&
3231
+ this._compactionLifecycle.state.status === "failed" &&
3232
+ getLatestCompactionEntry(this.sessionManager.getBranch()) !== null) {
3233
+ this._blockedPostCompactionAssistant = {
3234
+ assistant: assistantMessage,
3235
+ revision: this._messageRevision,
3236
+ };
3237
+ }
3238
+ return compacted;
2390
3239
  }
2391
3240
  }
2392
3241
  return false;
2393
3242
  }
2394
- async _runPrePromptCompaction(lastAssistantMessage, skipAbortedCheck, reason = "pre_prompt", willRetry = false) {
3243
+ async _runPrePromptCompaction(lastAssistantMessage, skipAbortedCheck, reason = "pre_prompt", willRetry = false, allowSummaryOnly = false) {
3244
+ const controller = new AbortController();
3245
+ this._claimCompactionController(controller, "compaction");
2395
3246
  this._emit({ type: "compaction_start", reason });
2396
- this._compactionAbortController = new AbortController();
2397
3247
  try {
2398
3248
  const execution = await this._executeCompaction({
3249
+ controller,
3250
+ owner: "compaction",
2399
3251
  reason,
2400
3252
  willRetry,
2401
3253
  lastAssistantMessage,
2402
3254
  skipAbortedCheck,
3255
+ allowSummaryOnly,
2403
3256
  });
2404
- if (!execution.accepted && isContextOverflow(lastAssistantMessage, this.model?.contextWindow ?? 0)) {
3257
+ if (!execution.accepted &&
3258
+ lastAssistantMessage &&
3259
+ isContextOverflow(lastAssistantMessage, this.model?.contextWindow ?? 0)) {
2405
3260
  this._overflowRecoveryAttempted = false;
2406
3261
  }
2407
3262
  return execution.accepted;
2408
3263
  }
2409
3264
  catch (error) {
2410
- if (isContextOverflow(lastAssistantMessage, this.model?.contextWindow ?? 0)) {
3265
+ if (!compactionExecutionOwnsTerminalTransition(error)) {
3266
+ return false;
3267
+ }
3268
+ if (lastAssistantMessage && isContextOverflow(lastAssistantMessage, this.model?.contextWindow ?? 0)) {
2411
3269
  this._overflowRecoveryAttempted = false;
2412
3270
  }
2413
3271
  const errorMessage = error instanceof Error ? error.message : "compaction failed";
2414
- const aborted = errorMessage === "Compaction cancelled" || (error instanceof Error && error.name === "AbortError");
3272
+ const aborted = isCompactionExecutionAborted(error);
2415
3273
  this._emit({
2416
3274
  type: "compaction_end",
2417
3275
  reason,
@@ -2423,12 +3281,44 @@ export class AgentSession {
2423
3281
  return false;
2424
3282
  }
2425
3283
  finally {
2426
- this._compactionAbortController = undefined;
3284
+ if (this._compactionAbortController === controller && this._compactionLifecycle.state.status !== "running") {
3285
+ this._compactionAbortController = undefined;
3286
+ }
2427
3287
  }
2428
3288
  }
3289
+ async _revalidateScheduledContinuationAdmission() {
3290
+ const model = this.model;
3291
+ const settings = this.settingsManager.getCompactionSettings();
3292
+ if (!model || !settings.enabled)
3293
+ return;
3294
+ const canonicalMessages = filterContextExcludedMessages(this.sessionManager.buildSessionContext().messages);
3295
+ const estimate = estimateContextTokens(canonicalMessages);
3296
+ const usageMessage = estimate.lastUsageIndex === null ? undefined : canonicalMessages[estimate.lastUsageIndex];
3297
+ const contextTokens = usageMessage?.role === "assistant" && this._isAssistantFromBeforeLatestCompaction(usageMessage)
3298
+ ? estimateMessagesTokens(canonicalMessages)
3299
+ : estimate.tokens;
3300
+ if (!shouldCompact(contextTokens, model.contextWindow, settings))
3301
+ return;
3302
+ const compacted = await this._runPrePromptCompaction(this._findLastAssistantMessage(), true, "pre_prompt");
3303
+ if (!compacted)
3304
+ throw new RequiredCompactionError();
3305
+ this._scheduledContinuationRecompacted = true;
3306
+ }
2429
3307
  async _continueAgentAfterCurrentRun() {
2430
3308
  await this.agent.waitForIdle();
2431
- await this.agent.continue();
3309
+ await this._revalidateScheduledContinuationAdmission();
3310
+ const useQueuedContinuation = this._scheduledContinuationRecompacted;
3311
+ try {
3312
+ if (useQueuedContinuation) {
3313
+ await this.agent.continueWithQueuedMessages();
3314
+ }
3315
+ else {
3316
+ await this.agent.continue();
3317
+ }
3318
+ }
3319
+ finally {
3320
+ this._scheduledContinuationRecompacted = false;
3321
+ }
2432
3322
  }
2433
3323
  _scheduleContinuationAfterCurrentEvent() {
2434
3324
  // Tool hooks wait for queued message persistence, so continue() cannot run inside this event promise.
@@ -2458,49 +3348,53 @@ export class AgentSession {
2458
3348
  async _runAutoCompaction(reason, willRetry) {
2459
3349
  const finishCompactionWork = this._sessionWorkBarrier.begin();
2460
3350
  const agentMessagesAtStart = this.agent.state.messages.slice();
2461
- this._emit({ type: "compaction_start", reason });
2462
3351
  const autoCompactionController = new AbortController();
2463
- this._autoCompactionAbortController = autoCompactionController;
3352
+ this._claimCompactionController(autoCompactionController, "auto");
3353
+ const endBeforeExecution = () => {
3354
+ this._emit({ type: "compaction_start", reason });
3355
+ if (reason === "overflow")
3356
+ this._overflowRecoveryAttempted = false;
3357
+ this._emit({
3358
+ type: "compaction_end",
3359
+ reason,
3360
+ result: undefined,
3361
+ aborted: false,
3362
+ willRetry: false,
3363
+ });
3364
+ return false;
3365
+ };
2464
3366
  try {
2465
3367
  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;
3368
+ return endBeforeExecution();
2476
3369
  }
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;
3370
+ try {
3371
+ // Resolve once before admission so a pending auth refresh remains a
3372
+ // cancellable boundary. _executeCompaction resolves the policy-specific
3373
+ // auth after extension compaction hooks have had a chance to provide a
3374
+ // summary without credentials.
3375
+ await this._modelRuntime.getAuth(this.model);
2489
3376
  }
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
- });
3377
+ catch {
3378
+ if (!this._ownsCompactionController(autoCompactionController, "auto"))
3379
+ return false;
3380
+ return endBeforeExecution();
3381
+ }
3382
+ if (!this._ownsCompactionController(autoCompactionController, "auto"))
2501
3383
  return false;
3384
+ const preparation = prepareCompaction(this.sessionManager.getBranch(), this.settingsManager.getCompactionSettings(), reason === "overflow");
3385
+ if (!preparation) {
3386
+ return endBeforeExecution();
2502
3387
  }
2503
- const execution = await this._executeCompaction({ reason, willRetry, agentMessagesAtStart });
3388
+ if (!this._ownsCompactionController(autoCompactionController, "auto"))
3389
+ return false;
3390
+ this._emit({ type: "compaction_start", reason });
3391
+ const execution = await this._executeCompaction({
3392
+ controller: autoCompactionController,
3393
+ owner: "auto",
3394
+ reason,
3395
+ willRetry,
3396
+ agentMessagesAtStart,
3397
+ });
2504
3398
  if (!execution.accepted) {
2505
3399
  if (reason === "overflow")
2506
3400
  this._overflowRecoveryAttempted = false;
@@ -2530,10 +3424,13 @@ export class AgentSession {
2530
3424
  return false;
2531
3425
  }
2532
3426
  catch (error) {
3427
+ if (!compactionExecutionOwnsTerminalTransition(error)) {
3428
+ return false;
3429
+ }
2533
3430
  if (reason === "overflow")
2534
3431
  this._overflowRecoveryAttempted = false;
2535
3432
  const errorMessage = error instanceof Error ? error.message : "compaction failed";
2536
- const aborted = errorMessage === "Compaction cancelled" || (error instanceof Error && error.name === "AbortError");
3433
+ const aborted = isCompactionExecutionAborted(error);
2537
3434
  this._emit({
2538
3435
  type: "compaction_end",
2539
3436
  reason,
@@ -2566,27 +3463,39 @@ export class AgentSession {
2566
3463
  return this.settingsManager.getCompactionEnabled();
2567
3464
  }
2568
3465
  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;
3466
+ const finishBindingWork = this._sessionWorkBarrier.begin();
3467
+ const bindingPromptReadiness = new Set();
3468
+ this._extensionBindingPromptReadiness = bindingPromptReadiness;
3469
+ try {
3470
+ if (bindings.uiContext !== undefined) {
3471
+ this._extensionUIContext = bindings.uiContext;
3472
+ }
3473
+ if (bindings.mode !== undefined) {
3474
+ this._extensionMode = bindings.mode;
3475
+ }
3476
+ if (bindings.commandContextActions !== undefined) {
3477
+ this._extensionCommandContextActions = bindings.commandContextActions;
3478
+ }
3479
+ if (bindings.abortHandler !== undefined) {
3480
+ this._extensionAbortHandler = bindings.abortHandler;
3481
+ }
3482
+ if (bindings.shutdownHandler !== undefined) {
3483
+ this._extensionShutdownHandler = bindings.shutdownHandler;
3484
+ }
3485
+ if (bindings.onError !== undefined) {
3486
+ this._extensionErrorListener = bindings.onError;
3487
+ }
3488
+ this._applyExtensionBindings(this._extensionRunner);
3489
+ await this._extensionRunner.emit(this._sessionStartEvent);
3490
+ await this.extendResourcesFromExtensions(this._sessionStartEvent.reason === "reload" ? "reload" : "startup");
2583
3491
  }
2584
- if (bindings.onError !== undefined) {
2585
- this._extensionErrorListener = bindings.onError;
3492
+ finally {
3493
+ if (this._extensionBindingPromptReadiness === bindingPromptReadiness) {
3494
+ this._extensionBindingPromptReadiness = undefined;
3495
+ }
3496
+ finishBindingWork();
2586
3497
  }
2587
- this._applyExtensionBindings(this._extensionRunner);
2588
- await this._extensionRunner.emit(this._sessionStartEvent);
2589
- await this.extendResourcesFromExtensions(this._sessionStartEvent.reason === "reload" ? "reload" : "startup");
3498
+ await Promise.all(bindingPromptReadiness);
2590
3499
  }
2591
3500
  async extendResourcesFromExtensions(reason) {
2592
3501
  if (!this._extensionRunner.hasHandlers("resources_discover")) {
@@ -2729,7 +3638,7 @@ export class AgentSession {
2729
3638
  getServiceTier: () => this.serviceTier,
2730
3639
  isIdle: () => this.isIdle,
2731
3640
  isProjectTrusted: () => this.settingsManager.isProjectTrusted(),
2732
- getSignal: () => this.agent.signal,
3641
+ getSignal: () => this._extensionEventSignal ?? this.agent.signal,
2733
3642
  abort: () => {
2734
3643
  if (this._extensionAbortHandler) {
2735
3644
  this._extensionAbortHandler();
@@ -2788,27 +3697,39 @@ export class AgentSession {
2788
3697
  },
2789
3698
  },
2790
3699
  compact: (options) => {
3700
+ const admission = this._claimPendingCompactionAdmission();
3701
+ const controller = admission.controller;
2791
3702
  void (async () => {
2792
- this._disconnectFromAgent();
2793
- await this.abort();
2794
- this._compactionAbortController = new AbortController();
2795
- this._emit({ type: "compaction_start", reason: "extension" });
3703
+ let outcome = "failed";
3704
+ let disconnected = false;
2796
3705
  try {
3706
+ await this._abortActiveAgentAndRetry();
3707
+ this._disconnectFromAgent();
3708
+ disconnected = true;
3709
+ this._emit({ type: "compaction_start", reason: "extension" });
2797
3710
  const execution = await this._executeCompaction({
3711
+ controller,
3712
+ owner: "compaction",
2798
3713
  reason: "extension",
2799
3714
  customInstructions: options?.customInstructions,
2800
3715
  willRetry: false,
2801
3716
  });
2802
3717
  if (execution.accepted) {
3718
+ outcome = "completed";
2803
3719
  options?.onComplete?.(execution.result);
2804
3720
  }
2805
3721
  else {
3722
+ outcome = "failed";
2806
3723
  options?.onError?.(new CompactionRejectedError(execution.rejectionCause));
2807
3724
  }
2808
3725
  }
2809
3726
  catch (error) {
3727
+ outcome = isCompactionExecutionAborted(error) ? "aborted" : "failed";
3728
+ if (!compactionExecutionOwnsTerminalTransition(error)) {
3729
+ return;
3730
+ }
2810
3731
  const message = error instanceof Error ? error.message : String(error);
2811
- const aborted = message === "Compaction cancelled" || (error instanceof Error && error.name === "AbortError");
3732
+ const aborted = isCompactionExecutionAborted(error);
2812
3733
  this._emit({
2813
3734
  type: "compaction_end",
2814
3735
  reason: "extension",
@@ -2821,8 +3742,13 @@ export class AgentSession {
2821
3742
  options?.onError?.(err);
2822
3743
  }
2823
3744
  finally {
2824
- this._compactionAbortController = undefined;
2825
- this._reconnectToAgent();
3745
+ if (this._compactionAbortController === controller &&
3746
+ this._compactionLifecycle.state.status !== "running") {
3747
+ this._compactionAbortController = undefined;
3748
+ }
3749
+ this._releasePendingCompactionAdmission(admission, outcome);
3750
+ if (disconnected && !this.isCompacting)
3751
+ this._reconnectToAgent();
2826
3752
  }
2827
3753
  })();
2828
3754
  },
@@ -3056,15 +3982,43 @@ export class AgentSession {
3056
3982
  }
3057
3983
  return Math.ceil(value);
3058
3984
  }
3985
+ /**
3986
+ * Retry policy + callbacks shared by compaction and branch-summary summarization calls.
3987
+ * Uses the same `settings.retry` budget/backoff as agent-turn retries so a single transient
3988
+ * stream drop no longer fails the whole operation. `source` carries the context
3989
+ * the TUI needs to render the retry and recreate the underlying indicator.
3990
+ */
3991
+ _summarizationRetryCallbacks(source) {
3992
+ return {
3993
+ onRetryScheduled: (attempt, maxAttempts, delayMs, errorMessage) => {
3994
+ this._emit({
3995
+ type: "summarization_retry_scheduled",
3996
+ attempt,
3997
+ maxAttempts,
3998
+ delayMs,
3999
+ errorMessage,
4000
+ });
4001
+ },
4002
+ onRetryAttemptStart: () => {
4003
+ this._emit({
4004
+ type: "summarization_retry_attempt_start",
4005
+ ...source,
4006
+ });
4007
+ },
4008
+ onRetryFinished: () => {
4009
+ this._emit({ type: "summarization_retry_finished" });
4010
+ },
4011
+ };
4012
+ }
3059
4013
  /**
3060
4014
  * Handle retryable errors with exponential backoff.
3061
- * @returns true if retry was initiated, false if max retries exceeded or disabled
4015
+ * @returns whether retry continuation started, was blocked by compaction, or was not handled
3062
4016
  */
3063
4017
  async _handleRetryableError(message, options = {}) {
3064
4018
  const settings = this.settingsManager.getRetrySettings();
3065
4019
  if (!settings.enabled) {
3066
4020
  this._resolveRetry();
3067
- return false;
4021
+ return "not-handled";
3068
4022
  }
3069
4023
  // Retry promise is created synchronously in _handleAgentEvent for agent_end.
3070
4024
  // Keep a defensive fallback here in case a future refactor bypasses that path.
@@ -3082,7 +4036,7 @@ export class AgentSession {
3082
4036
  switchedFallback = await this._retryFallback.tryFallback("hard-error", { errorMessage });
3083
4037
  if (!switchedFallback) {
3084
4038
  this._resolveRetry();
3085
- return false;
4039
+ return "not-handled";
3086
4040
  }
3087
4041
  // The fallback starts fresh; the failed model's transient attempts do not carry over.
3088
4042
  this._retryAttempt = 1;
@@ -3092,7 +4046,7 @@ export class AgentSession {
3092
4046
  // same-model retries or the transient over-budget fallback escape hatch.
3093
4047
  if (this._retryAttempt + 1 > settings.maxRetries) {
3094
4048
  this._resolveRetry();
3095
- return false;
4049
+ return "not-handled";
3096
4050
  }
3097
4051
  switchedFallback = await this._retryFallback.tryFallback("refusal", {});
3098
4052
  if (!switchedFallback) {
@@ -3101,7 +4055,7 @@ export class AgentSession {
3101
4055
  this._emit({ type: "retry_fallback_exhausted", chainKey: exhaustedChainKey, lastError: errorMessage });
3102
4056
  }
3103
4057
  this._resolveRetry();
3104
- return false;
4058
+ return "not-handled";
3105
4059
  }
3106
4060
  this._retryAttempt++;
3107
4061
  }
@@ -3133,7 +4087,7 @@ export class AgentSession {
3133
4087
  });
3134
4088
  this._retryAttempt = 0;
3135
4089
  this._resolveRetry();
3136
- return false;
4090
+ return "not-handled";
3137
4091
  }
3138
4092
  }
3139
4093
  }
@@ -3148,7 +4102,7 @@ export class AgentSession {
3148
4102
  });
3149
4103
  this._retryAttempt = 0;
3150
4104
  this._resolveRetry();
3151
- return false;
4105
+ return "not-handled";
3152
4106
  }
3153
4107
  if (!switchedFallback) {
3154
4108
  switchedFallback = await this._retryFallback.tryFallback("transient", {
@@ -3189,19 +4143,52 @@ export class AgentSession {
3189
4143
  finalError: "Retry cancelled",
3190
4144
  });
3191
4145
  this._resolveRetry();
3192
- return false;
4146
+ return "not-handled";
3193
4147
  }
3194
4148
  this._retryAbortController = undefined;
3195
4149
  // Turn boundary: a suppression that expired during the backoff sleep lets
3196
4150
  // the retry continue on the restored primary instead of the fallback model.
3197
4151
  await this._maybeRestoreFallbackPrimary();
3198
- // Retry via continue() - use setTimeout to break out of event handler chain
4152
+ // Model fallback (or reversion) can select a smaller context window after
4153
+ // the prior retry checks. Revalidate canonical session context immediately
4154
+ // before the continuation so a rejected compaction never admits that model.
4155
+ const model = this.model;
4156
+ const compactionSettings = this.settingsManager.getCompactionSettings();
4157
+ const contextTokens = estimateContextTokens(filterContextExcludedMessages(this.sessionManager.buildSessionContext().messages)).tokens;
4158
+ if (compactionSettings.enabled &&
4159
+ model &&
4160
+ shouldCompact(contextTokens, model.contextWindow, compactionSettings)) {
4161
+ if (!(await this._runPrePromptCompaction(message, true, "threshold", true, true))) {
4162
+ const attempt = this._retryAttempt;
4163
+ this._retryAttempt = 0;
4164
+ this._emit({
4165
+ type: "auto_retry_end",
4166
+ success: false,
4167
+ attempt,
4168
+ finalError: new RequiredCompactionError().message,
4169
+ });
4170
+ this._resolveRetry();
4171
+ return "blocked";
4172
+ }
4173
+ this._skipNextPostRetryCompactionCheck = true;
4174
+ }
4175
+ // Retry via the shared continuation path after the event handler chain settles.
4176
+ // Agent core would otherwise drain these queues before the continuation
4177
+ // revalidates its provider admission. The scheduled continuation owns them
4178
+ // until it either starts or reports a terminal admission failure.
4179
+ this.agent.suppressQueuedMessageDrain();
3199
4180
  setTimeout(() => {
3200
- this.agent.continue().catch(() => {
3201
- // Retry failed - will be caught by next agent_end
4181
+ void this._continueAgentAfterCurrentRun().catch(async (error) => {
4182
+ const message = error instanceof Error ? error.message : String(error);
4183
+ this._emit({
4184
+ type: "continuation_error",
4185
+ errorMessage: `Failed to continue queued messages: ${message}`,
4186
+ });
4187
+ await this._emitAgentSettled();
4188
+ this._resolveRetry();
3202
4189
  });
3203
4190
  }, 0);
3204
- return true;
4191
+ return "continued";
3205
4192
  }
3206
4193
  /**
3207
4194
  * Cancel in-progress retry.
@@ -3245,6 +4232,7 @@ export class AgentSession {
3245
4232
  * @param command The bash command to execute
3246
4233
  * @param onChunk Optional streaming callback for output
3247
4234
  * @param options.excludeFromContext If true, command output won't be sent to LLM (!! prefix)
4235
+ * @param options.id Optional identifier included in bash execution update events
3248
4236
  * @param options.operations Custom BashOperations for remote execution
3249
4237
  */
3250
4238
  async executeBash(command, onChunk, options) {
@@ -3255,7 +4243,10 @@ export class AgentSession {
3255
4243
  const resolvedCommand = prefix ? `${prefix}\n${command}` : command;
3256
4244
  try {
3257
4245
  const result = await executeBashWithOperations(resolvedCommand, this.sessionManager.getCwd(), options?.operations ?? createLocalBashOperations({ shellPath }), {
3258
- onChunk,
4246
+ onChunk: (delta) => {
4247
+ onChunk?.(delta);
4248
+ this._emit({ type: "bash_execution_update", id: options?.id, delta });
4249
+ },
3259
4250
  signal: this._bashAbortController.signal,
3260
4251
  });
3261
4252
  this.recordBashResult(command, result, options);
@@ -3413,6 +4404,7 @@ export class AgentSession {
3413
4404
  // Run default summarizer if needed
3414
4405
  let summaryText;
3415
4406
  let summaryDetails;
4407
+ let summaryUsage;
3416
4408
  if (options.summarize && entriesToSummarize.length > 0 && !extensionSummary) {
3417
4409
  const model = this.model;
3418
4410
  const { apiKey, headers, extraBody, env } = await this._getCompactionRequestAuth(model);
@@ -3427,7 +4419,9 @@ export class AgentSession {
3427
4419
  customInstructions,
3428
4420
  replaceInstructions,
3429
4421
  reserveTokens: branchSummarySettings.reserveTokens,
3430
- streamFn: this.agent.streamFn,
4422
+ streamFn: this.agent.streamFunction,
4423
+ retry: this.settingsManager.getRetrySettings(),
4424
+ callbacks: this._summarizationRetryCallbacks({ source: "branchSummary" }),
3431
4425
  });
3432
4426
  if (result.aborted) {
3433
4427
  return { cancelled: true, aborted: true };
@@ -3436,6 +4430,7 @@ export class AgentSession {
3436
4430
  throw new Error(result.error);
3437
4431
  }
3438
4432
  summaryText = result.summary;
4433
+ summaryUsage = result.usage;
3439
4434
  summaryDetails = {
3440
4435
  readFiles: result.readFiles || [],
3441
4436
  modifiedFiles: result.modifiedFiles || [],
@@ -3444,6 +4439,7 @@ export class AgentSession {
3444
4439
  else if (extensionSummary) {
3445
4440
  summaryText = extensionSummary.summary;
3446
4441
  summaryDetails = extensionSummary.details;
4442
+ summaryUsage = extensionSummary.usage;
3447
4443
  }
3448
4444
  // Determine the new leaf position based on target type
3449
4445
  let newLeafId;
@@ -3451,18 +4447,12 @@ export class AgentSession {
3451
4447
  if (targetEntry.type === "message" && targetEntry.message.role === "user") {
3452
4448
  // User message: leaf = parent (null if root), text goes to editor
3453
4449
  newLeafId = targetEntry.parentId;
3454
- editorText = this._extractUserMessageText(targetEntry.message.content);
4450
+ editorText = contentText(targetEntry.message.content, "");
3455
4451
  }
3456
4452
  else if (targetEntry.type === "custom_message") {
3457
4453
  // Custom message: leaf = parent (null if root), text goes to editor
3458
4454
  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("");
4455
+ editorText = contentText(targetEntry.content, "");
3466
4456
  }
3467
4457
  else {
3468
4458
  // Non-user message: leaf = selected node
@@ -3473,7 +4463,7 @@ export class AgentSession {
3473
4463
  let summaryEntry;
3474
4464
  if (summaryText) {
3475
4465
  // Create summary at target position (can be null for root)
3476
- const summaryId = this.sessionManager.branchWithSummary(newLeafId, summaryText, summaryDetails, fromExtension);
4466
+ const summaryId = this.sessionManager.branchWithSummary(newLeafId, summaryText, summaryDetails, fromExtension, summaryUsage);
3477
4467
  summaryEntry = this.sessionManager.getEntry(summaryId);
3478
4468
  // Attach label to the summary entry
3479
4469
  if (label) {
@@ -3492,9 +4482,8 @@ export class AgentSession {
3492
4482
  if (label && !summaryText) {
3493
4483
  this.sessionManager.appendLabelChange(targetId, label);
3494
4484
  }
3495
- // Update agent state
3496
- const sessionContext = this.sessionManager.buildSessionContext();
3497
- this.agent.state.messages = sessionContext.messages;
4485
+ // Update agent state (preserving exact messages still awaiting persistence)
4486
+ this._restoreAgentMessagesFromSession();
3498
4487
  this._incrementMessageRevision();
3499
4488
  // Emit session_tree event
3500
4489
  await this._extensionRunner.emit({
@@ -3522,24 +4511,13 @@ export class AgentSession {
3522
4511
  continue;
3523
4512
  if (entry.message.role !== "user")
3524
4513
  continue;
3525
- const text = this._extractUserMessageText(entry.message.content);
4514
+ const text = contentText(entry.message.content, "");
3526
4515
  if (text) {
3527
4516
  result.push({ entryId: entry.id, text });
3528
4517
  }
3529
4518
  }
3530
4519
  return result;
3531
4520
  }
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
4521
  /**
3544
4522
  * Get session statistics. Aggregates over ALL session entries (including
3545
4523
  * history that was compacted away), so token/cost totals reflect what was
@@ -3551,12 +4529,11 @@ export class AgentSession {
3551
4529
  let toolResults = 0;
3552
4530
  let totalMessages = 0;
3553
4531
  let toolCalls = 0;
3554
- let totalInput = 0;
3555
- let totalOutput = 0;
3556
- let totalCacheRead = 0;
3557
- let totalCacheWrite = 0;
3558
- let totalCost = 0;
4532
+ const usageTotals = createUsageTotals();
3559
4533
  for (const entry of this.sessionManager.getEntries()) {
4534
+ if ((entry.type === "branch_summary" || entry.type === "compaction") && entry.usage) {
4535
+ addUsageToTotals(usageTotals, entry.usage);
4536
+ }
3560
4537
  if (entry.type !== "message")
3561
4538
  continue;
3562
4539
  totalMessages++;
@@ -3566,6 +4543,9 @@ export class AgentSession {
3566
4543
  }
3567
4544
  else if (message.role === "toolResult") {
3568
4545
  toolResults++;
4546
+ if (message.usage) {
4547
+ addUsageToTotals(usageTotals, message.usage);
4548
+ }
3569
4549
  }
3570
4550
  else if (message.role === "assistant") {
3571
4551
  assistantMessages++;
@@ -3573,12 +4553,7 @@ export class AgentSession {
3573
4553
  if (Array.isArray(assistantMsg.content)) {
3574
4554
  toolCalls += assistantMsg.content.filter((c) => c.type === "toolCall").length;
3575
4555
  }
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;
4556
+ addUsageToTotals(usageTotals, assistantMsg.usage);
3582
4557
  }
3583
4558
  }
3584
4559
  return {
@@ -3590,13 +4565,13 @@ export class AgentSession {
3590
4565
  toolResults,
3591
4566
  totalMessages,
3592
4567
  tokens: {
3593
- input: totalInput,
3594
- output: totalOutput,
3595
- cacheRead: totalCacheRead,
3596
- cacheWrite: totalCacheWrite,
3597
- total: totalInput + totalOutput + totalCacheRead + totalCacheWrite,
4568
+ input: usageTotals.input,
4569
+ output: usageTotals.output,
4570
+ cacheRead: usageTotals.cacheRead,
4571
+ cacheWrite: usageTotals.cacheWrite,
4572
+ total: usageTotals.input + usageTotals.output + usageTotals.cacheRead + usageTotals.cacheWrite,
3598
4573
  },
3599
- cost: totalCost,
4574
+ cost: usageTotals.cost,
3600
4575
  contextUsage: this.getContextUsage(),
3601
4576
  };
3602
4577
  }