@astralyn/pi 0.85.3 → 0.86.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (323) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/bundle/chunks/anthropic-messages-R2LYJUDD.js +6 -0
  3. package/dist/bundle/chunks/azure-openai-responses-HS35BQV6.js +2 -0
  4. package/dist/bundle/chunks/bedrock-converse-stream.js +36 -32
  5. package/dist/bundle/chunks/{chunk-S2DJQQI2.js → chunk-2IO5GDBS.js} +10 -1
  6. package/dist/bundle/chunks/{chunk-7LO5Z4WE.js → chunk-I5IVW2WS.js} +1 -1
  7. package/dist/bundle/chunks/chunk-JI4WGISU.js +2 -0
  8. package/dist/bundle/chunks/chunk-LPQCB2VJ.js +44 -0
  9. package/dist/bundle/chunks/chunk-M2YWW37K.js +2 -0
  10. package/dist/bundle/chunks/{chunk-LB4P77SH.js → chunk-MSHHFVMD.js} +776 -762
  11. package/dist/bundle/chunks/chunk-NW5FIDD7.js +11 -0
  12. package/dist/bundle/chunks/chunk-PJVFYVHE.js +42 -0
  13. package/dist/bundle/chunks/github-copilot.js +1 -1
  14. package/dist/bundle/chunks/google-generative-ai-NOVAJBFC.js +2 -0
  15. package/dist/bundle/chunks/google-vertex-4VHCZJV5.js +2 -0
  16. package/dist/bundle/chunks/https-proxy-agent-JOOPB4RE.js +16 -0
  17. package/dist/bundle/chunks/jiti-loader-BFSSLU7G.js +21 -0
  18. package/dist/bundle/chunks/jiti-static-loader-CLT3YR3B.js +2 -0
  19. package/dist/bundle/chunks/mistral-conversations-WC6A2WCJ.js +5 -0
  20. package/dist/bundle/chunks/{node-G4XOIDPH.js → node-CXWYTK2I.js} +1 -1
  21. package/dist/bundle/chunks/openai-codex-responses-XCDG75W2.js +10 -0
  22. package/dist/bundle/chunks/openai-completions-WJIA7NEB.js +7 -0
  23. package/dist/bundle/chunks/openai-responses-UT6XFKC7.js +2 -0
  24. package/dist/bundle/chunks/{pi-messages-PE6B42TG.js → pi-messages-MQMPIPNC.js} +1 -1
  25. package/dist/bundle/chunks/virtual-modules-B4BILEXT.js +2 -0
  26. package/dist/bundle/cli.js +1 -1
  27. package/dist/bundle/index.js +1 -1
  28. package/dist/bundle/rpc-entry.js +1 -1
  29. package/dist/cli/session-picker.d.ts +1 -1
  30. package/dist/cli/session-picker.d.ts.map +1 -1
  31. package/dist/cli/session-picker.js +13 -11
  32. package/dist/cli/session-picker.js.map +1 -1
  33. package/dist/config.js +1 -1
  34. package/dist/config.js.map +1 -1
  35. package/dist/core/agent-session.d.ts +56 -14
  36. package/dist/core/agent-session.d.ts.map +1 -1
  37. package/dist/core/agent-session.js +305 -176
  38. package/dist/core/agent-session.js.map +1 -1
  39. package/dist/core/bug-report-upload.d.ts +12 -0
  40. package/dist/core/bug-report-upload.d.ts.map +1 -0
  41. package/dist/core/bug-report-upload.js +22 -0
  42. package/dist/core/bug-report-upload.js.map +1 -0
  43. package/dist/core/bug-report.d.ts +176 -0
  44. package/dist/core/bug-report.d.ts.map +1 -0
  45. package/dist/core/bug-report.js +292 -0
  46. package/dist/core/bug-report.js.map +1 -0
  47. package/dist/core/cache-stats.d.ts.map +1 -1
  48. package/dist/core/cache-stats.js +12 -1
  49. package/dist/core/cache-stats.js.map +1 -1
  50. package/dist/core/cache-warmer.d.ts +102 -0
  51. package/dist/core/cache-warmer.d.ts.map +1 -0
  52. package/dist/core/cache-warmer.js +333 -0
  53. package/dist/core/cache-warmer.js.map +1 -0
  54. package/dist/core/compaction/branch-summarization.js +2 -2
  55. package/dist/core/compaction/branch-summarization.js.map +1 -1
  56. package/dist/core/compaction/compaction.d.ts +14 -9
  57. package/dist/core/compaction/compaction.d.ts.map +1 -1
  58. package/dist/core/compaction/compaction.js +30 -23
  59. package/dist/core/compaction/compaction.js.map +1 -1
  60. package/dist/core/compaction/settings.d.ts +17 -20
  61. package/dist/core/compaction/settings.d.ts.map +1 -1
  62. package/dist/core/compaction/settings.js +19 -26
  63. package/dist/core/compaction/settings.js.map +1 -1
  64. package/dist/core/context-snapshot.d.ts +16 -5
  65. package/dist/core/context-snapshot.d.ts.map +1 -1
  66. package/dist/core/context-snapshot.js +20 -10
  67. package/dist/core/context-snapshot.js.map +1 -1
  68. package/dist/core/crash-log.d.ts +22 -0
  69. package/dist/core/crash-log.d.ts.map +1 -0
  70. package/dist/core/crash-log.js +71 -0
  71. package/dist/core/crash-log.js.map +1 -0
  72. package/dist/core/experimental.d.ts +0 -4
  73. package/dist/core/experimental.d.ts.map +1 -1
  74. package/dist/core/experimental.js +0 -4
  75. package/dist/core/experimental.js.map +1 -1
  76. package/dist/core/extensions/index.d.ts +1 -1
  77. package/dist/core/extensions/index.d.ts.map +1 -1
  78. package/dist/core/extensions/index.js.map +1 -1
  79. package/dist/core/extensions/jiti-loader.d.ts +2 -0
  80. package/dist/core/extensions/jiti-loader.d.ts.map +1 -0
  81. package/dist/core/extensions/jiti-loader.js +4 -0
  82. package/dist/core/extensions/jiti-loader.js.map +1 -0
  83. package/dist/core/extensions/jiti-static-loader.d.ts +2 -0
  84. package/dist/core/extensions/jiti-static-loader.d.ts.map +1 -0
  85. package/dist/core/extensions/jiti-static-loader.js +4 -0
  86. package/dist/core/extensions/jiti-static-loader.js.map +1 -0
  87. package/dist/core/extensions/loader.d.ts.map +1 -1
  88. package/dist/core/extensions/loader.js +38 -51
  89. package/dist/core/extensions/loader.js.map +1 -1
  90. package/dist/core/extensions/runner.d.ts +10 -7
  91. package/dist/core/extensions/runner.d.ts.map +1 -1
  92. package/dist/core/extensions/runner.js +85 -67
  93. package/dist/core/extensions/runner.js.map +1 -1
  94. package/dist/core/extensions/types.d.ts +60 -50
  95. package/dist/core/extensions/types.d.ts.map +1 -1
  96. package/dist/core/extensions/types.js.map +1 -1
  97. package/dist/core/extensions/virtual-modules.d.ts +3 -0
  98. package/dist/core/extensions/virtual-modules.d.ts.map +1 -0
  99. package/dist/core/extensions/virtual-modules.js +39 -0
  100. package/dist/core/extensions/virtual-modules.js.map +1 -0
  101. package/dist/core/extensions/wrapper.d.ts.map +1 -1
  102. package/dist/core/extensions/wrapper.js +1 -20
  103. package/dist/core/extensions/wrapper.js.map +1 -1
  104. package/dist/core/index.d.ts +2 -1
  105. package/dist/core/index.d.ts.map +1 -1
  106. package/dist/core/index.js.map +1 -1
  107. package/dist/core/keybindings.d.ts +1 -1
  108. package/dist/core/keybindings.js +1 -1
  109. package/dist/core/keybindings.js.map +1 -1
  110. package/dist/core/messages.d.ts.map +1 -1
  111. package/dist/core/messages.js +1 -0
  112. package/dist/core/messages.js.map +1 -1
  113. package/dist/core/model-config.d.ts +101 -20
  114. package/dist/core/model-config.d.ts.map +1 -1
  115. package/dist/core/model-config.js +25 -18
  116. package/dist/core/model-config.js.map +1 -1
  117. package/dist/core/model-registry.d.ts +5 -1
  118. package/dist/core/model-registry.d.ts.map +1 -1
  119. package/dist/core/model-registry.js +8 -0
  120. package/dist/core/model-registry.js.map +1 -1
  121. package/dist/core/model-resolver.js +1 -1
  122. package/dist/core/model-resolver.js.map +1 -1
  123. package/dist/core/model-runtime.d.ts.map +1 -1
  124. package/dist/core/model-runtime.js +5 -3
  125. package/dist/core/model-runtime.js.map +1 -1
  126. package/dist/core/provider-composer.d.ts +3 -2
  127. package/dist/core/provider-composer.d.ts.map +1 -1
  128. package/dist/core/provider-composer.js +2 -0
  129. package/dist/core/provider-composer.js.map +1 -1
  130. package/dist/core/radius.d.ts +3 -0
  131. package/dist/core/radius.d.ts.map +1 -1
  132. package/dist/core/radius.js +6 -0
  133. package/dist/core/radius.js.map +1 -1
  134. package/dist/core/sdk.d.ts.map +1 -1
  135. package/dist/core/sdk.js +46 -18
  136. package/dist/core/sdk.js.map +1 -1
  137. package/dist/core/session-export.d.ts +6 -2
  138. package/dist/core/session-export.d.ts.map +1 -1
  139. package/dist/core/session-export.js +14 -13
  140. package/dist/core/session-export.js.map +1 -1
  141. package/dist/core/session-manager.d.ts +29 -6
  142. package/dist/core/session-manager.d.ts.map +1 -1
  143. package/dist/core/session-manager.js +159 -96
  144. package/dist/core/session-manager.js.map +1 -1
  145. package/dist/core/settings-manager.d.ts +31 -4
  146. package/dist/core/settings-manager.d.ts.map +1 -1
  147. package/dist/core/settings-manager.js +65 -8
  148. package/dist/core/settings-manager.js.map +1 -1
  149. package/dist/core/slash-commands.d.ts.map +1 -1
  150. package/dist/core/slash-commands.js +1 -0
  151. package/dist/core/slash-commands.js.map +1 -1
  152. package/dist/core/system-prompt.d.ts +49 -6
  153. package/dist/core/system-prompt.d.ts.map +1 -1
  154. package/dist/core/system-prompt.js +121 -92
  155. package/dist/core/system-prompt.js.map +1 -1
  156. package/dist/core/tools/bash.d.ts +2 -1
  157. package/dist/core/tools/bash.d.ts.map +1 -1
  158. package/dist/core/tools/bash.js +6 -3
  159. package/dist/core/tools/bash.js.map +1 -1
  160. package/dist/core/tools/edit.d.ts.map +1 -1
  161. package/dist/core/tools/edit.js +1 -2
  162. package/dist/core/tools/edit.js.map +1 -1
  163. package/dist/core/tools/read.d.ts.map +1 -1
  164. package/dist/core/tools/read.js +1 -2
  165. package/dist/core/tools/read.js.map +1 -1
  166. package/dist/core/tools/shell-execution.js +2 -2
  167. package/dist/core/tools/shell-execution.js.map +1 -1
  168. package/dist/core/tools/write.d.ts.map +1 -1
  169. package/dist/core/tools/write.js +1 -2
  170. package/dist/core/tools/write.js.map +1 -1
  171. package/dist/core/usage-totals.d.ts +1 -1
  172. package/dist/core/usage-totals.d.ts.map +1 -1
  173. package/dist/core/usage-totals.js +4 -1
  174. package/dist/core/usage-totals.js.map +1 -1
  175. package/dist/extensions/background/schema.d.ts +1 -1
  176. package/dist/extensions/btw/agent.d.ts.map +1 -1
  177. package/dist/extensions/btw/agent.js +3 -2
  178. package/dist/extensions/btw/agent.js.map +1 -1
  179. package/dist/extensions/llama/client.d.ts +2 -0
  180. package/dist/extensions/llama/client.d.ts.map +1 -1
  181. package/dist/extensions/llama/client.js +7 -3
  182. package/dist/extensions/llama/client.js.map +1 -1
  183. package/dist/extensions/llama/provider.d.ts.map +1 -1
  184. package/dist/extensions/llama/provider.js +20 -5
  185. package/dist/extensions/llama/provider.js.map +1 -1
  186. package/dist/extensions/provider/compat-fields.d.ts.map +1 -1
  187. package/dist/extensions/provider/compat-fields.js +5 -2
  188. package/dist/extensions/provider/compat-fields.js.map +1 -1
  189. package/dist/extensions/provider/ui/app.js +2 -2
  190. package/dist/extensions/provider/ui/app.js.map +1 -1
  191. package/dist/extensions/todo/schema.d.ts +1 -1
  192. package/dist/index.d.ts +3 -2
  193. package/dist/index.d.ts.map +1 -1
  194. package/dist/index.js.map +1 -1
  195. package/dist/main.d.ts.map +1 -1
  196. package/dist/main.js +13 -9
  197. package/dist/main.js.map +1 -1
  198. package/dist/migrations.d.ts.map +1 -1
  199. package/dist/migrations.js +2 -2
  200. package/dist/migrations.js.map +1 -1
  201. package/dist/modes/interactive/bug-report.d.ts +14 -0
  202. package/dist/modes/interactive/bug-report.d.ts.map +1 -0
  203. package/dist/modes/interactive/bug-report.js +209 -0
  204. package/dist/modes/interactive/bug-report.js.map +1 -0
  205. package/dist/modes/interactive/chat-viewport.js +1 -1
  206. package/dist/modes/interactive/chat-viewport.js.map +1 -1
  207. package/dist/modes/interactive/components/armin.d.ts.map +1 -1
  208. package/dist/modes/interactive/components/armin.js +0 -5
  209. package/dist/modes/interactive/components/armin.js.map +1 -1
  210. package/dist/modes/interactive/components/bash-execution.d.ts +0 -1
  211. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  212. package/dist/modes/interactive/components/bash-execution.js +2 -3
  213. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  214. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  215. package/dist/modes/interactive/components/branch-summary-message.js +12 -5
  216. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  217. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  218. package/dist/modes/interactive/components/compaction-summary-message.js +12 -5
  219. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  220. package/dist/modes/interactive/components/countdown-timer.d.ts +6 -13
  221. package/dist/modes/interactive/components/countdown-timer.d.ts.map +1 -1
  222. package/dist/modes/interactive/components/countdown-timer.js +15 -51
  223. package/dist/modes/interactive/components/countdown-timer.js.map +1 -1
  224. package/dist/modes/interactive/components/custom-editor.d.ts +3 -3
  225. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  226. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  227. package/dist/modes/interactive/components/extension-input.d.ts +2 -0
  228. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  229. package/dist/modes/interactive/components/extension-input.js +6 -0
  230. package/dist/modes/interactive/components/extension-input.js.map +1 -1
  231. package/dist/modes/interactive/components/extension-selector.d.ts +1 -2
  232. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  233. package/dist/modes/interactive/components/extension-selector.js +4 -2
  234. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  235. package/dist/modes/interactive/components/session-selector.d.ts +5 -8
  236. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  237. package/dist/modes/interactive/components/session-selector.js +72 -95
  238. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  239. package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
  240. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  241. package/dist/modes/interactive/components/settings-selector.js +11 -0
  242. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  243. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  244. package/dist/modes/interactive/components/skill-invocation-message.js +11 -4
  245. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  246. package/dist/modes/interactive/components/status-indicator.d.ts +2 -2
  247. package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -1
  248. package/dist/modes/interactive/components/status-indicator.js +7 -7
  249. package/dist/modes/interactive/components/status-indicator.js.map +1 -1
  250. package/dist/modes/interactive/components/tool-chrome.d.ts.map +1 -1
  251. package/dist/modes/interactive/components/tool-chrome.js +4 -1
  252. package/dist/modes/interactive/components/tool-chrome.js.map +1 -1
  253. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  254. package/dist/modes/interactive/components/tool-execution.js +4 -3
  255. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  256. package/dist/modes/interactive/components/tree-selector.d.ts +1 -10
  257. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  258. package/dist/modes/interactive/components/tree-selector.js +4 -51
  259. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  260. package/dist/modes/interactive/interactive-mode.d.ts +13 -1
  261. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  262. package/dist/modes/interactive/interactive-mode.js +227 -91
  263. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  264. package/dist/modes/interactive/session-share.d.ts +2 -0
  265. package/dist/modes/interactive/session-share.d.ts.map +1 -1
  266. package/dist/modes/interactive/session-share.js +8 -4
  267. package/dist/modes/interactive/session-share.js.map +1 -1
  268. package/dist/modes/interactive/tui-renderer.js +2 -2
  269. package/dist/modes/interactive/tui-renderer.js.map +1 -1
  270. package/dist/modes/rpc/rpc-mode.js +2 -2
  271. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  272. package/dist/utils/clipboard-command.d.ts +7 -0
  273. package/dist/utils/clipboard-command.d.ts.map +1 -0
  274. package/dist/utils/clipboard-command.js +45 -0
  275. package/dist/utils/clipboard-command.js.map +1 -0
  276. package/dist/utils/clipboard-image.d.ts.map +1 -1
  277. package/dist/utils/clipboard-image.js +52 -69
  278. package/dist/utils/clipboard-image.js.map +1 -1
  279. package/dist/utils/clipboard.d.ts.map +1 -1
  280. package/dist/utils/clipboard.js +61 -125
  281. package/dist/utils/clipboard.js.map +1 -1
  282. package/dist/utils/zip.d.ts +7 -0
  283. package/dist/utils/zip.d.ts.map +1 -0
  284. package/dist/utils/zip.js +60 -0
  285. package/dist/utils/zip.js.map +1 -0
  286. package/docs/compaction.md +32 -11
  287. package/docs/custom-provider.md +19 -8
  288. package/docs/environment-variables.md +1 -0
  289. package/docs/extensions.md +50 -34
  290. package/docs/models.md +38 -3
  291. package/docs/providers.md +1 -1
  292. package/docs/sdk.md +4 -2
  293. package/docs/session-format.md +63 -24
  294. package/docs/sessions.md +28 -1
  295. package/docs/settings.md +58 -4
  296. package/docs/termux.md +0 -1
  297. package/docs/tui.md +2 -0
  298. package/docs/usage.md +1 -0
  299. package/examples/extensions/README.md +0 -1
  300. package/examples/extensions/custom-provider-anthropic/index.ts +18 -12
  301. package/examples/extensions/custom-provider-gitlab-duo/index.ts +2 -2
  302. package/examples/extensions/prompt-customizer.ts +19 -67
  303. package/npm-shrinkwrap.json +275 -1028
  304. package/package.json +17 -23
  305. package/dist/bundle/chunks/anthropic-messages-IHRRITCV.js +0 -6
  306. package/dist/bundle/chunks/azure-openai-responses-QXBIDYXO.js +0 -2
  307. package/dist/bundle/chunks/chunk-JSJPY26N.js +0 -2
  308. package/dist/bundle/chunks/chunk-KI6LC764.js +0 -42
  309. package/dist/bundle/chunks/chunk-OWESLY5T.js +0 -11
  310. package/dist/bundle/chunks/chunk-RRBI7OUG.js +0 -50
  311. package/dist/bundle/chunks/chunk-YZJWMPUI.js +0 -2
  312. package/dist/bundle/chunks/google-generative-ai-NBOR3IQR.js +0 -2
  313. package/dist/bundle/chunks/google-vertex-VTHX3SUB.js +0 -2
  314. package/dist/bundle/chunks/https-proxy-agent-2VXB7436.js +0 -13
  315. package/dist/bundle/chunks/mistral-conversations-NO4ANAA5.js +0 -5
  316. package/dist/bundle/chunks/openai-codex-responses-5S3T3HMA.js +0 -10
  317. package/dist/bundle/chunks/openai-completions-CXRRTRWE.js +0 -7
  318. package/dist/bundle/chunks/openai-responses-CV3MH6VP.js +0 -2
  319. package/dist/utils/clipboard-native.d.ts +0 -11
  320. package/dist/utils/clipboard-native.d.ts.map +0 -1
  321. package/dist/utils/clipboard-native.js +0 -20
  322. package/dist/utils/clipboard-native.js.map +0 -1
  323. package/examples/extensions/kimi-deferred-tools.ts +0 -61
@@ -14,6 +14,8 @@ import { APP_NAME, APP_TITLE, CONFIG_DIR_NAME, getAgentDir, getAuthPath, getDebu
14
14
  import { parseSkillBlock } from "../../core/agent-session.js";
15
15
  import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
16
16
  import { CACHE_TTL_MS, collectCacheMisses, computeCacheWaste, detectCacheMiss, } from "../../core/cache-stats.js";
17
+ import { formatCacheWarmingStatus, formatCacheWarmingUsage } from "../../core/cache-warmer.js";
18
+ import { recordCrash, takeUnnotifiedCrash } from "../../core/crash-log.js";
17
19
  import { DEFAULT_THINKING_LEVEL, THINKING_LEVEL_OPTIONS } from "../../core/defaults.js";
18
20
  import { FooterDataProvider } from "../../core/footer-data-provider.js";
19
21
  import { configureHttpDispatcher, formatHttpIdleTimeoutMs } from "../../core/http-dispatcher.js";
@@ -23,7 +25,7 @@ import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScop
23
25
  import { CredentialSynchronizationError } from "../../core/model-runtime.js";
24
26
  import { DefaultPackageManager } from "../../core/package-manager.js";
25
27
  import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
26
- import { SessionManager, sessionEntryToContextMessages } from "../../core/session-manager.js";
28
+ import { SessionManager, sessionEntryToContextMessages, } from "../../core/session-manager.js";
27
29
  import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
28
30
  import { isInstallTelemetryEnabled } from "../../core/telemetry.js";
29
31
  import { withBuiltInRenderers } from "../../core/tools/renderers/index.js";
@@ -38,6 +40,7 @@ import { getPiUserAgent } from "../../utils/pi-user-agent.js";
38
40
  import { killTrackedDetachedChildren } from "../../utils/shell.js";
39
41
  import { ensureTool } from "../../utils/tools-manager.js";
40
42
  import { checkForNewPiVersion } from "../../utils/version-check.js";
43
+ import { reportBug } from "./bug-report.js";
41
44
  import { createChatViewport } from "./chat-viewport.js";
42
45
  import { ArminComponent } from "./components/armin.js";
43
46
  import { AssistantMessageComponent } from "./components/assistant-message.js";
@@ -104,6 +107,9 @@ function isCustomSessionEntry(item) {
104
107
  function isCompactionCostNotice(item) {
105
108
  return "type" in item && item.type === "compaction_cost";
106
109
  }
110
+ function isUsageSessionEntry(item) {
111
+ return "type" in item && item.type === "usage";
112
+ }
107
113
  const DEAD_TERMINAL_ERROR_CODES = new Set(["EIO", "EPIPE", "ENOTCONN"]);
108
114
  function isDeadTerminalError(error) {
109
115
  if (!error || typeof error !== "object" || !("code" in error)) {
@@ -251,6 +257,8 @@ export class InteractiveMode {
251
257
  this.compactionQueuedMessages = [];
252
258
  // Shutdown state
253
259
  this.shutdownRequested = false;
260
+ /** The `/bug` hint is shown at most once per session so error output stays readable. */
261
+ this.bugReportHintShown = false;
254
262
  // Extension UI state
255
263
  this.extensionSelector = undefined;
256
264
  this.extensionInput = undefined;
@@ -292,6 +300,7 @@ export class InteractiveMode {
292
300
  tuiMode,
293
301
  showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
294
302
  logDirectory: getAgentDir(),
303
+ terminal: options.terminal,
295
304
  onRightClickPaste: this.onRightClickPaste,
296
305
  fullscreenCopyOnSelect: this.settingsManager.getFullscreenCopyOnSelect(),
297
306
  });
@@ -772,6 +781,11 @@ export class InteractiveMode {
772
781
  if (modelFallbackMessage) {
773
782
  this.showWarning(modelFallbackMessage);
774
783
  }
784
+ const crash = takeUnnotifiedCrash();
785
+ if (crash) {
786
+ const when = new Date(crash.timestamp).toLocaleString();
787
+ this.showWarning(`${APP_NAME} crashed on ${when} (${crash.message}). Run /bug to report it; the crash details are attached automatically.`);
788
+ }
775
789
  void this.maybeWarnAboutAnthropicSubscriptionAuth();
776
790
  // Process initial messages
777
791
  if (initialMessage) {
@@ -1492,10 +1506,38 @@ export class InteractiveMode {
1492
1506
  async handleFatalRuntimeError(prefix, error) {
1493
1507
  const message = error instanceof Error ? error.message : String(error);
1494
1508
  this.showError(`${prefix}: ${message}`);
1509
+ if (this.recordCrash("fatal_error", error)) {
1510
+ this.chatContainer.addChild(new Text(theme.fg("muted", this.crashReportInstructions()), this.outputPad, 0));
1511
+ }
1495
1512
  stopThemeWatcher();
1496
1513
  this.stop("transcript");
1497
1514
  process.exit(1);
1498
1515
  }
1516
+ /** Persist a crash so the next start can point the user at `/bug`. Returns false when nothing was written. */
1517
+ recordCrash(kind, error) {
1518
+ try {
1519
+ return (recordCrash({
1520
+ kind,
1521
+ error,
1522
+ sessionFile: this.session.sessionFile,
1523
+ cwd: this.session.sessionManager.getCwd(),
1524
+ }) !== undefined);
1525
+ }
1526
+ catch {
1527
+ return false;
1528
+ }
1529
+ }
1530
+ crashReportInstructions() {
1531
+ const resume = this.session.sessionFile ? `run \`${APP_NAME} -r\` to resume the session, then` : "start pi and";
1532
+ return `To report this crash: ${resume} run /bug. The crash details are attached automatically.`;
1533
+ }
1534
+ suggestBugReport() {
1535
+ if (this.bugReportHintShown)
1536
+ return;
1537
+ this.bugReportHintShown = true;
1538
+ this.chatContainer.addChild(new Text(theme.fg("muted", `If this looks like a ${APP_NAME} bug, /bug sends a report to the developers.`), this.outputPad, 0));
1539
+ this.ui.requestRender();
1540
+ }
1499
1541
  disposeChatToolComponents() {
1500
1542
  for (const child of this.chatContainer.children) {
1501
1543
  const disposable = child;
@@ -1612,7 +1654,7 @@ export class InteractiveMode {
1612
1654
  this.activeWorkingIndicatorEmbedded = false;
1613
1655
  this.statusContainer.clear();
1614
1656
  this.setEditorWorkingStatusIndicator(undefined);
1615
- if (indicator instanceof WorkingStatusIndicator && this.setEditorWorkingStatusIndicator(indicator)) {
1657
+ if (this.setEditorWorkingStatusIndicator(indicator)) {
1616
1658
  this.activeWorkingIndicatorEmbedded = true;
1617
1659
  return;
1618
1660
  }
@@ -1623,7 +1665,7 @@ export class InteractiveMode {
1623
1665
  return;
1624
1666
  }
1625
1667
  const clearedIndicator = this.activeStatusIndicator;
1626
- const clearedIndicatorWasEmbedded = clearedIndicator?.kind === "working" && this.activeWorkingIndicatorEmbedded;
1668
+ const clearedIndicatorWasEmbedded = this.activeWorkingIndicatorEmbedded;
1627
1669
  clearedIndicator?.dispose();
1628
1670
  this.activeStatusIndicator = undefined;
1629
1671
  this.activeWorkingIndicatorEmbedded = false;
@@ -1974,7 +2016,7 @@ export class InteractiveMode {
1974
2016
  }, {
1975
2017
  tui: this.ui,
1976
2018
  timeout: opts?.timeout,
1977
- subtitle: opts?.subtitle,
2019
+ description: opts?.description,
1978
2020
  onToggleToolsExpanded: () => this.toggleToolOutputExpansion(),
1979
2021
  });
1980
2022
  this.disposeActiveSelector();
@@ -1999,9 +2041,9 @@ export class InteractiveMode {
1999
2041
  * Show a confirmation dialog for extensions.
2000
2042
  */
2001
2043
  async showExtensionConfirm(title, message, opts) {
2002
- // The message is context, not a heading: render it as the selector's muted
2003
- // subtitle instead of folding it into the accent-bold title.
2004
- const result = await this.showExtensionSelector(title, ["Yes", "No"], { ...opts, subtitle: message });
2044
+ // The message is context, not a heading: render it as the selector's
2045
+ // description instead of folding it into the accent-bold title.
2046
+ const result = await this.showExtensionSelector(title, ["Yes", "No"], { ...opts, description: message });
2005
2047
  return result === "Yes";
2006
2048
  }
2007
2049
  async promptForMissingSessionCwd(error) {
@@ -2139,7 +2181,7 @@ export class InteractiveMode {
2139
2181
  this.editor = this.defaultEditor;
2140
2182
  }
2141
2183
  this.editorContainer.addChild(this.editor);
2142
- if (this.activeStatusIndicator instanceof WorkingStatusIndicator) {
2184
+ if (this.activeStatusIndicator) {
2143
2185
  this.statusContainer.clear();
2144
2186
  this.activeWorkingIndicatorEmbedded = this.setEditorWorkingStatusIndicator(this.activeStatusIndicator);
2145
2187
  if (!this.activeWorkingIndicatorEmbedded) {
@@ -2464,6 +2506,12 @@ export class InteractiveMode {
2464
2506
  this.editor.setText("");
2465
2507
  return;
2466
2508
  }
2509
+ if (text === "/bug" || text.startsWith("/bug ")) {
2510
+ const hint = text.slice("/bug".length).trim();
2511
+ this.editor.setText("");
2512
+ await this.handleBugCommand(hint ? hint : undefined);
2513
+ return;
2514
+ }
2467
2515
  if (text === "/copy") {
2468
2516
  await this.handleCopyCommand();
2469
2517
  this.editor.setText("");
@@ -2654,6 +2702,10 @@ export class InteractiveMode {
2654
2702
  this.addCustomEntryToChat(event.entry);
2655
2703
  this.ui.requestRender();
2656
2704
  }
2705
+ else if (event.entry.type === "usage" && event.entry.kind === "cache_warm") {
2706
+ this.addCacheWarmingUsage(event.entry);
2707
+ this.ui.requestRender();
2708
+ }
2657
2709
  break;
2658
2710
  case "session_info_changed":
2659
2711
  this.updateTerminalTitle();
@@ -2730,13 +2782,15 @@ export class InteractiveMode {
2730
2782
  });
2731
2783
  }
2732
2784
  this.pendingTools.clear();
2785
+ if (this.streamingMessage.stopReason === "error")
2786
+ this.suggestBugReport();
2733
2787
  }
2734
2788
  else {
2735
2789
  // Args are now complete - trigger diff computation for edit tools
2736
2790
  for (const [, component] of this.pendingTools.entries()) {
2737
2791
  component.setArgsComplete();
2738
2792
  }
2739
- this.maybeShowAssistantDiagnostics(this.streamingMessage);
2793
+ this.maybeShowThinkingDropNotice(this.streamingMessage);
2740
2794
  this.maybeShowCacheMissNotice(this.streamingMessage);
2741
2795
  }
2742
2796
  this.streamingComponent = undefined;
@@ -2873,6 +2927,7 @@ export class InteractiveMode {
2873
2927
  // Show error only on final failure (success shows normal response)
2874
2928
  if (!event.success) {
2875
2929
  this.showError(`Retry failed after ${event.attempt} attempts: ${event.finalError || "Unknown error"}`);
2930
+ this.suggestBugReport();
2876
2931
  }
2877
2932
  this.ui.requestRender();
2878
2933
  break;
@@ -2999,6 +3054,8 @@ export class InteractiveMode {
2999
3054
  this.chatContainer.addChild(component);
3000
3055
  break;
3001
3056
  }
3057
+ case "system":
3058
+ break;
3002
3059
  case "user": {
3003
3060
  const textContent = this.getUserMessageText(message);
3004
3061
  if (textContent) {
@@ -3045,8 +3102,8 @@ export class InteractiveMode {
3045
3102
  renderSessionItems(items, options = {}) {
3046
3103
  this.clearPendingTools();
3047
3104
  const renderedPendingTools = new Map();
3048
- // Cache-miss notices are not persisted; re-derive them from the full entry
3049
- // list and re-inject them after the assistant messages that paid for them.
3105
+ // Cache misses are not persisted, unlike successful cache-warming usage.
3106
+ // Re-derive them and inject them after the assistant messages that paid for them.
3050
3107
  const cacheMisses = this.settingsManager.getShowCacheMissNotices()
3051
3108
  ? collectCacheMisses(this.sessionManager.getEntries(), this.session.modelRuntime)
3052
3109
  : new Map();
@@ -3059,6 +3116,10 @@ export class InteractiveMode {
3059
3116
  this.addCustomEntryToChat(item);
3060
3117
  continue;
3061
3118
  }
3119
+ if (isUsageSessionEntry(item)) {
3120
+ this.addCacheWarmingUsage(item);
3121
+ continue;
3122
+ }
3062
3123
  if (isCompactionCostNotice(item)) {
3063
3124
  this.addCompactionCostNotice(item);
3064
3125
  continue;
@@ -3092,7 +3153,6 @@ export class InteractiveMode {
3092
3153
  }
3093
3154
  }
3094
3155
  if (message.stopReason !== "aborted" && message.stopReason !== "error") {
3095
- this.maybeShowAssistantDiagnostics(message);
3096
3156
  const miss = cacheMisses.get(message);
3097
3157
  if (miss)
3098
3158
  this.addCacheMissNotice(miss);
@@ -3124,7 +3184,7 @@ export class InteractiveMode {
3124
3184
  */
3125
3185
  renderSessionEntries(entries, options = {}) {
3126
3186
  const items = entries.flatMap((entry) => {
3127
- if (entry.type === "custom") {
3187
+ if (entry.type === "custom" || (entry.type === "usage" && entry.kind === "cache_warm")) {
3128
3188
  return [entry];
3129
3189
  }
3130
3190
  const messages = sessionEntryToContextMessages(entry);
@@ -3135,6 +3195,12 @@ export class InteractiveMode {
3135
3195
  });
3136
3196
  this.renderSessionItems(items, options);
3137
3197
  }
3198
+ addCacheWarmingUsage(entry) {
3199
+ if (!this.settingsManager.getShowCacheMissNotices())
3200
+ return;
3201
+ this.chatContainer.addChild(new Spacer(1));
3202
+ this.chatContainer.addChild(new Text(theme.fg("dim", formatCacheWarmingUsage(entry)), 1, 0));
3203
+ }
3138
3204
  /**
3139
3205
  * Render billing usage for a compaction or branch summary. The notice is derived
3140
3206
  * from persisted summary usage and is not stored as a separate session entry.
@@ -3149,31 +3215,42 @@ export class InteractiveMode {
3149
3215
  this.chatContainer.addChild(new Spacer(1));
3150
3216
  this.chatContainer.addChild(new Text(theme.fg("warning", `${label}: ${formatTokens(tokens)} tokens billed${cost}`), 1, 0));
3151
3217
  }
3152
- maybeShowAssistantDiagnostics(message) {
3153
- if (!this.settingsManager.getShowCacheMissNotices())
3154
- return;
3218
+ static countDroppedThinkingBlocks(message) {
3219
+ let count = 0;
3155
3220
  for (const diagnostic of message.diagnostics ?? []) {
3156
3221
  if (diagnostic.type !== "anthropic_input_transformations")
3157
3222
  continue;
3158
3223
  const transformations = diagnostic.details?.transformations;
3159
3224
  if (!Array.isArray(transformations))
3160
3225
  continue;
3161
- const dropped = transformations.flatMap((transformation) => {
3162
- if (typeof transformation !== "object" || transformation === null)
3163
- return [];
3164
- const details = transformation;
3165
- if (details.type !== "thinking_dropped")
3166
- return [];
3167
- const reason = typeof details.reason === "string" ? details.reason : "unknown reason";
3168
- const location = typeof details.path === "string" ? ` at ${details.path}` : "";
3169
- return [`${reason}${location}`];
3170
- });
3171
- if (dropped.length === 0)
3172
- continue;
3173
- const noun = dropped.length === 1 ? "thinking block" : `${dropped.length} thinking blocks`;
3174
- this.chatContainer.addChild(new Spacer(1));
3175
- this.chatContainer.addChild(new Text(theme.fg("warning", `Anthropic dropped ${noun}: ${dropped.join("; ")}`), 1, 0));
3226
+ count += transformations.filter((transformation) => typeof transformation === "object" &&
3227
+ transformation !== null &&
3228
+ transformation.type === "thinking_dropped").length;
3176
3229
  }
3230
+ return count;
3231
+ }
3232
+ maybeShowThinkingDropNotice(message) {
3233
+ if (!this.settingsManager.getShowCacheMissNotices())
3234
+ return;
3235
+ const droppedCount = InteractiveMode.countDroppedThinkingBlocks(message);
3236
+ if (droppedCount === 0)
3237
+ return;
3238
+ let previousDroppedCount = 0;
3239
+ // message_end reaches the UI before the current message is persisted,
3240
+ // so the branch's last assistant message is the previous response.
3241
+ const branch = this.sessionManager.getBranch();
3242
+ for (let i = branch.length - 1; i >= 0; i--) {
3243
+ const entry = branch[i];
3244
+ if (entry.type === "message" && entry.message.role === "assistant") {
3245
+ previousDroppedCount = InteractiveMode.countDroppedThinkingBlocks(entry.message);
3246
+ break;
3247
+ }
3248
+ }
3249
+ if (droppedCount <= previousDroppedCount)
3250
+ return;
3251
+ const noun = droppedCount === 1 ? "thinking block" : "thinking blocks";
3252
+ this.chatContainer.addChild(new Spacer(1));
3253
+ this.chatContainer.addChild(new Text(theme.fg("warning", `Anthropic dropped ${droppedCount} ${noun} (details in session)`), 1, 0));
3177
3254
  }
3178
3255
  /**
3179
3256
  * Show a transcript notice when a completed assistant message paid for a
@@ -3336,6 +3413,9 @@ export class InteractiveMode {
3336
3413
  catch { }
3337
3414
  console.error(`${APP_NAME} exiting due to uncaughtException:`);
3338
3415
  console.error(error);
3416
+ if (this.recordCrash("uncaught_exception", error)) {
3417
+ console.error(`\n${this.crashReportInstructions()}`);
3418
+ }
3339
3419
  process.exit(1);
3340
3420
  }
3341
3421
  /**
@@ -3443,9 +3523,7 @@ export class InteractiveMode {
3443
3523
  const level = this.session.thinkingLevel || "off";
3444
3524
  this.editor.borderColor = theme.getThinkingBorderColor(level);
3445
3525
  }
3446
- if (this.activeStatusIndicator?.kind === "working") {
3447
- this.activeStatusIndicator.invalidate();
3448
- }
3526
+ this.activeStatusIndicator?.invalidate();
3449
3527
  this.ui.requestRender();
3450
3528
  }
3451
3529
  cycleThinkingLevel() {
@@ -3635,7 +3713,7 @@ export class InteractiveMode {
3635
3713
  if (allQueued.length === 0) {
3636
3714
  this.updatePendingMessagesDisplay();
3637
3715
  if (options?.abort) {
3638
- this.agent.abort();
3716
+ void this.session.abort();
3639
3717
  }
3640
3718
  return 0;
3641
3719
  }
@@ -3645,7 +3723,7 @@ export class InteractiveMode {
3645
3723
  this.editor.setText(combinedText);
3646
3724
  this.updatePendingMessagesDisplay();
3647
3725
  if (options?.abort) {
3648
- this.agent.abort();
3726
+ void this.session.abort();
3649
3727
  }
3650
3728
  return allQueued.length;
3651
3729
  }
@@ -3806,6 +3884,7 @@ export class InteractiveMode {
3806
3884
  followUpMode: this.session.followUpMode,
3807
3885
  transport: this.settingsManager.getTransport(),
3808
3886
  httpIdleTimeoutMs: this.settingsManager.getHttpIdleTimeoutMs(),
3887
+ cacheWarmingMode: this.settingsManager.getCacheWarmingMode(),
3809
3888
  thinkingLevel: this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL,
3810
3889
  availableThinkingLevels: [...THINKING_LEVEL_OPTIONS],
3811
3890
  modelThinkingLevels: this.settingsManager.getAllModelThinkingLevels(),
@@ -3878,6 +3957,10 @@ export class InteractiveMode {
3878
3957
  configureHttpDispatcher(timeoutMs);
3879
3958
  this.showStatus(`HTTP idle timeout: ${formatHttpIdleTimeoutMs(timeoutMs)}`);
3880
3959
  },
3960
+ onCacheWarmingModeChange: (mode) => {
3961
+ this.session.setCacheWarmingMode(mode);
3962
+ this.showStatus(`Cache warming: ${mode}`);
3963
+ },
3881
3964
  onModelThinkingLevelChange: (provider, modelId, level) => {
3882
3965
  this.settingsManager.setModelThinkingLevel(provider, modelId, level);
3883
3966
  // If the override is for the current model, apply it to the session too
@@ -4429,6 +4512,11 @@ export class InteractiveMode {
4429
4512
  this.restoreQueuedMessagesToEditor();
4430
4513
  await this.session.abort();
4431
4514
  }
4515
+ // Recheck after the dialogs and streaming abort, before replacing another operation's UI.
4516
+ if (this.session.isCompacting) {
4517
+ this.showError("Wait for the current compaction or tree navigation to finish before navigating the session tree.");
4518
+ return;
4519
+ }
4432
4520
  // Set up escape handler and status indicator if summarizing
4433
4521
  let showingSummaryIndicator = false;
4434
4522
  const originalOnEscape = this.defaultEditor.onEscape;
@@ -4480,7 +4568,7 @@ export class InteractiveMode {
4480
4568
  }, (entryId, label) => {
4481
4569
  this.sessionManager.appendLabelChange(entryId, label);
4482
4570
  this.ui.requestRender();
4483
- }, initialSelectedId, initialFilterMode, () => this.ui.requestRender());
4571
+ }, initialSelectedId, initialFilterMode);
4484
4572
  selector.onCopy = async (text) => {
4485
4573
  if (!text) {
4486
4574
  this.showError("Selected entry has no text to copy");
@@ -4494,14 +4582,14 @@ export class InteractiveMode {
4494
4582
  this.showError(error instanceof Error ? error.message : String(error));
4495
4583
  }
4496
4584
  };
4497
- return { component: selector, focus: selector, dispose: () => selector.dispose() };
4585
+ return { component: selector, focus: selector };
4498
4586
  });
4499
4587
  }
4500
4588
  showSessionSelector() {
4501
4589
  this.showSelector((done) => {
4502
- const selector = new SessionSelectorComponent((onProgress) => SessionManager.list(this.sessionManager.getCwd(), this.sessionManager.getSessionDir(), onProgress), (onProgress) => this.sessionManager.usesDefaultSessionDir()
4503
- ? SessionManager.listAll(onProgress)
4504
- : SessionManager.listAll(this.sessionManager.getSessionDir(), onProgress), async (sessionPath) => {
4590
+ const selector = new SessionSelectorComponent((onProgress, signal) => SessionManager.list(this.sessionManager.getCwd(), this.sessionManager.getSessionDir(), onProgress, signal), (onProgress, signal) => this.sessionManager.usesDefaultSessionDir()
4591
+ ? SessionManager.listAll(onProgress, signal)
4592
+ : SessionManager.listAll(this.sessionManager.getSessionDir(), onProgress, signal), async (sessionPath) => {
4505
4593
  done();
4506
4594
  await this.handleResumeSession(sessionPath);
4507
4595
  }, () => {
@@ -4520,7 +4608,7 @@ export class InteractiveMode {
4520
4608
  showRenameHint: true,
4521
4609
  keybindings: this.keybindings,
4522
4610
  }, this.sessionManager.getSessionFile());
4523
- return { component: selector, focus: selector, dispose: () => selector.dispose() };
4611
+ return { component: selector, focus: selector };
4524
4612
  });
4525
4613
  }
4526
4614
  async handleResumeSession(sessionPath, options) {
@@ -4764,67 +4852,89 @@ export class InteractiveMode {
4764
4852
  }
4765
4853
  async completeProviderAuthentication(providerId, providerName, authType, previousModel) {
4766
4854
  const actionLabel = authType === "oauth" ? `Logged in to ${providerName}` : `Saved API key for ${providerName}`;
4767
- let selectedModel;
4768
- let selectionError;
4769
- if (isUnknownModel(previousModel)) {
4770
- const availableModels = this.session.modelRuntime.getAvailableSnapshot();
4771
- const providerModels = availableModels.filter((model) => model.provider === providerId);
4772
- // Matches LLAMA_PROVIDER_ID from extensions/llama/provider.ts; kept inline to avoid coupling interactive mode to the built-in extension.
4773
- if (providerId === "llama.cpp") {
4774
- selectionError = llamaCppPostLoginGuidance(actionLabel, providerModels.length);
4775
- }
4776
- else if (!hasDefaultModelProvider(providerId)) {
4777
- selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
4778
- }
4779
- else if (providerModels.length === 0) {
4780
- selectionError = `${actionLabel}, but no models are available for that provider. Use /model to select a model.`;
4781
- }
4782
- else {
4783
- const defaultModelId = defaultModelPerProvider[providerId];
4784
- selectedModel = providerModels.find((model) => model.id === defaultModelId);
4785
- if (!selectedModel) {
4786
- selectionError = `${actionLabel}, but its default model "${defaultModelId}" is not available. Use /model to select a model.`;
4855
+ const session = this.session;
4856
+ // Dynamic catalogs may be empty until the first authenticated network refresh.
4857
+ const deferSelection = isUnknownModel(previousModel) &&
4858
+ hasDefaultModelProvider(providerId) &&
4859
+ !session.modelRuntime
4860
+ .getAvailableSnapshot()
4861
+ .some((model) => model.provider === providerId && model.id === defaultModelPerProvider[providerId]);
4862
+ const finishAuthentication = async () => {
4863
+ let selectedModel;
4864
+ let selectionError;
4865
+ if (isUnknownModel(previousModel)) {
4866
+ const availableModels = this.session.modelRuntime.getAvailableSnapshot();
4867
+ const providerModels = availableModels.filter((model) => model.provider === providerId);
4868
+ // Matches LLAMA_PROVIDER_ID from extensions/llama/provider.ts; kept inline to avoid coupling interactive mode to the built-in extension.
4869
+ if (providerId === "llama.cpp") {
4870
+ selectionError = llamaCppPostLoginGuidance(actionLabel, providerModels.length);
4871
+ }
4872
+ else if (!hasDefaultModelProvider(providerId)) {
4873
+ selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
4874
+ }
4875
+ else if (providerModels.length === 0) {
4876
+ selectionError = `${actionLabel}, but no models are available for that provider. Use /model to select a model.`;
4787
4877
  }
4788
4878
  else {
4789
- try {
4790
- await this.session.setModel(selectedModel, { persist: true });
4879
+ const defaultModelId = defaultModelPerProvider[providerId];
4880
+ // Radius catalogs vary by account; prefer balanced, then use catalog order.
4881
+ selectedModel =
4882
+ providerModels.find((model) => model.id === defaultModelId) ??
4883
+ (providerId === "radius" ? providerModels[0] : undefined);
4884
+ if (!selectedModel) {
4885
+ selectionError = `${actionLabel}, but its default model "${defaultModelId}" is not available. Use /model to select a model.`;
4791
4886
  }
4792
- catch (error) {
4793
- selectedModel = undefined;
4794
- const errorMessage = error instanceof Error ? error.message : String(error);
4795
- selectionError = `${actionLabel}, but selecting its default model failed: ${errorMessage}. Use /model to select a model.`;
4887
+ else {
4888
+ try {
4889
+ await this.session.setModel(selectedModel, { persist: true });
4890
+ }
4891
+ catch (error) {
4892
+ selectedModel = undefined;
4893
+ const errorMessage = error instanceof Error ? error.message : String(error);
4894
+ selectionError = `${actionLabel}, but selecting its default model failed: ${errorMessage}. Use /model to select a model.`;
4895
+ }
4796
4896
  }
4797
4897
  }
4798
4898
  }
4799
- }
4800
- await this.updateAvailableProviderCount();
4801
- this.footer.invalidate();
4802
- this.updateEditorBorderColor();
4803
- if (selectedModel) {
4804
- this.showStatus(`${actionLabel}. Selected ${selectedModel.id}. Credentials saved to ${getAuthPath()}`);
4805
- void this.maybeWarnAboutAnthropicSubscriptionAuth(selectedModel);
4806
- this.checkDaxnutsEasterEgg(selectedModel);
4807
- }
4808
- else {
4809
- this.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}`);
4810
- if (selectionError) {
4811
- this.showError(selectionError);
4899
+ await this.updateAvailableProviderCount();
4900
+ this.footer.invalidate();
4901
+ this.updateEditorBorderColor();
4902
+ if (selectedModel) {
4903
+ this.showStatus(`${actionLabel}. Selected ${selectedModel.id}. Credentials saved to ${getAuthPath()}`);
4904
+ void this.maybeWarnAboutAnthropicSubscriptionAuth(selectedModel);
4905
+ this.checkDaxnutsEasterEgg(selectedModel);
4812
4906
  }
4813
4907
  else {
4814
- void this.maybeWarnAboutAnthropicSubscriptionAuth();
4908
+ this.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}`);
4909
+ if (selectionError) {
4910
+ this.showError(selectionError);
4911
+ }
4912
+ else {
4913
+ void this.maybeWarnAboutAnthropicSubscriptionAuth();
4914
+ }
4815
4915
  }
4916
+ };
4917
+ if (deferSelection) {
4918
+ this.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}. Refreshing model catalog…`);
4919
+ }
4920
+ else {
4921
+ await finishAuthentication();
4816
4922
  }
4817
4923
  const controller = new AbortController();
4818
4924
  const timeout = setTimeout(() => controller.abort(), 15_000);
4819
- void this.session.modelRuntime
4925
+ void session.modelRuntime
4820
4926
  .refresh({ providers: [providerId], signal: controller.signal })
4821
- .then((result) => {
4927
+ .then(async (result) => {
4822
4928
  if (result.aborted) {
4823
4929
  this.showWarning(`${actionLabel}, but its model catalog refresh timed out; using cached models.`);
4824
4930
  }
4825
4931
  else if (result.errors.size > 0) {
4826
4932
  this.showWarning(`${actionLabel}, but its model catalog could not be refreshed; using cached models.`);
4827
4933
  }
4934
+ // Do not replace a model or session selected while the refresh was running.
4935
+ if (deferSelection && this.session === session && session.model === previousModel) {
4936
+ await finishAuthentication();
4937
+ }
4828
4938
  this.updateAvailableProviderCount();
4829
4939
  this.footer.invalidate();
4830
4940
  this.ui.requestRender();
@@ -5166,6 +5276,16 @@ export class InteractiveMode {
5166
5276
  showError: (message) => this.showError(message),
5167
5277
  });
5168
5278
  }
5279
+ async handleBugCommand(hint) {
5280
+ await reportBug({
5281
+ session: this.session,
5282
+ ui: this.ui,
5283
+ editorContainer: this.editorContainer,
5284
+ editor: this.editor,
5285
+ showStatus: (message) => this.showStatus(message),
5286
+ showError: (message) => this.showError(message),
5287
+ }, hint);
5288
+ }
5169
5289
  async handleCopyCommand(options = {}) {
5170
5290
  if (options.preferSelection &&
5171
5291
  this.ui instanceof TuiAltScreen &&
@@ -5251,6 +5371,15 @@ export class InteractiveMode {
5251
5371
  }
5252
5372
  info += `${theme.fg("dim", "Output:")} ${stats.tokens.output.toLocaleString()}\n`;
5253
5373
  info += `${theme.fg("dim", "Total:")} ${stats.tokens.total.toLocaleString()}\n`;
5374
+ const cacheWarmingStatus = this.session.cacheWarmingStatus;
5375
+ info += `\n${theme.bold("Cache Warming")}\n`;
5376
+ info += `${theme.fg("dim", "Mode:")} ${this.settingsManager.getCacheWarmingMode()}\n`;
5377
+ info += `${theme.fg("dim", "Status:")} ${cacheWarmingStatus ? formatCacheWarmingStatus(cacheWarmingStatus) : "Inactive (cache warming unavailable)"}\n`;
5378
+ const decision = cacheWarmingStatus?.decision;
5379
+ if (decision?.economicsAvailable) {
5380
+ info += `${theme.fg("dim", "Cache miss penalty:")} $${decision.missCost.toFixed(3)}\n`;
5381
+ info += `${theme.fg("dim", "Refresh cost:")} $${decision.warmCost.toFixed(3)}\n`;
5382
+ }
5254
5383
  if (stats.cost > 0 || cacheWaste.missedTokens > 0) {
5255
5384
  info += `\n${theme.bold("Cost")}\n`;
5256
5385
  info += `${theme.fg("dim", "Total:")} $${stats.cost.toFixed(3)}`;
@@ -5381,7 +5510,7 @@ export class InteractiveMode {
5381
5510
  | \`${expandTools}\` | Toggle tool output expansion |
5382
5511
  | \`${toggleThinking}\` | Toggle thinking block visibility |
5383
5512
  | \`${externalEditor}\` | Edit message in external editor |
5384
- | \`${copyMessage}\` | Copy last assistant message |
5513
+ | \`${copyMessage}\` | Copy selection or last assistant message |
5385
5514
  | \`${followUp}\` | Queue follow-up message |
5386
5515
  | \`${dequeue}\` | Restore queued messages |
5387
5516
  | \`${pasteImage}\` | Paste image or text from clipboard |
@@ -5477,12 +5606,19 @@ export class InteractiveMode {
5477
5606
  async handleBashCommand(command, excludeFromContext = false) {
5478
5607
  const extensionRunner = this.session.extensionRunner;
5479
5608
  // Emit user_bash event to let extensions intercept
5480
- const eventResult = await extensionRunner.emitUserBash({
5481
- type: "user_bash",
5482
- command,
5483
- excludeFromContext,
5484
- cwd: this.sessionManager.getCwd(),
5485
- });
5609
+ let eventResult;
5610
+ try {
5611
+ eventResult = await extensionRunner.emitUserBash({
5612
+ type: "user_bash",
5613
+ command,
5614
+ excludeFromContext,
5615
+ cwd: this.sessionManager.getCwd(),
5616
+ });
5617
+ }
5618
+ catch {
5619
+ // The extension runner already reported the error. Do not fall back to local execution.
5620
+ return;
5621
+ }
5486
5622
  // If extension returned a full result, use it directly
5487
5623
  if (eventResult?.result) {
5488
5624
  const result = eventResult.result;