apex-code 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (512) hide show
  1. package/CHANGELOG.md +263 -5
  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 +55 -7
  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 +7 -8
  176. package/dist/core/tools/bash.d.ts.map +1 -1
  177. package/dist/core/tools/bash.js +20 -115
  178. package/dist/core/tools/bash.js.map +1 -1
  179. package/dist/core/tools/edit.d.ts +1 -12
  180. package/dist/core/tools/edit.d.ts.map +1 -1
  181. package/dist/core/tools/edit.js +6 -204
  182. package/dist/core/tools/edit.js.map +1 -1
  183. package/dist/core/tools/find.d.ts.map +1 -1
  184. package/dist/core/tools/find.js +4 -55
  185. package/dist/core/tools/find.js.map +1 -1
  186. package/dist/core/tools/grep.d.ts.map +1 -1
  187. package/dist/core/tools/grep.js +4 -60
  188. package/dist/core/tools/grep.js.map +1 -1
  189. package/dist/core/tools/ls.d.ts.map +1 -1
  190. package/dist/core/tools/ls.js +4 -49
  191. package/dist/core/tools/ls.js.map +1 -1
  192. package/dist/core/tools/read.d.ts +4 -1
  193. package/dist/core/tools/read.d.ts.map +1 -1
  194. package/dist/core/tools/read.js +10 -125
  195. package/dist/core/tools/read.js.map +1 -1
  196. package/dist/core/tools/renderers/bash.d.ts +18 -0
  197. package/dist/core/tools/renderers/bash.d.ts.map +1 -0
  198. package/dist/core/tools/renderers/bash.js +126 -0
  199. package/dist/core/tools/renderers/bash.js.map +1 -0
  200. package/dist/core/tools/renderers/edit.d.ts +23 -0
  201. package/dist/core/tools/renderers/edit.d.ts.map +1 -0
  202. package/dist/core/tools/renderers/edit.js +207 -0
  203. package/dist/core/tools/renderers/edit.js.map +1 -0
  204. package/dist/core/tools/renderers/find.d.ts +10 -0
  205. package/dist/core/tools/renderers/find.d.ts.map +1 -0
  206. package/dist/core/tools/renderers/find.js +64 -0
  207. package/dist/core/tools/renderers/find.js.map +1 -0
  208. package/dist/core/tools/renderers/grep.d.ts +10 -0
  209. package/dist/core/tools/renderers/grep.d.ts.map +1 -0
  210. package/dist/core/tools/renderers/grep.js +69 -0
  211. package/dist/core/tools/renderers/grep.js.map +1 -0
  212. package/dist/core/tools/renderers/index.d.ts +34 -0
  213. package/dist/core/tools/renderers/index.d.ts.map +1 -0
  214. package/dist/core/tools/renderers/index.js +47 -0
  215. package/dist/core/tools/renderers/index.js.map +1 -0
  216. package/dist/core/tools/renderers/ls.d.ts +10 -0
  217. package/dist/core/tools/renderers/ls.d.ts.map +1 -0
  218. package/dist/core/tools/renderers/ls.js +58 -0
  219. package/dist/core/tools/renderers/ls.js.map +1 -0
  220. package/dist/core/tools/renderers/read.d.ts +11 -0
  221. package/dist/core/tools/renderers/read.d.ts.map +1 -0
  222. package/dist/core/tools/renderers/read.js +132 -0
  223. package/dist/core/tools/renderers/read.js.map +1 -0
  224. package/dist/core/tools/renderers/write.d.ts +10 -0
  225. package/dist/core/tools/renderers/write.d.ts.map +1 -0
  226. package/dist/core/tools/renderers/write.js +152 -0
  227. package/dist/core/tools/renderers/write.js.map +1 -0
  228. package/dist/core/tools/write.d.ts.map +1 -1
  229. package/dist/core/tools/write.js +6 -146
  230. package/dist/core/tools/write.js.map +1 -1
  231. package/dist/core/usage-totals.d.ts +1 -1
  232. package/dist/core/usage-totals.d.ts.map +1 -1
  233. package/dist/core/usage-totals.js +5 -1
  234. package/dist/core/usage-totals.js.map +1 -1
  235. package/dist/extensions/llama/client.d.ts +2 -0
  236. package/dist/extensions/llama/client.d.ts.map +1 -1
  237. package/dist/extensions/llama/client.js +7 -3
  238. package/dist/extensions/llama/client.js.map +1 -1
  239. package/dist/extensions/llama/provider.d.ts.map +1 -1
  240. package/dist/extensions/llama/provider.js +20 -5
  241. package/dist/extensions/llama/provider.js.map +1 -1
  242. package/dist/index.d.ts +5 -4
  243. package/dist/index.d.ts.map +1 -1
  244. package/dist/index.js +1 -1
  245. package/dist/index.js.map +1 -1
  246. package/dist/main.d.ts.map +1 -1
  247. package/dist/main.js +21 -13
  248. package/dist/main.js.map +1 -1
  249. package/dist/modes/interactive/bug-report.d.ts +16 -0
  250. package/dist/modes/interactive/bug-report.d.ts.map +1 -0
  251. package/dist/modes/interactive/bug-report.js +167 -0
  252. package/dist/modes/interactive/bug-report.js.map +1 -0
  253. package/dist/modes/interactive/chat-viewport.d.ts +20 -0
  254. package/dist/modes/interactive/chat-viewport.d.ts.map +1 -0
  255. package/dist/modes/interactive/chat-viewport.js +28 -0
  256. package/dist/modes/interactive/chat-viewport.js.map +1 -0
  257. package/dist/modes/interactive/components/assistant-message.d.ts +7 -0
  258. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  259. package/dist/modes/interactive/components/assistant-message.js +63 -17
  260. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  261. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  262. package/dist/modes/interactive/components/branch-summary-message.js +12 -5
  263. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  264. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  265. package/dist/modes/interactive/components/compaction-summary-message.js +12 -5
  266. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  267. package/dist/modes/interactive/components/custom-editor.d.ts +12 -0
  268. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  269. package/dist/modes/interactive/components/custom-editor.js +38 -0
  270. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  271. package/dist/modes/interactive/components/error-summary.d.ts +11 -0
  272. package/dist/modes/interactive/components/error-summary.d.ts.map +1 -0
  273. package/dist/modes/interactive/components/error-summary.js +31 -0
  274. package/dist/modes/interactive/components/error-summary.js.map +1 -0
  275. package/dist/modes/interactive/components/extension-editor.d.ts +4 -1
  276. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  277. package/dist/modes/interactive/components/extension-editor.js +6 -1
  278. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  279. package/dist/modes/interactive/components/extension-input.d.ts +2 -0
  280. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  281. package/dist/modes/interactive/components/extension-input.js +6 -0
  282. package/dist/modes/interactive/components/extension-input.js.map +1 -1
  283. package/dist/modes/interactive/components/extension-selector.d.ts +2 -0
  284. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  285. package/dist/modes/interactive/components/extension-selector.js +4 -0
  286. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  287. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  288. package/dist/modes/interactive/components/footer.js +4 -1
  289. package/dist/modes/interactive/components/footer.js.map +1 -1
  290. package/dist/modes/interactive/components/index.d.ts +1 -1
  291. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  292. package/dist/modes/interactive/components/index.js.map +1 -1
  293. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  294. package/dist/modes/interactive/components/model-selector.js +3 -3
  295. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  296. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  297. package/dist/modes/interactive/components/scoped-models-selector.js +13 -15
  298. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  299. package/dist/modes/interactive/components/session-selector.d.ts +5 -5
  300. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  301. package/dist/modes/interactive/components/session-selector.js +74 -48
  302. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  303. package/dist/modes/interactive/components/settings-selector.d.ts +5 -1
  304. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  305. package/dist/modes/interactive/components/settings-selector.js +35 -10
  306. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  307. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  308. package/dist/modes/interactive/components/skill-invocation-message.js +11 -4
  309. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  310. package/dist/modes/interactive/components/status-indicator.d.ts +3 -1
  311. package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -1
  312. package/dist/modes/interactive/components/status-indicator.js +10 -3
  313. package/dist/modes/interactive/components/status-indicator.js.map +1 -1
  314. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  315. package/dist/modes/interactive/components/thinking-selector.js +6 -6
  316. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  317. package/dist/modes/interactive/components/tool-execution.d.ts +27 -5
  318. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  319. package/dist/modes/interactive/components/tool-execution.js +64 -42
  320. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  321. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  322. package/dist/modes/interactive/components/tree-selector.js +9 -0
  323. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  324. package/dist/modes/interactive/components/trust-selector.d.ts.map +1 -1
  325. package/dist/modes/interactive/components/trust-selector.js +2 -2
  326. package/dist/modes/interactive/components/trust-selector.js.map +1 -1
  327. package/dist/modes/interactive/interactive-mode.d.ts +43 -29
  328. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  329. package/dist/modes/interactive/interactive-mode.js +431 -166
  330. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  331. package/dist/modes/interactive/theme/dark.json +2 -1
  332. package/dist/modes/interactive/theme/light.json +2 -1
  333. package/dist/modes/interactive/theme/theme-controller.d.ts +1 -0
  334. package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
  335. package/dist/modes/interactive/theme/theme-controller.js +5 -0
  336. package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
  337. package/dist/modes/interactive/theme/theme-json.d.ts +84 -0
  338. package/dist/modes/interactive/theme/theme-json.d.ts.map +1 -0
  339. package/dist/modes/interactive/theme/theme-json.js +130 -0
  340. package/dist/modes/interactive/theme/theme-json.js.map +1 -0
  341. package/dist/modes/interactive/theme/theme-schema.json +6 -2
  342. package/dist/modes/interactive/theme/theme.d.ts +14 -5
  343. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  344. package/dist/modes/interactive/theme/theme.js +18 -119
  345. package/dist/modes/interactive/theme/theme.js.map +1 -1
  346. package/dist/modes/interactive/tui-renderer.d.ts +21 -0
  347. package/dist/modes/interactive/tui-renderer.d.ts.map +1 -0
  348. package/dist/modes/interactive/tui-renderer.js +66 -0
  349. package/dist/modes/interactive/tui-renderer.js.map +1 -0
  350. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  351. package/dist/modes/rpc/rpc-mode.js +2 -2
  352. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  353. package/dist/package-manager-cli.d.ts.map +1 -1
  354. package/dist/package-manager-cli.js +31 -72
  355. package/dist/package-manager-cli.js.map +1 -1
  356. package/dist/testing/replay/recorded-provider.d.ts +2 -2
  357. package/dist/testing/replay/recorded-provider.d.ts.map +1 -1
  358. package/dist/testing/replay/recorded-provider.js +2 -5
  359. package/dist/testing/replay/recorded-provider.js.map +1 -1
  360. package/dist/testing/replay/runner.d.ts.map +1 -1
  361. package/dist/testing/replay/runner.js +0 -1
  362. package/dist/testing/replay/runner.js.map +1 -1
  363. package/dist/utils/clipboard-command.d.ts +7 -0
  364. package/dist/utils/clipboard-command.d.ts.map +1 -0
  365. package/dist/utils/clipboard-command.js +45 -0
  366. package/dist/utils/clipboard-command.js.map +1 -0
  367. package/dist/utils/clipboard-image.d.ts.map +1 -1
  368. package/dist/utils/clipboard-image.js +53 -81
  369. package/dist/utils/clipboard-image.js.map +1 -1
  370. package/dist/utils/clipboard.d.ts.map +1 -1
  371. package/dist/utils/clipboard.js +111 -121
  372. package/dist/utils/clipboard.js.map +1 -1
  373. package/dist/utils/exif-orientation.d.ts.map +1 -1
  374. package/dist/utils/exif-orientation.js +2 -3
  375. package/dist/utils/exif-orientation.js.map +1 -1
  376. package/dist/utils/mime.d.ts.map +1 -1
  377. package/dist/utils/mime.js +1 -1
  378. package/dist/utils/mime.js.map +1 -1
  379. package/dist/utils/syntax-highlight.d.ts.map +1 -1
  380. package/dist/utils/syntax-highlight.js +21 -21
  381. package/dist/utils/syntax-highlight.js.map +1 -1
  382. package/dist/utils/tool-result-images.d.ts +3 -1
  383. package/dist/utils/tool-result-images.d.ts.map +1 -1
  384. package/dist/utils/tool-result-images.js +4 -1
  385. package/dist/utils/tool-result-images.js.map +1 -1
  386. package/dist/utils/tools-manager.d.ts.map +1 -1
  387. package/dist/utils/tools-manager.js +11 -2
  388. package/dist/utils/tools-manager.js.map +1 -1
  389. package/dist/utils/wsl.d.ts +3 -0
  390. package/dist/utils/wsl.d.ts.map +1 -0
  391. package/dist/utils/wsl.js +15 -0
  392. package/dist/utils/wsl.js.map +1 -0
  393. package/dist/utils/zip.d.ts +7 -0
  394. package/dist/utils/zip.d.ts.map +1 -0
  395. package/dist/utils/zip.js +60 -0
  396. package/dist/utils/zip.js.map +1 -0
  397. package/docs/cli-integration.md +107 -0
  398. package/docs/cli.md +269 -0
  399. package/docs/compaction.md +73 -24
  400. package/docs/configuration.md +45 -0
  401. package/docs/containerization.md +22 -21
  402. package/docs/custom-provider.md +21 -12
  403. package/docs/development.md +19 -0
  404. package/docs/docs.json +139 -95
  405. package/docs/extensions.md +243 -371
  406. package/docs/how-pi-works.md +49 -0
  407. package/docs/images/interactive-mode.png +0 -0
  408. package/docs/index.md +3 -3
  409. package/docs/json.md +7 -3
  410. package/docs/keybindings.md +57 -54
  411. package/docs/llama-cpp.md +2 -2
  412. package/docs/message-types.md +261 -0
  413. package/docs/models.md +103 -49
  414. package/docs/packages.md +49 -47
  415. package/docs/prompt-templates.md +29 -56
  416. package/docs/providers.md +91 -139
  417. package/docs/quickstart.md +42 -14
  418. package/docs/rpc-commands.md +854 -0
  419. package/docs/rpc-extension-ui.md +200 -0
  420. package/docs/rpc.md +121 -223
  421. package/docs/sdk.md +99 -124
  422. package/docs/security.md +7 -7
  423. package/docs/session-format.md +85 -82
  424. package/docs/sessions.md +41 -57
  425. package/docs/settings.md +94 -87
  426. package/docs/shell-aliases.md +68 -3
  427. package/docs/skills.md +62 -49
  428. package/docs/slash-commands.md +60 -0
  429. package/docs/terminal-setup.md +86 -55
  430. package/docs/termux.md +65 -75
  431. package/docs/themes.md +64 -86
  432. package/docs/tmux.md +29 -7
  433. package/docs/tui.md +67 -122
  434. package/docs/usage.md +31 -42
  435. package/docs/windows.md +41 -13
  436. package/examples/README.md +16 -2
  437. package/examples/extensions/README.md +0 -1
  438. package/examples/extensions/custom-provider-anthropic/index.ts +18 -12
  439. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  440. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  441. package/examples/extensions/custom-provider-gitlab-duo/index.ts +2 -2
  442. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  443. package/examples/extensions/dynamic-resources/dynamic.json +2 -0
  444. package/examples/extensions/gondolin/package-lock.json +2 -2
  445. package/examples/extensions/gondolin/package.json +1 -1
  446. package/examples/extensions/prompt-customizer.ts +19 -67
  447. package/examples/extensions/sandbox/package-lock.json +2 -2
  448. package/examples/extensions/sandbox/package.json +1 -1
  449. package/examples/extensions/with-deps/package-lock.json +2 -2
  450. package/examples/extensions/with-deps/package.json +1 -1
  451. package/examples/plugins/pi-example-plugin/README.md +38 -0
  452. package/examples/plugins/pi-example-plugin/package.json +10 -0
  453. package/examples/plugins/pi-example-plugin/src/contract.ts +13 -0
  454. package/examples/plugins/pi-example-plugin/src/session.ts +24 -0
  455. package/examples/plugins/pi-example-plugin/src/tui.ts +31 -0
  456. package/examples/rpc-client.ts +35 -0
  457. package/examples/rpc-extension-ui.ts +25 -5
  458. package/examples/sdk/README.md +1 -1
  459. package/npm-shrinkwrap.json +861 -513
  460. package/package.json +27 -18
  461. package/dist/bun/register-bedrock.d.ts +0 -2
  462. package/dist/bun/register-bedrock.d.ts.map +0 -1
  463. package/dist/bun/register-bedrock.js +0 -4
  464. package/dist/bun/register-bedrock.js.map +0 -1
  465. package/dist/cli/experimental/auth.d.ts +0 -16
  466. package/dist/cli/experimental/auth.d.ts.map +0 -1
  467. package/dist/cli/experimental/auth.js +0 -13
  468. package/dist/cli/experimental/auth.js.map +0 -1
  469. package/dist/cli/experimental/cli.d.ts +0 -6
  470. package/dist/cli/experimental/cli.d.ts.map +0 -1
  471. package/dist/cli/experimental/cli.js +0 -5
  472. package/dist/cli/experimental/cli.js.map +0 -1
  473. package/dist/cli/experimental/command-options.d.ts +0 -17
  474. package/dist/cli/experimental/command-options.d.ts.map +0 -1
  475. package/dist/cli/experimental/command-options.js +0 -35
  476. package/dist/cli/experimental/command-options.js.map +0 -1
  477. package/dist/cli/experimental/command.d.ts +0 -63
  478. package/dist/cli/experimental/command.d.ts.map +0 -1
  479. package/dist/cli/experimental/command.js +0 -130
  480. package/dist/cli/experimental/command.js.map +0 -1
  481. package/dist/cli/experimental/commands/client.d.ts +0 -13
  482. package/dist/cli/experimental/commands/client.d.ts.map +0 -1
  483. package/dist/cli/experimental/commands/client.js +0 -25
  484. package/dist/cli/experimental/commands/client.js.map +0 -1
  485. package/dist/cli/experimental/commands/pi.d.ts +0 -15
  486. package/dist/cli/experimental/commands/pi.d.ts.map +0 -1
  487. package/dist/cli/experimental/commands/pi.js +0 -28
  488. package/dist/cli/experimental/commands/pi.js.map +0 -1
  489. package/dist/cli/experimental/commands/server.d.ts +0 -13
  490. package/dist/cli/experimental/commands/server.d.ts.map +0 -1
  491. package/dist/cli/experimental/commands/server.js +0 -25
  492. package/dist/cli/experimental/commands/server.js.map +0 -1
  493. package/dist/cli/experimental/transport-address.d.ts +0 -10
  494. package/dist/cli/experimental/transport-address.d.ts.map +0 -1
  495. package/dist/cli/experimental/transport-address.js +0 -38
  496. package/dist/cli/experimental/transport-address.js.map +0 -1
  497. package/dist/client/index.d.ts +0 -3
  498. package/dist/client/index.d.ts.map +0 -1
  499. package/dist/client/index.js +0 -3
  500. package/dist/client/index.js.map +0 -1
  501. package/dist/client/remote-session.d.ts +0 -53
  502. package/dist/client/remote-session.d.ts.map +0 -1
  503. package/dist/client/remote-session.js +0 -340
  504. package/dist/client/remote-session.js.map +0 -1
  505. package/dist/client/transcript.d.ts +0 -12
  506. package/dist/client/transcript.d.ts.map +0 -1
  507. package/dist/client/transcript.js +0 -98
  508. package/dist/client/transcript.js.map +0 -1
  509. package/dist/utils/clipboard-native.d.ts +0 -11
  510. package/dist/utils/clipboard-native.d.ts.map +0 -1
  511. package/dist/utils/clipboard-native.js +0 -20
  512. 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,6 +47,8 @@ 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";
47
54
  import { describeWithSource } from "./components/autocomplete-source.js";
@@ -56,6 +63,7 @@ import { CustomMessageComponent } from "./components/custom-message.js";
56
63
  import { DaxnutsComponent } from "./components/daxnuts.js";
57
64
  import { DynamicBorder } from "./components/dynamic-border.js";
58
65
  import { EarendilAnnouncementComponent } from "./components/earendil-announcement.js";
66
+ import { collapsedErrorLine, summarizeError } from "./components/error-summary.js";
59
67
  import { ExtensionEditorComponent } from "./components/extension-editor.js";
60
68
  import { ExtensionInputComponent } from "./components/extension-input.js";
61
69
  import { ExtensionSelectorComponent } from "./components/extension-selector.js";
@@ -84,19 +92,28 @@ import { refreshModelCatalogs } from "./model-catalog-refresh.js";
84
92
  import { getModelSearchText } from "./model-search.js";
85
93
  import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, onThemeChange, setRegisteredThemes, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
86
94
  import { InteractiveThemeController } from "./theme/theme-controller.js";
87
- 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";
88
99
  /** The next rung, wrapping from `all` back to `overview`. */
89
100
  export function nextChatDetail(detail) {
90
- 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;
91
102
  }
92
103
  /** The single place the three rungs turn into rendering flags. */
93
104
  export function chatDetailView(detail) {
94
105
  return {
95
106
  toolOutputExpanded: detail === "all",
96
107
  editDiffsExpanded: detail !== "overview",
97
- revealThinking: detail === "all",
108
+ thinking: detail === "overview" ? "collapsed" : detail === "details" ? "preference" : "revealed",
98
109
  };
99
110
  }
111
+ function isWorkingStatusEditor(editor) {
112
+ return ("embedWorkingStatus" in editor &&
113
+ editor.embedWorkingStatus === true &&
114
+ "setWorkingStatusIndicator" in editor &&
115
+ typeof editor.setWorkingStatusIndicator === "function");
116
+ }
100
117
  function isExpandable(obj) {
101
118
  return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
102
119
  }
@@ -118,12 +135,27 @@ class ExpandableText extends Text {
118
135
  this.setText(expanded ? this.getExpandedText() : this.getCollapsedText());
119
136
  }
120
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
+ }
121
150
  function isCustomSessionEntry(item) {
122
151
  return "type" in item && item.type === "custom";
123
152
  }
124
153
  function isCompactionCostNotice(item) {
125
154
  return "type" in item && item.type === "compaction_cost";
126
155
  }
156
+ function isUsageSessionEntry(item) {
157
+ return "type" in item && item.type === "usage";
158
+ }
127
159
  const DEAD_TERMINAL_ERROR_CODES = new Set(["EIO", "EPIPE", "ENOTCONN"]);
128
160
  function isDeadTerminalError(error) {
129
161
  if (!error || typeof error !== "object" || !("code" in error)) {
@@ -132,6 +164,22 @@ function isDeadTerminalError(error) {
132
164
  const code = error.code;
133
165
  return code !== undefined && DEAD_TERMINAL_ERROR_CODES.has(code);
134
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
+ }
135
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.";
136
184
  function isAnthropicSubscriptionAuthKey(apiKey) {
137
185
  return typeof apiKey === "string" && apiKey.startsWith("sk-ant-oat");
@@ -204,61 +252,6 @@ function formatLoginProviderCompletionDescription(provider) {
204
252
  const authTypes = provider.authTypes.map(formatAuthSelectorProviderType).join("/");
205
253
  return provider.name === provider.id ? authTypes : `${provider.name} · ${authTypes}`;
206
254
  }
207
- /** Composition root for selecting the interactive terminal renderer. */
208
- export function createInteractiveTui(options) {
209
- const terminal = options.terminal ?? new ProcessTerminal();
210
- if (options.tuiMode === "fullscreen") {
211
- const styleSearchMatch = (text) => theme.bg("searchMatchBg", theme.fg("searchMatchText", text));
212
- return new TuiAltScreen(terminal, options.showHardwareCursor, options.logDirectory, {
213
- searchMatchStyle: (text) => theme.underline(styleSearchMatch(text)),
214
- searchCurrentMatchStyle: (text) => theme.bold(theme.inverse(styleSearchMatch(text))),
215
- openUrl: openBrowser,
216
- onRightClickPaste: options.onRightClickPaste,
217
- copyOnSelect: options.fullscreenCopyOnSelect,
218
- copySelection: async (text) => {
219
- try {
220
- await copyToClipboard(text);
221
- return true;
222
- }
223
- catch {
224
- return false;
225
- }
226
- },
227
- });
228
- }
229
- return new TuiMainScreen(terminal, options.showHardwareCursor, options.logDirectory);
230
- }
231
- /** Stable reference for components while InteractiveMode replaces the active renderer. */
232
- export function createInteractiveTuiReference(getTui) {
233
- return new Proxy({}, {
234
- get: (_target, property) => {
235
- const tui = getTui();
236
- const value = Reflect.get(tui, property, tui);
237
- if (typeof value !== "function")
238
- return value;
239
- let methodTui = tui;
240
- let method = value;
241
- return (...args) => {
242
- const currentTui = getTui();
243
- if (currentTui !== methodTui) {
244
- const currentMethod = Reflect.get(currentTui, property, currentTui);
245
- if (typeof currentMethod !== "function") {
246
- throw new TypeError(`TUI property ${String(property)} is not callable`);
247
- }
248
- methodTui = currentTui;
249
- method = currentMethod;
250
- }
251
- return Reflect.apply(method, methodTui, args);
252
- };
253
- },
254
- set: (_target, property, value) => {
255
- const tui = getTui();
256
- return Reflect.set(tui, property, value, tui);
257
- },
258
- has: (_target, property) => Reflect.has(getTui(), property),
259
- getPrototypeOf: () => Reflect.getPrototypeOf(getTui()),
260
- });
261
- }
262
255
  /**
263
256
  * Working status rides in the footer tray; everything else keeps its own rows.
264
257
  *
@@ -317,11 +310,12 @@ export class InteractiveMode {
317
310
  onInputCallback;
318
311
  pendingUserInputs = [];
319
312
  activeStatusIndicator = undefined;
313
+ activeWorkingIndicatorEmbedded = false;
320
314
  idleStatus = new IdleStatus();
321
315
  workingMessage = undefined;
322
316
  workingVisible = true;
323
317
  workingIndicatorOptions = undefined;
324
- defaultWorkingMessage = "Working...";
318
+ defaultWorkingMessage = "Working";
325
319
  defaultHiddenThinkingLabel = "Thinking...";
326
320
  hiddenThinkingLabel = this.defaultHiddenThinkingLabel;
327
321
  lastSigintTime = 0;
@@ -335,13 +329,14 @@ export class InteractiveMode {
335
329
  managedToolStatusStarted = false;
336
330
  // Streaming message tracking
337
331
  streamingComponent = undefined;
332
+ entriesRenderedByBoundaryCompaction = new Set();
338
333
  streamingMessage = undefined;
339
334
  // Tool execution tracking: toolCallId -> component
340
335
  pendingTools = new Map();
341
336
  // Tool output expansion state
342
- chatDetail = "details";
343
- toolOutputExpanded = false;
344
- editDiffsExpanded = true;
337
+ chatDetail = INITIAL_CHAT_DETAIL;
338
+ toolOutputExpanded = chatDetailView(INITIAL_CHAT_DETAIL).toolOutputExpanded;
339
+ editDiffsExpanded = chatDetailView(INITIAL_CHAT_DETAIL).editDiffsExpanded;
345
340
  // Thinking block visibility state
346
341
  hideThinkingBlock = false;
347
342
  outputPad = 1;
@@ -381,6 +376,8 @@ export class InteractiveMode {
381
376
  compactionQueuedMessages = [];
382
377
  // Shutdown state
383
378
  shutdownRequested = false;
379
+ /** The `/bug` hint is shown at most once per session so error output stays readable. */
380
+ bugReportHintShown = false;
384
381
  // Extension UI state
385
382
  extensionSelector = undefined;
386
383
  extensionInput = undefined;
@@ -444,6 +441,7 @@ export class InteractiveMode {
444
441
  tuiMode,
445
442
  showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
446
443
  logDirectory: getAgentDir(),
444
+ terminal: options.terminal,
447
445
  onRightClickPaste: this.onRightClickPaste,
448
446
  fullscreenCopyOnSelect: this.settingsManager.getFullscreenCopyOnSelect(),
449
447
  });
@@ -479,6 +477,9 @@ export class InteractiveMode {
479
477
  surfaceColor: (text) => theme.bg("userMessageBg", text),
480
478
  autocompleteRule: (width) => theme.fg("borderMuted", (this.settingsManager.getSymbolPreset() === "ascii" ? "-" : "┄").repeat(Math.max(1, width))),
481
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.
482
483
  });
483
484
  this.editor = this.defaultEditor;
484
485
  this.editorContainer = new ComposerDock(() => this.editor);
@@ -488,8 +489,8 @@ export class InteractiveMode {
488
489
  this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
489
490
  this.footerContainer = new Container();
490
491
  this.footerContainer.addChild(this.footer);
491
- // Load hide thinking block setting
492
492
  this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
493
+ this.loadChatDetail(this.settingsManager.getChatDetail());
493
494
  this.outputPad = this.settingsManager.getOutputPad();
494
495
  // Register themes from resource loader and initialize
495
496
  setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
@@ -743,25 +744,20 @@ export class InteractiveMode {
743
744
  }
744
745
  // Keep one component tree and remount it when changing renderers.
745
746
  this.renderWidgets(); // Initialize with default spacer
746
- this.transcriptScrollView = new TuiLayouts.ScrollView(this.documentContainer, {
747
- follow: "end",
748
- primary: true,
749
- 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,
750
755
  scrollbar: this.settingsManager.getFullscreenScrollbar(),
751
- scrollbarStyle: (text) => theme.bg("scrollbarThumb", text),
756
+ scrollbarTrackStyle: (text) => theme.fg("scrollbarTrack", text),
757
+ scrollbarThumbStyle: (text) => theme.fg("scrollbarThumb", text),
752
758
  });
753
- const dock = new TuiLayouts.VStack([
754
- { component: this.pendingMessagesContainer, shrink: 1, minSize: 0 },
755
- { component: this.statusContainer, shrink: 1, minSize: 0 },
756
- { component: this.widgetContainerAbove, shrink: 1, minSize: 0 },
757
- { component: this.editorContainer, shrink: 1, minSize: 3 },
758
- { component: this.widgetContainerBelow, shrink: 1, minSize: 0 },
759
- { component: this.footerContainer, shrink: 1, minSize: 1 },
760
- ]);
761
- this.fullscreenLayoutRoot = new TuiLayouts.VStack([
762
- { component: this.transcriptScrollView, basis: 0, grow: 1, shrink: 1, minSize: 1 },
763
- { component: dock, basis: "auto", grow: 0, shrink: 1, minSize: 1 },
764
- ]);
759
+ this.transcriptScrollView = viewport.transcript;
760
+ this.fullscreenLayoutRoot = viewport.root;
765
761
  this.mountInteractiveTui(this.renderer, [
766
762
  this.documentContainer,
767
763
  this.pendingMessagesContainer,
@@ -949,6 +945,11 @@ export class InteractiveMode {
949
945
  if (modelFallbackMessage) {
950
946
  this.showWarning(modelFallbackMessage);
951
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
+ }
952
953
  void this.maybeWarnAboutAnthropicSubscriptionAuth();
953
954
  // Process initial messages
954
955
  if (initialMessage) {
@@ -1709,10 +1710,57 @@ export class InteractiveMode {
1709
1710
  async handleFatalRuntimeError(prefix, error) {
1710
1711
  const message = error instanceof Error ? error.message : String(error);
1711
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
+ }
1712
1720
  stopThemeWatcher();
1713
1721
  this.stop("transcript");
1714
1722
  process.exit(1);
1715
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
+ }
1716
1764
  renderCurrentSessionState() {
1717
1765
  this.loadedResourcesContainer.clear();
1718
1766
  this.chatContainer.clear();
@@ -1740,8 +1788,12 @@ export class InteractiveMode {
1740
1788
  /**
1741
1789
  * Get a registered tool definition by name (for custom rendering).
1742
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
+ */
1743
1795
  getRegisteredToolDefinition(toolName) {
1744
- return this.session.getToolDefinition(toolName);
1796
+ return withBuiltInRenderers(toolName, this.session.getToolDefinition(toolName));
1745
1797
  }
1746
1798
  getMarkdownTransformers() {
1747
1799
  return [this.mermaidMarkdownTransformer, ...this.session.extensionRunner.getMarkdownTransformers()];
@@ -1811,38 +1863,58 @@ export class InteractiveMode {
1811
1863
  this.footerDataProvider.setExtensionStatus(key, text);
1812
1864
  this.ui.requestRender();
1813
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
+ }
1814
1873
  showStatusIndicator(indicator) {
1815
1874
  this.activeStatusIndicator?.dispose();
1816
1875
  this.activeStatusIndicator = indicator;
1876
+ this.activeWorkingIndicatorEmbedded = false;
1817
1877
  this.statusContainer.clear();
1878
+ this.setEditorWorkingStatusIndicator(undefined);
1818
1879
  if (trayOwnsActivity(indicator.kind, this.customFooter !== undefined)) {
1819
1880
  this.footer.setActivity(indicator);
1820
1881
  return;
1821
1882
  }
1822
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
+ }
1823
1890
  this.statusContainer.addChild(indicator);
1824
1891
  }
1825
1892
  clearStatusIndicator(kind) {
1826
1893
  if (kind && this.activeStatusIndicator?.kind !== kind) {
1827
1894
  return;
1828
1895
  }
1829
- const hadActiveStatusIndicator = this.activeStatusIndicator !== undefined;
1830
- const wasInTray = this.activeStatusIndicator !== undefined &&
1831
- trayOwnsActivity(this.activeStatusIndicator.kind, this.customFooter !== undefined);
1832
- 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();
1833
1900
  this.activeStatusIndicator = undefined;
1901
+ this.activeWorkingIndicatorEmbedded = false;
1834
1902
  this.footer?.setActivity(undefined);
1835
1903
  this.statusContainer.clear();
1836
- // The tray never occupied those rows, so it must not leave the idle filler
1837
- // behind to reserve them.
1838
- 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)
1839
1908
  return;
1840
- if (hadActiveStatusIndicator && this.options.tuiMode === "regular" && this.ui.getClearOnShrink()) {
1909
+ if (clearedIndicator && this.options.tuiMode === "regular" && this.ui.getClearOnShrink()) {
1841
1910
  this.statusContainer.addChild(this.idleStatus);
1842
1911
  }
1843
1912
  }
1844
1913
  showWorkingStatusIndicator() {
1845
- 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));
1846
1918
  }
1847
1919
  setWorkingVisible(visible) {
1848
1920
  this.workingVisible = visible;
@@ -2368,6 +2440,15 @@ export class InteractiveMode {
2368
2440
  this.editor = this.defaultEditor;
2369
2441
  }
2370
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
+ }
2371
2452
  this.ui.setFocus(this.editor);
2372
2453
  this.ui.requestRender();
2373
2454
  }
@@ -2646,6 +2727,12 @@ export class InteractiveMode {
2646
2727
  this.editor.setText("");
2647
2728
  return;
2648
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
+ }
2649
2736
  if (text === "/copy") {
2650
2737
  await this.handleCopyCommand();
2651
2738
  this.editor.setText("");
@@ -2852,10 +2939,44 @@ export class InteractiveMode {
2852
2939
  this.ui.requestRender();
2853
2940
  break;
2854
2941
  case "entry_appended":
2942
+ if (this.entriesRenderedByBoundaryCompaction.delete(event.entry.id))
2943
+ break;
2855
2944
  if (event.entry.type === "custom") {
2856
2945
  this.addCustomEntryToChat(event.entry);
2857
2946
  this.ui.requestRender();
2858
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
+ }
2859
2980
  break;
2860
2981
  case "session_info_changed":
2861
2982
  this.updateTerminalTitle();
@@ -2879,6 +3000,7 @@ export class InteractiveMode {
2879
3000
  else if (event.message.role === "assistant") {
2880
3001
  this.retriedAttempt = undefined;
2881
3002
  this.streamingComponent = new AssistantMessageComponent(undefined, this.isThinkingHidden(), this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad, this.getMarkdownTransformers());
3003
+ this.adoptChatDetail(this.streamingComponent);
2882
3004
  this.streamingMessage = event.message;
2883
3005
  this.chatContainer.addChild(this.streamingComponent);
2884
3006
  this.streamingComponent.updateContent(this.streamingMessage, true);
@@ -2942,12 +3064,14 @@ export class InteractiveMode {
2942
3064
  });
2943
3065
  }
2944
3066
  this.clearPendingTools();
3067
+ this.maybeSuggestBugReport(this.streamingMessage);
2945
3068
  }
2946
3069
  else {
2947
3070
  // Args are now complete - trigger diff computation for edit tools
2948
3071
  for (const [, component] of this.pendingTools.entries()) {
2949
3072
  component.setArgsComplete();
2950
3073
  }
3074
+ this.maybeShowThinkingDropNotice(this.streamingMessage);
2951
3075
  this.maybeShowCacheMissNotice(this.streamingMessage);
2952
3076
  }
2953
3077
  this.streamingComponent = undefined;
@@ -3062,7 +3186,7 @@ export class InteractiveMode {
3062
3186
  }
3063
3187
  else {
3064
3188
  this.chatContainer.addChild(new Spacer(1));
3065
- this.chatContainer.addChild(new Text(theme.fg("error", event.errorMessage), 1, 0));
3189
+ this.chatContainer.addChild(errorText(event.errorMessage, this.toolOutputExpanded, 1));
3066
3190
  }
3067
3191
  }
3068
3192
  void this.flushCompactionQueue({ willRetry: event.willRetry });
@@ -3101,7 +3225,7 @@ export class InteractiveMode {
3101
3225
  const outcome = this.retryCancelled
3102
3226
  ? "retry cancelled"
3103
3227
  : `gave up after ${event.attempt} ${event.attempt === 1 ? "retry" : "retries"}`;
3104
- this.showError(`${lastAttempt?.error ?? event.finalError ?? "Unknown error"} (${outcome})`);
3228
+ this.showError(lastAttempt?.error ?? event.finalError ?? "Unknown error", ` (${outcome})`);
3105
3229
  }
3106
3230
  this.retriedAttempt = undefined;
3107
3231
  this.retryCancelled = false;
@@ -3230,6 +3354,8 @@ export class InteractiveMode {
3230
3354
  this.chatContainer.addChild(component);
3231
3355
  break;
3232
3356
  }
3357
+ case "system":
3358
+ break;
3233
3359
  case "user": {
3234
3360
  const textContent = this.getUserMessageText(message);
3235
3361
  if (textContent) {
@@ -3261,6 +3387,7 @@ export class InteractiveMode {
3261
3387
  }
3262
3388
  case "assistant": {
3263
3389
  const assistantComponent = new AssistantMessageComponent(message, this.isThinkingHidden(), this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad, this.getMarkdownTransformers());
3390
+ this.adoptChatDetail(assistantComponent);
3264
3391
  this.chatContainer.addChild(assistantComponent);
3265
3392
  break;
3266
3393
  }
@@ -3276,8 +3403,8 @@ export class InteractiveMode {
3276
3403
  renderSessionItems(items, options = {}) {
3277
3404
  this.clearPendingTools();
3278
3405
  const renderedPendingTools = new Map();
3279
- // Cache-miss notices are not persisted; re-derive them from the full entry
3280
- // 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.
3281
3408
  const cacheMisses = this.settingsManager.getShowCacheMissNotices()
3282
3409
  ? collectCacheMisses(this.sessionManager.getEntries(), this.session.modelRuntime)
3283
3410
  : new Map();
@@ -3290,6 +3417,10 @@ export class InteractiveMode {
3290
3417
  this.addCustomEntryToChat(item);
3291
3418
  continue;
3292
3419
  }
3420
+ if (isUsageSessionEntry(item)) {
3421
+ this.addCacheWarmingUsage(item);
3422
+ continue;
3423
+ }
3293
3424
  if (isCompactionCostNotice(item)) {
3294
3425
  this.addCompactionCostNotice(item);
3295
3426
  continue;
@@ -3358,7 +3489,7 @@ export class InteractiveMode {
3358
3489
  */
3359
3490
  renderSessionEntries(entries, options = {}) {
3360
3491
  const items = entries.flatMap((entry) => {
3361
- if (entry.type === "custom") {
3492
+ if (entry.type === "custom" || (entry.type === "usage" && entry.kind === "cache_warm")) {
3362
3493
  return [entry];
3363
3494
  }
3364
3495
  const messages = sessionEntryToContextMessages(entry);
@@ -3369,6 +3500,12 @@ export class InteractiveMode {
3369
3500
  });
3370
3501
  this.renderSessionItems(items, options);
3371
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
+ }
3372
3509
  /**
3373
3510
  * Render billing usage for a compaction or branch summary. The notice is derived
3374
3511
  * from persisted summary usage and is not stored as a separate session entry.
@@ -3383,6 +3520,43 @@ export class InteractiveMode {
3383
3520
  this.chatContainer.addChild(new Spacer(1));
3384
3521
  this.chatContainer.addChild(new Text(theme.fg("warning", `${label}: ${formatTokens(tokens)} tokens billed${cost}`), 1, 0));
3385
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
+ }
3386
3560
  /**
3387
3561
  * Show a transcript notice when a completed assistant message paid for a
3388
3562
  * significant cache miss. Only states observable facts: the miss itself,
@@ -3549,6 +3723,12 @@ export class InteractiveMode {
3549
3723
  catch { }
3550
3724
  console.error("apex-code exiting due to uncaughtException:");
3551
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
+ }
3552
3732
  process.exit(1);
3553
3733
  }
3554
3734
  /**
@@ -3684,6 +3864,7 @@ export class InteractiveMode {
3684
3864
  const level = this.session.thinkingLevel || "off";
3685
3865
  this.editor.borderColor = theme.getThinkingBorderColor(level);
3686
3866
  }
3867
+ this.activeStatusIndicator?.invalidate();
3687
3868
  this.ui.requestRender();
3688
3869
  }
3689
3870
  cycleThinkingLevel() {
@@ -3729,27 +3910,40 @@ export class InteractiveMode {
3729
3910
  if (hasEditDiffsExpansion(component))
3730
3911
  component.setEditDiffsExpanded(this.editDiffsExpanded);
3731
3912
  }
3913
+ /** The expand key. A level the user picks is saved, so the next session opens at it. */
3732
3914
  cycleChatDetail() {
3733
- 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;
3734
3930
  }
3735
3931
  /**
3736
- * Presentation only. This never rewrites messages, settings, or the session
3737
- * transcript, which is why it may override the persisted thinking preference
3738
- * 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.
3739
3935
  */
3740
3936
  setChatDetail(detail) {
3741
3937
  if (detail === this.chatDetail)
3742
3938
  return;
3743
- this.chatDetail = detail;
3744
- const view = chatDetailView(detail);
3745
- this.toolOutputExpanded = view.toolOutputExpanded;
3746
- this.editDiffsExpanded = view.editDiffsExpanded;
3939
+ this.assignChatDetail(detail);
3747
3940
  this.applyChatDetail();
3748
3941
  this.showStatus(`Conversation detail: ${detail}`);
3749
3942
  }
3750
3943
  /** True when thinking blocks are hidden right now, setting and detail level combined. */
3751
3944
  isThinkingHidden() {
3752
- return this.hideThinkingBlock && !chatDetailView(this.chatDetail).revealThinking;
3945
+ const { thinking } = chatDetailView(this.chatDetail);
3946
+ return thinking === "preference" ? this.hideThinkingBlock : thinking === "collapsed";
3753
3947
  }
3754
3948
  applyChatDetail() {
3755
3949
  const activeHeader = this.customHeader ?? this.builtInHeader;
@@ -3776,7 +3970,8 @@ export class InteractiveMode {
3776
3970
  *
3777
3971
  * Collapsing maps to `details` rather than `overview`, because an extension
3778
3972
  * asking to collapse tool output has not asked to hide edit diffs, and
3779
- * `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.
3780
3975
  */
3781
3976
  setToolsExpanded(expanded) {
3782
3977
  this.setChatDetail(expanded ? "all" : "details");
@@ -3792,8 +3987,16 @@ export class InteractiveMode {
3792
3987
  }
3793
3988
  toggleThinkingBlockVisibility() {
3794
3989
  this.offerFirstUseHint("thinking");
3795
- this.hideThinkingBlock = !this.hideThinkingBlock;
3796
- 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
+ }
3797
4000
  this.updateThinkingBlockVisibility();
3798
4001
  this.showStatus(`Thinking blocks: ${this.hideThinkingBlock ? "hidden" : "visible"}`);
3799
4002
  }
@@ -3822,9 +4025,9 @@ export class InteractiveMode {
3822
4025
  this.editor.setText("");
3823
4026
  this.ui.requestRender();
3824
4027
  }
3825
- showError(errorMessage) {
4028
+ showError(errorMessage, outcome) {
3826
4029
  this.chatContainer.addChild(new Spacer(1));
3827
- 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));
3828
4031
  this.ui.requestRender();
3829
4032
  }
3830
4033
  showWarning(warningMessage) {
@@ -3923,7 +4126,7 @@ export class InteractiveMode {
3923
4126
  if (allQueued.length === 0) {
3924
4127
  this.updatePendingMessagesDisplay();
3925
4128
  if (options?.abort) {
3926
- this.agent.abort();
4129
+ void this.session.abort();
3927
4130
  }
3928
4131
  return 0;
3929
4132
  }
@@ -3933,7 +4136,7 @@ export class InteractiveMode {
3933
4136
  this.editor.setText(combinedText);
3934
4137
  this.updatePendingMessagesDisplay();
3935
4138
  if (options?.abort) {
3936
- this.agent.abort();
4139
+ void this.session.abort();
3937
4140
  }
3938
4141
  return allQueued.length;
3939
4142
  }
@@ -4133,6 +4336,7 @@ export class InteractiveMode {
4133
4336
  followUpMode: this.session.followUpMode,
4134
4337
  transport: this.settingsManager.getTransport(),
4135
4338
  httpIdleTimeoutMs: this.settingsManager.getHttpIdleTimeoutMs(),
4339
+ cacheWarmingMode: this.settingsManager.getCacheWarmingMode(),
4136
4340
  thinkingLevel: this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL,
4137
4341
  availableThinkingLevels: [...THINKING_LEVEL_OPTIONS],
4138
4342
  modelThinkingLevels: this.settingsManager.getAllModelThinkingLevels(),
@@ -4151,6 +4355,7 @@ export class InteractiveMode {
4151
4355
  permissionMode,
4152
4356
  editorPaddingX: this.settingsManager.getEditorPaddingX(),
4153
4357
  outputPad: this.settingsManager.getOutputPad(),
4358
+ chatDetail: this.chatDetail,
4154
4359
  autocompleteMaxVisible: this.settingsManager.getAutocompleteMaxVisible(),
4155
4360
  quietStartup: this.settingsManager.getQuietStartup(),
4156
4361
  clearOnShrink: this.settingsManager.getClearOnShrink(),
@@ -4206,6 +4411,10 @@ export class InteractiveMode {
4206
4411
  configureHttpDispatcher(timeoutMs);
4207
4412
  this.showStatus(`HTTP idle timeout: ${formatHttpIdleTimeoutMs(timeoutMs)}`);
4208
4413
  },
4414
+ onCacheWarmingModeChange: (mode) => {
4415
+ this.session.setCacheWarmingMode(mode);
4416
+ this.showStatus(`Cache warming: ${mode}`);
4417
+ },
4209
4418
  onModelThinkingLevelChange: (provider, modelId, level) => {
4210
4419
  this.settingsManager.setModelThinkingLevel(provider, modelId, level);
4211
4420
  // If the override is for the current model, apply it to the session too
@@ -4232,6 +4441,7 @@ export class InteractiveMode {
4232
4441
  void this.themeController.setThemeSetting(themeSetting);
4233
4442
  },
4234
4443
  onThemePreview: (themeName) => this.themeController.preview(themeName),
4444
+ onChatDetailChange: (detail) => this.chooseChatDetail(detail),
4235
4445
  onHideThinkingBlockChange: (hidden) => {
4236
4446
  this.hideThinkingBlock = hidden;
4237
4447
  this.settingsManager.setHideThinkingBlock(hidden);
@@ -4767,6 +4977,11 @@ export class InteractiveMode {
4767
4977
  this.restoreQueuedMessagesToEditor();
4768
4978
  await this.session.abort();
4769
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
+ }
4770
4985
  // Set up escape handler and status indicator if summarizing
4771
4986
  let showingSummaryIndicator = false;
4772
4987
  const originalOnEscape = this.defaultEditor.onEscape;
@@ -4837,9 +5052,9 @@ export class InteractiveMode {
4837
5052
  }
4838
5053
  showSessionSelector() {
4839
5054
  this.showSelector((done) => {
4840
- const selector = new SessionSelectorComponent((onProgress) => SessionManager.list(this.sessionManager.getCwd(), this.sessionManager.getSessionDir(), onProgress), (onProgress) => this.sessionManager.usesDefaultSessionDir()
4841
- ? SessionManager.listAll(onProgress)
4842
- : 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) => {
4843
5058
  done();
4844
5059
  await this.handleResumeSession(sessionPath);
4845
5060
  }, () => {
@@ -5123,67 +5338,89 @@ export class InteractiveMode {
5123
5338
  }
5124
5339
  async completeProviderAuthentication(providerId, providerName, authType, previousModel) {
5125
5340
  const actionLabel = authType === "oauth" ? `Logged in to ${providerName}` : `Saved API key for ${providerName}`;
5126
- let selectedModel;
5127
- let selectionError;
5128
- if (isUnknownModel(previousModel)) {
5129
- const availableModels = this.session.modelRuntime.getAvailableSnapshot();
5130
- const providerModels = availableModels.filter((model) => model.provider === providerId);
5131
- // Matches LLAMA_PROVIDER_ID from extensions/llama/provider.ts; kept inline to avoid coupling interactive mode to the built-in extension.
5132
- if (providerId === "llama.cpp") {
5133
- selectionError = llamaCppPostLoginGuidance(actionLabel, providerModels.length);
5134
- }
5135
- else if (!hasDefaultModelProvider(providerId)) {
5136
- selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
5137
- }
5138
- else if (providerModels.length === 0) {
5139
- selectionError = `${actionLabel}, but no models are available for that provider. Use /model to select a model.`;
5140
- }
5141
- else {
5142
- const defaultModelId = defaultModelPerProvider[providerId];
5143
- selectedModel = providerModels.find((model) => model.id === defaultModelId);
5144
- if (!selectedModel) {
5145
- 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.`;
5146
5363
  }
5147
5364
  else {
5148
- try {
5149
- 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.`;
5150
5372
  }
5151
- catch (error) {
5152
- selectedModel = undefined;
5153
- const errorMessage = error instanceof Error ? error.message : String(error);
5154
- 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
+ }
5155
5382
  }
5156
5383
  }
5157
5384
  }
5158
- }
5159
- await this.updateAvailableProviderCount();
5160
- this.footer.invalidate();
5161
- this.updateEditorBorderColor();
5162
- if (selectedModel) {
5163
- this.showStatus(`${actionLabel}. Selected ${selectedModel.id}. Credentials saved to ${getAuthPath()}`);
5164
- void this.maybeWarnAboutAnthropicSubscriptionAuth(selectedModel);
5165
- this.checkDaxnutsEasterEgg(selectedModel);
5166
- }
5167
- else {
5168
- this.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}`);
5169
- if (selectionError) {
5170
- 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);
5171
5392
  }
5172
5393
  else {
5173
- 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
+ }
5174
5401
  }
5402
+ };
5403
+ if (deferSelection) {
5404
+ this.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}. Refreshing model catalog…`);
5405
+ }
5406
+ else {
5407
+ await finishAuthentication();
5175
5408
  }
5176
5409
  const controller = new AbortController();
5177
5410
  const timeout = setTimeout(() => controller.abort(), 15_000);
5178
- void this.session.modelRuntime
5411
+ void session.modelRuntime
5179
5412
  .refresh({ providers: [providerId], signal: controller.signal })
5180
- .then((result) => {
5413
+ .then(async (result) => {
5181
5414
  if (result.aborted) {
5182
5415
  this.showWarning(`${actionLabel}, but its model catalog refresh timed out; using cached models.`);
5183
5416
  }
5184
5417
  else if (result.errors.size > 0) {
5185
5418
  this.showWarning(`${actionLabel}, but its model catalog could not be refreshed; using cached models.`);
5186
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
+ }
5187
5424
  this.updateAvailableProviderCount();
5188
5425
  this.footer.invalidate();
5189
5426
  this.ui.requestRender();
@@ -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)}`;
@@ -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;