@phi-code-admin/phi-code 0.74.3 → 0.75.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 (655) hide show
  1. package/CHANGELOG.md +1186 -4
  2. package/README.md +478 -379
  3. package/dist/bun/cli.d.ts +3 -0
  4. package/dist/bun/cli.d.ts.map +1 -0
  5. package/dist/bun/cli.js +9 -0
  6. package/dist/bun/cli.js.map +1 -0
  7. package/dist/bun/register-bedrock.d.ts +2 -0
  8. package/dist/bun/register-bedrock.d.ts.map +1 -0
  9. package/dist/bun/register-bedrock.js +4 -0
  10. package/dist/bun/register-bedrock.js.map +1 -0
  11. package/dist/bun/restore-sandbox-env.d.ts +13 -0
  12. package/dist/bun/restore-sandbox-env.d.ts.map +1 -0
  13. package/dist/bun/restore-sandbox-env.js +32 -0
  14. package/dist/bun/restore-sandbox-env.js.map +1 -0
  15. package/dist/cli/args.d.ts +12 -7
  16. package/dist/cli/args.d.ts.map +1 -1
  17. package/dist/cli/args.js +87 -45
  18. package/dist/cli/args.js.map +1 -1
  19. package/dist/cli/config-selector.d.ts.map +1 -1
  20. package/dist/cli/config-selector.js.map +1 -1
  21. package/dist/cli/file-processor.d.ts.map +1 -1
  22. package/dist/cli/file-processor.js +4 -0
  23. package/dist/cli/file-processor.js.map +1 -1
  24. package/dist/cli/initial-message.d.ts +18 -0
  25. package/dist/cli/initial-message.d.ts.map +1 -0
  26. package/dist/cli/initial-message.js +22 -0
  27. package/dist/cli/initial-message.js.map +1 -0
  28. package/dist/cli/list-models.d.ts.map +1 -1
  29. package/dist/cli/list-models.js +7 -1
  30. package/dist/cli/list-models.js.map +1 -1
  31. package/dist/cli/session-picker.d.ts.map +1 -1
  32. package/dist/cli/session-picker.js +2 -1
  33. package/dist/cli/session-picker.js.map +1 -1
  34. package/dist/cli.d.ts.map +1 -1
  35. package/dist/cli.js +9 -5
  36. package/dist/cli.js.map +1 -1
  37. package/dist/config.d.ts +24 -0
  38. package/dist/config.d.ts.map +1 -1
  39. package/dist/config.js +226 -30
  40. package/dist/config.js.map +1 -1
  41. package/dist/core/agent-session-runtime.d.ts +117 -0
  42. package/dist/core/agent-session-runtime.d.ts.map +1 -0
  43. package/dist/core/agent-session-runtime.js +300 -0
  44. package/dist/core/agent-session-runtime.js.map +1 -0
  45. package/dist/core/agent-session-services.d.ts +86 -0
  46. package/dist/core/agent-session-services.d.ts.map +1 -0
  47. package/dist/core/agent-session-services.js +117 -0
  48. package/dist/core/agent-session-services.js.map +1 -0
  49. package/dist/core/agent-session.d.ts +63 -82
  50. package/dist/core/agent-session.d.ts.map +1 -1
  51. package/dist/core/agent-session.js +674 -628
  52. package/dist/core/agent-session.js.map +1 -1
  53. package/dist/core/api-key-store.d.ts +87 -0
  54. package/dist/core/api-key-store.d.ts.map +1 -0
  55. package/dist/core/api-key-store.js +168 -0
  56. package/dist/core/api-key-store.js.map +1 -0
  57. package/dist/core/auth-guidance.d.ts +5 -0
  58. package/dist/core/auth-guidance.d.ts.map +1 -0
  59. package/dist/core/auth-guidance.js +21 -0
  60. package/dist/core/auth-guidance.js.map +1 -0
  61. package/dist/core/auth-storage.d.ts +12 -5
  62. package/dist/core/auth-storage.d.ts.map +1 -1
  63. package/dist/core/auth-storage.js +34 -8
  64. package/dist/core/auth-storage.js.map +1 -1
  65. package/dist/core/bash-executor.d.ts +0 -15
  66. package/dist/core/bash-executor.d.ts.map +1 -1
  67. package/dist/core/bash-executor.js +28 -129
  68. package/dist/core/bash-executor.js.map +1 -1
  69. package/dist/core/compaction/branch-summarization.d.ts +2 -0
  70. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  71. package/dist/core/compaction/branch-summarization.js +3 -2
  72. package/dist/core/compaction/branch-summarization.js.map +1 -1
  73. package/dist/core/compaction/compaction.d.ts +4 -4
  74. package/dist/core/compaction/compaction.d.ts.map +1 -1
  75. package/dist/core/compaction/compaction.js +32 -27
  76. package/dist/core/compaction/compaction.js.map +1 -1
  77. package/dist/core/compaction/index.d.ts.map +1 -1
  78. package/dist/core/compaction/utils.d.ts.map +1 -1
  79. package/dist/core/compaction/utils.js.map +1 -1
  80. package/dist/core/config-watcher.d.ts +47 -0
  81. package/dist/core/config-watcher.d.ts.map +1 -0
  82. package/dist/core/config-watcher.js +135 -0
  83. package/dist/core/config-watcher.js.map +1 -0
  84. package/dist/core/default-models.json +80 -0
  85. package/dist/core/defaults.d.ts.map +1 -1
  86. package/dist/core/diagnostics.d.ts.map +1 -1
  87. package/dist/core/event-bus.d.ts.map +1 -1
  88. package/dist/core/event-bus.js.map +1 -1
  89. package/dist/core/exec.d.ts.map +1 -1
  90. package/dist/core/exec.js +7 -3
  91. package/dist/core/exec.js.map +1 -1
  92. package/dist/core/export-html/ansi-to-html.d.ts.map +1 -1
  93. package/dist/core/export-html/ansi-to-html.js +1 -1
  94. package/dist/core/export-html/ansi-to-html.js.map +1 -1
  95. package/dist/core/export-html/index.d.ts +7 -4
  96. package/dist/core/export-html/index.d.ts.map +1 -1
  97. package/dist/core/export-html/index.js +15 -13
  98. package/dist/core/export-html/index.js.map +1 -1
  99. package/dist/core/export-html/template.css +112 -17
  100. package/dist/core/export-html/template.html +1 -0
  101. package/dist/core/export-html/template.js +312 -64
  102. package/dist/core/export-html/tool-renderer.d.ts +9 -10
  103. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  104. package/dist/core/export-html/tool-renderer.js +61 -16
  105. package/dist/core/export-html/tool-renderer.js.map +1 -1
  106. package/dist/core/extensions/index.d.ts +5 -4
  107. package/dist/core/extensions/index.d.ts.map +1 -1
  108. package/dist/core/extensions/index.js +2 -2
  109. package/dist/core/extensions/index.js.map +1 -1
  110. package/dist/core/extensions/loader.d.ts +0 -1
  111. package/dist/core/extensions/loader.d.ts.map +1 -1
  112. package/dist/core/extensions/loader.js +98 -18
  113. package/dist/core/extensions/loader.js.map +1 -1
  114. package/dist/core/extensions/runner.d.ts +27 -14
  115. package/dist/core/extensions/runner.d.ts.map +1 -1
  116. package/dist/core/extensions/runner.js +299 -115
  117. package/dist/core/extensions/runner.js.map +1 -1
  118. package/dist/core/extensions/types.d.ts +200 -44
  119. package/dist/core/extensions/types.d.ts.map +1 -1
  120. package/dist/core/extensions/types.js +10 -0
  121. package/dist/core/extensions/types.js.map +1 -1
  122. package/dist/core/extensions/wrapper.d.ts +4 -11
  123. package/dist/core/extensions/wrapper.d.ts.map +1 -1
  124. package/dist/core/extensions/wrapper.js +7 -87
  125. package/dist/core/extensions/wrapper.js.map +1 -1
  126. package/dist/core/footer-data-provider.d.ts +22 -2
  127. package/dist/core/footer-data-provider.d.ts.map +1 -1
  128. package/dist/core/footer-data-provider.js +225 -49
  129. package/dist/core/footer-data-provider.js.map +1 -1
  130. package/dist/core/index.d.ts +5 -2
  131. package/dist/core/index.d.ts.map +1 -1
  132. package/dist/core/index.js +5 -2
  133. package/dist/core/index.js.map +1 -1
  134. package/dist/core/keybindings.d.ts +348 -50
  135. package/dist/core/keybindings.d.ts.map +1 -1
  136. package/dist/core/keybindings.js +276 -132
  137. package/dist/core/keybindings.js.map +1 -1
  138. package/dist/core/messages.d.ts.map +1 -1
  139. package/dist/core/messages.js.map +1 -1
  140. package/dist/core/model-registry.d.ts +41 -5
  141. package/dist/core/model-registry.d.ts.map +1 -1
  142. package/dist/core/model-registry.js +316 -136
  143. package/dist/core/model-registry.js.map +1 -1
  144. package/dist/core/model-resolver.d.ts +6 -0
  145. package/dist/core/model-resolver.d.ts.map +1 -1
  146. package/dist/core/model-resolver.js +70 -37
  147. package/dist/core/model-resolver.js.map +1 -1
  148. package/dist/core/output-guard.d.ts +6 -0
  149. package/dist/core/output-guard.d.ts.map +1 -0
  150. package/dist/core/output-guard.js +59 -0
  151. package/dist/core/output-guard.js.map +1 -0
  152. package/dist/core/package-manager.d.ts +49 -7
  153. package/dist/core/package-manager.d.ts.map +1 -1
  154. package/dist/core/package-manager.js +655 -122
  155. package/dist/core/package-manager.js.map +1 -1
  156. package/dist/core/prompt-templates.d.ts +12 -10
  157. package/dist/core/prompt-templates.d.ts.map +1 -1
  158. package/dist/core/prompt-templates.js +37 -38
  159. package/dist/core/prompt-templates.js.map +1 -1
  160. package/dist/core/provider-display-names.d.ts +2 -0
  161. package/dist/core/provider-display-names.d.ts.map +1 -0
  162. package/dist/core/provider-display-names.js +33 -0
  163. package/dist/core/provider-display-names.js.map +1 -0
  164. package/dist/core/resolve-config-value.d.ts +6 -0
  165. package/dist/core/resolve-config-value.d.ts.map +1 -1
  166. package/dist/core/resolve-config-value.js +75 -8
  167. package/dist/core/resolve-config-value.js.map +1 -1
  168. package/dist/core/resource-loader.d.ts +18 -8
  169. package/dist/core/resource-loader.d.ts.map +1 -1
  170. package/dist/core/resource-loader.js +217 -123
  171. package/dist/core/resource-loader.js.map +1 -1
  172. package/dist/core/sdk.d.ts +25 -8
  173. package/dist/core/sdk.d.ts.map +1 -1
  174. package/dist/core/sdk.js +84 -37
  175. package/dist/core/sdk.js.map +1 -1
  176. package/dist/core/session-cwd.d.ts +19 -0
  177. package/dist/core/session-cwd.d.ts.map +1 -0
  178. package/dist/core/session-cwd.js +38 -0
  179. package/dist/core/session-cwd.js.map +1 -0
  180. package/dist/core/session-manager.d.ts +11 -1
  181. package/dist/core/session-manager.d.ts.map +1 -1
  182. package/dist/core/session-manager.js +42 -27
  183. package/dist/core/session-manager.js.map +1 -1
  184. package/dist/core/settings-manager.d.ts +34 -5
  185. package/dist/core/settings-manager.d.ts.map +1 -1
  186. package/dist/core/settings-manager.js +113 -13
  187. package/dist/core/settings-manager.js.map +1 -1
  188. package/dist/core/skills.d.ts +13 -11
  189. package/dist/core/skills.d.ts.map +1 -1
  190. package/dist/core/skills.js +59 -19
  191. package/dist/core/skills.js.map +1 -1
  192. package/dist/core/slash-commands.d.ts +2 -3
  193. package/dist/core/slash-commands.d.ts.map +1 -1
  194. package/dist/core/slash-commands.js +9 -6
  195. package/dist/core/slash-commands.js.map +1 -1
  196. package/dist/core/source-info.d.ts +18 -0
  197. package/dist/core/source-info.d.ts.map +1 -0
  198. package/dist/core/source-info.js +19 -0
  199. package/dist/core/source-info.js.map +1 -0
  200. package/dist/core/system-prompt.d.ts +3 -3
  201. package/dist/core/system-prompt.d.ts.map +1 -1
  202. package/dist/core/system-prompt.js +16 -55
  203. package/dist/core/system-prompt.js.map +1 -1
  204. package/dist/core/telemetry.d.ts +3 -0
  205. package/dist/core/telemetry.d.ts.map +1 -0
  206. package/dist/core/telemetry.js +9 -0
  207. package/dist/core/telemetry.js.map +1 -0
  208. package/dist/core/timings.d.ts +1 -0
  209. package/dist/core/timings.d.ts.map +1 -1
  210. package/dist/core/timings.js +6 -0
  211. package/dist/core/timings.js.map +1 -1
  212. package/dist/core/tools/bash.d.ts +27 -14
  213. package/dist/core/tools/bash.d.ts.map +1 -1
  214. package/dist/core/tools/bash.js +301 -208
  215. package/dist/core/tools/bash.js.map +1 -1
  216. package/dist/core/tools/edit-diff.d.ts +23 -1
  217. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  218. package/dist/core/tools/edit-diff.js +154 -59
  219. package/dist/core/tools/edit-diff.js.map +1 -1
  220. package/dist/core/tools/edit.d.ts +22 -12
  221. package/dist/core/tools/edit.d.ts.map +1 -1
  222. package/dist/core/tools/edit.js +243 -65
  223. package/dist/core/tools/edit.js.map +1 -1
  224. package/dist/core/tools/file-mutation-queue.d.ts +6 -0
  225. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -0
  226. package/dist/core/tools/file-mutation-queue.js +37 -0
  227. package/dist/core/tools/file-mutation-queue.js.map +1 -0
  228. package/dist/core/tools/find.d.ts +10 -14
  229. package/dist/core/tools/find.d.ts.map +1 -1
  230. package/dist/core/tools/find.js +202 -110
  231. package/dist/core/tools/find.js.map +1 -1
  232. package/dist/core/tools/grep.d.ts +14 -22
  233. package/dist/core/tools/grep.d.ts.map +1 -1
  234. package/dist/core/tools/grep.js +100 -35
  235. package/dist/core/tools/grep.js.map +1 -1
  236. package/dist/core/tools/index.d.ts +27 -60
  237. package/dist/core/tools/index.d.ts.map +1 -1
  238. package/dist/core/tools/index.js +96 -45
  239. package/dist/core/tools/index.js.map +1 -1
  240. package/dist/core/tools/ls.d.ts +8 -11
  241. package/dist/core/tools/ls.d.ts.map +1 -1
  242. package/dist/core/tools/ls.js +66 -15
  243. package/dist/core/tools/ls.js.map +1 -1
  244. package/dist/core/tools/output-accumulator.d.ts +50 -0
  245. package/dist/core/tools/output-accumulator.d.ts.map +1 -0
  246. package/dist/core/tools/output-accumulator.js +178 -0
  247. package/dist/core/tools/output-accumulator.js.map +1 -0
  248. package/dist/core/tools/path-utils.d.ts.map +1 -1
  249. package/dist/core/tools/path-utils.js +1 -1
  250. package/dist/core/tools/path-utils.js.map +1 -1
  251. package/dist/core/tools/read.d.ts +9 -13
  252. package/dist/core/tools/read.d.ts.map +1 -1
  253. package/dist/core/tools/read.js +175 -52
  254. package/dist/core/tools/read.js.map +1 -1
  255. package/dist/core/tools/render-utils.d.ts +21 -0
  256. package/dist/core/tools/render-utils.d.ts.map +1 -0
  257. package/dist/core/tools/render-utils.js +49 -0
  258. package/dist/core/tools/render-utils.js.map +1 -0
  259. package/dist/core/tools/tool-definition-wrapper.d.ts +14 -0
  260. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -0
  261. package/dist/core/tools/tool-definition-wrapper.js +34 -0
  262. package/dist/core/tools/tool-definition-wrapper.js.map +1 -0
  263. package/dist/core/tools/truncate.d.ts.map +1 -1
  264. package/dist/core/tools/truncate.js.map +1 -1
  265. package/dist/core/tools/write.d.ts +8 -11
  266. package/dist/core/tools/write.d.ts.map +1 -1
  267. package/dist/core/tools/write.js +167 -32
  268. package/dist/core/tools/write.js.map +1 -1
  269. package/dist/index.d.ts +12 -9
  270. package/dist/index.d.ts.map +1 -1
  271. package/dist/index.js +12 -10
  272. package/dist/index.js.map +1 -1
  273. package/dist/main.d.ts +5 -1
  274. package/dist/main.d.ts.map +1 -1
  275. package/dist/main.js +326 -404
  276. package/dist/main.js.map +1 -1
  277. package/dist/migrations.d.ts +2 -2
  278. package/dist/migrations.d.ts.map +1 -1
  279. package/dist/migrations.js +24 -4
  280. package/dist/migrations.js.map +1 -1
  281. package/dist/modes/index.d.ts.map +1 -1
  282. package/dist/modes/interactive/components/armin.d.ts.map +1 -1
  283. package/dist/modes/interactive/components/armin.js +10 -6
  284. package/dist/modes/interactive/components/armin.js.map +1 -1
  285. package/dist/modes/interactive/components/assistant-message.d.ts +5 -1
  286. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  287. package/dist/modes/interactive/components/assistant-message.js +32 -3
  288. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  289. package/dist/modes/interactive/components/bash-execution.d.ts +0 -1
  290. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  291. package/dist/modes/interactive/components/bash-execution.js +31 -12
  292. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  293. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
  294. package/dist/modes/interactive/components/bordered-loader.js +7 -1
  295. package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
  296. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  297. package/dist/modes/interactive/components/branch-summary-message.js +5 -3
  298. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  299. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  300. package/dist/modes/interactive/components/compaction-summary-message.js +5 -3
  301. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  302. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  303. package/dist/modes/interactive/components/config-selector.js +49 -16
  304. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  305. package/dist/modes/interactive/components/countdown-timer.d.ts.map +1 -1
  306. package/dist/modes/interactive/components/countdown-timer.js +5 -0
  307. package/dist/modes/interactive/components/countdown-timer.js.map +1 -1
  308. package/dist/modes/interactive/components/custom-editor.d.ts +3 -3
  309. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  310. package/dist/modes/interactive/components/custom-editor.js +14 -7
  311. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  312. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
  313. package/dist/modes/interactive/components/custom-message.js +6 -1
  314. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  315. package/dist/modes/interactive/components/daxnuts.d.ts.map +1 -1
  316. package/dist/modes/interactive/components/daxnuts.js +8 -6
  317. package/dist/modes/interactive/components/daxnuts.js.map +1 -1
  318. package/dist/modes/interactive/components/diff.d.ts.map +1 -1
  319. package/dist/modes/interactive/components/diff.js.map +1 -1
  320. package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
  321. package/dist/modes/interactive/components/dynamic-border.js +1 -0
  322. package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
  323. package/dist/modes/interactive/components/earendil-announcement.d.ts +5 -0
  324. package/dist/modes/interactive/components/earendil-announcement.d.ts.map +1 -0
  325. package/dist/modes/interactive/components/earendil-announcement.js +40 -0
  326. package/dist/modes/interactive/components/earendil-announcement.js.map +1 -0
  327. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  328. package/dist/modes/interactive/components/extension-editor.js +16 -10
  329. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  330. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  331. package/dist/modes/interactive/components/extension-input.js +13 -7
  332. package/dist/modes/interactive/components/extension-input.js.map +1 -1
  333. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  334. package/dist/modes/interactive/components/extension-selector.js +18 -11
  335. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  336. package/dist/modes/interactive/components/footer.d.ts +1 -0
  337. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  338. package/dist/modes/interactive/components/footer.js +7 -2
  339. package/dist/modes/interactive/components/footer.js.map +1 -1
  340. package/dist/modes/interactive/components/index.d.ts +1 -1
  341. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  342. package/dist/modes/interactive/components/index.js +1 -1
  343. package/dist/modes/interactive/components/index.js.map +1 -1
  344. package/dist/modes/interactive/components/keybinding-hints.d.ts +8 -36
  345. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
  346. package/dist/modes/interactive/components/keybinding-hints.js +23 -48
  347. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
  348. package/dist/modes/interactive/components/login-dialog.d.ts +5 -1
  349. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  350. package/dist/modes/interactive/components/login-dialog.js +35 -14
  351. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  352. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  353. package/dist/modes/interactive/components/model-selector.js +41 -22
  354. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  355. package/dist/modes/interactive/components/oauth-selector.d.ts +18 -6
  356. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  357. package/dist/modes/interactive/components/oauth-selector.js +104 -31
  358. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  359. package/dist/modes/interactive/components/scoped-models-selector.d.ts +5 -12
  360. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  361. package/dist/modes/interactive/components/scoped-models-selector.js +61 -42
  362. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  363. package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -1
  364. package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
  365. package/dist/modes/interactive/components/session-selector.d.ts +2 -1
  366. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  367. package/dist/modes/interactive/components/session-selector.js +109 -73
  368. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  369. package/dist/modes/interactive/components/settings-selector.d.ts +9 -0
  370. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  371. package/dist/modes/interactive/components/settings-selector.js +84 -4
  372. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  373. package/dist/modes/interactive/components/show-images-selector.d.ts.map +1 -1
  374. package/dist/modes/interactive/components/show-images-selector.js +6 -1
  375. package/dist/modes/interactive/components/show-images-selector.js.map +1 -1
  376. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  377. package/dist/modes/interactive/components/skill-invocation-message.js +5 -3
  378. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  379. package/dist/modes/interactive/components/theme-selector.d.ts.map +1 -1
  380. package/dist/modes/interactive/components/theme-selector.js +7 -1
  381. package/dist/modes/interactive/components/theme-selector.js.map +1 -1
  382. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  383. package/dist/modes/interactive/components/thinking-selector.js +6 -1
  384. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  385. package/dist/modes/interactive/components/tool-execution.d.ts +20 -34
  386. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  387. package/dist/modes/interactive/components/tool-execution.js +158 -636
  388. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  389. package/dist/modes/interactive/components/tree-selector.d.ts +21 -2
  390. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  391. package/dist/modes/interactive/components/tree-selector.js +224 -52
  392. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  393. package/dist/modes/interactive/components/user-message-selector.d.ts +2 -2
  394. package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
  395. package/dist/modes/interactive/components/user-message-selector.js +20 -16
  396. package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
  397. package/dist/modes/interactive/components/user-message.d.ts +1 -0
  398. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  399. package/dist/modes/interactive/components/user-message.js +8 -6
  400. package/dist/modes/interactive/components/user-message.js.map +1 -1
  401. package/dist/modes/interactive/components/visual-truncate.d.ts.map +1 -1
  402. package/dist/modes/interactive/components/visual-truncate.js.map +1 -1
  403. package/dist/modes/interactive/interactive-mode.d.ts +67 -39
  404. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  405. package/dist/modes/interactive/interactive-mode.js +1556 -680
  406. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  407. package/dist/modes/interactive/theme/dark.json +1 -1
  408. package/dist/modes/interactive/theme/light.json +1 -1
  409. package/dist/modes/interactive/theme/theme.d.ts +3 -0
  410. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  411. package/dist/modes/interactive/theme/theme.js +101 -72
  412. package/dist/modes/interactive/theme/theme.js.map +1 -1
  413. package/dist/modes/print-mode.d.ts +2 -2
  414. package/dist/modes/print-mode.d.ts.map +1 -1
  415. package/dist/modes/print-mode.js +107 -77
  416. package/dist/modes/print-mode.js.map +1 -1
  417. package/dist/modes/rpc/jsonl.d.ts +17 -0
  418. package/dist/modes/rpc/jsonl.d.ts.map +1 -0
  419. package/dist/modes/rpc/jsonl.js +49 -0
  420. package/dist/modes/rpc/jsonl.js.map +1 -0
  421. package/dist/modes/rpc/rpc-client.d.ts +8 -1
  422. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  423. package/dist/modes/rpc/rpc-client.js +22 -16
  424. package/dist/modes/rpc/rpc-client.js.map +1 -1
  425. package/dist/modes/rpc/rpc-mode.d.ts +2 -2
  426. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  427. package/dist/modes/rpc/rpc-mode.js +184 -94
  428. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  429. package/dist/modes/rpc/rpc-types.d.ts +14 -4
  430. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  431. package/dist/modes/rpc/rpc-types.js.map +1 -1
  432. package/dist/package-manager-cli.d.ts +4 -0
  433. package/dist/package-manager-cli.d.ts.map +1 -0
  434. package/dist/package-manager-cli.js +460 -0
  435. package/dist/package-manager-cli.js.map +1 -0
  436. package/dist/utils/changelog.d.ts.map +1 -1
  437. package/dist/utils/changelog.js.map +1 -1
  438. package/dist/utils/child-process.d.ts +12 -0
  439. package/dist/utils/child-process.d.ts.map +1 -0
  440. package/dist/utils/child-process.js +86 -0
  441. package/dist/utils/child-process.js.map +1 -0
  442. package/dist/utils/clipboard-image.d.ts.map +1 -1
  443. package/dist/utils/clipboard-image.js +94 -11
  444. package/dist/utils/clipboard-image.js.map +1 -1
  445. package/dist/utils/clipboard-native.d.ts +1 -0
  446. package/dist/utils/clipboard-native.d.ts.map +1 -1
  447. package/dist/utils/clipboard-native.js.map +1 -1
  448. package/dist/utils/clipboard.d.ts +1 -1
  449. package/dist/utils/clipboard.d.ts.map +1 -1
  450. package/dist/utils/clipboard.js +96 -46
  451. package/dist/utils/clipboard.js.map +1 -1
  452. package/dist/utils/exif-orientation.d.ts +5 -0
  453. package/dist/utils/exif-orientation.d.ts.map +1 -0
  454. package/dist/utils/exif-orientation.js +158 -0
  455. package/dist/utils/exif-orientation.js.map +1 -0
  456. package/dist/utils/frontmatter.d.ts.map +1 -1
  457. package/dist/utils/frontmatter.js.map +1 -1
  458. package/dist/utils/fs-watch.d.ts +5 -0
  459. package/dist/utils/fs-watch.d.ts.map +1 -0
  460. package/dist/utils/fs-watch.js +25 -0
  461. package/dist/utils/fs-watch.js.map +1 -0
  462. package/dist/utils/git.d.ts.map +1 -1
  463. package/dist/utils/git.js.map +1 -1
  464. package/dist/utils/image-convert.d.ts.map +1 -1
  465. package/dist/utils/image-convert.js +5 -1
  466. package/dist/utils/image-convert.js.map +1 -1
  467. package/dist/utils/image-resize.d.ts +5 -5
  468. package/dist/utils/image-resize.d.ts.map +1 -1
  469. package/dist/utils/image-resize.js +51 -95
  470. package/dist/utils/image-resize.js.map +1 -1
  471. package/dist/utils/mime.d.ts.map +1 -1
  472. package/dist/utils/mime.js.map +1 -1
  473. package/dist/utils/paths.d.ts +16 -0
  474. package/dist/utils/paths.d.ts.map +1 -0
  475. package/dist/utils/paths.js +50 -0
  476. package/dist/utils/paths.js.map +1 -0
  477. package/dist/utils/photon.d.ts.map +1 -1
  478. package/dist/utils/photon.js.map +1 -1
  479. package/dist/utils/pi-user-agent.d.ts +2 -0
  480. package/dist/utils/pi-user-agent.d.ts.map +1 -0
  481. package/dist/utils/pi-user-agent.js +5 -0
  482. package/dist/utils/pi-user-agent.js.map +1 -0
  483. package/dist/utils/shell.d.ts +10 -6
  484. package/dist/utils/shell.d.ts.map +1 -1
  485. package/dist/utils/shell.js +29 -25
  486. package/dist/utils/shell.js.map +1 -1
  487. package/dist/utils/sleep.d.ts.map +1 -1
  488. package/dist/utils/sleep.js.map +1 -1
  489. package/dist/utils/tools-manager.d.ts.map +1 -1
  490. package/dist/utils/tools-manager.js +11 -6
  491. package/dist/utils/tools-manager.js.map +1 -1
  492. package/dist/utils/version-check.d.ts +14 -0
  493. package/dist/utils/version-check.d.ts.map +1 -0
  494. package/dist/utils/version-check.js +77 -0
  495. package/dist/utils/version-check.js.map +1 -0
  496. package/docs/compaction.md +394 -0
  497. package/docs/custom-provider.md +646 -0
  498. package/docs/development.md +71 -0
  499. package/docs/docs.json +148 -0
  500. package/docs/extensions.md +2596 -0
  501. package/docs/images/doom-extension.png +0 -0
  502. package/docs/images/exy.png +0 -0
  503. package/docs/images/interactive-mode.png +0 -0
  504. package/docs/images/tree-view.png +0 -0
  505. package/docs/index.md +70 -0
  506. package/docs/json.md +82 -0
  507. package/docs/keybindings.md +197 -0
  508. package/docs/models.md +474 -0
  509. package/docs/packages.md +223 -0
  510. package/docs/prompt-templates.md +88 -0
  511. package/docs/providers.md +243 -0
  512. package/docs/quickstart.md +142 -0
  513. package/docs/rpc.md +1407 -0
  514. package/docs/sdk.md +1149 -0
  515. package/docs/session-format.md +412 -0
  516. package/docs/sessions.md +137 -0
  517. package/docs/settings.md +279 -0
  518. package/docs/shell-aliases.md +13 -0
  519. package/docs/skills.md +232 -0
  520. package/docs/terminal-setup.md +106 -0
  521. package/docs/termux.md +127 -0
  522. package/docs/themes.md +295 -0
  523. package/docs/tmux.md +61 -0
  524. package/docs/tui.md +918 -0
  525. package/docs/usage.md +277 -0
  526. package/docs/windows.md +17 -0
  527. package/examples/README.md +25 -0
  528. package/examples/extensions/README.md +208 -0
  529. package/examples/extensions/auto-commit-on-exit.ts +49 -0
  530. package/examples/extensions/bash-spawn-hook.ts +30 -0
  531. package/examples/extensions/bookmark.ts +50 -0
  532. package/examples/extensions/border-status-editor.ts +150 -0
  533. package/examples/extensions/built-in-tool-renderer.ts +249 -0
  534. package/examples/extensions/claude-rules.ts +86 -0
  535. package/examples/extensions/commands.ts +72 -0
  536. package/examples/extensions/confirm-destructive.ts +59 -0
  537. package/examples/extensions/custom-compaction.ts +127 -0
  538. package/examples/extensions/custom-footer.ts +64 -0
  539. package/examples/extensions/custom-header.ts +73 -0
  540. package/examples/extensions/custom-provider-anthropic/index.ts +604 -0
  541. package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
  542. package/examples/extensions/custom-provider-anthropic/package.json +19 -0
  543. package/examples/extensions/custom-provider-gitlab-duo/index.ts +349 -0
  544. package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
  545. package/examples/extensions/custom-provider-gitlab-duo/test.ts +82 -0
  546. package/examples/extensions/dirty-repo-guard.ts +56 -0
  547. package/examples/extensions/doom-overlay/README.md +46 -0
  548. package/examples/extensions/doom-overlay/doom/build/doom.js +21 -0
  549. package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
  550. package/examples/extensions/doom-overlay/doom/build.sh +152 -0
  551. package/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
  552. package/examples/extensions/doom-overlay/doom-component.ts +132 -0
  553. package/examples/extensions/doom-overlay/doom-engine.ts +173 -0
  554. package/examples/extensions/doom-overlay/doom-keys.ts +104 -0
  555. package/examples/extensions/doom-overlay/index.ts +74 -0
  556. package/examples/extensions/doom-overlay/wad-finder.ts +51 -0
  557. package/examples/extensions/dynamic-resources/SKILL.md +8 -0
  558. package/examples/extensions/dynamic-resources/dynamic.json +79 -0
  559. package/examples/extensions/dynamic-resources/dynamic.md +5 -0
  560. package/examples/extensions/dynamic-resources/index.ts +15 -0
  561. package/examples/extensions/dynamic-tools.ts +74 -0
  562. package/examples/extensions/event-bus.ts +43 -0
  563. package/examples/extensions/file-trigger.ts +41 -0
  564. package/examples/extensions/git-checkpoint.ts +53 -0
  565. package/examples/extensions/github-issue-autocomplete.ts +185 -0
  566. package/examples/extensions/handoff.ts +191 -0
  567. package/examples/extensions/hello.ts +26 -0
  568. package/examples/extensions/hidden-thinking-label.ts +53 -0
  569. package/examples/extensions/inline-bash.ts +94 -0
  570. package/examples/extensions/input-transform.ts +43 -0
  571. package/examples/extensions/interactive-shell.ts +196 -0
  572. package/examples/extensions/mac-system-theme.ts +47 -0
  573. package/examples/extensions/message-renderer.ts +59 -0
  574. package/examples/extensions/minimal-mode.ts +426 -0
  575. package/examples/extensions/modal-editor.ts +85 -0
  576. package/examples/extensions/model-status.ts +31 -0
  577. package/examples/extensions/notify.ts +55 -0
  578. package/examples/extensions/overlay-qa-tests.ts +1348 -0
  579. package/examples/extensions/overlay-test.ts +150 -0
  580. package/examples/extensions/permission-gate.ts +34 -0
  581. package/examples/extensions/pirate.ts +47 -0
  582. package/examples/extensions/plan-mode/README.md +65 -0
  583. package/examples/extensions/plan-mode/index.ts +340 -0
  584. package/examples/extensions/plan-mode/utils.ts +168 -0
  585. package/examples/extensions/preset.ts +430 -0
  586. package/examples/extensions/prompt-customizer.ts +97 -0
  587. package/examples/extensions/protected-paths.ts +30 -0
  588. package/examples/extensions/provider-payload.ts +18 -0
  589. package/examples/extensions/qna.ts +122 -0
  590. package/examples/extensions/question.ts +264 -0
  591. package/examples/extensions/questionnaire.ts +427 -0
  592. package/examples/extensions/rainbow-editor.ts +88 -0
  593. package/examples/extensions/reload-runtime.ts +37 -0
  594. package/examples/extensions/rpc-demo.ts +118 -0
  595. package/examples/extensions/sandbox/index.ts +321 -0
  596. package/examples/extensions/sandbox/package-lock.json +92 -0
  597. package/examples/extensions/sandbox/package.json +19 -0
  598. package/examples/extensions/send-user-message.ts +97 -0
  599. package/examples/extensions/session-name.ts +27 -0
  600. package/examples/extensions/shutdown-command.ts +63 -0
  601. package/examples/extensions/snake.ts +343 -0
  602. package/examples/extensions/space-invaders.ts +560 -0
  603. package/examples/extensions/ssh.ts +220 -0
  604. package/examples/extensions/status-line.ts +32 -0
  605. package/examples/extensions/structured-output.ts +65 -0
  606. package/examples/extensions/subagent/README.md +172 -0
  607. package/examples/extensions/subagent/agents/planner.md +37 -0
  608. package/examples/extensions/subagent/agents/reviewer.md +35 -0
  609. package/examples/extensions/subagent/agents/scout.md +50 -0
  610. package/examples/extensions/subagent/agents/worker.md +24 -0
  611. package/examples/extensions/subagent/agents.ts +126 -0
  612. package/examples/extensions/subagent/index.ts +987 -0
  613. package/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
  614. package/examples/extensions/subagent/prompts/implement.md +10 -0
  615. package/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
  616. package/examples/extensions/summarize.ts +206 -0
  617. package/examples/extensions/system-prompt-header.ts +17 -0
  618. package/examples/extensions/tic-tac-toe.ts +1008 -0
  619. package/examples/extensions/timed-confirm.ts +70 -0
  620. package/examples/extensions/titlebar-spinner.ts +58 -0
  621. package/examples/extensions/todo.ts +297 -0
  622. package/examples/extensions/tool-override.ts +144 -0
  623. package/examples/extensions/tools.ts +141 -0
  624. package/examples/extensions/trigger-compact.ts +50 -0
  625. package/examples/extensions/truncated-tool.ts +195 -0
  626. package/examples/extensions/widget-placement.ts +9 -0
  627. package/examples/extensions/with-deps/index.ts +32 -0
  628. package/examples/extensions/with-deps/package-lock.json +31 -0
  629. package/examples/extensions/with-deps/package.json +22 -0
  630. package/examples/extensions/working-indicator.ts +123 -0
  631. package/examples/extensions/working-message-test.ts +25 -0
  632. package/examples/rpc-extension-ui.ts +632 -0
  633. package/examples/sdk/01-minimal.ts +22 -0
  634. package/examples/sdk/02-custom-model.ts +49 -0
  635. package/examples/sdk/03-custom-prompt.ts +62 -0
  636. package/examples/sdk/04-skills.ts +55 -0
  637. package/examples/sdk/05-tools.ts +44 -0
  638. package/examples/sdk/06-extensions.ts +90 -0
  639. package/examples/sdk/07-context-files.ts +42 -0
  640. package/examples/sdk/08-prompt-templates.ts +51 -0
  641. package/examples/sdk/09-api-keys-and-oauth.ts +48 -0
  642. package/examples/sdk/10-settings.ts +53 -0
  643. package/examples/sdk/11-sessions.ts +48 -0
  644. package/examples/sdk/12-full-control.ts +73 -0
  645. package/examples/sdk/13-session-runtime.ts +67 -0
  646. package/examples/sdk/README.md +147 -0
  647. package/extensions/phi/init.ts +15 -1
  648. package/extensions/phi/keys.ts +186 -0
  649. package/extensions/phi/providers/alibaba.ts +126 -0
  650. package/extensions/phi/providers/opencode-go.ts +204 -0
  651. package/extensions/phi/setup.ts +692 -0
  652. package/extensions/phi/smart-router.ts +8 -0
  653. package/package.json +17 -12
  654. package/scripts/copy-assets.sh +0 -0
  655. package/scripts/migrate-sessions.sh +0 -0
@@ -12,24 +12,27 @@
12
12
  *
13
13
  * Modes use this class and add their own I/O layer on top.
14
14
  */
15
- import { readFileSync } from "node:fs";
16
- import { basename, dirname, join } from "node:path";
17
- import { isContextOverflow, modelsAreEqual, resetApiProviders, supportsXhigh } from "phi-code-ai";
18
- import { getDocsPath } from "../config.js";
15
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
16
+ import { basename, dirname, resolve } from "node:path";
17
+ import { clampThinkingLevel, cleanupSessionResources, getSupportedThinkingLevels, isContextOverflow, modelsAreEqual, resetApiProviders, } from "phi-code-ai";
19
18
  import { theme } from "../modes/interactive/theme/theme.js";
20
19
  import { stripFrontmatter } from "../utils/frontmatter.js";
21
20
  import { sleep } from "../utils/sleep.js";
22
- import { executeBash as executeBashCommand, executeBashWithOperations } from "./bash-executor.js";
21
+ import { formatNoApiKeyFoundMessage, formatNoModelSelectedMessage } from "./auth-guidance.js";
22
+ import { executeBashWithOperations } from "./bash-executor.js";
23
23
  import { calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, generateBranchSummary, prepareCompaction, shouldCompact, } from "./compaction/index.js";
24
24
  import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
25
25
  import { exportSessionToHtml } from "./export-html/index.js";
26
26
  import { createToolHtmlRenderer } from "./export-html/tool-renderer.js";
27
- import { ExtensionRunner, wrapRegisteredTools, wrapToolsWithExtensions, } from "./extensions/index.js";
27
+ import { ExtensionRunner, wrapRegisteredTools, } from "./extensions/index.js";
28
+ import { emitSessionShutdownEvent } from "./extensions/runner.js";
28
29
  import { expandPromptTemplate } from "./prompt-templates.js";
29
- import { getLatestCompactionEntry } from "./session-manager.js";
30
- import { BUILTIN_SLASH_COMMANDS } from "./slash-commands.js";
30
+ import { CURRENT_SESSION_VERSION, getLatestCompactionEntry } from "./session-manager.js";
31
+ import { createSyntheticSourceInfo } from "./source-info.js";
31
32
  import { buildSystemPrompt } from "./system-prompt.js";
32
- import { createAllTools } from "./tools/index.js";
33
+ import { createLocalBashOperations } from "./tools/bash.js";
34
+ import { createAllToolDefinitions } from "./tools/index.js";
35
+ import { createToolDefinitionFromAgentTool } from "./tools/tool-definition-wrapper.js";
33
36
  /**
34
37
  * Parse a skill block from message text.
35
38
  * Returns null if the text doesn't contain a skill block.
@@ -50,59 +53,66 @@ export function parseSkillBlock(text) {
50
53
  // ============================================================================
51
54
  /** Standard thinking levels */
52
55
  const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high"];
53
- /** Thinking levels including xhigh (for supported models) */
54
- const THINKING_LEVELS_WITH_XHIGH = ["off", "minimal", "low", "medium", "high", "xhigh"];
55
56
  // ============================================================================
56
57
  // AgentSession Class
57
58
  // ============================================================================
58
59
  export class AgentSession {
60
+ agent;
61
+ sessionManager;
62
+ settingsManager;
63
+ _scopedModels;
64
+ // Event subscription state
65
+ _unsubscribeAgent;
66
+ _eventListeners = [];
67
+ _agentEventQueue = Promise.resolve();
68
+ /** Tracks pending steering messages for UI display. Removed when delivered. */
69
+ _steeringMessages = [];
70
+ /** Tracks pending follow-up messages for UI display. Removed when delivered. */
71
+ _followUpMessages = [];
72
+ /** Messages queued to be included with the next user prompt as context ("asides"). */
73
+ _pendingNextTurnMessages = [];
74
+ // Compaction state
75
+ _compactionAbortController = undefined;
76
+ _autoCompactionAbortController = undefined;
77
+ _overflowRecoveryAttempted = false;
78
+ // Branch summarization state
79
+ _branchSummaryAbortController = undefined;
80
+ // Retry state
81
+ _retryAbortController = undefined;
82
+ _retryAttempt = 0;
83
+ _retryPromise = undefined;
84
+ _retryResolve = undefined;
85
+ // Bash execution state
86
+ _bashAbortController = undefined;
87
+ _pendingBashMessages = [];
88
+ // Extension system
89
+ _extensionRunner;
90
+ _turnIndex = 0;
91
+ _resourceLoader;
92
+ _customTools;
93
+ _baseToolDefinitions = new Map();
94
+ _cwd;
95
+ _extensionRunnerRef;
96
+ _initialActiveToolNames;
97
+ _allowedToolNames;
98
+ _baseToolsOverride;
99
+ _sessionStartEvent;
100
+ _extensionUIContext;
101
+ _extensionCommandContextActions;
102
+ _extensionShutdownHandler;
103
+ _extensionErrorListener;
104
+ _extensionErrorUnsubscriber;
105
+ // Model registry for API key resolution
106
+ _modelRegistry;
107
+ // Tool registry for extension getTools/setTools
108
+ _toolRegistry = new Map();
109
+ _toolDefinitions = new Map();
110
+ _toolPromptSnippets = new Map();
111
+ _toolPromptGuidelines = new Map();
112
+ // Base system prompt (without extension appends) - used to apply fresh appends each turn
113
+ _baseSystemPrompt = "";
114
+ _baseSystemPromptOptions;
59
115
  constructor(config) {
60
- this._eventListeners = [];
61
- this._agentEventQueue = Promise.resolve();
62
- /** Tracks pending steering messages for UI display. Removed when delivered. */
63
- this._steeringMessages = [];
64
- /** Tracks pending follow-up messages for UI display. Removed when delivered. */
65
- this._followUpMessages = [];
66
- /** Messages queued to be included with the next user prompt as context ("asides"). */
67
- this._pendingNextTurnMessages = [];
68
- // Compaction state
69
- this._compactionAbortController = undefined;
70
- this._autoCompactionAbortController = undefined;
71
- this._overflowRecoveryAttempted = false;
72
- // Branch summarization state
73
- this._branchSummaryAbortController = undefined;
74
- // Retry state
75
- this._retryAbortController = undefined;
76
- this._retryAttempt = 0;
77
- this._retryPromise = undefined;
78
- this._retryResolve = undefined;
79
- // Bash execution state
80
- this._bashAbortController = undefined;
81
- this._pendingBashMessages = [];
82
- // Extension system
83
- this._extensionRunner = undefined;
84
- this._turnIndex = 0;
85
- this._baseToolRegistry = new Map();
86
- // Tool registry for extension getTools/setTools
87
- this._toolRegistry = new Map();
88
- this._toolPromptSnippets = new Map();
89
- this._toolPromptGuidelines = new Map();
90
- // Base system prompt (without extension appends) - used to apply fresh appends each turn
91
- this._baseSystemPrompt = "";
92
- // Track last assistant message for auto-compaction check
93
- this._lastAssistantMessage = undefined;
94
- /** Internal handler for agent events - shared by subscribe and reconnect */
95
- this._handleAgentEvent = (event) => {
96
- // Create retry promise synchronously before queueing async processing.
97
- // Agent.emit() calls this handler synchronously, and prompt() calls waitForRetry()
98
- // as soon as agent.prompt() resolves. If _retryPromise is created only inside
99
- // _processAgentEvent, slow earlier queued events can delay agent_end processing
100
- // and waitForRetry() can miss the in-flight retry.
101
- this._createRetryPromiseForAgentEnd(event);
102
- this._agentEventQueue = this._agentEventQueue.then(() => this._processAgentEvent(event), () => this._processAgentEvent(event));
103
- // Keep queue alive if an event handler fails
104
- this._agentEventQueue.catch(() => { });
105
- };
106
116
  this.agent = config.agent;
107
117
  this.sessionManager = config.sessionManager;
108
118
  this.settingsManager = config.settingsManager;
@@ -113,10 +123,13 @@ export class AgentSession {
113
123
  this._modelRegistry = config.modelRegistry;
114
124
  this._extensionRunnerRef = config.extensionRunnerRef;
115
125
  this._initialActiveToolNames = config.initialActiveToolNames;
126
+ this._allowedToolNames = config.allowedToolNames ? new Set(config.allowedToolNames) : undefined;
116
127
  this._baseToolsOverride = config.baseToolsOverride;
128
+ this._sessionStartEvent = config.sessionStartEvent ?? { type: "session_start", reason: "startup" };
117
129
  // Always subscribe to agent events for internal handling
118
130
  // (session persistence, extensions, auto-compaction, retry logic)
119
131
  this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);
132
+ this._installAgentToolHooks();
120
133
  this._buildRuntime({
121
134
  activeToolNames: this._initialActiveToolNames,
122
135
  includeAllExtensionTools: true,
@@ -126,6 +139,79 @@ export class AgentSession {
126
139
  get modelRegistry() {
127
140
  return this._modelRegistry;
128
141
  }
142
+ async _getRequiredRequestAuth(model) {
143
+ const result = await this._modelRegistry.getApiKeyAndHeaders(model);
144
+ if (!result.ok) {
145
+ if (result.error.startsWith("No API key found")) {
146
+ throw new Error(formatNoApiKeyFoundMessage(model.provider));
147
+ }
148
+ throw new Error(result.error);
149
+ }
150
+ if (result.apiKey) {
151
+ return { apiKey: result.apiKey, headers: result.headers };
152
+ }
153
+ const isOAuth = this._modelRegistry.isUsingOAuth(model);
154
+ if (isOAuth) {
155
+ throw new Error(`Authentication failed for "${model.provider}". ` +
156
+ `Credentials may have expired or network is unavailable. ` +
157
+ `Run '/login ${model.provider}' to re-authenticate.`);
158
+ }
159
+ throw new Error(formatNoApiKeyFoundMessage(model.provider));
160
+ }
161
+ /**
162
+ * Install tool hooks once on the Agent instance.
163
+ *
164
+ * The callbacks read `this._extensionRunner` at execution time, so extension reload swaps in the
165
+ * new runner without reinstalling hooks. Extension-specific tool wrappers are still used to adapt
166
+ * registered tool execution to the extension context. Tool call and tool result interception now
167
+ * happens here instead of in wrappers.
168
+ */
169
+ _installAgentToolHooks() {
170
+ this.agent.beforeToolCall = async ({ toolCall, args }) => {
171
+ const runner = this._extensionRunner;
172
+ if (!runner.hasHandlers("tool_call")) {
173
+ return undefined;
174
+ }
175
+ await this._agentEventQueue;
176
+ try {
177
+ return await runner.emitToolCall({
178
+ type: "tool_call",
179
+ toolName: toolCall.name,
180
+ toolCallId: toolCall.id,
181
+ input: args,
182
+ });
183
+ }
184
+ catch (err) {
185
+ if (err instanceof Error) {
186
+ throw err;
187
+ }
188
+ throw new Error(`Extension failed, blocking execution: ${String(err)}`);
189
+ }
190
+ };
191
+ this.agent.afterToolCall = async ({ toolCall, args, result, isError }) => {
192
+ const runner = this._extensionRunner;
193
+ if (!runner.hasHandlers("tool_result")) {
194
+ return undefined;
195
+ }
196
+ const hookResult = await runner.emitToolResult({
197
+ type: "tool_result",
198
+ toolName: toolCall.name,
199
+ toolCallId: toolCall.id,
200
+ input: args,
201
+ content: result.content,
202
+ details: result.details,
203
+ isError,
204
+ });
205
+ if (!hookResult) {
206
+ return undefined;
207
+ }
208
+ return {
209
+ content: hookResult.content,
210
+ details: hookResult.details,
211
+ isError: hookResult.isError ?? isError,
212
+ };
213
+ };
214
+ }
129
215
  // =========================================================================
130
216
  // Event Subscription
131
217
  // =========================================================================
@@ -135,6 +221,27 @@ export class AgentSession {
135
221
  l(event);
136
222
  }
137
223
  }
224
+ _emitQueueUpdate() {
225
+ this._emit({
226
+ type: "queue_update",
227
+ steering: [...this._steeringMessages],
228
+ followUp: [...this._followUpMessages],
229
+ });
230
+ }
231
+ // Track last assistant message for auto-compaction check
232
+ _lastAssistantMessage = undefined;
233
+ /** Internal handler for agent events - shared by subscribe and reconnect */
234
+ _handleAgentEvent = (event) => {
235
+ // Create retry promise synchronously before queueing async processing.
236
+ // Agent.emit() calls this handler synchronously, and prompt() calls waitForRetry()
237
+ // as soon as agent.prompt() resolves. If _retryPromise is created only inside
238
+ // _processAgentEvent, slow earlier queued events can delay agent_end processing
239
+ // and waitForRetry() can miss the in-flight retry.
240
+ this._createRetryPromiseForAgentEnd(event);
241
+ this._agentEventQueue = this._agentEventQueue.then(() => this._processAgentEvent(event), () => this._processAgentEvent(event));
242
+ // Keep queue alive if an event handler fails
243
+ this._agentEventQueue.catch(() => { });
244
+ };
138
245
  _createRetryPromiseForAgentEnd(event) {
139
246
  if (event.type !== "agent_end" || this._retryPromise) {
140
247
  return;
@@ -171,12 +278,14 @@ export class AgentSession {
171
278
  const steeringIndex = this._steeringMessages.indexOf(messageText);
172
279
  if (steeringIndex !== -1) {
173
280
  this._steeringMessages.splice(steeringIndex, 1);
281
+ this._emitQueueUpdate();
174
282
  }
175
283
  else {
176
284
  // Check follow-up queue
177
285
  const followUpIndex = this._followUpMessages.indexOf(messageText);
178
286
  if (followUpIndex !== -1) {
179
287
  this._followUpMessages.splice(followUpIndex, 1);
288
+ this._emitQueueUpdate();
180
289
  }
181
290
  }
182
291
  }
@@ -215,7 +324,6 @@ export class AgentSession {
215
324
  attempt: this._retryAttempt,
216
325
  });
217
326
  this._retryAttempt = 0;
218
- this._resolveRetry();
219
327
  }
220
328
  }
221
329
  }
@@ -229,6 +337,7 @@ export class AgentSession {
229
337
  if (didRetry)
230
338
  return; // Retry was initiated, don't proceed to compaction
231
339
  }
340
+ this._resolveRetry();
232
341
  await this._checkCompaction(msg);
233
342
  }
234
343
  }
@@ -261,10 +370,22 @@ export class AgentSession {
261
370
  }
262
371
  return undefined;
263
372
  }
373
+ _replaceMessageInPlace(target, replacement) {
374
+ // Agent-core stores the finalized message object in its state before emitting message_end.
375
+ // SessionManager persistence happens later in _processAgentEvent() with event.message.
376
+ // Mutating this object in place keeps agent state, later turn/agent events, listeners,
377
+ // and the eventual SessionManager.appendMessage(event.message) persistence in sync.
378
+ if (target === replacement) {
379
+ return;
380
+ }
381
+ const targetRecord = target;
382
+ for (const key of Object.keys(targetRecord)) {
383
+ delete targetRecord[key];
384
+ }
385
+ Object.assign(targetRecord, replacement);
386
+ }
264
387
  /** Emit extension events based on agent events */
265
388
  async _emitExtensionEvent(event) {
266
- if (!this._extensionRunner)
267
- return;
268
389
  if (event.type === "agent_start") {
269
390
  this._turnIndex = 0;
270
391
  await this._extensionRunner.emit({ type: "agent_start" });
@@ -310,7 +431,10 @@ export class AgentSession {
310
431
  type: "message_end",
311
432
  message: event.message,
312
433
  };
313
- await this._extensionRunner.emit(extensionEvent);
434
+ const replacement = await this._extensionRunner.emitMessageEnd(extensionEvent);
435
+ if (replacement) {
436
+ this._replaceMessageInPlace(event.message, replacement);
437
+ }
314
438
  }
315
439
  else if (event.type === "tool_execution_start") {
316
440
  const extensionEvent = {
@@ -382,8 +506,10 @@ export class AgentSession {
382
506
  * Call this when completely done with the session.
383
507
  */
384
508
  dispose() {
509
+ this._extensionRunner.invalidate("This extension ctx is stale after session replacement or reload. Do not use a captured pi or command ctx after ctx.newSession(), ctx.fork(), ctx.switchSession(), or ctx.reload(). For newSession, fork, and switchSession, move post-replacement work into withSession and use the ctx passed to withSession. For reload, do not use the old ctx after await ctx.reload().");
385
510
  this._disconnectFromAgent();
386
511
  this._eventListeners = [];
512
+ cleanupSessionResources(this.sessionId);
387
513
  }
388
514
  // =========================================================================
389
515
  // Read-only State Access
@@ -420,15 +546,19 @@ export class AgentSession {
420
546
  return this.agent.state.tools.map((t) => t.name);
421
547
  }
422
548
  /**
423
- * Get all configured tools with name, description, and parameter schema.
549
+ * Get all configured tools with name, description, parameter schema, and source metadata.
424
550
  */
425
551
  getAllTools() {
426
- return Array.from(this._toolRegistry.values()).map((t) => ({
427
- name: t.name,
428
- description: t.description,
429
- parameters: t.parameters,
552
+ return Array.from(this._toolDefinitions.values()).map(({ definition, sourceInfo }) => ({
553
+ name: definition.name,
554
+ description: definition.description,
555
+ parameters: definition.parameters,
556
+ sourceInfo,
430
557
  }));
431
558
  }
559
+ getToolDefinition(name) {
560
+ return this._toolDefinitions.get(name)?.definition;
561
+ }
432
562
  /**
433
563
  * Set active tools by name.
434
564
  * Only tools in the registry can be enabled. Unknown tool names are ignored.
@@ -445,10 +575,10 @@ export class AgentSession {
445
575
  validToolNames.push(name);
446
576
  }
447
577
  }
448
- this.agent.setTools(tools);
578
+ this.agent.state.tools = tools;
449
579
  // Rebuild base system prompt with new tool set
450
580
  this._baseSystemPrompt = this._rebuildSystemPrompt(validToolNames);
451
- this.agent.setSystemPrompt(this._baseSystemPrompt);
581
+ this.agent.state.systemPrompt = this._baseSystemPrompt;
452
582
  }
453
583
  /** Whether compaction or branch summarization is currently running */
454
584
  get isCompacting() {
@@ -462,11 +592,11 @@ export class AgentSession {
462
592
  }
463
593
  /** Current steering mode */
464
594
  get steeringMode() {
465
- return this.agent.getSteeringMode();
595
+ return this.agent.steeringMode;
466
596
  }
467
597
  /** Current follow-up mode */
468
598
  get followUpMode() {
469
- return this.agent.getFollowUpMode();
599
+ return this.agent.followUpMode;
470
600
  }
471
601
  /** Current session file path, or undefined if sessions are disabled */
472
602
  get sessionFile() {
@@ -533,7 +663,7 @@ export class AgentSession {
533
663
  const appendSystemPrompt = loaderAppendSystemPrompt.length > 0 ? loaderAppendSystemPrompt.join("\n\n") : undefined;
534
664
  const loadedSkills = this._resourceLoader.getSkills().skills;
535
665
  const loadedContextFiles = this._resourceLoader.getAgentsFiles().agentsFiles;
536
- return buildSystemPrompt({
666
+ this._baseSystemPromptOptions = {
537
667
  cwd: this._cwd,
538
668
  skills: loadedSkills,
539
669
  contextFiles: loadedContextFiles,
@@ -542,7 +672,8 @@ export class AgentSession {
542
672
  selectedTools: validToolNames,
543
673
  toolSnippets,
544
674
  promptGuidelines,
545
- });
675
+ };
676
+ return buildSystemPrompt(this._baseSystemPromptOptions);
546
677
  }
547
678
  // =========================================================================
548
679
  // Prompting
@@ -558,92 +689,92 @@ export class AgentSession {
558
689
  */
559
690
  async prompt(text, options) {
560
691
  const expandPromptTemplates = options?.expandPromptTemplates ?? true;
561
- // Handle extension commands first (execute immediately, even during streaming)
562
- // Extension commands manage their own LLM interaction via pi.sendMessage()
563
- if (expandPromptTemplates && text.startsWith("/")) {
564
- const handled = await this._tryExecuteExtensionCommand(text);
565
- if (handled) {
566
- // Extension command executed, no prompt to send
567
- return;
568
- }
569
- }
570
- // Emit input event for extension interception (before skill/template expansion)
571
- let currentText = text;
572
- let currentImages = options?.images;
573
- if (this._extensionRunner?.hasHandlers("input")) {
574
- const inputResult = await this._extensionRunner.emitInput(currentText, currentImages, options?.source ?? "interactive");
575
- if (inputResult.action === "handled") {
576
- return;
692
+ const preflightResult = options?.preflightResult;
693
+ let messages;
694
+ try {
695
+ // Handle extension commands first (execute immediately, even during streaming)
696
+ // Extension commands manage their own LLM interaction via pi.sendMessage()
697
+ if (expandPromptTemplates && text.startsWith("/")) {
698
+ const handled = await this._tryExecuteExtensionCommand(text);
699
+ if (handled) {
700
+ // Extension command executed, no prompt to send
701
+ preflightResult?.(true);
702
+ return;
703
+ }
577
704
  }
578
- if (inputResult.action === "transform") {
579
- currentText = inputResult.text;
580
- currentImages = inputResult.images ?? currentImages;
705
+ // Emit input event for extension interception (before skill/template expansion)
706
+ let currentText = text;
707
+ let currentImages = options?.images;
708
+ if (this._extensionRunner.hasHandlers("input")) {
709
+ const inputResult = await this._extensionRunner.emitInput(currentText, currentImages, options?.source ?? "interactive");
710
+ if (inputResult.action === "handled") {
711
+ preflightResult?.(true);
712
+ return;
713
+ }
714
+ if (inputResult.action === "transform") {
715
+ currentText = inputResult.text;
716
+ currentImages = inputResult.images ?? currentImages;
717
+ }
581
718
  }
582
- }
583
- // Expand skill commands (/skill:name args) and prompt templates (/template args)
584
- let expandedText = currentText;
585
- if (expandPromptTemplates) {
586
- expandedText = this._expandSkillCommand(expandedText);
587
- expandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);
588
- }
589
- // If streaming, queue via steer() or followUp() based on option
590
- if (this.isStreaming) {
591
- if (!options?.streamingBehavior) {
592
- throw new Error("Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.");
719
+ // Expand skill commands (/skill:name args) and prompt templates (/template args)
720
+ let expandedText = currentText;
721
+ if (expandPromptTemplates) {
722
+ expandedText = this._expandSkillCommand(expandedText);
723
+ expandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);
593
724
  }
594
- if (options.streamingBehavior === "followUp") {
595
- await this._queueFollowUp(expandedText, currentImages);
725
+ // If streaming, queue via steer() or followUp() based on option
726
+ if (this.isStreaming) {
727
+ if (!options?.streamingBehavior) {
728
+ throw new Error("Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.");
729
+ }
730
+ if (options.streamingBehavior === "followUp") {
731
+ await this._queueFollowUp(expandedText, currentImages);
732
+ }
733
+ else {
734
+ await this._queueSteer(expandedText, currentImages);
735
+ }
736
+ preflightResult?.(true);
737
+ return;
596
738
  }
597
- else {
598
- await this._queueSteer(expandedText, currentImages);
739
+ // Flush any pending bash messages before the new prompt
740
+ this._flushPendingBashMessages();
741
+ // Validate model
742
+ if (!this.model) {
743
+ throw new Error(formatNoModelSelectedMessage());
744
+ }
745
+ if (!this._modelRegistry.hasConfiguredAuth(this.model)) {
746
+ const isOAuth = this._modelRegistry.isUsingOAuth(this.model);
747
+ if (isOAuth) {
748
+ throw new Error(`Authentication failed for "${this.model.provider}". ` +
749
+ `Credentials may have expired or network is unavailable. ` +
750
+ `Run '/login ${this.model.provider}' to re-authenticate.`);
751
+ }
752
+ throw new Error(formatNoApiKeyFoundMessage(this.model.provider));
753
+ }
754
+ // Check if we need to compact before sending (catches aborted responses)
755
+ const lastAssistant = this._findLastAssistantMessage();
756
+ if (lastAssistant) {
757
+ await this._checkCompaction(lastAssistant, false);
758
+ }
759
+ // Build messages array (custom message if any, then user message)
760
+ messages = [];
761
+ // Add user message
762
+ const userContent = [{ type: "text", text: expandedText }];
763
+ if (currentImages) {
764
+ userContent.push(...currentImages);
765
+ }
766
+ messages.push({
767
+ role: "user",
768
+ content: userContent,
769
+ timestamp: Date.now(),
770
+ });
771
+ // Inject any pending "nextTurn" messages as context alongside the user message
772
+ for (const msg of this._pendingNextTurnMessages) {
773
+ messages.push(msg);
599
774
  }
600
- return;
601
- }
602
- // Flush any pending bash messages before the new prompt
603
- this._flushPendingBashMessages();
604
- // Validate model
605
- if (!this.model) {
606
- throw new Error("No model selected.\n\n" +
607
- `Use /login or set an API key environment variable. See ${join(getDocsPath(), "providers.md")}\n\n` +
608
- "Then use /model to select a model.");
609
- }
610
- // Validate API key
611
- const apiKey = await this._modelRegistry.getApiKey(this.model);
612
- if (!apiKey) {
613
- const isOAuth = this._modelRegistry.isUsingOAuth(this.model);
614
- if (isOAuth) {
615
- throw new Error(`Authentication failed for "${this.model.provider}". ` +
616
- `Credentials may have expired or network is unavailable. ` +
617
- `Run '/login ${this.model.provider}' to re-authenticate.`);
618
- }
619
- throw new Error(`No API key found for ${this.model.provider}.\n\n` +
620
- `Use /login or set an API key environment variable. See ${join(getDocsPath(), "providers.md")}`);
621
- }
622
- // Check if we need to compact before sending (catches aborted responses)
623
- const lastAssistant = this._findLastAssistantMessage();
624
- if (lastAssistant) {
625
- await this._checkCompaction(lastAssistant, false);
626
- }
627
- // Build messages array (custom message if any, then user message)
628
- const messages = [];
629
- // Add user message
630
- const userContent = [{ type: "text", text: expandedText }];
631
- if (currentImages) {
632
- userContent.push(...currentImages);
633
- }
634
- messages.push({
635
- role: "user",
636
- content: userContent,
637
- timestamp: Date.now(),
638
- });
639
- // Inject any pending "nextTurn" messages as context alongside the user message
640
- for (const msg of this._pendingNextTurnMessages) {
641
- messages.push(msg);
642
- }
643
- this._pendingNextTurnMessages = [];
644
- // Emit before_agent_start extension event
645
- if (this._extensionRunner) {
646
- const result = await this._extensionRunner.emitBeforeAgentStart(expandedText, currentImages, this._baseSystemPrompt);
775
+ this._pendingNextTurnMessages = [];
776
+ // Emit before_agent_start extension event
777
+ const result = await this._extensionRunner.emitBeforeAgentStart(expandedText, currentImages, this._baseSystemPrompt, this._baseSystemPromptOptions);
647
778
  // Add all custom messages from extensions
648
779
  if (result?.messages) {
649
780
  for (const msg of result.messages) {
@@ -659,13 +790,21 @@ export class AgentSession {
659
790
  }
660
791
  // Apply extension-modified system prompt, or reset to base
661
792
  if (result?.systemPrompt) {
662
- this.agent.setSystemPrompt(result.systemPrompt);
793
+ this.agent.state.systemPrompt = result.systemPrompt;
663
794
  }
664
795
  else {
665
796
  // Ensure we're using the base prompt (in case previous turn had modifications)
666
- this.agent.setSystemPrompt(this._baseSystemPrompt);
797
+ this.agent.state.systemPrompt = this._baseSystemPrompt;
667
798
  }
668
799
  }
800
+ catch (error) {
801
+ preflightResult?.(false);
802
+ throw error;
803
+ }
804
+ if (!messages) {
805
+ return;
806
+ }
807
+ preflightResult?.(true);
669
808
  await this.agent.prompt(messages);
670
809
  await this.waitForRetry();
671
810
  }
@@ -673,8 +812,6 @@ export class AgentSession {
673
812
  * Try to execute an extension command. Returns true if command was found and executed.
674
813
  */
675
814
  async _tryExecuteExtensionCommand(text) {
676
- if (!this._extensionRunner)
677
- return false;
678
815
  // Parse command name and args
679
816
  const spaceIndex = text.indexOf(" ");
680
817
  const commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);
@@ -720,7 +857,7 @@ export class AgentSession {
720
857
  }
721
858
  catch (err) {
722
859
  // Emit error like extension commands do
723
- this._extensionRunner?.emitError({
860
+ this._extensionRunner.emitError({
724
861
  extensionPath: skill.filePath,
725
862
  event: "skill_expansion",
726
863
  error: err instanceof Error ? err.message : String(err),
@@ -729,8 +866,9 @@ export class AgentSession {
729
866
  }
730
867
  }
731
868
  /**
732
- * Queue a steering message to interrupt the agent mid-run.
733
- * Delivered after current tool execution, skips remaining tools.
869
+ * Queue a steering message while the agent is running.
870
+ * Delivered after the current assistant turn finishes executing its tool calls,
871
+ * before the next LLM call.
734
872
  * Expands skill commands and prompt templates. Errors on extension commands.
735
873
  * @param images Optional image attachments to include with the message
736
874
  * @throws Error if text is an extension command
@@ -767,6 +905,7 @@ export class AgentSession {
767
905
  */
768
906
  async _queueSteer(text, images) {
769
907
  this._steeringMessages.push(text);
908
+ this._emitQueueUpdate();
770
909
  const content = [{ type: "text", text }];
771
910
  if (images) {
772
911
  content.push(...images);
@@ -782,6 +921,7 @@ export class AgentSession {
782
921
  */
783
922
  async _queueFollowUp(text, images) {
784
923
  this._followUpMessages.push(text);
924
+ this._emitQueueUpdate();
785
925
  const content = [{ type: "text", text }];
786
926
  if (images) {
787
927
  content.push(...images);
@@ -796,8 +936,6 @@ export class AgentSession {
796
936
  * Throw an error if the text is an extension command.
797
937
  */
798
938
  _throwIfExtensionCommand(text) {
799
- if (!this._extensionRunner)
800
- return;
801
939
  const spaceIndex = text.indexOf(" ");
802
940
  const commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);
803
941
  const command = this._extensionRunner.getCommand(commandName);
@@ -841,7 +979,7 @@ export class AgentSession {
841
979
  await this.agent.prompt(appMessage);
842
980
  }
843
981
  else {
844
- this.agent.appendMessage(appMessage);
982
+ this.agent.state.messages.push(appMessage);
845
983
  this.sessionManager.appendCustomMessageEntry(message.customType, message.content, message.display, message.details);
846
984
  this._emit({ type: "message_start", message: appMessage });
847
985
  this._emit({ type: "message_end", message: appMessage });
@@ -895,6 +1033,7 @@ export class AgentSession {
895
1033
  this._steeringMessages = [];
896
1034
  this._followUpMessages = [];
897
1035
  this.agent.clearAllQueues();
1036
+ this._emitQueueUpdate();
898
1037
  return { steering, followUp };
899
1038
  }
900
1039
  /** Number of pending messages (includes both steering and follow-up) */
@@ -920,60 +1059,10 @@ export class AgentSession {
920
1059
  this.agent.abort();
921
1060
  await this.agent.waitForIdle();
922
1061
  }
923
- /**
924
- * Start a new session, optionally with initial messages and parent tracking.
925
- * Clears all messages and starts a new session.
926
- * Listeners are preserved and will continue receiving events.
927
- * @param options.parentSession - Optional parent session path for tracking
928
- * @param options.setup - Optional callback to initialize session (e.g., append messages)
929
- * @returns true if completed, false if cancelled by extension
930
- */
931
- async newSession(options) {
932
- const previousSessionFile = this.sessionFile;
933
- // Emit session_before_switch event with reason "new" (can be cancelled)
934
- if (this._extensionRunner?.hasHandlers("session_before_switch")) {
935
- const result = (await this._extensionRunner.emit({
936
- type: "session_before_switch",
937
- reason: "new",
938
- }));
939
- if (result?.cancel) {
940
- return false;
941
- }
942
- }
943
- this._disconnectFromAgent();
944
- await this.abort();
945
- this.agent.reset();
946
- this.sessionManager.newSession({ parentSession: options?.parentSession });
947
- this.agent.sessionId = this.sessionManager.getSessionId();
948
- this._steeringMessages = [];
949
- this._followUpMessages = [];
950
- this._pendingNextTurnMessages = [];
951
- this.sessionManager.appendThinkingLevelChange(this.thinkingLevel);
952
- // Run setup callback if provided (e.g., to append initial messages)
953
- if (options?.setup) {
954
- await options.setup(this.sessionManager);
955
- // Sync agent state with session manager after setup
956
- const sessionContext = this.sessionManager.buildSessionContext();
957
- this.agent.replaceMessages(sessionContext.messages);
958
- }
959
- this._reconnectToAgent();
960
- // Emit session_switch event with reason "new" to extensions
961
- if (this._extensionRunner) {
962
- await this._extensionRunner.emit({
963
- type: "session_switch",
964
- reason: "new",
965
- previousSessionFile,
966
- });
967
- }
968
- // Emit session event to custom tools
969
- return true;
970
- }
971
1062
  // =========================================================================
972
1063
  // Model Management
973
1064
  // =========================================================================
974
1065
  async _emitModelSelect(nextModel, previousModel, source) {
975
- if (!this._extensionRunner)
976
- return;
977
1066
  if (modelsAreEqual(previousModel, nextModel))
978
1067
  return;
979
1068
  await this._extensionRunner.emit({
@@ -985,17 +1074,16 @@ export class AgentSession {
985
1074
  }
986
1075
  /**
987
1076
  * Set model directly.
988
- * Validates API key, saves to session and settings.
989
- * @throws Error if no API key available for the model
1077
+ * Validates that auth is configured, saves to session and settings.
1078
+ * @throws Error if no auth is configured for the model
990
1079
  */
991
1080
  async setModel(model) {
992
- const apiKey = await this._modelRegistry.getApiKey(model);
993
- if (!apiKey) {
1081
+ if (!this._modelRegistry.hasConfiguredAuth(model)) {
994
1082
  throw new Error(`No API key for ${model.provider}/${model.id}`);
995
1083
  }
996
1084
  const previousModel = this.model;
997
1085
  const thinkingLevel = this._getThinkingLevelForModelSwitch();
998
- this.agent.setModel(model);
1086
+ this.agent.state.model = model;
999
1087
  this.sessionManager.appendModelChange(model.provider, model.id);
1000
1088
  this.settingsManager.setDefaultModelAndProvider(model.provider, model.id);
1001
1089
  // Re-clamp thinking level for new model's capabilities
@@ -1014,27 +1102,8 @@ export class AgentSession {
1014
1102
  }
1015
1103
  return this._cycleAvailableModel(direction);
1016
1104
  }
1017
- async _getScopedModelsWithApiKey() {
1018
- const apiKeysByProvider = new Map();
1019
- const result = [];
1020
- for (const scoped of this._scopedModels) {
1021
- const provider = scoped.model.provider;
1022
- let apiKey;
1023
- if (apiKeysByProvider.has(provider)) {
1024
- apiKey = apiKeysByProvider.get(provider);
1025
- }
1026
- else {
1027
- apiKey = await this._modelRegistry.getApiKeyForProvider(provider);
1028
- apiKeysByProvider.set(provider, apiKey);
1029
- }
1030
- if (apiKey) {
1031
- result.push(scoped);
1032
- }
1033
- }
1034
- return result;
1035
- }
1036
1105
  async _cycleScopedModel(direction) {
1037
- const scopedModels = await this._getScopedModelsWithApiKey();
1106
+ const scopedModels = this._scopedModels.filter((scoped) => this._modelRegistry.hasConfiguredAuth(scoped.model));
1038
1107
  if (scopedModels.length <= 1)
1039
1108
  return undefined;
1040
1109
  const currentModel = this.model;
@@ -1046,7 +1115,7 @@ export class AgentSession {
1046
1115
  const next = scopedModels[nextIndex];
1047
1116
  const thinkingLevel = this._getThinkingLevelForModelSwitch(next.thinkingLevel);
1048
1117
  // Apply model
1049
- this.agent.setModel(next.model);
1118
+ this.agent.state.model = next.model;
1050
1119
  this.sessionManager.appendModelChange(next.model.provider, next.model.id);
1051
1120
  this.settingsManager.setDefaultModelAndProvider(next.model.provider, next.model.id);
1052
1121
  // Apply thinking level.
@@ -1068,12 +1137,8 @@ export class AgentSession {
1068
1137
  const len = availableModels.length;
1069
1138
  const nextIndex = direction === "forward" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;
1070
1139
  const nextModel = availableModels[nextIndex];
1071
- const apiKey = await this._modelRegistry.getApiKey(nextModel);
1072
- if (!apiKey) {
1073
- throw new Error(`No API key for ${nextModel.provider}/${nextModel.id}`);
1074
- }
1075
1140
  const thinkingLevel = this._getThinkingLevelForModelSwitch();
1076
- this.agent.setModel(nextModel);
1141
+ this.agent.state.model = nextModel;
1077
1142
  this.sessionManager.appendModelChange(nextModel.provider, nextModel.id);
1078
1143
  this.settingsManager.setDefaultModelAndProvider(nextModel.provider, nextModel.id);
1079
1144
  // Re-clamp thinking level for new model's capabilities
@@ -1093,13 +1158,20 @@ export class AgentSession {
1093
1158
  const availableLevels = this.getAvailableThinkingLevels();
1094
1159
  const effectiveLevel = availableLevels.includes(level) ? level : this._clampThinkingLevel(level, availableLevels);
1095
1160
  // Only persist if actually changing
1096
- const isChanging = effectiveLevel !== this.agent.state.thinkingLevel;
1097
- this.agent.setThinkingLevel(effectiveLevel);
1161
+ const previousLevel = this.agent.state.thinkingLevel;
1162
+ const isChanging = effectiveLevel !== previousLevel;
1163
+ this.agent.state.thinkingLevel = effectiveLevel;
1098
1164
  if (isChanging) {
1099
1165
  this.sessionManager.appendThinkingLevelChange(effectiveLevel);
1100
1166
  if (this.supportsThinking() || effectiveLevel !== "off") {
1101
1167
  this.settingsManager.setDefaultThinkingLevel(effectiveLevel);
1102
1168
  }
1169
+ this._emit({ type: "thinking_level_changed", level: effectiveLevel });
1170
+ void this._extensionRunner.emit({
1171
+ type: "thinking_level_select",
1172
+ level: effectiveLevel,
1173
+ previousLevel,
1174
+ });
1103
1175
  }
1104
1176
  }
1105
1177
  /**
@@ -1121,15 +1193,9 @@ export class AgentSession {
1121
1193
  * The provider will clamp to what the specific model supports internally.
1122
1194
  */
1123
1195
  getAvailableThinkingLevels() {
1124
- if (!this.supportsThinking())
1125
- return ["off"];
1126
- return this.supportsXhighThinking() ? THINKING_LEVELS_WITH_XHIGH : THINKING_LEVELS;
1127
- }
1128
- /**
1129
- * Check if current model supports xhigh thinking level.
1130
- */
1131
- supportsXhighThinking() {
1132
- return this.model ? supportsXhigh(this.model) : false;
1196
+ if (!this.model)
1197
+ return THINKING_LEVELS;
1198
+ return getSupportedThinkingLevels(this.model);
1133
1199
  }
1134
1200
  /**
1135
1201
  * Check if current model supports thinking/reasoning.
@@ -1146,24 +1212,8 @@ export class AgentSession {
1146
1212
  }
1147
1213
  return this.thinkingLevel;
1148
1214
  }
1149
- _clampThinkingLevel(level, availableLevels) {
1150
- const ordered = THINKING_LEVELS_WITH_XHIGH;
1151
- const available = new Set(availableLevels);
1152
- const requestedIndex = ordered.indexOf(level);
1153
- if (requestedIndex === -1) {
1154
- return availableLevels[0] ?? "off";
1155
- }
1156
- for (let i = requestedIndex; i < ordered.length; i++) {
1157
- const candidate = ordered[i];
1158
- if (available.has(candidate))
1159
- return candidate;
1160
- }
1161
- for (let i = requestedIndex - 1; i >= 0; i--) {
1162
- const candidate = ordered[i];
1163
- if (available.has(candidate))
1164
- return candidate;
1165
- }
1166
- return availableLevels[0] ?? "off";
1215
+ _clampThinkingLevel(level, _availableLevels) {
1216
+ return this.model ? clampThinkingLevel(this.model, level) : "off";
1167
1217
  }
1168
1218
  // =========================================================================
1169
1219
  // Queue Mode Management
@@ -1173,7 +1223,7 @@ export class AgentSession {
1173
1223
  * Saves to settings.
1174
1224
  */
1175
1225
  setSteeringMode(mode) {
1176
- this.agent.setSteeringMode(mode);
1226
+ this.agent.steeringMode = mode;
1177
1227
  this.settingsManager.setSteeringMode(mode);
1178
1228
  }
1179
1229
  /**
@@ -1181,7 +1231,7 @@ export class AgentSession {
1181
1231
  * Saves to settings.
1182
1232
  */
1183
1233
  setFollowUpMode(mode) {
1184
- this.agent.setFollowUpMode(mode);
1234
+ this.agent.followUpMode = mode;
1185
1235
  this.settingsManager.setFollowUpMode(mode);
1186
1236
  }
1187
1237
  // =========================================================================
@@ -1196,14 +1246,12 @@ export class AgentSession {
1196
1246
  this._disconnectFromAgent();
1197
1247
  await this.abort();
1198
1248
  this._compactionAbortController = new AbortController();
1249
+ this._emit({ type: "compaction_start", reason: "manual" });
1199
1250
  try {
1200
1251
  if (!this.model) {
1201
- throw new Error("No model selected");
1202
- }
1203
- const apiKey = await this._modelRegistry.getApiKey(this.model);
1204
- if (!apiKey) {
1205
- throw new Error(`No API key for ${this.model.provider}`);
1252
+ throw new Error(formatNoModelSelectedMessage());
1206
1253
  }
1254
+ const { apiKey, headers } = await this._getRequiredRequestAuth(this.model);
1207
1255
  const pathEntries = this.sessionManager.getBranch();
1208
1256
  const settings = this.settingsManager.getCompactionSettings();
1209
1257
  const preparation = prepareCompaction(pathEntries, settings);
@@ -1217,7 +1265,7 @@ export class AgentSession {
1217
1265
  }
1218
1266
  let extensionCompaction;
1219
1267
  let fromExtension = false;
1220
- if (this._extensionRunner?.hasHandlers("session_before_compact")) {
1268
+ if (this._extensionRunner.hasHandlers("session_before_compact")) {
1221
1269
  const result = (await this._extensionRunner.emit({
1222
1270
  type: "session_before_compact",
1223
1271
  preparation,
@@ -1246,7 +1294,7 @@ export class AgentSession {
1246
1294
  }
1247
1295
  else {
1248
1296
  // Generate compaction result
1249
- const result = await compact(preparation, this.model, apiKey, customInstructions, this._compactionAbortController.signal);
1297
+ const result = await compact(preparation, this.model, apiKey, headers, customInstructions, this._compactionAbortController.signal, this.thinkingLevel);
1250
1298
  summary = result.summary;
1251
1299
  firstKeptEntryId = result.firstKeptEntryId;
1252
1300
  tokensBefore = result.tokensBefore;
@@ -1258,7 +1306,7 @@ export class AgentSession {
1258
1306
  this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
1259
1307
  const newEntries = this.sessionManager.getEntries();
1260
1308
  const sessionContext = this.sessionManager.buildSessionContext();
1261
- this.agent.replaceMessages(sessionContext.messages);
1309
+ this.agent.state.messages = sessionContext.messages;
1262
1310
  // Get the saved compaction entry for the extension event
1263
1311
  const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
1264
1312
  if (this._extensionRunner && savedCompactionEntry) {
@@ -1268,12 +1316,33 @@ export class AgentSession {
1268
1316
  fromExtension,
1269
1317
  });
1270
1318
  }
1271
- return {
1319
+ const compactionResult = {
1272
1320
  summary,
1273
1321
  firstKeptEntryId,
1274
1322
  tokensBefore,
1275
1323
  details,
1276
1324
  };
1325
+ this._emit({
1326
+ type: "compaction_end",
1327
+ reason: "manual",
1328
+ result: compactionResult,
1329
+ aborted: false,
1330
+ willRetry: false,
1331
+ });
1332
+ return compactionResult;
1333
+ }
1334
+ catch (error) {
1335
+ const message = error instanceof Error ? error.message : String(error);
1336
+ const aborted = message === "Compaction cancelled" || (error instanceof Error && error.name === "AbortError");
1337
+ this._emit({
1338
+ type: "compaction_end",
1339
+ reason: "manual",
1340
+ result: undefined,
1341
+ aborted,
1342
+ willRetry: false,
1343
+ errorMessage: aborted ? undefined : `Compaction failed: ${message}`,
1344
+ });
1345
+ throw error;
1277
1346
  }
1278
1347
  finally {
1279
1348
  this._compactionAbortController = undefined;
@@ -1329,7 +1398,8 @@ export class AgentSession {
1329
1398
  if (sameModel && isContextOverflow(assistantMessage, contextWindow)) {
1330
1399
  if (this._overflowRecoveryAttempted) {
1331
1400
  this._emit({
1332
- type: "auto_compaction_end",
1401
+ type: "compaction_end",
1402
+ reason: "overflow",
1333
1403
  result: undefined,
1334
1404
  aborted: false,
1335
1405
  willRetry: false,
@@ -1342,7 +1412,7 @@ export class AgentSession {
1342
1412
  // but we don't want it in context for the retry)
1343
1413
  const messages = this.agent.state.messages;
1344
1414
  if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
1345
- this.agent.replaceMessages(messages.slice(0, -1));
1415
+ this.agent.state.messages = messages.slice(0, -1);
1346
1416
  }
1347
1417
  await this._runAutoCompaction("overflow", true);
1348
1418
  return;
@@ -1379,27 +1449,46 @@ export class AgentSession {
1379
1449
  */
1380
1450
  async _runAutoCompaction(reason, willRetry) {
1381
1451
  const settings = this.settingsManager.getCompactionSettings();
1382
- this._emit({ type: "auto_compaction_start", reason });
1452
+ this._emit({ type: "compaction_start", reason });
1383
1453
  this._autoCompactionAbortController = new AbortController();
1384
1454
  try {
1385
1455
  if (!this.model) {
1386
- this._emit({ type: "auto_compaction_end", result: undefined, aborted: false, willRetry: false });
1456
+ this._emit({
1457
+ type: "compaction_end",
1458
+ reason,
1459
+ result: undefined,
1460
+ aborted: false,
1461
+ willRetry: false,
1462
+ });
1387
1463
  return;
1388
1464
  }
1389
- const apiKey = await this._modelRegistry.getApiKey(this.model);
1390
- if (!apiKey) {
1391
- this._emit({ type: "auto_compaction_end", result: undefined, aborted: false, willRetry: false });
1465
+ const authResult = await this._modelRegistry.getApiKeyAndHeaders(this.model);
1466
+ if (!authResult.ok || !authResult.apiKey) {
1467
+ this._emit({
1468
+ type: "compaction_end",
1469
+ reason,
1470
+ result: undefined,
1471
+ aborted: false,
1472
+ willRetry: false,
1473
+ });
1392
1474
  return;
1393
1475
  }
1476
+ const { apiKey, headers } = authResult;
1394
1477
  const pathEntries = this.sessionManager.getBranch();
1395
1478
  const preparation = prepareCompaction(pathEntries, settings);
1396
1479
  if (!preparation) {
1397
- this._emit({ type: "auto_compaction_end", result: undefined, aborted: false, willRetry: false });
1480
+ this._emit({
1481
+ type: "compaction_end",
1482
+ reason,
1483
+ result: undefined,
1484
+ aborted: false,
1485
+ willRetry: false,
1486
+ });
1398
1487
  return;
1399
1488
  }
1400
1489
  let extensionCompaction;
1401
1490
  let fromExtension = false;
1402
- if (this._extensionRunner?.hasHandlers("session_before_compact")) {
1491
+ if (this._extensionRunner.hasHandlers("session_before_compact")) {
1403
1492
  const extensionResult = (await this._extensionRunner.emit({
1404
1493
  type: "session_before_compact",
1405
1494
  preparation,
@@ -1408,7 +1497,13 @@ export class AgentSession {
1408
1497
  signal: this._autoCompactionAbortController.signal,
1409
1498
  }));
1410
1499
  if (extensionResult?.cancel) {
1411
- this._emit({ type: "auto_compaction_end", result: undefined, aborted: true, willRetry: false });
1500
+ this._emit({
1501
+ type: "compaction_end",
1502
+ reason,
1503
+ result: undefined,
1504
+ aborted: true,
1505
+ willRetry: false,
1506
+ });
1412
1507
  return;
1413
1508
  }
1414
1509
  if (extensionResult?.compaction) {
@@ -1429,20 +1524,26 @@ export class AgentSession {
1429
1524
  }
1430
1525
  else {
1431
1526
  // Generate compaction result
1432
- const compactResult = await compact(preparation, this.model, apiKey, undefined, this._autoCompactionAbortController.signal);
1527
+ const compactResult = await compact(preparation, this.model, apiKey, headers, undefined, this._autoCompactionAbortController.signal, this.thinkingLevel);
1433
1528
  summary = compactResult.summary;
1434
1529
  firstKeptEntryId = compactResult.firstKeptEntryId;
1435
1530
  tokensBefore = compactResult.tokensBefore;
1436
1531
  details = compactResult.details;
1437
1532
  }
1438
1533
  if (this._autoCompactionAbortController.signal.aborted) {
1439
- this._emit({ type: "auto_compaction_end", result: undefined, aborted: true, willRetry: false });
1534
+ this._emit({
1535
+ type: "compaction_end",
1536
+ reason,
1537
+ result: undefined,
1538
+ aborted: true,
1539
+ willRetry: false,
1540
+ });
1440
1541
  return;
1441
1542
  }
1442
1543
  this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
1443
1544
  const newEntries = this.sessionManager.getEntries();
1444
1545
  const sessionContext = this.sessionManager.buildSessionContext();
1445
- this.agent.replaceMessages(sessionContext.messages);
1546
+ this.agent.state.messages = sessionContext.messages;
1446
1547
  // Get the saved compaction entry for the extension event
1447
1548
  const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
1448
1549
  if (this._extensionRunner && savedCompactionEntry) {
@@ -1458,12 +1559,12 @@ export class AgentSession {
1458
1559
  tokensBefore,
1459
1560
  details,
1460
1561
  };
1461
- this._emit({ type: "auto_compaction_end", result, aborted: false, willRetry });
1562
+ this._emit({ type: "compaction_end", reason, result, aborted: false, willRetry });
1462
1563
  if (willRetry) {
1463
1564
  const messages = this.agent.state.messages;
1464
1565
  const lastMsg = messages[messages.length - 1];
1465
1566
  if (lastMsg?.role === "assistant" && lastMsg.stopReason === "error") {
1466
- this.agent.replaceMessages(messages.slice(0, -1));
1567
+ this.agent.state.messages = messages.slice(0, -1);
1467
1568
  }
1468
1569
  setTimeout(() => {
1469
1570
  this.agent.continue().catch(() => { });
@@ -1480,7 +1581,8 @@ export class AgentSession {
1480
1581
  catch (error) {
1481
1582
  const errorMessage = error instanceof Error ? error.message : "compaction failed";
1482
1583
  this._emit({
1483
- type: "auto_compaction_end",
1584
+ type: "compaction_end",
1585
+ reason,
1484
1586
  result: undefined,
1485
1587
  aborted: false,
1486
1588
  willRetry: false,
@@ -1516,14 +1618,12 @@ export class AgentSession {
1516
1618
  if (bindings.onError !== undefined) {
1517
1619
  this._extensionErrorListener = bindings.onError;
1518
1620
  }
1519
- if (this._extensionRunner) {
1520
- this._applyExtensionBindings(this._extensionRunner);
1521
- await this._extensionRunner.emit({ type: "session_start" });
1522
- await this.extendResourcesFromExtensions("startup");
1523
- }
1621
+ this._applyExtensionBindings(this._extensionRunner);
1622
+ await this._extensionRunner.emit(this._sessionStartEvent);
1623
+ await this.extendResourcesFromExtensions(this._sessionStartEvent.reason === "reload" ? "reload" : "startup");
1524
1624
  }
1525
1625
  async extendResourcesFromExtensions(reason) {
1526
- if (!this._extensionRunner?.hasHandlers("resources_discover")) {
1626
+ if (!this._extensionRunner.hasHandlers("resources_discover")) {
1527
1627
  return;
1528
1628
  }
1529
1629
  const { skillPaths, promptPaths, themePaths } = await this._extensionRunner.emitResourcesDiscover(this._cwd, reason);
@@ -1537,7 +1637,7 @@ export class AgentSession {
1537
1637
  };
1538
1638
  this._resourceLoader.extendResources(extensionPaths);
1539
1639
  this._baseSystemPrompt = this._rebuildSystemPrompt(this.getActiveToolNames());
1540
- this.agent.setSystemPrompt(this._baseSystemPrompt);
1640
+ this.agent.state.systemPrompt = this._baseSystemPrompt;
1541
1641
  }
1542
1642
  buildExtensionResourcePaths(entries) {
1543
1643
  return entries.map((entry) => {
@@ -1570,37 +1670,36 @@ export class AgentSession {
1570
1670
  ? runner.onError(this._extensionErrorListener)
1571
1671
  : undefined;
1572
1672
  }
1673
+ _refreshCurrentModelFromRegistry() {
1674
+ const currentModel = this.model;
1675
+ if (!currentModel) {
1676
+ return;
1677
+ }
1678
+ const refreshedModel = this._modelRegistry.find(currentModel.provider, currentModel.id);
1679
+ if (!refreshedModel || refreshedModel === currentModel) {
1680
+ return;
1681
+ }
1682
+ this.agent.state.model = refreshedModel;
1683
+ }
1573
1684
  _bindExtensionCore(runner) {
1574
- const normalizeLocation = (source) => {
1575
- if (source === "user" || source === "project" || source === "path") {
1576
- return source;
1577
- }
1578
- return undefined;
1579
- };
1580
- const reservedBuiltins = new Set(BUILTIN_SLASH_COMMANDS.map((command) => command.name));
1581
1685
  const getCommands = () => {
1582
- const extensionCommands = runner
1583
- .getRegisteredCommandsWithPaths()
1584
- .filter(({ command }) => !reservedBuiltins.has(command.name))
1585
- .map(({ command, extensionPath }) => ({
1586
- name: command.name,
1686
+ const extensionCommands = runner.getRegisteredCommands().map((command) => ({
1687
+ name: command.invocationName,
1587
1688
  description: command.description,
1588
1689
  source: "extension",
1589
- path: extensionPath,
1690
+ sourceInfo: command.sourceInfo,
1590
1691
  }));
1591
1692
  const templates = this.promptTemplates.map((template) => ({
1592
1693
  name: template.name,
1593
1694
  description: template.description,
1594
1695
  source: "prompt",
1595
- location: normalizeLocation(template.source),
1596
- path: template.filePath,
1696
+ sourceInfo: template.sourceInfo,
1597
1697
  }));
1598
1698
  const skills = this._resourceLoader.getSkills().skills.map((skill) => ({
1599
1699
  name: `skill:${skill.name}`,
1600
1700
  description: skill.description,
1601
1701
  source: "skill",
1602
- location: normalizeLocation(skill.source),
1603
- path: skill.filePath,
1702
+ sourceInfo: skill.sourceInfo,
1604
1703
  }));
1605
1704
  return [...extensionCommands, ...templates, ...skills];
1606
1705
  };
@@ -1627,7 +1726,7 @@ export class AgentSession {
1627
1726
  this.sessionManager.appendCustomEntry(customType, data);
1628
1727
  },
1629
1728
  setSessionName: (name) => {
1630
- this.sessionManager.appendSessionInfo(name);
1729
+ this.setSessionName(name);
1631
1730
  },
1632
1731
  getSessionName: () => {
1633
1732
  return this.sessionManager.getSessionName();
@@ -1641,8 +1740,7 @@ export class AgentSession {
1641
1740
  refreshTools: () => this._refreshToolRegistry(),
1642
1741
  getCommands,
1643
1742
  setModel: async (model) => {
1644
- const key = await this.modelRegistry.getApiKey(model);
1645
- if (!key)
1743
+ if (!this.modelRegistry.hasConfiguredAuth(model))
1646
1744
  return false;
1647
1745
  await this.setModel(model);
1648
1746
  return true;
@@ -1652,6 +1750,7 @@ export class AgentSession {
1652
1750
  }, {
1653
1751
  getModel: () => this.model,
1654
1752
  isIdle: () => !this.isStreaming,
1753
+ getSignal: () => this.agent.signal,
1655
1754
  abort: () => this.abort(),
1656
1755
  hasPendingMessages: () => this.pendingMessageCount > 0,
1657
1756
  shutdown: () => {
@@ -1671,46 +1770,80 @@ export class AgentSession {
1671
1770
  })();
1672
1771
  },
1673
1772
  getSystemPrompt: () => this.systemPrompt,
1773
+ }, {
1774
+ registerProvider: (name, config) => {
1775
+ this._modelRegistry.registerProvider(name, config);
1776
+ this._refreshCurrentModelFromRegistry();
1777
+ },
1778
+ unregisterProvider: (name) => {
1779
+ this._modelRegistry.unregisterProvider(name);
1780
+ this._refreshCurrentModelFromRegistry();
1781
+ },
1674
1782
  });
1675
1783
  }
1676
1784
  _refreshToolRegistry(options) {
1677
1785
  const previousRegistryNames = new Set(this._toolRegistry.keys());
1678
1786
  const previousActiveToolNames = this.getActiveToolNames();
1679
- const registeredTools = this._extensionRunner?.getAllRegisteredTools() ?? [];
1787
+ const allowedToolNames = this._allowedToolNames;
1788
+ const isAllowedTool = (name) => !allowedToolNames || allowedToolNames.has(name);
1789
+ const registeredTools = this._extensionRunner.getAllRegisteredTools();
1680
1790
  const allCustomTools = [
1681
1791
  ...registeredTools,
1682
- ...this._customTools.map((def) => ({ definition: def, extensionPath: "<sdk>" })),
1683
- ];
1684
- this._toolPromptSnippets = new Map(allCustomTools
1685
- .map((registeredTool) => {
1686
- const snippet = this._normalizePromptSnippet(registeredTool.definition.promptSnippet ?? registeredTool.definition.description);
1687
- return snippet ? [registeredTool.definition.name, snippet] : undefined;
1792
+ ...this._customTools.map((definition) => ({
1793
+ definition,
1794
+ sourceInfo: createSyntheticSourceInfo(`<sdk:${definition.name}>`, { source: "sdk" }),
1795
+ })),
1796
+ ].filter((tool) => isAllowedTool(tool.definition.name));
1797
+ const definitionRegistry = new Map(Array.from(this._baseToolDefinitions.entries())
1798
+ .filter(([name]) => isAllowedTool(name))
1799
+ .map(([name, definition]) => [
1800
+ name,
1801
+ {
1802
+ definition,
1803
+ sourceInfo: createSyntheticSourceInfo(`<builtin:${name}>`, { source: "builtin" }),
1804
+ },
1805
+ ]));
1806
+ for (const tool of allCustomTools) {
1807
+ definitionRegistry.set(tool.definition.name, {
1808
+ definition: tool.definition,
1809
+ sourceInfo: tool.sourceInfo,
1810
+ });
1811
+ }
1812
+ this._toolDefinitions = definitionRegistry;
1813
+ this._toolPromptSnippets = new Map(Array.from(definitionRegistry.values())
1814
+ .map(({ definition }) => {
1815
+ const snippet = this._normalizePromptSnippet(definition.promptSnippet);
1816
+ return snippet ? [definition.name, snippet] : undefined;
1688
1817
  })
1689
1818
  .filter((entry) => entry !== undefined));
1690
- this._toolPromptGuidelines = new Map(allCustomTools
1691
- .map((registeredTool) => {
1692
- const guidelines = this._normalizePromptGuidelines(registeredTool.definition.promptGuidelines);
1693
- return guidelines.length > 0 ? [registeredTool.definition.name, guidelines] : undefined;
1819
+ this._toolPromptGuidelines = new Map(Array.from(definitionRegistry.values())
1820
+ .map(({ definition }) => {
1821
+ const guidelines = this._normalizePromptGuidelines(definition.promptGuidelines);
1822
+ return guidelines.length > 0 ? [definition.name, guidelines] : undefined;
1694
1823
  })
1695
1824
  .filter((entry) => entry !== undefined));
1696
- const wrappedExtensionTools = this._extensionRunner
1697
- ? wrapRegisteredTools(allCustomTools, this._extensionRunner)
1698
- : [];
1699
- const toolRegistry = new Map(this._baseToolRegistry);
1825
+ const runner = this._extensionRunner;
1826
+ const wrappedExtensionTools = wrapRegisteredTools(allCustomTools, runner);
1827
+ const wrappedBuiltInTools = wrapRegisteredTools(Array.from(this._baseToolDefinitions.values())
1828
+ .filter((definition) => isAllowedTool(definition.name))
1829
+ .map((definition) => ({
1830
+ definition,
1831
+ sourceInfo: createSyntheticSourceInfo(`<builtin:${definition.name}>`, { source: "builtin" }),
1832
+ })), runner);
1833
+ const toolRegistry = new Map(wrappedBuiltInTools.map((tool) => [tool.name, tool]));
1700
1834
  for (const tool of wrappedExtensionTools) {
1701
1835
  toolRegistry.set(tool.name, tool);
1702
1836
  }
1703
- if (this._extensionRunner) {
1704
- const wrappedAllTools = wrapToolsWithExtensions(Array.from(toolRegistry.values()), this._extensionRunner);
1705
- this._toolRegistry = new Map(wrappedAllTools.map((tool) => [tool.name, tool]));
1706
- }
1707
- else {
1708
- this._toolRegistry = toolRegistry;
1837
+ this._toolRegistry = toolRegistry;
1838
+ const nextActiveToolNames = (options?.activeToolNames ? [...options.activeToolNames] : [...previousActiveToolNames]).filter((name) => isAllowedTool(name));
1839
+ if (allowedToolNames) {
1840
+ for (const toolName of this._toolRegistry.keys()) {
1841
+ if (allowedToolNames.has(toolName)) {
1842
+ nextActiveToolNames.push(toolName);
1843
+ }
1844
+ }
1709
1845
  }
1710
- const nextActiveToolNames = options?.activeToolNames
1711
- ? [...options.activeToolNames]
1712
- : [...previousActiveToolNames];
1713
- if (options?.includeAllExtensionTools) {
1846
+ else if (options?.includeAllExtensionTools) {
1714
1847
  for (const tool of wrappedExtensionTools) {
1715
1848
  nextActiveToolNames.push(tool.name);
1716
1849
  }
@@ -1727,32 +1860,29 @@ export class AgentSession {
1727
1860
  _buildRuntime(options) {
1728
1861
  const autoResizeImages = this.settingsManager.getImageAutoResize();
1729
1862
  const shellCommandPrefix = this.settingsManager.getShellCommandPrefix();
1730
- const baseTools = this._baseToolsOverride
1731
- ? this._baseToolsOverride
1732
- : createAllTools(this._cwd, {
1863
+ const shellPath = this.settingsManager.getShellPath();
1864
+ const baseToolDefinitions = this._baseToolsOverride
1865
+ ? Object.fromEntries(Object.entries(this._baseToolsOverride).map(([name, tool]) => [
1866
+ name,
1867
+ createToolDefinitionFromAgentTool(tool),
1868
+ ]))
1869
+ : createAllToolDefinitions(this._cwd, {
1733
1870
  read: { autoResizeImages },
1734
- bash: { commandPrefix: shellCommandPrefix },
1871
+ bash: { commandPrefix: shellCommandPrefix, shellPath },
1735
1872
  });
1736
- this._baseToolRegistry = new Map(Object.entries(baseTools).map(([name, tool]) => [name, tool]));
1873
+ this._baseToolDefinitions = new Map(Object.entries(baseToolDefinitions).map(([name, tool]) => [name, tool]));
1737
1874
  const extensionsResult = this._resourceLoader.getExtensions();
1738
1875
  if (options.flagValues) {
1739
1876
  for (const [name, value] of options.flagValues) {
1740
1877
  extensionsResult.runtime.flagValues.set(name, value);
1741
1878
  }
1742
1879
  }
1743
- const hasExtensions = extensionsResult.extensions.length > 0;
1744
- const hasCustomTools = this._customTools.length > 0;
1745
- this._extensionRunner =
1746
- hasExtensions || hasCustomTools
1747
- ? new ExtensionRunner(extensionsResult.extensions, extensionsResult.runtime, this._cwd, this.sessionManager, this._modelRegistry)
1748
- : undefined;
1880
+ this._extensionRunner = new ExtensionRunner(extensionsResult.extensions, extensionsResult.runtime, this._cwd, this.sessionManager, this._modelRegistry);
1749
1881
  if (this._extensionRunnerRef) {
1750
1882
  this._extensionRunnerRef.current = this._extensionRunner;
1751
1883
  }
1752
- if (this._extensionRunner) {
1753
- this._bindExtensionCore(this._extensionRunner);
1754
- this._applyExtensionBindings(this._extensionRunner);
1755
- }
1884
+ this._bindExtensionCore(this._extensionRunner);
1885
+ this._applyExtensionBindings(this._extensionRunner);
1756
1886
  const defaultActiveToolNames = this._baseToolsOverride
1757
1887
  ? Object.keys(this._baseToolsOverride)
1758
1888
  : ["read", "bash", "edit", "write"];
@@ -1763,9 +1893,9 @@ export class AgentSession {
1763
1893
  });
1764
1894
  }
1765
1895
  async reload() {
1766
- const previousFlagValues = this._extensionRunner?.getFlagValues();
1767
- await this._extensionRunner?.emit({ type: "session_shutdown" });
1768
- this.settingsManager.reload();
1896
+ const previousFlagValues = this._extensionRunner.getFlagValues();
1897
+ await emitSessionShutdownEvent(this._extensionRunner, { type: "session_shutdown", reason: "reload" });
1898
+ await this.settingsManager.reload();
1769
1899
  resetApiProviders();
1770
1900
  await this._resourceLoader.reload();
1771
1901
  this._buildRuntime({
@@ -1777,8 +1907,8 @@ export class AgentSession {
1777
1907
  this._extensionCommandContextActions ||
1778
1908
  this._extensionShutdownHandler ||
1779
1909
  this._extensionErrorListener;
1780
- if (this._extensionRunner && hasBindings) {
1781
- await this._extensionRunner.emit({ type: "session_start" });
1910
+ if (hasBindings) {
1911
+ await this._extensionRunner.emit({ type: "session_start", reason: "reload" });
1782
1912
  await this.extendResourcesFromExtensions("reload");
1783
1913
  }
1784
1914
  }
@@ -1797,8 +1927,8 @@ export class AgentSession {
1797
1927
  if (isContextOverflow(message, contextWindow))
1798
1928
  return false;
1799
1929
  const err = message.errorMessage;
1800
- // Match: overloaded_error, rate limit, 429, 500, 502, 503, 504, service unavailable, connection errors, fetch failed, terminated, retry delay exceeded
1801
- return /overloaded|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server error|internal error|connection.?error|connection.?refused|other side closed|fetch failed|upstream.?connect|reset before headers|terminated|retry delay/i.test(err);
1930
+ // Match: overloaded_error, provider returned error, rate limit, 429, 500, 502, 503, 504, service unavailable, network/connection errors (including connection lost), WebSocket transport closes/errors, fetch failed, request ended without sending chunks, HTTP/2 closed before response, terminated, retry delay exceeded
1931
+ return /overloaded|provider.?returned.?error|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|network.?error|connection.?error|connection.?refused|connection.?lost|websocket.?closed|websocket.?error|other side closed|fetch failed|upstream.?connect|reset before headers|socket hang up|ended without|http2 request did not get a response|timed? out|timeout|terminated|retry delay/i.test(err);
1802
1932
  }
1803
1933
  /**
1804
1934
  * Handle retryable errors with exponential backoff.
@@ -1841,7 +1971,7 @@ export class AgentSession {
1841
1971
  // Remove error message from agent state (keep in session for history)
1842
1972
  const messages = this.agent.state.messages;
1843
1973
  if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
1844
- this.agent.replaceMessages(messages.slice(0, -1));
1974
+ this.agent.state.messages = messages.slice(0, -1);
1845
1975
  }
1846
1976
  // Wait with exponential backoff (abortable)
1847
1977
  this._retryAbortController = new AbortController();
@@ -1884,9 +2014,11 @@ export class AgentSession {
1884
2014
  * Returns immediately if no retry is in progress.
1885
2015
  */
1886
2016
  async waitForRetry() {
1887
- if (this._retryPromise) {
1888
- await this._retryPromise;
2017
+ if (!this._retryPromise) {
2018
+ return;
1889
2019
  }
2020
+ await this._retryPromise;
2021
+ await this.agent.waitForIdle();
1890
2022
  }
1891
2023
  /** Whether auto-retry is currently in progress */
1892
2024
  get isRetrying() {
@@ -1917,17 +2049,13 @@ export class AgentSession {
1917
2049
  this._bashAbortController = new AbortController();
1918
2050
  // Apply command prefix if configured (e.g., "shopt -s expand_aliases" for alias support)
1919
2051
  const prefix = this.settingsManager.getShellCommandPrefix();
2052
+ const shellPath = this.settingsManager.getShellPath();
1920
2053
  const resolvedCommand = prefix ? `${prefix}\n${command}` : command;
1921
2054
  try {
1922
- const result = options?.operations
1923
- ? await executeBashWithOperations(resolvedCommand, process.cwd(), options.operations, {
1924
- onChunk,
1925
- signal: this._bashAbortController.signal,
1926
- })
1927
- : await executeBashCommand(resolvedCommand, {
1928
- onChunk,
1929
- signal: this._bashAbortController.signal,
1930
- });
2055
+ const result = await executeBashWithOperations(resolvedCommand, this.sessionManager.getCwd(), options?.operations ?? createLocalBashOperations({ shellPath }), {
2056
+ onChunk,
2057
+ signal: this._bashAbortController.signal,
2058
+ });
1931
2059
  this.recordBashResult(command, result, options);
1932
2060
  return result;
1933
2061
  }
@@ -1958,7 +2086,7 @@ export class AgentSession {
1958
2086
  }
1959
2087
  else {
1960
2088
  // Add to agent state immediately
1961
- this.agent.appendMessage(bashMessage);
2089
+ this.agent.state.messages.push(bashMessage);
1962
2090
  // Save to session
1963
2091
  this.sessionManager.appendMessage(bashMessage);
1964
2092
  }
@@ -1986,7 +2114,7 @@ export class AgentSession {
1986
2114
  return;
1987
2115
  for (const bashMessage of this._pendingBashMessages) {
1988
2116
  // Add to agent state
1989
- this.agent.appendMessage(bashMessage);
2117
+ this.agent.state.messages.push(bashMessage);
1990
2118
  // Save to session
1991
2119
  this.sessionManager.appendMessage(bashMessage);
1992
2120
  }
@@ -1995,129 +2123,12 @@ export class AgentSession {
1995
2123
  // =========================================================================
1996
2124
  // Session Management
1997
2125
  // =========================================================================
1998
- /**
1999
- * Switch to a different session file.
2000
- * Aborts current operation, loads messages, restores model/thinking.
2001
- * Listeners are preserved and will continue receiving events.
2002
- * @returns true if switch completed, false if cancelled by extension
2003
- */
2004
- async switchSession(sessionPath) {
2005
- const previousSessionFile = this.sessionManager.getSessionFile();
2006
- // Emit session_before_switch event (can be cancelled)
2007
- if (this._extensionRunner?.hasHandlers("session_before_switch")) {
2008
- const result = (await this._extensionRunner.emit({
2009
- type: "session_before_switch",
2010
- reason: "resume",
2011
- targetSessionFile: sessionPath,
2012
- }));
2013
- if (result?.cancel) {
2014
- return false;
2015
- }
2016
- }
2017
- this._disconnectFromAgent();
2018
- await this.abort();
2019
- this._steeringMessages = [];
2020
- this._followUpMessages = [];
2021
- this._pendingNextTurnMessages = [];
2022
- // Set new session
2023
- this.sessionManager.setSessionFile(sessionPath);
2024
- this.agent.sessionId = this.sessionManager.getSessionId();
2025
- // Reload messages
2026
- const sessionContext = this.sessionManager.buildSessionContext();
2027
- // Emit session_switch event to extensions
2028
- if (this._extensionRunner) {
2029
- await this._extensionRunner.emit({
2030
- type: "session_switch",
2031
- reason: "resume",
2032
- previousSessionFile,
2033
- });
2034
- }
2035
- // Emit session event to custom tools
2036
- this.agent.replaceMessages(sessionContext.messages);
2037
- // Restore model if saved
2038
- if (sessionContext.model) {
2039
- const previousModel = this.model;
2040
- const availableModels = await this._modelRegistry.getAvailable();
2041
- const match = availableModels.find((m) => m.provider === sessionContext.model.provider && m.id === sessionContext.model.modelId);
2042
- if (match) {
2043
- this.agent.setModel(match);
2044
- await this._emitModelSelect(match, previousModel, "restore");
2045
- }
2046
- }
2047
- const hasThinkingEntry = this.sessionManager.getBranch().some((entry) => entry.type === "thinking_level_change");
2048
- const defaultThinkingLevel = this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;
2049
- if (hasThinkingEntry) {
2050
- // Restore thinking level if saved (setThinkingLevel clamps to model capabilities)
2051
- this.setThinkingLevel(sessionContext.thinkingLevel);
2052
- }
2053
- else {
2054
- const availableLevels = this.getAvailableThinkingLevels();
2055
- const effectiveLevel = availableLevels.includes(defaultThinkingLevel)
2056
- ? defaultThinkingLevel
2057
- : this._clampThinkingLevel(defaultThinkingLevel, availableLevels);
2058
- this.agent.setThinkingLevel(effectiveLevel);
2059
- this.sessionManager.appendThinkingLevelChange(effectiveLevel);
2060
- }
2061
- this._reconnectToAgent();
2062
- return true;
2063
- }
2064
2126
  /**
2065
2127
  * Set a display name for the current session.
2066
2128
  */
2067
2129
  setSessionName(name) {
2068
2130
  this.sessionManager.appendSessionInfo(name);
2069
- }
2070
- /**
2071
- * Create a fork from a specific entry.
2072
- * Emits before_fork/fork session events to extensions.
2073
- *
2074
- * @param entryId ID of the entry to fork from
2075
- * @returns Object with:
2076
- * - selectedText: The text of the selected user message (for editor pre-fill)
2077
- * - cancelled: True if an extension cancelled the fork
2078
- */
2079
- async fork(entryId) {
2080
- const previousSessionFile = this.sessionFile;
2081
- const selectedEntry = this.sessionManager.getEntry(entryId);
2082
- if (!selectedEntry || selectedEntry.type !== "message" || selectedEntry.message.role !== "user") {
2083
- throw new Error("Invalid entry ID for forking");
2084
- }
2085
- const selectedText = this._extractUserMessageText(selectedEntry.message.content);
2086
- let skipConversationRestore = false;
2087
- // Emit session_before_fork event (can be cancelled)
2088
- if (this._extensionRunner?.hasHandlers("session_before_fork")) {
2089
- const result = (await this._extensionRunner.emit({
2090
- type: "session_before_fork",
2091
- entryId,
2092
- }));
2093
- if (result?.cancel) {
2094
- return { selectedText, cancelled: true };
2095
- }
2096
- skipConversationRestore = result?.skipConversationRestore ?? false;
2097
- }
2098
- // Clear pending messages (bound to old session state)
2099
- this._pendingNextTurnMessages = [];
2100
- if (!selectedEntry.parentId) {
2101
- this.sessionManager.newSession({ parentSession: previousSessionFile });
2102
- }
2103
- else {
2104
- this.sessionManager.createBranchedSession(selectedEntry.parentId);
2105
- }
2106
- this.agent.sessionId = this.sessionManager.getSessionId();
2107
- // Reload messages from entries (works for both file and in-memory mode)
2108
- const sessionContext = this.sessionManager.buildSessionContext();
2109
- // Emit session_fork event to extensions (after fork completes)
2110
- if (this._extensionRunner) {
2111
- await this._extensionRunner.emit({
2112
- type: "session_fork",
2113
- previousSessionFile,
2114
- });
2115
- }
2116
- // Emit session event to custom tools (with reason "fork")
2117
- if (!skipConversationRestore) {
2118
- this.agent.replaceMessages(sessionContext.messages);
2119
- }
2120
- return { selectedText, cancelled: false };
2131
+ this._emit({ type: "session_info_changed", name: this.sessionManager.getSessionName() });
2121
2132
  }
2122
2133
  // =========================================================================
2123
2134
  // Tree Navigation
@@ -2165,120 +2176,117 @@ export class AgentSession {
2165
2176
  };
2166
2177
  // Set up abort controller for summarization
2167
2178
  this._branchSummaryAbortController = new AbortController();
2168
- let extensionSummary;
2169
- let fromExtension = false;
2170
- // Emit session_before_tree event
2171
- if (this._extensionRunner?.hasHandlers("session_before_tree")) {
2172
- const result = (await this._extensionRunner.emit({
2173
- type: "session_before_tree",
2174
- preparation,
2175
- signal: this._branchSummaryAbortController.signal,
2176
- }));
2177
- if (result?.cancel) {
2178
- return { cancelled: true };
2179
- }
2180
- if (result?.summary && options.summarize) {
2181
- extensionSummary = result.summary;
2182
- fromExtension = true;
2183
- }
2184
- // Allow extensions to override instructions and label
2185
- if (result?.customInstructions !== undefined) {
2186
- customInstructions = result.customInstructions;
2187
- }
2188
- if (result?.replaceInstructions !== undefined) {
2189
- replaceInstructions = result.replaceInstructions;
2190
- }
2191
- if (result?.label !== undefined) {
2192
- label = result.label;
2193
- }
2194
- }
2195
- // Run default summarizer if needed
2196
- let summaryText;
2197
- let summaryDetails;
2198
- if (options.summarize && entriesToSummarize.length > 0 && !extensionSummary) {
2199
- const model = this.model;
2200
- const apiKey = await this._modelRegistry.getApiKey(model);
2201
- if (!apiKey) {
2202
- throw new Error(`No API key for ${model.provider}`);
2203
- }
2204
- const branchSummarySettings = this.settingsManager.getBranchSummarySettings();
2205
- const result = await generateBranchSummary(entriesToSummarize, {
2206
- model,
2207
- apiKey,
2208
- signal: this._branchSummaryAbortController.signal,
2209
- customInstructions,
2210
- replaceInstructions,
2211
- reserveTokens: branchSummarySettings.reserveTokens,
2212
- });
2213
- this._branchSummaryAbortController = undefined;
2214
- if (result.aborted) {
2215
- return { cancelled: true, aborted: true };
2179
+ try {
2180
+ let extensionSummary;
2181
+ let fromExtension = false;
2182
+ // Emit session_before_tree event
2183
+ if (this._extensionRunner.hasHandlers("session_before_tree")) {
2184
+ const result = (await this._extensionRunner.emit({
2185
+ type: "session_before_tree",
2186
+ preparation,
2187
+ signal: this._branchSummaryAbortController.signal,
2188
+ }));
2189
+ if (result?.cancel) {
2190
+ return { cancelled: true };
2191
+ }
2192
+ if (result?.summary && options.summarize) {
2193
+ extensionSummary = result.summary;
2194
+ fromExtension = true;
2195
+ }
2196
+ // Allow extensions to override instructions and label
2197
+ if (result?.customInstructions !== undefined) {
2198
+ customInstructions = result.customInstructions;
2199
+ }
2200
+ if (result?.replaceInstructions !== undefined) {
2201
+ replaceInstructions = result.replaceInstructions;
2202
+ }
2203
+ if (result?.label !== undefined) {
2204
+ label = result.label;
2205
+ }
2216
2206
  }
2217
- if (result.error) {
2218
- throw new Error(result.error);
2207
+ // Run default summarizer if needed
2208
+ let summaryText;
2209
+ let summaryDetails;
2210
+ if (options.summarize && entriesToSummarize.length > 0 && !extensionSummary) {
2211
+ const model = this.model;
2212
+ const { apiKey, headers } = await this._getRequiredRequestAuth(model);
2213
+ const branchSummarySettings = this.settingsManager.getBranchSummarySettings();
2214
+ const result = await generateBranchSummary(entriesToSummarize, {
2215
+ model,
2216
+ apiKey,
2217
+ headers,
2218
+ signal: this._branchSummaryAbortController.signal,
2219
+ customInstructions,
2220
+ replaceInstructions,
2221
+ reserveTokens: branchSummarySettings.reserveTokens,
2222
+ });
2223
+ if (result.aborted) {
2224
+ return { cancelled: true, aborted: true };
2225
+ }
2226
+ if (result.error) {
2227
+ throw new Error(result.error);
2228
+ }
2229
+ summaryText = result.summary;
2230
+ summaryDetails = {
2231
+ readFiles: result.readFiles || [],
2232
+ modifiedFiles: result.modifiedFiles || [],
2233
+ };
2234
+ }
2235
+ else if (extensionSummary) {
2236
+ summaryText = extensionSummary.summary;
2237
+ summaryDetails = extensionSummary.details;
2238
+ }
2239
+ // Determine the new leaf position based on target type
2240
+ let newLeafId;
2241
+ let editorText;
2242
+ if (targetEntry.type === "message" && targetEntry.message.role === "user") {
2243
+ // User message: leaf = parent (null if root), text goes to editor
2244
+ newLeafId = targetEntry.parentId;
2245
+ editorText = this._extractUserMessageText(targetEntry.message.content);
2246
+ }
2247
+ else if (targetEntry.type === "custom_message") {
2248
+ // Custom message: leaf = parent (null if root), text goes to editor
2249
+ newLeafId = targetEntry.parentId;
2250
+ editorText =
2251
+ typeof targetEntry.content === "string"
2252
+ ? targetEntry.content
2253
+ : targetEntry.content
2254
+ .filter((c) => c.type === "text")
2255
+ .map((c) => c.text)
2256
+ .join("");
2219
2257
  }
2220
- summaryText = result.summary;
2221
- summaryDetails = {
2222
- readFiles: result.readFiles || [],
2223
- modifiedFiles: result.modifiedFiles || [],
2224
- };
2225
- }
2226
- else if (extensionSummary) {
2227
- summaryText = extensionSummary.summary;
2228
- summaryDetails = extensionSummary.details;
2229
- }
2230
- // Determine the new leaf position based on target type
2231
- let newLeafId;
2232
- let editorText;
2233
- if (targetEntry.type === "message" && targetEntry.message.role === "user") {
2234
- // User message: leaf = parent (null if root), text goes to editor
2235
- newLeafId = targetEntry.parentId;
2236
- editorText = this._extractUserMessageText(targetEntry.message.content);
2237
- }
2238
- else if (targetEntry.type === "custom_message") {
2239
- // Custom message: leaf = parent (null if root), text goes to editor
2240
- newLeafId = targetEntry.parentId;
2241
- editorText =
2242
- typeof targetEntry.content === "string"
2243
- ? targetEntry.content
2244
- : targetEntry.content
2245
- .filter((c) => c.type === "text")
2246
- .map((c) => c.text)
2247
- .join("");
2248
- }
2249
- else {
2250
- // Non-user message: leaf = selected node
2251
- newLeafId = targetId;
2252
- }
2253
- // Switch leaf (with or without summary)
2254
- // Summary is attached at the navigation target position (newLeafId), not the old branch
2255
- let summaryEntry;
2256
- if (summaryText) {
2257
- // Create summary at target position (can be null for root)
2258
- const summaryId = this.sessionManager.branchWithSummary(newLeafId, summaryText, summaryDetails, fromExtension);
2259
- summaryEntry = this.sessionManager.getEntry(summaryId);
2260
- // Attach label to the summary entry
2261
- if (label) {
2262
- this.sessionManager.appendLabelChange(summaryId, label);
2258
+ else {
2259
+ // Non-user message: leaf = selected node
2260
+ newLeafId = targetId;
2261
+ }
2262
+ // Switch leaf (with or without summary)
2263
+ // Summary is attached at the navigation target position (newLeafId), not the old branch
2264
+ let summaryEntry;
2265
+ if (summaryText) {
2266
+ // Create summary at target position (can be null for root)
2267
+ const summaryId = this.sessionManager.branchWithSummary(newLeafId, summaryText, summaryDetails, fromExtension);
2268
+ summaryEntry = this.sessionManager.getEntry(summaryId);
2269
+ // Attach label to the summary entry
2270
+ if (label) {
2271
+ this.sessionManager.appendLabelChange(summaryId, label);
2272
+ }
2263
2273
  }
2264
- }
2265
- else if (newLeafId === null) {
2266
- // No summary, navigating to root - reset leaf
2267
- this.sessionManager.resetLeaf();
2268
- }
2269
- else {
2270
- // No summary, navigating to non-root
2271
- this.sessionManager.branch(newLeafId);
2272
- }
2273
- // Attach label to target entry when not summarizing (no summary entry to label)
2274
- if (label && !summaryText) {
2275
- this.sessionManager.appendLabelChange(targetId, label);
2276
- }
2277
- // Update agent state
2278
- const sessionContext = this.sessionManager.buildSessionContext();
2279
- this.agent.replaceMessages(sessionContext.messages);
2280
- // Emit session_tree event
2281
- if (this._extensionRunner) {
2274
+ else if (newLeafId === null) {
2275
+ // No summary, navigating to root - reset leaf
2276
+ this.sessionManager.resetLeaf();
2277
+ }
2278
+ else {
2279
+ // No summary, navigating to non-root
2280
+ this.sessionManager.branch(newLeafId);
2281
+ }
2282
+ // Attach label to target entry when not summarizing (no summary entry to label)
2283
+ if (label && !summaryText) {
2284
+ this.sessionManager.appendLabelChange(targetId, label);
2285
+ }
2286
+ // Update agent state
2287
+ const sessionContext = this.sessionManager.buildSessionContext();
2288
+ this.agent.state.messages = sessionContext.messages;
2289
+ // Emit session_tree event
2282
2290
  await this._extensionRunner.emit({
2283
2291
  type: "session_tree",
2284
2292
  newLeafId: this.sessionManager.getLeafId(),
@@ -2286,10 +2294,12 @@ export class AgentSession {
2286
2294
  summaryEntry,
2287
2295
  fromExtension: summaryText ? fromExtension : undefined,
2288
2296
  });
2297
+ // Emit to custom tools
2298
+ return { editorText, cancelled: false, summaryEntry };
2299
+ }
2300
+ finally {
2301
+ this._branchSummaryAbortController = undefined;
2289
2302
  }
2290
- // Emit to custom tools
2291
- this._branchSummaryAbortController = undefined;
2292
- return { editorText, cancelled: false, summaryEntry };
2293
2303
  }
2294
2304
  /**
2295
2305
  * Get all user messages from session for fork selector.
@@ -2361,6 +2371,7 @@ export class AgentSession {
2361
2371
  total: totalInput + totalOutput + totalCacheRead + totalCacheWrite,
2362
2372
  },
2363
2373
  cost: totalCost,
2374
+ contextUsage: this.getContextUsage(),
2364
2375
  };
2365
2376
  }
2366
2377
  getContextUsage() {
@@ -2412,19 +2423,48 @@ export class AgentSession {
2412
2423
  async exportToHtml(outputPath) {
2413
2424
  const themeName = this.settingsManager.getTheme();
2414
2425
  // Create tool renderer if we have an extension runner (for custom tool HTML rendering)
2415
- let toolRenderer;
2416
- if (this._extensionRunner) {
2417
- toolRenderer = createToolHtmlRenderer({
2418
- getToolDefinition: (name) => this._extensionRunner.getToolDefinition(name),
2419
- theme,
2420
- });
2421
- }
2426
+ const toolRenderer = createToolHtmlRenderer({
2427
+ getToolDefinition: (name) => this.getToolDefinition(name),
2428
+ theme,
2429
+ cwd: this.sessionManager.getCwd(),
2430
+ });
2422
2431
  return await exportSessionToHtml(this.sessionManager, this.state, {
2423
2432
  outputPath,
2424
2433
  themeName,
2425
2434
  toolRenderer,
2426
2435
  });
2427
2436
  }
2437
+ /**
2438
+ * Export the current session branch to a JSONL file.
2439
+ * Writes the session header followed by all entries on the current branch path.
2440
+ * @param outputPath Target file path. If omitted, generates a timestamped file in cwd.
2441
+ * @returns The resolved output file path.
2442
+ */
2443
+ exportToJsonl(outputPath) {
2444
+ const filePath = resolve(outputPath ?? `session-${new Date().toISOString().replace(/[:.]/g, "-")}.jsonl`);
2445
+ const dir = dirname(filePath);
2446
+ if (!existsSync(dir)) {
2447
+ mkdirSync(dir, { recursive: true });
2448
+ }
2449
+ const header = {
2450
+ type: "session",
2451
+ version: CURRENT_SESSION_VERSION,
2452
+ id: this.sessionManager.getSessionId(),
2453
+ timestamp: new Date().toISOString(),
2454
+ cwd: this.sessionManager.getCwd(),
2455
+ };
2456
+ const branchEntries = this.sessionManager.getBranch();
2457
+ const lines = [JSON.stringify(header)];
2458
+ // Re-chain parentIds to form a linear sequence
2459
+ let prevId = null;
2460
+ for (const entry of branchEntries) {
2461
+ const linear = { ...entry, parentId: prevId };
2462
+ lines.push(JSON.stringify(linear));
2463
+ prevId = entry.id;
2464
+ }
2465
+ writeFileSync(filePath, `${lines.join("\n")}\n`);
2466
+ return filePath;
2467
+ }
2428
2468
  // =========================================================================
2429
2469
  // Utilities
2430
2470
  // =========================================================================
@@ -2459,11 +2499,17 @@ export class AgentSession {
2459
2499
  // =========================================================================
2460
2500
  // Extension System
2461
2501
  // =========================================================================
2502
+ createReplacedSessionContext() {
2503
+ const context = Object.defineProperties({}, Object.getOwnPropertyDescriptors(this._extensionRunner.createCommandContext()));
2504
+ context.sendMessage = (message, options) => this.sendCustomMessage(message, options);
2505
+ context.sendUserMessage = (content, options) => this.sendUserMessage(content, options);
2506
+ return context;
2507
+ }
2462
2508
  /**
2463
2509
  * Check if extensions have handlers for a specific event type.
2464
2510
  */
2465
2511
  hasExtensionHandlers(eventType) {
2466
- return this._extensionRunner?.hasHandlers(eventType) ?? false;
2512
+ return this._extensionRunner.hasHandlers(eventType);
2467
2513
  }
2468
2514
  /**
2469
2515
  * Get the extension runner (for setting UI context and error handlers).