@astralyn/pi 0.85.3 → 0.86.0

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 (323) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/bundle/chunks/anthropic-messages-R2LYJUDD.js +6 -0
  3. package/dist/bundle/chunks/azure-openai-responses-HS35BQV6.js +2 -0
  4. package/dist/bundle/chunks/bedrock-converse-stream.js +36 -32
  5. package/dist/bundle/chunks/{chunk-S2DJQQI2.js → chunk-2IO5GDBS.js} +10 -1
  6. package/dist/bundle/chunks/{chunk-7LO5Z4WE.js → chunk-I5IVW2WS.js} +1 -1
  7. package/dist/bundle/chunks/chunk-JI4WGISU.js +2 -0
  8. package/dist/bundle/chunks/chunk-LPQCB2VJ.js +44 -0
  9. package/dist/bundle/chunks/chunk-M2YWW37K.js +2 -0
  10. package/dist/bundle/chunks/{chunk-LB4P77SH.js → chunk-MSHHFVMD.js} +776 -762
  11. package/dist/bundle/chunks/chunk-NW5FIDD7.js +11 -0
  12. package/dist/bundle/chunks/chunk-PJVFYVHE.js +42 -0
  13. package/dist/bundle/chunks/github-copilot.js +1 -1
  14. package/dist/bundle/chunks/google-generative-ai-NOVAJBFC.js +2 -0
  15. package/dist/bundle/chunks/google-vertex-4VHCZJV5.js +2 -0
  16. package/dist/bundle/chunks/https-proxy-agent-JOOPB4RE.js +16 -0
  17. package/dist/bundle/chunks/jiti-loader-BFSSLU7G.js +21 -0
  18. package/dist/bundle/chunks/jiti-static-loader-CLT3YR3B.js +2 -0
  19. package/dist/bundle/chunks/mistral-conversations-WC6A2WCJ.js +5 -0
  20. package/dist/bundle/chunks/{node-G4XOIDPH.js → node-CXWYTK2I.js} +1 -1
  21. package/dist/bundle/chunks/openai-codex-responses-XCDG75W2.js +10 -0
  22. package/dist/bundle/chunks/openai-completions-WJIA7NEB.js +7 -0
  23. package/dist/bundle/chunks/openai-responses-UT6XFKC7.js +2 -0
  24. package/dist/bundle/chunks/{pi-messages-PE6B42TG.js → pi-messages-MQMPIPNC.js} +1 -1
  25. package/dist/bundle/chunks/virtual-modules-B4BILEXT.js +2 -0
  26. package/dist/bundle/cli.js +1 -1
  27. package/dist/bundle/index.js +1 -1
  28. package/dist/bundle/rpc-entry.js +1 -1
  29. package/dist/cli/session-picker.d.ts +1 -1
  30. package/dist/cli/session-picker.d.ts.map +1 -1
  31. package/dist/cli/session-picker.js +13 -11
  32. package/dist/cli/session-picker.js.map +1 -1
  33. package/dist/config.js +1 -1
  34. package/dist/config.js.map +1 -1
  35. package/dist/core/agent-session.d.ts +56 -14
  36. package/dist/core/agent-session.d.ts.map +1 -1
  37. package/dist/core/agent-session.js +305 -176
  38. package/dist/core/agent-session.js.map +1 -1
  39. package/dist/core/bug-report-upload.d.ts +12 -0
  40. package/dist/core/bug-report-upload.d.ts.map +1 -0
  41. package/dist/core/bug-report-upload.js +22 -0
  42. package/dist/core/bug-report-upload.js.map +1 -0
  43. package/dist/core/bug-report.d.ts +176 -0
  44. package/dist/core/bug-report.d.ts.map +1 -0
  45. package/dist/core/bug-report.js +292 -0
  46. package/dist/core/bug-report.js.map +1 -0
  47. package/dist/core/cache-stats.d.ts.map +1 -1
  48. package/dist/core/cache-stats.js +12 -1
  49. package/dist/core/cache-stats.js.map +1 -1
  50. package/dist/core/cache-warmer.d.ts +102 -0
  51. package/dist/core/cache-warmer.d.ts.map +1 -0
  52. package/dist/core/cache-warmer.js +333 -0
  53. package/dist/core/cache-warmer.js.map +1 -0
  54. package/dist/core/compaction/branch-summarization.js +2 -2
  55. package/dist/core/compaction/branch-summarization.js.map +1 -1
  56. package/dist/core/compaction/compaction.d.ts +14 -9
  57. package/dist/core/compaction/compaction.d.ts.map +1 -1
  58. package/dist/core/compaction/compaction.js +30 -23
  59. package/dist/core/compaction/compaction.js.map +1 -1
  60. package/dist/core/compaction/settings.d.ts +17 -20
  61. package/dist/core/compaction/settings.d.ts.map +1 -1
  62. package/dist/core/compaction/settings.js +19 -26
  63. package/dist/core/compaction/settings.js.map +1 -1
  64. package/dist/core/context-snapshot.d.ts +16 -5
  65. package/dist/core/context-snapshot.d.ts.map +1 -1
  66. package/dist/core/context-snapshot.js +20 -10
  67. package/dist/core/context-snapshot.js.map +1 -1
  68. package/dist/core/crash-log.d.ts +22 -0
  69. package/dist/core/crash-log.d.ts.map +1 -0
  70. package/dist/core/crash-log.js +71 -0
  71. package/dist/core/crash-log.js.map +1 -0
  72. package/dist/core/experimental.d.ts +0 -4
  73. package/dist/core/experimental.d.ts.map +1 -1
  74. package/dist/core/experimental.js +0 -4
  75. package/dist/core/experimental.js.map +1 -1
  76. package/dist/core/extensions/index.d.ts +1 -1
  77. package/dist/core/extensions/index.d.ts.map +1 -1
  78. package/dist/core/extensions/index.js.map +1 -1
  79. package/dist/core/extensions/jiti-loader.d.ts +2 -0
  80. package/dist/core/extensions/jiti-loader.d.ts.map +1 -0
  81. package/dist/core/extensions/jiti-loader.js +4 -0
  82. package/dist/core/extensions/jiti-loader.js.map +1 -0
  83. package/dist/core/extensions/jiti-static-loader.d.ts +2 -0
  84. package/dist/core/extensions/jiti-static-loader.d.ts.map +1 -0
  85. package/dist/core/extensions/jiti-static-loader.js +4 -0
  86. package/dist/core/extensions/jiti-static-loader.js.map +1 -0
  87. package/dist/core/extensions/loader.d.ts.map +1 -1
  88. package/dist/core/extensions/loader.js +38 -51
  89. package/dist/core/extensions/loader.js.map +1 -1
  90. package/dist/core/extensions/runner.d.ts +10 -7
  91. package/dist/core/extensions/runner.d.ts.map +1 -1
  92. package/dist/core/extensions/runner.js +85 -67
  93. package/dist/core/extensions/runner.js.map +1 -1
  94. package/dist/core/extensions/types.d.ts +60 -50
  95. package/dist/core/extensions/types.d.ts.map +1 -1
  96. package/dist/core/extensions/types.js.map +1 -1
  97. package/dist/core/extensions/virtual-modules.d.ts +3 -0
  98. package/dist/core/extensions/virtual-modules.d.ts.map +1 -0
  99. package/dist/core/extensions/virtual-modules.js +39 -0
  100. package/dist/core/extensions/virtual-modules.js.map +1 -0
  101. package/dist/core/extensions/wrapper.d.ts.map +1 -1
  102. package/dist/core/extensions/wrapper.js +1 -20
  103. package/dist/core/extensions/wrapper.js.map +1 -1
  104. package/dist/core/index.d.ts +2 -1
  105. package/dist/core/index.d.ts.map +1 -1
  106. package/dist/core/index.js.map +1 -1
  107. package/dist/core/keybindings.d.ts +1 -1
  108. package/dist/core/keybindings.js +1 -1
  109. package/dist/core/keybindings.js.map +1 -1
  110. package/dist/core/messages.d.ts.map +1 -1
  111. package/dist/core/messages.js +1 -0
  112. package/dist/core/messages.js.map +1 -1
  113. package/dist/core/model-config.d.ts +101 -20
  114. package/dist/core/model-config.d.ts.map +1 -1
  115. package/dist/core/model-config.js +25 -18
  116. package/dist/core/model-config.js.map +1 -1
  117. package/dist/core/model-registry.d.ts +5 -1
  118. package/dist/core/model-registry.d.ts.map +1 -1
  119. package/dist/core/model-registry.js +8 -0
  120. package/dist/core/model-registry.js.map +1 -1
  121. package/dist/core/model-resolver.js +1 -1
  122. package/dist/core/model-resolver.js.map +1 -1
  123. package/dist/core/model-runtime.d.ts.map +1 -1
  124. package/dist/core/model-runtime.js +5 -3
  125. package/dist/core/model-runtime.js.map +1 -1
  126. package/dist/core/provider-composer.d.ts +3 -2
  127. package/dist/core/provider-composer.d.ts.map +1 -1
  128. package/dist/core/provider-composer.js +2 -0
  129. package/dist/core/provider-composer.js.map +1 -1
  130. package/dist/core/radius.d.ts +3 -0
  131. package/dist/core/radius.d.ts.map +1 -1
  132. package/dist/core/radius.js +6 -0
  133. package/dist/core/radius.js.map +1 -1
  134. package/dist/core/sdk.d.ts.map +1 -1
  135. package/dist/core/sdk.js +46 -18
  136. package/dist/core/sdk.js.map +1 -1
  137. package/dist/core/session-export.d.ts +6 -2
  138. package/dist/core/session-export.d.ts.map +1 -1
  139. package/dist/core/session-export.js +14 -13
  140. package/dist/core/session-export.js.map +1 -1
  141. package/dist/core/session-manager.d.ts +29 -6
  142. package/dist/core/session-manager.d.ts.map +1 -1
  143. package/dist/core/session-manager.js +159 -96
  144. package/dist/core/session-manager.js.map +1 -1
  145. package/dist/core/settings-manager.d.ts +31 -4
  146. package/dist/core/settings-manager.d.ts.map +1 -1
  147. package/dist/core/settings-manager.js +65 -8
  148. package/dist/core/settings-manager.js.map +1 -1
  149. package/dist/core/slash-commands.d.ts.map +1 -1
  150. package/dist/core/slash-commands.js +1 -0
  151. package/dist/core/slash-commands.js.map +1 -1
  152. package/dist/core/system-prompt.d.ts +49 -6
  153. package/dist/core/system-prompt.d.ts.map +1 -1
  154. package/dist/core/system-prompt.js +121 -92
  155. package/dist/core/system-prompt.js.map +1 -1
  156. package/dist/core/tools/bash.d.ts +2 -1
  157. package/dist/core/tools/bash.d.ts.map +1 -1
  158. package/dist/core/tools/bash.js +6 -3
  159. package/dist/core/tools/bash.js.map +1 -1
  160. package/dist/core/tools/edit.d.ts.map +1 -1
  161. package/dist/core/tools/edit.js +1 -2
  162. package/dist/core/tools/edit.js.map +1 -1
  163. package/dist/core/tools/read.d.ts.map +1 -1
  164. package/dist/core/tools/read.js +1 -2
  165. package/dist/core/tools/read.js.map +1 -1
  166. package/dist/core/tools/shell-execution.js +2 -2
  167. package/dist/core/tools/shell-execution.js.map +1 -1
  168. package/dist/core/tools/write.d.ts.map +1 -1
  169. package/dist/core/tools/write.js +1 -2
  170. package/dist/core/tools/write.js.map +1 -1
  171. package/dist/core/usage-totals.d.ts +1 -1
  172. package/dist/core/usage-totals.d.ts.map +1 -1
  173. package/dist/core/usage-totals.js +4 -1
  174. package/dist/core/usage-totals.js.map +1 -1
  175. package/dist/extensions/background/schema.d.ts +1 -1
  176. package/dist/extensions/btw/agent.d.ts.map +1 -1
  177. package/dist/extensions/btw/agent.js +3 -2
  178. package/dist/extensions/btw/agent.js.map +1 -1
  179. package/dist/extensions/llama/client.d.ts +2 -0
  180. package/dist/extensions/llama/client.d.ts.map +1 -1
  181. package/dist/extensions/llama/client.js +7 -3
  182. package/dist/extensions/llama/client.js.map +1 -1
  183. package/dist/extensions/llama/provider.d.ts.map +1 -1
  184. package/dist/extensions/llama/provider.js +20 -5
  185. package/dist/extensions/llama/provider.js.map +1 -1
  186. package/dist/extensions/provider/compat-fields.d.ts.map +1 -1
  187. package/dist/extensions/provider/compat-fields.js +5 -2
  188. package/dist/extensions/provider/compat-fields.js.map +1 -1
  189. package/dist/extensions/provider/ui/app.js +2 -2
  190. package/dist/extensions/provider/ui/app.js.map +1 -1
  191. package/dist/extensions/todo/schema.d.ts +1 -1
  192. package/dist/index.d.ts +3 -2
  193. package/dist/index.d.ts.map +1 -1
  194. package/dist/index.js.map +1 -1
  195. package/dist/main.d.ts.map +1 -1
  196. package/dist/main.js +13 -9
  197. package/dist/main.js.map +1 -1
  198. package/dist/migrations.d.ts.map +1 -1
  199. package/dist/migrations.js +2 -2
  200. package/dist/migrations.js.map +1 -1
  201. package/dist/modes/interactive/bug-report.d.ts +14 -0
  202. package/dist/modes/interactive/bug-report.d.ts.map +1 -0
  203. package/dist/modes/interactive/bug-report.js +209 -0
  204. package/dist/modes/interactive/bug-report.js.map +1 -0
  205. package/dist/modes/interactive/chat-viewport.js +1 -1
  206. package/dist/modes/interactive/chat-viewport.js.map +1 -1
  207. package/dist/modes/interactive/components/armin.d.ts.map +1 -1
  208. package/dist/modes/interactive/components/armin.js +0 -5
  209. package/dist/modes/interactive/components/armin.js.map +1 -1
  210. package/dist/modes/interactive/components/bash-execution.d.ts +0 -1
  211. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  212. package/dist/modes/interactive/components/bash-execution.js +2 -3
  213. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  214. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  215. package/dist/modes/interactive/components/branch-summary-message.js +12 -5
  216. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  217. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  218. package/dist/modes/interactive/components/compaction-summary-message.js +12 -5
  219. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  220. package/dist/modes/interactive/components/countdown-timer.d.ts +6 -13
  221. package/dist/modes/interactive/components/countdown-timer.d.ts.map +1 -1
  222. package/dist/modes/interactive/components/countdown-timer.js +15 -51
  223. package/dist/modes/interactive/components/countdown-timer.js.map +1 -1
  224. package/dist/modes/interactive/components/custom-editor.d.ts +3 -3
  225. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  226. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  227. package/dist/modes/interactive/components/extension-input.d.ts +2 -0
  228. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  229. package/dist/modes/interactive/components/extension-input.js +6 -0
  230. package/dist/modes/interactive/components/extension-input.js.map +1 -1
  231. package/dist/modes/interactive/components/extension-selector.d.ts +1 -2
  232. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  233. package/dist/modes/interactive/components/extension-selector.js +4 -2
  234. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  235. package/dist/modes/interactive/components/session-selector.d.ts +5 -8
  236. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  237. package/dist/modes/interactive/components/session-selector.js +72 -95
  238. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  239. package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
  240. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  241. package/dist/modes/interactive/components/settings-selector.js +11 -0
  242. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  243. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  244. package/dist/modes/interactive/components/skill-invocation-message.js +11 -4
  245. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  246. package/dist/modes/interactive/components/status-indicator.d.ts +2 -2
  247. package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -1
  248. package/dist/modes/interactive/components/status-indicator.js +7 -7
  249. package/dist/modes/interactive/components/status-indicator.js.map +1 -1
  250. package/dist/modes/interactive/components/tool-chrome.d.ts.map +1 -1
  251. package/dist/modes/interactive/components/tool-chrome.js +4 -1
  252. package/dist/modes/interactive/components/tool-chrome.js.map +1 -1
  253. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  254. package/dist/modes/interactive/components/tool-execution.js +4 -3
  255. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  256. package/dist/modes/interactive/components/tree-selector.d.ts +1 -10
  257. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  258. package/dist/modes/interactive/components/tree-selector.js +4 -51
  259. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  260. package/dist/modes/interactive/interactive-mode.d.ts +13 -1
  261. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  262. package/dist/modes/interactive/interactive-mode.js +227 -91
  263. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  264. package/dist/modes/interactive/session-share.d.ts +2 -0
  265. package/dist/modes/interactive/session-share.d.ts.map +1 -1
  266. package/dist/modes/interactive/session-share.js +8 -4
  267. package/dist/modes/interactive/session-share.js.map +1 -1
  268. package/dist/modes/interactive/tui-renderer.js +2 -2
  269. package/dist/modes/interactive/tui-renderer.js.map +1 -1
  270. package/dist/modes/rpc/rpc-mode.js +2 -2
  271. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  272. package/dist/utils/clipboard-command.d.ts +7 -0
  273. package/dist/utils/clipboard-command.d.ts.map +1 -0
  274. package/dist/utils/clipboard-command.js +45 -0
  275. package/dist/utils/clipboard-command.js.map +1 -0
  276. package/dist/utils/clipboard-image.d.ts.map +1 -1
  277. package/dist/utils/clipboard-image.js +52 -69
  278. package/dist/utils/clipboard-image.js.map +1 -1
  279. package/dist/utils/clipboard.d.ts.map +1 -1
  280. package/dist/utils/clipboard.js +61 -125
  281. package/dist/utils/clipboard.js.map +1 -1
  282. package/dist/utils/zip.d.ts +7 -0
  283. package/dist/utils/zip.d.ts.map +1 -0
  284. package/dist/utils/zip.js +60 -0
  285. package/dist/utils/zip.js.map +1 -0
  286. package/docs/compaction.md +32 -11
  287. package/docs/custom-provider.md +19 -8
  288. package/docs/environment-variables.md +1 -0
  289. package/docs/extensions.md +50 -34
  290. package/docs/models.md +38 -3
  291. package/docs/providers.md +1 -1
  292. package/docs/sdk.md +4 -2
  293. package/docs/session-format.md +63 -24
  294. package/docs/sessions.md +28 -1
  295. package/docs/settings.md +58 -4
  296. package/docs/termux.md +0 -1
  297. package/docs/tui.md +2 -0
  298. package/docs/usage.md +1 -0
  299. package/examples/extensions/README.md +0 -1
  300. package/examples/extensions/custom-provider-anthropic/index.ts +18 -12
  301. package/examples/extensions/custom-provider-gitlab-duo/index.ts +2 -2
  302. package/examples/extensions/prompt-customizer.ts +19 -67
  303. package/npm-shrinkwrap.json +275 -1028
  304. package/package.json +17 -23
  305. package/dist/bundle/chunks/anthropic-messages-IHRRITCV.js +0 -6
  306. package/dist/bundle/chunks/azure-openai-responses-QXBIDYXO.js +0 -2
  307. package/dist/bundle/chunks/chunk-JSJPY26N.js +0 -2
  308. package/dist/bundle/chunks/chunk-KI6LC764.js +0 -42
  309. package/dist/bundle/chunks/chunk-OWESLY5T.js +0 -11
  310. package/dist/bundle/chunks/chunk-RRBI7OUG.js +0 -50
  311. package/dist/bundle/chunks/chunk-YZJWMPUI.js +0 -2
  312. package/dist/bundle/chunks/google-generative-ai-NBOR3IQR.js +0 -2
  313. package/dist/bundle/chunks/google-vertex-VTHX3SUB.js +0 -2
  314. package/dist/bundle/chunks/https-proxy-agent-2VXB7436.js +0 -13
  315. package/dist/bundle/chunks/mistral-conversations-NO4ANAA5.js +0 -5
  316. package/dist/bundle/chunks/openai-codex-responses-5S3T3HMA.js +0 -10
  317. package/dist/bundle/chunks/openai-completions-CXRRTRWE.js +0 -7
  318. package/dist/bundle/chunks/openai-responses-CV3MH6VP.js +0 -2
  319. package/dist/utils/clipboard-native.d.ts +0 -11
  320. package/dist/utils/clipboard-native.d.ts.map +0 -1
  321. package/dist/utils/clipboard-native.js +0 -20
  322. package/dist/utils/clipboard-native.js.map +0 -1
  323. package/examples/extensions/kimi-deferred-tools.ts +0 -61
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import { readFileSync } from "node:fs";
16
16
  import { basename, dirname } from "node:path";
17
- import { contentText } from "@earendil-works/pi-ai";
17
+ import { contentText, getCurrentSystemMessage, getSystemMessageText, 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 { normalizeToolResultImages } from "../utils/tool-result-images.js";
23
23
  import { formatNoApiKeyFoundMessage, formatNoModelSelectedMessage } from "./auth-guidance.js";
24
24
  import { BackgroundSession } from "./background/session.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";
@@ -35,7 +36,7 @@ import { expandPromptTemplate } from "./prompt-templates.js";
35
36
  import { exportSessionToJsonl } from "./session-export.js";
36
37
  import { getLatestCompactionEntry } from "./session-manager.js";
37
38
  import { createSyntheticSourceInfo } from "./source-info.js";
38
- import { buildSystemPrompt } from "./system-prompt.js";
39
+ import { buildSystemPrompt, buildSystemPromptSections, diffSystemPromptSections, normalizeBuildSystemPromptOptions, } from "./system-prompt.js";
39
40
  import { createLocalBashOperations } from "./tools/bash.js";
40
41
  import { createAllToolDefinitions } from "./tools/index.js";
41
42
  import { createToolDefinitionFromAgentTool } from "./tools/tool-definition-wrapper.js";
@@ -71,15 +72,13 @@ function estimateMessagesTokens(messages) {
71
72
  return tokens;
72
73
  }
73
74
  // ============================================================================
74
- // Constants
75
- // ============================================================================
76
- // ============================================================================
77
75
  // AgentSession Class
78
76
  // ============================================================================
79
77
  export class AgentSession {
80
78
  constructor(config) {
81
79
  this._eventListeners = [];
82
80
  this._isAgentRunActive = false;
81
+ this._agentRunAbortRequested = false;
83
82
  /** Tracks pending steering messages for UI display. Removed when delivered. */
84
83
  this._steeringMessages = [];
85
84
  /** Tracks pending follow-up messages for UI display. Removed when delivered. */
@@ -108,8 +107,6 @@ export class AgentSession {
108
107
  this._toolDefinitions = new Map();
109
108
  this._toolPromptSnippets = new Map();
110
109
  this._toolPromptGuidelines = new Map();
111
- // Base system prompt (without extension appends) - used to apply fresh appends each turn
112
- this._baseSystemPrompt = "";
113
110
  // Track last assistant message for auto-compaction check
114
111
  this._lastAssistantMessage = undefined;
115
112
  /** Internal handler for agent events - shared by subscribe and reconnect */
@@ -200,6 +197,10 @@ export class AgentSession {
200
197
  this._cwd = config.cwd;
201
198
  this._modelRuntime = config.modelRuntime;
202
199
  this._captureModelContext = config.captureModelContext;
200
+ this._cacheWarmer = config.cacheWarmer;
201
+ if (this._cacheWarmer) {
202
+ this._cacheWarmer.onWarmed = (entry) => this._emit({ type: "entry_appended", entry });
203
+ }
203
204
  this._extensionRunnerRef = config.extensionRunnerRef;
204
205
  this._initialActiveToolNames = config.initialActiveToolNames;
205
206
  this._allowedToolNames = config.allowedToolNames ? new Set(config.allowedToolNames) : undefined;
@@ -211,10 +212,13 @@ export class AgentSession {
211
212
  this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);
212
213
  this._installAgentToolHooks();
213
214
  this._installAgentNextTurnRefresh();
215
+ this._installAgentForcedPromptProjection();
214
216
  this._buildRuntime({
215
217
  activeToolNames: this._initialActiveToolNames,
216
218
  includeAllExtensionTools: true,
217
219
  });
220
+ if (this._initialActiveToolNames === undefined)
221
+ this._restoreToolsFromTranscript();
218
222
  }
219
223
  get background() {
220
224
  return this._backgroundHost.service;
@@ -244,7 +248,6 @@ export class AgentSession {
244
248
  leafId: this.sessionManager.getLeafId(),
245
249
  model: structuredClone(model),
246
250
  thinkingLevel: this.thinkingLevel,
247
- systemPrompt: this.systemPrompt,
248
251
  tools: this.agent.state.tools.map(({ name, description, parameters, constrainedSampling }) => ({
249
252
  name,
250
253
  description,
@@ -260,18 +263,26 @@ export class AgentSession {
260
263
  onResponse: this.agent.onResponse,
261
264
  }),
262
265
  };
266
+ const currentSystemPrompt = this.systemPrompt;
263
267
  const messages = structuredClone(this.agent.state.messages);
264
268
  const convert = this.agent.convertToLlm;
265
269
  const transform = this.agent.transformContext;
266
270
  const prepared = this._captureModelContext
267
271
  ? await this._captureModelContext(messages)
268
272
  : await convert(transform ? await transform(messages) : messages);
269
- return { ...snapshot, messages: structuredClone(prepared) };
273
+ // The prepared transcript declares the prompt the provider received, including a forced
274
+ // prompt projected for the last request; before the first request there is none yet.
275
+ const declared = getCurrentSystemMessage(prepared);
276
+ return {
277
+ ...snapshot,
278
+ systemPrompt: declared ? getSystemMessageText(declared) : currentSystemPrompt,
279
+ messages: structuredClone(prepared),
280
+ };
270
281
  }
271
- async _getRequiredRequestAuth(model) {
282
+ async _getRequiredRequestAuth(model, signal) {
272
283
  let result;
273
284
  try {
274
- result = await this._modelRuntime.getAuth(model);
285
+ result = await this._modelRuntime.getAuth(model, { signal });
275
286
  }
276
287
  catch (error) {
277
288
  const cause = error instanceof Error ? error.cause : undefined;
@@ -297,20 +308,12 @@ export class AgentSession {
297
308
  }
298
309
  throw new Error(formatNoApiKeyFoundMessage(model.provider));
299
310
  }
300
- /**
301
- * Resolve summarization auth against a caller-supplied stream function.
302
- *
303
- * Callers that await this while the session stays live must capture
304
- * `this.agent.streamFunction` first and pass it in: a model switch during the
305
- * await would otherwise select auth for one stream function and generate the
306
- * summary with another.
307
- */
308
- async _getSummarizationRequestAuth(model, streamFunction) {
309
- if (streamFunction === streamSimple) {
310
- return this._getRequiredRequestAuth(model);
311
+ async _getSummarizationRequestAuth(model, signal) {
312
+ if (this.agent.streamFunction === streamSimple) {
313
+ return this._getRequiredRequestAuth(model, signal);
311
314
  }
312
315
  try {
313
- const result = await this._modelRuntime.getAuth(model);
316
+ const result = await this._modelRuntime.getAuth(model, { signal });
314
317
  if (!result)
315
318
  return { model };
316
319
  const requestModel = result.auth.baseUrl ? { ...model, baseUrl: result.auth.baseUrl } : model;
@@ -321,7 +324,9 @@ export class AgentSession {
321
324
  env: result.env,
322
325
  };
323
326
  }
324
- catch {
327
+ catch (error) {
328
+ if (signal?.aborted)
329
+ throw error;
325
330
  return { model };
326
331
  }
327
332
  }
@@ -386,7 +391,7 @@ export class AgentSession {
386
391
  }
387
392
  async _compactBeforeNextAssistantResponse(context) {
388
393
  const model = this.model;
389
- const settings = this.settingsManager.getCompactionSettings();
394
+ const settings = this.settingsManager.getCompactionSettings(model);
390
395
  if (!model ||
391
396
  model.contextWindow <= 0 ||
392
397
  !shouldCompact(estimateContextTokens(context.messages).tokens, model.contextWindow, settings)) {
@@ -407,13 +412,25 @@ export class AgentSession {
407
412
  const context = await this._compactBeforeNextAssistantResponse(turn.context);
408
413
  const previousSnapshot = await previousPrepareNextTurnWithContext?.({ ...turn, context }, signal);
409
414
  const nextContext = previousSnapshot?.context ?? context;
415
+ const runOptions = this._runSystemPromptOptions ?? this._baseSystemPromptOptions;
416
+ const options = normalizeBuildSystemPromptOptions({
417
+ ...runOptions,
418
+ selectedTools: this.getActiveToolNames(),
419
+ toolSnippets: { ...this._baseSystemPromptOptions.toolSnippets, ...runOptions.toolSnippets },
420
+ toolGuidelines: { ...this._baseSystemPromptOptions.toolGuidelines, ...runOptions.toolGuidelines },
421
+ });
422
+ const updateMessage = this._preparePromptAndToolLoadout(options, nextContext.messages);
423
+ // Keep session.systemPrompt and ctx.getSystemPrompt() in step with what the provider sees.
424
+ this._runSystemPromptOptions = options;
410
425
  return {
411
426
  ...previousSnapshot,
412
427
  context: {
413
428
  ...nextContext,
414
- systemPrompt: this._systemPromptOverride ?? this._baseSystemPrompt,
415
429
  tools: this.agent.state.tools.slice(),
416
430
  },
431
+ messages: updateMessage
432
+ ? [...(previousSnapshot?.messages ?? []), updateMessage]
433
+ : previousSnapshot?.messages,
417
434
  model: this.agent.state.model,
418
435
  thinkingLevel: this.agent.state.thinkingLevel,
419
436
  };
@@ -458,6 +475,7 @@ export class AgentSession {
458
475
  resolve();
459
476
  }
460
477
  async _emitAgentSettled() {
478
+ this._cacheWarmer?.onAgentSettled();
461
479
  const resumeBackground = this.pauseBackgroundNotifications();
462
480
  this._isAgentRunActive = false;
463
481
  try {
@@ -477,7 +495,10 @@ export class AgentSession {
477
495
  if (pendingIndex !== -1)
478
496
  this._pendingNextTurnMessages.splice(pendingIndex, 1);
479
497
  }
480
- else if (message.role === "user" || message.role === "assistant" || message.role === "toolResult") {
498
+ else if (message.role === "system" ||
499
+ message.role === "user" ||
500
+ message.role === "assistant" ||
501
+ message.role === "toolResult") {
481
502
  this.sessionManager.appendMessage(message);
482
503
  }
483
504
  else {
@@ -489,6 +510,8 @@ export class AgentSession {
489
510
  return true;
490
511
  }
491
512
  _willRetryAfterAgentEnd(event) {
513
+ if (this._agentRunAbortRequested)
514
+ return false;
492
515
  const settings = this.settingsManager.getRetrySettings();
493
516
  if (!settings.enabled || this._retryAttempt >= settings.maxRetries) {
494
517
  return false;
@@ -658,6 +681,10 @@ export class AgentSession {
658
681
  this._extensionRunner.invalidate(STALE_EXTENSION_CONTEXT_MESSAGE);
659
682
  this._disconnectFromAgent();
660
683
  this._eventListeners = [];
684
+ if (this._cacheWarmer) {
685
+ this._cacheWarmer.onWarmed = undefined;
686
+ this._cacheWarmer.cancel();
687
+ }
661
688
  cleanupSessionResources(this.sessionId);
662
689
  }
663
690
  // =========================================================================
@@ -667,6 +694,15 @@ export class AgentSession {
667
694
  get state() {
668
695
  return this.agent.state;
669
696
  }
697
+ /** Current cache-warming state and the policy inputs that produced it. */
698
+ get cacheWarmingStatus() {
699
+ return this._cacheWarmer?.status;
700
+ }
701
+ /** Persist the cache-warming mode and immediately reconcile active warming. */
702
+ setCacheWarmingMode(mode) {
703
+ this.settingsManager.setCacheWarmingMode(mode);
704
+ this._cacheWarmer?.onModeChanged();
705
+ }
670
706
  /** Current model (may be undefined if not yet selected) */
671
707
  get model() {
672
708
  return this.agent.state.model;
@@ -683,9 +719,9 @@ export class AgentSession {
683
719
  get isIdle() {
684
720
  return !this._isAgentRunActive && !this.isCompacting;
685
721
  }
686
- /** Current effective system prompt (includes any per-turn extension modifications) */
722
+ /** Current effective system prompt, including changes not yet sent to the model. */
687
723
  get systemPrompt() {
688
- return this.agent.state.systemPrompt;
724
+ return buildSystemPrompt(this._runSystemPromptOptions ?? this._baseSystemPromptOptions);
689
725
  }
690
726
  /** Current retry attempt (0 if not retrying) */
691
727
  get retryAttempt() {
@@ -730,9 +766,7 @@ export class AgentSession {
730
766
  }
731
767
  }
732
768
  this.agent.state.tools = tools;
733
- // Rebuild base system prompt with new tool set
734
- this._baseSystemPrompt = this._rebuildSystemPrompt(validToolNames);
735
- this.agent.state.systemPrompt = this._systemPromptOverride ?? this._baseSystemPrompt;
769
+ this._rebuildSystemPrompt(validToolNames);
736
770
  }
737
771
  /** Whether compaction or branch summarization is currently running */
738
772
  get isCompacting() {
@@ -801,23 +835,17 @@ export class AgentSession {
801
835
  _rebuildSystemPrompt(toolNames) {
802
836
  const validToolNames = toolNames.filter((name) => this._toolRegistry.has(name));
803
837
  const toolSnippets = {};
804
- const promptGuidelines = [];
805
- for (const name of validToolNames) {
838
+ for (const name of this._toolRegistry.keys()) {
806
839
  const snippet = this._toolPromptSnippets.get(name);
807
- if (snippet) {
840
+ if (snippet)
808
841
  toolSnippets[name] = snippet;
809
- }
810
- const toolGuidelines = this._toolPromptGuidelines.get(name);
811
- if (toolGuidelines) {
812
- promptGuidelines.push(...toolGuidelines);
813
- }
814
842
  }
815
843
  const loaderSystemPrompt = this._resourceLoader.getSystemPrompt();
816
844
  const loaderAppendSystemPrompt = this._resourceLoader.getAppendSystemPrompt();
817
- const appendSystemPrompt = loaderAppendSystemPrompt.length > 0 ? loaderAppendSystemPrompt.join("\n\n") : undefined;
845
+ const appendSystemPrompt = loaderAppendSystemPrompt.length > 0 ? loaderAppendSystemPrompt.join("\n\n") : "";
818
846
  const loadedSkills = this._resourceLoader.getSkills().skills;
819
847
  const loadedContextFiles = this._resourceLoader.getAgentsFiles().agentsFiles;
820
- this._baseSystemPromptOptions = {
848
+ this._baseSystemPromptOptions = normalizeBuildSystemPromptOptions({
821
849
  cwd: this._cwd,
822
850
  skills: loadedSkills,
823
851
  contextFiles: loadedContextFiles,
@@ -825,23 +853,87 @@ export class AgentSession {
825
853
  appendSystemPrompt,
826
854
  selectedTools: validToolNames,
827
855
  toolSnippets,
828
- promptGuidelines,
856
+ toolGuidelines: Object.fromEntries(this._toolPromptGuidelines),
857
+ });
858
+ }
859
+ /**
860
+ * Apply a prompt and tool loadout for the next request. Sets the executable tools and
861
+ * returns a system message patching the prompt sections the model currently has (replayed
862
+ * from `messages`), or undefined when the prompt is unchanged. Tool changes are declared by
863
+ * the agent loop before the request.
864
+ *
865
+ * A forced prompt does not affect the transcript: the structured sections are still diffed
866
+ * and persisted, and the forced text is projected onto the request by
867
+ * {@link _installAgentForcedPromptProjection}.
868
+ */
869
+ _preparePromptAndToolLoadout(options, messages = this.agent.state.messages) {
870
+ options.selectedTools = [...new Set(options.selectedTools)].filter((name) => this._toolRegistry.has(name));
871
+ this.agent.state.tools = options.selectedTools.flatMap((name) => {
872
+ const tool = this._toolRegistry.get(name);
873
+ return tool ? [tool] : [];
874
+ });
875
+ const sections = diffSystemPromptSections(getCurrentSystemMessage(messages)?.sections ?? {}, buildSystemPromptSections(options));
876
+ return sections ? { role: "system", content: "", sections, timestamp: Date.now() } : undefined;
877
+ }
878
+ /**
879
+ * Send a forced prompt as the provider's leading system prompt without recording it.
880
+ *
881
+ * A `before_agent_start` handler that returns `systemPrompt` needs that exact text at the
882
+ * head of the request; a mid-conversation system message would leave the original prompt
883
+ * in place. The forced text is a rendering of the current prompt, so the transcript keeps
884
+ * its structured sections and the request is projected instead: the system messages
885
+ * collapse into one head holding the forced text and the current tools. Runs after the
886
+ * `context` extension handlers.
887
+ */
888
+ _installAgentForcedPromptProjection() {
889
+ const previousTransformContext = this.agent.transformContext;
890
+ this.agent.transformContext = async (messages, signal) => {
891
+ const transformed = previousTransformContext ? await previousTransformContext(messages, signal) : messages;
892
+ const forced = this._runSystemPromptOptions?.forceSystemPrompt;
893
+ if (forced === undefined)
894
+ return transformed;
895
+ const current = getCurrentSystemMessage(transformed);
896
+ const head = {
897
+ role: "system",
898
+ content: forced,
899
+ ...(current?.toolsAdded ? { toolsAdded: current.toolsAdded } : {}),
900
+ timestamp: current?.timestamp ?? Date.now(),
901
+ };
902
+ return [head, ...transformed.filter((message) => message.role !== "system")];
829
903
  };
830
- return buildSystemPrompt(this._baseSystemPromptOptions);
904
+ }
905
+ /** Restore the active tool loadout declared by the session transcript, if it declares one. */
906
+ _restoreToolsFromTranscript() {
907
+ const current = getCurrentSystemMessage(this.sessionManager.buildSessionContext().messages);
908
+ if (!current)
909
+ return;
910
+ const toolNames = (current.toolsAdded ?? [])
911
+ .map((tool) => tool.name)
912
+ .filter((name) => this._toolRegistry.has(name));
913
+ this.agent.state.tools = toolNames.flatMap((name) => {
914
+ const registered = this._toolRegistry.get(name);
915
+ return registered ? [registered] : [];
916
+ });
917
+ this._rebuildSystemPrompt(toolNames);
831
918
  }
832
919
  // =========================================================================
833
920
  // Prompting
834
921
  // =========================================================================
835
922
  async _runAgentPrompt(messages) {
923
+ this._agentRunAbortRequested = false;
836
924
  this._isAgentRunActive = true;
837
925
  try {
838
926
  await this.agent.prompt(messages);
839
927
  while (await this._handlePostAgentRun()) {
928
+ if (this._agentRunAbortRequested)
929
+ break;
840
930
  await this.agent.continue();
841
931
  }
842
932
  }
843
933
  finally {
844
- this._systemPromptOverride = undefined;
934
+ if (this._agentRunAbortRequested)
935
+ this._finishCancelledRetry();
936
+ this._runSystemPromptOptions = undefined;
845
937
  this._flushPendingBashMessages();
846
938
  this._flushPendingCustomMessages();
847
939
  await this._emitAgentSettled();
@@ -850,11 +942,21 @@ export class AgentSession {
850
942
  async _handlePostAgentRun() {
851
943
  const msg = this._lastAssistantMessage;
852
944
  this._lastAssistantMessage = undefined;
945
+ if (this._agentRunAbortRequested) {
946
+ this._finishCancelledRetry();
947
+ return false;
948
+ }
853
949
  if (!msg) {
854
950
  return false;
855
951
  }
856
952
  if (this._isRetryableError(msg) && (await this._prepareRetry(msg))) {
857
- return true;
953
+ if (this._agentRunAbortRequested)
954
+ this._finishCancelledRetry();
955
+ return !this._agentRunAbortRequested;
956
+ }
957
+ if (this._agentRunAbortRequested) {
958
+ this._finishCancelledRetry();
959
+ return false;
858
960
  }
859
961
  if (msg.stopReason === "error" && this._retryAttempt > 0) {
860
962
  this._emit({
@@ -866,11 +968,24 @@ export class AgentSession {
866
968
  this._retryAttempt = 0;
867
969
  }
868
970
  if (await this._checkCompaction(msg)) {
869
- return true;
971
+ return !this._agentRunAbortRequested;
870
972
  }
871
973
  // The agent loop drains both queues before emitting agent_end. Any messages
872
974
  // here were queued by agent_end extension handlers and need a continuation.
873
- return this.agent.hasQueuedMessages();
975
+ return !this._agentRunAbortRequested && this.agent.hasQueuedMessages();
976
+ }
977
+ async _runInputHandlers(text, images, source, streamingBehavior) {
978
+ if (!this._extensionRunner.hasHandlers("input")) {
979
+ return { text, images };
980
+ }
981
+ const inputResult = await this._extensionRunner.emitInput(text, images, source, streamingBehavior);
982
+ if (inputResult.action === "handled") {
983
+ return undefined;
984
+ }
985
+ if (inputResult.action === "transform") {
986
+ return { text: inputResult.text, images: inputResult.images ?? images };
987
+ }
988
+ return { text, images };
874
989
  }
875
990
  /**
876
991
  * Send a prompt to the agent.
@@ -908,19 +1023,12 @@ export class AgentSession {
908
1023
  throw new Error("Cannot submit a prompt while compaction is in progress. Wait for compaction to finish and retry.");
909
1024
  }
910
1025
  // Emit input event for extension interception (before skill/template expansion)
911
- let currentText = text;
912
- let currentImages = options?.images;
913
- if (this._extensionRunner.hasHandlers("input")) {
914
- const inputResult = await this._extensionRunner.emitInput(currentText, currentImages, options?.source ?? "interactive", this.isStreaming ? options?.streamingBehavior : undefined);
915
- if (inputResult.action === "handled") {
916
- preflightResult?.(true);
917
- return;
918
- }
919
- if (inputResult.action === "transform") {
920
- currentText = inputResult.text;
921
- currentImages = inputResult.images ?? currentImages;
922
- }
1026
+ const processedInput = await this._runInputHandlers(text, options?.images, options?.source ?? "interactive", this.isStreaming ? options?.streamingBehavior : undefined);
1027
+ if (!processedInput) {
1028
+ preflightResult?.(true);
1029
+ return;
923
1030
  }
1031
+ const { text: currentText, images: currentImages } = processedInput;
924
1032
  // Expand skill commands (/skill:name args) and prompt templates (/template args)
925
1033
  let expandedText = currentText;
926
1034
  if (expandPromptTemplates) {
@@ -982,31 +1090,30 @@ export class AgentSession {
982
1090
  messages.push(msg);
983
1091
  }
984
1092
  // Emit before_agent_start extension event
985
- const result = await this._extensionRunner.emitBeforeAgentStart(expandedText, currentImages, this._baseSystemPrompt, this._baseSystemPromptOptions);
986
- // Add all custom messages from extensions
987
- if (result?.messages) {
988
- for (const msg of result.messages) {
989
- messages.push({
990
- role: "custom",
991
- customType: msg.customType,
992
- // Untyped extensions can pass null/missing content; normalize at ingestion.
993
- content: msg.content ?? [],
994
- display: msg.display,
995
- details: msg.details,
996
- timestamp: Date.now(),
997
- });
998
- }
999
- }
1000
- // Apply extension-modified system prompt, or reset to base
1001
- if (result?.systemPrompt !== undefined) {
1002
- this._systemPromptOverride = result.systemPrompt;
1003
- this.agent.state.systemPrompt = result.systemPrompt;
1004
- }
1005
- else {
1006
- // Ensure we're using the base prompt (in case previous turn had modifications)
1007
- this._systemPromptOverride = undefined;
1008
- this.agent.state.systemPrompt = this._baseSystemPrompt;
1093
+ const selectedToolsBefore = this._baseSystemPromptOptions.selectedTools;
1094
+ const result = await this._extensionRunner.emitBeforeAgentStart(expandedText, currentImages, this._baseSystemPromptOptions);
1095
+ // Handlers may edit event.systemPromptOptions.selectedTools or call setActiveTools(),
1096
+ // which updates the live loadout instead. An explicit edit wins; otherwise the live
1097
+ // loadout is authoritative, so a setActiveTools() call is not undone here.
1098
+ const handlerEditedTools = result.systemPromptOptions.selectedTools.length !== selectedToolsBefore.length ||
1099
+ result.systemPromptOptions.selectedTools.some((name, index) => name !== selectedToolsBefore[index]);
1100
+ if (!handlerEditedTools)
1101
+ result.systemPromptOptions.selectedTools = this.getActiveToolNames();
1102
+ for (const msg of result.messages) {
1103
+ messages.push({
1104
+ role: "custom",
1105
+ customType: msg.customType,
1106
+ // Untyped extensions can pass null/missing content; normalize at ingestion.
1107
+ content: msg.content ?? [],
1108
+ display: msg.display,
1109
+ details: msg.details,
1110
+ timestamp: Date.now(),
1111
+ });
1009
1112
  }
1113
+ const updateMessage = this._preparePromptAndToolLoadout(result.systemPromptOptions);
1114
+ this._runSystemPromptOptions = result.systemPromptOptions;
1115
+ if (updateMessage)
1116
+ messages.unshift(updateMessage);
1010
1117
  }
1011
1118
  catch (error) {
1012
1119
  preflightResult?.(false);
@@ -1079,40 +1186,44 @@ export class AgentSession {
1079
1186
  return text; // Return original on error
1080
1187
  }
1081
1188
  }
1189
+ async _queueUserInput(text, images, behavior, source) {
1190
+ if (text.startsWith("/")) {
1191
+ this._throwIfExtensionCommand(text);
1192
+ }
1193
+ const processedInput = await this._runInputHandlers(text, images, source, this.isStreaming ? behavior : undefined);
1194
+ if (!processedInput)
1195
+ return;
1196
+ let expandedText = this._expandSkillCommand(processedInput.text);
1197
+ expandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);
1198
+ if (behavior === "steer") {
1199
+ await this._queueSteer(expandedText, processedInput.images);
1200
+ }
1201
+ else {
1202
+ await this._queueFollowUp(expandedText, processedInput.images);
1203
+ }
1204
+ }
1082
1205
  /**
1083
1206
  * Queue a steering message while the agent is running.
1084
1207
  * Delivered after the current assistant turn finishes executing its tool calls,
1085
1208
  * before the next LLM call.
1086
1209
  * Expands skill commands and prompt templates. Errors on extension commands.
1087
1210
  * @param images Optional image attachments to include with the message
1211
+ * @param options Input source; defaults to interactive
1088
1212
  * @throws Error if text is an extension command
1089
1213
  */
1090
- async steer(text, images) {
1091
- // Check for extension commands (cannot be queued)
1092
- if (text.startsWith("/")) {
1093
- this._throwIfExtensionCommand(text);
1094
- }
1095
- // Expand skill commands and prompt templates
1096
- let expandedText = this._expandSkillCommand(text);
1097
- expandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);
1098
- await this._queueSteer(expandedText, images);
1214
+ async steer(text, images, options) {
1215
+ await this._queueUserInput(text, images, "steer", options?.source ?? "interactive");
1099
1216
  }
1100
1217
  /**
1101
1218
  * Queue a follow-up message to be processed after the agent finishes.
1102
1219
  * Delivered only when agent has no more tool calls or steering messages.
1103
1220
  * Expands skill commands and prompt templates. Errors on extension commands.
1104
1221
  * @param images Optional image attachments to include with the message
1222
+ * @param options Input source; defaults to interactive
1105
1223
  * @throws Error if text is an extension command
1106
1224
  */
1107
- async followUp(text, images) {
1108
- // Check for extension commands (cannot be queued)
1109
- if (text.startsWith("/")) {
1110
- this._throwIfExtensionCommand(text);
1111
- }
1112
- // Expand skill commands and prompt templates
1113
- let expandedText = this._expandSkillCommand(text);
1114
- expandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);
1115
- await this._queueFollowUp(expandedText, images);
1225
+ async followUp(text, images, options) {
1226
+ await this._queueUserInput(text, images, "followUp", options?.source ?? "interactive");
1116
1227
  }
1117
1228
  /**
1118
1229
  * Internal: Queue a steering message (already expanded, no extension command check).
@@ -1295,6 +1406,9 @@ export class AgentSession {
1295
1406
  * Abort current operation and wait for agent to become idle.
1296
1407
  */
1297
1408
  async abort() {
1409
+ if (this._isAgentRunActive) {
1410
+ this._agentRunAbortRequested = true;
1411
+ }
1298
1412
  this.abortRetry();
1299
1413
  this.abortCompaction();
1300
1414
  this.abortBranchSummary();
@@ -1524,8 +1638,8 @@ export class AgentSession {
1524
1638
  // Compaction
1525
1639
  // =========================================================================
1526
1640
  /** Generate Pi's built-in compaction summary for manual and automatic compaction. */
1527
- async _runDefaultCompaction(preparation, requestModel, apiKey, headers, customInstructions, signal, env, reason, streamFunction) {
1528
- return compact(preparation, requestModel, apiKey, headers, customInstructions, signal, this.thinkingLevel, streamFunction, env, this.settingsManager.getRetrySettings(), this._summarizationRetryCallbacks({ source: "compaction", reason }), undefined);
1641
+ async _runDefaultCompaction(preparation, requestModel, apiKey, headers, customInstructions, signal, env, reason) {
1642
+ return compact(preparation, requestModel, apiKey, headers, customInstructions, signal, this.thinkingLevel, this.agent.streamFunction, env, this.settingsManager.getRetrySettings(), this._summarizationRetryCallbacks({ source: "compaction", reason }), undefined);
1529
1643
  }
1530
1644
  _clearManualCompactionState() {
1531
1645
  this._compactionAbortController = undefined;
@@ -1553,15 +1667,16 @@ export class AgentSession {
1553
1667
  this._compactionAbortController = new AbortController();
1554
1668
  this._emit({ type: "compaction_start", reason: "manual" });
1555
1669
  let fromExtension = false;
1670
+ let cancelledByExtension = false;
1556
1671
  try {
1557
- if (!this.model) {
1672
+ const model = this.model;
1673
+ if (!model) {
1558
1674
  throw new Error(formatNoModelSelectedMessage());
1559
1675
  }
1560
- const streamFunction = this.agent.streamFunction;
1561
- const { model: requestModel, apiKey, headers, env, } = await this._getSummarizationRequestAuth(this.model, streamFunction);
1676
+ const settings = this.settingsManager.getCompactionSettings(model);
1677
+ const { model: requestModel, apiKey, headers, env, } = await this._getSummarizationRequestAuth(model, this._compactionAbortController.signal);
1562
1678
  const pathEntries = this.sessionManager.getBranch();
1563
- const settings = this.settingsManager.getCompactionSettings();
1564
- const preparation = prepareCompaction(pathEntries, settings, requestModel.contextWindow);
1679
+ const preparation = prepareCompaction(pathEntries, settings);
1565
1680
  if (!preparation) {
1566
1681
  // Check why we can't compact
1567
1682
  const lastEntry = pathEntries[pathEntries.length - 1];
@@ -1582,6 +1697,7 @@ export class AgentSession {
1582
1697
  signal: this._compactionAbortController.signal,
1583
1698
  }));
1584
1699
  if (result?.cancel) {
1700
+ cancelledByExtension = true;
1585
1701
  throw new Error("Compaction cancelled");
1586
1702
  }
1587
1703
  if (result?.compaction) {
@@ -1604,7 +1720,7 @@ export class AgentSession {
1604
1720
  }
1605
1721
  else {
1606
1722
  // Shared default summary generator, also used by automatic compaction.
1607
- const result = await this._runDefaultCompaction(preparation, requestModel, apiKey, headers, customInstructions, this._compactionAbortController.signal, env, "manual", streamFunction);
1723
+ const result = await this._runDefaultCompaction(preparation, requestModel, apiKey, headers, customInstructions, this._compactionAbortController.signal, env, "manual");
1608
1724
  summary = result.summary;
1609
1725
  firstKeptEntryId = result.firstKeptEntryId;
1610
1726
  tokensBefore = result.tokensBefore;
@@ -1651,7 +1767,7 @@ export class AgentSession {
1651
1767
  }
1652
1768
  catch (error) {
1653
1769
  const message = error instanceof Error ? error.message : String(error);
1654
- const aborted = message === "Compaction cancelled" || (error instanceof Error && error.name === "AbortError");
1770
+ const aborted = this._compactionAbortController?.signal.aborted === true || cancelledByExtension;
1655
1771
  const errorMessage = aborted ? undefined : `Compaction failed: ${message}`;
1656
1772
  this._clearManualCompactionState();
1657
1773
  this._emit({
@@ -1714,7 +1830,7 @@ export class AgentSession {
1714
1830
  * @returns Whether the post-run loop should call `agent.continue()` for overflow recovery or queued messages
1715
1831
  */
1716
1832
  async _checkCompaction(assistantMessage, skipAbortedCheck = true) {
1717
- const settings = this.settingsManager.getCompactionSettings();
1833
+ const settings = this.settingsManager.getCompactionSettings(this.model);
1718
1834
  if (!settings.enabled)
1719
1835
  return false;
1720
1836
  // Skip if message was aborted (user cancelled) - unless skipAbortedCheck is false
@@ -1821,25 +1937,28 @@ export class AgentSession {
1821
1937
  async _runAutoCompaction(reason, willRetry) {
1822
1938
  const resumeBackground = this.pauseBackgroundNotifications();
1823
1939
  try {
1824
- const settings = this.settingsManager.getCompactionSettings();
1940
+ const model = this.model;
1941
+ const settings = this.settingsManager.getCompactionSettings(model);
1942
+ let abortController;
1825
1943
  let started = false;
1826
1944
  let fromExtension = false;
1945
+ let cancelledByExtension = false;
1827
1946
  try {
1828
- if (!this.model) {
1947
+ if (!model) {
1829
1948
  return false;
1830
1949
  }
1831
- // Capture before awaiting auth: a model switch during the await would leave the
1832
- // resolved auth and the summary generator disagreeing about the stream function.
1833
- const streamFunction = this.agent.streamFunction;
1834
- const { model: requestModel, apiKey, headers, env, } = await this._getSummarizationRequestAuth(this.model, streamFunction);
1835
1950
  const pathEntries = this.sessionManager.getBranch();
1836
- const preparation = prepareCompaction(pathEntries, settings, requestModel.contextWindow);
1951
+ const preparation = prepareCompaction(pathEntries, settings);
1837
1952
  if (!preparation) {
1838
1953
  return false;
1839
1954
  }
1840
- this._emit({ type: "compaction_start", reason });
1841
- this._autoCompactionAbortController = new AbortController();
1955
+ abortController = new AbortController();
1956
+ this._autoCompactionAbortController = abortController;
1842
1957
  started = true;
1958
+ this._emit({ type: "compaction_start", reason });
1959
+ abortController.signal.throwIfAborted();
1960
+ const { model: requestModel, apiKey, headers, env, } = await this._getSummarizationRequestAuth(model, abortController.signal);
1961
+ abortController.signal.throwIfAborted();
1843
1962
  let extensionCompaction;
1844
1963
  if (this._extensionRunner.hasHandlers("session_before_compact")) {
1845
1964
  const extensionResult = (await this._extensionRunner.emit({
@@ -1849,29 +1968,18 @@ export class AgentSession {
1849
1968
  customInstructions: undefined,
1850
1969
  reason,
1851
1970
  willRetry,
1852
- signal: this._autoCompactionAbortController.signal,
1971
+ signal: abortController.signal,
1853
1972
  }));
1854
1973
  if (extensionResult?.cancel) {
1855
- this._emit({
1856
- type: "compaction_end",
1857
- reason,
1858
- result: undefined,
1859
- aborted: true,
1860
- willRetry: false,
1861
- });
1862
- await this._emitSessionCompactFailed({
1863
- reason,
1864
- aborted: true,
1865
- willRetry: false,
1866
- fromExtension: false,
1867
- });
1868
- return false;
1974
+ cancelledByExtension = true;
1975
+ throw new Error("Compaction cancelled");
1869
1976
  }
1870
1977
  if (extensionResult?.compaction) {
1871
1978
  extensionCompaction = extensionResult.compaction;
1872
1979
  fromExtension = true;
1873
1980
  }
1874
1981
  }
1982
+ abortController.signal.throwIfAborted();
1875
1983
  let summary;
1876
1984
  let firstKeptEntryId;
1877
1985
  let tokensBefore;
@@ -1887,29 +1995,14 @@ export class AgentSession {
1887
1995
  }
1888
1996
  else {
1889
1997
  // Shared default summary generator, also used by manual compaction.
1890
- const compactResult = await this._runDefaultCompaction(preparation, requestModel, apiKey, headers, undefined, this._autoCompactionAbortController.signal, env, reason, streamFunction);
1998
+ const compactResult = await this._runDefaultCompaction(preparation, requestModel, apiKey, headers, undefined, abortController.signal, env, reason);
1891
1999
  summary = compactResult.summary;
1892
2000
  firstKeptEntryId = compactResult.firstKeptEntryId;
1893
2001
  tokensBefore = compactResult.tokensBefore;
1894
2002
  usage = compactResult.usage;
1895
2003
  details = compactResult.details;
1896
2004
  }
1897
- if (this._autoCompactionAbortController.signal.aborted) {
1898
- this._emit({
1899
- type: "compaction_end",
1900
- reason,
1901
- result: undefined,
1902
- aborted: true,
1903
- willRetry: false,
1904
- });
1905
- await this._emitSessionCompactFailed({
1906
- reason,
1907
- aborted: true,
1908
- willRetry: false,
1909
- fromExtension,
1910
- });
1911
- return false;
1912
- }
2005
+ abortController.signal.throwIfAborted();
1913
2006
  this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension, usage);
1914
2007
  const newEntries = this.sessionManager.getEntries();
1915
2008
  const sessionContext = this.sessionManager.buildSessionContext();
@@ -1953,23 +2046,26 @@ export class AgentSession {
1953
2046
  return this.agent.hasQueuedMessages();
1954
2047
  }
1955
2048
  catch (error) {
1956
- const errorMessage = error instanceof Error ? error.message : "compaction failed";
2049
+ const message = error instanceof Error ? error.message : "compaction failed";
2050
+ const aborted = abortController?.signal.aborted === true || cancelledByExtension;
1957
2051
  if (started) {
1958
- const formattedErrorMessage = reason === "overflow"
1959
- ? `Context overflow recovery failed: ${errorMessage}`
1960
- : `Auto-compaction failed: ${errorMessage}`;
2052
+ const errorMessage = aborted
2053
+ ? undefined
2054
+ : reason === "overflow"
2055
+ ? `Context overflow recovery failed: ${message}`
2056
+ : `Auto-compaction failed: ${message}`;
1961
2057
  this._emit({
1962
2058
  type: "compaction_end",
1963
2059
  reason,
1964
2060
  result: undefined,
1965
- aborted: false,
2061
+ aborted,
1966
2062
  willRetry: false,
1967
- errorMessage: formattedErrorMessage,
2063
+ errorMessage,
1968
2064
  });
1969
2065
  await this._emitSessionCompactFailed({
1970
2066
  reason,
1971
- errorMessage: formattedErrorMessage,
1972
- aborted: false,
2067
+ errorMessage,
2068
+ aborted,
1973
2069
  willRetry: false,
1974
2070
  fromExtension,
1975
2071
  });
@@ -1977,7 +2073,9 @@ export class AgentSession {
1977
2073
  return false;
1978
2074
  }
1979
2075
  finally {
1980
- this._autoCompactionAbortController = undefined;
2076
+ if (this._autoCompactionAbortController === abortController) {
2077
+ this._autoCompactionAbortController = undefined;
2078
+ }
1981
2079
  this._resolveIdleWaitIfIdle();
1982
2080
  }
1983
2081
  }
@@ -2041,8 +2139,7 @@ export class AgentSession {
2041
2139
  themePaths: this.buildExtensionResourcePaths(themePaths),
2042
2140
  };
2043
2141
  this._resourceLoader.extendResources(extensionPaths);
2044
- this._baseSystemPrompt = this._rebuildSystemPrompt(this.getActiveToolNames());
2045
- this.agent.state.systemPrompt = this._baseSystemPrompt;
2142
+ this._rebuildSystemPrompt(this.getActiveToolNames());
2046
2143
  }
2047
2144
  buildExtensionResourcePaths(entries) {
2048
2145
  return entries.map((entry) => {
@@ -2392,6 +2489,18 @@ export class AgentSession {
2392
2489
  },
2393
2490
  };
2394
2491
  }
2492
+ _finishCancelledRetry() {
2493
+ if (this._retryAttempt === 0)
2494
+ return;
2495
+ const attempt = this._retryAttempt;
2496
+ this._retryAttempt = 0;
2497
+ this._emit({
2498
+ type: "auto_retry_end",
2499
+ success: false,
2500
+ attempt,
2501
+ finalError: "Retry cancelled",
2502
+ });
2503
+ }
2395
2504
  /**
2396
2505
  * Prepare a retryable error for continuation with exponential backoff.
2397
2506
  * @returns true if the caller should continue the agent, false otherwise
@@ -2407,7 +2516,7 @@ export class AgentSession {
2407
2516
  this._retryAttempt--;
2408
2517
  return false;
2409
2518
  }
2410
- const delayMs = settings.baseDelayMs * 2 ** (this._retryAttempt - 1);
2519
+ const delayMs = retryDelayMs(settings, this._retryAttempt);
2411
2520
  this._emit({
2412
2521
  type: "auto_retry_start",
2413
2522
  attempt: this._retryAttempt,
@@ -2427,14 +2536,7 @@ export class AgentSession {
2427
2536
  }
2428
2537
  catch {
2429
2538
  // Aborted during sleep - emit end event so UI can clean up
2430
- const attempt = this._retryAttempt;
2431
- this._retryAttempt = 0;
2432
- this._emit({
2433
- type: "auto_retry_end",
2434
- success: false,
2435
- attempt,
2436
- finalError: "Retry cancelled",
2437
- });
2539
+ this._finishCancelledRetry();
2438
2540
  return false;
2439
2541
  }
2440
2542
  finally {
@@ -2585,6 +2687,9 @@ export class AgentSession {
2585
2687
  if (this.isStreaming) {
2586
2688
  throw new Error("Wait for the current response to finish before navigating the session tree.");
2587
2689
  }
2690
+ if (this.isCompacting) {
2691
+ throw new Error("Wait for the current compaction or tree navigation to finish before navigating the session tree.");
2692
+ }
2588
2693
  const oldLeafId = this.sessionManager.getLeafId();
2589
2694
  // No-op if already at target
2590
2695
  if (targetId === oldLeafId) {
@@ -2651,8 +2756,7 @@ export class AgentSession {
2651
2756
  let summaryUsage;
2652
2757
  if (options.summarize && entriesToSummarize.length > 0 && !extensionSummary) {
2653
2758
  const model = this.model;
2654
- const streamFunction = this.agent.streamFunction;
2655
- const { model: requestModel, apiKey, headers, env, } = await this._getSummarizationRequestAuth(model, streamFunction);
2759
+ const { model: requestModel, apiKey, headers, env } = await this._getSummarizationRequestAuth(model);
2656
2760
  const branchSummarySettings = this.settingsManager.getBranchSummarySettings();
2657
2761
  const result = await generateBranchSummary(entriesToSummarize, {
2658
2762
  model: requestModel,
@@ -2663,7 +2767,7 @@ export class AgentSession {
2663
2767
  customInstructions,
2664
2768
  replaceInstructions,
2665
2769
  reserveTokens: branchSummarySettings.reserveTokens,
2666
- streamFn: streamFunction,
2770
+ streamFn: this.agent.streamFunction,
2667
2771
  retry: this.settingsManager.getRetrySettings(),
2668
2772
  callbacks: this._summarizationRetryCallbacks({ source: "branchSummary" }),
2669
2773
  });
@@ -2731,6 +2835,7 @@ export class AgentSession {
2731
2835
  // Update agent state
2732
2836
  const sessionContext = this.sessionManager.buildSessionContext();
2733
2837
  this.agent.state.messages = sessionContext.messages;
2838
+ this._restoreToolsFromTranscript();
2734
2839
  // Emit session_tree event
2735
2840
  await this._extensionRunner.emit({
2736
2841
  type: "session_tree",
@@ -2889,6 +2994,30 @@ export class AgentSession {
2889
2994
  exportToJsonl(outputPath) {
2890
2995
  return exportSessionToJsonl(this.sessionManager, outputPath);
2891
2996
  }
2997
+ /**
2998
+ * Ask the current model to describe what went wrong in this session for a bug report.
2999
+ * Used when the user declines to share the transcript itself.
3000
+ */
3001
+ async summarizeForBugReport(options) {
3002
+ const model = this.model;
3003
+ if (!model) {
3004
+ throw new Error("No model selected");
3005
+ }
3006
+ const { model: requestModel, apiKey, headers, env, } = await this._getSummarizationRequestAuth(model, options.signal);
3007
+ return generateBugReportSummary({
3008
+ messages: this.messages,
3009
+ hint: options.hint,
3010
+ model: requestModel,
3011
+ apiKey,
3012
+ headers,
3013
+ env,
3014
+ signal: options.signal,
3015
+ thinkingLevel: this.thinkingLevel,
3016
+ streamFn: this.agent.streamFunction,
3017
+ retry: this.settingsManager.getRetrySettings(),
3018
+ sessionId: this.sessionId,
3019
+ });
3020
+ }
2892
3021
  // =========================================================================
2893
3022
  // Utilities
2894
3023
  // =========================================================================