@averyyy/pi-coding-agent 0.85.1-piclient.9 → 0.86.1-piclient.2

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 (324) hide show
  1. package/CHANGELOG.md +109 -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-QJ7SPP5M.js +2 -0
  5. package/dist/bundle/chunks/bedrock-converse-stream.js +47 -38
  6. package/dist/bundle/chunks/chunk-7L6OAVKC.js +1663 -0
  7. package/dist/bundle/chunks/chunk-A3JYRWB6.js +2 -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-IDDQWTHI.js → chunk-RCZIEVGO.js} +10 -1
  11. package/dist/bundle/chunks/chunk-S7SZN6Z3.js +2 -0
  12. package/dist/bundle/chunks/chunk-UZNGGY6N.js +11 -0
  13. package/dist/bundle/chunks/chunk-XREANBOX.js +2 -0
  14. package/dist/bundle/chunks/devin-runtime.js +7 -2
  15. package/dist/bundle/chunks/github-copilot.js +1 -1
  16. package/dist/bundle/chunks/google-generative-ai-IMS5EK2Y.js +2 -0
  17. package/dist/bundle/chunks/google-vertex-O4YJEQUW.js +2 -0
  18. package/dist/bundle/chunks/jiti-loader-C3NU2SDH.js +21 -0
  19. package/dist/bundle/chunks/jiti-static-loader-E6ANQD5T.js +2 -0
  20. package/dist/bundle/chunks/meta.js +2 -0
  21. package/dist/bundle/chunks/mistral-conversations-NOSQHBEY.js +5 -0
  22. package/dist/bundle/chunks/{node-KG362ECQ.js → node-O3RIBJRH.js} +1 -1
  23. package/dist/bundle/chunks/openai-codex-responses-SG7PGUUZ.js +10 -0
  24. package/dist/bundle/chunks/openai-completions-CYGM3XXP.js +7 -0
  25. package/dist/bundle/chunks/openai-responses-AH7QTG23.js +2 -0
  26. package/dist/bundle/chunks/{pi-messages-PML4SPVJ.js → pi-messages-GFKBWJHZ.js} +1 -1
  27. package/dist/bundle/chunks/virtual-modules-SC5AFIK5.js +2 -0
  28. package/dist/bundle/cli-runtime.js +3 -0
  29. package/dist/bundle/cli.js +4 -2
  30. package/dist/bundle/index.js +1 -1
  31. package/dist/bundle/rpc-entry.js +1 -1
  32. package/dist/cli/args.d.ts.map +1 -1
  33. package/dist/cli/args.js +1 -0
  34. package/dist/cli/args.js.map +1 -1
  35. package/dist/cli/session-picker.d.ts +1 -1
  36. package/dist/cli/session-picker.d.ts.map +1 -1
  37. package/dist/cli/session-picker.js.map +1 -1
  38. package/dist/config.d.ts.map +1 -1
  39. package/dist/config.js +1 -1
  40. package/dist/config.js.map +1 -1
  41. package/dist/core/agent-session.d.ts +56 -6
  42. package/dist/core/agent-session.d.ts.map +1 -1
  43. package/dist/core/agent-session.js +362 -183
  44. package/dist/core/agent-session.js.map +1 -1
  45. package/dist/core/bug-report-upload.d.ts +12 -0
  46. package/dist/core/bug-report-upload.d.ts.map +1 -0
  47. package/dist/core/bug-report-upload.js +22 -0
  48. package/dist/core/bug-report-upload.js.map +1 -0
  49. package/dist/core/bug-report.d.ts +176 -0
  50. package/dist/core/bug-report.d.ts.map +1 -0
  51. package/dist/core/bug-report.js +292 -0
  52. package/dist/core/bug-report.js.map +1 -0
  53. package/dist/core/cache-stats.d.ts.map +1 -1
  54. package/dist/core/cache-stats.js +12 -1
  55. package/dist/core/cache-stats.js.map +1 -1
  56. package/dist/core/cache-warmer.d.ts +102 -0
  57. package/dist/core/cache-warmer.d.ts.map +1 -0
  58. package/dist/core/cache-warmer.js +342 -0
  59. package/dist/core/cache-warmer.js.map +1 -0
  60. package/dist/core/compaction/compaction.d.ts +2 -2
  61. package/dist/core/compaction/compaction.d.ts.map +1 -1
  62. package/dist/core/compaction/compaction.js +23 -18
  63. package/dist/core/compaction/compaction.js.map +1 -1
  64. package/dist/core/crash-log.d.ts +22 -0
  65. package/dist/core/crash-log.d.ts.map +1 -0
  66. package/dist/core/crash-log.js +71 -0
  67. package/dist/core/crash-log.js.map +1 -0
  68. package/dist/core/experimental.d.ts +0 -4
  69. package/dist/core/experimental.d.ts.map +1 -1
  70. package/dist/core/experimental.js +0 -4
  71. package/dist/core/experimental.js.map +1 -1
  72. package/dist/core/extensions/index.d.ts +1 -1
  73. package/dist/core/extensions/index.d.ts.map +1 -1
  74. package/dist/core/extensions/index.js.map +1 -1
  75. package/dist/core/extensions/jiti-loader.d.ts +2 -0
  76. package/dist/core/extensions/jiti-loader.d.ts.map +1 -0
  77. package/dist/core/extensions/jiti-loader.js +4 -0
  78. package/dist/core/extensions/jiti-loader.js.map +1 -0
  79. package/dist/core/extensions/jiti-static-loader.d.ts +2 -0
  80. package/dist/core/extensions/jiti-static-loader.d.ts.map +1 -0
  81. package/dist/core/extensions/jiti-static-loader.js +4 -0
  82. package/dist/core/extensions/jiti-static-loader.js.map +1 -0
  83. package/dist/core/extensions/loader.d.ts.map +1 -1
  84. package/dist/core/extensions/loader.js +38 -51
  85. package/dist/core/extensions/loader.js.map +1 -1
  86. package/dist/core/extensions/runner.d.ts +10 -7
  87. package/dist/core/extensions/runner.d.ts.map +1 -1
  88. package/dist/core/extensions/runner.js +85 -67
  89. package/dist/core/extensions/runner.js.map +1 -1
  90. package/dist/core/extensions/types.d.ts +60 -50
  91. package/dist/core/extensions/types.d.ts.map +1 -1
  92. package/dist/core/extensions/types.js.map +1 -1
  93. package/dist/core/extensions/virtual-modules.d.ts +3 -0
  94. package/dist/core/extensions/virtual-modules.d.ts.map +1 -0
  95. package/dist/core/extensions/virtual-modules.js +38 -0
  96. package/dist/core/extensions/virtual-modules.js.map +1 -0
  97. package/dist/core/extensions/wrapper.d.ts.map +1 -1
  98. package/dist/core/extensions/wrapper.js +1 -20
  99. package/dist/core/extensions/wrapper.js.map +1 -1
  100. package/dist/core/index.d.ts +2 -1
  101. package/dist/core/index.d.ts.map +1 -1
  102. package/dist/core/index.js.map +1 -1
  103. package/dist/core/keybindings.d.ts +1 -1
  104. package/dist/core/keybindings.d.ts.map +1 -1
  105. package/dist/core/keybindings.js +1 -1
  106. package/dist/core/keybindings.js.map +1 -1
  107. package/dist/core/messages.d.ts.map +1 -1
  108. package/dist/core/messages.js +1 -0
  109. package/dist/core/messages.js.map +1 -1
  110. package/dist/core/model-config.d.ts +101 -20
  111. package/dist/core/model-config.d.ts.map +1 -1
  112. package/dist/core/model-config.js +25 -18
  113. package/dist/core/model-config.js.map +1 -1
  114. package/dist/core/model-registry.d.ts +5 -1
  115. package/dist/core/model-registry.d.ts.map +1 -1
  116. package/dist/core/model-registry.js +8 -0
  117. package/dist/core/model-registry.js.map +1 -1
  118. package/dist/core/model-resolver.d.ts.map +1 -1
  119. package/dist/core/model-resolver.js +2 -1
  120. package/dist/core/model-resolver.js.map +1 -1
  121. package/dist/core/model-runtime.d.ts.map +1 -1
  122. package/dist/core/model-runtime.js +5 -3
  123. package/dist/core/model-runtime.js.map +1 -1
  124. package/dist/core/provider-composer.d.ts +3 -2
  125. package/dist/core/provider-composer.d.ts.map +1 -1
  126. package/dist/core/provider-composer.js +2 -0
  127. package/dist/core/provider-composer.js.map +1 -1
  128. package/dist/core/radius.d.ts +3 -0
  129. package/dist/core/radius.d.ts.map +1 -1
  130. package/dist/core/radius.js +6 -0
  131. package/dist/core/radius.js.map +1 -1
  132. package/dist/core/sdk.d.ts.map +1 -1
  133. package/dist/core/sdk.js +61 -41
  134. package/dist/core/sdk.js.map +1 -1
  135. package/dist/core/session-export.d.ts +6 -2
  136. package/dist/core/session-export.d.ts.map +1 -1
  137. package/dist/core/session-export.js +14 -13
  138. package/dist/core/session-export.js.map +1 -1
  139. package/dist/core/session-manager.d.ts +29 -6
  140. package/dist/core/session-manager.d.ts.map +1 -1
  141. package/dist/core/session-manager.js +159 -96
  142. package/dist/core/session-manager.js.map +1 -1
  143. package/dist/core/settings-manager.d.ts +20 -4
  144. package/dist/core/settings-manager.d.ts.map +1 -1
  145. package/dist/core/settings-manager.js +43 -7
  146. package/dist/core/settings-manager.js.map +1 -1
  147. package/dist/core/slash-commands.d.ts.map +1 -1
  148. package/dist/core/slash-commands.js +1 -0
  149. package/dist/core/slash-commands.js.map +1 -1
  150. package/dist/core/system-prompt.d.ts +49 -6
  151. package/dist/core/system-prompt.d.ts.map +1 -1
  152. package/dist/core/system-prompt.js +121 -92
  153. package/dist/core/system-prompt.js.map +1 -1
  154. package/dist/core/tools/bash.d.ts +2 -1
  155. package/dist/core/tools/bash.d.ts.map +1 -1
  156. package/dist/core/tools/bash.js +19 -5
  157. package/dist/core/tools/bash.js.map +1 -1
  158. package/dist/core/tools/edit.d.ts.map +1 -1
  159. package/dist/core/tools/edit.js +1 -2
  160. package/dist/core/tools/edit.js.map +1 -1
  161. package/dist/core/tools/read.d.ts.map +1 -1
  162. package/dist/core/tools/read.js +1 -2
  163. package/dist/core/tools/read.js.map +1 -1
  164. package/dist/core/tools/renderers/bash.d.ts.map +1 -1
  165. package/dist/core/tools/renderers/bash.js +9 -1
  166. package/dist/core/tools/renderers/bash.js.map +1 -1
  167. package/dist/core/tools/write.d.ts.map +1 -1
  168. package/dist/core/tools/write.js +1 -2
  169. package/dist/core/tools/write.js.map +1 -1
  170. package/dist/core/usage-totals.d.ts +1 -1
  171. package/dist/core/usage-totals.d.ts.map +1 -1
  172. package/dist/core/usage-totals.js +5 -1
  173. package/dist/core/usage-totals.js.map +1 -1
  174. package/dist/extensions/llama/client.d.ts +2 -0
  175. package/dist/extensions/llama/client.d.ts.map +1 -1
  176. package/dist/extensions/llama/client.js +7 -3
  177. package/dist/extensions/llama/client.js.map +1 -1
  178. package/dist/extensions/llama/provider.d.ts.map +1 -1
  179. package/dist/extensions/llama/provider.js +20 -5
  180. package/dist/extensions/llama/provider.js.map +1 -1
  181. package/dist/index.d.ts +3 -2
  182. package/dist/index.d.ts.map +1 -1
  183. package/dist/index.js.map +1 -1
  184. package/dist/main.d.ts.map +1 -1
  185. package/dist/main.js +13 -9
  186. package/dist/main.js.map +1 -1
  187. package/dist/migrations.d.ts.map +1 -1
  188. package/dist/migrations.js +2 -2
  189. package/dist/migrations.js.map +1 -1
  190. package/dist/modes/interactive/bug-report.d.ts +16 -0
  191. package/dist/modes/interactive/bug-report.d.ts.map +1 -0
  192. package/dist/modes/interactive/bug-report.js +206 -0
  193. package/dist/modes/interactive/bug-report.js.map +1 -0
  194. package/dist/modes/interactive/chat-viewport.d.ts.map +1 -1
  195. package/dist/modes/interactive/chat-viewport.js +1 -1
  196. package/dist/modes/interactive/chat-viewport.js.map +1 -1
  197. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  198. package/dist/modes/interactive/components/branch-summary-message.js +12 -5
  199. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  200. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  201. package/dist/modes/interactive/components/compaction-summary-message.js +12 -5
  202. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  203. package/dist/modes/interactive/components/custom-editor.d.ts +3 -3
  204. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  205. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  206. package/dist/modes/interactive/components/extension-editor.d.ts +4 -1
  207. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  208. package/dist/modes/interactive/components/extension-editor.js +7 -2
  209. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  210. package/dist/modes/interactive/components/extension-input.d.ts +2 -0
  211. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  212. package/dist/modes/interactive/components/extension-input.js +6 -0
  213. package/dist/modes/interactive/components/extension-input.js.map +1 -1
  214. package/dist/modes/interactive/components/extension-selector.d.ts +1 -0
  215. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  216. package/dist/modes/interactive/components/extension-selector.js +4 -0
  217. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  218. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  219. package/dist/modes/interactive/components/footer.js +4 -1
  220. package/dist/modes/interactive/components/footer.js.map +1 -1
  221. package/dist/modes/interactive/components/session-selector.d.ts +5 -5
  222. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  223. package/dist/modes/interactive/components/session-selector.js +74 -48
  224. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  225. package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
  226. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  227. package/dist/modes/interactive/components/settings-selector.js +11 -0
  228. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  229. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  230. package/dist/modes/interactive/components/skill-invocation-message.js +11 -4
  231. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  232. package/dist/modes/interactive/components/status-indicator.d.ts +2 -2
  233. package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -1
  234. package/dist/modes/interactive/components/status-indicator.js +7 -7
  235. package/dist/modes/interactive/components/status-indicator.js.map +1 -1
  236. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  237. package/dist/modes/interactive/components/tool-execution.js +18 -9
  238. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  239. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  240. package/dist/modes/interactive/components/tree-selector.js +2 -0
  241. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  242. package/dist/modes/interactive/interactive-mode.d.ts +15 -2
  243. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  244. package/dist/modes/interactive/interactive-mode.js +235 -86
  245. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  246. package/dist/modes/interactive/session-share.d.ts +2 -0
  247. package/dist/modes/interactive/session-share.d.ts.map +1 -1
  248. package/dist/modes/interactive/session-share.js +8 -4
  249. package/dist/modes/interactive/session-share.js.map +1 -1
  250. package/dist/modes/interactive/tui-renderer.d.ts.map +1 -1
  251. package/dist/modes/interactive/tui-renderer.js +2 -2
  252. package/dist/modes/interactive/tui-renderer.js.map +1 -1
  253. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  254. package/dist/modes/rpc/rpc-mode.js +2 -2
  255. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  256. package/dist/utils/clipboard-command.d.ts +7 -0
  257. package/dist/utils/clipboard-command.d.ts.map +1 -0
  258. package/dist/utils/clipboard-command.js +45 -0
  259. package/dist/utils/clipboard-command.js.map +1 -0
  260. package/dist/utils/clipboard-image.d.ts.map +1 -1
  261. package/dist/utils/clipboard-image.js +53 -81
  262. package/dist/utils/clipboard-image.js.map +1 -1
  263. package/dist/utils/clipboard.d.ts.map +1 -1
  264. package/dist/utils/clipboard.js +111 -121
  265. package/dist/utils/clipboard.js.map +1 -1
  266. package/dist/utils/wsl.d.ts +3 -0
  267. package/dist/utils/wsl.d.ts.map +1 -0
  268. package/dist/utils/wsl.js +15 -0
  269. package/dist/utils/wsl.js.map +1 -0
  270. package/dist/utils/zip.d.ts +7 -0
  271. package/dist/utils/zip.d.ts.map +1 -0
  272. package/dist/utils/zip.js +60 -0
  273. package/dist/utils/zip.js.map +1 -0
  274. package/docs/compaction.md +39 -13
  275. package/docs/custom-provider.md +22 -13
  276. package/docs/development.md +4 -4
  277. package/docs/docs.json +4 -0
  278. package/docs/environment-variables.md +1 -0
  279. package/docs/extensions.md +57 -43
  280. package/docs/json.md +4 -4
  281. package/docs/models.md +33 -2
  282. package/docs/providers.md +10 -2
  283. package/docs/sdk.md +4 -2
  284. package/docs/security.md +1 -1
  285. package/docs/session-format.md +82 -40
  286. package/docs/sessions.md +27 -0
  287. package/docs/settings.md +60 -1
  288. package/docs/termux.md +0 -1
  289. package/docs/tui.md +2 -2
  290. package/docs/usage.md +1 -0
  291. package/examples/extensions/README.md +0 -1
  292. package/examples/extensions/custom-provider-anthropic/index.ts +18 -12
  293. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  294. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  295. package/examples/extensions/custom-provider-gitlab-duo/index.ts +2 -2
  296. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  297. package/examples/extensions/gondolin/package-lock.json +2 -2
  298. package/examples/extensions/gondolin/package.json +1 -1
  299. package/examples/extensions/prompt-customizer.ts +19 -67
  300. package/examples/extensions/sandbox/package-lock.json +2 -2
  301. package/examples/extensions/sandbox/package.json +1 -1
  302. package/examples/extensions/with-deps/package-lock.json +2 -2
  303. package/examples/extensions/with-deps/package.json +1 -1
  304. package/package.json +17 -23
  305. package/dist/bundle/chunks/anthropic-messages-VWZZOSJQ.js +0 -6
  306. package/dist/bundle/chunks/azure-openai-responses-B467Y57F.js +0 -2
  307. package/dist/bundle/chunks/chunk-6WPJTKUF.js +0 -11
  308. package/dist/bundle/chunks/chunk-AXIIZGTV.js +0 -2
  309. package/dist/bundle/chunks/chunk-CLNPYIDP.js +0 -42
  310. package/dist/bundle/chunks/chunk-GMWCTUPB.js +0 -2
  311. package/dist/bundle/chunks/chunk-ORJJLIYM.js +0 -1648
  312. package/dist/bundle/chunks/chunk-U6ZMQKGD.js +0 -50
  313. package/dist/bundle/chunks/chunk-UAQELI3K.js +0 -2
  314. package/dist/bundle/chunks/google-generative-ai-Q2K7FBDI.js +0 -2
  315. package/dist/bundle/chunks/google-vertex-7UJBF4S7.js +0 -2
  316. package/dist/bundle/chunks/mistral-conversations-4M6BZBX6.js +0 -5
  317. package/dist/bundle/chunks/openai-codex-responses-MERYAUSE.js +0 -10
  318. package/dist/bundle/chunks/openai-completions-EKZT2IH2.js +0 -7
  319. package/dist/bundle/chunks/openai-responses-YZ6M7S4X.js +0 -2
  320. package/dist/utils/clipboard-native.d.ts +0 -11
  321. package/dist/utils/clipboard-native.d.ts.map +0 -1
  322. package/dist/utils/clipboard-native.js +0 -20
  323. package/dist/utils/clipboard-native.js.map +0 -1
  324. 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";
@@ -24,7 +27,7 @@ import { CredentialSynchronizationError } from "../../core/model-runtime.js";
24
27
  import { DefaultPackageManager } from "../../core/package-manager.js";
25
28
  import { getCliBinName } from "../../core/pi-client-cli-adapter.js";
26
29
  import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
27
- import { SessionManager, sessionEntryToContextMessages } from "../../core/session-manager.js";
30
+ import { SessionManager, sessionEntryToContextMessages, } from "../../core/session-manager.js";
28
31
  import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
29
32
  import { isInstallTelemetryEnabled } from "../../core/telemetry.js";
30
33
  import { withBuiltInRenderers } from "../../core/tools/renderers/index.js";
@@ -40,6 +43,7 @@ import { killTrackedDetachedChildren } from "../../utils/shell.js";
40
43
  import { loadAllHighlightLanguages } from "../../utils/syntax-highlight.js";
41
44
  import { ensureTool } from "../../utils/tools-manager.js";
42
45
  import { checkForNewPiVersion } from "../../utils/version-check.js";
46
+ import { reportBug } from "./bug-report.js";
43
47
  import { createChatViewport } from "./chat-viewport.js";
44
48
  import { ArminComponent } from "./components/armin.js";
45
49
  import { AssistantMessageComponent } from "./components/assistant-message.js";
@@ -107,6 +111,9 @@ function isCustomSessionEntry(item) {
107
111
  function isCompactionCostNotice(item) {
108
112
  return "type" in item && item.type === "compaction_cost";
109
113
  }
114
+ function isUsageSessionEntry(item) {
115
+ return "type" in item && item.type === "usage";
116
+ }
110
117
  const DEAD_TERMINAL_ERROR_CODES = new Set(["EIO", "EPIPE", "ENOTCONN"]);
111
118
  function isDeadTerminalError(error) {
112
119
  if (!error || typeof error !== "object" || !("code" in error)) {
@@ -270,6 +277,8 @@ export class InteractiveMode {
270
277
  compactionQueuedMessages = [];
271
278
  // Shutdown state
272
279
  shutdownRequested = false;
280
+ /** The `/bug` hint is shown at most once per session so error output stays readable. */
281
+ bugReportHintShown = false;
273
282
  // Extension UI state
274
283
  extensionSelector = undefined;
275
284
  extensionInput = undefined;
@@ -328,6 +337,7 @@ export class InteractiveMode {
328
337
  tuiMode,
329
338
  showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
330
339
  logDirectory: getAgentDir(),
340
+ terminal: options.terminal,
331
341
  onRightClickPaste: this.onRightClickPaste,
332
342
  fullscreenCopyOnSelect: this.settingsManager.getFullscreenCopyOnSelect(),
333
343
  });
@@ -831,6 +841,11 @@ export class InteractiveMode {
831
841
  if (modelFallbackMessage) {
832
842
  this.showWarning(modelFallbackMessage);
833
843
  }
844
+ const crash = takeUnnotifiedCrash();
845
+ if (crash) {
846
+ const when = new Date(crash.timestamp).toLocaleString();
847
+ this.showWarning(`${APP_NAME} crashed on ${when} (${crash.message}). Run /bug to report it; the crash details are attached automatically.`);
848
+ }
834
849
  void this.maybeWarnAboutAnthropicSubscriptionAuth();
835
850
  // Process initial messages
836
851
  if (initialMessage) {
@@ -1547,10 +1562,45 @@ export class InteractiveMode {
1547
1562
  async handleFatalRuntimeError(prefix, error) {
1548
1563
  const message = error instanceof Error ? error.message : String(error);
1549
1564
  this.showError(`${prefix}: ${message}`);
1565
+ if (this.recordCrash("fatal_error", error)) {
1566
+ this.chatContainer.addChild(new Text(theme.fg("muted", this.crashReportInstructions()), this.outputPad, 0));
1567
+ }
1550
1568
  stopThemeWatcher();
1551
1569
  this.stop("transcript");
1552
1570
  process.exit(1);
1553
1571
  }
1572
+ /** Persist a crash so the next start can point the user at `/bug`. Returns false when nothing was written. */
1573
+ recordCrash(kind, error) {
1574
+ try {
1575
+ return (recordCrash({
1576
+ kind,
1577
+ error,
1578
+ sessionFile: this.session.sessionFile,
1579
+ cwd: this.session.sessionManager.getCwd(),
1580
+ }) !== undefined);
1581
+ }
1582
+ catch {
1583
+ return false;
1584
+ }
1585
+ }
1586
+ crashReportInstructions() {
1587
+ const resume = this.session.sessionFile ? `run \`${APP_NAME} -r\` to resume the session, then` : "start pi and";
1588
+ return `To report this crash: ${resume} run /bug. The crash details are attached automatically.`;
1589
+ }
1590
+ suggestBugReport() {
1591
+ if (this.bugReportHintShown)
1592
+ return;
1593
+ this.bugReportHintShown = true;
1594
+ 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));
1595
+ this.ui.requestRender();
1596
+ }
1597
+ maybeSuggestBugReport(message) {
1598
+ if (message.stopReason !== "error" || isRetryableAssistantError(message))
1599
+ return;
1600
+ if (/\b(?:abort(?:ed)?|cancel(?:l?ed)?)\b/i.test(message.errorMessage ?? ""))
1601
+ return;
1602
+ this.suggestBugReport();
1603
+ }
1554
1604
  renderCurrentSessionState() {
1555
1605
  this.loadedResourcesContainer.clear();
1556
1606
  this.chatContainer.clear();
@@ -1640,7 +1690,7 @@ export class InteractiveMode {
1640
1690
  this.ui.requestRender();
1641
1691
  }
1642
1692
  showStatusIndicator(indicator, force = false) {
1643
- if (this.session.isAborting && !force) {
1693
+ if (this.session?.isAborting && !force) {
1644
1694
  indicator.dispose();
1645
1695
  if (!this.abortingStatusActive) {
1646
1696
  this.showAbortingStatusIndicator();
@@ -1652,7 +1702,7 @@ export class InteractiveMode {
1652
1702
  this.activeWorkingIndicatorEmbedded = false;
1653
1703
  this.statusContainer.clear();
1654
1704
  this.setEditorWorkingStatusIndicator(undefined);
1655
- if (indicator instanceof WorkingStatusIndicator && this.setEditorWorkingStatusIndicator(indicator)) {
1705
+ if (this.setEditorWorkingStatusIndicator(indicator)) {
1656
1706
  this.activeWorkingIndicatorEmbedded = true;
1657
1707
  return;
1658
1708
  }
@@ -1680,7 +1730,7 @@ export class InteractiveMode {
1680
1730
  this.ui.requestRender();
1681
1731
  }
1682
1732
  clearStatusIndicator(kind, force = false) {
1683
- if (this.session.isAborting && !force) {
1733
+ if (this.session?.isAborting && !force) {
1684
1734
  this.showAbortingStatusIndicator();
1685
1735
  return;
1686
1736
  }
@@ -1688,7 +1738,7 @@ export class InteractiveMode {
1688
1738
  return;
1689
1739
  }
1690
1740
  const clearedIndicator = this.activeStatusIndicator;
1691
- const clearedIndicatorWasEmbedded = clearedIndicator?.kind === "working" && this.activeWorkingIndicatorEmbedded;
1741
+ const clearedIndicatorWasEmbedded = this.activeWorkingIndicatorEmbedded;
1692
1742
  this.abortingStatusActive = false;
1693
1743
  clearedIndicator?.dispose();
1694
1744
  this.activeStatusIndicator = undefined;
@@ -2226,7 +2276,7 @@ export class InteractiveMode {
2226
2276
  this.editor = this.defaultEditor;
2227
2277
  }
2228
2278
  this.editorContainer.addChild(this.editor);
2229
- if (this.activeStatusIndicator instanceof WorkingStatusIndicator) {
2279
+ if (this.activeStatusIndicator) {
2230
2280
  this.statusContainer.clear();
2231
2281
  this.activeWorkingIndicatorEmbedded = this.setEditorWorkingStatusIndicator(this.activeStatusIndicator);
2232
2282
  if (!this.activeWorkingIndicatorEmbedded) {
@@ -2556,6 +2606,12 @@ export class InteractiveMode {
2556
2606
  this.editor.setText("");
2557
2607
  return;
2558
2608
  }
2609
+ if (text === "/bug" || text.startsWith("/bug ")) {
2610
+ const hint = text.slice("/bug".length).trim();
2611
+ this.editor.setText("");
2612
+ await this.handleBugCommand(hint ? hint : undefined);
2613
+ return;
2614
+ }
2559
2615
  if (text === "/copy") {
2560
2616
  await this.handleCopyCommand();
2561
2617
  this.editor.setText("");
@@ -2769,6 +2825,10 @@ export class InteractiveMode {
2769
2825
  this.addCustomEntryToChat(event.entry);
2770
2826
  this.ui.requestRender();
2771
2827
  }
2828
+ else if (event.entry.type === "usage" && event.entry.kind === "cache_warm") {
2829
+ this.addCacheWarmingUsage(event.entry);
2830
+ this.ui.requestRender();
2831
+ }
2772
2832
  break;
2773
2833
  case "session_info_changed":
2774
2834
  this.updateTerminalTitle();
@@ -2849,13 +2909,14 @@ export class InteractiveMode {
2849
2909
  });
2850
2910
  }
2851
2911
  this.pendingTools.clear();
2912
+ this.maybeSuggestBugReport(this.streamingMessage);
2852
2913
  }
2853
2914
  else {
2854
2915
  // Args are now complete - trigger diff computation for edit tools
2855
2916
  for (const [, component] of this.pendingTools.entries()) {
2856
2917
  component.setArgsComplete();
2857
2918
  }
2858
- this.maybeShowAssistantDiagnostics(this.streamingMessage);
2919
+ this.maybeShowThinkingDropNotice(this.streamingMessage);
2859
2920
  this.maybeShowCacheMissNotice(this.streamingMessage);
2860
2921
  }
2861
2922
  this.streamingComponent = undefined;
@@ -3147,6 +3208,8 @@ export class InteractiveMode {
3147
3208
  this.chatContainer.addChild(component);
3148
3209
  break;
3149
3210
  }
3211
+ case "system":
3212
+ break;
3150
3213
  case "user": {
3151
3214
  const textContent = this.getUserMessageText(message);
3152
3215
  if (textContent) {
@@ -3193,8 +3256,8 @@ export class InteractiveMode {
3193
3256
  renderSessionItems(items, options = {}) {
3194
3257
  this.pendingTools.clear();
3195
3258
  const renderedPendingTools = new Map();
3196
- // Cache-miss notices are not persisted; re-derive them from the full entry
3197
- // list and re-inject them after the assistant messages that paid for them.
3259
+ // Cache misses are not persisted, unlike successful cache-warming usage.
3260
+ // Re-derive them and inject them after the assistant messages that paid for them.
3198
3261
  const cacheMisses = this.settingsManager.getShowCacheMissNotices()
3199
3262
  ? collectCacheMisses(this.sessionManager.getEntries(), this.session.modelRuntime)
3200
3263
  : new Map();
@@ -3207,6 +3270,10 @@ export class InteractiveMode {
3207
3270
  this.addCustomEntryToChat(item);
3208
3271
  continue;
3209
3272
  }
3273
+ if (isUsageSessionEntry(item)) {
3274
+ this.addCacheWarmingUsage(item);
3275
+ continue;
3276
+ }
3210
3277
  if (isCompactionCostNotice(item)) {
3211
3278
  this.addCompactionCostNotice(item);
3212
3279
  continue;
@@ -3244,7 +3311,6 @@ export class InteractiveMode {
3244
3311
  }
3245
3312
  }
3246
3313
  if (message.stopReason !== "aborted" && message.stopReason !== "error") {
3247
- this.maybeShowAssistantDiagnostics(message);
3248
3314
  const miss = cacheMisses.get(message);
3249
3315
  if (miss)
3250
3316
  this.addCacheMissNotice(miss);
@@ -3276,7 +3342,7 @@ export class InteractiveMode {
3276
3342
  */
3277
3343
  renderSessionEntries(entries, options = {}) {
3278
3344
  const items = entries.flatMap((entry) => {
3279
- if (entry.type === "custom") {
3345
+ if (entry.type === "custom" || (entry.type === "usage" && entry.kind === "cache_warm")) {
3280
3346
  return [entry];
3281
3347
  }
3282
3348
  const messages = sessionEntryToContextMessages(entry);
@@ -3287,6 +3353,12 @@ export class InteractiveMode {
3287
3353
  });
3288
3354
  this.renderSessionItems(items, options);
3289
3355
  }
3356
+ addCacheWarmingUsage(entry) {
3357
+ if (!this.settingsManager.getShowCacheMissNotices())
3358
+ return;
3359
+ this.chatContainer.addChild(new Spacer(1));
3360
+ this.chatContainer.addChild(new Text(theme.fg("dim", formatCacheWarmingUsage(entry)), 1, 0));
3361
+ }
3290
3362
  /**
3291
3363
  * Render billing usage for a compaction or branch summary. The notice is derived
3292
3364
  * from persisted summary usage and is not stored as a separate session entry.
@@ -3301,31 +3373,42 @@ export class InteractiveMode {
3301
3373
  this.chatContainer.addChild(new Spacer(1));
3302
3374
  this.chatContainer.addChild(new Text(theme.fg("warning", `${label}: ${formatTokens(tokens)} tokens billed${cost}`), 1, 0));
3303
3375
  }
3304
- maybeShowAssistantDiagnostics(message) {
3305
- if (!this.settingsManager.getShowCacheMissNotices())
3306
- return;
3376
+ static countDroppedThinkingBlocks(message) {
3377
+ let count = 0;
3307
3378
  for (const diagnostic of message.diagnostics ?? []) {
3308
3379
  if (diagnostic.type !== "anthropic_input_transformations")
3309
3380
  continue;
3310
3381
  const transformations = diagnostic.details?.transformations;
3311
3382
  if (!Array.isArray(transformations))
3312
3383
  continue;
3313
- const dropped = transformations.flatMap((transformation) => {
3314
- if (typeof transformation !== "object" || transformation === null)
3315
- return [];
3316
- const details = transformation;
3317
- if (details.type !== "thinking_dropped")
3318
- return [];
3319
- const reason = typeof details.reason === "string" ? details.reason : "unknown reason";
3320
- const location = typeof details.path === "string" ? ` at ${details.path}` : "";
3321
- return [`${reason}${location}`];
3322
- });
3323
- if (dropped.length === 0)
3324
- continue;
3325
- const noun = dropped.length === 1 ? "thinking block" : `${dropped.length} thinking blocks`;
3326
- this.chatContainer.addChild(new Spacer(1));
3327
- this.chatContainer.addChild(new Text(theme.fg("warning", `Anthropic dropped ${noun}: ${dropped.join("; ")}`), 1, 0));
3384
+ count += transformations.filter((transformation) => typeof transformation === "object" &&
3385
+ transformation !== null &&
3386
+ transformation.type === "thinking_dropped").length;
3387
+ }
3388
+ return count;
3389
+ }
3390
+ maybeShowThinkingDropNotice(message) {
3391
+ if (!this.settingsManager.getShowCacheMissNotices())
3392
+ return;
3393
+ const droppedCount = InteractiveMode.countDroppedThinkingBlocks(message);
3394
+ if (droppedCount === 0)
3395
+ return;
3396
+ let previousDroppedCount = 0;
3397
+ // message_end reaches the UI before the current message is persisted,
3398
+ // so the branch's last assistant message is the previous response.
3399
+ const branch = this.sessionManager.getBranch();
3400
+ for (let i = branch.length - 1; i >= 0; i--) {
3401
+ const entry = branch[i];
3402
+ if (entry.type === "message" && entry.message.role === "assistant") {
3403
+ previousDroppedCount = InteractiveMode.countDroppedThinkingBlocks(entry.message);
3404
+ break;
3405
+ }
3328
3406
  }
3407
+ if (droppedCount <= previousDroppedCount)
3408
+ return;
3409
+ const noun = droppedCount === 1 ? "thinking block" : "thinking blocks";
3410
+ this.chatContainer.addChild(new Spacer(1));
3411
+ this.chatContainer.addChild(new Text(theme.fg("warning", `Anthropic dropped ${droppedCount} ${noun} (details in session)`), 1, 0));
3329
3412
  }
3330
3413
  /**
3331
3414
  * Show a transcript notice when a completed assistant message paid for a
@@ -3493,6 +3576,9 @@ export class InteractiveMode {
3493
3576
  catch { }
3494
3577
  console.error(`${APP_NAME} exiting due to uncaughtException:`);
3495
3578
  console.error(error);
3579
+ if (this.recordCrash("uncaught_exception", error)) {
3580
+ console.error(`\n${this.crashReportInstructions()}`);
3581
+ }
3496
3582
  process.exit(1);
3497
3583
  }
3498
3584
  /**
@@ -3628,9 +3714,7 @@ export class InteractiveMode {
3628
3714
  const level = this.session.thinkingLevel || "off";
3629
3715
  this.editor.borderColor = theme.getThinkingBorderColor(level);
3630
3716
  }
3631
- if (this.activeStatusIndicator?.kind === "working") {
3632
- this.activeStatusIndicator.invalidate();
3633
- }
3717
+ this.activeStatusIndicator?.invalidate();
3634
3718
  this.ui.requestRender();
3635
3719
  }
3636
3720
  cycleThinkingLevel() {
@@ -3827,7 +3911,10 @@ export class InteractiveMode {
3827
3911
  if (allQueued.length === 0) {
3828
3912
  this.updatePendingMessagesDisplay();
3829
3913
  if (options?.abort) {
3830
- this.requestAbort();
3914
+ if (typeof this.requestAbort === "function")
3915
+ this.requestAbort();
3916
+ else
3917
+ void this.session.abort();
3831
3918
  }
3832
3919
  return 0;
3833
3920
  }
@@ -3837,7 +3924,10 @@ export class InteractiveMode {
3837
3924
  this.editor.setText(combinedText);
3838
3925
  this.updatePendingMessagesDisplay();
3839
3926
  if (options?.abort) {
3840
- this.requestAbort();
3927
+ if (typeof this.requestAbort === "function")
3928
+ this.requestAbort();
3929
+ else
3930
+ void this.session.abort();
3841
3931
  }
3842
3932
  return allQueued.length;
3843
3933
  }
@@ -4000,6 +4090,7 @@ export class InteractiveMode {
4000
4090
  followUpMode: this.session.followUpMode,
4001
4091
  transport: this.settingsManager.getTransport(),
4002
4092
  httpIdleTimeoutMs: this.settingsManager.getHttpIdleTimeoutMs(),
4093
+ cacheWarmingMode: this.settingsManager.getCacheWarmingMode(),
4003
4094
  thinkingLevel: this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL,
4004
4095
  availableThinkingLevels: [...THINKING_LEVEL_OPTIONS],
4005
4096
  modelThinkingLevels: this.settingsManager.getAllModelThinkingLevels(),
@@ -4072,6 +4163,10 @@ export class InteractiveMode {
4072
4163
  configureHttpDispatcher(timeoutMs);
4073
4164
  this.showStatus(`HTTP idle timeout: ${formatHttpIdleTimeoutMs(timeoutMs)}`);
4074
4165
  },
4166
+ onCacheWarmingModeChange: (mode) => {
4167
+ this.session.setCacheWarmingMode(mode);
4168
+ this.showStatus(`Cache warming: ${mode}`);
4169
+ },
4075
4170
  onModelThinkingLevelChange: (provider, modelId, level) => {
4076
4171
  this.settingsManager.setModelThinkingLevel(provider, modelId, level);
4077
4172
  // If the override is for the current model, apply it to the session too
@@ -4623,6 +4718,11 @@ export class InteractiveMode {
4623
4718
  this.restoreQueuedMessagesToEditor();
4624
4719
  await this.session.abort();
4625
4720
  }
4721
+ // Recheck after the dialogs and streaming abort, before replacing another operation's UI.
4722
+ if (this.session.isCompacting) {
4723
+ this.showError("Wait for the current compaction or tree navigation to finish before navigating the session tree.");
4724
+ return;
4725
+ }
4626
4726
  // Set up escape handler and status indicator if summarizing
4627
4727
  let showingSummaryIndicator = false;
4628
4728
  const originalOnEscape = this.defaultEditor.onEscape;
@@ -4693,9 +4793,9 @@ export class InteractiveMode {
4693
4793
  }
4694
4794
  showSessionSelector() {
4695
4795
  this.showSelector((done) => {
4696
- const selector = new SessionSelectorComponent((onProgress) => SessionManager.list(this.sessionManager.getCwd(), this.sessionManager.getSessionDir(), onProgress), (onProgress) => this.sessionManager.usesDefaultSessionDir()
4697
- ? SessionManager.listAll(onProgress)
4698
- : SessionManager.listAll(this.sessionManager.getSessionDir(), onProgress), async (sessionPath) => {
4796
+ const selector = new SessionSelectorComponent((onProgress, signal) => SessionManager.list(this.sessionManager.getCwd(), this.sessionManager.getSessionDir(), onProgress, signal), (onProgress, signal) => this.sessionManager.usesDefaultSessionDir()
4797
+ ? SessionManager.listAll(onProgress, signal)
4798
+ : SessionManager.listAll(this.sessionManager.getSessionDir(), onProgress, signal), async (sessionPath) => {
4699
4799
  done();
4700
4800
  await this.handleResumeSession(sessionPath);
4701
4801
  }, () => {
@@ -4958,67 +5058,89 @@ export class InteractiveMode {
4958
5058
  }
4959
5059
  async completeProviderAuthentication(providerId, providerName, authType, previousModel) {
4960
5060
  const actionLabel = authType === "oauth" ? `Logged in to ${providerName}` : `Saved API key for ${providerName}`;
4961
- let selectedModel;
4962
- let selectionError;
4963
- if (isUnknownModel(previousModel)) {
4964
- const availableModels = this.session.modelRuntime.getAvailableSnapshot();
4965
- const providerModels = availableModels.filter((model) => model.provider === providerId);
4966
- // Matches LLAMA_PROVIDER_ID from extensions/llama/provider.ts; kept inline to avoid coupling interactive mode to the built-in extension.
4967
- if (providerId === "llama.cpp") {
4968
- selectionError = llamaCppPostLoginGuidance(actionLabel, providerModels.length);
4969
- }
4970
- else if (!hasDefaultModelProvider(providerId)) {
4971
- selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
4972
- }
4973
- else if (providerModels.length === 0) {
4974
- selectionError = `${actionLabel}, but no models are available for that provider. Use /model to select a model.`;
4975
- }
4976
- else {
4977
- const defaultModelId = defaultModelPerProvider[providerId];
4978
- selectedModel = providerModels.find((model) => model.id === defaultModelId);
4979
- if (!selectedModel) {
4980
- selectionError = `${actionLabel}, but its default model "${defaultModelId}" is not available. Use /model to select a model.`;
5061
+ const session = this.session;
5062
+ // Dynamic catalogs may be empty until the first authenticated network refresh.
5063
+ const deferSelection = isUnknownModel(previousModel) &&
5064
+ hasDefaultModelProvider(providerId) &&
5065
+ !session.modelRuntime
5066
+ .getAvailableSnapshot()
5067
+ .some((model) => model.provider === providerId && model.id === defaultModelPerProvider[providerId]);
5068
+ const finishAuthentication = async () => {
5069
+ let selectedModel;
5070
+ let selectionError;
5071
+ if (isUnknownModel(previousModel)) {
5072
+ const availableModels = this.session.modelRuntime.getAvailableSnapshot();
5073
+ const providerModels = availableModels.filter((model) => model.provider === providerId);
5074
+ // Matches LLAMA_PROVIDER_ID from extensions/llama/provider.ts; kept inline to avoid coupling interactive mode to the built-in extension.
5075
+ if (providerId === "llama.cpp") {
5076
+ selectionError = llamaCppPostLoginGuidance(actionLabel, providerModels.length);
5077
+ }
5078
+ else if (!hasDefaultModelProvider(providerId)) {
5079
+ selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
5080
+ }
5081
+ else if (providerModels.length === 0) {
5082
+ selectionError = `${actionLabel}, but no models are available for that provider. Use /model to select a model.`;
4981
5083
  }
4982
5084
  else {
4983
- try {
4984
- await this.session.setModel(selectedModel, { persist: true });
5085
+ const defaultModelId = defaultModelPerProvider[providerId];
5086
+ // Radius catalogs vary by account; prefer balanced, then use catalog order.
5087
+ selectedModel =
5088
+ providerModels.find((model) => model.id === defaultModelId) ??
5089
+ (providerId === "radius" ? providerModels[0] : undefined);
5090
+ if (!selectedModel) {
5091
+ selectionError = `${actionLabel}, but its default model "${defaultModelId}" is not available. Use /model to select a model.`;
4985
5092
  }
4986
- catch (error) {
4987
- selectedModel = undefined;
4988
- const errorMessage = error instanceof Error ? error.message : String(error);
4989
- selectionError = `${actionLabel}, but selecting its default model failed: ${errorMessage}. Use /model to select a model.`;
5093
+ else {
5094
+ try {
5095
+ await this.session.setModel(selectedModel, { persist: true });
5096
+ }
5097
+ catch (error) {
5098
+ selectedModel = undefined;
5099
+ const errorMessage = error instanceof Error ? error.message : String(error);
5100
+ selectionError = `${actionLabel}, but selecting its default model failed: ${errorMessage}. Use /model to select a model.`;
5101
+ }
4990
5102
  }
4991
5103
  }
4992
5104
  }
4993
- }
4994
- await this.updateAvailableProviderCount();
4995
- this.footer.invalidate();
4996
- this.updateEditorBorderColor();
4997
- if (selectedModel) {
4998
- this.showStatus(`${actionLabel}. Selected ${selectedModel.id}. Credentials saved to ${getAuthPath()}`);
4999
- void this.maybeWarnAboutAnthropicSubscriptionAuth(selectedModel);
5000
- this.checkDaxnutsEasterEgg(selectedModel);
5001
- }
5002
- else {
5003
- this.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}`);
5004
- if (selectionError) {
5005
- this.showError(selectionError);
5105
+ await this.updateAvailableProviderCount();
5106
+ this.footer.invalidate();
5107
+ this.updateEditorBorderColor();
5108
+ if (selectedModel) {
5109
+ this.showStatus(`${actionLabel}. Selected ${selectedModel.id}. Credentials saved to ${getAuthPath()}`);
5110
+ void this.maybeWarnAboutAnthropicSubscriptionAuth(selectedModel);
5111
+ this.checkDaxnutsEasterEgg(selectedModel);
5006
5112
  }
5007
5113
  else {
5008
- void this.maybeWarnAboutAnthropicSubscriptionAuth();
5114
+ this.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}`);
5115
+ if (selectionError) {
5116
+ this.showError(selectionError);
5117
+ }
5118
+ else {
5119
+ void this.maybeWarnAboutAnthropicSubscriptionAuth();
5120
+ }
5009
5121
  }
5122
+ };
5123
+ if (deferSelection) {
5124
+ this.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}. Refreshing model catalog…`);
5125
+ }
5126
+ else {
5127
+ await finishAuthentication();
5010
5128
  }
5011
5129
  const controller = new AbortController();
5012
5130
  const timeout = setTimeout(() => controller.abort(), 15_000);
5013
- void this.session.modelRuntime
5131
+ void session.modelRuntime
5014
5132
  .refresh({ providers: [providerId], signal: controller.signal })
5015
- .then((result) => {
5133
+ .then(async (result) => {
5016
5134
  if (result.aborted) {
5017
5135
  this.showWarning(`${actionLabel}, but its model catalog refresh timed out; using cached models.`);
5018
5136
  }
5019
5137
  else if (result.errors.size > 0) {
5020
5138
  this.showWarning(`${actionLabel}, but its model catalog could not be refreshed; using cached models.`);
5021
5139
  }
5140
+ // Do not replace a model or session selected while the refresh was running.
5141
+ if (deferSelection && this.session === session && session.model === previousModel) {
5142
+ await finishAuthentication();
5143
+ }
5022
5144
  this.updateAvailableProviderCount();
5023
5145
  this.footer.invalidate();
5024
5146
  this.ui.requestRender();
@@ -5360,6 +5482,17 @@ export class InteractiveMode {
5360
5482
  showError: (message) => this.showError(message),
5361
5483
  });
5362
5484
  }
5485
+ async handleBugCommand(hint) {
5486
+ await reportBug({
5487
+ session: this.session,
5488
+ ui: this.ui,
5489
+ editorContainer: this.editorContainer,
5490
+ editor: this.editor,
5491
+ keybindings: this.keybindings,
5492
+ showStatus: (message) => this.showStatus(message),
5493
+ showError: (message) => this.showError(message),
5494
+ }, hint);
5495
+ }
5363
5496
  async handleCopyCommand(options = {}) {
5364
5497
  if (options.preferSelection &&
5365
5498
  this.ui instanceof TuiAltScreen &&
@@ -5445,6 +5578,15 @@ export class InteractiveMode {
5445
5578
  }
5446
5579
  info += `${theme.fg("dim", "Output:")} ${stats.tokens.output.toLocaleString()}\n`;
5447
5580
  info += `${theme.fg("dim", "Total:")} ${stats.tokens.total.toLocaleString()}\n`;
5581
+ const cacheWarmingStatus = this.session.cacheWarmingStatus;
5582
+ info += `\n${theme.bold("Cache Warming")}\n`;
5583
+ info += `${theme.fg("dim", "Mode:")} ${this.settingsManager.getCacheWarmingMode()}\n`;
5584
+ info += `${theme.fg("dim", "Status:")} ${cacheWarmingStatus ? formatCacheWarmingStatus(cacheWarmingStatus) : "Inactive (cache warming unavailable)"}\n`;
5585
+ const decision = cacheWarmingStatus?.decision;
5586
+ if (decision?.economicsAvailable) {
5587
+ info += `${theme.fg("dim", "Cache miss penalty:")} $${decision.missCost.toFixed(3)}\n`;
5588
+ info += `${theme.fg("dim", "Refresh cost:")} $${decision.warmCost.toFixed(3)}\n`;
5589
+ }
5448
5590
  if (stats.cost > 0 || cacheWaste.missedTokens > 0) {
5449
5591
  info += `\n${theme.bold("Cost")}\n`;
5450
5592
  info += `${theme.fg("dim", "Total:")} $${stats.cost.toFixed(3)}`;
@@ -5575,7 +5717,7 @@ export class InteractiveMode {
5575
5717
  | \`${expandTools}\` | Toggle tool output expansion |
5576
5718
  | \`${toggleThinking}\` | Toggle thinking block visibility |
5577
5719
  | \`${externalEditor}\` | Edit message in external editor |
5578
- | \`${copyMessage}\` | Copy last assistant message |
5720
+ | \`${copyMessage}\` | Copy selection or last assistant message |
5579
5721
  | \`${followUp}\` | Queue follow-up message |
5580
5722
  | \`${dequeue}\` | Restore queued messages |
5581
5723
  | \`${pasteImage}\` | Paste image or text from clipboard |
@@ -5671,12 +5813,19 @@ export class InteractiveMode {
5671
5813
  async handleBashCommand(command, excludeFromContext = false) {
5672
5814
  const extensionRunner = this.session.extensionRunner;
5673
5815
  // Emit user_bash event to let extensions intercept
5674
- const eventResult = await extensionRunner.emitUserBash({
5675
- type: "user_bash",
5676
- command,
5677
- excludeFromContext,
5678
- cwd: this.sessionManager.getCwd(),
5679
- });
5816
+ let eventResult;
5817
+ try {
5818
+ eventResult = await extensionRunner.emitUserBash({
5819
+ type: "user_bash",
5820
+ command,
5821
+ excludeFromContext,
5822
+ cwd: this.sessionManager.getCwd(),
5823
+ });
5824
+ }
5825
+ catch {
5826
+ // The extension runner already reported the error. Do not fall back to local execution.
5827
+ return;
5828
+ }
5680
5829
  // If extension returned a full result, use it directly
5681
5830
  if (eventResult?.result) {
5682
5831
  const result = eventResult.result;