pi-namespace-patch 0.85.1 → 0.86.0-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 (317) hide show
  1. package/CHANGELOG.md +86 -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-A3JYRWB6.js +2 -0
  7. package/dist/bundle/chunks/chunk-DTD7JQ7Y.js +42 -0
  8. package/dist/bundle/chunks/chunk-FUXEF6JQ.js +44 -0
  9. package/dist/bundle/chunks/chunk-IS2VY2NY.js +1561 -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-SXW7XMQG.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/mistral-conversations-NOSQHBEY.js +5 -0
  20. package/dist/bundle/chunks/{node-KG362ECQ.js → node-O3RIBJRH.js} +1 -1
  21. package/dist/bundle/chunks/openai-codex-responses-QDSOTBBO.js +10 -0
  22. package/dist/bundle/chunks/openai-completions-KQPOPEVM.js +7 -0
  23. package/dist/bundle/chunks/openai-responses-HWEUZ6WZ.js +2 -0
  24. package/dist/bundle/chunks/{pi-messages-PML4SPVJ.js → pi-messages-GFKBWJHZ.js} +1 -1
  25. package/dist/bundle/chunks/virtual-modules-UQVS3IVK.js +2 -0
  26. package/dist/bundle/cli.js +1 -1
  27. package/dist/bundle/index.js +1 -1
  28. package/dist/bundle/rpc-entry.js +1 -1
  29. package/dist/cli/session-picker.d.ts +1 -1
  30. package/dist/cli/session-picker.d.ts.map +1 -1
  31. package/dist/cli/session-picker.js.map +1 -1
  32. package/dist/config.d.ts.map +1 -1
  33. package/dist/config.js +1 -1
  34. package/dist/config.js.map +1 -1
  35. package/dist/core/agent-session.d.ts +56 -6
  36. package/dist/core/agent-session.d.ts.map +1 -1
  37. package/dist/core/agent-session.js +294 -157
  38. package/dist/core/agent-session.js.map +1 -1
  39. package/dist/core/bug-report-upload.d.ts +12 -0
  40. package/dist/core/bug-report-upload.d.ts.map +1 -0
  41. package/dist/core/bug-report-upload.js +22 -0
  42. package/dist/core/bug-report-upload.js.map +1 -0
  43. package/dist/core/bug-report.d.ts +176 -0
  44. package/dist/core/bug-report.d.ts.map +1 -0
  45. package/dist/core/bug-report.js +292 -0
  46. package/dist/core/bug-report.js.map +1 -0
  47. package/dist/core/cache-stats.d.ts.map +1 -1
  48. package/dist/core/cache-stats.js +12 -1
  49. package/dist/core/cache-stats.js.map +1 -1
  50. package/dist/core/cache-warmer.d.ts +102 -0
  51. package/dist/core/cache-warmer.d.ts.map +1 -0
  52. package/dist/core/cache-warmer.js +342 -0
  53. package/dist/core/cache-warmer.js.map +1 -0
  54. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  55. package/dist/core/compaction/branch-summarization.js +2 -2
  56. package/dist/core/compaction/branch-summarization.js.map +1 -1
  57. package/dist/core/compaction/compaction.d.ts +2 -2
  58. package/dist/core/compaction/compaction.d.ts.map +1 -1
  59. package/dist/core/compaction/compaction.js +11 -12
  60. package/dist/core/compaction/compaction.js.map +1 -1
  61. package/dist/core/crash-log.d.ts +22 -0
  62. package/dist/core/crash-log.d.ts.map +1 -0
  63. package/dist/core/crash-log.js +71 -0
  64. package/dist/core/crash-log.js.map +1 -0
  65. package/dist/core/experimental.d.ts +0 -4
  66. package/dist/core/experimental.d.ts.map +1 -1
  67. package/dist/core/experimental.js +0 -4
  68. package/dist/core/experimental.js.map +1 -1
  69. package/dist/core/extensions/index.d.ts +1 -1
  70. package/dist/core/extensions/index.d.ts.map +1 -1
  71. package/dist/core/extensions/index.js.map +1 -1
  72. package/dist/core/extensions/jiti-loader.d.ts +2 -0
  73. package/dist/core/extensions/jiti-loader.d.ts.map +1 -0
  74. package/dist/core/extensions/jiti-loader.js +4 -0
  75. package/dist/core/extensions/jiti-loader.js.map +1 -0
  76. package/dist/core/extensions/jiti-static-loader.d.ts +2 -0
  77. package/dist/core/extensions/jiti-static-loader.d.ts.map +1 -0
  78. package/dist/core/extensions/jiti-static-loader.js +4 -0
  79. package/dist/core/extensions/jiti-static-loader.js.map +1 -0
  80. package/dist/core/extensions/loader.d.ts.map +1 -1
  81. package/dist/core/extensions/loader.js +38 -51
  82. package/dist/core/extensions/loader.js.map +1 -1
  83. package/dist/core/extensions/runner.d.ts +10 -7
  84. package/dist/core/extensions/runner.d.ts.map +1 -1
  85. package/dist/core/extensions/runner.js +85 -67
  86. package/dist/core/extensions/runner.js.map +1 -1
  87. package/dist/core/extensions/types.d.ts +60 -50
  88. package/dist/core/extensions/types.d.ts.map +1 -1
  89. package/dist/core/extensions/types.js.map +1 -1
  90. package/dist/core/extensions/virtual-modules.d.ts +3 -0
  91. package/dist/core/extensions/virtual-modules.d.ts.map +1 -0
  92. package/dist/core/extensions/virtual-modules.js +38 -0
  93. package/dist/core/extensions/virtual-modules.js.map +1 -0
  94. package/dist/core/extensions/wrapper.d.ts.map +1 -1
  95. package/dist/core/extensions/wrapper.js +1 -20
  96. package/dist/core/extensions/wrapper.js.map +1 -1
  97. package/dist/core/index.d.ts +2 -1
  98. package/dist/core/index.d.ts.map +1 -1
  99. package/dist/core/index.js.map +1 -1
  100. package/dist/core/keybindings.d.ts +1 -1
  101. package/dist/core/keybindings.d.ts.map +1 -1
  102. package/dist/core/keybindings.js +1 -1
  103. package/dist/core/keybindings.js.map +1 -1
  104. package/dist/core/messages.d.ts.map +1 -1
  105. package/dist/core/messages.js +1 -0
  106. package/dist/core/messages.js.map +1 -1
  107. package/dist/core/model-config.d.ts +101 -20
  108. package/dist/core/model-config.d.ts.map +1 -1
  109. package/dist/core/model-config.js +25 -18
  110. package/dist/core/model-config.js.map +1 -1
  111. package/dist/core/model-registry.d.ts +5 -1
  112. package/dist/core/model-registry.d.ts.map +1 -1
  113. package/dist/core/model-registry.js +8 -0
  114. package/dist/core/model-registry.js.map +1 -1
  115. package/dist/core/model-resolver.d.ts.map +1 -1
  116. package/dist/core/model-resolver.js +1 -1
  117. package/dist/core/model-resolver.js.map +1 -1
  118. package/dist/core/model-runtime.d.ts.map +1 -1
  119. package/dist/core/model-runtime.js +5 -3
  120. package/dist/core/model-runtime.js.map +1 -1
  121. package/dist/core/provider-composer.d.ts +3 -2
  122. package/dist/core/provider-composer.d.ts.map +1 -1
  123. package/dist/core/provider-composer.js +2 -0
  124. package/dist/core/provider-composer.js.map +1 -1
  125. package/dist/core/radius.d.ts +3 -0
  126. package/dist/core/radius.d.ts.map +1 -1
  127. package/dist/core/radius.js +6 -0
  128. package/dist/core/radius.js.map +1 -1
  129. package/dist/core/sdk.d.ts.map +1 -1
  130. package/dist/core/sdk.js +60 -38
  131. package/dist/core/sdk.js.map +1 -1
  132. package/dist/core/session-export.d.ts +6 -2
  133. package/dist/core/session-export.d.ts.map +1 -1
  134. package/dist/core/session-export.js +14 -13
  135. package/dist/core/session-export.js.map +1 -1
  136. package/dist/core/session-manager.d.ts +29 -6
  137. package/dist/core/session-manager.d.ts.map +1 -1
  138. package/dist/core/session-manager.js +159 -96
  139. package/dist/core/session-manager.js.map +1 -1
  140. package/dist/core/settings-manager.d.ts +20 -4
  141. package/dist/core/settings-manager.d.ts.map +1 -1
  142. package/dist/core/settings-manager.js +43 -7
  143. package/dist/core/settings-manager.js.map +1 -1
  144. package/dist/core/slash-commands.d.ts.map +1 -1
  145. package/dist/core/slash-commands.js +1 -0
  146. package/dist/core/slash-commands.js.map +1 -1
  147. package/dist/core/system-prompt.d.ts +49 -6
  148. package/dist/core/system-prompt.d.ts.map +1 -1
  149. package/dist/core/system-prompt.js +121 -92
  150. package/dist/core/system-prompt.js.map +1 -1
  151. package/dist/core/tools/bash.d.ts +2 -1
  152. package/dist/core/tools/bash.d.ts.map +1 -1
  153. package/dist/core/tools/bash.js +10 -4
  154. package/dist/core/tools/bash.js.map +1 -1
  155. package/dist/core/tools/edit.d.ts.map +1 -1
  156. package/dist/core/tools/edit.js +1 -2
  157. package/dist/core/tools/edit.js.map +1 -1
  158. package/dist/core/tools/read.d.ts.map +1 -1
  159. package/dist/core/tools/read.js +1 -2
  160. package/dist/core/tools/read.js.map +1 -1
  161. package/dist/core/tools/renderers/bash.d.ts.map +1 -1
  162. package/dist/core/tools/renderers/bash.js +9 -1
  163. package/dist/core/tools/renderers/bash.js.map +1 -1
  164. package/dist/core/tools/write.d.ts.map +1 -1
  165. package/dist/core/tools/write.js +1 -2
  166. package/dist/core/tools/write.js.map +1 -1
  167. package/dist/core/usage-totals.d.ts +1 -1
  168. package/dist/core/usage-totals.d.ts.map +1 -1
  169. package/dist/core/usage-totals.js +5 -1
  170. package/dist/core/usage-totals.js.map +1 -1
  171. package/dist/extensions/llama/client.d.ts +2 -0
  172. package/dist/extensions/llama/client.d.ts.map +1 -1
  173. package/dist/extensions/llama/client.js +7 -3
  174. package/dist/extensions/llama/client.js.map +1 -1
  175. package/dist/extensions/llama/provider.d.ts.map +1 -1
  176. package/dist/extensions/llama/provider.js +20 -5
  177. package/dist/extensions/llama/provider.js.map +1 -1
  178. package/dist/index.d.ts +3 -2
  179. package/dist/index.d.ts.map +1 -1
  180. package/dist/index.js.map +1 -1
  181. package/dist/main.d.ts.map +1 -1
  182. package/dist/main.js +13 -9
  183. package/dist/main.js.map +1 -1
  184. package/dist/migrations.d.ts.map +1 -1
  185. package/dist/migrations.js +2 -2
  186. package/dist/migrations.js.map +1 -1
  187. package/dist/modes/interactive/bug-report.d.ts +14 -0
  188. package/dist/modes/interactive/bug-report.d.ts.map +1 -0
  189. package/dist/modes/interactive/bug-report.js +209 -0
  190. package/dist/modes/interactive/bug-report.js.map +1 -0
  191. package/dist/modes/interactive/chat-viewport.d.ts.map +1 -1
  192. package/dist/modes/interactive/chat-viewport.js +1 -1
  193. package/dist/modes/interactive/chat-viewport.js.map +1 -1
  194. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  195. package/dist/modes/interactive/components/branch-summary-message.js +12 -5
  196. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  197. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  198. package/dist/modes/interactive/components/compaction-summary-message.js +12 -5
  199. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  200. package/dist/modes/interactive/components/custom-editor.d.ts +3 -3
  201. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  202. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  203. package/dist/modes/interactive/components/extension-input.d.ts +2 -0
  204. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  205. package/dist/modes/interactive/components/extension-input.js +6 -0
  206. package/dist/modes/interactive/components/extension-input.js.map +1 -1
  207. package/dist/modes/interactive/components/extension-selector.d.ts +1 -0
  208. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  209. package/dist/modes/interactive/components/extension-selector.js +4 -0
  210. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  211. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  212. package/dist/modes/interactive/components/footer.js +4 -1
  213. package/dist/modes/interactive/components/footer.js.map +1 -1
  214. package/dist/modes/interactive/components/session-selector.d.ts +5 -5
  215. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  216. package/dist/modes/interactive/components/session-selector.js +74 -48
  217. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  218. package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
  219. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  220. package/dist/modes/interactive/components/settings-selector.js +11 -0
  221. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  222. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  223. package/dist/modes/interactive/components/skill-invocation-message.js +11 -4
  224. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  225. package/dist/modes/interactive/components/status-indicator.d.ts +2 -2
  226. package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -1
  227. package/dist/modes/interactive/components/status-indicator.js +7 -7
  228. package/dist/modes/interactive/components/status-indicator.js.map +1 -1
  229. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  230. package/dist/modes/interactive/components/tool-execution.js +18 -9
  231. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  232. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  233. package/dist/modes/interactive/components/tree-selector.js +2 -0
  234. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  235. package/dist/modes/interactive/interactive-mode.d.ts +13 -1
  236. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  237. package/dist/modes/interactive/interactive-mode.js +220 -84
  238. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  239. package/dist/modes/interactive/session-share.d.ts +2 -0
  240. package/dist/modes/interactive/session-share.d.ts.map +1 -1
  241. package/dist/modes/interactive/session-share.js +8 -4
  242. package/dist/modes/interactive/session-share.js.map +1 -1
  243. package/dist/modes/interactive/tui-renderer.d.ts.map +1 -1
  244. package/dist/modes/interactive/tui-renderer.js +2 -2
  245. package/dist/modes/interactive/tui-renderer.js.map +1 -1
  246. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  247. package/dist/modes/rpc/rpc-mode.js +2 -2
  248. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  249. package/dist/utils/clipboard-command.d.ts +7 -0
  250. package/dist/utils/clipboard-command.d.ts.map +1 -0
  251. package/dist/utils/clipboard-command.js +45 -0
  252. package/dist/utils/clipboard-command.js.map +1 -0
  253. package/dist/utils/clipboard-image.d.ts.map +1 -1
  254. package/dist/utils/clipboard-image.js +52 -69
  255. package/dist/utils/clipboard-image.js.map +1 -1
  256. package/dist/utils/clipboard.d.ts.map +1 -1
  257. package/dist/utils/clipboard.js +61 -125
  258. package/dist/utils/clipboard.js.map +1 -1
  259. package/dist/utils/version-check.d.ts.map +1 -1
  260. package/dist/utils/version-check.js +11 -4
  261. package/dist/utils/version-check.js.map +1 -1
  262. package/dist/utils/zip.d.ts +7 -0
  263. package/dist/utils/zip.d.ts.map +1 -0
  264. package/dist/utils/zip.js +60 -0
  265. package/dist/utils/zip.js.map +1 -0
  266. package/docs/compaction.md +39 -13
  267. package/docs/custom-provider.md +22 -13
  268. package/docs/development.md +4 -4
  269. package/docs/docs.json +4 -0
  270. package/docs/environment-variables.md +1 -0
  271. package/docs/extensions.md +57 -43
  272. package/docs/json.md +4 -4
  273. package/docs/models.md +33 -2
  274. package/docs/providers.md +2 -2
  275. package/docs/sdk.md +4 -2
  276. package/docs/security.md +1 -1
  277. package/docs/session-format.md +82 -40
  278. package/docs/sessions.md +27 -0
  279. package/docs/settings.md +60 -1
  280. package/docs/termux.md +0 -1
  281. package/docs/tui.md +2 -2
  282. package/docs/usage.md +1 -0
  283. package/examples/extensions/README.md +0 -1
  284. package/examples/extensions/custom-provider-anthropic/index.ts +18 -12
  285. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  286. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  287. package/examples/extensions/custom-provider-gitlab-duo/index.ts +2 -2
  288. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  289. package/examples/extensions/gondolin/package-lock.json +2 -2
  290. package/examples/extensions/gondolin/package.json +1 -1
  291. package/examples/extensions/prompt-customizer.ts +19 -67
  292. package/examples/extensions/sandbox/package-lock.json +2 -2
  293. package/examples/extensions/sandbox/package.json +1 -1
  294. package/examples/extensions/with-deps/package-lock.json +2 -2
  295. package/examples/extensions/with-deps/package.json +1 -1
  296. package/npm-shrinkwrap.json +435 -762
  297. package/package.json +17 -23
  298. package/dist/bundle/chunks/anthropic-messages-VWZZOSJQ.js +0 -6
  299. package/dist/bundle/chunks/azure-openai-responses-BJLAM6CD.js +0 -2
  300. package/dist/bundle/chunks/chunk-AXIIZGTV.js +0 -2
  301. package/dist/bundle/chunks/chunk-CLNPYIDP.js +0 -42
  302. package/dist/bundle/chunks/chunk-GMWCTUPB.js +0 -2
  303. package/dist/bundle/chunks/chunk-JJ2YFCJF.js +0 -1547
  304. package/dist/bundle/chunks/chunk-OUXLLA64.js +0 -11
  305. package/dist/bundle/chunks/chunk-U6ZMQKGD.js +0 -50
  306. package/dist/bundle/chunks/chunk-UAQELI3K.js +0 -2
  307. package/dist/bundle/chunks/google-generative-ai-Q2K7FBDI.js +0 -2
  308. package/dist/bundle/chunks/google-vertex-7UJBF4S7.js +0 -2
  309. package/dist/bundle/chunks/mistral-conversations-4M6BZBX6.js +0 -5
  310. package/dist/bundle/chunks/openai-codex-responses-R6VYZTVY.js +0 -10
  311. package/dist/bundle/chunks/openai-completions-EKZT2IH2.js +0 -7
  312. package/dist/bundle/chunks/openai-responses-TFDINO6W.js +0 -2
  313. package/dist/utils/clipboard-native.d.ts +0 -11
  314. package/dist/utils/clipboard-native.d.ts.map +0 -1
  315. package/dist/utils/clipboard-native.js +0 -20
  316. package/dist/utils/clipboard-native.js.map +0 -1
  317. package/examples/extensions/kimi-deferred-tools.ts +0 -61
@@ -14,6 +14,8 @@ import { APP_NAME, APP_TITLE, CONFIG_DIR_NAME, getAgentDir, getAuthPath, getDebu
14
14
  import { parseSkillBlock } from "../../core/agent-session.js";
15
15
  import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
16
16
  import { CACHE_TTL_MS, collectCacheMisses, computeCacheWaste, detectCacheMiss, } from "../../core/cache-stats.js";
17
+ import { formatCacheWarmingStatus, formatCacheWarmingUsage } from "../../core/cache-warmer.js";
18
+ import { recordCrash, takeUnnotifiedCrash } from "../../core/crash-log.js";
17
19
  import { DEFAULT_THINKING_LEVEL, THINKING_LEVEL_OPTIONS } from "../../core/defaults.js";
18
20
  import { FooterDataProvider } from "../../core/footer-data-provider.js";
19
21
  import { configureHttpDispatcher, formatHttpIdleTimeoutMs } from "../../core/http-dispatcher.js";
@@ -23,7 +25,7 @@ import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScop
23
25
  import { CredentialSynchronizationError } from "../../core/model-runtime.js";
24
26
  import { DefaultPackageManager } from "../../core/package-manager.js";
25
27
  import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
26
- import { SessionManager, sessionEntryToContextMessages } from "../../core/session-manager.js";
28
+ import { SessionManager, sessionEntryToContextMessages, } from "../../core/session-manager.js";
27
29
  import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
28
30
  import { isInstallTelemetryEnabled } from "../../core/telemetry.js";
29
31
  import { withBuiltInRenderers } from "../../core/tools/renderers/index.js";
@@ -39,6 +41,7 @@ import { killTrackedDetachedChildren } from "../../utils/shell.js";
39
41
  import { loadAllHighlightLanguages } from "../../utils/syntax-highlight.js";
40
42
  import { ensureTool } from "../../utils/tools-manager.js";
41
43
  import { checkForNewPiVersion } from "../../utils/version-check.js";
44
+ import { reportBug } from "./bug-report.js";
42
45
  import { createChatViewport } from "./chat-viewport.js";
43
46
  import { ArminComponent } from "./components/armin.js";
44
47
  import { AssistantMessageComponent } from "./components/assistant-message.js";
@@ -106,6 +109,9 @@ function isCustomSessionEntry(item) {
106
109
  function isCompactionCostNotice(item) {
107
110
  return "type" in item && item.type === "compaction_cost";
108
111
  }
112
+ function isUsageSessionEntry(item) {
113
+ return "type" in item && item.type === "usage";
114
+ }
109
115
  const DEAD_TERMINAL_ERROR_CODES = new Set(["EIO", "EPIPE", "ENOTCONN"]);
110
116
  function isDeadTerminalError(error) {
111
117
  if (!error || typeof error !== "object" || !("code" in error)) {
@@ -267,6 +273,8 @@ export class InteractiveMode {
267
273
  compactionQueuedMessages = [];
268
274
  // Shutdown state
269
275
  shutdownRequested = false;
276
+ /** The `/bug` hint is shown at most once per session so error output stays readable. */
277
+ bugReportHintShown = false;
270
278
  // Extension UI state
271
279
  extensionSelector = undefined;
272
280
  extensionInput = undefined;
@@ -322,6 +330,7 @@ export class InteractiveMode {
322
330
  tuiMode,
323
331
  showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
324
332
  logDirectory: getAgentDir(),
333
+ terminal: options.terminal,
325
334
  onRightClickPaste: this.onRightClickPaste,
326
335
  fullscreenCopyOnSelect: this.settingsManager.getFullscreenCopyOnSelect(),
327
336
  });
@@ -819,6 +828,11 @@ export class InteractiveMode {
819
828
  if (modelFallbackMessage) {
820
829
  this.showWarning(modelFallbackMessage);
821
830
  }
831
+ const crash = takeUnnotifiedCrash();
832
+ if (crash) {
833
+ const when = new Date(crash.timestamp).toLocaleString();
834
+ this.showWarning(`${APP_NAME} crashed on ${when} (${crash.message}). Run /bug to report it; the crash details are attached automatically.`);
835
+ }
822
836
  void this.maybeWarnAboutAnthropicSubscriptionAuth();
823
837
  // Process initial messages
824
838
  if (initialMessage) {
@@ -1535,10 +1549,38 @@ export class InteractiveMode {
1535
1549
  async handleFatalRuntimeError(prefix, error) {
1536
1550
  const message = error instanceof Error ? error.message : String(error);
1537
1551
  this.showError(`${prefix}: ${message}`);
1552
+ if (this.recordCrash("fatal_error", error)) {
1553
+ this.chatContainer.addChild(new Text(theme.fg("muted", this.crashReportInstructions()), this.outputPad, 0));
1554
+ }
1538
1555
  stopThemeWatcher();
1539
1556
  this.stop("transcript");
1540
1557
  process.exit(1);
1541
1558
  }
1559
+ /** Persist a crash so the next start can point the user at `/bug`. Returns false when nothing was written. */
1560
+ recordCrash(kind, error) {
1561
+ try {
1562
+ return (recordCrash({
1563
+ kind,
1564
+ error,
1565
+ sessionFile: this.session.sessionFile,
1566
+ cwd: this.session.sessionManager.getCwd(),
1567
+ }) !== undefined);
1568
+ }
1569
+ catch {
1570
+ return false;
1571
+ }
1572
+ }
1573
+ crashReportInstructions() {
1574
+ const resume = this.session.sessionFile ? `run \`${APP_NAME} -r\` to resume the session, then` : "start pi and";
1575
+ return `To report this crash: ${resume} run /bug. The crash details are attached automatically.`;
1576
+ }
1577
+ suggestBugReport() {
1578
+ if (this.bugReportHintShown)
1579
+ return;
1580
+ this.bugReportHintShown = true;
1581
+ 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));
1582
+ this.ui.requestRender();
1583
+ }
1542
1584
  renderCurrentSessionState() {
1543
1585
  this.loadedResourcesContainer.clear();
1544
1586
  this.chatContainer.clear();
@@ -1640,7 +1682,7 @@ export class InteractiveMode {
1640
1682
  this.activeWorkingIndicatorEmbedded = false;
1641
1683
  this.statusContainer.clear();
1642
1684
  this.setEditorWorkingStatusIndicator(undefined);
1643
- if (indicator instanceof WorkingStatusIndicator && this.setEditorWorkingStatusIndicator(indicator)) {
1685
+ if (this.setEditorWorkingStatusIndicator(indicator)) {
1644
1686
  this.activeWorkingIndicatorEmbedded = true;
1645
1687
  return;
1646
1688
  }
@@ -1651,7 +1693,7 @@ export class InteractiveMode {
1651
1693
  return;
1652
1694
  }
1653
1695
  const clearedIndicator = this.activeStatusIndicator;
1654
- const clearedIndicatorWasEmbedded = clearedIndicator?.kind === "working" && this.activeWorkingIndicatorEmbedded;
1696
+ const clearedIndicatorWasEmbedded = this.activeWorkingIndicatorEmbedded;
1655
1697
  clearedIndicator?.dispose();
1656
1698
  this.activeStatusIndicator = undefined;
1657
1699
  this.activeWorkingIndicatorEmbedded = false;
@@ -2140,7 +2182,7 @@ export class InteractiveMode {
2140
2182
  this.editor = this.defaultEditor;
2141
2183
  }
2142
2184
  this.editorContainer.addChild(this.editor);
2143
- if (this.activeStatusIndicator instanceof WorkingStatusIndicator) {
2185
+ if (this.activeStatusIndicator) {
2144
2186
  this.statusContainer.clear();
2145
2187
  this.activeWorkingIndicatorEmbedded = this.setEditorWorkingStatusIndicator(this.activeStatusIndicator);
2146
2188
  if (!this.activeWorkingIndicatorEmbedded) {
@@ -2411,6 +2453,12 @@ export class InteractiveMode {
2411
2453
  this.editor.setText("");
2412
2454
  return;
2413
2455
  }
2456
+ if (text === "/bug" || text.startsWith("/bug ")) {
2457
+ const hint = text.slice("/bug".length).trim();
2458
+ this.editor.setText("");
2459
+ await this.handleBugCommand(hint ? hint : undefined);
2460
+ return;
2461
+ }
2414
2462
  if (text === "/copy") {
2415
2463
  await this.handleCopyCommand();
2416
2464
  this.editor.setText("");
@@ -2602,6 +2650,10 @@ export class InteractiveMode {
2602
2650
  this.addCustomEntryToChat(event.entry);
2603
2651
  this.ui.requestRender();
2604
2652
  }
2653
+ else if (event.entry.type === "usage" && event.entry.kind === "cache_warm") {
2654
+ this.addCacheWarmingUsage(event.entry);
2655
+ this.ui.requestRender();
2656
+ }
2605
2657
  break;
2606
2658
  case "session_info_changed":
2607
2659
  this.updateTerminalTitle();
@@ -2682,13 +2734,15 @@ export class InteractiveMode {
2682
2734
  });
2683
2735
  }
2684
2736
  this.pendingTools.clear();
2737
+ if (this.streamingMessage.stopReason === "error")
2738
+ this.suggestBugReport();
2685
2739
  }
2686
2740
  else {
2687
2741
  // Args are now complete - trigger diff computation for edit tools
2688
2742
  for (const [, component] of this.pendingTools.entries()) {
2689
2743
  component.setArgsComplete();
2690
2744
  }
2691
- this.maybeShowAssistantDiagnostics(this.streamingMessage);
2745
+ this.maybeShowThinkingDropNotice(this.streamingMessage);
2692
2746
  this.maybeShowCacheMissNotice(this.streamingMessage);
2693
2747
  }
2694
2748
  this.streamingComponent = undefined;
@@ -2829,6 +2883,7 @@ export class InteractiveMode {
2829
2883
  // Show error only on final failure (success shows normal response)
2830
2884
  if (!event.success) {
2831
2885
  this.showError(`Retry failed after ${event.attempt} attempts: ${event.finalError || "Unknown error"}`);
2886
+ this.suggestBugReport();
2832
2887
  }
2833
2888
  this.ui.requestRender();
2834
2889
  break;
@@ -2955,6 +3010,8 @@ export class InteractiveMode {
2955
3010
  this.chatContainer.addChild(component);
2956
3011
  break;
2957
3012
  }
3013
+ case "system":
3014
+ break;
2958
3015
  case "user": {
2959
3016
  const textContent = this.getUserMessageText(message);
2960
3017
  if (textContent) {
@@ -3001,8 +3058,8 @@ export class InteractiveMode {
3001
3058
  renderSessionItems(items, options = {}) {
3002
3059
  this.pendingTools.clear();
3003
3060
  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.
3061
+ // Cache misses are not persisted, unlike successful cache-warming usage.
3062
+ // Re-derive them and inject them after the assistant messages that paid for them.
3006
3063
  const cacheMisses = this.settingsManager.getShowCacheMissNotices()
3007
3064
  ? collectCacheMisses(this.sessionManager.getEntries(), this.session.modelRuntime)
3008
3065
  : new Map();
@@ -3015,6 +3072,10 @@ export class InteractiveMode {
3015
3072
  this.addCustomEntryToChat(item);
3016
3073
  continue;
3017
3074
  }
3075
+ if (isUsageSessionEntry(item)) {
3076
+ this.addCacheWarmingUsage(item);
3077
+ continue;
3078
+ }
3018
3079
  if (isCompactionCostNotice(item)) {
3019
3080
  this.addCompactionCostNotice(item);
3020
3081
  continue;
@@ -3052,7 +3113,6 @@ export class InteractiveMode {
3052
3113
  }
3053
3114
  }
3054
3115
  if (message.stopReason !== "aborted" && message.stopReason !== "error") {
3055
- this.maybeShowAssistantDiagnostics(message);
3056
3116
  const miss = cacheMisses.get(message);
3057
3117
  if (miss)
3058
3118
  this.addCacheMissNotice(miss);
@@ -3084,7 +3144,7 @@ export class InteractiveMode {
3084
3144
  */
3085
3145
  renderSessionEntries(entries, options = {}) {
3086
3146
  const items = entries.flatMap((entry) => {
3087
- if (entry.type === "custom") {
3147
+ if (entry.type === "custom" || (entry.type === "usage" && entry.kind === "cache_warm")) {
3088
3148
  return [entry];
3089
3149
  }
3090
3150
  const messages = sessionEntryToContextMessages(entry);
@@ -3095,6 +3155,12 @@ export class InteractiveMode {
3095
3155
  });
3096
3156
  this.renderSessionItems(items, options);
3097
3157
  }
3158
+ addCacheWarmingUsage(entry) {
3159
+ if (!this.settingsManager.getShowCacheMissNotices())
3160
+ return;
3161
+ this.chatContainer.addChild(new Spacer(1));
3162
+ this.chatContainer.addChild(new Text(theme.fg("dim", formatCacheWarmingUsage(entry)), 1, 0));
3163
+ }
3098
3164
  /**
3099
3165
  * Render billing usage for a compaction or branch summary. The notice is derived
3100
3166
  * from persisted summary usage and is not stored as a separate session entry.
@@ -3109,31 +3175,42 @@ export class InteractiveMode {
3109
3175
  this.chatContainer.addChild(new Spacer(1));
3110
3176
  this.chatContainer.addChild(new Text(theme.fg("warning", `${label}: ${formatTokens(tokens)} tokens billed${cost}`), 1, 0));
3111
3177
  }
3112
- maybeShowAssistantDiagnostics(message) {
3113
- if (!this.settingsManager.getShowCacheMissNotices())
3114
- return;
3178
+ static countDroppedThinkingBlocks(message) {
3179
+ let count = 0;
3115
3180
  for (const diagnostic of message.diagnostics ?? []) {
3116
3181
  if (diagnostic.type !== "anthropic_input_transformations")
3117
3182
  continue;
3118
3183
  const transformations = diagnostic.details?.transformations;
3119
3184
  if (!Array.isArray(transformations))
3120
3185
  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));
3186
+ count += transformations.filter((transformation) => typeof transformation === "object" &&
3187
+ transformation !== null &&
3188
+ transformation.type === "thinking_dropped").length;
3189
+ }
3190
+ return count;
3191
+ }
3192
+ maybeShowThinkingDropNotice(message) {
3193
+ if (!this.settingsManager.getShowCacheMissNotices())
3194
+ return;
3195
+ const droppedCount = InteractiveMode.countDroppedThinkingBlocks(message);
3196
+ if (droppedCount === 0)
3197
+ return;
3198
+ let previousDroppedCount = 0;
3199
+ // message_end reaches the UI before the current message is persisted,
3200
+ // so the branch's last assistant message is the previous response.
3201
+ const branch = this.sessionManager.getBranch();
3202
+ for (let i = branch.length - 1; i >= 0; i--) {
3203
+ const entry = branch[i];
3204
+ if (entry.type === "message" && entry.message.role === "assistant") {
3205
+ previousDroppedCount = InteractiveMode.countDroppedThinkingBlocks(entry.message);
3206
+ break;
3207
+ }
3136
3208
  }
3209
+ if (droppedCount <= previousDroppedCount)
3210
+ return;
3211
+ const noun = droppedCount === 1 ? "thinking block" : "thinking blocks";
3212
+ this.chatContainer.addChild(new Spacer(1));
3213
+ this.chatContainer.addChild(new Text(theme.fg("warning", `Anthropic dropped ${droppedCount} ${noun} (details in session)`), 1, 0));
3137
3214
  }
3138
3215
  /**
3139
3216
  * Show a transcript notice when a completed assistant message paid for a
@@ -3301,6 +3378,9 @@ export class InteractiveMode {
3301
3378
  catch { }
3302
3379
  console.error(`${APP_NAME} exiting due to uncaughtException:`);
3303
3380
  console.error(error);
3381
+ if (this.recordCrash("uncaught_exception", error)) {
3382
+ console.error(`\n${this.crashReportInstructions()}`);
3383
+ }
3304
3384
  process.exit(1);
3305
3385
  }
3306
3386
  /**
@@ -3432,9 +3512,7 @@ export class InteractiveMode {
3432
3512
  const level = this.session.thinkingLevel || "off";
3433
3513
  this.editor.borderColor = theme.getThinkingBorderColor(level);
3434
3514
  }
3435
- if (this.activeStatusIndicator?.kind === "working") {
3436
- this.activeStatusIndicator.invalidate();
3437
- }
3515
+ this.activeStatusIndicator?.invalidate();
3438
3516
  this.ui.requestRender();
3439
3517
  }
3440
3518
  cycleThinkingLevel() {
@@ -3628,7 +3706,7 @@ export class InteractiveMode {
3628
3706
  if (allQueued.length === 0) {
3629
3707
  this.updatePendingMessagesDisplay();
3630
3708
  if (options?.abort) {
3631
- this.agent.abort();
3709
+ void this.session.abort();
3632
3710
  }
3633
3711
  return 0;
3634
3712
  }
@@ -3638,7 +3716,7 @@ export class InteractiveMode {
3638
3716
  this.editor.setText(combinedText);
3639
3717
  this.updatePendingMessagesDisplay();
3640
3718
  if (options?.abort) {
3641
- this.agent.abort();
3719
+ void this.session.abort();
3642
3720
  }
3643
3721
  return allQueued.length;
3644
3722
  }
@@ -3792,6 +3870,7 @@ export class InteractiveMode {
3792
3870
  followUpMode: this.session.followUpMode,
3793
3871
  transport: this.settingsManager.getTransport(),
3794
3872
  httpIdleTimeoutMs: this.settingsManager.getHttpIdleTimeoutMs(),
3873
+ cacheWarmingMode: this.settingsManager.getCacheWarmingMode(),
3795
3874
  thinkingLevel: this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL,
3796
3875
  availableThinkingLevels: [...THINKING_LEVEL_OPTIONS],
3797
3876
  modelThinkingLevels: this.settingsManager.getAllModelThinkingLevels(),
@@ -3864,6 +3943,10 @@ export class InteractiveMode {
3864
3943
  configureHttpDispatcher(timeoutMs);
3865
3944
  this.showStatus(`HTTP idle timeout: ${formatHttpIdleTimeoutMs(timeoutMs)}`);
3866
3945
  },
3946
+ onCacheWarmingModeChange: (mode) => {
3947
+ this.session.setCacheWarmingMode(mode);
3948
+ this.showStatus(`Cache warming: ${mode}`);
3949
+ },
3867
3950
  onModelThinkingLevelChange: (provider, modelId, level) => {
3868
3951
  this.settingsManager.setModelThinkingLevel(provider, modelId, level);
3869
3952
  // If the override is for the current model, apply it to the session too
@@ -4415,6 +4498,11 @@ export class InteractiveMode {
4415
4498
  this.restoreQueuedMessagesToEditor();
4416
4499
  await this.session.abort();
4417
4500
  }
4501
+ // Recheck after the dialogs and streaming abort, before replacing another operation's UI.
4502
+ if (this.session.isCompacting) {
4503
+ this.showError("Wait for the current compaction or tree navigation to finish before navigating the session tree.");
4504
+ return;
4505
+ }
4418
4506
  // Set up escape handler and status indicator if summarizing
4419
4507
  let showingSummaryIndicator = false;
4420
4508
  const originalOnEscape = this.defaultEditor.onEscape;
@@ -4485,9 +4573,9 @@ export class InteractiveMode {
4485
4573
  }
4486
4574
  showSessionSelector() {
4487
4575
  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) => {
4576
+ const selector = new SessionSelectorComponent((onProgress, signal) => SessionManager.list(this.sessionManager.getCwd(), this.sessionManager.getSessionDir(), onProgress, signal), (onProgress, signal) => this.sessionManager.usesDefaultSessionDir()
4577
+ ? SessionManager.listAll(onProgress, signal)
4578
+ : SessionManager.listAll(this.sessionManager.getSessionDir(), onProgress, signal), async (sessionPath) => {
4491
4579
  done();
4492
4580
  await this.handleResumeSession(sessionPath);
4493
4581
  }, () => {
@@ -4750,67 +4838,89 @@ export class InteractiveMode {
4750
4838
  }
4751
4839
  async completeProviderAuthentication(providerId, providerName, authType, previousModel) {
4752
4840
  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.`;
4841
+ const session = this.session;
4842
+ // Dynamic catalogs may be empty until the first authenticated network refresh.
4843
+ const deferSelection = isUnknownModel(previousModel) &&
4844
+ hasDefaultModelProvider(providerId) &&
4845
+ !session.modelRuntime
4846
+ .getAvailableSnapshot()
4847
+ .some((model) => model.provider === providerId && model.id === defaultModelPerProvider[providerId]);
4848
+ const finishAuthentication = async () => {
4849
+ let selectedModel;
4850
+ let selectionError;
4851
+ if (isUnknownModel(previousModel)) {
4852
+ const availableModels = this.session.modelRuntime.getAvailableSnapshot();
4853
+ const providerModels = availableModels.filter((model) => model.provider === providerId);
4854
+ // Matches LLAMA_PROVIDER_ID from extensions/llama/provider.ts; kept inline to avoid coupling interactive mode to the built-in extension.
4855
+ if (providerId === "llama.cpp") {
4856
+ selectionError = llamaCppPostLoginGuidance(actionLabel, providerModels.length);
4857
+ }
4858
+ else if (!hasDefaultModelProvider(providerId)) {
4859
+ selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
4860
+ }
4861
+ else if (providerModels.length === 0) {
4862
+ selectionError = `${actionLabel}, but no models are available for that provider. Use /model to select a model.`;
4773
4863
  }
4774
4864
  else {
4775
- try {
4776
- await this.session.setModel(selectedModel, { persist: true });
4865
+ const defaultModelId = defaultModelPerProvider[providerId];
4866
+ // Radius catalogs vary by account; prefer balanced, then use catalog order.
4867
+ selectedModel =
4868
+ providerModels.find((model) => model.id === defaultModelId) ??
4869
+ (providerId === "radius" ? providerModels[0] : undefined);
4870
+ if (!selectedModel) {
4871
+ selectionError = `${actionLabel}, but its default model "${defaultModelId}" is not available. Use /model to select a model.`;
4777
4872
  }
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.`;
4873
+ else {
4874
+ try {
4875
+ await this.session.setModel(selectedModel, { persist: true });
4876
+ }
4877
+ catch (error) {
4878
+ selectedModel = undefined;
4879
+ const errorMessage = error instanceof Error ? error.message : String(error);
4880
+ selectionError = `${actionLabel}, but selecting its default model failed: ${errorMessage}. Use /model to select a model.`;
4881
+ }
4782
4882
  }
4783
4883
  }
4784
4884
  }
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);
4885
+ await this.updateAvailableProviderCount();
4886
+ this.footer.invalidate();
4887
+ this.updateEditorBorderColor();
4888
+ if (selectedModel) {
4889
+ this.showStatus(`${actionLabel}. Selected ${selectedModel.id}. Credentials saved to ${getAuthPath()}`);
4890
+ void this.maybeWarnAboutAnthropicSubscriptionAuth(selectedModel);
4891
+ this.checkDaxnutsEasterEgg(selectedModel);
4798
4892
  }
4799
4893
  else {
4800
- void this.maybeWarnAboutAnthropicSubscriptionAuth();
4894
+ this.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}`);
4895
+ if (selectionError) {
4896
+ this.showError(selectionError);
4897
+ }
4898
+ else {
4899
+ void this.maybeWarnAboutAnthropicSubscriptionAuth();
4900
+ }
4801
4901
  }
4902
+ };
4903
+ if (deferSelection) {
4904
+ this.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}. Refreshing model catalog…`);
4905
+ }
4906
+ else {
4907
+ await finishAuthentication();
4802
4908
  }
4803
4909
  const controller = new AbortController();
4804
4910
  const timeout = setTimeout(() => controller.abort(), 15_000);
4805
- void this.session.modelRuntime
4911
+ void session.modelRuntime
4806
4912
  .refresh({ providers: [providerId], signal: controller.signal })
4807
- .then((result) => {
4913
+ .then(async (result) => {
4808
4914
  if (result.aborted) {
4809
4915
  this.showWarning(`${actionLabel}, but its model catalog refresh timed out; using cached models.`);
4810
4916
  }
4811
4917
  else if (result.errors.size > 0) {
4812
4918
  this.showWarning(`${actionLabel}, but its model catalog could not be refreshed; using cached models.`);
4813
4919
  }
4920
+ // Do not replace a model or session selected while the refresh was running.
4921
+ if (deferSelection && this.session === session && session.model === previousModel) {
4922
+ await finishAuthentication();
4923
+ }
4814
4924
  this.updateAvailableProviderCount();
4815
4925
  this.footer.invalidate();
4816
4926
  this.ui.requestRender();
@@ -5152,6 +5262,16 @@ export class InteractiveMode {
5152
5262
  showError: (message) => this.showError(message),
5153
5263
  });
5154
5264
  }
5265
+ async handleBugCommand(hint) {
5266
+ await reportBug({
5267
+ session: this.session,
5268
+ ui: this.ui,
5269
+ editorContainer: this.editorContainer,
5270
+ editor: this.editor,
5271
+ showStatus: (message) => this.showStatus(message),
5272
+ showError: (message) => this.showError(message),
5273
+ }, hint);
5274
+ }
5155
5275
  async handleCopyCommand(options = {}) {
5156
5276
  if (options.preferSelection &&
5157
5277
  this.ui instanceof TuiAltScreen &&
@@ -5237,6 +5357,15 @@ export class InteractiveMode {
5237
5357
  }
5238
5358
  info += `${theme.fg("dim", "Output:")} ${stats.tokens.output.toLocaleString()}\n`;
5239
5359
  info += `${theme.fg("dim", "Total:")} ${stats.tokens.total.toLocaleString()}\n`;
5360
+ const cacheWarmingStatus = this.session.cacheWarmingStatus;
5361
+ info += `\n${theme.bold("Cache Warming")}\n`;
5362
+ info += `${theme.fg("dim", "Mode:")} ${this.settingsManager.getCacheWarmingMode()}\n`;
5363
+ info += `${theme.fg("dim", "Status:")} ${cacheWarmingStatus ? formatCacheWarmingStatus(cacheWarmingStatus) : "Inactive (cache warming unavailable)"}\n`;
5364
+ const decision = cacheWarmingStatus?.decision;
5365
+ if (decision?.economicsAvailable) {
5366
+ info += `${theme.fg("dim", "Cache miss penalty:")} $${decision.missCost.toFixed(3)}\n`;
5367
+ info += `${theme.fg("dim", "Refresh cost:")} $${decision.warmCost.toFixed(3)}\n`;
5368
+ }
5240
5369
  if (stats.cost > 0 || cacheWaste.missedTokens > 0) {
5241
5370
  info += `\n${theme.bold("Cost")}\n`;
5242
5371
  info += `${theme.fg("dim", "Total:")} $${stats.cost.toFixed(3)}`;
@@ -5367,7 +5496,7 @@ export class InteractiveMode {
5367
5496
  | \`${expandTools}\` | Toggle tool output expansion |
5368
5497
  | \`${toggleThinking}\` | Toggle thinking block visibility |
5369
5498
  | \`${externalEditor}\` | Edit message in external editor |
5370
- | \`${copyMessage}\` | Copy last assistant message |
5499
+ | \`${copyMessage}\` | Copy selection or last assistant message |
5371
5500
  | \`${followUp}\` | Queue follow-up message |
5372
5501
  | \`${dequeue}\` | Restore queued messages |
5373
5502
  | \`${pasteImage}\` | Paste image or text from clipboard |
@@ -5463,12 +5592,19 @@ export class InteractiveMode {
5463
5592
  async handleBashCommand(command, excludeFromContext = false) {
5464
5593
  const extensionRunner = this.session.extensionRunner;
5465
5594
  // 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
- });
5595
+ let eventResult;
5596
+ try {
5597
+ eventResult = await extensionRunner.emitUserBash({
5598
+ type: "user_bash",
5599
+ command,
5600
+ excludeFromContext,
5601
+ cwd: this.sessionManager.getCwd(),
5602
+ });
5603
+ }
5604
+ catch {
5605
+ // The extension runner already reported the error. Do not fall back to local execution.
5606
+ return;
5607
+ }
5472
5608
  // If extension returned a full result, use it directly
5473
5609
  if (eventResult?.result) {
5474
5610
  const result = eventResult.result;