apex-code 0.4.0 → 0.5.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 (573) hide show
  1. package/CHANGELOG.md +280 -6
  2. package/README.md +624 -0
  3. package/dist/bun/cli.d.ts +3 -1
  4. package/dist/bun/cli.d.ts.map +1 -1
  5. package/dist/bun/cli.js +3 -9
  6. package/dist/bun/cli.js.map +1 -1
  7. package/dist/bun/runtime-setup.d.ts +2 -0
  8. package/dist/bun/runtime-setup.d.ts.map +1 -0
  9. package/dist/bun/runtime-setup.js +9 -0
  10. package/dist/bun/runtime-setup.js.map +1 -0
  11. package/dist/bun/sandbox-env-setup.d.ts +2 -0
  12. package/dist/bun/sandbox-env-setup.d.ts.map +1 -0
  13. package/dist/bun/sandbox-env-setup.js +4 -0
  14. package/dist/bun/sandbox-env-setup.js.map +1 -0
  15. package/dist/cli/args.d.ts.map +1 -1
  16. package/dist/cli/args.js +16 -5
  17. package/dist/cli/args.js.map +1 -1
  18. package/dist/cli/config-selector.d.ts.map +1 -1
  19. package/dist/cli/config-selector.js +2 -1
  20. package/dist/cli/config-selector.js.map +1 -1
  21. package/dist/cli/file-processor.d.ts +1 -1
  22. package/dist/cli/file-processor.d.ts.map +1 -1
  23. package/dist/cli/file-processor.js.map +1 -1
  24. package/dist/cli/session-picker.d.ts +1 -1
  25. package/dist/cli/session-picker.d.ts.map +1 -1
  26. package/dist/cli/session-picker.js.map +1 -1
  27. package/dist/cli/setup.d.ts +2 -0
  28. package/dist/cli/setup.d.ts.map +1 -0
  29. package/dist/cli/setup.js +13 -0
  30. package/dist/cli/setup.js.map +1 -0
  31. package/dist/config.d.ts +1 -0
  32. package/dist/config.d.ts.map +1 -1
  33. package/dist/config.js +1 -0
  34. package/dist/config.js.map +1 -1
  35. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  36. package/dist/core/agent-session-runtime.js +16 -8
  37. package/dist/core/agent-session-runtime.js.map +1 -1
  38. package/dist/core/agent-session.d.ts +85 -15
  39. package/dist/core/agent-session.d.ts.map +1 -1
  40. package/dist/core/agent-session.js +685 -266
  41. package/dist/core/agent-session.js.map +1 -1
  42. package/dist/core/auth-storage.d.ts +4 -0
  43. package/dist/core/auth-storage.d.ts.map +1 -1
  44. package/dist/core/auth-storage.js +55 -0
  45. package/dist/core/auth-storage.js.map +1 -1
  46. package/dist/core/bug-report.d.ts +176 -0
  47. package/dist/core/bug-report.d.ts.map +1 -0
  48. package/dist/core/bug-report.js +291 -0
  49. package/dist/core/bug-report.js.map +1 -0
  50. package/dist/core/cache-stats.d.ts.map +1 -1
  51. package/dist/core/cache-stats.js +12 -1
  52. package/dist/core/cache-stats.js.map +1 -1
  53. package/dist/core/cache-warmer.d.ts +103 -0
  54. package/dist/core/cache-warmer.d.ts.map +1 -0
  55. package/dist/core/cache-warmer.js +356 -0
  56. package/dist/core/cache-warmer.js.map +1 -0
  57. package/dist/core/compaction/branch-summarization.d.ts +1 -1
  58. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  59. package/dist/core/compaction/branch-summarization.js +4 -3
  60. package/dist/core/compaction/branch-summarization.js.map +1 -1
  61. package/dist/core/compaction/compaction.d.ts +5 -3
  62. package/dist/core/compaction/compaction.d.ts.map +1 -1
  63. package/dist/core/compaction/compaction.js +163 -66
  64. package/dist/core/compaction/compaction.js.map +1 -1
  65. package/dist/core/context/pipeline.d.ts +8 -8
  66. package/dist/core/context/pipeline.d.ts.map +1 -1
  67. package/dist/core/context/pipeline.js +21 -12
  68. package/dist/core/context/pipeline.js.map +1 -1
  69. package/dist/core/crash-log.d.ts +27 -0
  70. package/dist/core/crash-log.d.ts.map +1 -0
  71. package/dist/core/crash-log.js +139 -0
  72. package/dist/core/crash-log.js.map +1 -0
  73. package/dist/core/export-html/template.js +6 -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 +40 -59
  87. package/dist/core/extensions/loader.js.map +1 -1
  88. package/dist/core/extensions/runner.d.ts +24 -8
  89. package/dist/core/extensions/runner.d.ts.map +1 -1
  90. package/dist/core/extensions/runner.js +195 -72
  91. package/dist/core/extensions/runner.js.map +1 -1
  92. package/dist/core/extensions/types.d.ts +142 -55
  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 +41 -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/http-dispatcher.d.ts.map +1 -1
  103. package/dist/core/http-dispatcher.js +2 -0
  104. package/dist/core/http-dispatcher.js.map +1 -1
  105. package/dist/core/index.d.ts +2 -1
  106. package/dist/core/index.d.ts.map +1 -1
  107. package/dist/core/index.js.map +1 -1
  108. package/dist/core/keybindings.d.ts +6 -1
  109. package/dist/core/keybindings.d.ts.map +1 -1
  110. package/dist/core/keybindings.js +5 -1
  111. package/dist/core/keybindings.js.map +1 -1
  112. package/dist/core/messages.d.ts +1 -1
  113. package/dist/core/messages.d.ts.map +1 -1
  114. package/dist/core/messages.js +1 -0
  115. package/dist/core/messages.js.map +1 -1
  116. package/dist/core/model-config.d.ts +168 -20
  117. package/dist/core/model-config.d.ts.map +1 -1
  118. package/dist/core/model-config.js +44 -18
  119. package/dist/core/model-config.js.map +1 -1
  120. package/dist/core/model-registry.d.ts +5 -1
  121. package/dist/core/model-registry.d.ts.map +1 -1
  122. package/dist/core/model-registry.js +8 -0
  123. package/dist/core/model-registry.js.map +1 -1
  124. package/dist/core/model-resolver.d.ts.map +1 -1
  125. package/dist/core/model-resolver.js +4 -3
  126. package/dist/core/model-resolver.js.map +1 -1
  127. package/dist/core/model-runtime.d.ts +1 -0
  128. package/dist/core/model-runtime.d.ts.map +1 -1
  129. package/dist/core/model-runtime.js +12 -6
  130. package/dist/core/model-runtime.js.map +1 -1
  131. package/dist/core/permissions/store.d.ts.map +1 -1
  132. package/dist/core/permissions/store.js +11 -8
  133. package/dist/core/permissions/store.js.map +1 -1
  134. package/dist/core/prompt-templates.d.ts +6 -1
  135. package/dist/core/prompt-templates.d.ts.map +1 -1
  136. package/dist/core/prompt-templates.js +61 -35
  137. package/dist/core/prompt-templates.js.map +1 -1
  138. package/dist/core/provider-composer.d.ts +4 -2
  139. package/dist/core/provider-composer.d.ts.map +1 -1
  140. package/dist/core/provider-composer.js +29 -2
  141. package/dist/core/provider-composer.js.map +1 -1
  142. package/dist/core/radius.d.ts +3 -0
  143. package/dist/core/radius.d.ts.map +1 -1
  144. package/dist/core/radius.js +6 -0
  145. package/dist/core/radius.js.map +1 -1
  146. package/dist/core/resource-loader.d.ts.map +1 -1
  147. package/dist/core/resource-loader.js +6 -2
  148. package/dist/core/resource-loader.js.map +1 -1
  149. package/dist/core/sdk.d.ts.map +1 -1
  150. package/dist/core/sdk.js +63 -42
  151. package/dist/core/sdk.js.map +1 -1
  152. package/dist/core/session-export.d.ts +6 -2
  153. package/dist/core/session-export.d.ts.map +1 -1
  154. package/dist/core/session-export.js +14 -13
  155. package/dist/core/session-export.js.map +1 -1
  156. package/dist/core/session-manager.d.ts +66 -15
  157. package/dist/core/session-manager.d.ts.map +1 -1
  158. package/dist/core/session-manager.js +295 -115
  159. package/dist/core/session-manager.js.map +1 -1
  160. package/dist/core/settings-manager.d.ts +27 -4
  161. package/dist/core/settings-manager.d.ts.map +1 -1
  162. package/dist/core/settings-manager.js +56 -8
  163. package/dist/core/settings-manager.js.map +1 -1
  164. package/dist/core/skills.d.ts +1 -1
  165. package/dist/core/skills.d.ts.map +1 -1
  166. package/dist/core/skills.js +5 -4
  167. package/dist/core/skills.js.map +1 -1
  168. package/dist/core/slash-commands.d.ts.map +1 -1
  169. package/dist/core/slash-commands.js +1 -0
  170. package/dist/core/slash-commands.js.map +1 -1
  171. package/dist/core/system-prompt.d.ts +49 -6
  172. package/dist/core/system-prompt.d.ts.map +1 -1
  173. package/dist/core/system-prompt.js +140 -79
  174. package/dist/core/system-prompt.js.map +1 -1
  175. package/dist/core/tools/bash.d.ts +9 -15
  176. package/dist/core/tools/bash.d.ts.map +1 -1
  177. package/dist/core/tools/bash.js +20 -137
  178. package/dist/core/tools/bash.js.map +1 -1
  179. package/dist/core/tools/delegate.d.ts.map +1 -1
  180. package/dist/core/tools/delegate.js +2 -2
  181. package/dist/core/tools/delegate.js.map +1 -1
  182. package/dist/core/tools/edit.d.ts +1 -12
  183. package/dist/core/tools/edit.d.ts.map +1 -1
  184. package/dist/core/tools/edit.js +6 -204
  185. package/dist/core/tools/edit.js.map +1 -1
  186. package/dist/core/tools/find.d.ts.map +1 -1
  187. package/dist/core/tools/find.js +4 -55
  188. package/dist/core/tools/find.js.map +1 -1
  189. package/dist/core/tools/grep.d.ts.map +1 -1
  190. package/dist/core/tools/grep.js +4 -60
  191. package/dist/core/tools/grep.js.map +1 -1
  192. package/dist/core/tools/ls.d.ts.map +1 -1
  193. package/dist/core/tools/ls.js +4 -49
  194. package/dist/core/tools/ls.js.map +1 -1
  195. package/dist/core/tools/read.d.ts +4 -1
  196. package/dist/core/tools/read.d.ts.map +1 -1
  197. package/dist/core/tools/read.js +10 -122
  198. package/dist/core/tools/read.js.map +1 -1
  199. package/dist/core/tools/renderers/bash.d.ts +18 -0
  200. package/dist/core/tools/renderers/bash.d.ts.map +1 -0
  201. package/dist/core/tools/renderers/bash.js +126 -0
  202. package/dist/core/tools/renderers/bash.js.map +1 -0
  203. package/dist/core/tools/renderers/edit.d.ts +23 -0
  204. package/dist/core/tools/renderers/edit.d.ts.map +1 -0
  205. package/dist/core/tools/renderers/edit.js +207 -0
  206. package/dist/core/tools/renderers/edit.js.map +1 -0
  207. package/dist/core/tools/renderers/find.d.ts +10 -0
  208. package/dist/core/tools/renderers/find.d.ts.map +1 -0
  209. package/dist/core/tools/renderers/find.js +64 -0
  210. package/dist/core/tools/renderers/find.js.map +1 -0
  211. package/dist/core/tools/renderers/grep.d.ts +10 -0
  212. package/dist/core/tools/renderers/grep.d.ts.map +1 -0
  213. package/dist/core/tools/renderers/grep.js +69 -0
  214. package/dist/core/tools/renderers/grep.js.map +1 -0
  215. package/dist/core/tools/renderers/index.d.ts +34 -0
  216. package/dist/core/tools/renderers/index.d.ts.map +1 -0
  217. package/dist/core/tools/renderers/index.js +47 -0
  218. package/dist/core/tools/renderers/index.js.map +1 -0
  219. package/dist/core/tools/renderers/ls.d.ts +10 -0
  220. package/dist/core/tools/renderers/ls.d.ts.map +1 -0
  221. package/dist/core/tools/renderers/ls.js +58 -0
  222. package/dist/core/tools/renderers/ls.js.map +1 -0
  223. package/dist/core/tools/renderers/read.d.ts +11 -0
  224. package/dist/core/tools/renderers/read.d.ts.map +1 -0
  225. package/dist/core/tools/renderers/read.js +132 -0
  226. package/dist/core/tools/renderers/read.js.map +1 -0
  227. package/dist/core/tools/renderers/write.d.ts +10 -0
  228. package/dist/core/tools/renderers/write.d.ts.map +1 -0
  229. package/dist/core/tools/renderers/write.js +152 -0
  230. package/dist/core/tools/renderers/write.js.map +1 -0
  231. package/dist/core/tools/write.d.ts.map +1 -1
  232. package/dist/core/tools/write.js +6 -147
  233. package/dist/core/tools/write.js.map +1 -1
  234. package/dist/core/usage-totals.d.ts +1 -1
  235. package/dist/core/usage-totals.d.ts.map +1 -1
  236. package/dist/core/usage-totals.js +5 -1
  237. package/dist/core/usage-totals.js.map +1 -1
  238. package/dist/extensions/llama/client.d.ts +2 -0
  239. package/dist/extensions/llama/client.d.ts.map +1 -1
  240. package/dist/extensions/llama/client.js +7 -3
  241. package/dist/extensions/llama/client.js.map +1 -1
  242. package/dist/extensions/llama/provider.d.ts.map +1 -1
  243. package/dist/extensions/llama/provider.js +20 -5
  244. package/dist/extensions/llama/provider.js.map +1 -1
  245. package/dist/extensions/llama/ui.d.ts.map +1 -1
  246. package/dist/extensions/llama/ui.js +3 -2
  247. package/dist/extensions/llama/ui.js.map +1 -1
  248. package/dist/index.d.ts +5 -4
  249. package/dist/index.d.ts.map +1 -1
  250. package/dist/index.js +1 -1
  251. package/dist/index.js.map +1 -1
  252. package/dist/main.d.ts.map +1 -1
  253. package/dist/main.js +21 -13
  254. package/dist/main.js.map +1 -1
  255. package/dist/modes/interactive/bug-report.d.ts +16 -0
  256. package/dist/modes/interactive/bug-report.d.ts.map +1 -0
  257. package/dist/modes/interactive/bug-report.js +167 -0
  258. package/dist/modes/interactive/bug-report.js.map +1 -0
  259. package/dist/modes/interactive/chat-viewport.d.ts +20 -0
  260. package/dist/modes/interactive/chat-viewport.d.ts.map +1 -0
  261. package/dist/modes/interactive/chat-viewport.js +28 -0
  262. package/dist/modes/interactive/chat-viewport.js.map +1 -0
  263. package/dist/modes/interactive/components/assistant-message.d.ts +7 -0
  264. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  265. package/dist/modes/interactive/components/assistant-message.js +63 -27
  266. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  267. package/dist/modes/interactive/components/autocomplete-source.d.ts +8 -0
  268. package/dist/modes/interactive/components/autocomplete-source.d.ts.map +1 -0
  269. package/dist/modes/interactive/components/autocomplete-source.js +23 -0
  270. package/dist/modes/interactive/components/autocomplete-source.js.map +1 -0
  271. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  272. package/dist/modes/interactive/components/bash-execution.js +6 -4
  273. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  274. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
  275. package/dist/modes/interactive/components/bordered-loader.js +1 -1
  276. package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
  277. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  278. package/dist/modes/interactive/components/branch-summary-message.js +12 -5
  279. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  280. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  281. package/dist/modes/interactive/components/compaction-summary-message.js +12 -5
  282. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  283. package/dist/modes/interactive/components/composer-dock.d.ts +12 -0
  284. package/dist/modes/interactive/components/composer-dock.d.ts.map +1 -0
  285. package/dist/modes/interactive/components/composer-dock.js +24 -0
  286. package/dist/modes/interactive/components/composer-dock.js.map +1 -0
  287. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  288. package/dist/modes/interactive/components/config-selector.js +11 -9
  289. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  290. package/dist/modes/interactive/components/custom-editor.d.ts +12 -0
  291. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  292. package/dist/modes/interactive/components/custom-editor.js +38 -0
  293. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  294. package/dist/modes/interactive/components/error-summary.d.ts +11 -0
  295. package/dist/modes/interactive/components/error-summary.d.ts.map +1 -0
  296. package/dist/modes/interactive/components/error-summary.js +31 -0
  297. package/dist/modes/interactive/components/error-summary.js.map +1 -0
  298. package/dist/modes/interactive/components/extension-editor.d.ts +4 -1
  299. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  300. package/dist/modes/interactive/components/extension-editor.js +15 -10
  301. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  302. package/dist/modes/interactive/components/extension-input.d.ts +2 -0
  303. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  304. package/dist/modes/interactive/components/extension-input.js +15 -5
  305. package/dist/modes/interactive/components/extension-input.js.map +1 -1
  306. package/dist/modes/interactive/components/extension-selector.d.ts +2 -0
  307. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  308. package/dist/modes/interactive/components/extension-selector.js +15 -10
  309. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  310. package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
  311. package/dist/modes/interactive/components/first-time-setup.js +6 -6
  312. package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
  313. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  314. package/dist/modes/interactive/components/footer.js +12 -4
  315. package/dist/modes/interactive/components/footer.js.map +1 -1
  316. package/dist/modes/interactive/components/index.d.ts +1 -1
  317. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  318. package/dist/modes/interactive/components/index.js.map +1 -1
  319. package/dist/modes/interactive/components/keybinding-hints.d.ts +21 -0
  320. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
  321. package/dist/modes/interactive/components/keybinding-hints.js +34 -0
  322. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
  323. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  324. package/dist/modes/interactive/components/login-dialog.js +27 -23
  325. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  326. package/dist/modes/interactive/components/model-selector.d.ts +1 -1
  327. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  328. package/dist/modes/interactive/components/model-selector.js +16 -20
  329. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  330. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  331. package/dist/modes/interactive/components/oauth-selector.js +7 -6
  332. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  333. package/dist/modes/interactive/components/permission-preview.d.ts.map +1 -1
  334. package/dist/modes/interactive/components/permission-preview.js +3 -3
  335. package/dist/modes/interactive/components/permission-preview.js.map +1 -1
  336. package/dist/modes/interactive/components/prompt-input.d.ts +12 -0
  337. package/dist/modes/interactive/components/prompt-input.d.ts.map +1 -0
  338. package/dist/modes/interactive/components/prompt-input.js +18 -0
  339. package/dist/modes/interactive/components/prompt-input.js.map +1 -0
  340. package/dist/modes/interactive/components/scoped-models-selector.d.ts +1 -1
  341. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  342. package/dist/modes/interactive/components/scoped-models-selector.js +17 -18
  343. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  344. package/dist/modes/interactive/components/session-selector.d.ts +5 -5
  345. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  346. package/dist/modes/interactive/components/session-selector.js +100 -67
  347. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  348. package/dist/modes/interactive/components/settings-selector.d.ts +5 -1
  349. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  350. package/dist/modes/interactive/components/settings-selector.js +74 -16
  351. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  352. package/dist/modes/interactive/components/settings-submenu.d.ts.map +1 -1
  353. package/dist/modes/interactive/components/settings-submenu.js +8 -6
  354. package/dist/modes/interactive/components/settings-submenu.js.map +1 -1
  355. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  356. package/dist/modes/interactive/components/skill-invocation-message.js +11 -4
  357. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  358. package/dist/modes/interactive/components/status-indicator.d.ts +3 -1
  359. package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -1
  360. package/dist/modes/interactive/components/status-indicator.js +10 -3
  361. package/dist/modes/interactive/components/status-indicator.js.map +1 -1
  362. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  363. package/dist/modes/interactive/components/thinking-selector.js +14 -9
  364. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  365. package/dist/modes/interactive/components/tool-execution.d.ts +28 -16
  366. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  367. package/dist/modes/interactive/components/tool-execution.js +73 -80
  368. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  369. package/dist/modes/interactive/components/tool-panel.d.ts.map +1 -1
  370. package/dist/modes/interactive/components/tool-panel.js +7 -2
  371. package/dist/modes/interactive/components/tool-panel.js.map +1 -1
  372. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  373. package/dist/modes/interactive/components/tree-selector.js +18 -5
  374. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  375. package/dist/modes/interactive/components/trust-selector.d.ts.map +1 -1
  376. package/dist/modes/interactive/components/trust-selector.js +12 -12
  377. package/dist/modes/interactive/components/trust-selector.js.map +1 -1
  378. package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
  379. package/dist/modes/interactive/components/user-message-selector.js +2 -2
  380. package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
  381. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  382. package/dist/modes/interactive/components/user-message.js +6 -3
  383. package/dist/modes/interactive/components/user-message.js.map +1 -1
  384. package/dist/modes/interactive/interactive-mode.d.ts +50 -31
  385. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  386. package/dist/modes/interactive/interactive-mode.js +494 -229
  387. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  388. package/dist/modes/interactive/theme/dark.json +2 -1
  389. package/dist/modes/interactive/theme/light.json +2 -1
  390. package/dist/modes/interactive/theme/theme-controller.d.ts +1 -0
  391. package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
  392. package/dist/modes/interactive/theme/theme-controller.js +5 -0
  393. package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
  394. package/dist/modes/interactive/theme/theme-json.d.ts +84 -0
  395. package/dist/modes/interactive/theme/theme-json.d.ts.map +1 -0
  396. package/dist/modes/interactive/theme/theme-json.js +130 -0
  397. package/dist/modes/interactive/theme/theme-json.js.map +1 -0
  398. package/dist/modes/interactive/theme/theme-schema.json +6 -2
  399. package/dist/modes/interactive/theme/theme.d.ts +14 -5
  400. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  401. package/dist/modes/interactive/theme/theme.js +18 -119
  402. package/dist/modes/interactive/theme/theme.js.map +1 -1
  403. package/dist/modes/interactive/tui-renderer.d.ts +21 -0
  404. package/dist/modes/interactive/tui-renderer.d.ts.map +1 -0
  405. package/dist/modes/interactive/tui-renderer.js +66 -0
  406. package/dist/modes/interactive/tui-renderer.js.map +1 -0
  407. package/dist/modes/print-mode.d.ts.map +1 -1
  408. package/dist/modes/print-mode.js +49 -23
  409. package/dist/modes/print-mode.js.map +1 -1
  410. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  411. package/dist/modes/rpc/rpc-mode.js +2 -2
  412. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  413. package/dist/testing/replay/recorded-provider.d.ts +2 -2
  414. package/dist/testing/replay/recorded-provider.d.ts.map +1 -1
  415. package/dist/testing/replay/recorded-provider.js +2 -5
  416. package/dist/testing/replay/recorded-provider.js.map +1 -1
  417. package/dist/testing/replay/runner.d.ts.map +1 -1
  418. package/dist/testing/replay/runner.js +0 -1
  419. package/dist/testing/replay/runner.js.map +1 -1
  420. package/dist/utils/clipboard-command.d.ts +7 -0
  421. package/dist/utils/clipboard-command.d.ts.map +1 -0
  422. package/dist/utils/clipboard-command.js +45 -0
  423. package/dist/utils/clipboard-command.js.map +1 -0
  424. package/dist/utils/clipboard-image.d.ts.map +1 -1
  425. package/dist/utils/clipboard-image.js +53 -81
  426. package/dist/utils/clipboard-image.js.map +1 -1
  427. package/dist/utils/clipboard.d.ts.map +1 -1
  428. package/dist/utils/clipboard.js +111 -121
  429. package/dist/utils/clipboard.js.map +1 -1
  430. package/dist/utils/exif-orientation.d.ts.map +1 -1
  431. package/dist/utils/exif-orientation.js +2 -3
  432. package/dist/utils/exif-orientation.js.map +1 -1
  433. package/dist/utils/mime.d.ts.map +1 -1
  434. package/dist/utils/mime.js +1 -1
  435. package/dist/utils/mime.js.map +1 -1
  436. package/dist/utils/syntax-highlight.d.ts.map +1 -1
  437. package/dist/utils/syntax-highlight.js +21 -21
  438. package/dist/utils/syntax-highlight.js.map +1 -1
  439. package/dist/utils/tool-result-images.d.ts +3 -1
  440. package/dist/utils/tool-result-images.d.ts.map +1 -1
  441. package/dist/utils/tool-result-images.js +4 -1
  442. package/dist/utils/tool-result-images.js.map +1 -1
  443. package/dist/utils/tools-manager.d.ts.map +1 -1
  444. package/dist/utils/tools-manager.js +11 -2
  445. package/dist/utils/tools-manager.js.map +1 -1
  446. package/dist/utils/wsl.d.ts +3 -0
  447. package/dist/utils/wsl.d.ts.map +1 -0
  448. package/dist/utils/wsl.js +15 -0
  449. package/dist/utils/wsl.js.map +1 -0
  450. package/dist/utils/zip.d.ts +7 -0
  451. package/dist/utils/zip.d.ts.map +1 -0
  452. package/dist/utils/zip.js +60 -0
  453. package/dist/utils/zip.js.map +1 -0
  454. package/docs/cli-integration.md +107 -0
  455. package/docs/cli.md +269 -0
  456. package/docs/compaction.md +73 -24
  457. package/docs/configuration.md +45 -0
  458. package/docs/containerization.md +22 -21
  459. package/docs/custom-provider.md +21 -12
  460. package/docs/development.md +19 -0
  461. package/docs/docs.json +139 -95
  462. package/docs/extensions.md +243 -371
  463. package/docs/how-pi-works.md +49 -0
  464. package/docs/images/interactive-mode.png +0 -0
  465. package/docs/index.md +3 -3
  466. package/docs/json.md +43 -3
  467. package/docs/keybindings.md +57 -54
  468. package/docs/llama-cpp.md +2 -2
  469. package/docs/message-types.md +261 -0
  470. package/docs/models.md +103 -49
  471. package/docs/packages.md +49 -47
  472. package/docs/prompt-templates.md +29 -56
  473. package/docs/providers.md +91 -139
  474. package/docs/quickstart.md +42 -14
  475. package/docs/rpc-commands.md +854 -0
  476. package/docs/rpc-extension-ui.md +200 -0
  477. package/docs/rpc.md +121 -223
  478. package/docs/sdk.md +99 -124
  479. package/docs/security.md +7 -7
  480. package/docs/session-format.md +85 -82
  481. package/docs/sessions.md +41 -57
  482. package/docs/settings.md +95 -88
  483. package/docs/shell-aliases.md +68 -3
  484. package/docs/skills.md +62 -49
  485. package/docs/slash-commands.md +60 -0
  486. package/docs/terminal-setup.md +86 -55
  487. package/docs/termux.md +65 -75
  488. package/docs/themes.md +64 -86
  489. package/docs/tmux.md +29 -7
  490. package/docs/tui.md +67 -122
  491. package/docs/usage.md +31 -42
  492. package/docs/windows.md +41 -13
  493. package/examples/README.md +16 -2
  494. package/examples/extensions/README.md +0 -1
  495. package/examples/extensions/custom-provider-anthropic/index.ts +18 -12
  496. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  497. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  498. package/examples/extensions/custom-provider-gitlab-duo/index.ts +2 -2
  499. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  500. package/examples/extensions/dynamic-resources/dynamic.json +2 -0
  501. package/examples/extensions/gondolin/package-lock.json +2 -2
  502. package/examples/extensions/gondolin/package.json +1 -1
  503. package/examples/extensions/prompt-customizer.ts +19 -67
  504. package/examples/extensions/sandbox/package-lock.json +2 -2
  505. package/examples/extensions/sandbox/package.json +1 -1
  506. package/examples/extensions/with-deps/package-lock.json +2 -2
  507. package/examples/extensions/with-deps/package.json +1 -1
  508. package/examples/plugins/pi-example-plugin/README.md +38 -0
  509. package/examples/plugins/pi-example-plugin/package.json +10 -0
  510. package/examples/plugins/pi-example-plugin/src/contract.ts +13 -0
  511. package/examples/plugins/pi-example-plugin/src/session.ts +24 -0
  512. package/examples/plugins/pi-example-plugin/src/tui.ts +31 -0
  513. package/examples/rpc-client.ts +35 -0
  514. package/examples/rpc-extension-ui.ts +25 -5
  515. package/examples/sdk/README.md +1 -1
  516. package/npm-shrinkwrap.json +861 -513
  517. package/package.json +27 -18
  518. package/dist/bun/register-bedrock.d.ts +0 -2
  519. package/dist/bun/register-bedrock.d.ts.map +0 -1
  520. package/dist/bun/register-bedrock.js +0 -4
  521. package/dist/bun/register-bedrock.js.map +0 -1
  522. package/dist/cli/experimental/auth.d.ts +0 -16
  523. package/dist/cli/experimental/auth.d.ts.map +0 -1
  524. package/dist/cli/experimental/auth.js +0 -13
  525. package/dist/cli/experimental/auth.js.map +0 -1
  526. package/dist/cli/experimental/cli.d.ts +0 -6
  527. package/dist/cli/experimental/cli.d.ts.map +0 -1
  528. package/dist/cli/experimental/cli.js +0 -5
  529. package/dist/cli/experimental/cli.js.map +0 -1
  530. package/dist/cli/experimental/command-options.d.ts +0 -17
  531. package/dist/cli/experimental/command-options.d.ts.map +0 -1
  532. package/dist/cli/experimental/command-options.js +0 -35
  533. package/dist/cli/experimental/command-options.js.map +0 -1
  534. package/dist/cli/experimental/command.d.ts +0 -63
  535. package/dist/cli/experimental/command.d.ts.map +0 -1
  536. package/dist/cli/experimental/command.js +0 -130
  537. package/dist/cli/experimental/command.js.map +0 -1
  538. package/dist/cli/experimental/commands/client.d.ts +0 -13
  539. package/dist/cli/experimental/commands/client.d.ts.map +0 -1
  540. package/dist/cli/experimental/commands/client.js +0 -25
  541. package/dist/cli/experimental/commands/client.js.map +0 -1
  542. package/dist/cli/experimental/commands/pi.d.ts +0 -15
  543. package/dist/cli/experimental/commands/pi.d.ts.map +0 -1
  544. package/dist/cli/experimental/commands/pi.js +0 -28
  545. package/dist/cli/experimental/commands/pi.js.map +0 -1
  546. package/dist/cli/experimental/commands/server.d.ts +0 -13
  547. package/dist/cli/experimental/commands/server.d.ts.map +0 -1
  548. package/dist/cli/experimental/commands/server.js +0 -25
  549. package/dist/cli/experimental/commands/server.js.map +0 -1
  550. package/dist/cli/experimental/transport-address.d.ts +0 -10
  551. package/dist/cli/experimental/transport-address.d.ts.map +0 -1
  552. package/dist/cli/experimental/transport-address.js +0 -38
  553. package/dist/cli/experimental/transport-address.js.map +0 -1
  554. package/dist/client/index.d.ts +0 -3
  555. package/dist/client/index.d.ts.map +0 -1
  556. package/dist/client/index.js +0 -3
  557. package/dist/client/index.js.map +0 -1
  558. package/dist/client/remote-session.d.ts +0 -53
  559. package/dist/client/remote-session.d.ts.map +0 -1
  560. package/dist/client/remote-session.js +0 -340
  561. package/dist/client/remote-session.js.map +0 -1
  562. package/dist/client/transcript.d.ts +0 -12
  563. package/dist/client/transcript.d.ts.map +0 -1
  564. package/dist/client/transcript.js +0 -98
  565. package/dist/client/transcript.js.map +0 -1
  566. package/dist/modes/interactive/components/message-spine.d.ts +0 -14
  567. package/dist/modes/interactive/components/message-spine.d.ts.map +0 -1
  568. package/dist/modes/interactive/components/message-spine.js +0 -19
  569. package/dist/modes/interactive/components/message-spine.js.map +0 -1
  570. package/dist/utils/clipboard-native.d.ts +0 -11
  571. package/dist/utils/clipboard-native.d.ts.map +0 -1
  572. package/dist/utils/clipboard-native.js +0 -20
  573. package/dist/utils/clipboard-native.js.map +0 -1
@@ -6,30 +6,35 @@ 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
- import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Markdown, matchesKey, ProcessTerminal, Spacer, setCapabilityOverrides, setKeybindings, Text, TruncatedText, TuiAltScreen, TuiMainScreen, visibleWidth, } from "@earendil-works/pi-tui";
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";
12
13
  import { spawn, spawnSync } from "child_process";
13
14
  import { APP_NAME, APP_TITLE, CONFIG_DIR_NAME, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, getShareViewerUrl, VERSION, } from "../../config.js";
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 { findExtensionStackMatches, recordCrash, takeUnnotifiedCrash } from "../../core/crash-log.js";
17
20
  import { DEFAULT_THINKING_LEVEL, THINKING_LEVEL_OPTIONS } from "../../core/defaults.js";
18
21
  import { getApexEnvironment } from "../../core/environment.js";
19
22
  import { FooterDataProvider } from "../../core/footer-data-provider.js";
20
23
  import { configureHttpDispatcher, formatHttpIdleTimeoutMs } from "../../core/http-dispatcher.js";
21
24
  import { KeybindingsManager } from "../../core/keybindings.js";
22
25
  import { authorizeConfiguredServer } from "../../core/mcp/oauth/authorize.js";
23
- import { createCompactionSummaryMessage } from "../../core/messages.js";
26
+ import { createCompactionSummaryMessage, createCustomMessage } from "../../core/messages.js";
24
27
  import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScopeFromModels, } from "../../core/model-resolver.js";
25
28
  import { CredentialSynchronizationError } from "../../core/model-runtime.js";
26
29
  import { aggregateUsagePerformance } from "../../core/observability/aggregate.js";
27
30
  import { DefaultPackageManager } from "../../core/package-manager.js";
28
31
  import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
29
- import { SessionManager, sessionEntryToContextMessages } from "../../core/session-manager.js";
32
+ import { SessionManager, sessionEntryToContextMessages, } from "../../core/session-manager.js";
30
33
  import { formatShareUnavailableMessage, publishSessionShare } from "../../core/session-share.js";
34
+ import { CHAT_DETAILS } from "../../core/settings-manager.js";
31
35
  import { slugifySkillCommandName } from "../../core/skills.js";
32
36
  import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
37
+ import { withBuiltInRenderers } from "../../core/tools/renderers/index.js";
33
38
  import { hasTrustRequiringProjectResources, ProjectTrustStore } from "../../core/trust-manager.js";
34
39
  import { getUsageCostBreakdown } from "../../core/usage-totals.js";
35
40
  import { getChangelogPath, getNewEntries, normalizeChangelogLinks, parseChangelog } from "../../utils/changelog.js";
@@ -42,24 +47,29 @@ import { killTrackedDetachedChildren } from "../../utils/shell.js";
42
47
  import { loadAllHighlightLanguages } from "../../utils/syntax-highlight.js";
43
48
  import { ensureTool } from "../../utils/tools-manager.js";
44
49
  import { checkForNewApexCodeVersion } from "../../utils/version-check.js";
50
+ import { reportBug } from "./bug-report.js";
51
+ import { createChatViewport } from "./chat-viewport.js";
45
52
  import { ArminComponent } from "./components/armin.js";
46
53
  import { AssistantMessageComponent } from "./components/assistant-message.js";
54
+ import { describeWithSource } from "./components/autocomplete-source.js";
47
55
  import { BashExecutionComponent } from "./components/bash-execution.js";
48
56
  import { BorderedLoader } from "./components/bordered-loader.js";
49
57
  import { BranchSummaryMessageComponent } from "./components/branch-summary-message.js";
50
58
  import { CompactionSummaryMessageComponent } from "./components/compaction-summary-message.js";
59
+ import { ComposerDock } from "./components/composer-dock.js";
51
60
  import { CustomEditor } from "./components/custom-editor.js";
52
61
  import { CustomEntryComponent } from "./components/custom-entry.js";
53
62
  import { CustomMessageComponent } from "./components/custom-message.js";
54
63
  import { DaxnutsComponent } from "./components/daxnuts.js";
55
64
  import { DynamicBorder } from "./components/dynamic-border.js";
56
65
  import { EarendilAnnouncementComponent } from "./components/earendil-announcement.js";
66
+ import { collapsedErrorLine, summarizeError } from "./components/error-summary.js";
57
67
  import { ExtensionEditorComponent } from "./components/extension-editor.js";
58
68
  import { ExtensionInputComponent } from "./components/extension-input.js";
59
69
  import { ExtensionSelectorComponent } from "./components/extension-selector.js";
60
70
  import { FirstUseHints } from "./components/first-use-hints.js";
61
71
  import { FooterComponent, formatTokens } from "./components/footer.js";
62
- import { formatKeyText, keyDisplayText, keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
72
+ import { formatKeyText, hintRow, keyDisplayText, keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
63
73
  import { LoginDialogComponent } from "./components/login-dialog.js";
64
74
  import { createMermaidMarkdownTransformer } from "./components/mermaid.js";
65
75
  import { ModelSelectorComponent } from "./components/model-selector.js";
@@ -82,19 +92,28 @@ import { refreshModelCatalogs } from "./model-catalog-refresh.js";
82
92
  import { getModelSearchText } from "./model-search.js";
83
93
  import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, onThemeChange, setRegisteredThemes, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
84
94
  import { InteractiveThemeController } from "./theme/theme-controller.js";
85
- const CHAT_DETAIL_ORDER = ["overview", "details", "all"];
95
+ import { createInteractiveTui, createInteractiveTuiReference } from "./tui-renderer.js";
96
+ export { createInteractiveTui, createInteractiveTuiReference } from "./tui-renderer.js";
97
+ /** A session opens with everything collapsed; the expand key opens it rung by rung. */
98
+ export const INITIAL_CHAT_DETAIL = "overview";
86
99
  /** The next rung, wrapping from `all` back to `overview`. */
87
100
  export function nextChatDetail(detail) {
88
- return CHAT_DETAIL_ORDER[(CHAT_DETAIL_ORDER.indexOf(detail) + 1) % CHAT_DETAIL_ORDER.length] ?? "details";
101
+ return CHAT_DETAILS[(CHAT_DETAILS.indexOf(detail) + 1) % CHAT_DETAILS.length] ?? INITIAL_CHAT_DETAIL;
89
102
  }
90
103
  /** The single place the three rungs turn into rendering flags. */
91
104
  export function chatDetailView(detail) {
92
105
  return {
93
106
  toolOutputExpanded: detail === "all",
94
107
  editDiffsExpanded: detail !== "overview",
95
- revealThinking: detail === "all",
108
+ thinking: detail === "overview" ? "collapsed" : detail === "details" ? "preference" : "revealed",
96
109
  };
97
110
  }
111
+ function isWorkingStatusEditor(editor) {
112
+ return ("embedWorkingStatus" in editor &&
113
+ editor.embedWorkingStatus === true &&
114
+ "setWorkingStatusIndicator" in editor &&
115
+ typeof editor.setWorkingStatusIndicator === "function");
116
+ }
98
117
  function isExpandable(obj) {
99
118
  return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
100
119
  }
@@ -116,12 +135,27 @@ class ExpandableText extends Text {
116
135
  this.setText(expanded ? this.getExpandedText() : this.getCollapsedText());
117
136
  }
118
137
  }
138
+ /**
139
+ * An error line that joins the detail cycle: a multi-line error folds to its
140
+ * summary until the transcript is fully expanded. `outcome` trails both forms,
141
+ * so what happened to the request stays visible while the detail is folded.
142
+ */
143
+ function errorText(message, expanded, paddingX, outcome = "") {
144
+ const summary = summarizeError(message);
145
+ const full = () => theme.fg("error", `${message}${outcome}`);
146
+ if (summary === undefined)
147
+ return new Text(full(), paddingX, 0);
148
+ return new ExpandableText(() => collapsedErrorLine(theme.fg("error", `${summary}${outcome}`)), full, expanded, paddingX, 0);
149
+ }
119
150
  function isCustomSessionEntry(item) {
120
151
  return "type" in item && item.type === "custom";
121
152
  }
122
153
  function isCompactionCostNotice(item) {
123
154
  return "type" in item && item.type === "compaction_cost";
124
155
  }
156
+ function isUsageSessionEntry(item) {
157
+ return "type" in item && item.type === "usage";
158
+ }
125
159
  const DEAD_TERMINAL_ERROR_CODES = new Set(["EIO", "EPIPE", "ENOTCONN"]);
126
160
  function isDeadTerminalError(error) {
127
161
  if (!error || typeof error !== "object" || !("code" in error)) {
@@ -130,6 +164,22 @@ function isDeadTerminalError(error) {
130
164
  const code = error.code;
131
165
  return code !== undefined && DEAD_TERMINAL_ERROR_CODES.has(code);
132
166
  }
167
+ export function formatCrashExtensionHint(extensionMatches) {
168
+ const matches = Array.isArray(extensionMatches)
169
+ ? extensionMatches.filter((match) => typeof match === "string" && match.length > 0)
170
+ : [];
171
+ if (matches.length === 0)
172
+ return undefined;
173
+ const quoted = matches.map((match) => `\`${match}\``);
174
+ const labels = quoted.length === 1
175
+ ? quoted[0]
176
+ : quoted.length === 2
177
+ ? quoted.join(" and ")
178
+ : `${quoted.slice(0, -1).join(", ")}, and ${quoted[quoted.length - 1]}`;
179
+ const noun = matches.length === 1 ? "extension" : "extensions";
180
+ const pronoun = matches.length === 1 ? "it" : "them";
181
+ return `A stack frame came from loaded ${noun} ${labels}, which may be involved. Try disabling ${pronoun} with \`${APP_NAME} config\`, or run \`${APP_NAME} -ne\` to confirm.`;
182
+ }
133
183
  const ANTHROPIC_SUBSCRIPTION_AUTH_WARNING = "Anthropic subscription auth is active. Third-party harness usage draws from extra usage and is billed per token, not your Claude plan limits. Manage extra usage at https://claude.ai/settings/usage. Disable this warning in /settings.";
134
184
  function isAnthropicSubscriptionAuthKey(apiKey) {
135
185
  return typeof apiKey === "string" && apiKey.startsWith("sk-ant-oat");
@@ -202,61 +252,6 @@ function formatLoginProviderCompletionDescription(provider) {
202
252
  const authTypes = provider.authTypes.map(formatAuthSelectorProviderType).join("/");
203
253
  return provider.name === provider.id ? authTypes : `${provider.name} · ${authTypes}`;
204
254
  }
205
- /** Composition root for selecting the interactive terminal renderer. */
206
- export function createInteractiveTui(options) {
207
- const terminal = options.terminal ?? new ProcessTerminal();
208
- if (options.tuiMode === "fullscreen") {
209
- const styleSearchMatch = (text) => theme.bg("searchMatchBg", theme.fg("searchMatchText", text));
210
- return new TuiAltScreen(terminal, options.showHardwareCursor, options.logDirectory, {
211
- searchMatchStyle: (text) => theme.underline(styleSearchMatch(text)),
212
- searchCurrentMatchStyle: (text) => theme.bold(theme.inverse(styleSearchMatch(text))),
213
- openUrl: openBrowser,
214
- onRightClickPaste: options.onRightClickPaste,
215
- copyOnSelect: options.fullscreenCopyOnSelect,
216
- copySelection: async (text) => {
217
- try {
218
- await copyToClipboard(text);
219
- return true;
220
- }
221
- catch {
222
- return false;
223
- }
224
- },
225
- });
226
- }
227
- return new TuiMainScreen(terminal, options.showHardwareCursor, options.logDirectory);
228
- }
229
- /** Stable reference for components while InteractiveMode replaces the active renderer. */
230
- export function createInteractiveTuiReference(getTui) {
231
- return new Proxy({}, {
232
- get: (_target, property) => {
233
- const tui = getTui();
234
- const value = Reflect.get(tui, property, tui);
235
- if (typeof value !== "function")
236
- return value;
237
- let methodTui = tui;
238
- let method = value;
239
- return (...args) => {
240
- const currentTui = getTui();
241
- if (currentTui !== methodTui) {
242
- const currentMethod = Reflect.get(currentTui, property, currentTui);
243
- if (typeof currentMethod !== "function") {
244
- throw new TypeError(`TUI property ${String(property)} is not callable`);
245
- }
246
- methodTui = currentTui;
247
- method = currentMethod;
248
- }
249
- return Reflect.apply(method, methodTui, args);
250
- };
251
- },
252
- set: (_target, property, value) => {
253
- const tui = getTui();
254
- return Reflect.set(tui, property, value, tui);
255
- },
256
- has: (_target, property) => Reflect.has(getTui(), property),
257
- getPrototypeOf: () => Reflect.getPrototypeOf(getTui()),
258
- });
259
- }
260
255
  /**
261
256
  * Working status rides in the footer tray; everything else keeps its own rows.
262
257
  *
@@ -315,11 +310,12 @@ export class InteractiveMode {
315
310
  onInputCallback;
316
311
  pendingUserInputs = [];
317
312
  activeStatusIndicator = undefined;
313
+ activeWorkingIndicatorEmbedded = false;
318
314
  idleStatus = new IdleStatus();
319
315
  workingMessage = undefined;
320
316
  workingVisible = true;
321
317
  workingIndicatorOptions = undefined;
322
- defaultWorkingMessage = "Working...";
318
+ defaultWorkingMessage = "Working";
323
319
  defaultHiddenThinkingLabel = "Thinking...";
324
320
  hiddenThinkingLabel = this.defaultHiddenThinkingLabel;
325
321
  lastSigintTime = 0;
@@ -333,13 +329,14 @@ export class InteractiveMode {
333
329
  managedToolStatusStarted = false;
334
330
  // Streaming message tracking
335
331
  streamingComponent = undefined;
332
+ entriesRenderedByBoundaryCompaction = new Set();
336
333
  streamingMessage = undefined;
337
334
  // Tool execution tracking: toolCallId -> component
338
335
  pendingTools = new Map();
339
336
  // Tool output expansion state
340
- chatDetail = "details";
341
- toolOutputExpanded = false;
342
- editDiffsExpanded = true;
337
+ chatDetail = INITIAL_CHAT_DETAIL;
338
+ toolOutputExpanded = chatDetailView(INITIAL_CHAT_DETAIL).toolOutputExpanded;
339
+ editDiffsExpanded = chatDetailView(INITIAL_CHAT_DETAIL).editDiffsExpanded;
343
340
  // Thinking block visibility state
344
341
  hideThinkingBlock = false;
345
342
  outputPad = 1;
@@ -368,10 +365,19 @@ export class InteractiveMode {
368
365
  autoCompactionEscapeHandler;
369
366
  // Auto-retry state
370
367
  retryEscapeHandler;
368
+ /**
369
+ * The failed attempt an auto-retry would supersede. `component` is set while its error is
370
+ * still on screen, and a retry removes it from the chat.
371
+ */
372
+ retriedAttempt;
373
+ /** Set when the user presses Escape during a retry wait, which is the only way to cancel one. */
374
+ retryCancelled = false;
371
375
  // Messages queued while compaction is running
372
376
  compactionQueuedMessages = [];
373
377
  // Shutdown state
374
378
  shutdownRequested = false;
379
+ /** The `/bug` hint is shown at most once per session so error output stays readable. */
380
+ bugReportHintShown = false;
375
381
  // Extension UI state
376
382
  extensionSelector = undefined;
377
383
  extensionInput = undefined;
@@ -435,6 +441,7 @@ export class InteractiveMode {
435
441
  tuiMode,
436
442
  showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
437
443
  logDirectory: getAgentDir(),
444
+ terminal: options.terminal,
438
445
  onRightClickPaste: this.onRightClickPaste,
439
446
  fullscreenCopyOnSelect: this.settingsManager.getFullscreenCopyOnSelect(),
440
447
  });
@@ -470,17 +477,20 @@ export class InteractiveMode {
470
477
  surfaceColor: (text) => theme.bg("userMessageBg", text),
471
478
  autocompleteRule: (width) => theme.fg("borderMuted", (this.settingsManager.getSymbolPreset() === "ascii" ? "-" : "┄").repeat(Math.max(1, width))),
472
479
  autocompleteFooter: () => this.buildAutocompleteFooter(),
480
+ // Upstream embeds the working status in this editor's top border. The Apex
481
+ // composer draws a filled surface with no top border and the footer tray
482
+ // carries that status, so the default editor leaves `embedWorkingStatus` off.
473
483
  });
474
484
  this.editor = this.defaultEditor;
475
- this.editorContainer = new Container();
485
+ this.editorContainer = new ComposerDock(() => this.editor);
476
486
  this.editorContainer.addChild(this.editor);
477
487
  this.footerDataProvider = new FooterDataProvider(this.sessionManager.getCwd());
478
488
  this.footer = new FooterComponent(this.session, this.footerDataProvider, this.settingsManager);
479
489
  this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
480
490
  this.footerContainer = new Container();
481
491
  this.footerContainer.addChild(this.footer);
482
- // Load hide thinking block setting
483
492
  this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
493
+ this.loadChatDetail(this.settingsManager.getChatDetail());
484
494
  this.outputPad = this.settingsManager.getOutputPad();
485
495
  // Register themes from resource loader and initialize
486
496
  setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
@@ -491,32 +501,6 @@ export class InteractiveMode {
491
501
  initialThemeSetting: options.initialThemeSetting,
492
502
  });
493
503
  }
494
- getAutocompleteSourceTag(sourceInfo) {
495
- if (!sourceInfo) {
496
- return undefined;
497
- }
498
- const scopePrefix = sourceInfo.scope === "user" ? "u" : sourceInfo.scope === "project" ? "p" : "t";
499
- const source = sourceInfo.source.trim();
500
- if (source === "auto" || source === "local" || source === "cli") {
501
- return scopePrefix;
502
- }
503
- if (source.startsWith("npm:")) {
504
- return `${scopePrefix}:${source}`;
505
- }
506
- const gitSource = parseGitUrl(source);
507
- if (gitSource) {
508
- const ref = gitSource.ref ? `@${gitSource.ref}` : "";
509
- return `${scopePrefix}:git:${gitSource.host}/${gitSource.path}${ref}`;
510
- }
511
- return scopePrefix;
512
- }
513
- prefixAutocompleteDescription(description, sourceInfo) {
514
- const sourceTag = this.getAutocompleteSourceTag(sourceInfo);
515
- if (!sourceTag) {
516
- return description;
517
- }
518
- return description ? `[${sourceTag}] ${description}` : `[${sourceTag}]`;
519
- }
520
504
  getBuiltInCommandConflictDiagnostics(extensionRunner) {
521
505
  const builtinNames = new Set(BUILTIN_SLASH_COMMANDS.map((command) => command.name));
522
506
  return extensionRunner
@@ -595,7 +579,7 @@ export class InteractiveMode {
595
579
  // Convert prompt templates to SlashCommand format for autocomplete
596
580
  const templateCommands = this.session.promptTemplates.map((cmd) => ({
597
581
  name: cmd.name,
598
- description: this.prefixAutocompleteDescription(cmd.description, cmd.sourceInfo),
582
+ description: describeWithSource(cmd.description, cmd.sourceInfo),
599
583
  ...(cmd.argumentHint && { argumentHint: cmd.argumentHint }),
600
584
  }));
601
585
  // Convert extension commands to SlashCommand format
@@ -605,7 +589,7 @@ export class InteractiveMode {
605
589
  .filter((cmd) => !builtinCommandNames.has(cmd.name))
606
590
  .map((cmd) => ({
607
591
  name: cmd.invocationName,
608
- description: this.prefixAutocompleteDescription(cmd.description, cmd.sourceInfo),
592
+ description: describeWithSource(cmd.description, cmd.sourceInfo),
609
593
  getArgumentCompletions: cmd.getArgumentCompletions,
610
594
  }));
611
595
  // Build skill commands from session.skills (if enabled)
@@ -617,7 +601,7 @@ export class InteractiveMode {
617
601
  this.skillCommands.set(commandName, skill.filePath);
618
602
  skillCommandList.push({
619
603
  name: commandName,
620
- description: this.prefixAutocompleteDescription(skill.description, skill.sourceInfo),
604
+ description: describeWithSource(skill.description, skill.sourceInfo),
621
605
  });
622
606
  }
623
607
  }
@@ -661,7 +645,7 @@ export class InteractiveMode {
661
645
  this.chatContainer.addChild(new Text(condensedText, 1, 0));
662
646
  }
663
647
  else {
664
- this.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", "What's New")), 1, 0));
648
+ this.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", "What's new")), 1, 0));
665
649
  this.chatContainer.addChild(new Spacer(1));
666
650
  this.chatContainer.addChild(new Markdown(this.changelogMarkdown.trim(), 1, 0, this.getMarkdownThemeWithSettings()));
667
651
  this.chatContainer.addChild(new Spacer(1));
@@ -760,25 +744,20 @@ export class InteractiveMode {
760
744
  }
761
745
  // Keep one component tree and remount it when changing renderers.
762
746
  this.renderWidgets(); // Initialize with default spacer
763
- this.transcriptScrollView = new TuiLayouts.ScrollView(this.documentContainer, {
764
- follow: "end",
765
- primary: true,
766
- overscroll: "chain",
747
+ const viewport = createChatViewport({
748
+ document: this.documentContainer,
749
+ pendingMessages: this.pendingMessagesContainer,
750
+ status: this.statusContainer,
751
+ widgetsAbove: this.widgetContainerAbove,
752
+ editor: this.editorContainer,
753
+ widgetsBelow: this.widgetContainerBelow,
754
+ footer: this.footerContainer,
767
755
  scrollbar: this.settingsManager.getFullscreenScrollbar(),
768
- scrollbarStyle: (text) => theme.bg("scrollbarThumb", text),
756
+ scrollbarTrackStyle: (text) => theme.fg("scrollbarTrack", text),
757
+ scrollbarThumbStyle: (text) => theme.fg("scrollbarThumb", text),
769
758
  });
770
- const dock = new TuiLayouts.VStack([
771
- { component: this.pendingMessagesContainer, shrink: 1, minSize: 0 },
772
- { component: this.statusContainer, shrink: 1, minSize: 0 },
773
- { component: this.widgetContainerAbove, shrink: 1, minSize: 0 },
774
- { component: this.editorContainer, shrink: 1, minSize: 3 },
775
- { component: this.widgetContainerBelow, shrink: 1, minSize: 0 },
776
- { component: this.footerContainer, shrink: 1, minSize: 1 },
777
- ]);
778
- this.fullscreenLayoutRoot = new TuiLayouts.VStack([
779
- { component: this.transcriptScrollView, basis: 0, grow: 1, shrink: 1, minSize: 1 },
780
- { component: dock, basis: "auto", grow: 0, shrink: 1, minSize: 1 },
781
- ]);
759
+ this.transcriptScrollView = viewport.transcript;
760
+ this.fullscreenLayoutRoot = viewport.root;
782
761
  this.mountInteractiveTui(this.renderer, [
783
762
  this.documentContainer,
784
763
  this.pendingMessagesContainer,
@@ -806,25 +785,25 @@ export class InteractiveMode {
806
785
  // The full cheatsheet is verbose-only. The default screen leads with the
807
786
  // mark and the runtime facts that actually differ between launches.
808
787
  const expandedInstructions = [
809
- hint("app.interrupt", "to interrupt"),
810
- hint("app.clear", "to clear"),
811
- rawKeyHint(`${keyText("app.clear")} twice`, "to exit"),
812
- hint("app.exit", "to exit (empty)"),
813
- hint("app.suspend", "to suspend"),
814
- keyHint("tui.editor.deleteToLineEnd", "to delete to end"),
815
- hint("app.thinking.cycle", "to cycle thinking level"),
816
- rawKeyHint(`${keyText("app.model.cycleForward")}/${keyText("app.model.cycleBackward")}`, "to cycle models"),
817
- hint("app.model.select", "to select model"),
818
- hint("app.tools.expand", "to expand tools"),
819
- hint("app.thinking.toggle", "to expand thinking"),
820
- hint("app.editor.external", "for external editor"),
821
- rawKeyHint("/", "for commands"),
822
- rawKeyHint("!", "to run bash"),
823
- rawKeyHint("!!", "to run bash (no context)"),
824
- hint("app.message.followUp", "to queue follow-up"),
825
- hint("app.message.dequeue", "to edit all queued messages"),
826
- hint("app.clipboard.pasteImage", "to paste image (with text fallback)"),
827
- rawKeyHint("drop files", "to attach"),
788
+ hint("app.interrupt", "interrupt"),
789
+ hint("app.clear", "clear"),
790
+ rawKeyHint(`${keyText("app.clear")} twice`, "exit"),
791
+ hint("app.exit", "exit (empty)"),
792
+ hint("app.suspend", "suspend"),
793
+ keyHint("tui.editor.deleteToLineEnd", "delete to end"),
794
+ hint("app.thinking.cycle", "cycle thinking level"),
795
+ rawKeyHint(`${keyText("app.model.cycleForward")}/${keyText("app.model.cycleBackward")}`, "cycle models"),
796
+ hint("app.model.select", "select model"),
797
+ hint("app.tools.expand", "expand tools"),
798
+ hint("app.thinking.toggle", "expand thinking"),
799
+ hint("app.editor.external", "external editor"),
800
+ rawKeyHint("/", "commands"),
801
+ rawKeyHint("!", "run bash"),
802
+ rawKeyHint("!!", "run bash (no context)"),
803
+ hint("app.message.followUp", "queue follow-up"),
804
+ hint("app.message.dequeue", "edit all queued messages"),
805
+ hint("app.clipboard.pasteImage", "paste image (with text fallback)"),
806
+ rawKeyHint("drop files", "attach"),
828
807
  ].join("\n");
829
808
  this.builtInHeader = new ApexSplashHeader(this.version, () => this.session.state.model?.id, () => this.session.sessionManager.getCwd(), this.options.verbose ? expandedInstructions : undefined, {
830
809
  topPadding: true,
@@ -966,6 +945,11 @@ export class InteractiveMode {
966
945
  if (modelFallbackMessage) {
967
946
  this.showWarning(modelFallbackMessage);
968
947
  }
948
+ const crash = takeUnnotifiedCrash();
949
+ if (crash) {
950
+ const when = new Date(crash.timestamp).toLocaleString();
951
+ this.showWarning(`${APP_NAME} crashed on ${when} (${crash.message}). Run /bug to report it; the crash details are attached automatically.`);
952
+ }
969
953
  void this.maybeWarnAboutAnthropicSubscriptionAuth();
970
954
  // Process initial messages
971
955
  if (initialMessage) {
@@ -1553,13 +1537,12 @@ export class InteractiveMode {
1553
1537
  */
1554
1538
  /** Live keybindings under the autocomplete dropdown. */
1555
1539
  buildAutocompleteFooter() {
1556
- const separator = theme.fg("borderMuted", this.settingsManager.getSymbolPreset() === "ascii" ? " - " : " · ");
1557
- return [
1558
- rawKeyHint(`${keyText("tui.select.up")}/${keyText("tui.select.down")}`, "move"),
1559
- keyHint("tui.select.confirm", "select"),
1560
- keyHint("tui.input.tab", "complete"),
1561
- keyHint("tui.select.cancel", "dismiss"),
1562
- ].join(separator);
1540
+ return hintRow([
1541
+ [["tui.select.up", "tui.select.down"], "move"],
1542
+ ["tui.select.confirm", "select"],
1543
+ ["tui.input.tab", "complete"],
1544
+ ["tui.select.cancel", "dismiss"],
1545
+ ], { ascii: this.settingsManager.getSymbolPreset() === "ascii" });
1563
1546
  }
1564
1547
  buildStartupShortcuts() {
1565
1548
  const entry = (sigil, label) => `${theme.fg("accent", sigil)} ${theme.fg("muted", label)}`;
@@ -1727,10 +1710,57 @@ export class InteractiveMode {
1727
1710
  async handleFatalRuntimeError(prefix, error) {
1728
1711
  const message = error instanceof Error ? error.message : String(error);
1729
1712
  this.showError(`${prefix}: ${message}`);
1713
+ const extensionHint = this.getCrashExtensionHint(error);
1714
+ if (extensionHint) {
1715
+ this.chatContainer.addChild(new Text(theme.fg("warning", extensionHint), this.outputPad, 0));
1716
+ }
1717
+ if (this.recordCrash("fatal_error", error)) {
1718
+ this.chatContainer.addChild(new Text(theme.fg("muted", this.crashReportInstructions()), this.outputPad, 0));
1719
+ }
1730
1720
  stopThemeWatcher();
1731
1721
  this.stop("transcript");
1732
1722
  process.exit(1);
1733
1723
  }
1724
+ getCrashExtensionHint(error) {
1725
+ try {
1726
+ return formatCrashExtensionHint(findExtensionStackMatches(error instanceof Error ? error.stack : undefined, this.session.resourceLoader.getExtensions().extensions));
1727
+ }
1728
+ catch {
1729
+ return undefined;
1730
+ }
1731
+ }
1732
+ /** Persist a crash so the next start can point the user at `/bug`. Returns false when nothing was written. */
1733
+ recordCrash(kind, error) {
1734
+ try {
1735
+ return (recordCrash({
1736
+ kind,
1737
+ error,
1738
+ sessionFile: this.session.sessionFile,
1739
+ cwd: this.session.sessionManager.getCwd(),
1740
+ }) !== undefined);
1741
+ }
1742
+ catch {
1743
+ return false;
1744
+ }
1745
+ }
1746
+ crashReportInstructions() {
1747
+ const resume = this.session.sessionFile ? `run \`${APP_NAME} -r\` to resume the session, then` : "start pi and";
1748
+ return `To report this crash: ${resume} run /bug. The crash details are attached automatically.`;
1749
+ }
1750
+ suggestBugReport() {
1751
+ if (this.bugReportHintShown)
1752
+ return;
1753
+ this.bugReportHintShown = true;
1754
+ this.chatContainer.addChild(new Text(theme.fg("muted", `If this looks like a ${APP_NAME} bug, /bug exports a report locally without uploading it.`), this.outputPad, 0));
1755
+ this.ui.requestRender();
1756
+ }
1757
+ maybeSuggestBugReport(message) {
1758
+ if (message.stopReason !== "error" || isRetryableAssistantError(message))
1759
+ return;
1760
+ if (/\b(?:abort(?:ed)?|cancel(?:l?ed)?)\b/i.test(message.errorMessage ?? ""))
1761
+ return;
1762
+ this.suggestBugReport();
1763
+ }
1734
1764
  renderCurrentSessionState() {
1735
1765
  this.loadedResourcesContainer.clear();
1736
1766
  this.chatContainer.clear();
@@ -1758,8 +1788,12 @@ export class InteractiveMode {
1758
1788
  /**
1759
1789
  * Get a registered tool definition by name (for custom rendering).
1760
1790
  */
1791
+ /**
1792
+ * Extension-registered definition, falling back to the built-in one. The renderer components take
1793
+ * whatever this returns, so they never reach into the tool registry themselves.
1794
+ */
1761
1795
  getRegisteredToolDefinition(toolName) {
1762
- return this.session.getToolDefinition(toolName);
1796
+ return withBuiltInRenderers(toolName, this.session.getToolDefinition(toolName));
1763
1797
  }
1764
1798
  getMarkdownTransformers() {
1765
1799
  return [this.mermaidMarkdownTransformer, ...this.session.extensionRunner.getMarkdownTransformers()];
@@ -1829,38 +1863,58 @@ export class InteractiveMode {
1829
1863
  this.footerDataProvider.setExtensionStatus(key, text);
1830
1864
  this.ui.requestRender();
1831
1865
  }
1866
+ setEditorWorkingStatusIndicator(indicator) {
1867
+ this.defaultEditor.setWorkingStatusIndicator(undefined);
1868
+ if (!isWorkingStatusEditor(this.editor))
1869
+ return false;
1870
+ this.editor.setWorkingStatusIndicator(indicator);
1871
+ return true;
1872
+ }
1832
1873
  showStatusIndicator(indicator) {
1833
1874
  this.activeStatusIndicator?.dispose();
1834
1875
  this.activeStatusIndicator = indicator;
1876
+ this.activeWorkingIndicatorEmbedded = false;
1835
1877
  this.statusContainer.clear();
1878
+ this.setEditorWorkingStatusIndicator(undefined);
1836
1879
  if (trayOwnsActivity(indicator.kind, this.customFooter !== undefined)) {
1837
1880
  this.footer.setActivity(indicator);
1838
1881
  return;
1839
1882
  }
1840
1883
  this.footer.setActivity(undefined);
1884
+ // With a custom footer there is no tray; an editor that opts in to
1885
+ // `embedWorkingStatus` then carries the working status in its border.
1886
+ if (indicator instanceof WorkingStatusIndicator && this.setEditorWorkingStatusIndicator(indicator)) {
1887
+ this.activeWorkingIndicatorEmbedded = true;
1888
+ return;
1889
+ }
1841
1890
  this.statusContainer.addChild(indicator);
1842
1891
  }
1843
1892
  clearStatusIndicator(kind) {
1844
1893
  if (kind && this.activeStatusIndicator?.kind !== kind) {
1845
1894
  return;
1846
1895
  }
1847
- const hadActiveStatusIndicator = this.activeStatusIndicator !== undefined;
1848
- const wasInTray = this.activeStatusIndicator !== undefined &&
1849
- trayOwnsActivity(this.activeStatusIndicator.kind, this.customFooter !== undefined);
1850
- this.activeStatusIndicator?.dispose();
1896
+ const clearedIndicator = this.activeStatusIndicator;
1897
+ const wasInTray = clearedIndicator !== undefined && trayOwnsActivity(clearedIndicator.kind, this.customFooter !== undefined);
1898
+ const clearedIndicatorWasEmbedded = clearedIndicator?.kind === "working" && this.activeWorkingIndicatorEmbedded;
1899
+ clearedIndicator?.dispose();
1851
1900
  this.activeStatusIndicator = undefined;
1901
+ this.activeWorkingIndicatorEmbedded = false;
1852
1902
  this.footer?.setActivity(undefined);
1853
1903
  this.statusContainer.clear();
1854
- // The tray never occupied those rows, so it must not leave the idle filler
1855
- // behind to reserve them.
1856
- if (wasInTray)
1904
+ this.setEditorWorkingStatusIndicator(undefined);
1905
+ // Neither the tray nor the editor border occupied those rows, so they must
1906
+ // not leave the idle filler behind to reserve them.
1907
+ if (wasInTray || clearedIndicatorWasEmbedded)
1857
1908
  return;
1858
- if (hadActiveStatusIndicator && this.options.tuiMode === "regular" && this.ui.getClearOnShrink()) {
1909
+ if (clearedIndicator && this.options.tuiMode === "regular" && this.ui.getClearOnShrink()) {
1859
1910
  this.statusContainer.addChild(this.idleStatus);
1860
1911
  }
1861
1912
  }
1862
1913
  showWorkingStatusIndicator() {
1863
- this.showStatusIndicator(new WorkingStatusIndicator(this.ui, this.workingMessage ?? this.defaultWorkingMessage, this.workingIndicatorOptions));
1914
+ const colorFn = !trayOwnsActivity("working", this.customFooter !== undefined) && isWorkingStatusEditor(this.editor)
1915
+ ? (text) => (this.editor.borderColor ?? theme.getThinkingBorderColor(this.session.thinkingLevel || "off"))(text)
1916
+ : undefined;
1917
+ this.showStatusIndicator(new WorkingStatusIndicator(this.ui, this.workingMessage ?? this.defaultWorkingMessage, this.workingIndicatorOptions, colorFn));
1864
1918
  }
1865
1919
  setWorkingVisible(visible) {
1866
1920
  this.workingVisible = visible;
@@ -2386,6 +2440,15 @@ export class InteractiveMode {
2386
2440
  this.editor = this.defaultEditor;
2387
2441
  }
2388
2442
  this.editorContainer.addChild(this.editor);
2443
+ // A tray-owned working status stays in the footer whichever editor is mounted.
2444
+ if (this.activeStatusIndicator instanceof WorkingStatusIndicator &&
2445
+ !trayOwnsActivity("working", this.customFooter !== undefined)) {
2446
+ this.statusContainer.clear();
2447
+ this.activeWorkingIndicatorEmbedded = this.setEditorWorkingStatusIndicator(this.activeStatusIndicator);
2448
+ if (!this.activeWorkingIndicatorEmbedded) {
2449
+ this.statusContainer.addChild(this.activeStatusIndicator);
2450
+ }
2451
+ }
2389
2452
  this.ui.setFocus(this.editor);
2390
2453
  this.ui.requestRender();
2391
2454
  }
@@ -2664,6 +2727,12 @@ export class InteractiveMode {
2664
2727
  this.editor.setText("");
2665
2728
  return;
2666
2729
  }
2730
+ if (text === "/bug" || text.startsWith("/bug ")) {
2731
+ const hint = text.slice("/bug".length).trim();
2732
+ this.editor.setText("");
2733
+ await this.handleBugCommand(hint ? hint : undefined);
2734
+ return;
2735
+ }
2667
2736
  if (text === "/copy") {
2668
2737
  await this.handleCopyCommand();
2669
2738
  this.editor.setText("");
@@ -2870,10 +2939,44 @@ export class InteractiveMode {
2870
2939
  this.ui.requestRender();
2871
2940
  break;
2872
2941
  case "entry_appended":
2942
+ if (this.entriesRenderedByBoundaryCompaction.delete(event.entry.id))
2943
+ break;
2873
2944
  if (event.entry.type === "custom") {
2874
2945
  this.addCustomEntryToChat(event.entry);
2875
2946
  this.ui.requestRender();
2876
2947
  }
2948
+ else if (event.entry.type === "usage" && event.entry.kind === "cache_warm") {
2949
+ this.addCacheWarmingUsage(event.entry);
2950
+ this.ui.requestRender();
2951
+ }
2952
+ else if (event.entry.type === "custom_message" && event.entry.display) {
2953
+ this.addMessageToChat(createCustomMessage(event.entry.customType, event.entry.content, event.entry.display, event.entry.details, event.entry.timestamp));
2954
+ this.ui.requestRender();
2955
+ }
2956
+ else if (event.entry.type === "compaction") {
2957
+ const entries = this.sessionManager.buildContextEntries();
2958
+ if (entries[0]?.id !== event.entry.id)
2959
+ break;
2960
+ this.chatContainer.clear();
2961
+ const branch = this.sessionManager.getBranch();
2962
+ const compactionIndex = branch.findIndex((entry) => entry.id === event.entry.id);
2963
+ const entriesAfterCompaction = new Set(branch.slice(compactionIndex + 1).map((entry) => entry.id));
2964
+ const retainedEntries = entries.slice(1);
2965
+ this.renderSessionEntries(retainedEntries.filter((entry) => !entriesAfterCompaction.has(entry.id)));
2966
+ this.addMessageToChat(createCompactionSummaryMessage(event.entry.summary, event.entry.tokensBefore, event.entry.timestamp));
2967
+ if (event.entry.usage) {
2968
+ this.addCompactionCostNotice({
2969
+ type: "compaction_cost",
2970
+ kind: "compaction",
2971
+ usage: event.entry.usage,
2972
+ });
2973
+ }
2974
+ this.renderSessionEntries(retainedEntries.filter((entry) => entriesAfterCompaction.has(entry.id)));
2975
+ for (const entryId of entriesAfterCompaction)
2976
+ this.entriesRenderedByBoundaryCompaction.add(entryId);
2977
+ this.footer.invalidate();
2978
+ this.ui.requestRender();
2979
+ }
2877
2980
  break;
2878
2981
  case "session_info_changed":
2879
2982
  this.updateTerminalTitle();
@@ -2895,7 +2998,9 @@ export class InteractiveMode {
2895
2998
  this.ui.requestRender();
2896
2999
  }
2897
3000
  else if (event.message.role === "assistant") {
3001
+ this.retriedAttempt = undefined;
2898
3002
  this.streamingComponent = new AssistantMessageComponent(undefined, this.isThinkingHidden(), this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad, this.getMarkdownTransformers());
3003
+ this.adoptChatDetail(this.streamingComponent);
2899
3004
  this.streamingMessage = event.message;
2900
3005
  this.chatContainer.addChild(this.streamingComponent);
2901
3006
  this.streamingComponent.updateContent(this.streamingMessage, true);
@@ -2949,6 +3054,9 @@ export class InteractiveMode {
2949
3054
  if (!errorMessage) {
2950
3055
  errorMessage = this.streamingMessage.errorMessage || "Error";
2951
3056
  }
3057
+ if (this.streamingMessage.stopReason === "error" && this.pendingTools.size === 0) {
3058
+ this.retriedAttempt = { error: errorMessage, component: this.streamingComponent };
3059
+ }
2952
3060
  for (const [, component] of this.pendingTools.entries()) {
2953
3061
  component.updateResult({
2954
3062
  content: [{ type: "text", text: errorMessage }],
@@ -2956,12 +3064,14 @@ export class InteractiveMode {
2956
3064
  });
2957
3065
  }
2958
3066
  this.clearPendingTools();
3067
+ this.maybeSuggestBugReport(this.streamingMessage);
2959
3068
  }
2960
3069
  else {
2961
3070
  // Args are now complete - trigger diff computation for edit tools
2962
3071
  for (const [, component] of this.pendingTools.entries()) {
2963
3072
  component.setArgsComplete();
2964
3073
  }
3074
+ this.maybeShowThinkingDropNotice(this.streamingMessage);
2965
3075
  this.maybeShowCacheMissNotice(this.streamingMessage);
2966
3076
  }
2967
3077
  this.streamingComponent = undefined;
@@ -3076,7 +3186,7 @@ export class InteractiveMode {
3076
3186
  }
3077
3187
  else {
3078
3188
  this.chatContainer.addChild(new Spacer(1));
3079
- this.chatContainer.addChild(new Text(theme.fg("error", event.errorMessage), 1, 0));
3189
+ this.chatContainer.addChild(errorText(event.errorMessage, this.toolOutputExpanded, 1));
3080
3190
  }
3081
3191
  }
3082
3192
  void this.flushCompactionQueue({ willRetry: event.willRetry });
@@ -3084,9 +3194,16 @@ export class InteractiveMode {
3084
3194
  break;
3085
3195
  }
3086
3196
  case "auto_retry_start": {
3197
+ // The session drops the failed attempt from agent state; the chat drops it too, and
3198
+ // the retry indicator carries the attempt count instead.
3199
+ if (this.retriedAttempt?.component) {
3200
+ this.chatContainer.removeChild(this.retriedAttempt.component);
3201
+ this.retriedAttempt = { error: this.retriedAttempt.error };
3202
+ }
3087
3203
  // Set up escape to abort retry
3088
3204
  this.retryEscapeHandler = this.defaultEditor.onEscape;
3089
3205
  this.defaultEditor.onEscape = () => {
3206
+ this.retryCancelled = true;
3090
3207
  this.session.abortRetry();
3091
3208
  };
3092
3209
  this.showStatusIndicator(new RetryStatusIndicator(this.ui, event.attempt, event.maxAttempts, event.delayMs));
@@ -3102,8 +3219,16 @@ export class InteractiveMode {
3102
3219
  this.clearStatusIndicator("retry");
3103
3220
  // Show error only on final failure (success shows normal response)
3104
3221
  if (!event.success) {
3105
- this.showError(`Retry failed after ${event.attempt} attempts: ${event.finalError || "Unknown error"}`);
3222
+ const lastAttempt = this.retriedAttempt;
3223
+ if (lastAttempt?.component)
3224
+ this.chatContainer.removeChild(lastAttempt.component);
3225
+ const outcome = this.retryCancelled
3226
+ ? "retry cancelled"
3227
+ : `gave up after ${event.attempt} ${event.attempt === 1 ? "retry" : "retries"}`;
3228
+ this.showError(lastAttempt?.error ?? event.finalError ?? "Unknown error", ` (${outcome})`);
3106
3229
  }
3230
+ this.retriedAttempt = undefined;
3231
+ this.retryCancelled = false;
3107
3232
  this.ui.requestRender();
3108
3233
  break;
3109
3234
  }
@@ -3229,6 +3354,8 @@ export class InteractiveMode {
3229
3354
  this.chatContainer.addChild(component);
3230
3355
  break;
3231
3356
  }
3357
+ case "system":
3358
+ break;
3232
3359
  case "user": {
3233
3360
  const textContent = this.getUserMessageText(message);
3234
3361
  if (textContent) {
@@ -3260,6 +3387,7 @@ export class InteractiveMode {
3260
3387
  }
3261
3388
  case "assistant": {
3262
3389
  const assistantComponent = new AssistantMessageComponent(message, this.isThinkingHidden(), this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad, this.getMarkdownTransformers());
3390
+ this.adoptChatDetail(assistantComponent);
3263
3391
  this.chatContainer.addChild(assistantComponent);
3264
3392
  break;
3265
3393
  }
@@ -3275,8 +3403,8 @@ export class InteractiveMode {
3275
3403
  renderSessionItems(items, options = {}) {
3276
3404
  this.clearPendingTools();
3277
3405
  const renderedPendingTools = new Map();
3278
- // Cache-miss notices are not persisted; re-derive them from the full entry
3279
- // list and re-inject them after the assistant messages that paid for them.
3406
+ // Cache misses are not persisted, unlike successful cache-warming usage.
3407
+ // Re-derive them and inject them after the assistant messages that paid for them.
3280
3408
  const cacheMisses = this.settingsManager.getShowCacheMissNotices()
3281
3409
  ? collectCacheMisses(this.sessionManager.getEntries(), this.session.modelRuntime)
3282
3410
  : new Map();
@@ -3289,6 +3417,10 @@ export class InteractiveMode {
3289
3417
  this.addCustomEntryToChat(item);
3290
3418
  continue;
3291
3419
  }
3420
+ if (isUsageSessionEntry(item)) {
3421
+ this.addCacheWarmingUsage(item);
3422
+ continue;
3423
+ }
3292
3424
  if (isCompactionCostNotice(item)) {
3293
3425
  this.addCompactionCostNotice(item);
3294
3426
  continue;
@@ -3357,7 +3489,7 @@ export class InteractiveMode {
3357
3489
  */
3358
3490
  renderSessionEntries(entries, options = {}) {
3359
3491
  const items = entries.flatMap((entry) => {
3360
- if (entry.type === "custom") {
3492
+ if (entry.type === "custom" || (entry.type === "usage" && entry.kind === "cache_warm")) {
3361
3493
  return [entry];
3362
3494
  }
3363
3495
  const messages = sessionEntryToContextMessages(entry);
@@ -3368,6 +3500,12 @@ export class InteractiveMode {
3368
3500
  });
3369
3501
  this.renderSessionItems(items, options);
3370
3502
  }
3503
+ addCacheWarmingUsage(entry) {
3504
+ if (!this.settingsManager.getShowCacheMissNotices())
3505
+ return;
3506
+ this.chatContainer.addChild(new Spacer(1));
3507
+ this.chatContainer.addChild(new Text(theme.fg("dim", formatCacheWarmingUsage(entry)), 1, 0));
3508
+ }
3371
3509
  /**
3372
3510
  * Render billing usage for a compaction or branch summary. The notice is derived
3373
3511
  * from persisted summary usage and is not stored as a separate session entry.
@@ -3382,6 +3520,43 @@ export class InteractiveMode {
3382
3520
  this.chatContainer.addChild(new Spacer(1));
3383
3521
  this.chatContainer.addChild(new Text(theme.fg("warning", `${label}: ${formatTokens(tokens)} tokens billed${cost}`), 1, 0));
3384
3522
  }
3523
+ static countDroppedThinkingBlocks(message) {
3524
+ let count = 0;
3525
+ for (const diagnostic of message.diagnostics ?? []) {
3526
+ if (diagnostic.type !== "anthropic_input_transformations")
3527
+ continue;
3528
+ const transformations = diagnostic.details?.transformations;
3529
+ if (!Array.isArray(transformations))
3530
+ continue;
3531
+ count += transformations.filter((transformation) => typeof transformation === "object" &&
3532
+ transformation !== null &&
3533
+ transformation.type === "thinking_dropped").length;
3534
+ }
3535
+ return count;
3536
+ }
3537
+ maybeShowThinkingDropNotice(message) {
3538
+ if (!this.settingsManager.getShowCacheMissNotices())
3539
+ return;
3540
+ const droppedCount = InteractiveMode.countDroppedThinkingBlocks(message);
3541
+ if (droppedCount === 0)
3542
+ return;
3543
+ let previousDroppedCount = 0;
3544
+ // message_end reaches the UI before the current message is persisted,
3545
+ // so the branch's last assistant message is the previous response.
3546
+ const branch = this.sessionManager.getBranch();
3547
+ for (let i = branch.length - 1; i >= 0; i--) {
3548
+ const entry = branch[i];
3549
+ if (entry.type === "message" && entry.message.role === "assistant") {
3550
+ previousDroppedCount = InteractiveMode.countDroppedThinkingBlocks(entry.message);
3551
+ break;
3552
+ }
3553
+ }
3554
+ if (droppedCount <= previousDroppedCount)
3555
+ return;
3556
+ const noun = droppedCount === 1 ? "thinking block" : "thinking blocks";
3557
+ this.chatContainer.addChild(new Spacer(1));
3558
+ this.chatContainer.addChild(new Text(theme.fg("warning", `Anthropic dropped ${droppedCount} ${noun} (details in session)`), 1, 0));
3559
+ }
3385
3560
  /**
3386
3561
  * Show a transcript notice when a completed assistant message paid for a
3387
3562
  * significant cache miss. Only states observable facts: the miss itself,
@@ -3548,6 +3723,12 @@ export class InteractiveMode {
3548
3723
  catch { }
3549
3724
  console.error("apex-code exiting due to uncaughtException:");
3550
3725
  console.error(error);
3726
+ const extensionHint = this.getCrashExtensionHint(error);
3727
+ if (extensionHint)
3728
+ console.error(`\n${extensionHint}`);
3729
+ if (this.recordCrash("uncaught_exception", error)) {
3730
+ console.error(`\n${this.crashReportInstructions()}`);
3731
+ }
3551
3732
  process.exit(1);
3552
3733
  }
3553
3734
  /**
@@ -3683,6 +3864,7 @@ export class InteractiveMode {
3683
3864
  const level = this.session.thinkingLevel || "off";
3684
3865
  this.editor.borderColor = theme.getThinkingBorderColor(level);
3685
3866
  }
3867
+ this.activeStatusIndicator?.invalidate();
3686
3868
  this.ui.requestRender();
3687
3869
  }
3688
3870
  cycleThinkingLevel() {
@@ -3728,27 +3910,40 @@ export class InteractiveMode {
3728
3910
  if (hasEditDiffsExpansion(component))
3729
3911
  component.setEditDiffsExpanded(this.editDiffsExpanded);
3730
3912
  }
3913
+ /** The expand key. A level the user picks is saved, so the next session opens at it. */
3731
3914
  cycleChatDetail() {
3732
- this.setChatDetail(nextChatDetail(this.chatDetail));
3915
+ this.chooseChatDetail(nextChatDetail(this.chatDetail));
3916
+ }
3917
+ chooseChatDetail(detail) {
3918
+ this.settingsManager.setChatDetail(detail);
3919
+ this.setChatDetail(detail);
3920
+ }
3921
+ /** Adopt a saved level before the transcript renders; unset opens collapsed. */
3922
+ loadChatDetail(saved) {
3923
+ this.assignChatDetail(saved ?? INITIAL_CHAT_DETAIL);
3924
+ }
3925
+ assignChatDetail(detail) {
3926
+ const view = chatDetailView(detail);
3927
+ this.chatDetail = detail;
3928
+ this.toolOutputExpanded = view.toolOutputExpanded;
3929
+ this.editDiffsExpanded = view.editDiffsExpanded;
3733
3930
  }
3734
3931
  /**
3735
- * Presentation only. This never rewrites messages, settings, or the session
3736
- * transcript, which is why it may override the persisted thinking preference
3737
- * at `all` without saving that override.
3932
+ * Presentation only. This never rewrites messages or the session transcript,
3933
+ * and never the hide-thinking preference, which `all` overrides unsaved.
3934
+ * Saving the level itself is `chooseChatDetail`'s job, for user choices only.
3738
3935
  */
3739
3936
  setChatDetail(detail) {
3740
3937
  if (detail === this.chatDetail)
3741
3938
  return;
3742
- this.chatDetail = detail;
3743
- const view = chatDetailView(detail);
3744
- this.toolOutputExpanded = view.toolOutputExpanded;
3745
- this.editDiffsExpanded = view.editDiffsExpanded;
3939
+ this.assignChatDetail(detail);
3746
3940
  this.applyChatDetail();
3747
3941
  this.showStatus(`Conversation detail: ${detail}`);
3748
3942
  }
3749
3943
  /** True when thinking blocks are hidden right now, setting and detail level combined. */
3750
3944
  isThinkingHidden() {
3751
- return this.hideThinkingBlock && !chatDetailView(this.chatDetail).revealThinking;
3945
+ const { thinking } = chatDetailView(this.chatDetail);
3946
+ return thinking === "preference" ? this.hideThinkingBlock : thinking === "collapsed";
3752
3947
  }
3753
3948
  applyChatDetail() {
3754
3949
  const activeHeader = this.customHeader ?? this.builtInHeader;
@@ -3775,7 +3970,8 @@ export class InteractiveMode {
3775
3970
  *
3776
3971
  * Collapsing maps to `details` rather than `overview`, because an extension
3777
3972
  * asking to collapse tool output has not asked to hide edit diffs, and
3778
- * `details` is the level where only tool output is collapsed.
3973
+ * `details` is the level where only tool output is collapsed. Not saved: an
3974
+ * extension's request should not decide how the next session opens.
3779
3975
  */
3780
3976
  setToolsExpanded(expanded) {
3781
3977
  this.setChatDetail(expanded ? "all" : "details");
@@ -3791,8 +3987,16 @@ export class InteractiveMode {
3791
3987
  }
3792
3988
  toggleThinkingBlockVisibility() {
3793
3989
  this.offerFirstUseHint("thinking");
3794
- this.hideThinkingBlock = !this.hideThinkingBlock;
3795
- this.settingsManager.setHideThinkingBlock(this.hideThinkingBlock);
3990
+ if (this.chatDetail === "overview") {
3991
+ // Overview hides thinking regardless of the preference. Treat the toggle as a request to show it.
3992
+ this.hideThinkingBlock = false;
3993
+ this.settingsManager.setHideThinkingBlock(false);
3994
+ this.chooseChatDetail("details");
3995
+ }
3996
+ else {
3997
+ this.hideThinkingBlock = !this.hideThinkingBlock;
3998
+ this.settingsManager.setHideThinkingBlock(this.hideThinkingBlock);
3999
+ }
3796
4000
  this.updateThinkingBlockVisibility();
3797
4001
  this.showStatus(`Thinking blocks: ${this.hideThinkingBlock ? "hidden" : "visible"}`);
3798
4002
  }
@@ -3821,9 +4025,9 @@ export class InteractiveMode {
3821
4025
  this.editor.setText("");
3822
4026
  this.ui.requestRender();
3823
4027
  }
3824
- showError(errorMessage) {
4028
+ showError(errorMessage, outcome) {
3825
4029
  this.chatContainer.addChild(new Spacer(1));
3826
- this.chatContainer.addChild(new Text(theme.fg("error", `Error: ${errorMessage}`), this.outputPad, 0));
4030
+ this.chatContainer.addChild(errorText(`Error: ${errorMessage}`, this.toolOutputExpanded, this.outputPad, outcome));
3827
4031
  this.ui.requestRender();
3828
4032
  }
3829
4033
  showWarning(warningMessage) {
@@ -3922,7 +4126,7 @@ export class InteractiveMode {
3922
4126
  if (allQueued.length === 0) {
3923
4127
  this.updatePendingMessagesDisplay();
3924
4128
  if (options?.abort) {
3925
- this.agent.abort();
4129
+ void this.session.abort();
3926
4130
  }
3927
4131
  return 0;
3928
4132
  }
@@ -3932,7 +4136,7 @@ export class InteractiveMode {
3932
4136
  this.editor.setText(combinedText);
3933
4137
  this.updatePendingMessagesDisplay();
3934
4138
  if (options?.abort) {
3935
- this.agent.abort();
4139
+ void this.session.abort();
3936
4140
  }
3937
4141
  return allQueued.length;
3938
4142
  }
@@ -4132,6 +4336,7 @@ export class InteractiveMode {
4132
4336
  followUpMode: this.session.followUpMode,
4133
4337
  transport: this.settingsManager.getTransport(),
4134
4338
  httpIdleTimeoutMs: this.settingsManager.getHttpIdleTimeoutMs(),
4339
+ cacheWarmingMode: this.settingsManager.getCacheWarmingMode(),
4135
4340
  thinkingLevel: this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL,
4136
4341
  availableThinkingLevels: [...THINKING_LEVEL_OPTIONS],
4137
4342
  modelThinkingLevels: this.settingsManager.getAllModelThinkingLevels(),
@@ -4150,6 +4355,7 @@ export class InteractiveMode {
4150
4355
  permissionMode,
4151
4356
  editorPaddingX: this.settingsManager.getEditorPaddingX(),
4152
4357
  outputPad: this.settingsManager.getOutputPad(),
4358
+ chatDetail: this.chatDetail,
4153
4359
  autocompleteMaxVisible: this.settingsManager.getAutocompleteMaxVisible(),
4154
4360
  quietStartup: this.settingsManager.getQuietStartup(),
4155
4361
  clearOnShrink: this.settingsManager.getClearOnShrink(),
@@ -4205,6 +4411,10 @@ export class InteractiveMode {
4205
4411
  configureHttpDispatcher(timeoutMs);
4206
4412
  this.showStatus(`HTTP idle timeout: ${formatHttpIdleTimeoutMs(timeoutMs)}`);
4207
4413
  },
4414
+ onCacheWarmingModeChange: (mode) => {
4415
+ this.session.setCacheWarmingMode(mode);
4416
+ this.showStatus(`Cache warming: ${mode}`);
4417
+ },
4208
4418
  onModelThinkingLevelChange: (provider, modelId, level) => {
4209
4419
  this.settingsManager.setModelThinkingLevel(provider, modelId, level);
4210
4420
  // If the override is for the current model, apply it to the session too
@@ -4231,6 +4441,7 @@ export class InteractiveMode {
4231
4441
  void this.themeController.setThemeSetting(themeSetting);
4232
4442
  },
4233
4443
  onThemePreview: (themeName) => this.themeController.preview(themeName),
4444
+ onChatDetailChange: (detail) => this.chooseChatDetail(detail),
4234
4445
  onHideThinkingBlockChange: (hidden) => {
4235
4446
  this.hideThinkingBlock = hidden;
4236
4447
  this.settingsManager.setHideThinkingBlock(hidden);
@@ -4766,6 +4977,11 @@ export class InteractiveMode {
4766
4977
  this.restoreQueuedMessagesToEditor();
4767
4978
  await this.session.abort();
4768
4979
  }
4980
+ // Recheck after the dialogs and streaming abort, before replacing another operation's UI.
4981
+ if (this.session.isCompacting) {
4982
+ this.showError("Wait for the current compaction or tree navigation to finish before navigating the session tree.");
4983
+ return;
4984
+ }
4769
4985
  // Set up escape handler and status indicator if summarizing
4770
4986
  let showingSummaryIndicator = false;
4771
4987
  const originalOnEscape = this.defaultEditor.onEscape;
@@ -4836,9 +5052,9 @@ export class InteractiveMode {
4836
5052
  }
4837
5053
  showSessionSelector() {
4838
5054
  this.showSelector((done) => {
4839
- const selector = new SessionSelectorComponent((onProgress) => SessionManager.list(this.sessionManager.getCwd(), this.sessionManager.getSessionDir(), onProgress), (onProgress) => this.sessionManager.usesDefaultSessionDir()
4840
- ? SessionManager.listAll(onProgress)
4841
- : SessionManager.listAll(this.sessionManager.getSessionDir(), onProgress), async (sessionPath) => {
5055
+ const selector = new SessionSelectorComponent((onProgress, signal) => SessionManager.list(this.sessionManager.getCwd(), this.sessionManager.getSessionDir(), onProgress, signal), (onProgress, signal) => this.sessionManager.usesDefaultSessionDir()
5056
+ ? SessionManager.listAll(onProgress, signal)
5057
+ : SessionManager.listAll(this.sessionManager.getSessionDir(), onProgress, signal), async (sessionPath) => {
4842
5058
  done();
4843
5059
  await this.handleResumeSession(sessionPath);
4844
5060
  }, () => {
@@ -5122,67 +5338,89 @@ export class InteractiveMode {
5122
5338
  }
5123
5339
  async completeProviderAuthentication(providerId, providerName, authType, previousModel) {
5124
5340
  const actionLabel = authType === "oauth" ? `Logged in to ${providerName}` : `Saved API key for ${providerName}`;
5125
- let selectedModel;
5126
- let selectionError;
5127
- if (isUnknownModel(previousModel)) {
5128
- const availableModels = this.session.modelRuntime.getAvailableSnapshot();
5129
- const providerModels = availableModels.filter((model) => model.provider === providerId);
5130
- // Matches LLAMA_PROVIDER_ID from extensions/llama/provider.ts; kept inline to avoid coupling interactive mode to the built-in extension.
5131
- if (providerId === "llama.cpp") {
5132
- selectionError = llamaCppPostLoginGuidance(actionLabel, providerModels.length);
5133
- }
5134
- else if (!hasDefaultModelProvider(providerId)) {
5135
- selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
5136
- }
5137
- else if (providerModels.length === 0) {
5138
- selectionError = `${actionLabel}, but no models are available for that provider. Use /model to select a model.`;
5139
- }
5140
- else {
5141
- const defaultModelId = defaultModelPerProvider[providerId];
5142
- selectedModel = providerModels.find((model) => model.id === defaultModelId);
5143
- if (!selectedModel) {
5144
- selectionError = `${actionLabel}, but its default model "${defaultModelId}" is not available. Use /model to select a model.`;
5341
+ const session = this.session;
5342
+ // Dynamic catalogs may be empty until the first authenticated network refresh.
5343
+ const deferSelection = isUnknownModel(previousModel) &&
5344
+ hasDefaultModelProvider(providerId) &&
5345
+ !session.modelRuntime
5346
+ .getAvailableSnapshot()
5347
+ .some((model) => model.provider === providerId && model.id === defaultModelPerProvider[providerId]);
5348
+ const finishAuthentication = async () => {
5349
+ let selectedModel;
5350
+ let selectionError;
5351
+ if (isUnknownModel(previousModel)) {
5352
+ const availableModels = this.session.modelRuntime.getAvailableSnapshot();
5353
+ const providerModels = availableModels.filter((model) => model.provider === providerId);
5354
+ // Matches LLAMA_PROVIDER_ID from extensions/llama/provider.ts; kept inline to avoid coupling interactive mode to the built-in extension.
5355
+ if (providerId === "llama.cpp") {
5356
+ selectionError = llamaCppPostLoginGuidance(actionLabel, providerModels.length);
5357
+ }
5358
+ else if (!hasDefaultModelProvider(providerId)) {
5359
+ selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
5360
+ }
5361
+ else if (providerModels.length === 0) {
5362
+ selectionError = `${actionLabel}, but no models are available for that provider. Use /model to select a model.`;
5145
5363
  }
5146
5364
  else {
5147
- try {
5148
- await this.session.setModel(selectedModel, { persist: true });
5365
+ const defaultModelId = defaultModelPerProvider[providerId];
5366
+ // Radius catalogs vary by account; prefer balanced, then use catalog order.
5367
+ selectedModel =
5368
+ providerModels.find((model) => model.id === defaultModelId) ??
5369
+ (providerId === "radius" ? providerModels[0] : undefined);
5370
+ if (!selectedModel) {
5371
+ selectionError = `${actionLabel}, but its default model "${defaultModelId}" is not available. Use /model to select a model.`;
5149
5372
  }
5150
- catch (error) {
5151
- selectedModel = undefined;
5152
- const errorMessage = error instanceof Error ? error.message : String(error);
5153
- selectionError = `${actionLabel}, but selecting its default model failed: ${errorMessage}. Use /model to select a model.`;
5373
+ else {
5374
+ try {
5375
+ await this.session.setModel(selectedModel, { persist: true });
5376
+ }
5377
+ catch (error) {
5378
+ selectedModel = undefined;
5379
+ const errorMessage = error instanceof Error ? error.message : String(error);
5380
+ selectionError = `${actionLabel}, but selecting its default model failed: ${errorMessage}. Use /model to select a model.`;
5381
+ }
5154
5382
  }
5155
5383
  }
5156
5384
  }
5157
- }
5158
- await this.updateAvailableProviderCount();
5159
- this.footer.invalidate();
5160
- this.updateEditorBorderColor();
5161
- if (selectedModel) {
5162
- this.showStatus(`${actionLabel}. Selected ${selectedModel.id}. Credentials saved to ${getAuthPath()}`);
5163
- void this.maybeWarnAboutAnthropicSubscriptionAuth(selectedModel);
5164
- this.checkDaxnutsEasterEgg(selectedModel);
5165
- }
5166
- else {
5167
- this.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}`);
5168
- if (selectionError) {
5169
- this.showError(selectionError);
5385
+ await this.updateAvailableProviderCount();
5386
+ this.footer.invalidate();
5387
+ this.updateEditorBorderColor();
5388
+ if (selectedModel) {
5389
+ this.showStatus(`${actionLabel}. Selected ${selectedModel.id}. Credentials saved to ${getAuthPath()}`);
5390
+ void this.maybeWarnAboutAnthropicSubscriptionAuth(selectedModel);
5391
+ this.checkDaxnutsEasterEgg(selectedModel);
5170
5392
  }
5171
5393
  else {
5172
- void this.maybeWarnAboutAnthropicSubscriptionAuth();
5394
+ this.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}`);
5395
+ if (selectionError) {
5396
+ this.showError(selectionError);
5397
+ }
5398
+ else {
5399
+ void this.maybeWarnAboutAnthropicSubscriptionAuth();
5400
+ }
5173
5401
  }
5402
+ };
5403
+ if (deferSelection) {
5404
+ this.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}. Refreshing model catalog…`);
5405
+ }
5406
+ else {
5407
+ await finishAuthentication();
5174
5408
  }
5175
5409
  const controller = new AbortController();
5176
5410
  const timeout = setTimeout(() => controller.abort(), 15_000);
5177
- void this.session.modelRuntime
5411
+ void session.modelRuntime
5178
5412
  .refresh({ providers: [providerId], signal: controller.signal })
5179
- .then((result) => {
5413
+ .then(async (result) => {
5180
5414
  if (result.aborted) {
5181
5415
  this.showWarning(`${actionLabel}, but its model catalog refresh timed out; using cached models.`);
5182
5416
  }
5183
5417
  else if (result.errors.size > 0) {
5184
5418
  this.showWarning(`${actionLabel}, but its model catalog could not be refreshed; using cached models.`);
5185
5419
  }
5420
+ // Do not replace a model or session selected while the refresh was running.
5421
+ if (deferSelection && this.session === session && session.model === previousModel) {
5422
+ await finishAuthentication();
5423
+ }
5186
5424
  this.updateAvailableProviderCount();
5187
5425
  this.footer.invalidate();
5188
5426
  this.ui.requestRender();
@@ -5364,12 +5602,11 @@ export class InteractiveMode {
5364
5602
  }
5365
5603
  this.resetExtensionUI();
5366
5604
  const reloadBox = new Container();
5367
- const borderColor = (s) => theme.fg("border", s);
5368
- reloadBox.addChild(new DynamicBorder(borderColor));
5605
+ reloadBox.addChild(new DynamicBorder());
5369
5606
  reloadBox.addChild(new Spacer(1));
5370
- reloadBox.addChild(new Text(theme.fg("muted", "Reloading keybindings, extensions, skills, prompts, themes, and context files..."), 1, 0));
5607
+ reloadBox.addChild(new Text(theme.fg("muted", "Reloading keybindings, extensions, skills, prompts, themes, and context files..."), 0, 0));
5371
5608
  reloadBox.addChild(new Spacer(1));
5372
- reloadBox.addChild(new DynamicBorder(borderColor));
5609
+ reloadBox.addChild(new DynamicBorder());
5373
5610
  const previousEditor = this.editor;
5374
5611
  this.editorContainer.clear();
5375
5612
  this.editorContainer.addChild(reloadBox);
@@ -5389,6 +5626,7 @@ export class InteractiveMode {
5389
5626
  return;
5390
5627
  }
5391
5628
  this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
5629
+ this.loadChatDetail(this.settingsManager.getChatDetail());
5392
5630
  this.outputPad = this.settingsManager.getOutputPad();
5393
5631
  this.rebuildChatFromMessages();
5394
5632
  chatRestoredBeforeSessionStart = true;
@@ -5586,6 +5824,17 @@ export class InteractiveMode {
5586
5824
  }
5587
5825
  }
5588
5826
  }
5827
+ async handleBugCommand(hint) {
5828
+ await reportBug({
5829
+ session: this.session,
5830
+ ui: this.ui,
5831
+ editorContainer: this.editorContainer,
5832
+ editor: this.editor,
5833
+ keybindings: this.keybindings,
5834
+ showStatus: (message) => this.showStatus(message),
5835
+ showError: (message) => this.showError(message),
5836
+ }, hint);
5837
+ }
5589
5838
  async handleCopyCommand(options = {}) {
5590
5839
  if (options.preferSelection &&
5591
5840
  this.ui instanceof TuiAltScreen &&
@@ -5671,6 +5920,15 @@ export class InteractiveMode {
5671
5920
  }
5672
5921
  info += `${theme.fg("dim", "Output:")} ${stats.tokens.output.toLocaleString()}\n`;
5673
5922
  info += `${theme.fg("dim", "Total:")} ${stats.tokens.total.toLocaleString()}\n`;
5923
+ const cacheWarmingStatus = this.session.cacheWarmingStatus;
5924
+ info += `\n${theme.bold("Cache Warming")}\n`;
5925
+ info += `${theme.fg("dim", "Mode:")} ${this.settingsManager.getCacheWarmingMode()}\n`;
5926
+ info += `${theme.fg("dim", "Status:")} ${cacheWarmingStatus ? formatCacheWarmingStatus(cacheWarmingStatus) : "Inactive (cache warming unavailable)"}\n`;
5927
+ const decision = cacheWarmingStatus?.decision;
5928
+ if (decision?.economicsAvailable) {
5929
+ info += `${theme.fg("dim", "Cache miss penalty:")} $${decision.missCost.toFixed(3)}\n`;
5930
+ info += `${theme.fg("dim", "Refresh cost:")} $${decision.warmCost.toFixed(3)}\n`;
5931
+ }
5674
5932
  if (stats.cost > 0 || cacheWaste.missedTokens > 0) {
5675
5933
  info += `\n${theme.bold("Cost")}\n`;
5676
5934
  info += `${theme.fg("dim", "Total:")} $${stats.cost.toFixed(3)}`;
@@ -5723,7 +5981,7 @@ export class InteractiveMode {
5723
5981
  : "No changelog entries found.";
5724
5982
  this.chatContainer.addChild(new Spacer(1));
5725
5983
  this.chatContainer.addChild(new DynamicBorder());
5726
- this.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", "What's New")), 1, 0));
5984
+ this.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", "What's new")), 1, 0));
5727
5985
  this.chatContainer.addChild(new Spacer(1));
5728
5986
  this.chatContainer.addChild(new Markdown(changelogMarkdown, 1, 1, this.getMarkdownThemeWithSettings()));
5729
5987
  this.chatContainer.addChild(new DynamicBorder());
@@ -5821,7 +6079,7 @@ export class InteractiveMode {
5821
6079
  | \`${expandTools}\` | Cycle conversation detail (overview / details / all) |
5822
6080
  | \`${toggleThinking}\` | Toggle thinking block visibility |
5823
6081
  | \`${externalEditor}\` | Edit message in external editor |
5824
- | \`${copyMessage}\` | Copy last assistant message |
6082
+ | \`${copyMessage}\` | Copy selection or last assistant message |
5825
6083
  | \`${followUp}\` | Queue follow-up message |
5826
6084
  | \`${dequeue}\` | Restore queued messages |
5827
6085
  | \`${pasteImage}\` | Paste image or text from clipboard |
@@ -5952,12 +6210,19 @@ export class InteractiveMode {
5952
6210
  async runBashCommand(command, excludeFromContext = false) {
5953
6211
  const extensionRunner = this.session.extensionRunner;
5954
6212
  // Emit user_bash event to let extensions intercept
5955
- const eventResult = await extensionRunner.emitUserBash({
5956
- type: "user_bash",
5957
- command,
5958
- excludeFromContext,
5959
- cwd: this.sessionManager.getCwd(),
5960
- });
6213
+ let eventResult;
6214
+ try {
6215
+ eventResult = await extensionRunner.emitUserBash({
6216
+ type: "user_bash",
6217
+ command,
6218
+ excludeFromContext,
6219
+ cwd: this.sessionManager.getCwd(),
6220
+ });
6221
+ }
6222
+ catch {
6223
+ // The extension runner already reported the error. Do not fall back to local execution.
6224
+ return;
6225
+ }
5961
6226
  // If extension returned a full result, use it directly
5962
6227
  if (eventResult?.result) {
5963
6228
  const result = eventResult.result;