pi-namespace-patch 0.85.1 → 0.86.1-namespace.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 (330) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/README.md +26 -705
  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-4YJERF5N.js +2 -0
  7. package/dist/bundle/chunks/chunk-A3JYRWB6.js +2 -0
  8. package/dist/bundle/chunks/chunk-CVPNLAPW.js +1562 -0
  9. package/dist/bundle/chunks/chunk-DTD7JQ7Y.js +42 -0
  10. package/dist/bundle/chunks/chunk-FUXEF6JQ.js +44 -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-R7YYFUSE.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 +294 -157
  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/version-check.d.ts.map +1 -1
  269. package/dist/utils/version-check.js +11 -4
  270. package/dist/utils/version-check.js.map +1 -1
  271. package/dist/utils/wsl.d.ts +3 -0
  272. package/dist/utils/wsl.d.ts.map +1 -0
  273. package/dist/utils/wsl.js +15 -0
  274. package/dist/utils/wsl.js.map +1 -0
  275. package/dist/utils/zip.d.ts +7 -0
  276. package/dist/utils/zip.d.ts.map +1 -0
  277. package/dist/utils/zip.js +60 -0
  278. package/dist/utils/zip.js.map +1 -0
  279. package/docs/compaction.md +39 -13
  280. package/docs/custom-provider.md +22 -13
  281. package/docs/development.md +4 -4
  282. package/docs/docs.json +4 -0
  283. package/docs/environment-variables.md +1 -0
  284. package/docs/extensions.md +57 -43
  285. package/docs/json.md +4 -4
  286. package/docs/models.md +33 -2
  287. package/docs/providers.md +10 -2
  288. package/docs/sdk.md +4 -2
  289. package/docs/security.md +1 -1
  290. package/docs/session-format.md +82 -40
  291. package/docs/sessions.md +27 -0
  292. package/docs/settings.md +60 -1
  293. package/docs/termux.md +0 -1
  294. package/docs/tui.md +2 -2
  295. package/docs/usage.md +1 -0
  296. package/examples/extensions/README.md +0 -1
  297. package/examples/extensions/custom-provider-anthropic/index.ts +18 -12
  298. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  299. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  300. package/examples/extensions/custom-provider-gitlab-duo/index.ts +2 -2
  301. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  302. package/examples/extensions/gondolin/package-lock.json +2 -2
  303. package/examples/extensions/gondolin/package.json +1 -1
  304. package/examples/extensions/prompt-customizer.ts +19 -67
  305. package/examples/extensions/sandbox/package-lock.json +2 -2
  306. package/examples/extensions/sandbox/package.json +1 -1
  307. package/examples/extensions/with-deps/package-lock.json +2 -2
  308. package/examples/extensions/with-deps/package.json +1 -1
  309. package/npm-shrinkwrap.json +435 -762
  310. package/package.json +17 -23
  311. package/dist/bundle/chunks/anthropic-messages-VWZZOSJQ.js +0 -6
  312. package/dist/bundle/chunks/azure-openai-responses-BJLAM6CD.js +0 -2
  313. package/dist/bundle/chunks/chunk-AXIIZGTV.js +0 -2
  314. package/dist/bundle/chunks/chunk-CLNPYIDP.js +0 -42
  315. package/dist/bundle/chunks/chunk-GMWCTUPB.js +0 -2
  316. package/dist/bundle/chunks/chunk-JJ2YFCJF.js +0 -1547
  317. package/dist/bundle/chunks/chunk-OUXLLA64.js +0 -11
  318. package/dist/bundle/chunks/chunk-U6ZMQKGD.js +0 -50
  319. package/dist/bundle/chunks/chunk-UAQELI3K.js +0 -2
  320. package/dist/bundle/chunks/google-generative-ai-Q2K7FBDI.js +0 -2
  321. package/dist/bundle/chunks/google-vertex-7UJBF4S7.js +0 -2
  322. package/dist/bundle/chunks/mistral-conversations-4M6BZBX6.js +0 -5
  323. package/dist/bundle/chunks/openai-codex-responses-R6VYZTVY.js +0 -10
  324. package/dist/bundle/chunks/openai-completions-EKZT2IH2.js +0 -7
  325. package/dist/bundle/chunks/openai-responses-TFDINO6W.js +0 -2
  326. package/dist/utils/clipboard-native.d.ts +0 -11
  327. package/dist/utils/clipboard-native.d.ts.map +0 -1
  328. package/dist/utils/clipboard-native.js +0 -20
  329. package/dist/utils/clipboard-native.js.map +0 -1
  330. 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
  });
@@ -819,6 +829,11 @@ export class InteractiveMode {
819
829
  if (modelFallbackMessage) {
820
830
  this.showWarning(modelFallbackMessage);
821
831
  }
832
+ const crash = takeUnnotifiedCrash();
833
+ if (crash) {
834
+ const when = new Date(crash.timestamp).toLocaleString();
835
+ this.showWarning(`${APP_NAME} crashed on ${when} (${crash.message}). Run /bug to report it; the crash details are attached automatically.`);
836
+ }
822
837
  void this.maybeWarnAboutAnthropicSubscriptionAuth();
823
838
  // Process initial messages
824
839
  if (initialMessage) {
@@ -1535,10 +1550,45 @@ export class InteractiveMode {
1535
1550
  async handleFatalRuntimeError(prefix, error) {
1536
1551
  const message = error instanceof Error ? error.message : String(error);
1537
1552
  this.showError(`${prefix}: ${message}`);
1553
+ if (this.recordCrash("fatal_error", error)) {
1554
+ this.chatContainer.addChild(new Text(theme.fg("muted", this.crashReportInstructions()), this.outputPad, 0));
1555
+ }
1538
1556
  stopThemeWatcher();
1539
1557
  this.stop("transcript");
1540
1558
  process.exit(1);
1541
1559
  }
1560
+ /** Persist a crash so the next start can point the user at `/bug`. Returns false when nothing was written. */
1561
+ recordCrash(kind, error) {
1562
+ try {
1563
+ return (recordCrash({
1564
+ kind,
1565
+ error,
1566
+ sessionFile: this.session.sessionFile,
1567
+ cwd: this.session.sessionManager.getCwd(),
1568
+ }) !== undefined);
1569
+ }
1570
+ catch {
1571
+ return false;
1572
+ }
1573
+ }
1574
+ crashReportInstructions() {
1575
+ const resume = this.session.sessionFile ? `run \`${APP_NAME} -r\` to resume the session, then` : "start pi and";
1576
+ return `To report this crash: ${resume} run /bug. The crash details are attached automatically.`;
1577
+ }
1578
+ suggestBugReport() {
1579
+ if (this.bugReportHintShown)
1580
+ return;
1581
+ this.bugReportHintShown = true;
1582
+ 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));
1583
+ this.ui.requestRender();
1584
+ }
1585
+ maybeSuggestBugReport(message) {
1586
+ if (message.stopReason !== "error" || isRetryableAssistantError(message))
1587
+ return;
1588
+ if (/\b(?:abort(?:ed)?|cancel(?:l?ed)?)\b/i.test(message.errorMessage ?? ""))
1589
+ return;
1590
+ this.suggestBugReport();
1591
+ }
1542
1592
  renderCurrentSessionState() {
1543
1593
  this.loadedResourcesContainer.clear();
1544
1594
  this.chatContainer.clear();
@@ -1640,7 +1690,7 @@ export class InteractiveMode {
1640
1690
  this.activeWorkingIndicatorEmbedded = false;
1641
1691
  this.statusContainer.clear();
1642
1692
  this.setEditorWorkingStatusIndicator(undefined);
1643
- if (indicator instanceof WorkingStatusIndicator && this.setEditorWorkingStatusIndicator(indicator)) {
1693
+ if (this.setEditorWorkingStatusIndicator(indicator)) {
1644
1694
  this.activeWorkingIndicatorEmbedded = true;
1645
1695
  return;
1646
1696
  }
@@ -1651,7 +1701,7 @@ export class InteractiveMode {
1651
1701
  return;
1652
1702
  }
1653
1703
  const clearedIndicator = this.activeStatusIndicator;
1654
- const clearedIndicatorWasEmbedded = clearedIndicator?.kind === "working" && this.activeWorkingIndicatorEmbedded;
1704
+ const clearedIndicatorWasEmbedded = this.activeWorkingIndicatorEmbedded;
1655
1705
  clearedIndicator?.dispose();
1656
1706
  this.activeStatusIndicator = undefined;
1657
1707
  this.activeWorkingIndicatorEmbedded = false;
@@ -2140,7 +2190,7 @@ export class InteractiveMode {
2140
2190
  this.editor = this.defaultEditor;
2141
2191
  }
2142
2192
  this.editorContainer.addChild(this.editor);
2143
- if (this.activeStatusIndicator instanceof WorkingStatusIndicator) {
2193
+ if (this.activeStatusIndicator) {
2144
2194
  this.statusContainer.clear();
2145
2195
  this.activeWorkingIndicatorEmbedded = this.setEditorWorkingStatusIndicator(this.activeStatusIndicator);
2146
2196
  if (!this.activeWorkingIndicatorEmbedded) {
@@ -2411,6 +2461,12 @@ export class InteractiveMode {
2411
2461
  this.editor.setText("");
2412
2462
  return;
2413
2463
  }
2464
+ if (text === "/bug" || text.startsWith("/bug ")) {
2465
+ const hint = text.slice("/bug".length).trim();
2466
+ this.editor.setText("");
2467
+ await this.handleBugCommand(hint ? hint : undefined);
2468
+ return;
2469
+ }
2414
2470
  if (text === "/copy") {
2415
2471
  await this.handleCopyCommand();
2416
2472
  this.editor.setText("");
@@ -2602,6 +2658,10 @@ export class InteractiveMode {
2602
2658
  this.addCustomEntryToChat(event.entry);
2603
2659
  this.ui.requestRender();
2604
2660
  }
2661
+ else if (event.entry.type === "usage" && event.entry.kind === "cache_warm") {
2662
+ this.addCacheWarmingUsage(event.entry);
2663
+ this.ui.requestRender();
2664
+ }
2605
2665
  break;
2606
2666
  case "session_info_changed":
2607
2667
  this.updateTerminalTitle();
@@ -2682,13 +2742,14 @@ export class InteractiveMode {
2682
2742
  });
2683
2743
  }
2684
2744
  this.pendingTools.clear();
2745
+ this.maybeSuggestBugReport(this.streamingMessage);
2685
2746
  }
2686
2747
  else {
2687
2748
  // Args are now complete - trigger diff computation for edit tools
2688
2749
  for (const [, component] of this.pendingTools.entries()) {
2689
2750
  component.setArgsComplete();
2690
2751
  }
2691
- this.maybeShowAssistantDiagnostics(this.streamingMessage);
2752
+ this.maybeShowThinkingDropNotice(this.streamingMessage);
2692
2753
  this.maybeShowCacheMissNotice(this.streamingMessage);
2693
2754
  }
2694
2755
  this.streamingComponent = undefined;
@@ -2955,6 +3016,8 @@ export class InteractiveMode {
2955
3016
  this.chatContainer.addChild(component);
2956
3017
  break;
2957
3018
  }
3019
+ case "system":
3020
+ break;
2958
3021
  case "user": {
2959
3022
  const textContent = this.getUserMessageText(message);
2960
3023
  if (textContent) {
@@ -3001,8 +3064,8 @@ export class InteractiveMode {
3001
3064
  renderSessionItems(items, options = {}) {
3002
3065
  this.pendingTools.clear();
3003
3066
  const renderedPendingTools = new Map();
3004
- // Cache-miss notices are not persisted; re-derive them from the full entry
3005
- // list and re-inject them after the assistant messages that paid for them.
3067
+ // Cache misses are not persisted, unlike successful cache-warming usage.
3068
+ // Re-derive them and inject them after the assistant messages that paid for them.
3006
3069
  const cacheMisses = this.settingsManager.getShowCacheMissNotices()
3007
3070
  ? collectCacheMisses(this.sessionManager.getEntries(), this.session.modelRuntime)
3008
3071
  : new Map();
@@ -3015,6 +3078,10 @@ export class InteractiveMode {
3015
3078
  this.addCustomEntryToChat(item);
3016
3079
  continue;
3017
3080
  }
3081
+ if (isUsageSessionEntry(item)) {
3082
+ this.addCacheWarmingUsage(item);
3083
+ continue;
3084
+ }
3018
3085
  if (isCompactionCostNotice(item)) {
3019
3086
  this.addCompactionCostNotice(item);
3020
3087
  continue;
@@ -3052,7 +3119,6 @@ export class InteractiveMode {
3052
3119
  }
3053
3120
  }
3054
3121
  if (message.stopReason !== "aborted" && message.stopReason !== "error") {
3055
- this.maybeShowAssistantDiagnostics(message);
3056
3122
  const miss = cacheMisses.get(message);
3057
3123
  if (miss)
3058
3124
  this.addCacheMissNotice(miss);
@@ -3084,7 +3150,7 @@ export class InteractiveMode {
3084
3150
  */
3085
3151
  renderSessionEntries(entries, options = {}) {
3086
3152
  const items = entries.flatMap((entry) => {
3087
- if (entry.type === "custom") {
3153
+ if (entry.type === "custom" || (entry.type === "usage" && entry.kind === "cache_warm")) {
3088
3154
  return [entry];
3089
3155
  }
3090
3156
  const messages = sessionEntryToContextMessages(entry);
@@ -3095,6 +3161,12 @@ export class InteractiveMode {
3095
3161
  });
3096
3162
  this.renderSessionItems(items, options);
3097
3163
  }
3164
+ addCacheWarmingUsage(entry) {
3165
+ if (!this.settingsManager.getShowCacheMissNotices())
3166
+ return;
3167
+ this.chatContainer.addChild(new Spacer(1));
3168
+ this.chatContainer.addChild(new Text(theme.fg("dim", formatCacheWarmingUsage(entry)), 1, 0));
3169
+ }
3098
3170
  /**
3099
3171
  * Render billing usage for a compaction or branch summary. The notice is derived
3100
3172
  * from persisted summary usage and is not stored as a separate session entry.
@@ -3109,31 +3181,42 @@ export class InteractiveMode {
3109
3181
  this.chatContainer.addChild(new Spacer(1));
3110
3182
  this.chatContainer.addChild(new Text(theme.fg("warning", `${label}: ${formatTokens(tokens)} tokens billed${cost}`), 1, 0));
3111
3183
  }
3112
- maybeShowAssistantDiagnostics(message) {
3113
- if (!this.settingsManager.getShowCacheMissNotices())
3114
- return;
3184
+ static countDroppedThinkingBlocks(message) {
3185
+ let count = 0;
3115
3186
  for (const diagnostic of message.diagnostics ?? []) {
3116
3187
  if (diagnostic.type !== "anthropic_input_transformations")
3117
3188
  continue;
3118
3189
  const transformations = diagnostic.details?.transformations;
3119
3190
  if (!Array.isArray(transformations))
3120
3191
  continue;
3121
- const dropped = transformations.flatMap((transformation) => {
3122
- if (typeof transformation !== "object" || transformation === null)
3123
- return [];
3124
- const details = transformation;
3125
- if (details.type !== "thinking_dropped")
3126
- return [];
3127
- const reason = typeof details.reason === "string" ? details.reason : "unknown reason";
3128
- const location = typeof details.path === "string" ? ` at ${details.path}` : "";
3129
- return [`${reason}${location}`];
3130
- });
3131
- if (dropped.length === 0)
3132
- continue;
3133
- const noun = dropped.length === 1 ? "thinking block" : `${dropped.length} thinking blocks`;
3134
- this.chatContainer.addChild(new Spacer(1));
3135
- this.chatContainer.addChild(new Text(theme.fg("warning", `Anthropic dropped ${noun}: ${dropped.join("; ")}`), 1, 0));
3192
+ count += transformations.filter((transformation) => typeof transformation === "object" &&
3193
+ transformation !== null &&
3194
+ transformation.type === "thinking_dropped").length;
3136
3195
  }
3196
+ return count;
3197
+ }
3198
+ maybeShowThinkingDropNotice(message) {
3199
+ if (!this.settingsManager.getShowCacheMissNotices())
3200
+ return;
3201
+ const droppedCount = InteractiveMode.countDroppedThinkingBlocks(message);
3202
+ if (droppedCount === 0)
3203
+ return;
3204
+ let previousDroppedCount = 0;
3205
+ // message_end reaches the UI before the current message is persisted,
3206
+ // so the branch's last assistant message is the previous response.
3207
+ const branch = this.sessionManager.getBranch();
3208
+ for (let i = branch.length - 1; i >= 0; i--) {
3209
+ const entry = branch[i];
3210
+ if (entry.type === "message" && entry.message.role === "assistant") {
3211
+ previousDroppedCount = InteractiveMode.countDroppedThinkingBlocks(entry.message);
3212
+ break;
3213
+ }
3214
+ }
3215
+ if (droppedCount <= previousDroppedCount)
3216
+ return;
3217
+ const noun = droppedCount === 1 ? "thinking block" : "thinking blocks";
3218
+ this.chatContainer.addChild(new Spacer(1));
3219
+ this.chatContainer.addChild(new Text(theme.fg("warning", `Anthropic dropped ${droppedCount} ${noun} (details in session)`), 1, 0));
3137
3220
  }
3138
3221
  /**
3139
3222
  * Show a transcript notice when a completed assistant message paid for a
@@ -3301,6 +3384,9 @@ export class InteractiveMode {
3301
3384
  catch { }
3302
3385
  console.error(`${APP_NAME} exiting due to uncaughtException:`);
3303
3386
  console.error(error);
3387
+ if (this.recordCrash("uncaught_exception", error)) {
3388
+ console.error(`\n${this.crashReportInstructions()}`);
3389
+ }
3304
3390
  process.exit(1);
3305
3391
  }
3306
3392
  /**
@@ -3432,9 +3518,7 @@ export class InteractiveMode {
3432
3518
  const level = this.session.thinkingLevel || "off";
3433
3519
  this.editor.borderColor = theme.getThinkingBorderColor(level);
3434
3520
  }
3435
- if (this.activeStatusIndicator?.kind === "working") {
3436
- this.activeStatusIndicator.invalidate();
3437
- }
3521
+ this.activeStatusIndicator?.invalidate();
3438
3522
  this.ui.requestRender();
3439
3523
  }
3440
3524
  cycleThinkingLevel() {
@@ -3628,7 +3712,7 @@ export class InteractiveMode {
3628
3712
  if (allQueued.length === 0) {
3629
3713
  this.updatePendingMessagesDisplay();
3630
3714
  if (options?.abort) {
3631
- this.agent.abort();
3715
+ void this.session.abort();
3632
3716
  }
3633
3717
  return 0;
3634
3718
  }
@@ -3638,7 +3722,7 @@ export class InteractiveMode {
3638
3722
  this.editor.setText(combinedText);
3639
3723
  this.updatePendingMessagesDisplay();
3640
3724
  if (options?.abort) {
3641
- this.agent.abort();
3725
+ void this.session.abort();
3642
3726
  }
3643
3727
  return allQueued.length;
3644
3728
  }
@@ -3792,6 +3876,7 @@ export class InteractiveMode {
3792
3876
  followUpMode: this.session.followUpMode,
3793
3877
  transport: this.settingsManager.getTransport(),
3794
3878
  httpIdleTimeoutMs: this.settingsManager.getHttpIdleTimeoutMs(),
3879
+ cacheWarmingMode: this.settingsManager.getCacheWarmingMode(),
3795
3880
  thinkingLevel: this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL,
3796
3881
  availableThinkingLevels: [...THINKING_LEVEL_OPTIONS],
3797
3882
  modelThinkingLevels: this.settingsManager.getAllModelThinkingLevels(),
@@ -3864,6 +3949,10 @@ export class InteractiveMode {
3864
3949
  configureHttpDispatcher(timeoutMs);
3865
3950
  this.showStatus(`HTTP idle timeout: ${formatHttpIdleTimeoutMs(timeoutMs)}`);
3866
3951
  },
3952
+ onCacheWarmingModeChange: (mode) => {
3953
+ this.session.setCacheWarmingMode(mode);
3954
+ this.showStatus(`Cache warming: ${mode}`);
3955
+ },
3867
3956
  onModelThinkingLevelChange: (provider, modelId, level) => {
3868
3957
  this.settingsManager.setModelThinkingLevel(provider, modelId, level);
3869
3958
  // If the override is for the current model, apply it to the session too
@@ -4415,6 +4504,11 @@ export class InteractiveMode {
4415
4504
  this.restoreQueuedMessagesToEditor();
4416
4505
  await this.session.abort();
4417
4506
  }
4507
+ // Recheck after the dialogs and streaming abort, before replacing another operation's UI.
4508
+ if (this.session.isCompacting) {
4509
+ this.showError("Wait for the current compaction or tree navigation to finish before navigating the session tree.");
4510
+ return;
4511
+ }
4418
4512
  // Set up escape handler and status indicator if summarizing
4419
4513
  let showingSummaryIndicator = false;
4420
4514
  const originalOnEscape = this.defaultEditor.onEscape;
@@ -4485,9 +4579,9 @@ export class InteractiveMode {
4485
4579
  }
4486
4580
  showSessionSelector() {
4487
4581
  this.showSelector((done) => {
4488
- const selector = new SessionSelectorComponent((onProgress) => SessionManager.list(this.sessionManager.getCwd(), this.sessionManager.getSessionDir(), onProgress), (onProgress) => this.sessionManager.usesDefaultSessionDir()
4489
- ? SessionManager.listAll(onProgress)
4490
- : SessionManager.listAll(this.sessionManager.getSessionDir(), onProgress), async (sessionPath) => {
4582
+ const selector = new SessionSelectorComponent((onProgress, signal) => SessionManager.list(this.sessionManager.getCwd(), this.sessionManager.getSessionDir(), onProgress, signal), (onProgress, signal) => this.sessionManager.usesDefaultSessionDir()
4583
+ ? SessionManager.listAll(onProgress, signal)
4584
+ : SessionManager.listAll(this.sessionManager.getSessionDir(), onProgress, signal), async (sessionPath) => {
4491
4585
  done();
4492
4586
  await this.handleResumeSession(sessionPath);
4493
4587
  }, () => {
@@ -4750,67 +4844,89 @@ export class InteractiveMode {
4750
4844
  }
4751
4845
  async completeProviderAuthentication(providerId, providerName, authType, previousModel) {
4752
4846
  const actionLabel = authType === "oauth" ? `Logged in to ${providerName}` : `Saved API key for ${providerName}`;
4753
- let selectedModel;
4754
- let selectionError;
4755
- if (isUnknownModel(previousModel)) {
4756
- const availableModels = this.session.modelRuntime.getAvailableSnapshot();
4757
- const providerModels = availableModels.filter((model) => model.provider === providerId);
4758
- // Matches LLAMA_PROVIDER_ID from extensions/llama/provider.ts; kept inline to avoid coupling interactive mode to the built-in extension.
4759
- if (providerId === "llama.cpp") {
4760
- selectionError = llamaCppPostLoginGuidance(actionLabel, providerModels.length);
4761
- }
4762
- else if (!hasDefaultModelProvider(providerId)) {
4763
- selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
4764
- }
4765
- else if (providerModels.length === 0) {
4766
- selectionError = `${actionLabel}, but no models are available for that provider. Use /model to select a model.`;
4767
- }
4768
- else {
4769
- const defaultModelId = defaultModelPerProvider[providerId];
4770
- selectedModel = providerModels.find((model) => model.id === defaultModelId);
4771
- if (!selectedModel) {
4772
- selectionError = `${actionLabel}, but its default model "${defaultModelId}" is not available. Use /model to select a model.`;
4847
+ const session = this.session;
4848
+ // Dynamic catalogs may be empty until the first authenticated network refresh.
4849
+ const deferSelection = isUnknownModel(previousModel) &&
4850
+ hasDefaultModelProvider(providerId) &&
4851
+ !session.modelRuntime
4852
+ .getAvailableSnapshot()
4853
+ .some((model) => model.provider === providerId && model.id === defaultModelPerProvider[providerId]);
4854
+ const finishAuthentication = async () => {
4855
+ let selectedModel;
4856
+ let selectionError;
4857
+ if (isUnknownModel(previousModel)) {
4858
+ const availableModels = this.session.modelRuntime.getAvailableSnapshot();
4859
+ const providerModels = availableModels.filter((model) => model.provider === providerId);
4860
+ // Matches LLAMA_PROVIDER_ID from extensions/llama/provider.ts; kept inline to avoid coupling interactive mode to the built-in extension.
4861
+ if (providerId === "llama.cpp") {
4862
+ selectionError = llamaCppPostLoginGuidance(actionLabel, providerModels.length);
4863
+ }
4864
+ else if (!hasDefaultModelProvider(providerId)) {
4865
+ selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
4866
+ }
4867
+ else if (providerModels.length === 0) {
4868
+ selectionError = `${actionLabel}, but no models are available for that provider. Use /model to select a model.`;
4773
4869
  }
4774
4870
  else {
4775
- try {
4776
- await this.session.setModel(selectedModel, { persist: true });
4871
+ const defaultModelId = defaultModelPerProvider[providerId];
4872
+ // Radius catalogs vary by account; prefer balanced, then use catalog order.
4873
+ selectedModel =
4874
+ providerModels.find((model) => model.id === defaultModelId) ??
4875
+ (providerId === "radius" ? providerModels[0] : undefined);
4876
+ if (!selectedModel) {
4877
+ selectionError = `${actionLabel}, but its default model "${defaultModelId}" is not available. Use /model to select a model.`;
4777
4878
  }
4778
- catch (error) {
4779
- selectedModel = undefined;
4780
- const errorMessage = error instanceof Error ? error.message : String(error);
4781
- selectionError = `${actionLabel}, but selecting its default model failed: ${errorMessage}. Use /model to select a model.`;
4879
+ else {
4880
+ try {
4881
+ await this.session.setModel(selectedModel, { persist: true });
4882
+ }
4883
+ catch (error) {
4884
+ selectedModel = undefined;
4885
+ const errorMessage = error instanceof Error ? error.message : String(error);
4886
+ selectionError = `${actionLabel}, but selecting its default model failed: ${errorMessage}. Use /model to select a model.`;
4887
+ }
4782
4888
  }
4783
4889
  }
4784
4890
  }
4785
- }
4786
- await this.updateAvailableProviderCount();
4787
- this.footer.invalidate();
4788
- this.updateEditorBorderColor();
4789
- if (selectedModel) {
4790
- this.showStatus(`${actionLabel}. Selected ${selectedModel.id}. Credentials saved to ${getAuthPath()}`);
4791
- void this.maybeWarnAboutAnthropicSubscriptionAuth(selectedModel);
4792
- this.checkDaxnutsEasterEgg(selectedModel);
4793
- }
4794
- else {
4795
- this.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}`);
4796
- if (selectionError) {
4797
- this.showError(selectionError);
4891
+ await this.updateAvailableProviderCount();
4892
+ this.footer.invalidate();
4893
+ this.updateEditorBorderColor();
4894
+ if (selectedModel) {
4895
+ this.showStatus(`${actionLabel}. Selected ${selectedModel.id}. Credentials saved to ${getAuthPath()}`);
4896
+ void this.maybeWarnAboutAnthropicSubscriptionAuth(selectedModel);
4897
+ this.checkDaxnutsEasterEgg(selectedModel);
4798
4898
  }
4799
4899
  else {
4800
- void this.maybeWarnAboutAnthropicSubscriptionAuth();
4900
+ this.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}`);
4901
+ if (selectionError) {
4902
+ this.showError(selectionError);
4903
+ }
4904
+ else {
4905
+ void this.maybeWarnAboutAnthropicSubscriptionAuth();
4906
+ }
4801
4907
  }
4908
+ };
4909
+ if (deferSelection) {
4910
+ this.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}. Refreshing model catalog…`);
4911
+ }
4912
+ else {
4913
+ await finishAuthentication();
4802
4914
  }
4803
4915
  const controller = new AbortController();
4804
4916
  const timeout = setTimeout(() => controller.abort(), 15_000);
4805
- void this.session.modelRuntime
4917
+ void session.modelRuntime
4806
4918
  .refresh({ providers: [providerId], signal: controller.signal })
4807
- .then((result) => {
4919
+ .then(async (result) => {
4808
4920
  if (result.aborted) {
4809
4921
  this.showWarning(`${actionLabel}, but its model catalog refresh timed out; using cached models.`);
4810
4922
  }
4811
4923
  else if (result.errors.size > 0) {
4812
4924
  this.showWarning(`${actionLabel}, but its model catalog could not be refreshed; using cached models.`);
4813
4925
  }
4926
+ // Do not replace a model or session selected while the refresh was running.
4927
+ if (deferSelection && this.session === session && session.model === previousModel) {
4928
+ await finishAuthentication();
4929
+ }
4814
4930
  this.updateAvailableProviderCount();
4815
4931
  this.footer.invalidate();
4816
4932
  this.ui.requestRender();
@@ -5152,6 +5268,17 @@ export class InteractiveMode {
5152
5268
  showError: (message) => this.showError(message),
5153
5269
  });
5154
5270
  }
5271
+ async handleBugCommand(hint) {
5272
+ await reportBug({
5273
+ session: this.session,
5274
+ ui: this.ui,
5275
+ editorContainer: this.editorContainer,
5276
+ editor: this.editor,
5277
+ keybindings: this.keybindings,
5278
+ showStatus: (message) => this.showStatus(message),
5279
+ showError: (message) => this.showError(message),
5280
+ }, hint);
5281
+ }
5155
5282
  async handleCopyCommand(options = {}) {
5156
5283
  if (options.preferSelection &&
5157
5284
  this.ui instanceof TuiAltScreen &&
@@ -5237,6 +5364,15 @@ export class InteractiveMode {
5237
5364
  }
5238
5365
  info += `${theme.fg("dim", "Output:")} ${stats.tokens.output.toLocaleString()}\n`;
5239
5366
  info += `${theme.fg("dim", "Total:")} ${stats.tokens.total.toLocaleString()}\n`;
5367
+ const cacheWarmingStatus = this.session.cacheWarmingStatus;
5368
+ info += `\n${theme.bold("Cache Warming")}\n`;
5369
+ info += `${theme.fg("dim", "Mode:")} ${this.settingsManager.getCacheWarmingMode()}\n`;
5370
+ info += `${theme.fg("dim", "Status:")} ${cacheWarmingStatus ? formatCacheWarmingStatus(cacheWarmingStatus) : "Inactive (cache warming unavailable)"}\n`;
5371
+ const decision = cacheWarmingStatus?.decision;
5372
+ if (decision?.economicsAvailable) {
5373
+ info += `${theme.fg("dim", "Cache miss penalty:")} $${decision.missCost.toFixed(3)}\n`;
5374
+ info += `${theme.fg("dim", "Refresh cost:")} $${decision.warmCost.toFixed(3)}\n`;
5375
+ }
5240
5376
  if (stats.cost > 0 || cacheWaste.missedTokens > 0) {
5241
5377
  info += `\n${theme.bold("Cost")}\n`;
5242
5378
  info += `${theme.fg("dim", "Total:")} $${stats.cost.toFixed(3)}`;
@@ -5367,7 +5503,7 @@ export class InteractiveMode {
5367
5503
  | \`${expandTools}\` | Toggle tool output expansion |
5368
5504
  | \`${toggleThinking}\` | Toggle thinking block visibility |
5369
5505
  | \`${externalEditor}\` | Edit message in external editor |
5370
- | \`${copyMessage}\` | Copy last assistant message |
5506
+ | \`${copyMessage}\` | Copy selection or last assistant message |
5371
5507
  | \`${followUp}\` | Queue follow-up message |
5372
5508
  | \`${dequeue}\` | Restore queued messages |
5373
5509
  | \`${pasteImage}\` | Paste image or text from clipboard |
@@ -5463,12 +5599,19 @@ export class InteractiveMode {
5463
5599
  async handleBashCommand(command, excludeFromContext = false) {
5464
5600
  const extensionRunner = this.session.extensionRunner;
5465
5601
  // Emit user_bash event to let extensions intercept
5466
- const eventResult = await extensionRunner.emitUserBash({
5467
- type: "user_bash",
5468
- command,
5469
- excludeFromContext,
5470
- cwd: this.sessionManager.getCwd(),
5471
- });
5602
+ let eventResult;
5603
+ try {
5604
+ eventResult = await extensionRunner.emitUserBash({
5605
+ type: "user_bash",
5606
+ command,
5607
+ excludeFromContext,
5608
+ cwd: this.sessionManager.getCwd(),
5609
+ });
5610
+ }
5611
+ catch {
5612
+ // The extension runner already reported the error. Do not fall back to local execution.
5613
+ return;
5614
+ }
5472
5615
  // If extension returned a full result, use it directly
5473
5616
  if (eventResult?.result) {
5474
5617
  const result = eventResult.result;