@astralyn/pi 0.87.1 → 0.87.3

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 (328) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/bundle/chunks/{chunk-CDMUKIXY.js → chunk-MJ45I4SM.js} +1 -1
  3. package/dist/bundle/chunks/{chunk-772GXEFD.js → chunk-TYOQD6VQ.js} +97 -84
  4. package/dist/bundle/chunks/{virtual-modules-LQO2MG3T.js → virtual-modules-NDGCHNG7.js} +1 -1
  5. package/dist/bundle/cli.js +1 -1
  6. package/dist/bundle/index.js +1 -1
  7. package/dist/bundle/rpc-entry.js +1 -1
  8. package/dist/config.d.ts +1 -1
  9. package/dist/config.d.ts.map +1 -1
  10. package/dist/config.js.map +1 -1
  11. package/dist/core/agent-session-runtime.d.ts +9 -0
  12. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  13. package/dist/core/agent-session-runtime.js +158 -157
  14. package/dist/core/agent-session-runtime.js.map +1 -1
  15. package/dist/core/agent-session.d.ts +14 -0
  16. package/dist/core/agent-session.d.ts.map +1 -1
  17. package/dist/core/agent-session.js +437 -434
  18. package/dist/core/agent-session.js.map +1 -1
  19. package/dist/core/background/types.d.ts +2 -0
  20. package/dist/core/background/types.d.ts.map +1 -1
  21. package/dist/core/background/types.js.map +1 -1
  22. package/dist/core/extensions/distribution-api.d.ts +41 -0
  23. package/dist/core/extensions/distribution-api.d.ts.map +1 -0
  24. package/dist/core/extensions/distribution-api.js +12 -0
  25. package/dist/core/extensions/distribution-api.js.map +1 -0
  26. package/dist/core/extensions/index.d.ts +1 -1
  27. package/dist/core/extensions/index.d.ts.map +1 -1
  28. package/dist/core/extensions/index.js.map +1 -1
  29. package/dist/core/extensions/loader.d.ts.map +1 -1
  30. package/dist/core/extensions/loader.js +1 -0
  31. package/dist/core/extensions/loader.js.map +1 -1
  32. package/dist/core/extensions/runner.d.ts.map +1 -1
  33. package/dist/core/extensions/runner.js +0 -2
  34. package/dist/core/extensions/runner.js.map +1 -1
  35. package/dist/core/extensions/types.d.ts +15 -45
  36. package/dist/core/extensions/types.d.ts.map +1 -1
  37. package/dist/core/extensions/types.js +1 -11
  38. package/dist/core/extensions/types.js.map +1 -1
  39. package/dist/core/extensions/virtual-modules.d.ts.map +1 -1
  40. package/dist/core/extensions/virtual-modules.js +2 -1
  41. package/dist/core/extensions/virtual-modules.js.map +1 -1
  42. package/dist/core/keybinding-registry.d.ts +11 -0
  43. package/dist/core/keybinding-registry.d.ts.map +1 -0
  44. package/dist/core/keybinding-registry.js +24 -0
  45. package/dist/core/keybinding-registry.js.map +1 -0
  46. package/dist/core/keybindings.d.ts +4 -64
  47. package/dist/core/keybindings.d.ts.map +1 -1
  48. package/dist/core/keybindings.js +2 -41
  49. package/dist/core/keybindings.js.map +1 -1
  50. package/dist/core/model-config.d.ts +70 -2
  51. package/dist/core/model-config.d.ts.map +1 -1
  52. package/dist/core/model-config.js +2 -26
  53. package/dist/core/model-config.js.map +1 -1
  54. package/dist/core/resource-loader.d.ts.map +1 -1
  55. package/dist/core/resource-loader.js +5 -11
  56. package/dist/core/resource-loader.js.map +1 -1
  57. package/dist/core/tools/bash.d.ts.map +1 -1
  58. package/dist/core/tools/bash.js +2 -2
  59. package/dist/core/tools/bash.js.map +1 -1
  60. package/dist/core/tools/shell-execution.d.ts +1 -0
  61. package/dist/core/tools/shell-execution.d.ts.map +1 -1
  62. package/dist/core/tools/shell-execution.js +21 -0
  63. package/dist/core/tools/shell-execution.js.map +1 -1
  64. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  65. package/dist/core/tools/tool-definition-wrapper.js +114 -1
  66. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  67. package/dist/core/trust-manager.d.ts.map +1 -1
  68. package/dist/core/trust-manager.js +11 -48
  69. package/dist/core/trust-manager.js.map +1 -1
  70. package/dist/extensions/background/actions.d.ts.map +1 -1
  71. package/dist/extensions/background/actions.js +107 -55
  72. package/dist/extensions/background/actions.js.map +1 -1
  73. package/dist/extensions/background/index.d.ts +2 -0
  74. package/dist/extensions/background/index.d.ts.map +1 -1
  75. package/dist/extensions/background/index.js +20 -3
  76. package/dist/extensions/background/index.js.map +1 -1
  77. package/dist/extensions/background/keybindings.d.ts +10 -0
  78. package/dist/extensions/background/keybindings.d.ts.map +1 -0
  79. package/dist/extensions/background/keybindings.js +14 -0
  80. package/dist/extensions/background/keybindings.js.map +1 -0
  81. package/dist/extensions/background/manager.d.ts +2 -0
  82. package/dist/extensions/background/manager.d.ts.map +1 -1
  83. package/dist/extensions/background/manager.js +1 -0
  84. package/dist/extensions/background/manager.js.map +1 -1
  85. package/dist/extensions/btw/controller.d.ts +1 -0
  86. package/dist/extensions/btw/controller.d.ts.map +1 -1
  87. package/dist/extensions/btw/controller.js +5 -5
  88. package/dist/extensions/btw/controller.js.map +1 -1
  89. package/dist/extensions/btw/index.js +1 -2
  90. package/dist/extensions/btw/index.js.map +1 -1
  91. package/dist/extensions/btw/keybindings.d.ts +10 -0
  92. package/dist/extensions/btw/keybindings.d.ts.map +1 -0
  93. package/dist/extensions/btw/keybindings.js +8 -0
  94. package/dist/extensions/btw/keybindings.js.map +1 -0
  95. package/dist/extensions/btw/panel.d.ts +1 -0
  96. package/dist/extensions/btw/panel.d.ts.map +1 -1
  97. package/dist/extensions/btw/panel.js +1 -0
  98. package/dist/extensions/btw/panel.js.map +1 -1
  99. package/dist/extensions/list-keybindings.d.ts +8 -0
  100. package/dist/extensions/list-keybindings.d.ts.map +1 -0
  101. package/dist/extensions/list-keybindings.js +8 -0
  102. package/dist/extensions/list-keybindings.js.map +1 -0
  103. package/dist/extensions/provider/compat-fields.d.ts.map +1 -1
  104. package/dist/extensions/provider/compat-fields.js +23 -3
  105. package/dist/extensions/provider/compat-fields.js.map +1 -1
  106. package/dist/extensions/provider/connection.d.ts.map +1 -1
  107. package/dist/extensions/provider/connection.js +1 -1
  108. package/dist/extensions/provider/connection.js.map +1 -1
  109. package/dist/extensions/provider/constants.d.ts +2 -0
  110. package/dist/extensions/provider/constants.d.ts.map +1 -1
  111. package/dist/extensions/provider/constants.js +2 -0
  112. package/dist/extensions/provider/constants.js.map +1 -1
  113. package/dist/extensions/provider/keybindings.d.ts +9 -0
  114. package/dist/extensions/provider/keybindings.d.ts.map +1 -0
  115. package/dist/extensions/provider/keybindings.js +12 -0
  116. package/dist/extensions/provider/keybindings.js.map +1 -0
  117. package/dist/extensions/provider/probe.d.ts +8 -4
  118. package/dist/extensions/provider/probe.d.ts.map +1 -1
  119. package/dist/extensions/provider/probe.js +169 -11
  120. package/dist/extensions/provider/probe.js.map +1 -1
  121. package/dist/extensions/provider/ui/builtin-data.d.ts +1 -0
  122. package/dist/extensions/provider/ui/builtin-data.d.ts.map +1 -1
  123. package/dist/extensions/provider/ui/builtin-data.js +1 -0
  124. package/dist/extensions/provider/ui/builtin-data.js.map +1 -1
  125. package/dist/extensions/provider/ui/compat.d.ts +1 -0
  126. package/dist/extensions/provider/ui/compat.d.ts.map +1 -1
  127. package/dist/extensions/provider/ui/compat.js +1 -0
  128. package/dist/extensions/provider/ui/compat.js.map +1 -1
  129. package/dist/extensions/provider/ui/dictionary.d.ts +1 -0
  130. package/dist/extensions/provider/ui/dictionary.d.ts.map +1 -1
  131. package/dist/extensions/provider/ui/dictionary.js +1 -0
  132. package/dist/extensions/provider/ui/dictionary.js.map +1 -1
  133. package/dist/extensions/provider/ui/editor.d.ts +1 -0
  134. package/dist/extensions/provider/ui/editor.d.ts.map +1 -1
  135. package/dist/extensions/provider/ui/editor.js +1 -0
  136. package/dist/extensions/provider/ui/editor.js.map +1 -1
  137. package/dist/extensions/provider/ui/fetch-models.d.ts +5 -3
  138. package/dist/extensions/provider/ui/fetch-models.d.ts.map +1 -1
  139. package/dist/extensions/provider/ui/fetch-models.js +28 -3
  140. package/dist/extensions/provider/ui/fetch-models.js.map +1 -1
  141. package/dist/extensions/provider/ui/model-fields.d.ts +1 -0
  142. package/dist/extensions/provider/ui/model-fields.d.ts.map +1 -1
  143. package/dist/extensions/provider/ui/model-fields.js +1 -0
  144. package/dist/extensions/provider/ui/model-fields.js.map +1 -1
  145. package/dist/extensions/provider/ui/model-options.d.ts +1 -0
  146. package/dist/extensions/provider/ui/model-options.d.ts.map +1 -1
  147. package/dist/extensions/provider/ui/model-options.js +1 -0
  148. package/dist/extensions/provider/ui/model-options.js.map +1 -1
  149. package/dist/extensions/provider/ui/provider-fields.d.ts +1 -0
  150. package/dist/extensions/provider/ui/provider-fields.d.ts.map +1 -1
  151. package/dist/extensions/provider/ui/provider-fields.js +1 -0
  152. package/dist/extensions/provider/ui/provider-fields.js.map +1 -1
  153. package/dist/extensions/question/dialog.d.ts +1 -0
  154. package/dist/extensions/question/dialog.d.ts.map +1 -1
  155. package/dist/extensions/question/dialog.js +1 -0
  156. package/dist/extensions/question/dialog.js.map +1 -1
  157. package/dist/extensions/question/index.d.ts.map +1 -1
  158. package/dist/extensions/question/index.js +3 -1
  159. package/dist/extensions/question/index.js.map +1 -1
  160. package/dist/extensions/question/keybindings.d.ts +2 -0
  161. package/dist/extensions/question/keybindings.d.ts.map +1 -0
  162. package/dist/extensions/question/keybindings.js +3 -0
  163. package/dist/extensions/question/keybindings.js.map +1 -0
  164. package/dist/extensions/question/schema.d.ts +18 -0
  165. package/dist/extensions/question/schema.d.ts.map +1 -1
  166. package/dist/extensions/question/schema.js +52 -0
  167. package/dist/extensions/question/schema.js.map +1 -1
  168. package/dist/extensions/subagent/index.d.ts.map +1 -1
  169. package/dist/extensions/subagent/index.js +3 -1
  170. package/dist/extensions/subagent/index.js.map +1 -1
  171. package/dist/extensions/subagent/resolve.d.ts +14 -1
  172. package/dist/extensions/subagent/resolve.d.ts.map +1 -1
  173. package/dist/extensions/subagent/resolve.js +32 -18
  174. package/dist/extensions/subagent/resolve.js.map +1 -1
  175. package/dist/extensions/subagent/runner.d.ts.map +1 -1
  176. package/dist/extensions/subagent/runner.js +4 -1
  177. package/dist/extensions/subagent/runner.js.map +1 -1
  178. package/dist/extensions/subagent/schema.d.ts +9 -0
  179. package/dist/extensions/subagent/schema.d.ts.map +1 -1
  180. package/dist/extensions/subagent/schema.js +30 -1
  181. package/dist/extensions/subagent/schema.js.map +1 -1
  182. package/dist/extensions/subagent/types.d.ts +2 -0
  183. package/dist/extensions/subagent/types.d.ts.map +1 -1
  184. package/dist/extensions/subagent/types.js.map +1 -1
  185. package/dist/extensions/todo/constants.d.ts +1 -1
  186. package/dist/extensions/todo/constants.d.ts.map +1 -1
  187. package/dist/extensions/todo/constants.js +8 -8
  188. package/dist/extensions/todo/constants.js.map +1 -1
  189. package/dist/extensions/todo/index.d.ts.map +1 -1
  190. package/dist/extensions/todo/index.js +1 -0
  191. package/dist/extensions/todo/index.js.map +1 -1
  192. package/dist/extensions/todo/schema.d.ts +30 -25
  193. package/dist/extensions/todo/schema.d.ts.map +1 -1
  194. package/dist/extensions/todo/schema.js +26 -20
  195. package/dist/extensions/todo/schema.js.map +1 -1
  196. package/dist/extensions/todo/state.d.ts +12 -8
  197. package/dist/extensions/todo/state.d.ts.map +1 -1
  198. package/dist/extensions/todo/state.js +255 -171
  199. package/dist/extensions/todo/state.js.map +1 -1
  200. package/dist/extensions/todo/view.d.ts +6 -2
  201. package/dist/extensions/todo/view.d.ts.map +1 -1
  202. package/dist/extensions/todo/view.js +234 -144
  203. package/dist/extensions/todo/view.js.map +1 -1
  204. package/dist/index.d.ts +1 -1
  205. package/dist/index.d.ts.map +1 -1
  206. package/dist/index.js.map +1 -1
  207. package/dist/modes/interactive/editor-host.d.ts +43 -0
  208. package/dist/modes/interactive/editor-host.d.ts.map +1 -0
  209. package/dist/modes/interactive/editor-host.js +83 -0
  210. package/dist/modes/interactive/editor-host.js.map +1 -0
  211. package/dist/modes/interactive/interactive-mode.d.ts +1 -11
  212. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  213. package/dist/modes/interactive/interactive-mode.js +300 -360
  214. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  215. package/dist/modes/interactive/tool-chat.d.ts +21 -0
  216. package/dist/modes/interactive/tool-chat.d.ts.map +1 -0
  217. package/dist/modes/interactive/tool-chat.js +58 -0
  218. package/dist/modes/interactive/tool-chat.js.map +1 -0
  219. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  220. package/dist/modes/rpc/rpc-mode.js +0 -2
  221. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  222. package/dist/utils/path-identity.d.ts +12 -0
  223. package/dist/utils/path-identity.d.ts.map +1 -0
  224. package/dist/utils/path-identity.js +63 -0
  225. package/dist/utils/path-identity.js.map +1 -0
  226. package/dist/utils/shell.d.ts +0 -1
  227. package/dist/utils/shell.d.ts.map +1 -1
  228. package/dist/utils/shell.js +0 -21
  229. package/dist/utils/shell.js.map +1 -1
  230. package/dist/utils/syntax-highlight.d.ts +2 -0
  231. package/dist/utils/syntax-highlight.d.ts.map +1 -1
  232. package/dist/utils/syntax-highlight.js +2 -0
  233. package/dist/utils/syntax-highlight.js.map +1 -1
  234. package/docs/bundled/extensions/background.md +1 -1
  235. package/docs/bundled/extensions/provider.md +7 -3
  236. package/docs/bundled/extensions/question.md +1 -0
  237. package/docs/bundled/extensions/subagent.md +3 -1
  238. package/docs/bundled/extensions/todo.md +35 -23
  239. package/docs/extensions.md +17 -5
  240. package/examples/extensions/README.md +34 -14
  241. package/examples/extensions/auto-commit-on-exit.ts +1 -1
  242. package/examples/extensions/bash-spawn-hook.ts +2 -2
  243. package/examples/extensions/bookmark.ts +1 -1
  244. package/examples/extensions/border-status-editor.ts +6 -1
  245. package/examples/extensions/built-in-tool-renderer.ts +2 -2
  246. package/examples/extensions/claude-rules.ts +1 -1
  247. package/examples/extensions/commands.ts +1 -1
  248. package/examples/extensions/confirm-destructive.ts +1 -1
  249. package/examples/extensions/custom-compaction.ts +2 -2
  250. package/examples/extensions/custom-footer.ts +1 -1
  251. package/examples/extensions/custom-header.ts +2 -2
  252. package/examples/extensions/custom-provider-anthropic/index.ts +6 -6
  253. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  254. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  255. package/examples/extensions/custom-provider-gitlab-duo/index.ts +3 -3
  256. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  257. package/examples/extensions/dirty-repo-guard.ts +1 -1
  258. package/examples/extensions/doom-overlay/index.ts +1 -1
  259. package/examples/extensions/dynamic-resources/dynamic.json +3 -1
  260. package/examples/extensions/dynamic-resources/index.ts +1 -1
  261. package/examples/extensions/dynamic-tools.ts +1 -1
  262. package/examples/extensions/entry-renderer.ts +1 -1
  263. package/examples/extensions/event-bus.ts +1 -1
  264. package/examples/extensions/file-trigger.ts +1 -1
  265. package/examples/extensions/git-checkpoint.ts +1 -1
  266. package/examples/extensions/git-merge-and-resolve.ts +1 -1
  267. package/examples/extensions/github-issue-autocomplete.ts +1 -1
  268. package/examples/extensions/gondolin/index.ts +9 -7
  269. package/examples/extensions/gondolin/package-lock.json +2 -2
  270. package/examples/extensions/gondolin/package.json +1 -1
  271. package/examples/extensions/handoff.ts +2 -2
  272. package/examples/extensions/hello.ts +1 -1
  273. package/examples/extensions/hidden-thinking-label.ts +1 -1
  274. package/examples/extensions/inline-bash.ts +1 -1
  275. package/examples/extensions/input-transform-streaming.ts +1 -1
  276. package/examples/extensions/input-transform.ts +1 -1
  277. package/examples/extensions/interactive-shell.ts +1 -1
  278. package/examples/extensions/mac-system-theme.ts +1 -1
  279. package/examples/extensions/message-renderer.ts +1 -1
  280. package/examples/extensions/minimal-mode.ts +2 -2
  281. package/examples/extensions/modal-editor.ts +1 -1
  282. package/examples/extensions/model-status.ts +1 -1
  283. package/examples/extensions/notify.ts +1 -1
  284. package/examples/extensions/overlay-qa-tests.ts +3 -3
  285. package/examples/extensions/overlay-test.ts +1 -1
  286. package/examples/extensions/permission-gate.ts +1 -1
  287. package/examples/extensions/pirate.ts +1 -1
  288. package/examples/extensions/plan-mode/index.ts +1 -1
  289. package/examples/extensions/preset.ts +2 -2
  290. package/examples/extensions/project-trust.ts +3 -3
  291. package/examples/extensions/prompt-customizer.ts +1 -1
  292. package/examples/extensions/protected-paths.ts +1 -1
  293. package/examples/extensions/provider-payload.ts +1 -1
  294. package/examples/extensions/qna.ts +2 -2
  295. package/examples/extensions/question.ts +1 -1
  296. package/examples/extensions/questionnaire.ts +1 -1
  297. package/examples/extensions/rainbow-editor.ts +1 -1
  298. package/examples/extensions/reload-runtime.ts +1 -1
  299. package/examples/extensions/rpc-demo.ts +1 -1
  300. package/examples/extensions/sandbox/index.ts +2 -2
  301. package/examples/extensions/sandbox/package-lock.json +2 -2
  302. package/examples/extensions/sandbox/package.json +1 -1
  303. package/examples/extensions/send-user-message.ts +1 -1
  304. package/examples/extensions/session-name.ts +1 -1
  305. package/examples/extensions/shutdown-command.ts +1 -1
  306. package/examples/extensions/snake.ts +1 -1
  307. package/examples/extensions/space-invaders.ts +1 -1
  308. package/examples/extensions/ssh.ts +2 -2
  309. package/examples/extensions/status-line.ts +1 -1
  310. package/examples/extensions/structured-output.ts +1 -1
  311. package/examples/extensions/summarize.ts +2 -3
  312. package/examples/extensions/system-prompt-header.ts +1 -1
  313. package/examples/extensions/tic-tac-toe.ts +1 -1
  314. package/examples/extensions/timed-confirm.ts +1 -1
  315. package/examples/extensions/titlebar-spinner.ts +1 -1
  316. package/examples/extensions/todo.ts +1 -1
  317. package/examples/extensions/tool-override.ts +1 -1
  318. package/examples/extensions/tools.ts +2 -2
  319. package/examples/extensions/trigger-compact.ts +1 -1
  320. package/examples/extensions/truncated-tool.ts +4 -4
  321. package/examples/extensions/widget-placement.ts +1 -1
  322. package/examples/extensions/with-deps/index.ts +1 -1
  323. package/examples/extensions/with-deps/package-lock.json +2 -2
  324. package/examples/extensions/with-deps/package.json +1 -1
  325. package/examples/extensions/working-indicator.ts +1 -1
  326. package/examples/extensions/working-message-test.ts +1 -1
  327. package/npm-shrinkwrap.json +2 -2
  328. package/package.json +1 -1
@@ -251,6 +251,19 @@ export class AgentSession {
251
251
  retryBackgroundNotifications() {
252
252
  this._backgroundHost.retry();
253
253
  }
254
+ /**
255
+ * Run a lifecycle operation with delivery paused across its asynchronous gaps.
256
+ * Lifecycle methods are thin shells over upstream bodies so those keep upstream's shape.
257
+ */
258
+ async _withBackgroundPaused(operation) {
259
+ const resumeBackground = this.pauseBackgroundNotifications();
260
+ try {
261
+ return await operation();
262
+ }
263
+ finally {
264
+ resumeBackground();
265
+ }
266
+ }
254
267
  get modelRuntime() {
255
268
  return this._modelRuntime;
256
269
  }
@@ -643,28 +656,36 @@ export class AgentSession {
643
656
  this._resolveIdleWait = undefined;
644
657
  resolve();
645
658
  }
659
+ /**
660
+ * Background completions stay paused until runs requested by settled handlers have started,
661
+ * so a deferred prompt never races a delivery for the same idle gap.
662
+ */
646
663
  async _emitAgentSettled() {
664
+ return this._withBackgroundPaused(() => this._emitAgentSettledBody());
665
+ }
666
+ async _emitAgentSettledBody() {
647
667
  this._cacheWarmer?.onAgentSettled();
648
- // Background completions stay paused until runs requested by settled handlers have started,
649
- // so a deferred prompt never races a delivery for the same idle gap.
650
- const resumeBackground = this.pauseBackgroundNotifications();
651
668
  this._isAgentRunActive = false;
669
+ this._isEmittingAgentSettled = true;
652
670
  try {
653
- this._isEmittingAgentSettled = true;
671
+ await this._extensionRunner.emit({ type: "agent_settled" });
672
+ this._emit({ type: "agent_settled" });
673
+ }
674
+ finally {
675
+ this._isEmittingAgentSettled = false;
676
+ }
677
+ const deferred = this._deferredSettledActions.splice(0);
678
+ if (deferred.length > 0) {
654
679
  try {
655
- await this._extensionRunner.emit({ type: "agent_settled" });
656
- this._emit({ type: "agent_settled" });
680
+ for (const action of deferred)
681
+ await action();
657
682
  }
658
683
  finally {
659
- this._isEmittingAgentSettled = false;
684
+ this._resolveIdleWaitIfIdle();
660
685
  }
661
- for (const action of this._deferredSettledActions.splice(0))
662
- await action();
663
- }
664
- finally {
665
- this._resolveIdleWaitIfIdle();
666
- resumeBackground();
686
+ return;
667
687
  }
688
+ this._resolveIdleWaitIfIdle();
668
689
  }
669
690
  /**
670
691
  * Persist model/custom messages and consume queued context only after the append succeeds.
@@ -1334,138 +1355,132 @@ export class AgentSession {
1334
1355
  this.retryBackgroundNotifications();
1335
1356
  const expandPromptTemplates = options?.expandPromptTemplates ?? true;
1336
1357
  const preflightResult = options?.preflightResult;
1337
- // Observer commands may stay open indefinitely. Their lifecycle operations
1338
- // pause independently; only real model input owns the preflight pause.
1339
- if (expandPromptTemplates && text.startsWith("/")) {
1340
- try {
1341
- if (await this._tryExecuteExtensionCommand(text)) {
1358
+ let messages;
1359
+ let resumeBackground;
1360
+ try {
1361
+ // Handle extension commands first (execute immediately, even during streaming)
1362
+ // Extension commands manage their own LLM interaction via pi.sendMessage()
1363
+ if (expandPromptTemplates && text.startsWith("/")) {
1364
+ const handled = await this._tryExecuteExtensionCommand(text);
1365
+ if (handled) {
1366
+ // Extension command executed, no prompt to send
1342
1367
  preflightResult?.(true);
1343
1368
  return;
1344
1369
  }
1345
1370
  }
1346
- catch (error) {
1347
- preflightResult?.(false);
1348
- throw error;
1371
+ // Observer commands above may stay open indefinitely; only real model input owns
1372
+ // the preflight pause, released once the user message is established.
1373
+ resumeBackground = this.pauseBackgroundNotifications();
1374
+ if (this._compactionAbortController !== undefined) {
1375
+ throw new Error("Cannot submit a prompt while compaction is in progress. Wait for compaction to finish and retry.");
1349
1376
  }
1350
- }
1351
- const resumeBackground = this.pauseBackgroundNotifications();
1352
- try {
1353
- let messages;
1354
- try {
1355
- if (this._compactionAbortController !== undefined) {
1356
- throw new Error("Cannot submit a prompt while compaction is in progress. Wait for compaction to finish and retry.");
1357
- }
1358
- // Emit input event for extension interception (before skill/template expansion)
1359
- const processedInput = await this._runInputHandlers(text, options?.images, options?.source ?? "interactive", this.isStreaming ? options?.streamingBehavior : undefined);
1360
- if (!processedInput) {
1361
- preflightResult?.(true);
1362
- return;
1363
- }
1364
- const { text: currentText, images: currentImages } = processedInput;
1365
- // Expand skill commands (/skill:name args) and prompt templates (/template args)
1366
- let expandedText = currentText;
1367
- if (expandPromptTemplates) {
1368
- expandedText = this._expandSkillCommand(expandedText);
1369
- expandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);
1370
- }
1371
- // If streaming, queue via steer() or followUp() based on option
1372
- if (this.isStreaming) {
1373
- if (!options?.streamingBehavior) {
1374
- throw new Error("Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.");
1375
- }
1376
- if (options.streamingBehavior === "followUp") {
1377
- await this._queueFollowUp(expandedText, currentImages);
1378
- }
1379
- else {
1380
- await this._queueSteer(expandedText, currentImages);
1381
- }
1382
- preflightResult?.(true);
1383
- return;
1377
+ // Emit input event for extension interception (before skill/template expansion)
1378
+ const processedInput = await this._runInputHandlers(text, options?.images, options?.source ?? "interactive", this.isStreaming ? options?.streamingBehavior : undefined);
1379
+ if (!processedInput) {
1380
+ preflightResult?.(true);
1381
+ return;
1382
+ }
1383
+ const { text: currentText, images: currentImages } = processedInput;
1384
+ // Expand skill commands (/skill:name args) and prompt templates (/template args)
1385
+ let expandedText = currentText;
1386
+ if (expandPromptTemplates) {
1387
+ expandedText = this._expandSkillCommand(expandedText);
1388
+ expandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);
1389
+ }
1390
+ // If streaming, queue via steer() or followUp() based on option
1391
+ if (this.isStreaming) {
1392
+ if (!options?.streamingBehavior) {
1393
+ throw new Error("Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.");
1384
1394
  }
1385
- // Flush any pending bash and custom messages before the new prompt
1386
- this._flushPendingBashMessages();
1387
- this._flushPendingCustomMessages();
1388
- // Validate model
1389
- if (!this.model) {
1390
- throw new Error(formatNoModelSelectedMessage());
1395
+ if (options.streamingBehavior === "followUp") {
1396
+ await this._queueFollowUp(expandedText, currentImages);
1391
1397
  }
1392
- const hasConfiguredAuth = this._modelRuntime.hasConfiguredAuth(this.model.provider) ||
1393
- (await this._modelRuntime.checkAuth(this.model.provider)) !== undefined;
1394
- if (!hasConfiguredAuth) {
1395
- const isOAuth = this._modelRuntime.isUsingOAuth(this.model.provider);
1396
- if (isOAuth) {
1397
- throw new Error(`Authentication failed for "${this.model.provider}". ` +
1398
- `Credentials may have expired or network is unavailable. ` +
1399
- `Run '/login ${this.model.provider}' to re-authenticate.`);
1400
- }
1401
- throw new Error(formatNoApiKeyFoundMessage(this.model.provider));
1398
+ else {
1399
+ await this._queueSteer(expandedText, currentImages);
1402
1400
  }
1403
- // Check if we need to compact before sending (catches aborted responses).
1404
- // The user's new prompt is sent below, so do not call agent.continue() here.
1405
- const lastAssistant = this._findLastAssistantMessage();
1406
- if (lastAssistant) {
1407
- await this._checkCompaction(lastAssistant, false);
1401
+ preflightResult?.(true);
1402
+ return;
1403
+ }
1404
+ // Flush any pending bash and custom messages before the new prompt
1405
+ this._flushPendingBashMessages();
1406
+ this._flushPendingCustomMessages();
1407
+ // Validate model
1408
+ if (!this.model) {
1409
+ throw new Error(formatNoModelSelectedMessage());
1410
+ }
1411
+ const hasConfiguredAuth = this._modelRuntime.hasConfiguredAuth(this.model.provider) ||
1412
+ (await this._modelRuntime.checkAuth(this.model.provider)) !== undefined;
1413
+ if (!hasConfiguredAuth) {
1414
+ const isOAuth = this._modelRuntime.isUsingOAuth(this.model.provider);
1415
+ if (isOAuth) {
1416
+ throw new Error(`Authentication failed for "${this.model.provider}". ` +
1417
+ `Credentials may have expired or network is unavailable. ` +
1418
+ `Run '/login ${this.model.provider}' to re-authenticate.`);
1408
1419
  }
1409
- // Emit before_agent_start before normalizing images so extension-driven model
1410
- // selection determines the resize profile used for the request and history.
1411
- const selectedToolsBefore = this._baseSystemPromptOptions.selectedTools;
1412
- const result = await this._extensionRunner.emitBeforeAgentStart(expandedText, currentImages, this._baseSystemPromptOptions);
1413
- // Handlers may edit event.systemPromptOptions.selectedTools or call setActiveTools(),
1414
- // which updates the live loadout instead. An explicit edit wins; otherwise the live
1415
- // loadout is authoritative, so a setActiveTools() call is not undone here.
1416
- const handlerEditedTools = result.systemPromptOptions.selectedTools.length !== selectedToolsBefore.length ||
1417
- result.systemPromptOptions.selectedTools.some((name, index) => name !== selectedToolsBefore[index]);
1418
- if (!handlerEditedTools)
1419
- result.systemPromptOptions.selectedTools = this.getActiveToolNames();
1420
- const normalized = await this._normalizePromptImages(currentImages);
1421
- const userText = normalized.hints.length > 0 ? `${expandedText}\n\n${normalized.hints.join("\n")}` : expandedText;
1422
- // Build messages only after hooks and image normalization have completed.
1423
- messages = [];
1424
- const userContent = [{ type: "text", text: userText }];
1425
- userContent.push(...normalized.images);
1420
+ throw new Error(formatNoApiKeyFoundMessage(this.model.provider));
1421
+ }
1422
+ // Check if we need to compact before sending (catches aborted responses).
1423
+ // The user's new prompt is sent below, so do not call agent.continue() here.
1424
+ const lastAssistant = this._findLastAssistantMessage();
1425
+ if (lastAssistant) {
1426
+ await this._checkCompaction(lastAssistant, false);
1427
+ }
1428
+ // Emit before_agent_start before normalizing images so extension-driven model
1429
+ // selection determines the resize profile used for the request and history.
1430
+ const selectedToolsBefore = this._baseSystemPromptOptions.selectedTools;
1431
+ const result = await this._extensionRunner.emitBeforeAgentStart(expandedText, currentImages, this._baseSystemPromptOptions);
1432
+ // Handlers may edit event.systemPromptOptions.selectedTools or call setActiveTools(),
1433
+ // which updates the live loadout instead. An explicit edit wins; otherwise the live
1434
+ // loadout is authoritative, so a setActiveTools() call is not undone here.
1435
+ const handlerEditedTools = result.systemPromptOptions.selectedTools.length !== selectedToolsBefore.length ||
1436
+ result.systemPromptOptions.selectedTools.some((name, index) => name !== selectedToolsBefore[index]);
1437
+ if (!handlerEditedTools)
1438
+ result.systemPromptOptions.selectedTools = this.getActiveToolNames();
1439
+ const normalized = await this._normalizePromptImages(currentImages);
1440
+ const userText = normalized.hints.length > 0 ? `${expandedText}\n\n${normalized.hints.join("\n")}` : expandedText;
1441
+ // Build messages only after hooks and image normalization have completed.
1442
+ messages = [];
1443
+ const userContent = [{ type: "text", text: userText }];
1444
+ userContent.push(...normalized.images);
1445
+ messages.push({
1446
+ role: "user",
1447
+ content: userContent,
1448
+ timestamp: Date.now(),
1449
+ });
1450
+ // Inject any pending "nextTurn" messages as context alongside the user message.
1451
+ // They stay queued until _persistMessage() confirms the append.
1452
+ for (const msg of this._pendingNextTurnMessages) {
1453
+ messages.push(msg);
1454
+ }
1455
+ for (const msg of result.messages) {
1426
1456
  messages.push({
1427
- role: "user",
1428
- content: userContent,
1457
+ role: "custom",
1458
+ customType: msg.customType,
1459
+ // Untyped extensions can pass null/missing content; normalize at ingestion.
1460
+ content: msg.content ?? [],
1461
+ display: msg.display,
1462
+ details: msg.details,
1429
1463
  timestamp: Date.now(),
1430
1464
  });
1431
- // Inject any pending "nextTurn" messages as context alongside the user message.
1432
- // They stay queued until _persistMessage() confirms the append.
1433
- for (const msg of this._pendingNextTurnMessages) {
1434
- messages.push(msg);
1435
- }
1436
- for (const msg of result.messages) {
1437
- messages.push({
1438
- role: "custom",
1439
- customType: msg.customType,
1440
- // Untyped extensions can pass null/missing content; normalize at ingestion.
1441
- content: msg.content ?? [],
1442
- display: msg.display,
1443
- details: msg.details,
1444
- timestamp: Date.now(),
1445
- });
1446
- }
1447
- const updateMessage = this._preparePromptAndToolLoadout(result.systemPromptOptions);
1448
- this._runSystemPromptOptions = result.systemPromptOptions;
1449
- if (updateMessage)
1450
- messages.unshift(updateMessage);
1451
- }
1452
- catch (error) {
1453
- preflightResult?.(false);
1454
- throw error;
1455
1465
  }
1456
- if (!messages) {
1457
- return;
1458
- }
1459
- preflightResult?.(true);
1460
- // The user's message is now established, so a completion may join this run through the
1461
- // steering queue instead of waiting for the turn to end. Everything above still holds
1462
- // the pause, so no delivery can race input hooks, model validation or compaction.
1463
- resumeBackground();
1464
- await this._runAgentPrompt(messages);
1466
+ const updateMessage = this._preparePromptAndToolLoadout(result.systemPromptOptions);
1467
+ this._runSystemPromptOptions = result.systemPromptOptions;
1468
+ if (updateMessage)
1469
+ messages.unshift(updateMessage);
1470
+ }
1471
+ catch (error) {
1472
+ preflightResult?.(false);
1473
+ throw error;
1465
1474
  }
1466
1475
  finally {
1467
- resumeBackground();
1476
+ // Delivery is timer-scheduled, so a completion cannot start before the run below does.
1477
+ resumeBackground?.();
1468
1478
  }
1479
+ if (!messages) {
1480
+ return;
1481
+ }
1482
+ preflightResult?.(true);
1483
+ await this._runAgentPrompt(messages);
1469
1484
  }
1470
1485
  /**
1471
1486
  * Try to execute an extension command. Returns true if command was found and executed.
@@ -2005,137 +2020,134 @@ export class AgentSession {
2005
2020
  * @param customInstructions Optional instructions for the compaction summary
2006
2021
  */
2007
2022
  async compact(customInstructions) {
2008
- const resumeBackground = this.pauseBackgroundNotifications();
2023
+ return this._withBackgroundPaused(() => this._compactBody(customInstructions));
2024
+ }
2025
+ async _compactBody(customInstructions) {
2026
+ await this.abort();
2027
+ this._compactionAbortController = new AbortController();
2028
+ this._emit({ type: "compaction_start", reason: "manual" });
2029
+ let fromExtension = false;
2030
+ let cancelledByExtension = false;
2009
2031
  try {
2010
- await this.abort();
2011
- this._compactionAbortController = new AbortController();
2012
- this._emit({ type: "compaction_start", reason: "manual" });
2013
- let fromExtension = false;
2014
- let cancelledByExtension = false;
2015
- try {
2016
- const model = this.model;
2017
- if (!model) {
2018
- throw new Error(formatNoModelSelectedMessage());
2019
- }
2020
- const settings = this.settingsManager.getCompactionSettings(model);
2021
- const { model: requestModel, apiKey, headers, env, } = await this._getSummarizationRequestAuth(model, this._compactionAbortController.signal);
2022
- const pathEntries = this.sessionManager.getBranch();
2023
- const preparation = prepareCompaction(pathEntries, settings);
2024
- if (!preparation) {
2025
- // Check why we can't compact
2026
- const lastEntry = pathEntries[pathEntries.length - 1];
2027
- if (lastEntry?.type === "compaction") {
2028
- throw new Error("Already compacted");
2029
- }
2030
- throw new Error("Nothing to compact (session too small)");
2031
- }
2032
- let extensionCompaction;
2033
- if (this._extensionRunner.hasHandlers("session_before_compact")) {
2034
- const result = (await this._extensionRunner.emit({
2035
- type: "session_before_compact",
2036
- preparation,
2037
- branchEntries: pathEntries,
2038
- customInstructions,
2039
- reason: "manual",
2040
- willRetry: false,
2041
- signal: this._compactionAbortController.signal,
2042
- }));
2043
- if (result?.cancel) {
2044
- cancelledByExtension = true;
2045
- throw new Error("Compaction cancelled");
2046
- }
2047
- if (result?.compaction) {
2048
- extensionCompaction = result.compaction;
2049
- fromExtension = true;
2050
- }
2051
- }
2052
- let summary;
2053
- let firstKeptEntryId;
2054
- let tokensBefore;
2055
- let usage;
2056
- let details;
2057
- if (extensionCompaction) {
2058
- // Extension provided compaction content
2059
- summary = extensionCompaction.summary;
2060
- firstKeptEntryId = extensionCompaction.firstKeptEntryId;
2061
- tokensBefore = extensionCompaction.tokensBefore;
2062
- usage = extensionCompaction.usage;
2063
- details = extensionCompaction.details;
2064
- }
2065
- else {
2066
- // Shared default summary generator, also used by automatic compaction.
2067
- const result = await this._runDefaultCompaction(preparation, requestModel, apiKey, headers, customInstructions, this._compactionAbortController.signal, env, "manual");
2068
- summary = result.summary;
2069
- firstKeptEntryId = result.firstKeptEntryId;
2070
- tokensBefore = result.tokensBefore;
2071
- usage = result.usage;
2072
- details = result.details;
2032
+ const model = this.model;
2033
+ if (!model) {
2034
+ throw new Error(formatNoModelSelectedMessage());
2035
+ }
2036
+ const settings = this.settingsManager.getCompactionSettings(model);
2037
+ const { model: requestModel, apiKey, headers, env, } = await this._getSummarizationRequestAuth(model, this._compactionAbortController.signal);
2038
+ const pathEntries = this.sessionManager.getBranch();
2039
+ const preparation = prepareCompaction(pathEntries, settings);
2040
+ if (!preparation) {
2041
+ // Check why we can't compact
2042
+ const lastEntry = pathEntries[pathEntries.length - 1];
2043
+ if (lastEntry?.type === "compaction") {
2044
+ throw new Error("Already compacted");
2073
2045
  }
2074
- if (this._compactionAbortController.signal.aborted) {
2046
+ throw new Error("Nothing to compact (session too small)");
2047
+ }
2048
+ let extensionCompaction;
2049
+ if (this._extensionRunner.hasHandlers("session_before_compact")) {
2050
+ const result = (await this._extensionRunner.emit({
2051
+ type: "session_before_compact",
2052
+ preparation,
2053
+ branchEntries: pathEntries,
2054
+ customInstructions,
2055
+ reason: "manual",
2056
+ willRetry: false,
2057
+ signal: this._compactionAbortController.signal,
2058
+ }));
2059
+ if (result?.cancel) {
2060
+ cancelledByExtension = true;
2075
2061
  throw new Error("Compaction cancelled");
2076
2062
  }
2077
- this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension, usage);
2078
- const newEntries = this.sessionManager.getEntries();
2079
- this._refreshFinalizedContext();
2080
- const estimatedTokensAfter = estimateMessagesTokens(this.sessionManager.buildSessionProjection().messages);
2081
- // Get the saved compaction entry for the extension event
2082
- const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
2083
- if (this._extensionRunner && savedCompactionEntry) {
2084
- await this._extensionRunner.emit({
2085
- type: "session_compact",
2086
- compactionEntry: savedCompactionEntry,
2087
- fromExtension,
2088
- reason: "manual",
2089
- willRetry: false,
2090
- });
2063
+ if (result?.compaction) {
2064
+ extensionCompaction = result.compaction;
2065
+ fromExtension = true;
2091
2066
  }
2092
- const compactionResult = {
2093
- summary,
2094
- firstKeptEntryId,
2095
- tokensBefore,
2096
- estimatedTokensAfter,
2097
- usage,
2098
- details,
2099
- };
2100
- // compaction_end listeners may submit queued prompts, so expose idle state before notifying them.
2101
- this._clearManualCompactionState();
2102
- this._emit({
2103
- type: "compaction_end",
2104
- reason: "manual",
2105
- result: compactionResult,
2106
- aborted: false,
2107
- willRetry: false,
2108
- });
2109
- return compactionResult;
2110
2067
  }
2111
- catch (error) {
2112
- const message = error instanceof Error ? error.message : String(error);
2113
- const aborted = this._compactionAbortController?.signal.aborted === true || cancelledByExtension;
2114
- const errorMessage = aborted ? undefined : `Compaction failed: ${message}`;
2115
- this._clearManualCompactionState();
2116
- this._emit({
2117
- type: "compaction_end",
2118
- reason: "manual",
2119
- result: undefined,
2120
- aborted,
2121
- willRetry: false,
2122
- errorMessage,
2123
- });
2124
- await this._emitSessionCompactFailed({
2068
+ let summary;
2069
+ let firstKeptEntryId;
2070
+ let tokensBefore;
2071
+ let usage;
2072
+ let details;
2073
+ if (extensionCompaction) {
2074
+ // Extension provided compaction content
2075
+ summary = extensionCompaction.summary;
2076
+ firstKeptEntryId = extensionCompaction.firstKeptEntryId;
2077
+ tokensBefore = extensionCompaction.tokensBefore;
2078
+ usage = extensionCompaction.usage;
2079
+ details = extensionCompaction.details;
2080
+ }
2081
+ else {
2082
+ // Shared default summary generator, also used by automatic compaction.
2083
+ const result = await this._runDefaultCompaction(preparation, requestModel, apiKey, headers, customInstructions, this._compactionAbortController.signal, env, "manual");
2084
+ summary = result.summary;
2085
+ firstKeptEntryId = result.firstKeptEntryId;
2086
+ tokensBefore = result.tokensBefore;
2087
+ usage = result.usage;
2088
+ details = result.details;
2089
+ }
2090
+ if (this._compactionAbortController.signal.aborted) {
2091
+ throw new Error("Compaction cancelled");
2092
+ }
2093
+ this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension, usage);
2094
+ const newEntries = this.sessionManager.getEntries();
2095
+ this._refreshFinalizedContext();
2096
+ const estimatedTokensAfter = estimateMessagesTokens(this.sessionManager.buildSessionProjection().messages);
2097
+ // Get the saved compaction entry for the extension event
2098
+ const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
2099
+ if (this._extensionRunner && savedCompactionEntry) {
2100
+ await this._extensionRunner.emit({
2101
+ type: "session_compact",
2102
+ compactionEntry: savedCompactionEntry,
2103
+ fromExtension,
2125
2104
  reason: "manual",
2126
- errorMessage,
2127
- aborted,
2128
2105
  willRetry: false,
2129
- fromExtension,
2130
2106
  });
2131
- throw error;
2132
- }
2133
- finally {
2134
- this._clearManualCompactionState();
2135
2107
  }
2108
+ const compactionResult = {
2109
+ summary,
2110
+ firstKeptEntryId,
2111
+ tokensBefore,
2112
+ estimatedTokensAfter,
2113
+ usage,
2114
+ details,
2115
+ };
2116
+ // compaction_end listeners may submit queued prompts, so expose idle state before notifying them.
2117
+ this._clearManualCompactionState();
2118
+ this._emit({
2119
+ type: "compaction_end",
2120
+ reason: "manual",
2121
+ result: compactionResult,
2122
+ aborted: false,
2123
+ willRetry: false,
2124
+ });
2125
+ return compactionResult;
2126
+ }
2127
+ catch (error) {
2128
+ const message = error instanceof Error ? error.message : String(error);
2129
+ const aborted = this._compactionAbortController.signal.aborted || cancelledByExtension;
2130
+ const errorMessage = aborted ? undefined : `Compaction failed: ${message}`;
2131
+ this._clearManualCompactionState();
2132
+ this._emit({
2133
+ type: "compaction_end",
2134
+ reason: "manual",
2135
+ result: undefined,
2136
+ aborted,
2137
+ willRetry: false,
2138
+ errorMessage,
2139
+ });
2140
+ await this._emitSessionCompactFailed({
2141
+ reason: "manual",
2142
+ errorMessage,
2143
+ aborted,
2144
+ willRetry: false,
2145
+ fromExtension,
2146
+ });
2147
+ throw error;
2136
2148
  }
2137
2149
  finally {
2138
- resumeBackground();
2150
+ this._clearManualCompactionState();
2139
2151
  }
2140
2152
  }
2141
2153
  /**
@@ -2298,140 +2310,137 @@ export class AgentSession {
2298
2310
  * @returns Whether the post-run loop should call `agent.continue()`
2299
2311
  */
2300
2312
  async _runAutoCompaction(reason, willRetry) {
2301
- const resumeBackground = this.pauseBackgroundNotifications();
2313
+ return this._withBackgroundPaused(() => this._runAutoCompactionBody(reason, willRetry));
2314
+ }
2315
+ async _runAutoCompactionBody(reason, willRetry) {
2316
+ const model = this.model;
2317
+ const settings = this.settingsManager.getCompactionSettings(model);
2318
+ let abortController;
2319
+ let started = false;
2320
+ let fromExtension = false;
2321
+ let cancelledByExtension = false;
2302
2322
  try {
2303
- const model = this.model;
2304
- const settings = this.settingsManager.getCompactionSettings(model);
2305
- let abortController;
2306
- let started = false;
2307
- let fromExtension = false;
2308
- let cancelledByExtension = false;
2309
- try {
2310
- if (!model) {
2311
- return false;
2312
- }
2313
- const pathEntries = this.sessionManager.getBranch();
2314
- const preparation = prepareCompaction(pathEntries, settings);
2315
- if (!preparation) {
2316
- return false;
2317
- }
2318
- abortController = new AbortController();
2319
- this._autoCompactionAbortController = abortController;
2320
- started = true;
2321
- this._emit({ type: "compaction_start", reason });
2322
- abortController.signal.throwIfAborted();
2323
- const { model: requestModel, apiKey, headers, env, } = await this._getSummarizationRequestAuth(model, abortController.signal);
2324
- abortController.signal.throwIfAborted();
2325
- let extensionCompaction;
2326
- if (this._extensionRunner.hasHandlers("session_before_compact")) {
2327
- const extensionResult = (await this._extensionRunner.emit({
2328
- type: "session_before_compact",
2329
- preparation,
2330
- branchEntries: pathEntries,
2331
- customInstructions: undefined,
2332
- reason,
2333
- willRetry,
2334
- signal: abortController.signal,
2335
- }));
2336
- if (extensionResult?.cancel) {
2337
- cancelledByExtension = true;
2338
- throw new Error("Compaction cancelled");
2339
- }
2340
- if (extensionResult?.compaction) {
2341
- extensionCompaction = extensionResult.compaction;
2342
- fromExtension = true;
2343
- }
2344
- }
2345
- abortController.signal.throwIfAborted();
2346
- let summary;
2347
- let firstKeptEntryId;
2348
- let tokensBefore;
2349
- let usage;
2350
- let details;
2351
- if (extensionCompaction) {
2352
- // Extension provided compaction content
2353
- summary = extensionCompaction.summary;
2354
- firstKeptEntryId = extensionCompaction.firstKeptEntryId;
2355
- tokensBefore = extensionCompaction.tokensBefore;
2356
- usage = extensionCompaction.usage;
2357
- details = extensionCompaction.details;
2358
- }
2359
- else {
2360
- // Shared default summary generator, also used by manual compaction.
2361
- const compactResult = await this._runDefaultCompaction(preparation, requestModel, apiKey, headers, undefined, abortController.signal, env, reason);
2362
- summary = compactResult.summary;
2363
- firstKeptEntryId = compactResult.firstKeptEntryId;
2364
- tokensBefore = compactResult.tokensBefore;
2365
- usage = compactResult.usage;
2366
- details = compactResult.details;
2367
- }
2368
- abortController.signal.throwIfAborted();
2369
- this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension, usage);
2370
- const newEntries = this.sessionManager.getEntries();
2371
- this._refreshFinalizedContext();
2372
- const estimatedTokensAfter = estimateMessagesTokens(this.sessionManager.buildSessionProjection().messages);
2373
- // Get the saved compaction entry for the extension event
2374
- const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
2375
- if (this._extensionRunner && savedCompactionEntry) {
2376
- await this._extensionRunner.emit({
2377
- type: "session_compact",
2378
- compactionEntry: savedCompactionEntry,
2379
- fromExtension,
2380
- reason,
2381
- willRetry,
2382
- });
2383
- }
2384
- const result = {
2385
- summary,
2386
- firstKeptEntryId,
2387
- tokensBefore,
2388
- estimatedTokensAfter,
2389
- usage,
2390
- details,
2391
- };
2392
- this._emit({ type: "compaction_end", reason, result, aborted: false, willRetry });
2393
- if (willRetry)
2394
- return true;
2395
- // Auto-compaction can complete while follow-up/steering/custom messages are waiting.
2396
- // Continue once so queued messages are delivered.
2397
- return this.agent.hasQueuedMessages();
2323
+ if (!model) {
2324
+ return false;
2398
2325
  }
2399
- catch (error) {
2400
- const message = error instanceof Error ? error.message : "compaction failed";
2401
- const aborted = abortController?.signal.aborted === true || cancelledByExtension;
2402
- if (started) {
2403
- const errorMessage = aborted
2404
- ? undefined
2405
- : reason === "overflow"
2406
- ? `Context overflow recovery failed: ${message}`
2407
- : `Auto-compaction failed: ${message}`;
2408
- this._emit({
2409
- type: "compaction_end",
2410
- reason,
2411
- result: undefined,
2412
- aborted,
2413
- willRetry: false,
2414
- errorMessage,
2415
- });
2416
- await this._emitSessionCompactFailed({
2417
- reason,
2418
- errorMessage,
2419
- aborted,
2420
- willRetry: false,
2421
- fromExtension,
2422
- });
2423
- }
2326
+ const pathEntries = this.sessionManager.getBranch();
2327
+ const preparation = prepareCompaction(pathEntries, settings);
2328
+ if (!preparation) {
2424
2329
  return false;
2425
2330
  }
2426
- finally {
2427
- if (this._autoCompactionAbortController === abortController) {
2428
- this._autoCompactionAbortController = undefined;
2331
+ abortController = new AbortController();
2332
+ this._autoCompactionAbortController = abortController;
2333
+ started = true;
2334
+ this._emit({ type: "compaction_start", reason });
2335
+ abortController.signal.throwIfAborted();
2336
+ const { model: requestModel, apiKey, headers, env, } = await this._getSummarizationRequestAuth(model, abortController.signal);
2337
+ abortController.signal.throwIfAborted();
2338
+ let extensionCompaction;
2339
+ if (this._extensionRunner.hasHandlers("session_before_compact")) {
2340
+ const extensionResult = (await this._extensionRunner.emit({
2341
+ type: "session_before_compact",
2342
+ preparation,
2343
+ branchEntries: pathEntries,
2344
+ customInstructions: undefined,
2345
+ reason,
2346
+ willRetry,
2347
+ signal: abortController.signal,
2348
+ }));
2349
+ if (extensionResult?.cancel) {
2350
+ cancelledByExtension = true;
2351
+ throw new Error("Compaction cancelled");
2429
2352
  }
2430
- this._resolveIdleWaitIfIdle();
2353
+ if (extensionResult?.compaction) {
2354
+ extensionCompaction = extensionResult.compaction;
2355
+ fromExtension = true;
2356
+ }
2357
+ }
2358
+ abortController.signal.throwIfAborted();
2359
+ let summary;
2360
+ let firstKeptEntryId;
2361
+ let tokensBefore;
2362
+ let usage;
2363
+ let details;
2364
+ if (extensionCompaction) {
2365
+ // Extension provided compaction content
2366
+ summary = extensionCompaction.summary;
2367
+ firstKeptEntryId = extensionCompaction.firstKeptEntryId;
2368
+ tokensBefore = extensionCompaction.tokensBefore;
2369
+ usage = extensionCompaction.usage;
2370
+ details = extensionCompaction.details;
2371
+ }
2372
+ else {
2373
+ // Shared default summary generator, also used by manual compaction.
2374
+ const compactResult = await this._runDefaultCompaction(preparation, requestModel, apiKey, headers, undefined, abortController.signal, env, reason);
2375
+ summary = compactResult.summary;
2376
+ firstKeptEntryId = compactResult.firstKeptEntryId;
2377
+ tokensBefore = compactResult.tokensBefore;
2378
+ usage = compactResult.usage;
2379
+ details = compactResult.details;
2380
+ }
2381
+ abortController.signal.throwIfAborted();
2382
+ this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension, usage);
2383
+ const newEntries = this.sessionManager.getEntries();
2384
+ this._refreshFinalizedContext();
2385
+ const estimatedTokensAfter = estimateMessagesTokens(this.sessionManager.buildSessionProjection().messages);
2386
+ // Get the saved compaction entry for the extension event
2387
+ const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
2388
+ if (this._extensionRunner && savedCompactionEntry) {
2389
+ await this._extensionRunner.emit({
2390
+ type: "session_compact",
2391
+ compactionEntry: savedCompactionEntry,
2392
+ fromExtension,
2393
+ reason,
2394
+ willRetry,
2395
+ });
2396
+ }
2397
+ const result = {
2398
+ summary,
2399
+ firstKeptEntryId,
2400
+ tokensBefore,
2401
+ estimatedTokensAfter,
2402
+ usage,
2403
+ details,
2404
+ };
2405
+ this._emit({ type: "compaction_end", reason, result, aborted: false, willRetry });
2406
+ if (willRetry)
2407
+ return true;
2408
+ // Auto-compaction can complete while follow-up/steering/custom messages are waiting.
2409
+ // Continue once so queued messages are delivered.
2410
+ return this.agent.hasQueuedMessages();
2411
+ }
2412
+ catch (error) {
2413
+ const message = error instanceof Error ? error.message : "compaction failed";
2414
+ const aborted = abortController?.signal.aborted === true || cancelledByExtension;
2415
+ if (started) {
2416
+ const errorMessage = aborted
2417
+ ? undefined
2418
+ : reason === "overflow"
2419
+ ? `Context overflow recovery failed: ${message}`
2420
+ : `Auto-compaction failed: ${message}`;
2421
+ this._emit({
2422
+ type: "compaction_end",
2423
+ reason,
2424
+ result: undefined,
2425
+ aborted,
2426
+ willRetry: false,
2427
+ errorMessage,
2428
+ });
2429
+ await this._emitSessionCompactFailed({
2430
+ reason,
2431
+ errorMessage,
2432
+ aborted,
2433
+ willRetry: false,
2434
+ fromExtension,
2435
+ });
2431
2436
  }
2437
+ return false;
2432
2438
  }
2433
2439
  finally {
2434
- resumeBackground();
2440
+ if (this._autoCompactionAbortController === abortController) {
2441
+ this._autoCompactionAbortController = undefined;
2442
+ }
2443
+ this._resolveIdleWaitIfIdle();
2435
2444
  }
2436
2445
  }
2437
2446
  /**
@@ -2445,36 +2454,33 @@ export class AgentSession {
2445
2454
  return this.settingsManager.getCompactionEnabled();
2446
2455
  }
2447
2456
  async bindExtensions(bindings) {
2448
- const resumeBackground = this.pauseBackgroundNotifications();
2449
- try {
2450
- if (bindings.backgroundEnabled !== undefined) {
2451
- this._backgroundHost.setEnabled(bindings.backgroundEnabled);
2452
- }
2453
- if (bindings.uiContext !== undefined) {
2454
- this._extensionUIContext = bindings.uiContext;
2455
- }
2456
- if (bindings.mode !== undefined) {
2457
- this._extensionMode = bindings.mode;
2458
- }
2459
- if (bindings.commandContextActions !== undefined) {
2460
- this._extensionCommandContextActions = bindings.commandContextActions;
2461
- }
2462
- if (bindings.abortHandler !== undefined) {
2463
- this._extensionAbortHandler = bindings.abortHandler;
2464
- }
2465
- if (bindings.shutdownHandler !== undefined) {
2466
- this._extensionShutdownHandler = bindings.shutdownHandler;
2467
- }
2468
- if (bindings.onError !== undefined) {
2469
- this._extensionErrorListener = bindings.onError;
2470
- }
2471
- this._applyExtensionBindings(this._extensionRunner);
2472
- await this._extensionRunner.emit(this._sessionStartEvent);
2473
- await this.extendResourcesFromExtensions(this._sessionStartEvent.reason === "reload" ? "reload" : "startup");
2457
+ return this._withBackgroundPaused(() => this._bindExtensionsBody(bindings));
2458
+ }
2459
+ async _bindExtensionsBody(bindings) {
2460
+ if (bindings.backgroundEnabled !== undefined) {
2461
+ this._backgroundHost.setEnabled(bindings.backgroundEnabled);
2474
2462
  }
2475
- finally {
2476
- resumeBackground();
2463
+ if (bindings.uiContext !== undefined) {
2464
+ this._extensionUIContext = bindings.uiContext;
2465
+ }
2466
+ if (bindings.mode !== undefined) {
2467
+ this._extensionMode = bindings.mode;
2468
+ }
2469
+ if (bindings.commandContextActions !== undefined) {
2470
+ this._extensionCommandContextActions = bindings.commandContextActions;
2471
+ }
2472
+ if (bindings.abortHandler !== undefined) {
2473
+ this._extensionAbortHandler = bindings.abortHandler;
2477
2474
  }
2475
+ if (bindings.shutdownHandler !== undefined) {
2476
+ this._extensionShutdownHandler = bindings.shutdownHandler;
2477
+ }
2478
+ if (bindings.onError !== undefined) {
2479
+ this._extensionErrorListener = bindings.onError;
2480
+ }
2481
+ this._applyExtensionBindings(this._extensionRunner);
2482
+ await this._extensionRunner.emit(this._sessionStartEvent);
2483
+ await this.extendResourcesFromExtensions(this._sessionStartEvent.reason === "reload" ? "reload" : "startup");
2478
2484
  }
2479
2485
  async extendResourcesFromExtensions(reason) {
2480
2486
  if (!this._extensionRunner.hasHandlers("resources_discover")) {
@@ -2766,37 +2772,34 @@ export class AgentSession {
2766
2772
  });
2767
2773
  }
2768
2774
  async reload(options) {
2769
- const resumeBackground = this.pauseBackgroundNotifications();
2770
- try {
2771
- const oldRunner = this._extensionRunner;
2772
- const previousFlagValues = oldRunner.getFlagValues();
2773
- this.background.close();
2774
- await Promise.all([this.background.shutdown(), this.abort()]);
2775
- await emitSessionShutdownEvent(oldRunner, { type: "session_shutdown", reason: "reload" });
2776
- oldRunner.invalidate();
2777
- await this.settingsManager.reload();
2778
- this.syncQueueModesFromSettings();
2779
- resetApiProviders();
2780
- await this._resourceLoader.reload();
2781
- this._backgroundHost.replaceService();
2782
- this._buildRuntime({
2783
- activeToolNames: this.getActiveToolNames(),
2784
- flagValues: previousFlagValues,
2785
- includeAllExtensionTools: true,
2786
- });
2787
- const hasBindings = this.background.enabled ||
2788
- this._extensionUIContext ||
2789
- this._extensionCommandContextActions ||
2790
- this._extensionShutdownHandler ||
2791
- this._extensionErrorListener;
2792
- if (hasBindings) {
2793
- await options?.beforeSessionStart?.();
2794
- await this._extensionRunner.emit({ type: "session_start", reason: "reload" });
2795
- await this.extendResourcesFromExtensions("reload");
2796
- }
2797
- }
2798
- finally {
2799
- resumeBackground();
2775
+ return this._withBackgroundPaused(() => this._reloadBody(options));
2776
+ }
2777
+ async _reloadBody(options) {
2778
+ const oldRunner = this._extensionRunner;
2779
+ const previousFlagValues = oldRunner.getFlagValues();
2780
+ this.background.close();
2781
+ await Promise.all([this.background.shutdown(), this.abort()]);
2782
+ await emitSessionShutdownEvent(oldRunner, { type: "session_shutdown", reason: "reload" });
2783
+ oldRunner.invalidate();
2784
+ await this.settingsManager.reload();
2785
+ this.syncQueueModesFromSettings();
2786
+ resetApiProviders();
2787
+ await this._resourceLoader.reload();
2788
+ this._backgroundHost.replaceService();
2789
+ this._buildRuntime({
2790
+ activeToolNames: this.getActiveToolNames(),
2791
+ flagValues: previousFlagValues,
2792
+ includeAllExtensionTools: true,
2793
+ });
2794
+ const hasBindings = this.background.enabled ||
2795
+ this._extensionUIContext ||
2796
+ this._extensionCommandContextActions ||
2797
+ this._extensionShutdownHandler ||
2798
+ this._extensionErrorListener;
2799
+ if (hasBindings) {
2800
+ await options?.beforeSessionStart?.();
2801
+ await this._extensionRunner.emit({ type: "session_start", reason: "reload" });
2802
+ await this.extendResourcesFromExtensions("reload");
2800
2803
  }
2801
2804
  }
2802
2805
  // =========================================================================
@@ -3340,7 +3343,7 @@ export class AgentSession {
3340
3343
  if (!model) {
3341
3344
  throw new Error("No model selected");
3342
3345
  }
3343
- const { model: requestModel, apiKey, headers, env, } = await this._getSummarizationRequestAuth(model, options.signal);
3346
+ const { model: requestModel, apiKey, headers, env } = await this._getSummarizationRequestAuth(model);
3344
3347
  return generateBugReportSummary({
3345
3348
  messages: this.messages,
3346
3349
  hint: options.hint,