@earendil-works/pi-coding-agent 0.85.1 → 0.86.1

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 (327) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/README.md +3 -1
  3. package/dist/bundle/chunks/anthropic-messages-MYU5ZMRF.js +6 -0
  4. package/dist/bundle/chunks/azure-openai-responses-HUSQ3GP2.js +2 -0
  5. package/dist/bundle/chunks/bedrock-converse-stream.js +47 -38
  6. package/dist/bundle/chunks/chunk-A3JYRWB6.js +2 -0
  7. package/dist/bundle/chunks/chunk-CMRUVXTE.js +1562 -0
  8. package/dist/bundle/chunks/chunk-DTD7JQ7Y.js +42 -0
  9. package/dist/bundle/chunks/chunk-FUXEF6JQ.js +44 -0
  10. package/dist/bundle/chunks/chunk-HTEQD2HM.js +2 -0
  11. package/dist/bundle/chunks/{chunk-IDDQWTHI.js → chunk-RCZIEVGO.js} +10 -1
  12. package/dist/bundle/chunks/chunk-S7SZN6Z3.js +2 -0
  13. package/dist/bundle/chunks/chunk-WIUZI2CJ.js +11 -0
  14. package/dist/bundle/chunks/github-copilot.js +1 -1
  15. package/dist/bundle/chunks/google-generative-ai-IMS5EK2Y.js +2 -0
  16. package/dist/bundle/chunks/google-vertex-O4YJEQUW.js +2 -0
  17. package/dist/bundle/chunks/jiti-loader-C3NU2SDH.js +21 -0
  18. package/dist/bundle/chunks/jiti-static-loader-E6ANQD5T.js +2 -0
  19. package/dist/bundle/chunks/meta.js +2 -0
  20. package/dist/bundle/chunks/mistral-conversations-NOSQHBEY.js +5 -0
  21. package/dist/bundle/chunks/{node-KG362ECQ.js → node-O3RIBJRH.js} +1 -1
  22. package/dist/bundle/chunks/openai-codex-responses-QDSOTBBO.js +10 -0
  23. package/dist/bundle/chunks/openai-completions-CYGM3XXP.js +7 -0
  24. package/dist/bundle/chunks/openai-responses-HWEUZ6WZ.js +2 -0
  25. package/dist/bundle/chunks/{pi-messages-PML4SPVJ.js → pi-messages-GFKBWJHZ.js} +1 -1
  26. package/dist/bundle/chunks/virtual-modules-MGTKWDID.js +2 -0
  27. package/dist/bundle/cli-runtime.js +3 -0
  28. package/dist/bundle/cli.js +4 -2
  29. package/dist/bundle/index.js +1 -1
  30. package/dist/bundle/rpc-entry.js +1 -1
  31. package/dist/cli/args.d.ts.map +1 -1
  32. package/dist/cli/args.js +1 -0
  33. package/dist/cli/args.js.map +1 -1
  34. package/dist/cli/session-picker.d.ts +1 -1
  35. package/dist/cli/session-picker.d.ts.map +1 -1
  36. package/dist/cli/session-picker.js.map +1 -1
  37. package/dist/config.d.ts.map +1 -1
  38. package/dist/config.js +1 -1
  39. package/dist/config.js.map +1 -1
  40. package/dist/core/agent-session.d.ts +56 -6
  41. package/dist/core/agent-session.d.ts.map +1 -1
  42. package/dist/core/agent-session.js +293 -155
  43. package/dist/core/agent-session.js.map +1 -1
  44. package/dist/core/bug-report-upload.d.ts +12 -0
  45. package/dist/core/bug-report-upload.d.ts.map +1 -0
  46. package/dist/core/bug-report-upload.js +22 -0
  47. package/dist/core/bug-report-upload.js.map +1 -0
  48. package/dist/core/bug-report.d.ts +176 -0
  49. package/dist/core/bug-report.d.ts.map +1 -0
  50. package/dist/core/bug-report.js +292 -0
  51. package/dist/core/bug-report.js.map +1 -0
  52. package/dist/core/cache-stats.d.ts.map +1 -1
  53. package/dist/core/cache-stats.js +12 -1
  54. package/dist/core/cache-stats.js.map +1 -1
  55. package/dist/core/cache-warmer.d.ts +102 -0
  56. package/dist/core/cache-warmer.d.ts.map +1 -0
  57. package/dist/core/cache-warmer.js +342 -0
  58. package/dist/core/cache-warmer.js.map +1 -0
  59. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  60. package/dist/core/compaction/branch-summarization.js +2 -2
  61. package/dist/core/compaction/branch-summarization.js.map +1 -1
  62. package/dist/core/compaction/compaction.d.ts +2 -2
  63. package/dist/core/compaction/compaction.d.ts.map +1 -1
  64. package/dist/core/compaction/compaction.js +11 -12
  65. package/dist/core/compaction/compaction.js.map +1 -1
  66. package/dist/core/crash-log.d.ts +22 -0
  67. package/dist/core/crash-log.d.ts.map +1 -0
  68. package/dist/core/crash-log.js +71 -0
  69. package/dist/core/crash-log.js.map +1 -0
  70. package/dist/core/experimental.d.ts +0 -4
  71. package/dist/core/experimental.d.ts.map +1 -1
  72. package/dist/core/experimental.js +0 -4
  73. package/dist/core/experimental.js.map +1 -1
  74. package/dist/core/extensions/index.d.ts +1 -1
  75. package/dist/core/extensions/index.d.ts.map +1 -1
  76. package/dist/core/extensions/index.js.map +1 -1
  77. package/dist/core/extensions/jiti-loader.d.ts +2 -0
  78. package/dist/core/extensions/jiti-loader.d.ts.map +1 -0
  79. package/dist/core/extensions/jiti-loader.js +4 -0
  80. package/dist/core/extensions/jiti-loader.js.map +1 -0
  81. package/dist/core/extensions/jiti-static-loader.d.ts +2 -0
  82. package/dist/core/extensions/jiti-static-loader.d.ts.map +1 -0
  83. package/dist/core/extensions/jiti-static-loader.js +4 -0
  84. package/dist/core/extensions/jiti-static-loader.js.map +1 -0
  85. package/dist/core/extensions/loader.d.ts.map +1 -1
  86. package/dist/core/extensions/loader.js +38 -51
  87. package/dist/core/extensions/loader.js.map +1 -1
  88. package/dist/core/extensions/runner.d.ts +10 -7
  89. package/dist/core/extensions/runner.d.ts.map +1 -1
  90. package/dist/core/extensions/runner.js +85 -67
  91. package/dist/core/extensions/runner.js.map +1 -1
  92. package/dist/core/extensions/types.d.ts +60 -50
  93. package/dist/core/extensions/types.d.ts.map +1 -1
  94. package/dist/core/extensions/types.js.map +1 -1
  95. package/dist/core/extensions/virtual-modules.d.ts +3 -0
  96. package/dist/core/extensions/virtual-modules.d.ts.map +1 -0
  97. package/dist/core/extensions/virtual-modules.js +38 -0
  98. package/dist/core/extensions/virtual-modules.js.map +1 -0
  99. package/dist/core/extensions/wrapper.d.ts.map +1 -1
  100. package/dist/core/extensions/wrapper.js +1 -20
  101. package/dist/core/extensions/wrapper.js.map +1 -1
  102. package/dist/core/index.d.ts +2 -1
  103. package/dist/core/index.d.ts.map +1 -1
  104. package/dist/core/index.js.map +1 -1
  105. package/dist/core/keybindings.d.ts +1 -1
  106. package/dist/core/keybindings.d.ts.map +1 -1
  107. package/dist/core/keybindings.js +1 -1
  108. package/dist/core/keybindings.js.map +1 -1
  109. package/dist/core/messages.d.ts.map +1 -1
  110. package/dist/core/messages.js +1 -0
  111. package/dist/core/messages.js.map +1 -1
  112. package/dist/core/model-config.d.ts +101 -20
  113. package/dist/core/model-config.d.ts.map +1 -1
  114. package/dist/core/model-config.js +25 -18
  115. package/dist/core/model-config.js.map +1 -1
  116. package/dist/core/model-registry.d.ts +5 -1
  117. package/dist/core/model-registry.d.ts.map +1 -1
  118. package/dist/core/model-registry.js +8 -0
  119. package/dist/core/model-registry.js.map +1 -1
  120. package/dist/core/model-resolver.d.ts.map +1 -1
  121. package/dist/core/model-resolver.js +2 -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 +60 -38
  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 +20 -4
  146. package/dist/core/settings-manager.d.ts.map +1 -1
  147. package/dist/core/settings-manager.js +43 -7
  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 +10 -4
  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/renderers/bash.d.ts.map +1 -1
  167. package/dist/core/tools/renderers/bash.js +9 -1
  168. package/dist/core/tools/renderers/bash.js.map +1 -1
  169. package/dist/core/tools/write.d.ts.map +1 -1
  170. package/dist/core/tools/write.js +1 -2
  171. package/dist/core/tools/write.js.map +1 -1
  172. package/dist/core/usage-totals.d.ts +1 -1
  173. package/dist/core/usage-totals.d.ts.map +1 -1
  174. package/dist/core/usage-totals.js +5 -1
  175. package/dist/core/usage-totals.js.map +1 -1
  176. package/dist/extensions/llama/client.d.ts +2 -0
  177. package/dist/extensions/llama/client.d.ts.map +1 -1
  178. package/dist/extensions/llama/client.js +7 -3
  179. package/dist/extensions/llama/client.js.map +1 -1
  180. package/dist/extensions/llama/provider.d.ts.map +1 -1
  181. package/dist/extensions/llama/provider.js +20 -5
  182. package/dist/extensions/llama/provider.js.map +1 -1
  183. package/dist/index.d.ts +3 -2
  184. package/dist/index.d.ts.map +1 -1
  185. package/dist/index.js.map +1 -1
  186. package/dist/main.d.ts.map +1 -1
  187. package/dist/main.js +13 -9
  188. package/dist/main.js.map +1 -1
  189. package/dist/migrations.d.ts.map +1 -1
  190. package/dist/migrations.js +2 -2
  191. package/dist/migrations.js.map +1 -1
  192. package/dist/modes/interactive/bug-report.d.ts +16 -0
  193. package/dist/modes/interactive/bug-report.d.ts.map +1 -0
  194. package/dist/modes/interactive/bug-report.js +206 -0
  195. package/dist/modes/interactive/bug-report.js.map +1 -0
  196. package/dist/modes/interactive/chat-viewport.d.ts.map +1 -1
  197. package/dist/modes/interactive/chat-viewport.js +1 -1
  198. package/dist/modes/interactive/chat-viewport.js.map +1 -1
  199. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  200. package/dist/modes/interactive/components/branch-summary-message.js +12 -5
  201. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  202. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  203. package/dist/modes/interactive/components/compaction-summary-message.js +12 -5
  204. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  205. package/dist/modes/interactive/components/custom-editor.d.ts +3 -3
  206. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  207. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  208. package/dist/modes/interactive/components/extension-editor.d.ts +4 -1
  209. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  210. package/dist/modes/interactive/components/extension-editor.js +7 -2
  211. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  212. package/dist/modes/interactive/components/extension-input.d.ts +2 -0
  213. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  214. package/dist/modes/interactive/components/extension-input.js +6 -0
  215. package/dist/modes/interactive/components/extension-input.js.map +1 -1
  216. package/dist/modes/interactive/components/extension-selector.d.ts +1 -0
  217. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  218. package/dist/modes/interactive/components/extension-selector.js +4 -0
  219. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  220. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  221. package/dist/modes/interactive/components/footer.js +4 -1
  222. package/dist/modes/interactive/components/footer.js.map +1 -1
  223. package/dist/modes/interactive/components/session-selector.d.ts +5 -5
  224. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  225. package/dist/modes/interactive/components/session-selector.js +74 -48
  226. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  227. package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
  228. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  229. package/dist/modes/interactive/components/settings-selector.js +11 -0
  230. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  231. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  232. package/dist/modes/interactive/components/skill-invocation-message.js +11 -4
  233. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  234. package/dist/modes/interactive/components/status-indicator.d.ts +2 -2
  235. package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -1
  236. package/dist/modes/interactive/components/status-indicator.js +7 -7
  237. package/dist/modes/interactive/components/status-indicator.js.map +1 -1
  238. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  239. package/dist/modes/interactive/components/tool-execution.js +18 -9
  240. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  241. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  242. package/dist/modes/interactive/components/tree-selector.js +2 -0
  243. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  244. package/dist/modes/interactive/interactive-mode.d.ts +15 -2
  245. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  246. package/dist/modes/interactive/interactive-mode.js +227 -84
  247. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  248. package/dist/modes/interactive/session-share.d.ts +2 -0
  249. package/dist/modes/interactive/session-share.d.ts.map +1 -1
  250. package/dist/modes/interactive/session-share.js +8 -4
  251. package/dist/modes/interactive/session-share.js.map +1 -1
  252. package/dist/modes/interactive/tui-renderer.d.ts.map +1 -1
  253. package/dist/modes/interactive/tui-renderer.js +2 -2
  254. package/dist/modes/interactive/tui-renderer.js.map +1 -1
  255. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  256. package/dist/modes/rpc/rpc-mode.js +2 -2
  257. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  258. package/dist/utils/clipboard-command.d.ts +7 -0
  259. package/dist/utils/clipboard-command.d.ts.map +1 -0
  260. package/dist/utils/clipboard-command.js +45 -0
  261. package/dist/utils/clipboard-command.js.map +1 -0
  262. package/dist/utils/clipboard-image.d.ts.map +1 -1
  263. package/dist/utils/clipboard-image.js +53 -81
  264. package/dist/utils/clipboard-image.js.map +1 -1
  265. package/dist/utils/clipboard.d.ts.map +1 -1
  266. package/dist/utils/clipboard.js +111 -121
  267. package/dist/utils/clipboard.js.map +1 -1
  268. package/dist/utils/wsl.d.ts +3 -0
  269. package/dist/utils/wsl.d.ts.map +1 -0
  270. package/dist/utils/wsl.js +15 -0
  271. package/dist/utils/wsl.js.map +1 -0
  272. package/dist/utils/zip.d.ts +7 -0
  273. package/dist/utils/zip.d.ts.map +1 -0
  274. package/dist/utils/zip.js +60 -0
  275. package/dist/utils/zip.js.map +1 -0
  276. package/docs/compaction.md +39 -13
  277. package/docs/custom-provider.md +22 -13
  278. package/docs/development.md +4 -4
  279. package/docs/docs.json +4 -0
  280. package/docs/environment-variables.md +1 -0
  281. package/docs/extensions.md +57 -43
  282. package/docs/json.md +4 -4
  283. package/docs/models.md +33 -2
  284. package/docs/providers.md +10 -2
  285. package/docs/sdk.md +4 -2
  286. package/docs/security.md +1 -1
  287. package/docs/session-format.md +82 -40
  288. package/docs/sessions.md +27 -0
  289. package/docs/settings.md +60 -1
  290. package/docs/termux.md +0 -1
  291. package/docs/tui.md +2 -2
  292. package/docs/usage.md +1 -0
  293. package/examples/extensions/README.md +0 -1
  294. package/examples/extensions/custom-provider-anthropic/index.ts +18 -12
  295. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  296. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  297. package/examples/extensions/custom-provider-gitlab-duo/index.ts +2 -2
  298. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  299. package/examples/extensions/gondolin/package-lock.json +2 -2
  300. package/examples/extensions/gondolin/package.json +1 -1
  301. package/examples/extensions/prompt-customizer.ts +19 -67
  302. package/examples/extensions/sandbox/package-lock.json +2 -2
  303. package/examples/extensions/sandbox/package.json +1 -1
  304. package/examples/extensions/with-deps/package-lock.json +2 -2
  305. package/examples/extensions/with-deps/package.json +1 -1
  306. package/npm-shrinkwrap.json +435 -762
  307. package/package.json +17 -23
  308. package/dist/bundle/chunks/anthropic-messages-VWZZOSJQ.js +0 -6
  309. package/dist/bundle/chunks/azure-openai-responses-BJLAM6CD.js +0 -2
  310. package/dist/bundle/chunks/chunk-AXIIZGTV.js +0 -2
  311. package/dist/bundle/chunks/chunk-CLNPYIDP.js +0 -42
  312. package/dist/bundle/chunks/chunk-GMWCTUPB.js +0 -2
  313. package/dist/bundle/chunks/chunk-JVUZSMYM.js +0 -1547
  314. package/dist/bundle/chunks/chunk-OUXLLA64.js +0 -11
  315. package/dist/bundle/chunks/chunk-U6ZMQKGD.js +0 -50
  316. package/dist/bundle/chunks/chunk-UAQELI3K.js +0 -2
  317. package/dist/bundle/chunks/google-generative-ai-Q2K7FBDI.js +0 -2
  318. package/dist/bundle/chunks/google-vertex-7UJBF4S7.js +0 -2
  319. package/dist/bundle/chunks/mistral-conversations-4M6BZBX6.js +0 -5
  320. package/dist/bundle/chunks/openai-codex-responses-R6VYZTVY.js +0 -10
  321. package/dist/bundle/chunks/openai-completions-EKZT2IH2.js +0 -7
  322. package/dist/bundle/chunks/openai-responses-TFDINO6W.js +0 -2
  323. package/dist/utils/clipboard-native.d.ts +0 -11
  324. package/dist/utils/clipboard-native.d.ts.map +0 -1
  325. package/dist/utils/clipboard-native.js +0 -20
  326. package/dist/utils/clipboard-native.js.map +0 -1
  327. package/examples/extensions/kimi-deferred-tools.ts +0 -61
@@ -6,6 +6,7 @@ import * as crypto from "node:crypto";
6
6
  import * as fs from "node:fs";
7
7
  import * as os from "node:os";
8
8
  import * as path from "node:path";
9
+ import { isRetryableAssistantError, } from "@earendil-works/pi-ai/compat";
9
10
  import * as TuiLayouts from "@earendil-works/pi-tui";
10
11
  import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Markdown, matchesKey, Spacer, setCapabilityOverrides, setKeybindings, Text, TruncatedText, TuiAltScreen, TuiMainScreen, visibleWidth, } from "@earendil-works/pi-tui";
11
12
  import chalk from "chalk";
@@ -14,6 +15,8 @@ import { APP_NAME, APP_TITLE, CONFIG_DIR_NAME, getAgentDir, getAuthPath, getDebu
14
15
  import { parseSkillBlock } from "../../core/agent-session.js";
15
16
  import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
16
17
  import { CACHE_TTL_MS, collectCacheMisses, computeCacheWaste, detectCacheMiss, } from "../../core/cache-stats.js";
18
+ import { formatCacheWarmingStatus, formatCacheWarmingUsage } from "../../core/cache-warmer.js";
19
+ import { recordCrash, takeUnnotifiedCrash } from "../../core/crash-log.js";
17
20
  import { DEFAULT_THINKING_LEVEL, THINKING_LEVEL_OPTIONS } from "../../core/defaults.js";
18
21
  import { FooterDataProvider } from "../../core/footer-data-provider.js";
19
22
  import { configureHttpDispatcher, formatHttpIdleTimeoutMs } from "../../core/http-dispatcher.js";
@@ -23,7 +26,7 @@ import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScop
23
26
  import { CredentialSynchronizationError } from "../../core/model-runtime.js";
24
27
  import { DefaultPackageManager } from "../../core/package-manager.js";
25
28
  import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
26
- import { SessionManager, sessionEntryToContextMessages } from "../../core/session-manager.js";
29
+ import { SessionManager, sessionEntryToContextMessages, } from "../../core/session-manager.js";
27
30
  import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
28
31
  import { isInstallTelemetryEnabled } from "../../core/telemetry.js";
29
32
  import { withBuiltInRenderers } from "../../core/tools/renderers/index.js";
@@ -39,6 +42,7 @@ import { killTrackedDetachedChildren } from "../../utils/shell.js";
39
42
  import { loadAllHighlightLanguages } from "../../utils/syntax-highlight.js";
40
43
  import { ensureTool } from "../../utils/tools-manager.js";
41
44
  import { checkForNewPiVersion } from "../../utils/version-check.js";
45
+ import { reportBug } from "./bug-report.js";
42
46
  import { createChatViewport } from "./chat-viewport.js";
43
47
  import { ArminComponent } from "./components/armin.js";
44
48
  import { AssistantMessageComponent } from "./components/assistant-message.js";
@@ -106,6 +110,9 @@ function isCustomSessionEntry(item) {
106
110
  function isCompactionCostNotice(item) {
107
111
  return "type" in item && item.type === "compaction_cost";
108
112
  }
113
+ function isUsageSessionEntry(item) {
114
+ return "type" in item && item.type === "usage";
115
+ }
109
116
  const DEAD_TERMINAL_ERROR_CODES = new Set(["EIO", "EPIPE", "ENOTCONN"]);
110
117
  function isDeadTerminalError(error) {
111
118
  if (!error || typeof error !== "object" || !("code" in error)) {
@@ -267,6 +274,8 @@ export class InteractiveMode {
267
274
  compactionQueuedMessages = [];
268
275
  // Shutdown state
269
276
  shutdownRequested = false;
277
+ /** The `/bug` hint is shown at most once per session so error output stays readable. */
278
+ bugReportHintShown = false;
270
279
  // Extension UI state
271
280
  extensionSelector = undefined;
272
281
  extensionInput = undefined;
@@ -322,6 +331,7 @@ export class InteractiveMode {
322
331
  tuiMode,
323
332
  showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
324
333
  logDirectory: getAgentDir(),
334
+ terminal: options.terminal,
325
335
  onRightClickPaste: this.onRightClickPaste,
326
336
  fullscreenCopyOnSelect: this.settingsManager.getFullscreenCopyOnSelect(),
327
337
  });
@@ -809,6 +819,11 @@ export class InteractiveMode {
809
819
  if (modelFallbackMessage) {
810
820
  this.showWarning(modelFallbackMessage);
811
821
  }
822
+ const crash = takeUnnotifiedCrash();
823
+ if (crash) {
824
+ const when = new Date(crash.timestamp).toLocaleString();
825
+ this.showWarning(`${APP_NAME} crashed on ${when} (${crash.message}). Run /bug to report it; the crash details are attached automatically.`);
826
+ }
812
827
  void this.maybeWarnAboutAnthropicSubscriptionAuth();
813
828
  // Process initial messages
814
829
  if (initialMessage) {
@@ -1525,10 +1540,45 @@ export class InteractiveMode {
1525
1540
  async handleFatalRuntimeError(prefix, error) {
1526
1541
  const message = error instanceof Error ? error.message : String(error);
1527
1542
  this.showError(`${prefix}: ${message}`);
1543
+ if (this.recordCrash("fatal_error", error)) {
1544
+ this.chatContainer.addChild(new Text(theme.fg("muted", this.crashReportInstructions()), this.outputPad, 0));
1545
+ }
1528
1546
  stopThemeWatcher();
1529
1547
  this.stop("transcript");
1530
1548
  process.exit(1);
1531
1549
  }
1550
+ /** Persist a crash so the next start can point the user at `/bug`. Returns false when nothing was written. */
1551
+ recordCrash(kind, error) {
1552
+ try {
1553
+ return (recordCrash({
1554
+ kind,
1555
+ error,
1556
+ sessionFile: this.session.sessionFile,
1557
+ cwd: this.session.sessionManager.getCwd(),
1558
+ }) !== undefined);
1559
+ }
1560
+ catch {
1561
+ return false;
1562
+ }
1563
+ }
1564
+ crashReportInstructions() {
1565
+ const resume = this.session.sessionFile ? `run \`${APP_NAME} -r\` to resume the session, then` : "start pi and";
1566
+ return `To report this crash: ${resume} run /bug. The crash details are attached automatically.`;
1567
+ }
1568
+ suggestBugReport() {
1569
+ if (this.bugReportHintShown)
1570
+ return;
1571
+ this.bugReportHintShown = true;
1572
+ 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));
1573
+ this.ui.requestRender();
1574
+ }
1575
+ maybeSuggestBugReport(message) {
1576
+ if (message.stopReason !== "error" || isRetryableAssistantError(message))
1577
+ return;
1578
+ if (/\b(?:abort(?:ed)?|cancel(?:l?ed)?)\b/i.test(message.errorMessage ?? ""))
1579
+ return;
1580
+ this.suggestBugReport();
1581
+ }
1532
1582
  renderCurrentSessionState() {
1533
1583
  this.loadedResourcesContainer.clear();
1534
1584
  this.chatContainer.clear();
@@ -1630,7 +1680,7 @@ export class InteractiveMode {
1630
1680
  this.activeWorkingIndicatorEmbedded = false;
1631
1681
  this.statusContainer.clear();
1632
1682
  this.setEditorWorkingStatusIndicator(undefined);
1633
- if (indicator instanceof WorkingStatusIndicator && this.setEditorWorkingStatusIndicator(indicator)) {
1683
+ if (this.setEditorWorkingStatusIndicator(indicator)) {
1634
1684
  this.activeWorkingIndicatorEmbedded = true;
1635
1685
  return;
1636
1686
  }
@@ -1641,7 +1691,7 @@ export class InteractiveMode {
1641
1691
  return;
1642
1692
  }
1643
1693
  const clearedIndicator = this.activeStatusIndicator;
1644
- const clearedIndicatorWasEmbedded = clearedIndicator?.kind === "working" && this.activeWorkingIndicatorEmbedded;
1694
+ const clearedIndicatorWasEmbedded = this.activeWorkingIndicatorEmbedded;
1645
1695
  clearedIndicator?.dispose();
1646
1696
  this.activeStatusIndicator = undefined;
1647
1697
  this.activeWorkingIndicatorEmbedded = false;
@@ -2130,7 +2180,7 @@ export class InteractiveMode {
2130
2180
  this.editor = this.defaultEditor;
2131
2181
  }
2132
2182
  this.editorContainer.addChild(this.editor);
2133
- if (this.activeStatusIndicator instanceof WorkingStatusIndicator) {
2183
+ if (this.activeStatusIndicator) {
2134
2184
  this.statusContainer.clear();
2135
2185
  this.activeWorkingIndicatorEmbedded = this.setEditorWorkingStatusIndicator(this.activeStatusIndicator);
2136
2186
  if (!this.activeWorkingIndicatorEmbedded) {
@@ -2401,6 +2451,12 @@ export class InteractiveMode {
2401
2451
  this.editor.setText("");
2402
2452
  return;
2403
2453
  }
2454
+ if (text === "/bug" || text.startsWith("/bug ")) {
2455
+ const hint = text.slice("/bug".length).trim();
2456
+ this.editor.setText("");
2457
+ await this.handleBugCommand(hint ? hint : undefined);
2458
+ return;
2459
+ }
2404
2460
  if (text === "/copy") {
2405
2461
  await this.handleCopyCommand();
2406
2462
  this.editor.setText("");
@@ -2592,6 +2648,10 @@ export class InteractiveMode {
2592
2648
  this.addCustomEntryToChat(event.entry);
2593
2649
  this.ui.requestRender();
2594
2650
  }
2651
+ else if (event.entry.type === "usage" && event.entry.kind === "cache_warm") {
2652
+ this.addCacheWarmingUsage(event.entry);
2653
+ this.ui.requestRender();
2654
+ }
2595
2655
  break;
2596
2656
  case "session_info_changed":
2597
2657
  this.updateTerminalTitle();
@@ -2672,13 +2732,14 @@ export class InteractiveMode {
2672
2732
  });
2673
2733
  }
2674
2734
  this.pendingTools.clear();
2735
+ this.maybeSuggestBugReport(this.streamingMessage);
2675
2736
  }
2676
2737
  else {
2677
2738
  // Args are now complete - trigger diff computation for edit tools
2678
2739
  for (const [, component] of this.pendingTools.entries()) {
2679
2740
  component.setArgsComplete();
2680
2741
  }
2681
- this.maybeShowAssistantDiagnostics(this.streamingMessage);
2742
+ this.maybeShowThinkingDropNotice(this.streamingMessage);
2682
2743
  this.maybeShowCacheMissNotice(this.streamingMessage);
2683
2744
  }
2684
2745
  this.streamingComponent = undefined;
@@ -2945,6 +3006,8 @@ export class InteractiveMode {
2945
3006
  this.chatContainer.addChild(component);
2946
3007
  break;
2947
3008
  }
3009
+ case "system":
3010
+ break;
2948
3011
  case "user": {
2949
3012
  const textContent = this.getUserMessageText(message);
2950
3013
  if (textContent) {
@@ -2991,8 +3054,8 @@ export class InteractiveMode {
2991
3054
  renderSessionItems(items, options = {}) {
2992
3055
  this.pendingTools.clear();
2993
3056
  const renderedPendingTools = new Map();
2994
- // Cache-miss notices are not persisted; re-derive them from the full entry
2995
- // list and re-inject them after the assistant messages that paid for them.
3057
+ // Cache misses are not persisted, unlike successful cache-warming usage.
3058
+ // Re-derive them and inject them after the assistant messages that paid for them.
2996
3059
  const cacheMisses = this.settingsManager.getShowCacheMissNotices()
2997
3060
  ? collectCacheMisses(this.sessionManager.getEntries(), this.session.modelRuntime)
2998
3061
  : new Map();
@@ -3005,6 +3068,10 @@ export class InteractiveMode {
3005
3068
  this.addCustomEntryToChat(item);
3006
3069
  continue;
3007
3070
  }
3071
+ if (isUsageSessionEntry(item)) {
3072
+ this.addCacheWarmingUsage(item);
3073
+ continue;
3074
+ }
3008
3075
  if (isCompactionCostNotice(item)) {
3009
3076
  this.addCompactionCostNotice(item);
3010
3077
  continue;
@@ -3042,7 +3109,6 @@ export class InteractiveMode {
3042
3109
  }
3043
3110
  }
3044
3111
  if (message.stopReason !== "aborted" && message.stopReason !== "error") {
3045
- this.maybeShowAssistantDiagnostics(message);
3046
3112
  const miss = cacheMisses.get(message);
3047
3113
  if (miss)
3048
3114
  this.addCacheMissNotice(miss);
@@ -3074,7 +3140,7 @@ export class InteractiveMode {
3074
3140
  */
3075
3141
  renderSessionEntries(entries, options = {}) {
3076
3142
  const items = entries.flatMap((entry) => {
3077
- if (entry.type === "custom") {
3143
+ if (entry.type === "custom" || (entry.type === "usage" && entry.kind === "cache_warm")) {
3078
3144
  return [entry];
3079
3145
  }
3080
3146
  const messages = sessionEntryToContextMessages(entry);
@@ -3085,6 +3151,12 @@ export class InteractiveMode {
3085
3151
  });
3086
3152
  this.renderSessionItems(items, options);
3087
3153
  }
3154
+ addCacheWarmingUsage(entry) {
3155
+ if (!this.settingsManager.getShowCacheMissNotices())
3156
+ return;
3157
+ this.chatContainer.addChild(new Spacer(1));
3158
+ this.chatContainer.addChild(new Text(theme.fg("dim", formatCacheWarmingUsage(entry)), 1, 0));
3159
+ }
3088
3160
  /**
3089
3161
  * Render billing usage for a compaction or branch summary. The notice is derived
3090
3162
  * from persisted summary usage and is not stored as a separate session entry.
@@ -3099,31 +3171,42 @@ export class InteractiveMode {
3099
3171
  this.chatContainer.addChild(new Spacer(1));
3100
3172
  this.chatContainer.addChild(new Text(theme.fg("warning", `${label}: ${formatTokens(tokens)} tokens billed${cost}`), 1, 0));
3101
3173
  }
3102
- maybeShowAssistantDiagnostics(message) {
3103
- if (!this.settingsManager.getShowCacheMissNotices())
3104
- return;
3174
+ static countDroppedThinkingBlocks(message) {
3175
+ let count = 0;
3105
3176
  for (const diagnostic of message.diagnostics ?? []) {
3106
3177
  if (diagnostic.type !== "anthropic_input_transformations")
3107
3178
  continue;
3108
3179
  const transformations = diagnostic.details?.transformations;
3109
3180
  if (!Array.isArray(transformations))
3110
3181
  continue;
3111
- const dropped = transformations.flatMap((transformation) => {
3112
- if (typeof transformation !== "object" || transformation === null)
3113
- return [];
3114
- const details = transformation;
3115
- if (details.type !== "thinking_dropped")
3116
- return [];
3117
- const reason = typeof details.reason === "string" ? details.reason : "unknown reason";
3118
- const location = typeof details.path === "string" ? ` at ${details.path}` : "";
3119
- return [`${reason}${location}`];
3120
- });
3121
- if (dropped.length === 0)
3122
- continue;
3123
- const noun = dropped.length === 1 ? "thinking block" : `${dropped.length} thinking blocks`;
3124
- this.chatContainer.addChild(new Spacer(1));
3125
- this.chatContainer.addChild(new Text(theme.fg("warning", `Anthropic dropped ${noun}: ${dropped.join("; ")}`), 1, 0));
3182
+ count += transformations.filter((transformation) => typeof transformation === "object" &&
3183
+ transformation !== null &&
3184
+ transformation.type === "thinking_dropped").length;
3126
3185
  }
3186
+ return count;
3187
+ }
3188
+ maybeShowThinkingDropNotice(message) {
3189
+ if (!this.settingsManager.getShowCacheMissNotices())
3190
+ return;
3191
+ const droppedCount = InteractiveMode.countDroppedThinkingBlocks(message);
3192
+ if (droppedCount === 0)
3193
+ return;
3194
+ let previousDroppedCount = 0;
3195
+ // message_end reaches the UI before the current message is persisted,
3196
+ // so the branch's last assistant message is the previous response.
3197
+ const branch = this.sessionManager.getBranch();
3198
+ for (let i = branch.length - 1; i >= 0; i--) {
3199
+ const entry = branch[i];
3200
+ if (entry.type === "message" && entry.message.role === "assistant") {
3201
+ previousDroppedCount = InteractiveMode.countDroppedThinkingBlocks(entry.message);
3202
+ break;
3203
+ }
3204
+ }
3205
+ if (droppedCount <= previousDroppedCount)
3206
+ return;
3207
+ const noun = droppedCount === 1 ? "thinking block" : "thinking blocks";
3208
+ this.chatContainer.addChild(new Spacer(1));
3209
+ this.chatContainer.addChild(new Text(theme.fg("warning", `Anthropic dropped ${droppedCount} ${noun} (details in session)`), 1, 0));
3127
3210
  }
3128
3211
  /**
3129
3212
  * Show a transcript notice when a completed assistant message paid for a
@@ -3291,6 +3374,9 @@ export class InteractiveMode {
3291
3374
  catch { }
3292
3375
  console.error(`${APP_NAME} exiting due to uncaughtException:`);
3293
3376
  console.error(error);
3377
+ if (this.recordCrash("uncaught_exception", error)) {
3378
+ console.error(`\n${this.crashReportInstructions()}`);
3379
+ }
3294
3380
  process.exit(1);
3295
3381
  }
3296
3382
  /**
@@ -3422,9 +3508,7 @@ export class InteractiveMode {
3422
3508
  const level = this.session.thinkingLevel || "off";
3423
3509
  this.editor.borderColor = theme.getThinkingBorderColor(level);
3424
3510
  }
3425
- if (this.activeStatusIndicator?.kind === "working") {
3426
- this.activeStatusIndicator.invalidate();
3427
- }
3511
+ this.activeStatusIndicator?.invalidate();
3428
3512
  this.ui.requestRender();
3429
3513
  }
3430
3514
  cycleThinkingLevel() {
@@ -3618,7 +3702,7 @@ export class InteractiveMode {
3618
3702
  if (allQueued.length === 0) {
3619
3703
  this.updatePendingMessagesDisplay();
3620
3704
  if (options?.abort) {
3621
- this.agent.abort();
3705
+ void this.session.abort();
3622
3706
  }
3623
3707
  return 0;
3624
3708
  }
@@ -3628,7 +3712,7 @@ export class InteractiveMode {
3628
3712
  this.editor.setText(combinedText);
3629
3713
  this.updatePendingMessagesDisplay();
3630
3714
  if (options?.abort) {
3631
- this.agent.abort();
3715
+ void this.session.abort();
3632
3716
  }
3633
3717
  return allQueued.length;
3634
3718
  }
@@ -3782,6 +3866,7 @@ export class InteractiveMode {
3782
3866
  followUpMode: this.session.followUpMode,
3783
3867
  transport: this.settingsManager.getTransport(),
3784
3868
  httpIdleTimeoutMs: this.settingsManager.getHttpIdleTimeoutMs(),
3869
+ cacheWarmingMode: this.settingsManager.getCacheWarmingMode(),
3785
3870
  thinkingLevel: this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL,
3786
3871
  availableThinkingLevels: [...THINKING_LEVEL_OPTIONS],
3787
3872
  modelThinkingLevels: this.settingsManager.getAllModelThinkingLevels(),
@@ -3854,6 +3939,10 @@ export class InteractiveMode {
3854
3939
  configureHttpDispatcher(timeoutMs);
3855
3940
  this.showStatus(`HTTP idle timeout: ${formatHttpIdleTimeoutMs(timeoutMs)}`);
3856
3941
  },
3942
+ onCacheWarmingModeChange: (mode) => {
3943
+ this.session.setCacheWarmingMode(mode);
3944
+ this.showStatus(`Cache warming: ${mode}`);
3945
+ },
3857
3946
  onModelThinkingLevelChange: (provider, modelId, level) => {
3858
3947
  this.settingsManager.setModelThinkingLevel(provider, modelId, level);
3859
3948
  // If the override is for the current model, apply it to the session too
@@ -4405,6 +4494,11 @@ export class InteractiveMode {
4405
4494
  this.restoreQueuedMessagesToEditor();
4406
4495
  await this.session.abort();
4407
4496
  }
4497
+ // Recheck after the dialogs and streaming abort, before replacing another operation's UI.
4498
+ if (this.session.isCompacting) {
4499
+ this.showError("Wait for the current compaction or tree navigation to finish before navigating the session tree.");
4500
+ return;
4501
+ }
4408
4502
  // Set up escape handler and status indicator if summarizing
4409
4503
  let showingSummaryIndicator = false;
4410
4504
  const originalOnEscape = this.defaultEditor.onEscape;
@@ -4475,9 +4569,9 @@ export class InteractiveMode {
4475
4569
  }
4476
4570
  showSessionSelector() {
4477
4571
  this.showSelector((done) => {
4478
- const selector = new SessionSelectorComponent((onProgress) => SessionManager.list(this.sessionManager.getCwd(), this.sessionManager.getSessionDir(), onProgress), (onProgress) => this.sessionManager.usesDefaultSessionDir()
4479
- ? SessionManager.listAll(onProgress)
4480
- : SessionManager.listAll(this.sessionManager.getSessionDir(), onProgress), async (sessionPath) => {
4572
+ const selector = new SessionSelectorComponent((onProgress, signal) => SessionManager.list(this.sessionManager.getCwd(), this.sessionManager.getSessionDir(), onProgress, signal), (onProgress, signal) => this.sessionManager.usesDefaultSessionDir()
4573
+ ? SessionManager.listAll(onProgress, signal)
4574
+ : SessionManager.listAll(this.sessionManager.getSessionDir(), onProgress, signal), async (sessionPath) => {
4481
4575
  done();
4482
4576
  await this.handleResumeSession(sessionPath);
4483
4577
  }, () => {
@@ -4740,67 +4834,89 @@ export class InteractiveMode {
4740
4834
  }
4741
4835
  async completeProviderAuthentication(providerId, providerName, authType, previousModel) {
4742
4836
  const actionLabel = authType === "oauth" ? `Logged in to ${providerName}` : `Saved API key for ${providerName}`;
4743
- let selectedModel;
4744
- let selectionError;
4745
- if (isUnknownModel(previousModel)) {
4746
- const availableModels = this.session.modelRuntime.getAvailableSnapshot();
4747
- const providerModels = availableModels.filter((model) => model.provider === providerId);
4748
- // Matches LLAMA_PROVIDER_ID from extensions/llama/provider.ts; kept inline to avoid coupling interactive mode to the built-in extension.
4749
- if (providerId === "llama.cpp") {
4750
- selectionError = llamaCppPostLoginGuidance(actionLabel, providerModels.length);
4751
- }
4752
- else if (!hasDefaultModelProvider(providerId)) {
4753
- selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
4754
- }
4755
- else if (providerModels.length === 0) {
4756
- selectionError = `${actionLabel}, but no models are available for that provider. Use /model to select a model.`;
4757
- }
4758
- else {
4759
- const defaultModelId = defaultModelPerProvider[providerId];
4760
- selectedModel = providerModels.find((model) => model.id === defaultModelId);
4761
- if (!selectedModel) {
4762
- selectionError = `${actionLabel}, but its default model "${defaultModelId}" is not available. Use /model to select a model.`;
4837
+ const session = this.session;
4838
+ // Dynamic catalogs may be empty until the first authenticated network refresh.
4839
+ const deferSelection = isUnknownModel(previousModel) &&
4840
+ hasDefaultModelProvider(providerId) &&
4841
+ !session.modelRuntime
4842
+ .getAvailableSnapshot()
4843
+ .some((model) => model.provider === providerId && model.id === defaultModelPerProvider[providerId]);
4844
+ const finishAuthentication = async () => {
4845
+ let selectedModel;
4846
+ let selectionError;
4847
+ if (isUnknownModel(previousModel)) {
4848
+ const availableModels = this.session.modelRuntime.getAvailableSnapshot();
4849
+ const providerModels = availableModels.filter((model) => model.provider === providerId);
4850
+ // Matches LLAMA_PROVIDER_ID from extensions/llama/provider.ts; kept inline to avoid coupling interactive mode to the built-in extension.
4851
+ if (providerId === "llama.cpp") {
4852
+ selectionError = llamaCppPostLoginGuidance(actionLabel, providerModels.length);
4853
+ }
4854
+ else if (!hasDefaultModelProvider(providerId)) {
4855
+ selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
4856
+ }
4857
+ else if (providerModels.length === 0) {
4858
+ selectionError = `${actionLabel}, but no models are available for that provider. Use /model to select a model.`;
4763
4859
  }
4764
4860
  else {
4765
- try {
4766
- await this.session.setModel(selectedModel, { persist: true });
4861
+ const defaultModelId = defaultModelPerProvider[providerId];
4862
+ // Radius catalogs vary by account; prefer balanced, then use catalog order.
4863
+ selectedModel =
4864
+ providerModels.find((model) => model.id === defaultModelId) ??
4865
+ (providerId === "radius" ? providerModels[0] : undefined);
4866
+ if (!selectedModel) {
4867
+ selectionError = `${actionLabel}, but its default model "${defaultModelId}" is not available. Use /model to select a model.`;
4767
4868
  }
4768
- catch (error) {
4769
- selectedModel = undefined;
4770
- const errorMessage = error instanceof Error ? error.message : String(error);
4771
- selectionError = `${actionLabel}, but selecting its default model failed: ${errorMessage}. Use /model to select a model.`;
4869
+ else {
4870
+ try {
4871
+ await this.session.setModel(selectedModel, { persist: true });
4872
+ }
4873
+ catch (error) {
4874
+ selectedModel = undefined;
4875
+ const errorMessage = error instanceof Error ? error.message : String(error);
4876
+ selectionError = `${actionLabel}, but selecting its default model failed: ${errorMessage}. Use /model to select a model.`;
4877
+ }
4772
4878
  }
4773
4879
  }
4774
4880
  }
4775
- }
4776
- await this.updateAvailableProviderCount();
4777
- this.footer.invalidate();
4778
- this.updateEditorBorderColor();
4779
- if (selectedModel) {
4780
- this.showStatus(`${actionLabel}. Selected ${selectedModel.id}. Credentials saved to ${getAuthPath()}`);
4781
- void this.maybeWarnAboutAnthropicSubscriptionAuth(selectedModel);
4782
- this.checkDaxnutsEasterEgg(selectedModel);
4783
- }
4784
- else {
4785
- this.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}`);
4786
- if (selectionError) {
4787
- this.showError(selectionError);
4881
+ await this.updateAvailableProviderCount();
4882
+ this.footer.invalidate();
4883
+ this.updateEditorBorderColor();
4884
+ if (selectedModel) {
4885
+ this.showStatus(`${actionLabel}. Selected ${selectedModel.id}. Credentials saved to ${getAuthPath()}`);
4886
+ void this.maybeWarnAboutAnthropicSubscriptionAuth(selectedModel);
4887
+ this.checkDaxnutsEasterEgg(selectedModel);
4788
4888
  }
4789
4889
  else {
4790
- void this.maybeWarnAboutAnthropicSubscriptionAuth();
4890
+ this.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}`);
4891
+ if (selectionError) {
4892
+ this.showError(selectionError);
4893
+ }
4894
+ else {
4895
+ void this.maybeWarnAboutAnthropicSubscriptionAuth();
4896
+ }
4791
4897
  }
4898
+ };
4899
+ if (deferSelection) {
4900
+ this.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}. Refreshing model catalog…`);
4901
+ }
4902
+ else {
4903
+ await finishAuthentication();
4792
4904
  }
4793
4905
  const controller = new AbortController();
4794
4906
  const timeout = setTimeout(() => controller.abort(), 15_000);
4795
- void this.session.modelRuntime
4907
+ void session.modelRuntime
4796
4908
  .refresh({ providers: [providerId], signal: controller.signal })
4797
- .then((result) => {
4909
+ .then(async (result) => {
4798
4910
  if (result.aborted) {
4799
4911
  this.showWarning(`${actionLabel}, but its model catalog refresh timed out; using cached models.`);
4800
4912
  }
4801
4913
  else if (result.errors.size > 0) {
4802
4914
  this.showWarning(`${actionLabel}, but its model catalog could not be refreshed; using cached models.`);
4803
4915
  }
4916
+ // Do not replace a model or session selected while the refresh was running.
4917
+ if (deferSelection && this.session === session && session.model === previousModel) {
4918
+ await finishAuthentication();
4919
+ }
4804
4920
  this.updateAvailableProviderCount();
4805
4921
  this.footer.invalidate();
4806
4922
  this.ui.requestRender();
@@ -5142,6 +5258,17 @@ export class InteractiveMode {
5142
5258
  showError: (message) => this.showError(message),
5143
5259
  });
5144
5260
  }
5261
+ async handleBugCommand(hint) {
5262
+ await reportBug({
5263
+ session: this.session,
5264
+ ui: this.ui,
5265
+ editorContainer: this.editorContainer,
5266
+ editor: this.editor,
5267
+ keybindings: this.keybindings,
5268
+ showStatus: (message) => this.showStatus(message),
5269
+ showError: (message) => this.showError(message),
5270
+ }, hint);
5271
+ }
5145
5272
  async handleCopyCommand(options = {}) {
5146
5273
  if (options.preferSelection &&
5147
5274
  this.ui instanceof TuiAltScreen &&
@@ -5227,6 +5354,15 @@ export class InteractiveMode {
5227
5354
  }
5228
5355
  info += `${theme.fg("dim", "Output:")} ${stats.tokens.output.toLocaleString()}\n`;
5229
5356
  info += `${theme.fg("dim", "Total:")} ${stats.tokens.total.toLocaleString()}\n`;
5357
+ const cacheWarmingStatus = this.session.cacheWarmingStatus;
5358
+ info += `\n${theme.bold("Cache Warming")}\n`;
5359
+ info += `${theme.fg("dim", "Mode:")} ${this.settingsManager.getCacheWarmingMode()}\n`;
5360
+ info += `${theme.fg("dim", "Status:")} ${cacheWarmingStatus ? formatCacheWarmingStatus(cacheWarmingStatus) : "Inactive (cache warming unavailable)"}\n`;
5361
+ const decision = cacheWarmingStatus?.decision;
5362
+ if (decision?.economicsAvailable) {
5363
+ info += `${theme.fg("dim", "Cache miss penalty:")} $${decision.missCost.toFixed(3)}\n`;
5364
+ info += `${theme.fg("dim", "Refresh cost:")} $${decision.warmCost.toFixed(3)}\n`;
5365
+ }
5230
5366
  if (stats.cost > 0 || cacheWaste.missedTokens > 0) {
5231
5367
  info += `\n${theme.bold("Cost")}\n`;
5232
5368
  info += `${theme.fg("dim", "Total:")} $${stats.cost.toFixed(3)}`;
@@ -5357,7 +5493,7 @@ export class InteractiveMode {
5357
5493
  | \`${expandTools}\` | Toggle tool output expansion |
5358
5494
  | \`${toggleThinking}\` | Toggle thinking block visibility |
5359
5495
  | \`${externalEditor}\` | Edit message in external editor |
5360
- | \`${copyMessage}\` | Copy last assistant message |
5496
+ | \`${copyMessage}\` | Copy selection or last assistant message |
5361
5497
  | \`${followUp}\` | Queue follow-up message |
5362
5498
  | \`${dequeue}\` | Restore queued messages |
5363
5499
  | \`${pasteImage}\` | Paste image or text from clipboard |
@@ -5453,12 +5589,19 @@ export class InteractiveMode {
5453
5589
  async handleBashCommand(command, excludeFromContext = false) {
5454
5590
  const extensionRunner = this.session.extensionRunner;
5455
5591
  // Emit user_bash event to let extensions intercept
5456
- const eventResult = await extensionRunner.emitUserBash({
5457
- type: "user_bash",
5458
- command,
5459
- excludeFromContext,
5460
- cwd: this.sessionManager.getCwd(),
5461
- });
5592
+ let eventResult;
5593
+ try {
5594
+ eventResult = await extensionRunner.emitUserBash({
5595
+ type: "user_bash",
5596
+ command,
5597
+ excludeFromContext,
5598
+ cwd: this.sessionManager.getCwd(),
5599
+ });
5600
+ }
5601
+ catch {
5602
+ // The extension runner already reported the error. Do not fall back to local execution.
5603
+ return;
5604
+ }
5462
5605
  // If extension returned a full result, use it directly
5463
5606
  if (eventResult?.result) {
5464
5607
  const result = eventResult.result;