@averyyy/pi-coding-agent 0.85.1-piclient.9 → 0.86.1-piclient.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (324) hide show
  1. package/CHANGELOG.md +109 -0
  2. package/README.md +3 -1
  3. package/dist/bundle/chunks/anthropic-messages-MYU5ZMRF.js +6 -0
  4. package/dist/bundle/chunks/azure-openai-responses-QJ7SPP5M.js +2 -0
  5. package/dist/bundle/chunks/bedrock-converse-stream.js +47 -38
  6. package/dist/bundle/chunks/chunk-7L6OAVKC.js +1663 -0
  7. package/dist/bundle/chunks/chunk-A3JYRWB6.js +2 -0
  8. package/dist/bundle/chunks/chunk-DTD7JQ7Y.js +42 -0
  9. package/dist/bundle/chunks/chunk-FUXEF6JQ.js +44 -0
  10. package/dist/bundle/chunks/{chunk-IDDQWTHI.js → chunk-RCZIEVGO.js} +10 -1
  11. package/dist/bundle/chunks/chunk-S7SZN6Z3.js +2 -0
  12. package/dist/bundle/chunks/chunk-UZNGGY6N.js +11 -0
  13. package/dist/bundle/chunks/chunk-XREANBOX.js +2 -0
  14. package/dist/bundle/chunks/devin-runtime.js +7 -2
  15. package/dist/bundle/chunks/github-copilot.js +1 -1
  16. package/dist/bundle/chunks/google-generative-ai-IMS5EK2Y.js +2 -0
  17. package/dist/bundle/chunks/google-vertex-O4YJEQUW.js +2 -0
  18. package/dist/bundle/chunks/jiti-loader-C3NU2SDH.js +21 -0
  19. package/dist/bundle/chunks/jiti-static-loader-E6ANQD5T.js +2 -0
  20. package/dist/bundle/chunks/meta.js +2 -0
  21. package/dist/bundle/chunks/mistral-conversations-NOSQHBEY.js +5 -0
  22. package/dist/bundle/chunks/{node-KG362ECQ.js → node-O3RIBJRH.js} +1 -1
  23. package/dist/bundle/chunks/openai-codex-responses-SG7PGUUZ.js +10 -0
  24. package/dist/bundle/chunks/openai-completions-CYGM3XXP.js +7 -0
  25. package/dist/bundle/chunks/openai-responses-AH7QTG23.js +2 -0
  26. package/dist/bundle/chunks/{pi-messages-PML4SPVJ.js → pi-messages-GFKBWJHZ.js} +1 -1
  27. package/dist/bundle/chunks/virtual-modules-SC5AFIK5.js +2 -0
  28. package/dist/bundle/cli-runtime.js +3 -0
  29. package/dist/bundle/cli.js +4 -2
  30. package/dist/bundle/index.js +1 -1
  31. package/dist/bundle/rpc-entry.js +1 -1
  32. package/dist/cli/args.d.ts.map +1 -1
  33. package/dist/cli/args.js +1 -0
  34. package/dist/cli/args.js.map +1 -1
  35. package/dist/cli/session-picker.d.ts +1 -1
  36. package/dist/cli/session-picker.d.ts.map +1 -1
  37. package/dist/cli/session-picker.js.map +1 -1
  38. package/dist/config.d.ts.map +1 -1
  39. package/dist/config.js +1 -1
  40. package/dist/config.js.map +1 -1
  41. package/dist/core/agent-session.d.ts +56 -6
  42. package/dist/core/agent-session.d.ts.map +1 -1
  43. package/dist/core/agent-session.js +362 -183
  44. package/dist/core/agent-session.js.map +1 -1
  45. package/dist/core/bug-report-upload.d.ts +12 -0
  46. package/dist/core/bug-report-upload.d.ts.map +1 -0
  47. package/dist/core/bug-report-upload.js +22 -0
  48. package/dist/core/bug-report-upload.js.map +1 -0
  49. package/dist/core/bug-report.d.ts +176 -0
  50. package/dist/core/bug-report.d.ts.map +1 -0
  51. package/dist/core/bug-report.js +292 -0
  52. package/dist/core/bug-report.js.map +1 -0
  53. package/dist/core/cache-stats.d.ts.map +1 -1
  54. package/dist/core/cache-stats.js +12 -1
  55. package/dist/core/cache-stats.js.map +1 -1
  56. package/dist/core/cache-warmer.d.ts +102 -0
  57. package/dist/core/cache-warmer.d.ts.map +1 -0
  58. package/dist/core/cache-warmer.js +342 -0
  59. package/dist/core/cache-warmer.js.map +1 -0
  60. package/dist/core/compaction/compaction.d.ts +2 -2
  61. package/dist/core/compaction/compaction.d.ts.map +1 -1
  62. package/dist/core/compaction/compaction.js +23 -18
  63. package/dist/core/compaction/compaction.js.map +1 -1
  64. package/dist/core/crash-log.d.ts +22 -0
  65. package/dist/core/crash-log.d.ts.map +1 -0
  66. package/dist/core/crash-log.js +71 -0
  67. package/dist/core/crash-log.js.map +1 -0
  68. package/dist/core/experimental.d.ts +0 -4
  69. package/dist/core/experimental.d.ts.map +1 -1
  70. package/dist/core/experimental.js +0 -4
  71. package/dist/core/experimental.js.map +1 -1
  72. package/dist/core/extensions/index.d.ts +1 -1
  73. package/dist/core/extensions/index.d.ts.map +1 -1
  74. package/dist/core/extensions/index.js.map +1 -1
  75. package/dist/core/extensions/jiti-loader.d.ts +2 -0
  76. package/dist/core/extensions/jiti-loader.d.ts.map +1 -0
  77. package/dist/core/extensions/jiti-loader.js +4 -0
  78. package/dist/core/extensions/jiti-loader.js.map +1 -0
  79. package/dist/core/extensions/jiti-static-loader.d.ts +2 -0
  80. package/dist/core/extensions/jiti-static-loader.d.ts.map +1 -0
  81. package/dist/core/extensions/jiti-static-loader.js +4 -0
  82. package/dist/core/extensions/jiti-static-loader.js.map +1 -0
  83. package/dist/core/extensions/loader.d.ts.map +1 -1
  84. package/dist/core/extensions/loader.js +38 -51
  85. package/dist/core/extensions/loader.js.map +1 -1
  86. package/dist/core/extensions/runner.d.ts +10 -7
  87. package/dist/core/extensions/runner.d.ts.map +1 -1
  88. package/dist/core/extensions/runner.js +85 -67
  89. package/dist/core/extensions/runner.js.map +1 -1
  90. package/dist/core/extensions/types.d.ts +60 -50
  91. package/dist/core/extensions/types.d.ts.map +1 -1
  92. package/dist/core/extensions/types.js.map +1 -1
  93. package/dist/core/extensions/virtual-modules.d.ts +3 -0
  94. package/dist/core/extensions/virtual-modules.d.ts.map +1 -0
  95. package/dist/core/extensions/virtual-modules.js +38 -0
  96. package/dist/core/extensions/virtual-modules.js.map +1 -0
  97. package/dist/core/extensions/wrapper.d.ts.map +1 -1
  98. package/dist/core/extensions/wrapper.js +1 -20
  99. package/dist/core/extensions/wrapper.js.map +1 -1
  100. package/dist/core/index.d.ts +2 -1
  101. package/dist/core/index.d.ts.map +1 -1
  102. package/dist/core/index.js.map +1 -1
  103. package/dist/core/keybindings.d.ts +1 -1
  104. package/dist/core/keybindings.d.ts.map +1 -1
  105. package/dist/core/keybindings.js +1 -1
  106. package/dist/core/keybindings.js.map +1 -1
  107. package/dist/core/messages.d.ts.map +1 -1
  108. package/dist/core/messages.js +1 -0
  109. package/dist/core/messages.js.map +1 -1
  110. package/dist/core/model-config.d.ts +101 -20
  111. package/dist/core/model-config.d.ts.map +1 -1
  112. package/dist/core/model-config.js +25 -18
  113. package/dist/core/model-config.js.map +1 -1
  114. package/dist/core/model-registry.d.ts +5 -1
  115. package/dist/core/model-registry.d.ts.map +1 -1
  116. package/dist/core/model-registry.js +8 -0
  117. package/dist/core/model-registry.js.map +1 -1
  118. package/dist/core/model-resolver.d.ts.map +1 -1
  119. package/dist/core/model-resolver.js +2 -1
  120. package/dist/core/model-resolver.js.map +1 -1
  121. package/dist/core/model-runtime.d.ts.map +1 -1
  122. package/dist/core/model-runtime.js +5 -3
  123. package/dist/core/model-runtime.js.map +1 -1
  124. package/dist/core/provider-composer.d.ts +3 -2
  125. package/dist/core/provider-composer.d.ts.map +1 -1
  126. package/dist/core/provider-composer.js +2 -0
  127. package/dist/core/provider-composer.js.map +1 -1
  128. package/dist/core/radius.d.ts +3 -0
  129. package/dist/core/radius.d.ts.map +1 -1
  130. package/dist/core/radius.js +6 -0
  131. package/dist/core/radius.js.map +1 -1
  132. package/dist/core/sdk.d.ts.map +1 -1
  133. package/dist/core/sdk.js +61 -41
  134. package/dist/core/sdk.js.map +1 -1
  135. package/dist/core/session-export.d.ts +6 -2
  136. package/dist/core/session-export.d.ts.map +1 -1
  137. package/dist/core/session-export.js +14 -13
  138. package/dist/core/session-export.js.map +1 -1
  139. package/dist/core/session-manager.d.ts +29 -6
  140. package/dist/core/session-manager.d.ts.map +1 -1
  141. package/dist/core/session-manager.js +159 -96
  142. package/dist/core/session-manager.js.map +1 -1
  143. package/dist/core/settings-manager.d.ts +20 -4
  144. package/dist/core/settings-manager.d.ts.map +1 -1
  145. package/dist/core/settings-manager.js +43 -7
  146. package/dist/core/settings-manager.js.map +1 -1
  147. package/dist/core/slash-commands.d.ts.map +1 -1
  148. package/dist/core/slash-commands.js +1 -0
  149. package/dist/core/slash-commands.js.map +1 -1
  150. package/dist/core/system-prompt.d.ts +49 -6
  151. package/dist/core/system-prompt.d.ts.map +1 -1
  152. package/dist/core/system-prompt.js +121 -92
  153. package/dist/core/system-prompt.js.map +1 -1
  154. package/dist/core/tools/bash.d.ts +2 -1
  155. package/dist/core/tools/bash.d.ts.map +1 -1
  156. package/dist/core/tools/bash.js +19 -5
  157. package/dist/core/tools/bash.js.map +1 -1
  158. package/dist/core/tools/edit.d.ts.map +1 -1
  159. package/dist/core/tools/edit.js +1 -2
  160. package/dist/core/tools/edit.js.map +1 -1
  161. package/dist/core/tools/read.d.ts.map +1 -1
  162. package/dist/core/tools/read.js +1 -2
  163. package/dist/core/tools/read.js.map +1 -1
  164. package/dist/core/tools/renderers/bash.d.ts.map +1 -1
  165. package/dist/core/tools/renderers/bash.js +9 -1
  166. package/dist/core/tools/renderers/bash.js.map +1 -1
  167. package/dist/core/tools/write.d.ts.map +1 -1
  168. package/dist/core/tools/write.js +1 -2
  169. package/dist/core/tools/write.js.map +1 -1
  170. package/dist/core/usage-totals.d.ts +1 -1
  171. package/dist/core/usage-totals.d.ts.map +1 -1
  172. package/dist/core/usage-totals.js +5 -1
  173. package/dist/core/usage-totals.js.map +1 -1
  174. package/dist/extensions/llama/client.d.ts +2 -0
  175. package/dist/extensions/llama/client.d.ts.map +1 -1
  176. package/dist/extensions/llama/client.js +7 -3
  177. package/dist/extensions/llama/client.js.map +1 -1
  178. package/dist/extensions/llama/provider.d.ts.map +1 -1
  179. package/dist/extensions/llama/provider.js +20 -5
  180. package/dist/extensions/llama/provider.js.map +1 -1
  181. package/dist/index.d.ts +3 -2
  182. package/dist/index.d.ts.map +1 -1
  183. package/dist/index.js.map +1 -1
  184. package/dist/main.d.ts.map +1 -1
  185. package/dist/main.js +13 -9
  186. package/dist/main.js.map +1 -1
  187. package/dist/migrations.d.ts.map +1 -1
  188. package/dist/migrations.js +2 -2
  189. package/dist/migrations.js.map +1 -1
  190. package/dist/modes/interactive/bug-report.d.ts +16 -0
  191. package/dist/modes/interactive/bug-report.d.ts.map +1 -0
  192. package/dist/modes/interactive/bug-report.js +206 -0
  193. package/dist/modes/interactive/bug-report.js.map +1 -0
  194. package/dist/modes/interactive/chat-viewport.d.ts.map +1 -1
  195. package/dist/modes/interactive/chat-viewport.js +1 -1
  196. package/dist/modes/interactive/chat-viewport.js.map +1 -1
  197. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  198. package/dist/modes/interactive/components/branch-summary-message.js +12 -5
  199. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  200. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  201. package/dist/modes/interactive/components/compaction-summary-message.js +12 -5
  202. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  203. package/dist/modes/interactive/components/custom-editor.d.ts +3 -3
  204. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  205. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  206. package/dist/modes/interactive/components/extension-editor.d.ts +4 -1
  207. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  208. package/dist/modes/interactive/components/extension-editor.js +7 -2
  209. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  210. package/dist/modes/interactive/components/extension-input.d.ts +2 -0
  211. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  212. package/dist/modes/interactive/components/extension-input.js +6 -0
  213. package/dist/modes/interactive/components/extension-input.js.map +1 -1
  214. package/dist/modes/interactive/components/extension-selector.d.ts +1 -0
  215. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  216. package/dist/modes/interactive/components/extension-selector.js +4 -0
  217. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  218. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  219. package/dist/modes/interactive/components/footer.js +4 -1
  220. package/dist/modes/interactive/components/footer.js.map +1 -1
  221. package/dist/modes/interactive/components/session-selector.d.ts +5 -5
  222. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  223. package/dist/modes/interactive/components/session-selector.js +74 -48
  224. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  225. package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
  226. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  227. package/dist/modes/interactive/components/settings-selector.js +11 -0
  228. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  229. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  230. package/dist/modes/interactive/components/skill-invocation-message.js +11 -4
  231. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  232. package/dist/modes/interactive/components/status-indicator.d.ts +2 -2
  233. package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -1
  234. package/dist/modes/interactive/components/status-indicator.js +7 -7
  235. package/dist/modes/interactive/components/status-indicator.js.map +1 -1
  236. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  237. package/dist/modes/interactive/components/tool-execution.js +18 -9
  238. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  239. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  240. package/dist/modes/interactive/components/tree-selector.js +2 -0
  241. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  242. package/dist/modes/interactive/interactive-mode.d.ts +15 -2
  243. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  244. package/dist/modes/interactive/interactive-mode.js +235 -86
  245. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  246. package/dist/modes/interactive/session-share.d.ts +2 -0
  247. package/dist/modes/interactive/session-share.d.ts.map +1 -1
  248. package/dist/modes/interactive/session-share.js +8 -4
  249. package/dist/modes/interactive/session-share.js.map +1 -1
  250. package/dist/modes/interactive/tui-renderer.d.ts.map +1 -1
  251. package/dist/modes/interactive/tui-renderer.js +2 -2
  252. package/dist/modes/interactive/tui-renderer.js.map +1 -1
  253. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  254. package/dist/modes/rpc/rpc-mode.js +2 -2
  255. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  256. package/dist/utils/clipboard-command.d.ts +7 -0
  257. package/dist/utils/clipboard-command.d.ts.map +1 -0
  258. package/dist/utils/clipboard-command.js +45 -0
  259. package/dist/utils/clipboard-command.js.map +1 -0
  260. package/dist/utils/clipboard-image.d.ts.map +1 -1
  261. package/dist/utils/clipboard-image.js +53 -81
  262. package/dist/utils/clipboard-image.js.map +1 -1
  263. package/dist/utils/clipboard.d.ts.map +1 -1
  264. package/dist/utils/clipboard.js +111 -121
  265. package/dist/utils/clipboard.js.map +1 -1
  266. package/dist/utils/wsl.d.ts +3 -0
  267. package/dist/utils/wsl.d.ts.map +1 -0
  268. package/dist/utils/wsl.js +15 -0
  269. package/dist/utils/wsl.js.map +1 -0
  270. package/dist/utils/zip.d.ts +7 -0
  271. package/dist/utils/zip.d.ts.map +1 -0
  272. package/dist/utils/zip.js +60 -0
  273. package/dist/utils/zip.js.map +1 -0
  274. package/docs/compaction.md +39 -13
  275. package/docs/custom-provider.md +22 -13
  276. package/docs/development.md +4 -4
  277. package/docs/docs.json +4 -0
  278. package/docs/environment-variables.md +1 -0
  279. package/docs/extensions.md +57 -43
  280. package/docs/json.md +4 -4
  281. package/docs/models.md +33 -2
  282. package/docs/providers.md +10 -2
  283. package/docs/sdk.md +4 -2
  284. package/docs/security.md +1 -1
  285. package/docs/session-format.md +82 -40
  286. package/docs/sessions.md +27 -0
  287. package/docs/settings.md +60 -1
  288. package/docs/termux.md +0 -1
  289. package/docs/tui.md +2 -2
  290. package/docs/usage.md +1 -0
  291. package/examples/extensions/README.md +0 -1
  292. package/examples/extensions/custom-provider-anthropic/index.ts +18 -12
  293. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  294. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  295. package/examples/extensions/custom-provider-gitlab-duo/index.ts +2 -2
  296. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  297. package/examples/extensions/gondolin/package-lock.json +2 -2
  298. package/examples/extensions/gondolin/package.json +1 -1
  299. package/examples/extensions/prompt-customizer.ts +19 -67
  300. package/examples/extensions/sandbox/package-lock.json +2 -2
  301. package/examples/extensions/sandbox/package.json +1 -1
  302. package/examples/extensions/with-deps/package-lock.json +2 -2
  303. package/examples/extensions/with-deps/package.json +1 -1
  304. package/package.json +17 -23
  305. package/dist/bundle/chunks/anthropic-messages-VWZZOSJQ.js +0 -6
  306. package/dist/bundle/chunks/azure-openai-responses-B467Y57F.js +0 -2
  307. package/dist/bundle/chunks/chunk-6WPJTKUF.js +0 -11
  308. package/dist/bundle/chunks/chunk-AXIIZGTV.js +0 -2
  309. package/dist/bundle/chunks/chunk-CLNPYIDP.js +0 -42
  310. package/dist/bundle/chunks/chunk-GMWCTUPB.js +0 -2
  311. package/dist/bundle/chunks/chunk-ORJJLIYM.js +0 -1648
  312. package/dist/bundle/chunks/chunk-U6ZMQKGD.js +0 -50
  313. package/dist/bundle/chunks/chunk-UAQELI3K.js +0 -2
  314. package/dist/bundle/chunks/google-generative-ai-Q2K7FBDI.js +0 -2
  315. package/dist/bundle/chunks/google-vertex-7UJBF4S7.js +0 -2
  316. package/dist/bundle/chunks/mistral-conversations-4M6BZBX6.js +0 -5
  317. package/dist/bundle/chunks/openai-codex-responses-MERYAUSE.js +0 -10
  318. package/dist/bundle/chunks/openai-completions-EKZT2IH2.js +0 -7
  319. package/dist/bundle/chunks/openai-responses-YZ6M7S4X.js +0 -2
  320. package/dist/utils/clipboard-native.d.ts +0 -11
  321. package/dist/utils/clipboard-native.d.ts.map +0 -1
  322. package/dist/utils/clipboard-native.js +0 -20
  323. package/dist/utils/clipboard-native.js.map +0 -1
  324. package/examples/extensions/kimi-deferred-tools.ts +0 -61
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
16
16
  import { basename, dirname } from "node:path";
17
- import { contentText } from "@earendil-works/pi-ai";
17
+ import { contentText, getCurrentSystemMessage, normalizeContext, retryDelayMs } from "@earendil-works/pi-ai";
18
18
  import { clampThinkingLevel, cleanupSessionResources, getSupportedThinkingLevels, isContextOverflow, isRecoverableLength, isRetryableAssistantError, modelsAreEqual, resetApiProviders, streamSimple, } from "@earendil-works/pi-ai/compat";
19
19
  import { getThemeByName, theme } from "../modes/interactive/theme/theme.js";
20
20
  import { stripFrontmatter } from "../utils/frontmatter.js";
@@ -23,6 +23,7 @@ import { sleep } from "../utils/sleep.js";
23
23
  import { normalizeToolResultImages } from "../utils/tool-result-images.js";
24
24
  import { formatNoApiKeyFoundMessage, formatNoModelSelectedMessage } from "./auth-guidance.js";
25
25
  import { executeBashWithOperations } from "./bash-executor.js";
26
+ import { generateBugReportSummary } from "./bug-report.js";
26
27
  import { calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, estimateTokens, generateBranchSummary, prepareCompaction, shouldCompact, } from "./compaction/index.js";
27
28
  import { DEFAULT_THINKING_LEVEL, THINKING_LEVEL_OPTIONS } from "./defaults.js";
28
29
  import { exportSessionToHtml } from "./export-html/index.js";
@@ -34,7 +35,7 @@ import { cancelPiServerOperations, compactPiServer, } from "./pi-server-client.j
34
35
  import { expandPromptTemplate } from "./prompt-templates.js";
35
36
  import { CURRENT_SESSION_VERSION, getLatestCompactionEntry } from "./session-manager.js";
36
37
  import { createSyntheticSourceInfo } from "./source-info.js";
37
- import { buildSystemPrompt } from "./system-prompt.js";
38
+ import { buildSystemPrompt, buildSystemPromptSections, diffSystemPromptSections, normalizeBuildSystemPromptOptions, } from "./system-prompt.js";
38
39
  import { createLocalBashOperations } from "./tools/bash.js";
39
40
  import { createAllToolDefinitions } from "./tools/index.js";
40
41
  import { createToolDefinitionFromAgentTool } from "./tools/tool-definition-wrapper.js";
@@ -80,6 +81,26 @@ const AUTO_SESSION_NAME_PROMPT = "Name this coding session from the transcript b
80
81
  function isPiServerMode() {
81
82
  return process.env.PI_SERVER_MODE === "true";
82
83
  }
84
+ /** Project the coding-agent session tree into the legacy pi-server wire tree. */
85
+ function buildPiServerSessionTree(entries, leafId) {
86
+ const byId = new Map(entries.map((entry) => [entry.id, entry]));
87
+ const usageIds = new Set(entries.filter((entry) => entry.type === "usage").map((entry) => entry.id));
88
+ const resolveParentId = (parentId) => {
89
+ let current = parentId;
90
+ const visited = new Set();
91
+ while (current !== null && usageIds.has(current)) {
92
+ if (visited.has(current))
93
+ throw new Error(`Usage entry parent cycle at ${current}`);
94
+ visited.add(current);
95
+ current = byId.get(current)?.parentId ?? null;
96
+ }
97
+ return current;
98
+ };
99
+ const legacyEntries = entries
100
+ .filter((entry) => entry.type !== "usage")
101
+ .map((entry) => ({ ...entry, parentId: resolveParentId(entry.parentId) }));
102
+ return { entries: legacyEntries, leafId: resolveParentId(leafId) };
103
+ }
83
104
  function toProviderReasoning(thinkingLevel) {
84
105
  return thinkingLevel === "off" ? undefined : thinkingLevel;
85
106
  }
@@ -138,7 +159,6 @@ function sanitizeGeneratedSessionName(text) {
138
159
  return undefined;
139
160
  return title.length > AUTO_SESSION_NAME_MAX_CHARS ? title.slice(0, AUTO_SESSION_NAME_MAX_CHARS).trim() : title;
140
161
  }
141
- // ============================================================================
142
162
  // AgentSession Class
143
163
  // ============================================================================
144
164
  export class AgentSession {
@@ -150,6 +170,7 @@ export class AgentSession {
150
170
  _unsubscribeAgent;
151
171
  _eventListeners = [];
152
172
  _isAgentRunActive = false;
173
+ _agentRunAbortRequested = false;
153
174
  _idleWaitPromise;
154
175
  _resolveIdleWait;
155
176
  _abortPromise;
@@ -207,15 +228,15 @@ export class AgentSession {
207
228
  _autoSessionNameEnabled;
208
229
  _autoSessionNameAttempted = false;
209
230
  _modelRuntime;
231
+ _cacheWarmer;
210
232
  // Tool registry for extension getTools/setTools
211
233
  _toolRegistry = new Map();
212
234
  _toolDefinitions = new Map();
213
235
  _toolPromptSnippets = new Map();
214
236
  _toolPromptGuidelines = new Map();
215
- // Base system prompt (without extension appends) - used to apply fresh appends each turn
216
- _baseSystemPrompt = "";
217
237
  _baseSystemPromptOptions;
218
- _systemPromptOverride;
238
+ /** Prompt options after before_agent_start mutations for the active run. */
239
+ _runSystemPromptOptions;
219
240
  constructor(config) {
220
241
  this.agent = config.agent;
221
242
  this.sessionManager = config.sessionManager;
@@ -225,6 +246,10 @@ export class AgentSession {
225
246
  this._customTools = config.customTools ?? [];
226
247
  this._cwd = config.cwd;
227
248
  this._modelRuntime = config.modelRuntime;
249
+ this._cacheWarmer = config.cacheWarmer;
250
+ if (this._cacheWarmer) {
251
+ this._cacheWarmer.onWarmed = (entry) => this._emit({ type: "entry_appended", entry });
252
+ }
228
253
  this._extensionRunnerRef = config.extensionRunnerRef;
229
254
  this._initialActiveToolNames = config.initialActiveToolNames;
230
255
  this._allowedToolNames = config.allowedToolNames ? new Set(config.allowedToolNames) : undefined;
@@ -240,10 +265,13 @@ export class AgentSession {
240
265
  this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);
241
266
  this._installAgentToolHooks();
242
267
  this._installAgentNextTurnRefresh();
268
+ this._installAgentForcedPromptProjection();
243
269
  this._buildRuntime({
244
270
  activeToolNames: this._initialActiveToolNames,
245
271
  includeAllExtensionTools: true,
246
272
  });
273
+ if (this._initialActiveToolNames === undefined)
274
+ this._restoreToolsFromTranscript();
247
275
  }
248
276
  get modelRuntime() {
249
277
  return this._modelRuntime;
@@ -258,10 +286,10 @@ export class AgentSession {
258
286
  messageCount: snapshot.messages.length,
259
287
  });
260
288
  }
261
- async _getRequiredRequestAuth(model) {
289
+ async _getRequiredRequestAuth(model, signal) {
262
290
  let result;
263
291
  try {
264
- result = await this._modelRuntime.getAuth(model);
292
+ result = await this._modelRuntime.getAuth(model, { signal });
265
293
  }
266
294
  catch (error) {
267
295
  const cause = error instanceof Error ? error.cause : undefined;
@@ -287,12 +315,12 @@ export class AgentSession {
287
315
  }
288
316
  throw new Error(formatNoApiKeyFoundMessage(model.provider));
289
317
  }
290
- async _getSummarizationRequestAuth(model) {
318
+ async _getSummarizationRequestAuth(model, signal) {
291
319
  if (this.agent.streamFunction === streamSimple) {
292
- return this._getRequiredRequestAuth(model);
320
+ return this._getRequiredRequestAuth(model, signal);
293
321
  }
294
322
  try {
295
- const result = await this._modelRuntime.getAuth(model);
323
+ const result = await this._modelRuntime.getAuth(model, { signal });
296
324
  if (!result)
297
325
  return { model };
298
326
  const requestModel = result.auth.baseUrl ? { ...model, baseUrl: result.auth.baseUrl } : model;
@@ -303,7 +331,9 @@ export class AgentSession {
303
331
  env: result.env,
304
332
  };
305
333
  }
306
- catch {
334
+ catch (error) {
335
+ if (signal?.aborted)
336
+ throw error;
307
337
  return { model };
308
338
  }
309
339
  }
@@ -375,14 +405,26 @@ export class AgentSession {
375
405
  const compactedContext = await this._compactBeforeNextProviderRequest(turn, signal);
376
406
  const nextTurn = compactedContext ? { ...turn, context: compactedContext } : turn;
377
407
  const previousSnapshot = await previousPrepareNextTurnWithContext?.(nextTurn, signal);
378
- const previousContext = previousSnapshot?.context ?? nextTurn.context;
408
+ const nextContext = previousSnapshot?.context ?? nextTurn.context;
409
+ const runOptions = this._runSystemPromptOptions ?? this._baseSystemPromptOptions;
410
+ const options = normalizeBuildSystemPromptOptions({
411
+ ...runOptions,
412
+ selectedTools: this.getActiveToolNames(),
413
+ toolSnippets: { ...this._baseSystemPromptOptions.toolSnippets, ...runOptions.toolSnippets },
414
+ toolGuidelines: { ...this._baseSystemPromptOptions.toolGuidelines, ...runOptions.toolGuidelines },
415
+ });
416
+ const updateMessage = this._preparePromptAndToolLoadout(options, nextContext.messages);
417
+ // Keep session.systemPrompt and ctx.getSystemPrompt() in step with what the provider sees.
418
+ this._runSystemPromptOptions = options;
379
419
  return {
380
420
  ...previousSnapshot,
381
421
  context: {
382
- ...previousContext,
383
- systemPrompt: this._systemPromptOverride ?? this._baseSystemPrompt,
422
+ ...nextContext,
384
423
  tools: this.agent.state.tools.slice(),
385
424
  },
425
+ messages: updateMessage
426
+ ? [...(previousSnapshot?.messages ?? []), updateMessage]
427
+ : previousSnapshot?.messages,
386
428
  model: this.agent.state.model,
387
429
  thinkingLevel: this.agent.state.thinkingLevel,
388
430
  };
@@ -427,6 +469,7 @@ export class AgentSession {
427
469
  resolve();
428
470
  }
429
471
  async _emitAgentSettled() {
472
+ this._cacheWarmer?.onAgentSettled();
430
473
  this._isAgentRunActive = false;
431
474
  try {
432
475
  await this._extensionRunner.emit({ type: "agent_settled" });
@@ -473,7 +516,8 @@ export class AgentSession {
473
516
  // Persist as CustomMessageEntry
474
517
  this.sessionManager.appendCustomMessageEntry(event.message.customType, event.message.content, event.message.display, event.message.details);
475
518
  }
476
- else if (event.message.role === "user" ||
519
+ else if (event.message.role === "system" ||
520
+ event.message.role === "user" ||
477
521
  event.message.role === "assistant" ||
478
522
  event.message.role === "toolResult") {
479
523
  // Regular LLM message - persist as SessionMessageEntry
@@ -509,6 +553,8 @@ export class AgentSession {
509
553
  }
510
554
  };
511
555
  _willRetryAfterAgentEnd(event) {
556
+ if (this._agentRunAbortRequested)
557
+ return false;
512
558
  const settings = this.settingsManager.getRetrySettings();
513
559
  if (!settings.enabled || this._retryAttempt >= settings.maxRetries) {
514
560
  return false;
@@ -733,6 +779,10 @@ export class AgentSession {
733
779
  this._extensionRunner.invalidate("This extension ctx is stale after session replacement or reload. Do not use a captured pi or command ctx after ctx.newSession(), ctx.fork(), ctx.switchSession(), or ctx.reload(). For newSession, fork, and switchSession, move post-replacement work into withSession and use the ctx passed to withSession. For reload, do not use the old ctx after await ctx.reload().");
734
780
  this._disconnectFromAgent();
735
781
  this._eventListeners = [];
782
+ if (this._cacheWarmer) {
783
+ this._cacheWarmer.onWarmed = undefined;
784
+ this._cacheWarmer.cancel();
785
+ }
736
786
  cleanupSessionResources(this.sessionId);
737
787
  }
738
788
  // =========================================================================
@@ -742,6 +792,15 @@ export class AgentSession {
742
792
  get state() {
743
793
  return this.agent.state;
744
794
  }
795
+ /** Current cache-warming state and the policy inputs that produced it. */
796
+ get cacheWarmingStatus() {
797
+ return this._cacheWarmer?.status;
798
+ }
799
+ /** Persist the cache-warming mode and immediately reconcile active warming. */
800
+ setCacheWarmingMode(mode) {
801
+ this.settingsManager.setCacheWarmingMode(mode);
802
+ this._cacheWarmer?.onModeChanged();
803
+ }
745
804
  /** Current model (may be undefined if not yet selected) */
746
805
  get model() {
747
806
  return this.agent.state.model;
@@ -766,9 +825,9 @@ export class AgentSession {
766
825
  get isIdle() {
767
826
  return !this._isAgentRunActive && !this.isCompacting;
768
827
  }
769
- /** Current effective system prompt (includes any per-turn extension modifications) */
828
+ /** Current effective system prompt, including changes not yet sent to the model. */
770
829
  get systemPrompt() {
771
- return this.agent.state.systemPrompt;
830
+ return buildSystemPrompt(this._runSystemPromptOptions ?? this._baseSystemPromptOptions);
772
831
  }
773
832
  /** Current retry attempt (0 if not retrying) */
774
833
  get retryAttempt() {
@@ -813,9 +872,7 @@ export class AgentSession {
813
872
  }
814
873
  }
815
874
  this.agent.state.tools = tools;
816
- // Rebuild base system prompt with new tool set
817
- this._baseSystemPrompt = this._rebuildSystemPrompt(validToolNames);
818
- this.agent.state.systemPrompt = this._systemPromptOverride ?? this._baseSystemPrompt;
875
+ this._rebuildSystemPrompt(validToolNames);
819
876
  }
820
877
  /** Whether compaction or branch summarization is currently running */
821
878
  get isCompacting() {
@@ -884,23 +941,17 @@ export class AgentSession {
884
941
  _rebuildSystemPrompt(toolNames) {
885
942
  const validToolNames = toolNames.filter((name) => this._toolRegistry.has(name));
886
943
  const toolSnippets = {};
887
- const promptGuidelines = [];
888
- for (const name of validToolNames) {
944
+ for (const name of this._toolRegistry.keys()) {
889
945
  const snippet = this._toolPromptSnippets.get(name);
890
- if (snippet) {
946
+ if (snippet)
891
947
  toolSnippets[name] = snippet;
892
- }
893
- const toolGuidelines = this._toolPromptGuidelines.get(name);
894
- if (toolGuidelines) {
895
- promptGuidelines.push(...toolGuidelines);
896
- }
897
948
  }
898
949
  const loaderSystemPrompt = this._resourceLoader.getSystemPrompt();
899
950
  const loaderAppendSystemPrompt = this._resourceLoader.getAppendSystemPrompt();
900
- const appendSystemPrompt = loaderAppendSystemPrompt.length > 0 ? loaderAppendSystemPrompt.join("\n\n") : undefined;
951
+ const appendSystemPrompt = loaderAppendSystemPrompt.length > 0 ? loaderAppendSystemPrompt.join("\n\n") : "";
901
952
  const loadedSkills = this._resourceLoader.getSkills().skills;
902
953
  const loadedContextFiles = this._resourceLoader.getAgentsFiles().agentsFiles;
903
- this._baseSystemPromptOptions = {
954
+ this._baseSystemPromptOptions = normalizeBuildSystemPromptOptions({
904
955
  cwd: this._cwd,
905
956
  skills: loadedSkills,
906
957
  contextFiles: loadedContextFiles,
@@ -908,21 +959,81 @@ export class AgentSession {
908
959
  appendSystemPrompt,
909
960
  selectedTools: validToolNames,
910
961
  toolSnippets,
911
- promptGuidelines,
962
+ toolGuidelines: Object.fromEntries(this._toolPromptGuidelines),
963
+ });
964
+ }
965
+ /**
966
+ * Apply a prompt and tool loadout for the next request. Sets the executable tools and
967
+ * returns a system message patching the prompt sections the model currently has (replayed
968
+ * from `messages`), or undefined when the prompt is unchanged. Tool changes are declared by
969
+ * the agent loop before the request.
970
+ *
971
+ * A forced prompt does not affect the transcript: the structured sections are still diffed
972
+ * and persisted, and the forced text is projected onto the request by
973
+ * {@link _installAgentForcedPromptProjection}.
974
+ */
975
+ _preparePromptAndToolLoadout(options, messages = this.agent.state.messages) {
976
+ options.selectedTools = [...new Set(options.selectedTools)].filter((name) => this._toolRegistry.has(name));
977
+ this.agent.state.tools = options.selectedTools.flatMap((name) => {
978
+ const tool = this._toolRegistry.get(name);
979
+ return tool ? [tool] : [];
980
+ });
981
+ const sections = diffSystemPromptSections(getCurrentSystemMessage(messages)?.sections ?? {}, buildSystemPromptSections(options));
982
+ return sections ? { role: "system", content: "", sections, timestamp: Date.now() } : undefined;
983
+ }
984
+ /**
985
+ * Send a forced prompt as the provider's leading system prompt without recording it.
986
+ *
987
+ * A `before_agent_start` handler that returns `systemPrompt` needs that exact text at the
988
+ * head of the request; a mid-conversation system message would leave the original prompt
989
+ * in place. The forced text is a rendering of the current prompt, so the transcript keeps
990
+ * its structured sections and the request is projected instead: the system messages
991
+ * collapse into one head holding the forced text and the current tools. Runs after the
992
+ * `context` extension handlers.
993
+ */
994
+ _installAgentForcedPromptProjection() {
995
+ const previousTransformContext = this.agent.transformContext;
996
+ this.agent.transformContext = async (messages, signal) => {
997
+ const transformed = previousTransformContext ? await previousTransformContext(messages, signal) : messages;
998
+ const forced = this._runSystemPromptOptions?.forceSystemPrompt;
999
+ if (forced === undefined)
1000
+ return transformed;
1001
+ const current = getCurrentSystemMessage(transformed);
1002
+ const head = {
1003
+ role: "system",
1004
+ content: forced,
1005
+ ...(current?.toolsAdded ? { toolsAdded: current.toolsAdded } : {}),
1006
+ timestamp: current?.timestamp ?? Date.now(),
1007
+ };
1008
+ return [head, ...transformed.filter((message) => message.role !== "system")];
912
1009
  };
913
- return buildSystemPrompt(this._baseSystemPromptOptions);
1010
+ }
1011
+ /** Restore the active tool loadout declared by the session transcript, if it declares one. */
1012
+ _restoreToolsFromTranscript() {
1013
+ const current = getCurrentSystemMessage(this.sessionManager.buildSessionContext().messages);
1014
+ if (!current)
1015
+ return;
1016
+ const toolNames = (current.toolsAdded ?? [])
1017
+ .map((tool) => tool.name)
1018
+ .filter((name) => this._toolRegistry.has(name));
1019
+ this.agent.state.tools = toolNames.flatMap((name) => {
1020
+ const registered = this._toolRegistry.get(name);
1021
+ return registered ? [registered] : [];
1022
+ });
1023
+ this._rebuildSystemPrompt(toolNames);
914
1024
  }
915
1025
  // =========================================================================
916
1026
  // Prompting
917
1027
  // =========================================================================
918
1028
  async _runAgentOperation(start) {
1029
+ this._agentRunAbortRequested = false;
919
1030
  this._isAgentRunActive = true;
920
1031
  try {
921
1032
  await start();
922
1033
  while (await this._handlePostAgentRun()) {
923
1034
  // Esc cancels the current agent run. Keep messages queued during that
924
1035
  // cancellation for the single deferred continuation in abort().
925
- if (this._isAborting) {
1036
+ if (this._isAborting || this._agentRunAbortRequested) {
926
1037
  break;
927
1038
  }
928
1039
  try {
@@ -942,12 +1053,14 @@ export class AgentSession {
942
1053
  throw error;
943
1054
  }
944
1055
  }
945
- if (!this._isAborting) {
1056
+ if (!this._isAborting && !this._agentRunAbortRequested) {
946
1057
  await this._maybeGenerateSessionName();
947
1058
  }
948
1059
  }
949
1060
  finally {
950
- this._systemPromptOverride = undefined;
1061
+ if (this._agentRunAbortRequested)
1062
+ this._finishCancelledRetry();
1063
+ this._runSystemPromptOptions = undefined;
951
1064
  this._flushPendingBashMessages();
952
1065
  this._flushPendingCustomMessages();
953
1066
  await this._emitAgentSettled();
@@ -991,7 +1104,7 @@ export class AgentSession {
991
1104
  return;
992
1105
  this._sessionNameAbortController = new AbortController();
993
1106
  try {
994
- const stream = await this.agent.streamFunction(model, {
1107
+ const stream = await this.agent.streamFunction(model, normalizeContext({
995
1108
  systemPrompt: this.systemPrompt,
996
1109
  messages: [
997
1110
  {
@@ -1001,7 +1114,7 @@ export class AgentSession {
1001
1114
  },
1002
1115
  ],
1003
1116
  tools: this.agent.state.tools,
1004
- }, {
1117
+ }), {
1005
1118
  maxTokens: 32,
1006
1119
  reasoning: undefined,
1007
1120
  sessionId: this.sessionId,
@@ -1030,6 +1143,10 @@ export class AgentSession {
1030
1143
  async _handlePostAgentRun() {
1031
1144
  const msg = this._lastAssistantMessage;
1032
1145
  this._lastAssistantMessage = undefined;
1146
+ if (this._agentRunAbortRequested) {
1147
+ this._finishCancelledRetry();
1148
+ return false;
1149
+ }
1033
1150
  if (!msg) {
1034
1151
  return false;
1035
1152
  }
@@ -1040,7 +1157,13 @@ export class AgentSession {
1040
1157
  return false;
1041
1158
  }
1042
1159
  if (this._isRetryableError(msg) && (await this._prepareRetry(msg))) {
1043
- return true;
1160
+ if (this._agentRunAbortRequested)
1161
+ this._finishCancelledRetry();
1162
+ return !this._agentRunAbortRequested;
1163
+ }
1164
+ if (this._agentRunAbortRequested) {
1165
+ this._finishCancelledRetry();
1166
+ return false;
1044
1167
  }
1045
1168
  if (this._isAborting) {
1046
1169
  return false;
@@ -1055,14 +1178,27 @@ export class AgentSession {
1055
1178
  this._retryAttempt = 0;
1056
1179
  }
1057
1180
  if (await this._checkCompaction(msg)) {
1058
- return true;
1181
+ return !this._agentRunAbortRequested;
1059
1182
  }
1060
1183
  if (isPiServerMode() && this._isTerminalAssistantFailure(msg)) {
1061
1184
  this._detachPiServerTerminalAssistantFailure();
1062
1185
  }
1063
1186
  // The agent loop drains both queues before emitting agent_end. Any messages
1064
1187
  // here were queued by agent_end extension handlers and need a continuation.
1065
- return this.agent.hasQueuedMessages();
1188
+ return !this._agentRunAbortRequested && this.agent.hasQueuedMessages();
1189
+ }
1190
+ async _runInputHandlers(text, images, source, streamingBehavior) {
1191
+ if (!this._extensionRunner.hasHandlers("input")) {
1192
+ return { text, images };
1193
+ }
1194
+ const inputResult = await this._extensionRunner.emitInput(text, images, source, streamingBehavior);
1195
+ if (inputResult.action === "handled") {
1196
+ return undefined;
1197
+ }
1198
+ if (inputResult.action === "transform") {
1199
+ return { text: inputResult.text, images: inputResult.images ?? images };
1200
+ }
1201
+ return { text, images };
1066
1202
  }
1067
1203
  /**
1068
1204
  * Send a prompt to the agent.
@@ -1092,19 +1228,12 @@ export class AgentSession {
1092
1228
  throw new Error("Cannot submit a prompt while compaction is in progress. Wait for compaction to finish and retry.");
1093
1229
  }
1094
1230
  // Emit input event for extension interception (before skill/template expansion)
1095
- let currentText = text;
1096
- let currentImages = options?.images;
1097
- if (this._extensionRunner.hasHandlers("input")) {
1098
- const inputResult = await this._extensionRunner.emitInput(currentText, currentImages, options?.source ?? "interactive", this.isStreaming ? options?.streamingBehavior : undefined);
1099
- if (inputResult.action === "handled") {
1100
- preflightResult?.(true);
1101
- return;
1102
- }
1103
- if (inputResult.action === "transform") {
1104
- currentText = inputResult.text;
1105
- currentImages = inputResult.images ?? currentImages;
1106
- }
1231
+ const processedInput = await this._runInputHandlers(text, options?.images, options?.source ?? "interactive", this.isStreaming ? options?.streamingBehavior : undefined);
1232
+ if (!processedInput) {
1233
+ preflightResult?.(true);
1234
+ return;
1107
1235
  }
1236
+ const { text: currentText, images: currentImages } = processedInput;
1108
1237
  // Expand skill commands (/skill:name args) and prompt templates (/template args)
1109
1238
  let expandedText = currentText;
1110
1239
  if (expandPromptTemplates) {
@@ -1196,31 +1325,30 @@ export class AgentSession {
1196
1325
  }
1197
1326
  this._pendingNextTurnMessages = [];
1198
1327
  // Emit before_agent_start extension event
1199
- const result = await this._extensionRunner.emitBeforeAgentStart(expandedText, currentImages, this._baseSystemPrompt, this._baseSystemPromptOptions);
1200
- // Add all custom messages from extensions
1201
- if (result?.messages) {
1202
- for (const msg of result.messages) {
1203
- messages.push({
1204
- role: "custom",
1205
- customType: msg.customType,
1206
- // Untyped extensions can pass null/missing content; normalize at ingestion.
1207
- content: msg.content ?? [],
1208
- display: msg.display,
1209
- details: msg.details,
1210
- timestamp: Date.now(),
1211
- });
1212
- }
1213
- }
1214
- // Apply extension-modified system prompt, or reset to base
1215
- if (result?.systemPrompt !== undefined) {
1216
- this._systemPromptOverride = result.systemPrompt;
1217
- this.agent.state.systemPrompt = result.systemPrompt;
1218
- }
1219
- else {
1220
- // Ensure we're using the base prompt (in case previous turn had modifications)
1221
- this._systemPromptOverride = undefined;
1222
- this.agent.state.systemPrompt = this._baseSystemPrompt;
1328
+ const selectedToolsBefore = this._baseSystemPromptOptions.selectedTools;
1329
+ const result = await this._extensionRunner.emitBeforeAgentStart(expandedText, currentImages, this._baseSystemPromptOptions);
1330
+ // Handlers may edit event.systemPromptOptions.selectedTools or call setActiveTools(),
1331
+ // which updates the live loadout instead. An explicit edit wins; otherwise the live
1332
+ // loadout is authoritative, so a setActiveTools() call is not undone here.
1333
+ const handlerEditedTools = result.systemPromptOptions.selectedTools.length !== selectedToolsBefore.length ||
1334
+ result.systemPromptOptions.selectedTools.some((name, index) => name !== selectedToolsBefore[index]);
1335
+ if (!handlerEditedTools)
1336
+ result.systemPromptOptions.selectedTools = this.getActiveToolNames();
1337
+ for (const msg of result.messages) {
1338
+ messages.push({
1339
+ role: "custom",
1340
+ customType: msg.customType,
1341
+ // Untyped extensions can pass null/missing content; normalize at ingestion.
1342
+ content: msg.content ?? [],
1343
+ display: msg.display,
1344
+ details: msg.details,
1345
+ timestamp: Date.now(),
1346
+ });
1223
1347
  }
1348
+ const updateMessage = this._preparePromptAndToolLoadout(result.systemPromptOptions);
1349
+ this._runSystemPromptOptions = result.systemPromptOptions;
1350
+ if (updateMessage)
1351
+ messages.unshift(updateMessage);
1224
1352
  }
1225
1353
  catch (error) {
1226
1354
  preflightResult?.(false);
@@ -1289,40 +1417,44 @@ export class AgentSession {
1289
1417
  return text; // Return original on error
1290
1418
  }
1291
1419
  }
1420
+ async _queueUserInput(text, images, behavior, source) {
1421
+ if (text.startsWith("/")) {
1422
+ this._throwIfExtensionCommand(text);
1423
+ }
1424
+ const processedInput = await this._runInputHandlers(text, images, source, this.isStreaming ? behavior : undefined);
1425
+ if (!processedInput)
1426
+ return;
1427
+ let expandedText = this._expandSkillCommand(processedInput.text);
1428
+ expandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);
1429
+ if (behavior === "steer") {
1430
+ await this._queueSteer(expandedText, processedInput.images);
1431
+ }
1432
+ else {
1433
+ await this._queueFollowUp(expandedText, processedInput.images);
1434
+ }
1435
+ }
1292
1436
  /**
1293
1437
  * Queue a steering message while the agent is running.
1294
1438
  * Delivered after the current assistant turn finishes executing its tool calls,
1295
1439
  * before the next LLM call.
1296
1440
  * Expands skill commands and prompt templates. Errors on extension commands.
1297
1441
  * @param images Optional image attachments to include with the message
1442
+ * @param options Input source; defaults to interactive
1298
1443
  * @throws Error if text is an extension command
1299
1444
  */
1300
- async steer(text, images) {
1301
- // Check for extension commands (cannot be queued)
1302
- if (text.startsWith("/")) {
1303
- this._throwIfExtensionCommand(text);
1304
- }
1305
- // Expand skill commands and prompt templates
1306
- let expandedText = this._expandSkillCommand(text);
1307
- expandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);
1308
- await this._queueSteer(expandedText, images);
1445
+ async steer(text, images, options) {
1446
+ await this._queueUserInput(text, images, "steer", options?.source ?? "interactive");
1309
1447
  }
1310
1448
  /**
1311
1449
  * Queue a follow-up message to be processed after the agent finishes.
1312
1450
  * Delivered only when agent has no more tool calls or steering messages.
1313
1451
  * Expands skill commands and prompt templates. Errors on extension commands.
1314
1452
  * @param images Optional image attachments to include with the message
1453
+ * @param options Input source; defaults to interactive
1315
1454
  * @throws Error if text is an extension command
1316
1455
  */
1317
- async followUp(text, images) {
1318
- // Check for extension commands (cannot be queued)
1319
- if (text.startsWith("/")) {
1320
- this._throwIfExtensionCommand(text);
1321
- }
1322
- // Expand skill commands and prompt templates
1323
- let expandedText = this._expandSkillCommand(text);
1324
- expandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);
1325
- await this._queueFollowUp(expandedText, images);
1456
+ async followUp(text, images, options) {
1457
+ await this._queueUserInput(text, images, "followUp", options?.source ?? "interactive");
1326
1458
  }
1327
1459
  /**
1328
1460
  * Internal: Queue a steering message (already expanded, no extension command check).
@@ -1525,6 +1657,9 @@ export class AgentSession {
1525
1657
  });
1526
1658
  }
1527
1659
  async _performAbort() {
1660
+ if (this._isAgentRunActive) {
1661
+ this._agentRunAbortRequested = true;
1662
+ }
1528
1663
  this.abortRetry();
1529
1664
  this.abortCompaction();
1530
1665
  this.abortBranchSummary();
@@ -1550,6 +1685,11 @@ export class AgentSession {
1550
1685
  if (cleanupFailure?.status === "rejected") {
1551
1686
  throw cleanupFailure.reason;
1552
1687
  }
1688
+ if (isPiServerMode()) {
1689
+ // The provider may persist a terminal assistant after agent.abort() without
1690
+ // routing through _handlePostAgentRun; keep the durable leaf resumable.
1691
+ this._detachPiServerTerminalAssistantFailure();
1692
+ }
1553
1693
  }
1554
1694
  catch (error) {
1555
1695
  this._abortError = error instanceof Error ? error.message : String(error);
@@ -1572,6 +1712,9 @@ export class AgentSession {
1572
1712
  * Messages queued while cancellation is in progress are sent in one deferred continuation.
1573
1713
  */
1574
1714
  abort() {
1715
+ if (this._isAgentRunActive) {
1716
+ this._agentRunAbortRequested = true;
1717
+ }
1575
1718
  if (this._abortPromise) {
1576
1719
  return this._abortPromise;
1577
1720
  }
@@ -1851,6 +1994,8 @@ export class AgentSession {
1851
1994
  async compact(customInstructions) {
1852
1995
  await this.abort();
1853
1996
  this._compactionAbortController = new AbortController();
1997
+ let fromExtension = false;
1998
+ let cancelledByExtension = false;
1854
1999
  let resolveCompactionCompletion;
1855
2000
  const compactionCompletion = new Promise((resolve) => {
1856
2001
  resolveCompactionCompletion = resolve;
@@ -1858,10 +2003,12 @@ export class AgentSession {
1858
2003
  this._compactionCompletionPromise = compactionCompletion;
1859
2004
  try {
1860
2005
  this._emit({ type: "compaction_start", reason: "manual" });
1861
- if (!this.model) {
2006
+ const model = this.model;
2007
+ if (!model) {
1862
2008
  throw new Error(formatNoModelSelectedMessage());
1863
2009
  }
1864
- const { model: requestModel, apiKey, headers, env } = await this._getSummarizationRequestAuth(this.model);
2010
+ const settings = this.settingsManager.getCompactionSettings(model);
2011
+ const { model: requestModel, apiKey, headers, env, } = await this._getSummarizationRequestAuth(model, this._compactionAbortController.signal);
1865
2012
  if (isPiServerMode()) {
1866
2013
  this._detachPiServerTerminalAssistantFailure();
1867
2014
  const piServerResult = await compactPiServer(requestModel, {
@@ -1874,7 +2021,7 @@ export class AgentSession {
1874
2021
  headers,
1875
2022
  customInstructions,
1876
2023
  settings: this.settingsManager.getCompactionSettings(),
1877
- sessionTree: { entries: this.sessionManager.getEntries(), leafId: this.sessionManager.getLeafId() },
2024
+ sessionTree: buildPiServerSessionTree(this.sessionManager.getEntries(), this.sessionManager.getLeafId()),
1878
2025
  signal: this._compactionAbortController.signal,
1879
2026
  reasoning: toProviderReasoning(this.thinkingLevel),
1880
2027
  onHistoryReconciled: (snapshot) => this.reconcilePiServerHistory(snapshot),
@@ -1891,7 +2038,6 @@ export class AgentSession {
1891
2038
  return result;
1892
2039
  }
1893
2040
  const pathEntries = this.sessionManager.getBranch();
1894
- const settings = this.settingsManager.getCompactionSettings();
1895
2041
  const preparation = prepareCompaction(pathEntries, settings);
1896
2042
  if (!preparation) {
1897
2043
  // Check why we can't compact
@@ -1902,7 +2048,6 @@ export class AgentSession {
1902
2048
  throw new Error("Nothing to compact (session too small)");
1903
2049
  }
1904
2050
  let extensionCompaction;
1905
- let fromExtension = false;
1906
2051
  if (this._extensionRunner.hasHandlers("session_before_compact")) {
1907
2052
  const result = (await this._extensionRunner.emit({
1908
2053
  type: "session_before_compact",
@@ -1914,6 +2059,7 @@ export class AgentSession {
1914
2059
  signal: this._compactionAbortController.signal,
1915
2060
  }));
1916
2061
  if (result?.cancel) {
2062
+ cancelledByExtension = true;
1917
2063
  throw new Error("Compaction cancelled");
1918
2064
  }
1919
2065
  if (result?.compaction) {
@@ -1983,8 +2129,10 @@ export class AgentSession {
1983
2129
  }
1984
2130
  catch (error) {
1985
2131
  const message = error instanceof Error ? error.message : String(error);
1986
- const aborted = message === "Compaction cancelled" || (error instanceof Error && error.name === "AbortError");
1987
- this._compactionAbortController = undefined;
2132
+ const aborted = this._compactionAbortController?.signal.aborted === true ||
2133
+ cancelledByExtension ||
2134
+ message === "Compaction cancelled" ||
2135
+ (error instanceof Error && error.name === "AbortError");
1988
2136
  const errorMessage = aborted ? undefined : `Compaction failed: ${message}`;
1989
2137
  this._clearManualCompactionState();
1990
2138
  this._emit({
@@ -1995,6 +2143,13 @@ export class AgentSession {
1995
2143
  willRetry: false,
1996
2144
  errorMessage,
1997
2145
  });
2146
+ await this._emitSessionCompactFailed({
2147
+ reason: "manual",
2148
+ errorMessage,
2149
+ aborted,
2150
+ willRetry: false,
2151
+ fromExtension,
2152
+ });
1998
2153
  throw error;
1999
2154
  }
2000
2155
  finally {
@@ -2034,7 +2189,7 @@ export class AgentSession {
2034
2189
  async _checkCompaction(assistantMessage, skipAbortedCheck = true, externalSignal) {
2035
2190
  if (externalSignal?.aborted)
2036
2191
  return false;
2037
- const settings = this.settingsManager.getCompactionSettings();
2192
+ const settings = this.settingsManager.getCompactionSettings(this.model);
2038
2193
  if (!settings.enabled)
2039
2194
  return false;
2040
2195
  // Skip if message was aborted (user cancelled) - unless skipAbortedCheck is false
@@ -2066,7 +2221,9 @@ export class AgentSession {
2066
2221
  if (contextOverflow || recoverableLength) {
2067
2222
  const willRetry = assistantMessage.stopReason !== "stop";
2068
2223
  if (!willRetry) {
2069
- return await this._runAutoCompaction("overflow", false, undefined, undefined, externalSignal);
2224
+ return externalSignal
2225
+ ? await this._runAutoCompaction("overflow", false, undefined, undefined, externalSignal)
2226
+ : await this._runAutoCompaction("overflow", false);
2070
2227
  }
2071
2228
  if (this._overflowRecoveryAttempted) {
2072
2229
  const errorMessage = contextOverflow
@@ -2125,7 +2282,9 @@ export class AgentSession {
2125
2282
  contextTokens = directContextTokens;
2126
2283
  }
2127
2284
  if (shouldCompact(contextTokens, contextWindow, settings)) {
2128
- return await this._runAutoCompaction("threshold", false, undefined, undefined, externalSignal);
2285
+ return externalSignal
2286
+ ? await this._runAutoCompaction("threshold", false, undefined, undefined, externalSignal)
2287
+ : await this._runAutoCompaction("threshold", false);
2129
2288
  }
2130
2289
  return false;
2131
2290
  }
@@ -2133,27 +2292,28 @@ export class AgentSession {
2133
2292
  * Internal: Run auto-compaction with events.
2134
2293
  */
2135
2294
  async _runAutoCompaction(reason, willRetry, preparationOverride, preparationOptions, externalSignal) {
2136
- const settings = this.settingsManager.getCompactionSettings();
2295
+ const model = this.model;
2296
+ const settings = this.settingsManager.getCompactionSettings(model);
2297
+ let abortController;
2137
2298
  let started = false;
2138
2299
  let fromExtension = false;
2300
+ let cancelledByExtension = false;
2139
2301
  if (externalSignal?.aborted)
2140
2302
  return false;
2141
- const abortExternal = () => this._autoCompactionAbortController?.abort();
2303
+ const abortExternal = () => abortController?.abort();
2142
2304
  externalSignal?.addEventListener("abort", abortExternal, { once: true });
2143
2305
  try {
2144
- if (!this.model) {
2306
+ if (!model) {
2145
2307
  return false;
2146
2308
  }
2147
- const { model: requestModel, apiKey, headers, env } = await this._getSummarizationRequestAuth(this.model);
2148
- if (externalSignal?.aborted)
2149
- return false;
2150
2309
  if (isPiServerMode()) {
2151
- this._emit({ type: "compaction_start", reason });
2152
- this._autoCompactionAbortController = new AbortController();
2153
- if (externalSignal?.aborted)
2154
- this._autoCompactionAbortController.abort();
2310
+ abortController = new AbortController();
2311
+ this._autoCompactionAbortController = abortController;
2155
2312
  started = true;
2156
- const signal = this._autoCompactionAbortController.signal;
2313
+ this._emit({ type: "compaction_start", reason });
2314
+ abortController.signal.throwIfAborted();
2315
+ const { model: requestModel, apiKey, headers, } = await this._getSummarizationRequestAuth(model, abortController.signal);
2316
+ abortController.signal.throwIfAborted();
2157
2317
  const piServerResult = await compactPiServer(requestModel, {
2158
2318
  systemPrompt: this.systemPrompt,
2159
2319
  messages: this.agent.state.messages,
@@ -2164,13 +2324,19 @@ export class AgentSession {
2164
2324
  headers,
2165
2325
  settings,
2166
2326
  preparation: preparationOptions,
2167
- sessionTree: { entries: this.sessionManager.getEntries(), leafId: this.sessionManager.getLeafId() },
2168
- signal,
2327
+ sessionTree: buildPiServerSessionTree(this.sessionManager.getEntries(), this.sessionManager.getLeafId()),
2328
+ signal: abortController.signal,
2169
2329
  reasoning: toProviderReasoning(this.thinkingLevel),
2170
2330
  onHistoryReconciled: (snapshot) => this.reconcilePiServerHistory(snapshot),
2171
2331
  });
2172
- if (externalSignal?.aborted || signal.aborted) {
2332
+ if (externalSignal?.aborted || abortController.signal.aborted) {
2173
2333
  this._emit({ type: "compaction_end", reason, result: undefined, aborted: true, willRetry: false });
2334
+ await this._emitSessionCompactFailed({
2335
+ reason,
2336
+ aborted: true,
2337
+ willRetry: false,
2338
+ fromExtension,
2339
+ });
2174
2340
  return false;
2175
2341
  }
2176
2342
  const result = await this._applyPiServerCompactionResult(piServerResult, reason, willRetry);
@@ -2179,17 +2345,17 @@ export class AgentSession {
2179
2345
  return willRetry ? true : this.agent.hasQueuedMessages();
2180
2346
  }
2181
2347
  const pathEntries = this.sessionManager.getBranch();
2182
- if (externalSignal?.aborted)
2183
- return false;
2184
2348
  const preparation = preparationOverride ?? prepareCompaction(pathEntries, settings);
2185
2349
  if (!preparation) {
2186
2350
  return false;
2187
2351
  }
2188
- this._emit({ type: "compaction_start", reason });
2189
- this._autoCompactionAbortController = new AbortController();
2190
- if (externalSignal?.aborted)
2191
- this._autoCompactionAbortController.abort();
2352
+ abortController = new AbortController();
2353
+ this._autoCompactionAbortController = abortController;
2192
2354
  started = true;
2355
+ this._emit({ type: "compaction_start", reason });
2356
+ abortController.signal.throwIfAborted();
2357
+ const { model: requestModel, apiKey, headers, env, } = await this._getSummarizationRequestAuth(model, abortController.signal);
2358
+ abortController.signal.throwIfAborted();
2193
2359
  let extensionCompaction;
2194
2360
  if (this._extensionRunner.hasHandlers("session_before_compact")) {
2195
2361
  const extensionResult = (await this._extensionRunner.emit({
@@ -2199,36 +2365,24 @@ export class AgentSession {
2199
2365
  customInstructions: undefined,
2200
2366
  reason,
2201
2367
  willRetry,
2202
- signal: this._autoCompactionAbortController.signal,
2368
+ signal: abortController.signal,
2203
2369
  }));
2204
2370
  if (extensionResult?.cancel) {
2205
- this._emit({
2206
- type: "compaction_end",
2207
- reason,
2208
- result: undefined,
2209
- aborted: true,
2210
- willRetry: false,
2211
- });
2212
- await this._emitSessionCompactFailed({
2213
- reason,
2214
- aborted: true,
2215
- willRetry: false,
2216
- fromExtension: false,
2217
- });
2218
- return false;
2371
+ cancelledByExtension = true;
2372
+ throw new Error("Compaction cancelled");
2219
2373
  }
2220
2374
  if (extensionResult?.compaction) {
2221
2375
  extensionCompaction = extensionResult.compaction;
2222
2376
  fromExtension = true;
2223
2377
  }
2224
2378
  }
2379
+ abortController.signal.throwIfAborted();
2225
2380
  let summary;
2226
2381
  let firstKeptEntryId;
2227
2382
  let tokensBefore;
2228
2383
  let usage;
2229
2384
  let details;
2230
2385
  if (extensionCompaction) {
2231
- // Extension provided compaction content
2232
2386
  summary = extensionCompaction.summary;
2233
2387
  firstKeptEntryId = extensionCompaction.firstKeptEntryId;
2234
2388
  tokensBefore = extensionCompaction.tokensBefore;
@@ -2236,22 +2390,15 @@ export class AgentSession {
2236
2390
  details = extensionCompaction.details;
2237
2391
  }
2238
2392
  else {
2239
- // Generate compaction result
2240
- const compactResult = await compact(preparation, requestModel, apiKey, headers, undefined, this._autoCompactionAbortController.signal, this.thinkingLevel, this.agent.streamFunction, env, this.settingsManager.getRetrySettings(), this._summarizationRetryCallbacks({ source: "compaction", reason }));
2393
+ const compactResult = await compact(preparation, requestModel, apiKey, headers, undefined, abortController.signal, this.thinkingLevel, this.agent.streamFunction, env, this.settingsManager.getRetrySettings(), this._summarizationRetryCallbacks({ source: "compaction", reason }));
2241
2394
  summary = compactResult.summary;
2242
2395
  firstKeptEntryId = compactResult.firstKeptEntryId;
2243
2396
  tokensBefore = compactResult.tokensBefore;
2244
2397
  usage = compactResult.usage;
2245
2398
  details = compactResult.details;
2246
2399
  }
2247
- if (externalSignal?.aborted || this._autoCompactionAbortController.signal.aborted) {
2248
- this._emit({
2249
- type: "compaction_end",
2250
- reason,
2251
- result: undefined,
2252
- aborted: true,
2253
- willRetry: false,
2254
- });
2400
+ if (externalSignal?.aborted || abortController.signal.aborted) {
2401
+ this._emit({ type: "compaction_end", reason, result: undefined, aborted: true, willRetry: false });
2255
2402
  await this._emitSessionCompactFailed({
2256
2403
  reason,
2257
2404
  aborted: true,
@@ -2260,12 +2407,12 @@ export class AgentSession {
2260
2407
  });
2261
2408
  return false;
2262
2409
  }
2410
+ abortController.signal.throwIfAborted();
2263
2411
  this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension, usage);
2264
2412
  const newEntries = this.sessionManager.getEntries();
2265
2413
  const sessionContext = this.sessionManager.buildSessionContext();
2266
2414
  this.agent.state.messages = sessionContext.messages;
2267
2415
  const estimatedTokensAfter = estimateMessagesTokens(sessionContext.messages);
2268
- // Get the saved compaction entry for the extension event
2269
2416
  const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
2270
2417
  if (this._extensionRunner && savedCompactionEntry) {
2271
2418
  await this._extensionRunner.emit({
@@ -2288,38 +2435,34 @@ export class AgentSession {
2288
2435
  if (willRetry) {
2289
2436
  const messages = this.agent.state.messages;
2290
2437
  const lastMsg = messages[messages.length - 1];
2291
- // The overflow response was persisted on message_end before _checkCompaction() removed it
2292
- // from agent state. Rebuilding state from the new compaction can restore that kept entry,
2293
- // leaving an assistant as the final message. agent.continue() rejects that state, so remove
2294
- // the retriable error or truncated-length response again before continuing the interrupted turn.
2295
2438
  if (lastMsg?.role === "assistant" && (lastMsg.stopReason === "error" || lastMsg.stopReason === "length")) {
2296
2439
  this.agent.state.messages = messages.slice(0, -1);
2297
2440
  }
2298
2441
  return true;
2299
2442
  }
2300
- // Auto-compaction can complete while follow-up/steering/custom messages are waiting.
2301
- // Continue once so queued messages are delivered.
2302
2443
  return this.agent.hasQueuedMessages();
2303
2444
  }
2304
2445
  catch (error) {
2305
- const errorMessage = error instanceof Error ? error.message : "compaction failed";
2446
+ const message = error instanceof Error ? error.message : "compaction failed";
2447
+ const aborted = externalSignal?.aborted === true || abortController?.signal.aborted === true || cancelledByExtension;
2306
2448
  if (started) {
2449
+ const errorMessage = aborted
2450
+ ? undefined
2451
+ : reason === "overflow"
2452
+ ? `Context overflow recovery failed: ${message}`
2453
+ : `Auto-compaction failed: ${message}`;
2307
2454
  this._emit({
2308
2455
  type: "compaction_end",
2309
2456
  reason,
2310
2457
  result: undefined,
2311
- aborted: false,
2458
+ aborted,
2312
2459
  willRetry: false,
2313
- errorMessage: reason === "overflow"
2314
- ? `Context overflow recovery failed: ${errorMessage}`
2315
- : `Auto-compaction failed: ${errorMessage}`,
2460
+ errorMessage,
2316
2461
  });
2317
2462
  await this._emitSessionCompactFailed({
2318
2463
  reason,
2319
- errorMessage: reason === "overflow"
2320
- ? `Context overflow recovery failed: ${errorMessage}`
2321
- : `Auto-compaction failed: ${errorMessage}`,
2322
- aborted: false,
2464
+ errorMessage,
2465
+ aborted,
2323
2466
  willRetry: false,
2324
2467
  fromExtension,
2325
2468
  });
@@ -2328,7 +2471,9 @@ export class AgentSession {
2328
2471
  }
2329
2472
  finally {
2330
2473
  externalSignal?.removeEventListener("abort", abortExternal);
2331
- this._autoCompactionAbortController = undefined;
2474
+ if (this._autoCompactionAbortController === abortController) {
2475
+ this._autoCompactionAbortController = undefined;
2476
+ }
2332
2477
  this._resolveIdleWaitIfIdle();
2333
2478
  }
2334
2479
  }
@@ -2343,7 +2488,7 @@ export class AgentSession {
2343
2488
  return this.settingsManager.getCompactionEnabled();
2344
2489
  }
2345
2490
  _shouldCompactBeforeNextProviderRequest(turn) {
2346
- const settings = this.settingsManager.getCompactionSettings();
2491
+ const settings = this.settingsManager.getCompactionSettings(this.model);
2347
2492
  if (!settings.enabled || turn.toolResults.length === 0)
2348
2493
  return false;
2349
2494
  const contextWindow = this.model?.contextWindow ?? 0;
@@ -2369,7 +2514,6 @@ export class AgentSession {
2369
2514
  return {
2370
2515
  ...baseContext,
2371
2516
  messages: this.agent.state.messages.slice(),
2372
- systemPrompt: this._systemPromptOverride ?? this._baseSystemPrompt,
2373
2517
  tools: this.agent.state.tools.slice(),
2374
2518
  };
2375
2519
  }
@@ -2377,7 +2521,7 @@ export class AgentSession {
2377
2521
  if (signal?.aborted || !this._shouldCompactBeforeNextProviderRequest(turn)) {
2378
2522
  return undefined;
2379
2523
  }
2380
- const settings = this.settingsManager.getCompactionSettings();
2524
+ const settings = this.settingsManager.getCompactionSettings(this.model);
2381
2525
  const markerParentId = this.sessionManager.getLeafId();
2382
2526
  const markerId = this._appendIntraTurnCompactionMarker();
2383
2527
  const preparationOptions = { firstKeptEntryId: markerId };
@@ -2433,8 +2577,7 @@ export class AgentSession {
2433
2577
  themePaths: this.buildExtensionResourcePaths(themePaths),
2434
2578
  };
2435
2579
  this._resourceLoader.extendResources(extensionPaths);
2436
- this._baseSystemPrompt = this._rebuildSystemPrompt(this.getActiveToolNames());
2437
- this.agent.state.systemPrompt = this._baseSystemPrompt;
2580
+ this._rebuildSystemPrompt(this.getActiveToolNames());
2438
2581
  }
2439
2582
  buildExtensionResourcePaths(entries) {
2440
2583
  return entries.map((entry) => {
@@ -2786,6 +2929,18 @@ export class AgentSession {
2786
2929
  },
2787
2930
  };
2788
2931
  }
2932
+ _finishCancelledRetry() {
2933
+ if (this._retryAttempt === 0)
2934
+ return;
2935
+ const attempt = this._retryAttempt;
2936
+ this._retryAttempt = 0;
2937
+ this._emit({
2938
+ type: "auto_retry_end",
2939
+ success: false,
2940
+ attempt,
2941
+ finalError: "Retry cancelled",
2942
+ });
2943
+ }
2789
2944
  /**
2790
2945
  * Prepare a retryable error for continuation with exponential backoff.
2791
2946
  * @returns true if the caller should continue the agent, false otherwise
@@ -2801,7 +2956,7 @@ export class AgentSession {
2801
2956
  this._retryAttempt--;
2802
2957
  return false;
2803
2958
  }
2804
- const delayMs = settings.baseDelayMs * 2 ** (this._retryAttempt - 1);
2959
+ const delayMs = retryDelayMs(settings, this._retryAttempt);
2805
2960
  this._emit({
2806
2961
  type: "auto_retry_start",
2807
2962
  attempt: this._retryAttempt,
@@ -2838,14 +2993,7 @@ export class AgentSession {
2838
2993
  }
2839
2994
  catch {
2840
2995
  // Aborted during sleep - emit end event so UI can clean up
2841
- const attempt = this._retryAttempt;
2842
- this._retryAttempt = 0;
2843
- this._emit({
2844
- type: "auto_retry_end",
2845
- success: false,
2846
- attempt,
2847
- finalError: "Retry cancelled",
2848
- });
2996
+ this._finishCancelledRetry();
2849
2997
  return false;
2850
2998
  }
2851
2999
  finally {
@@ -3003,6 +3151,9 @@ export class AgentSession {
3003
3151
  if (this.isStreaming) {
3004
3152
  throw new Error("Wait for the current response to finish before navigating the session tree.");
3005
3153
  }
3154
+ if (this.isCompacting) {
3155
+ throw new Error("Wait for the current compaction or tree navigation to finish before navigating the session tree.");
3156
+ }
3006
3157
  const oldLeafId = this.sessionManager.getLeafId();
3007
3158
  // No-op if already at target
3008
3159
  if (targetId === oldLeafId) {
@@ -3150,6 +3301,7 @@ export class AgentSession {
3150
3301
  // Update agent state
3151
3302
  const sessionContext = this.sessionManager.buildSessionContext();
3152
3303
  this.agent.state.messages = sessionContext.messages;
3304
+ this._restoreToolsFromTranscript();
3153
3305
  // Emit session_tree event
3154
3306
  await this._extensionRunner.emit({
3155
3307
  type: "session_tree",
@@ -3201,7 +3353,10 @@ export class AgentSession {
3201
3353
  let toolCalls = 0;
3202
3354
  const usageTotals = createUsageTotals();
3203
3355
  for (const entry of this.sessionManager.getEntries()) {
3204
- if ((entry.type === "branch_summary" || entry.type === "compaction") && entry.usage) {
3356
+ if (entry.type === "usage") {
3357
+ addUsageToTotals(usageTotals, entry.usage);
3358
+ }
3359
+ else if ((entry.type === "branch_summary" || entry.type === "compaction") && entry.usage) {
3205
3360
  addUsageToTotals(usageTotals, entry.usage);
3206
3361
  }
3207
3362
  if (entry.type !== "message")
@@ -3337,6 +3492,30 @@ export class AgentSession {
3337
3492
  writeFileSync(filePath, `${lines.join("\n")}\n`);
3338
3493
  return filePath;
3339
3494
  }
3495
+ /**
3496
+ * Ask the current model to describe what went wrong in this session for a bug report.
3497
+ * Used when the user declines to share the transcript itself.
3498
+ */
3499
+ async summarizeForBugReport(options) {
3500
+ const model = this.model;
3501
+ if (!model) {
3502
+ throw new Error("No model selected");
3503
+ }
3504
+ const { model: requestModel, apiKey, headers, env } = await this._getSummarizationRequestAuth(model);
3505
+ return generateBugReportSummary({
3506
+ messages: this.messages,
3507
+ hint: options.hint,
3508
+ model: requestModel,
3509
+ apiKey,
3510
+ headers,
3511
+ env,
3512
+ signal: options.signal,
3513
+ thinkingLevel: this.thinkingLevel,
3514
+ streamFn: this.agent.streamFunction,
3515
+ retry: this.settingsManager.getRetrySettings(),
3516
+ sessionId: this.sessionId,
3517
+ });
3518
+ }
3340
3519
  // =========================================================================
3341
3520
  // Utilities
3342
3521
  // =========================================================================