apex-code 0.0.1-alpha.7 → 0.0.1-alpha.9

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 (393) hide show
  1. package/CHANGELOG.md +229 -0
  2. package/README.md +5 -0
  3. package/dist/cli/args.d.ts +4 -1
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +44 -9
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/cli/concurrent-session.d.ts +3 -0
  8. package/dist/cli/concurrent-session.d.ts.map +1 -0
  9. package/dist/cli/concurrent-session.js +13 -0
  10. package/dist/cli/concurrent-session.js.map +1 -0
  11. package/dist/cli/file-processor.d.ts.map +1 -1
  12. package/dist/cli/file-processor.js +2 -1
  13. package/dist/cli/file-processor.js.map +1 -1
  14. package/dist/cli.d.ts.map +1 -1
  15. package/dist/cli.js +1 -2
  16. package/dist/cli.js.map +1 -1
  17. package/dist/config.d.ts +3 -2
  18. package/dist/config.d.ts.map +1 -1
  19. package/dist/config.js +21 -13
  20. package/dist/config.js.map +1 -1
  21. package/dist/core/agent-session-services.d.ts.map +1 -1
  22. package/dist/core/agent-session-services.js +20 -0
  23. package/dist/core/agent-session-services.js.map +1 -1
  24. package/dist/core/agent-session.d.ts +41 -9
  25. package/dist/core/agent-session.d.ts.map +1 -1
  26. package/dist/core/agent-session.js +172 -101
  27. package/dist/core/agent-session.js.map +1 -1
  28. package/dist/core/auth-storage.d.ts +4 -1
  29. package/dist/core/auth-storage.d.ts.map +1 -1
  30. package/dist/core/auth-storage.js +12 -9
  31. package/dist/core/auth-storage.js.map +1 -1
  32. package/dist/core/bash-executor.d.ts.map +1 -1
  33. package/dist/core/bash-executor.js +10 -9
  34. package/dist/core/bash-executor.js.map +1 -1
  35. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  36. package/dist/core/compaction/branch-summarization.js +3 -0
  37. package/dist/core/compaction/branch-summarization.js.map +1 -1
  38. package/dist/core/compaction/compaction.d.ts +4 -3
  39. package/dist/core/compaction/compaction.d.ts.map +1 -1
  40. package/dist/core/compaction/compaction.js +41 -32
  41. package/dist/core/compaction/compaction.js.map +1 -1
  42. package/dist/core/defaults.d.ts +1 -0
  43. package/dist/core/defaults.d.ts.map +1 -1
  44. package/dist/core/defaults.js +9 -0
  45. package/dist/core/defaults.js.map +1 -1
  46. package/dist/core/durable-state/sqlite.d.ts.map +1 -1
  47. package/dist/core/durable-state/sqlite.js +26 -2
  48. package/dist/core/durable-state/sqlite.js.map +1 -1
  49. package/dist/core/experimental.d.ts +4 -0
  50. package/dist/core/experimental.d.ts.map +1 -1
  51. package/dist/core/experimental.js +4 -0
  52. package/dist/core/experimental.js.map +1 -1
  53. package/dist/core/extensions/index.d.ts +2 -2
  54. package/dist/core/extensions/index.d.ts.map +1 -1
  55. package/dist/core/extensions/index.js +1 -1
  56. package/dist/core/extensions/index.js.map +1 -1
  57. package/dist/core/extensions/loader.d.ts.map +1 -1
  58. package/dist/core/extensions/loader.js +115 -48
  59. package/dist/core/extensions/loader.js.map +1 -1
  60. package/dist/core/extensions/types.d.ts +42 -9
  61. package/dist/core/extensions/types.d.ts.map +1 -1
  62. package/dist/core/extensions/types.js +3 -0
  63. package/dist/core/extensions/types.js.map +1 -1
  64. package/dist/core/footer-data-provider.d.ts +1 -1
  65. package/dist/core/footer-data-provider.d.ts.map +1 -1
  66. package/dist/core/footer-data-provider.js +1 -1
  67. package/dist/core/footer-data-provider.js.map +1 -1
  68. package/dist/core/http-dispatcher.d.ts +1 -2
  69. package/dist/core/http-dispatcher.d.ts.map +1 -1
  70. package/dist/core/http-dispatcher.js +2 -17
  71. package/dist/core/http-dispatcher.js.map +1 -1
  72. package/dist/core/http-idle-timeout.d.ts +12 -0
  73. package/dist/core/http-idle-timeout.d.ts.map +1 -0
  74. package/dist/core/http-idle-timeout.js +27 -0
  75. package/dist/core/http-idle-timeout.js.map +1 -0
  76. package/dist/core/keybindings.d.ts +38 -13
  77. package/dist/core/keybindings.d.ts.map +1 -1
  78. package/dist/core/keybindings.js +26 -5
  79. package/dist/core/keybindings.js.map +1 -1
  80. package/dist/core/lsp/client.d.ts +2 -0
  81. package/dist/core/lsp/client.d.ts.map +1 -1
  82. package/dist/core/lsp/client.js +27 -2
  83. package/dist/core/lsp/client.js.map +1 -1
  84. package/dist/core/model-config.d.ts +10 -0
  85. package/dist/core/model-config.d.ts.map +1 -1
  86. package/dist/core/model-config.js +4 -1
  87. package/dist/core/model-config.js.map +1 -1
  88. package/dist/core/model-resolver.d.ts +1 -0
  89. package/dist/core/model-resolver.d.ts.map +1 -1
  90. package/dist/core/model-resolver.js +14 -8
  91. package/dist/core/model-resolver.js.map +1 -1
  92. package/dist/core/models-store.d.ts.map +1 -1
  93. package/dist/core/models-store.js +2 -1
  94. package/dist/core/models-store.js.map +1 -1
  95. package/dist/core/package-manager.d.ts.map +1 -1
  96. package/dist/core/package-manager.js +22 -14
  97. package/dist/core/package-manager.js.map +1 -1
  98. package/dist/core/pi-manifest.d.ts.map +1 -1
  99. package/dist/core/pi-manifest.js +2 -1
  100. package/dist/core/pi-manifest.js.map +1 -1
  101. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  102. package/dist/core/remote-catalog-provider.js +2 -1
  103. package/dist/core/remote-catalog-provider.js.map +1 -1
  104. package/dist/core/resource-loader.d.ts.map +1 -1
  105. package/dist/core/resource-loader.js +3 -2
  106. package/dist/core/resource-loader.js.map +1 -1
  107. package/dist/core/sandbox/child-entry.d.ts.map +1 -1
  108. package/dist/core/sandbox/child-entry.js +8 -1
  109. package/dist/core/sandbox/child-entry.js.map +1 -1
  110. package/dist/core/sandbox/cli-launch.d.ts +3 -0
  111. package/dist/core/sandbox/cli-launch.d.ts.map +1 -1
  112. package/dist/core/sandbox/cli-launch.js +25 -4
  113. package/dist/core/sandbox/cli-launch.js.map +1 -1
  114. package/dist/core/sandbox/cli-supervisor.d.ts.map +1 -1
  115. package/dist/core/sandbox/cli-supervisor.js +53 -13
  116. package/dist/core/sandbox/cli-supervisor.js.map +1 -1
  117. package/dist/core/sandbox/linux-backend.d.ts.map +1 -1
  118. package/dist/core/sandbox/linux-backend.js +15 -0
  119. package/dist/core/sandbox/linux-backend.js.map +1 -1
  120. package/dist/core/sandbox/macos-backend.d.ts.map +1 -1
  121. package/dist/core/sandbox/macos-backend.js +24 -1
  122. package/dist/core/sandbox/macos-backend.js.map +1 -1
  123. package/dist/core/sandbox/rpc/credential-client.d.ts +20 -0
  124. package/dist/core/sandbox/rpc/credential-client.d.ts.map +1 -0
  125. package/dist/core/sandbox/rpc/credential-client.js +170 -0
  126. package/dist/core/sandbox/rpc/credential-client.js.map +1 -0
  127. package/dist/core/sandbox/rpc/credential-proxy.d.ts +44 -0
  128. package/dist/core/sandbox/rpc/credential-proxy.d.ts.map +1 -0
  129. package/dist/core/sandbox/rpc/credential-proxy.js +381 -0
  130. package/dist/core/sandbox/rpc/credential-proxy.js.map +1 -0
  131. package/dist/core/sandbox/supervisor.d.ts +11 -0
  132. package/dist/core/sandbox/supervisor.d.ts.map +1 -1
  133. package/dist/core/sandbox/supervisor.js.map +1 -1
  134. package/dist/core/sandbox/terminal-size.d.ts +40 -0
  135. package/dist/core/sandbox/terminal-size.d.ts.map +1 -0
  136. package/dist/core/sandbox/terminal-size.js +105 -0
  137. package/dist/core/sandbox/terminal-size.js.map +1 -0
  138. package/dist/core/sdk.d.ts +8 -5
  139. package/dist/core/sdk.d.ts.map +1 -1
  140. package/dist/core/sdk.js +26 -7
  141. package/dist/core/sdk.js.map +1 -1
  142. package/dist/core/session-export.d.ts +4 -0
  143. package/dist/core/session-export.d.ts.map +1 -0
  144. package/dist/core/session-export.js +32 -0
  145. package/dist/core/session-export.js.map +1 -0
  146. package/dist/core/session-lease.d.ts +31 -0
  147. package/dist/core/session-lease.d.ts.map +1 -0
  148. package/dist/core/session-lease.js +138 -0
  149. package/dist/core/session-lease.js.map +1 -0
  150. package/dist/core/session-manager.d.ts +5 -0
  151. package/dist/core/session-manager.d.ts.map +1 -1
  152. package/dist/core/session-manager.js +5 -4
  153. package/dist/core/session-manager.js.map +1 -1
  154. package/dist/core/settings-diagnostics.d.ts +9 -0
  155. package/dist/core/settings-diagnostics.d.ts.map +1 -0
  156. package/dist/core/settings-diagnostics.js +21 -0
  157. package/dist/core/settings-diagnostics.js.map +1 -0
  158. package/dist/core/settings-manager.d.ts +35 -0
  159. package/dist/core/settings-manager.d.ts.map +1 -1
  160. package/dist/core/settings-manager.js +93 -12
  161. package/dist/core/settings-manager.js.map +1 -1
  162. package/dist/core/skills.d.ts.map +1 -1
  163. package/dist/core/skills.js +51 -33
  164. package/dist/core/skills.js.map +1 -1
  165. package/dist/core/slash-commands.d.ts.map +1 -1
  166. package/dist/core/slash-commands.js +4 -1
  167. package/dist/core/slash-commands.js.map +1 -1
  168. package/dist/core/system-prompt.d.ts +2 -2
  169. package/dist/core/system-prompt.d.ts.map +1 -1
  170. package/dist/core/system-prompt.js +53 -62
  171. package/dist/core/system-prompt.js.map +1 -1
  172. package/dist/core/tools/bash.d.ts +14 -1
  173. package/dist/core/tools/bash.d.ts.map +1 -1
  174. package/dist/core/tools/bash.js +38 -20
  175. package/dist/core/tools/bash.js.map +1 -1
  176. package/dist/core/tools/delegate.d.ts.map +1 -1
  177. package/dist/core/tools/delegate.js +24 -0
  178. package/dist/core/tools/delegate.js.map +1 -1
  179. package/dist/core/tools/edit-diff.d.ts +0 -5
  180. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  181. package/dist/core/tools/edit-diff.js +2 -5
  182. package/dist/core/tools/edit-diff.js.map +1 -1
  183. package/dist/core/tools/edit.d.ts.map +1 -1
  184. package/dist/core/tools/edit.js +22 -4
  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 +1 -1
  188. package/dist/core/tools/find.js.map +1 -1
  189. package/dist/core/tools/grep.d.ts +2 -0
  190. package/dist/core/tools/grep.d.ts.map +1 -1
  191. package/dist/core/tools/grep.js +35 -10
  192. package/dist/core/tools/grep.js.map +1 -1
  193. package/dist/core/tools/index.d.ts +4 -1
  194. package/dist/core/tools/index.d.ts.map +1 -1
  195. package/dist/core/tools/index.js +9 -0
  196. package/dist/core/tools/index.js.map +1 -1
  197. package/dist/core/tools/ls.d.ts +2 -0
  198. package/dist/core/tools/ls.d.ts.map +1 -1
  199. package/dist/core/tools/ls.js +5 -1
  200. package/dist/core/tools/ls.js.map +1 -1
  201. package/dist/core/tools/powershell.d.ts +16 -0
  202. package/dist/core/tools/powershell.d.ts.map +1 -0
  203. package/dist/core/tools/powershell.js +39 -0
  204. package/dist/core/tools/powershell.js.map +1 -0
  205. package/dist/core/tools/read.d.ts.map +1 -1
  206. package/dist/core/tools/read.js +2 -0
  207. package/dist/core/tools/read.js.map +1 -1
  208. package/dist/core/tools/web-search-exa.d.ts +33 -0
  209. package/dist/core/tools/web-search-exa.d.ts.map +1 -0
  210. package/dist/core/tools/web-search-exa.js +128 -0
  211. package/dist/core/tools/web-search-exa.js.map +1 -0
  212. package/dist/core/tools/write.d.ts.map +1 -1
  213. package/dist/core/tools/write.js +2 -0
  214. package/dist/core/tools/write.js.map +1 -1
  215. package/dist/core/trust-manager.d.ts.map +1 -1
  216. package/dist/core/trust-manager.js +2 -1
  217. package/dist/core/trust-manager.js.map +1 -1
  218. package/dist/core/web-search-provider.d.ts +64 -0
  219. package/dist/core/web-search-provider.d.ts.map +1 -0
  220. package/dist/core/web-search-provider.js +149 -0
  221. package/dist/core/web-search-provider.js.map +1 -0
  222. package/dist/extensions/llama/index.d.ts.map +1 -1
  223. package/dist/extensions/llama/index.js +2 -0
  224. package/dist/extensions/llama/index.js.map +1 -1
  225. package/dist/extensions/llama/provider.d.ts.map +1 -1
  226. package/dist/extensions/llama/provider.js +6 -2
  227. package/dist/extensions/llama/provider.js.map +1 -1
  228. package/dist/index.d.ts +6 -6
  229. package/dist/index.d.ts.map +1 -1
  230. package/dist/index.js +4 -4
  231. package/dist/index.js.map +1 -1
  232. package/dist/main.d.ts +5 -0
  233. package/dist/main.d.ts.map +1 -1
  234. package/dist/main.js +38 -16
  235. package/dist/main.js.map +1 -1
  236. package/dist/migrations.d.ts.map +1 -1
  237. package/dist/migrations.js +4 -3
  238. package/dist/migrations.js.map +1 -1
  239. package/dist/modes/interactive/components/custom-editor.d.ts +23 -0
  240. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  241. package/dist/modes/interactive/components/custom-editor.js +105 -2
  242. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  243. package/dist/modes/interactive/components/dynamic-border.d.ts +7 -1
  244. package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
  245. package/dist/modes/interactive/components/dynamic-border.js +11 -2
  246. package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
  247. package/dist/modes/interactive/components/extension-selector.d.ts +4 -0
  248. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  249. package/dist/modes/interactive/components/extension-selector.js +24 -6
  250. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  251. package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
  252. package/dist/modes/interactive/components/first-time-setup.js +6 -3
  253. package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
  254. package/dist/modes/interactive/components/first-use-hints.d.ts +8 -0
  255. package/dist/modes/interactive/components/first-use-hints.d.ts.map +1 -0
  256. package/dist/modes/interactive/components/first-use-hints.js +22 -0
  257. package/dist/modes/interactive/components/first-use-hints.js.map +1 -0
  258. package/dist/modes/interactive/components/footer.d.ts +12 -0
  259. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  260. package/dist/modes/interactive/components/footer.js +128 -9
  261. package/dist/modes/interactive/components/footer.js.map +1 -1
  262. package/dist/modes/interactive/components/index.d.ts +1 -0
  263. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  264. package/dist/modes/interactive/components/index.js +1 -0
  265. package/dist/modes/interactive/components/index.js.map +1 -1
  266. package/dist/modes/interactive/components/model-selector.d.ts +12 -3
  267. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  268. package/dist/modes/interactive/components/model-selector.js +71 -14
  269. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  270. package/dist/modes/interactive/components/settings-selector.d.ts +10 -3
  271. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  272. package/dist/modes/interactive/components/settings-selector.js +128 -56
  273. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  274. package/dist/modes/interactive/components/settings-submenu.d.ts +72 -0
  275. package/dist/modes/interactive/components/settings-submenu.d.ts.map +1 -0
  276. package/dist/modes/interactive/components/settings-submenu.js +165 -0
  277. package/dist/modes/interactive/components/settings-submenu.js.map +1 -0
  278. package/dist/modes/interactive/components/splash-header.d.ts +29 -4
  279. package/dist/modes/interactive/components/splash-header.d.ts.map +1 -1
  280. package/dist/modes/interactive/components/splash-header.js +58 -23
  281. package/dist/modes/interactive/components/splash-header.js.map +1 -1
  282. package/dist/modes/interactive/components/thinking-selector.d.ts +15 -3
  283. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  284. package/dist/modes/interactive/components/thinking-selector.js +74 -17
  285. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  286. package/dist/modes/interactive/components/tool-execution.d.ts +14 -0
  287. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  288. package/dist/modes/interactive/components/tool-execution.js +113 -35
  289. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  290. package/dist/modes/interactive/components/tool-panel.d.ts +30 -0
  291. package/dist/modes/interactive/components/tool-panel.d.ts.map +1 -0
  292. package/dist/modes/interactive/components/tool-panel.js +130 -0
  293. package/dist/modes/interactive/components/tool-panel.js.map +1 -0
  294. package/dist/modes/interactive/external-editor.d.ts.map +1 -1
  295. package/dist/modes/interactive/external-editor.js +2 -1
  296. package/dist/modes/interactive/external-editor.js.map +1 -1
  297. package/dist/modes/interactive/interactive-mode.d.ts +55 -9
  298. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  299. package/dist/modes/interactive/interactive-mode.js +446 -87
  300. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  301. package/dist/modes/interactive/model-catalog-refresh.d.ts +7 -0
  302. package/dist/modes/interactive/model-catalog-refresh.d.ts.map +1 -0
  303. package/dist/modes/interactive/model-catalog-refresh.js +34 -0
  304. package/dist/modes/interactive/model-catalog-refresh.js.map +1 -0
  305. package/dist/modes/interactive/theme/apex.json +62 -63
  306. package/dist/modes/interactive/theme/dark.json +2 -0
  307. package/dist/modes/interactive/theme/light.json +2 -0
  308. package/dist/modes/interactive/theme/theme-controller.d.ts +10 -2
  309. package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
  310. package/dist/modes/interactive/theme/theme-controller.js +25 -11
  311. package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
  312. package/dist/modes/interactive/theme/theme-schema.json +9 -1
  313. package/dist/modes/interactive/theme/theme.d.ts +14 -3
  314. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  315. package/dist/modes/interactive/theme/theme.js +60 -10
  316. package/dist/modes/interactive/theme/theme.js.map +1 -1
  317. package/dist/modes/json-event.d.ts +18 -12
  318. package/dist/modes/json-event.d.ts.map +1 -1
  319. package/dist/modes/json-event.js +22 -5
  320. package/dist/modes/json-event.js.map +1 -1
  321. package/dist/package-manager-cli.d.ts +1 -0
  322. package/dist/package-manager-cli.d.ts.map +1 -1
  323. package/dist/package-manager-cli.js +197 -5
  324. package/dist/package-manager-cli.js.map +1 -1
  325. package/dist/server/create-harness.d.ts.map +1 -1
  326. package/dist/server/create-harness.js +2 -0
  327. package/dist/server/create-harness.js.map +1 -1
  328. package/dist/themes/apex-logo.d.ts +36 -16
  329. package/dist/themes/apex-logo.d.ts.map +1 -1
  330. package/dist/themes/apex-logo.js +32 -28
  331. package/dist/themes/apex-logo.js.map +1 -1
  332. package/dist/utils/frontmatter.d.ts.map +1 -1
  333. package/dist/utils/frontmatter.js +2 -1
  334. package/dist/utils/frontmatter.js.map +1 -1
  335. package/dist/utils/management-http.d.ts +5 -3
  336. package/dist/utils/management-http.d.ts.map +1 -1
  337. package/dist/utils/management-http.js +14 -10
  338. package/dist/utils/management-http.js.map +1 -1
  339. package/dist/utils/shell.d.ts +3 -0
  340. package/dist/utils/shell.d.ts.map +1 -1
  341. package/dist/utils/shell.js +17 -5
  342. package/dist/utils/shell.js.map +1 -1
  343. package/dist/utils/syntax-highlight.d.ts +1 -0
  344. package/dist/utils/syntax-highlight.d.ts.map +1 -1
  345. package/dist/utils/syntax-highlight.js +60 -1
  346. package/dist/utils/syntax-highlight.js.map +1 -1
  347. package/dist/utils/text.d.ts +8 -0
  348. package/dist/utils/text.d.ts.map +1 -0
  349. package/dist/utils/text.js +9 -0
  350. package/dist/utils/text.js.map +1 -0
  351. package/dist/utils/tools-manager.d.ts +10 -1
  352. package/dist/utils/tools-manager.d.ts.map +1 -1
  353. package/dist/utils/tools-manager.js +15 -20
  354. package/dist/utils/tools-manager.js.map +1 -1
  355. package/docs/compaction.md +18 -3
  356. package/docs/custom-provider.md +6 -3
  357. package/docs/environment-variables.md +1 -0
  358. package/docs/extensions.md +21 -7
  359. package/docs/json.md +12 -5
  360. package/docs/keybindings.md +15 -9
  361. package/docs/llama-cpp.md +2 -0
  362. package/docs/models.md +8 -2
  363. package/docs/packages.md +2 -2
  364. package/docs/rpc.md +24 -7
  365. package/docs/sdk.md +16 -2
  366. package/docs/settings.md +73 -2
  367. package/docs/skills.md +4 -3
  368. package/docs/terminal-setup.md +34 -9
  369. package/docs/themes.md +24 -3
  370. package/docs/tui.md +2 -2
  371. package/docs/usage.md +9 -4
  372. package/docs/windows.md +24 -2
  373. package/examples/extensions/border-status-editor.ts +1 -1
  374. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  375. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  376. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  377. package/examples/extensions/git-checkpoint.ts +2 -2
  378. package/examples/extensions/gondolin/package-lock.json +2 -2
  379. package/examples/extensions/gondolin/package.json +1 -1
  380. package/examples/extensions/notify.ts +3 -1
  381. package/examples/extensions/sandbox/package-lock.json +2 -2
  382. package/examples/extensions/sandbox/package.json +1 -1
  383. package/examples/extensions/subagent/README.md +3 -1
  384. package/examples/extensions/subagent/agents.ts +40 -9
  385. package/examples/extensions/subagent/index.ts +27 -4
  386. package/examples/extensions/titlebar-spinner.ts +1 -1
  387. package/examples/extensions/with-deps/package-lock.json +2 -2
  388. package/examples/extensions/with-deps/package.json +1 -1
  389. package/examples/rpc-extension-ui.ts +1 -1
  390. package/examples/sdk/06-extensions.ts +1 -1
  391. package/examples/sdk/README.md +1 -1
  392. package/npm-shrinkwrap.json +280 -445
  393. package/package.json +9 -11
@@ -14,6 +14,7 @@ import { APP_NAME, APP_TITLE, CONFIG_DIR_NAME, getAgentDir, getAuthPath, getDebu
14
14
  import { parseSkillBlock } from "../../core/agent-session.js";
15
15
  import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
16
16
  import { CACHE_TTL_MS, collectCacheMisses, computeCacheWaste, detectCacheMiss, } from "../../core/cache-stats.js";
17
+ import { DEFAULT_THINKING_LEVEL, THINKING_LEVEL_OPTIONS } from "../../core/defaults.js";
17
18
  import { getApexEnvironment } from "../../core/environment.js";
18
19
  import { FooterDataProvider } from "../../core/footer-data-provider.js";
19
20
  import { configureHttpDispatcher, formatHttpIdleTimeoutMs } from "../../core/http-dispatcher.js";
@@ -37,6 +38,7 @@ import { parseGitUrl } from "../../utils/git.js";
37
38
  import { openBrowser } from "../../utils/open-browser.js";
38
39
  import { getCwdRelativePath } from "../../utils/paths.js";
39
40
  import { killTrackedDetachedChildren } from "../../utils/shell.js";
41
+ import { loadAllHighlightLanguages } from "../../utils/syntax-highlight.js";
40
42
  import { ensureTool } from "../../utils/tools-manager.js";
41
43
  import { checkForNewApexCodeVersion } from "../../utils/version-check.js";
42
44
  import { ArminComponent } from "./components/armin.js";
@@ -54,6 +56,7 @@ import { EarendilAnnouncementComponent } from "./components/earendil-announcemen
54
56
  import { ExtensionEditorComponent } from "./components/extension-editor.js";
55
57
  import { ExtensionInputComponent } from "./components/extension-input.js";
56
58
  import { ExtensionSelectorComponent } from "./components/extension-selector.js";
59
+ import { FirstUseHints } from "./components/first-use-hints.js";
57
60
  import { FooterComponent, formatTokens } from "./components/footer.js";
58
61
  import { formatKeyText, keyDisplayText, keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
59
62
  import { LoginDialogComponent } from "./components/login-dialog.js";
@@ -66,12 +69,14 @@ import { PERMISSION_MODE_OVERRIDE_HINTS, SettingsSelectorComponent } from "./com
66
69
  import { SkillInvocationMessageComponent } from "./components/skill-invocation-message.js";
67
70
  import { ApexSplashHeader } from "./components/splash-header.js";
68
71
  import { BranchSummaryStatusIndicator, CompactionStatusIndicator, IdleStatus, RetryStatusIndicator, WorkingStatusIndicator, } from "./components/status-indicator.js";
72
+ import { ThinkingSelectorComponent } from "./components/thinking-selector.js";
69
73
  import { ToolExecutionComponent } from "./components/tool-execution.js";
70
74
  import { TreeSelectorComponent } from "./components/tree-selector.js";
71
75
  import { TrustSelectorComponent } from "./components/trust-selector.js";
72
76
  import { UserMessageComponent } from "./components/user-message.js";
73
77
  import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
74
78
  import { editInExternalEditor } from "./external-editor.js";
79
+ import { refreshModelCatalogs } from "./model-catalog-refresh.js";
75
80
  import { getModelSearchText } from "./model-search.js";
76
81
  import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, onThemeChange, setRegisteredThemes, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
77
82
  import { InteractiveThemeController } from "./theme/theme-controller.js";
@@ -93,6 +98,9 @@ class ExpandableText extends Text {
93
98
  function isCustomSessionEntry(item) {
94
99
  return "type" in item && item.type === "custom";
95
100
  }
101
+ function isCompactionCostNotice(item) {
102
+ return "type" in item && item.type === "compaction_cost";
103
+ }
96
104
  const DEAD_TERMINAL_ERROR_CODES = new Set(["EIO", "EPIPE", "ENOTCONN"]);
97
105
  function isDeadTerminalError(error) {
98
106
  if (!error || typeof error !== "object" || !("code" in error)) {
@@ -132,6 +140,11 @@ export function formatResumeCommand(sessionManager) {
132
140
  function hasDefaultModelProvider(providerId) {
133
141
  return providerId in defaultModelPerProvider;
134
142
  }
143
+ function llamaCppPostLoginGuidance(actionLabel, loadedModelCount) {
144
+ return loadedModelCount === 0
145
+ ? `${actionLabel}. No llama.cpp models are loaded. Use /llama to load a model, then /model to select it.`
146
+ : `${actionLabel}. Use /model to select a loaded llama.cpp model, or /llama to manage models.`;
147
+ }
135
148
  const AUTH_TYPE_ORDER = { oauth: 0, api_key: 1 };
136
149
  function createFuzzyAutocompleteItems(items, prefix, getSearchText, toAutocompleteItem) {
137
150
  const filtered = fuzzyFilter(items, prefix, getSearchText);
@@ -172,9 +185,21 @@ function formatLoginProviderCompletionDescription(provider) {
172
185
  export function createInteractiveTui(options) {
173
186
  const terminal = options.terminal ?? new ProcessTerminal();
174
187
  if (options.tuiMode === "fullscreen") {
188
+ const styleSearchMatch = (text) => theme.bg("searchMatchBg", theme.fg("searchMatchText", text));
175
189
  return new TuiAltScreen(terminal, options.showHardwareCursor, options.logDirectory, {
190
+ searchMatchStyle: (text) => theme.underline(styleSearchMatch(text)),
191
+ searchCurrentMatchStyle: (text) => theme.bold(theme.inverse(styleSearchMatch(text))),
176
192
  openUrl: openBrowser,
177
193
  onRightClickPaste: options.onRightClickPaste,
194
+ copySelection: async (text) => {
195
+ try {
196
+ await copyToClipboard(text);
197
+ return true;
198
+ }
199
+ catch {
200
+ return false;
201
+ }
202
+ },
178
203
  });
179
204
  }
180
205
  return new TuiMainScreen(terminal, options.showHardwareCursor, options.logDirectory);
@@ -236,6 +261,14 @@ export class InteractiveMode {
236
261
  footer;
237
262
  footerContainer;
238
263
  footerDataProvider;
264
+ /**
265
+ * Whether the startup screen is showing its counted inventory line.
266
+ *
267
+ * Warnings are only allowed to collapse into that count when the count is
268
+ * actually on screen. Under a quiet startup there is no splash header, so
269
+ * collapsing them would hide conflicts entirely rather than summarise them.
270
+ */
271
+ startupInventoryVisible = false;
239
272
  // Stored so the same manager can be injected into custom editors, selectors, and extension UI.
240
273
  keybindings;
241
274
  version;
@@ -258,6 +291,7 @@ export class InteractiveMode {
258
291
  // Status line tracking (for mutating immediately-sequential status updates)
259
292
  lastStatusSpacer = undefined;
260
293
  lastStatusText = undefined;
294
+ managedToolStatusStarted = false;
261
295
  // Streaming message tracking
262
296
  streamingComponent = undefined;
263
297
  streamingMessage = undefined;
@@ -279,6 +313,7 @@ export class InteractiveMode {
279
313
  signalCleanupHandlers = [];
280
314
  // Track if editor is in bash mode (text starts with !)
281
315
  isBashMode = false;
316
+ firstUseHints;
282
317
  // Track current bash execution component
283
318
  bashComponent = undefined;
284
319
  // Track pending bash components (shown in pending area, moved to chat on submit)
@@ -328,6 +363,14 @@ export class InteractiveMode {
328
363
  get settingsManager() {
329
364
  return this.session.settingsManager;
330
365
  }
366
+ offerFirstUseHint(id) {
367
+ this.firstUseHints ??= new FirstUseHints(this.settingsManager.getFirstUseHints());
368
+ const hint = this.firstUseHints.offer(id);
369
+ if (!hint)
370
+ return;
371
+ this.settingsManager.setFirstUseHints(this.firstUseHints.getSeen());
372
+ this.chatContainer.addChild(new Text(theme.fg("dim", hint), this.outputPad, 0));
373
+ }
331
374
  constructor(runtimeHost, options = {}) {
332
375
  this.runtimeHost = runtimeHost;
333
376
  const tuiMode = options.tuiMode ?? this.settingsManager.getTuiMode();
@@ -338,6 +381,7 @@ export class InteractiveMode {
338
381
  });
339
382
  this.runtimeHost.setRebindSession(async () => {
340
383
  await this.rebindCurrentSession({ renderBeforeBind: true });
384
+ await this.themeController.applyFromSettings();
341
385
  });
342
386
  this.version = VERSION;
343
387
  this.renderer = createInteractiveTui({
@@ -366,11 +410,14 @@ export class InteractiveMode {
366
410
  this.defaultEditor = new CustomEditor(this.ui, getEditorTheme(), this.keybindings, {
367
411
  paddingX: editorPaddingX,
368
412
  autocompleteMaxVisible,
369
- promptPrefix: "> ",
413
+ promptPrefix: " ",
370
414
  promptColor: (text) => theme.fg("accent", text),
371
- placeholder: "Ask anything, / for commands, ! for bash",
415
+ placeholder: "Ask anything",
372
416
  placeholderColor: (text) => theme.fg("dim", text),
373
417
  commandColor: (text) => theme.fg("accent", text),
418
+ surfaceColor: (text) => theme.bg("userMessageBg", text),
419
+ autocompleteRule: (width) => theme.fg("borderMuted", (this.settingsManager.getSymbolPreset() === "ascii" ? "-" : "┄").repeat(Math.max(1, width))),
420
+ autocompleteFooter: () => this.buildAutocompleteFooter(),
374
421
  });
375
422
  this.editor = this.defaultEditor;
376
423
  this.editorContainer = new Container();
@@ -385,7 +432,12 @@ export class InteractiveMode {
385
432
  this.outputPad = this.settingsManager.getOutputPad();
386
433
  // Register themes from resource loader and initialize
387
434
  setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
388
- this.themeController = new InteractiveThemeController(this.ui, this.settingsManager, (message) => this.showError(message), () => this.updateEditorBorderColor());
435
+ this.themeController = new InteractiveThemeController(this.ui, {
436
+ getSettingsManager: () => this.settingsManager,
437
+ showError: (message) => this.showError(message),
438
+ onChanged: () => this.updateEditorBorderColor(),
439
+ initialThemeSetting: options.initialThemeSetting,
440
+ });
389
441
  }
390
442
  getAutocompleteSourceTag(sourceInfo) {
391
443
  if (!sourceInfo) {
@@ -455,6 +507,15 @@ export class InteractiveMode {
455
507
  }));
456
508
  };
457
509
  }
510
+ const thinkingCommand = slashCommands.find((command) => command.name === "thinking");
511
+ if (thinkingCommand) {
512
+ thinkingCommand.getArgumentCompletions = (prefix) => {
513
+ return createFuzzyAutocompleteItems(this.session.getAvailableThinkingLevels(), prefix, (level) => level, (level) => ({
514
+ value: level,
515
+ label: level,
516
+ }));
517
+ };
518
+ }
458
519
  const loginCommand = slashCommands.find((command) => command.name === "login");
459
520
  if (loginCommand) {
460
521
  loginCommand.getArgumentCompletions = (prefix) => {
@@ -548,14 +609,14 @@ export class InteractiveMode {
548
609
  tui.setLayoutRoot(this.fullscreenLayoutRoot);
549
610
  }
550
611
  }
551
- stopInteractiveTui() {
552
- if (this.renderer.mode === "fullscreen") {
612
+ stopInteractiveTui(fullscreenExitOutput) {
613
+ if (this.renderer.mode === "fullscreen" && fullscreenExitOutput === "transcript") {
553
614
  while (this.renderer.hasOverlayEntries)
554
615
  this.renderer.hideOverlay();
555
616
  this.switchTuiMode("regular", false, false);
556
617
  this.renderer.renderNow();
557
618
  }
558
- this.ui.stop();
619
+ this.ui.stop({ preserveScreen: this.renderer.mode === "fullscreen" });
559
620
  }
560
621
  switchTuiMode(mode, restoreProgress = true, startRenderer = true) {
561
622
  const previousUi = this.renderer;
@@ -612,10 +673,9 @@ export class InteractiveMode {
612
673
  this.registerSignalHandlers();
613
674
  // Load changelog (only show new entries, skip for resumed sessions)
614
675
  this.changelogMarkdown = this.getChangelogForDisplay();
615
- // Ensure fd and rg are available (downloads if missing, adds to PATH via getBinDir)
616
- // Both are needed: fd for autocomplete, rg for grep tool and bash commands
617
- const [fdPath] = await Promise.all([ensureTool("fd"), ensureTool("rg")]);
618
- this.fdPath = fdPath;
676
+ // Resolve the effective mode before mounting or focusing components. Both
677
+ // operations can schedule a frame even before the renderer is started.
678
+ await this.refreshFooterPermissionMode();
619
679
  if (this.session.scopedModels.length > 0 && (this.options.verbose || !this.settingsManager.getQuietStartup())) {
620
680
  const modelList = this.session.scopedModels
621
681
  .map((sm) => {
@@ -659,9 +719,11 @@ export class InteractiveMode {
659
719
  this.widgetContainerBelow,
660
720
  this.footerContainer,
661
721
  ]);
722
+ // Accept text while startup completes, but only enable interrupt, exit, and submission feedback.
723
+ this.defaultEditor.onAction("app.clear", () => this.handleCtrlC());
724
+ this.defaultEditor.onCtrlD = () => this.handleCtrlD();
725
+ this.defaultEditor.onSubmit = (text) => this.handleStartupSubmit(text);
662
726
  this.ui.setFocus(this.editor);
663
- this.setupKeyHandlers();
664
- this.setupEditorSubmitHandler();
665
727
  // Start the UI before initializing extensions so session_start handlers can use interactive dialogs
666
728
  this.ui.start();
667
729
  this.isInitialized = true;
@@ -698,18 +760,42 @@ export class InteractiveMode {
698
760
  this.builtInHeader = new ApexSplashHeader(this.version, () => this.session.state.model?.id, () => this.session.sessionManager.getCwd(), this.options.verbose ? expandedInstructions : undefined, {
699
761
  topPadding: true,
700
762
  getSymbolPreset: () => this.settingsManager.getSymbolPreset(),
763
+ getExtraMetadata: () => {
764
+ // Which branch the session is rooted on differs between launches
765
+ // and is not derivable from the prompt, so it belongs here. The
766
+ // tray carries it too, but drops it first when space is tight.
767
+ const branch = this.footerDataProvider.getGitBranch();
768
+ return branch ? [{ label: "branch", value: branch }] : [];
769
+ },
770
+ getInventory: () => this.buildStartupInventory(),
771
+ inventoryHint: "/resources",
772
+ getShortcuts: () => this.buildStartupShortcuts(),
701
773
  getHint: () => "Apex Code can explain its own features and look up its docs.",
702
774
  });
703
775
  // Setup UI layout
776
+ this.startupInventoryVisible = true;
704
777
  this.headerContainer.addChild(this.builtInHeader);
705
778
  this.headerContainer.addChild(new Spacer(1));
706
779
  }
707
780
  else {
708
781
  // Minimal header when silenced
782
+ this.startupInventoryVisible = false;
709
783
  this.builtInHeader = new Text("", 0, 0);
710
784
  this.headerContainer.addChild(this.builtInHeader);
711
785
  }
712
786
  this.ui.requestRender();
787
+ // Ensure fd and rg are available after mounting the TUI (downloads if missing, adds to PATH via getBinDir)
788
+ // so slow downloads do not make startup appear frozen.
789
+ // Both are needed: fd for autocomplete, rg for grep tool and bash commands.
790
+ const [fdPath] = await Promise.all([
791
+ ensureTool("fd", (status) => this.showManagedToolStatus(status)),
792
+ ensureTool("rg", (status) => this.showManagedToolStatus(status)),
793
+ ]);
794
+ this.fdPath = fdPath;
795
+ // Enable the remaining input handlers only after managed-tool setup completes.
796
+ this.setupKeyHandlers();
797
+ this.setupEditorSubmitHandler();
798
+ this.ui.requestRender();
713
799
  // Initialize extensions first so resources are shown before messages
714
800
  await this.rebindCurrentSession();
715
801
  // Render initial messages AFTER showing loaded resources
@@ -726,6 +812,14 @@ export class InteractiveMode {
726
812
  });
727
813
  // Initialize available provider count for footer display
728
814
  await this.updateAvailableProviderCount();
815
+ // Flush the completed startup state before loading the remaining syntax grammars.
816
+ this.ui.renderNow();
817
+ void loadAllHighlightLanguages().then(() => {
818
+ if (!this.isInitialized)
819
+ return;
820
+ this.ui.invalidate();
821
+ this.ui.requestRender();
822
+ });
729
823
  }
730
824
  /**
731
825
  * Update terminal title with session name and cwd.
@@ -749,8 +843,7 @@ export class InteractiveMode {
749
843
  if (!getApexEnvironment("APEX_CODE_OFFLINE")) {
750
844
  const controller = new AbortController();
751
845
  const timeout = setTimeout(() => controller.abort(), 15_000);
752
- void this.session.modelRuntime
753
- .refresh({ signal: controller.signal })
846
+ void refreshModelCatalogs(this.session.modelRuntime, controller.signal)
754
847
  .then(() => this.updateAvailableProviderCount())
755
848
  .catch(() => { })
756
849
  .finally(() => clearTimeout(timeout));
@@ -782,7 +875,18 @@ export class InteractiveMode {
782
875
  }
783
876
  });
784
877
  // Show startup warnings
785
- const { migratedProviders, modelFallbackMessage, initialMessage, initialImages, initialMessages } = this.options;
878
+ const { migratedProviders, startupDiagnostics, modelFallbackMessage, initialMessage, initialImages, initialMessages, } = this.options;
879
+ for (const diagnostic of startupDiagnostics ?? []) {
880
+ if (diagnostic.type === "error") {
881
+ this.showError(diagnostic.message);
882
+ }
883
+ else if (diagnostic.type === "warning") {
884
+ this.showWarning(diagnostic.message);
885
+ }
886
+ else {
887
+ this.showStatus(diagnostic.message);
888
+ }
889
+ }
786
890
  if (migratedProviders && migratedProviders.length > 0) {
787
891
  this.showWarning(`Migrated credentials to auth.json: ${migratedProviders.join(", ")}`);
788
892
  }
@@ -1205,7 +1309,7 @@ export class InteractiveMode {
1205
1309
  showLoadedResources(options) {
1206
1310
  // Resource rendering is idempotent; chat clears no longer clear this separate container.
1207
1311
  this.loadedResourcesContainer.clear();
1208
- const showListing = options?.force || this.options.verbose || !this.settingsManager.getQuietStartup();
1312
+ const showListing = options?.force || this.options.verbose;
1209
1313
  const showDiagnostics = showListing || options?.showDiagnosticsWhenQuiet === true;
1210
1314
  if (!showListing && !showDiagnostics) {
1211
1315
  return;
@@ -1322,43 +1426,105 @@ export class InteractiveMode {
1322
1426
  addLoadedSection("Themes", themeCompactList, themeList);
1323
1427
  }
1324
1428
  }
1429
+ // Warnings collapse into the counted line when that line is on screen; the
1430
+ // full trace lives behind /resources. Errors always surface inline,
1431
+ // because an error means a resource did not load at all.
1325
1432
  if (showDiagnostics) {
1326
- const skillDiagnostics = skillsResult.diagnostics;
1327
- if (skillDiagnostics.length > 0) {
1328
- const warningLines = this.formatDiagnostics(skillDiagnostics, sourceInfos);
1329
- this.loadedResourcesContainer.addChild(new Text(`${theme.fg("warning", "[Skill conflicts]")}\n${warningLines}`, 0, 0));
1330
- this.loadedResourcesContainer.addChild(new Spacer(1));
1331
- }
1332
- const promptDiagnostics = promptsResult.diagnostics;
1333
- if (promptDiagnostics.length > 0) {
1334
- const warningLines = this.formatDiagnostics(promptDiagnostics, sourceInfos);
1335
- this.loadedResourcesContainer.addChild(new Text(`${theme.fg("warning", "[Prompt conflicts]")}\n${warningLines}`, 0, 0));
1336
- this.loadedResourcesContainer.addChild(new Spacer(1));
1337
- }
1338
- const extensionDiagnostics = [];
1339
- const extensionErrors = this.session.resourceLoader.getExtensions().errors;
1340
- if (extensionErrors.length > 0) {
1341
- for (const error of extensionErrors) {
1342
- extensionDiagnostics.push({ type: "error", message: error.error, path: error.path });
1343
- }
1344
- }
1345
- const commandDiagnostics = this.session.extensionRunner.getCommandDiagnostics();
1346
- extensionDiagnostics.push(...commandDiagnostics);
1347
- extensionDiagnostics.push(...this.getBuiltInCommandConflictDiagnostics(this.session.extensionRunner));
1348
- const shortcutDiagnostics = this.session.extensionRunner.getShortcutDiagnostics();
1349
- extensionDiagnostics.push(...shortcutDiagnostics);
1350
- if (extensionDiagnostics.length > 0) {
1351
- const warningLines = this.formatDiagnostics(extensionDiagnostics, sourceInfos);
1352
- this.loadedResourcesContainer.addChild(new Text(`${theme.fg("warning", "[Extension issues]")}\n${warningLines}`, 0, 0));
1353
- this.loadedResourcesContainer.addChild(new Spacer(1));
1354
- }
1355
- const themeDiagnostics = themesResult.diagnostics;
1356
- if (themeDiagnostics.length > 0) {
1357
- const warningLines = this.formatDiagnostics(themeDiagnostics, sourceInfos);
1358
- this.loadedResourcesContainer.addChild(new Text(`${theme.fg("warning", "[Theme conflicts]")}\n${warningLines}`, 0, 0));
1433
+ const collapseWarnings = !showListing && this.startupInventoryVisible;
1434
+ for (const group of this.collectDiagnosticGroups()) {
1435
+ const visible = collapseWarnings
1436
+ ? group.diagnostics.filter((diagnostic) => diagnostic.type === "error")
1437
+ : group.diagnostics;
1438
+ if (visible.length === 0)
1439
+ continue;
1440
+ const severity = visible.some((diagnostic) => diagnostic.type === "error") ? "error" : "warning";
1441
+ const body = this.formatDiagnostics(visible, sourceInfos);
1442
+ this.loadedResourcesContainer.addChild(new Text(`${theme.fg(severity, `[${group.label}]`)}\n${body}`, 0, 0));
1359
1443
  this.loadedResourcesContainer.addChild(new Spacer(1));
1360
1444
  }
1361
1445
  }
1446
+ // Counts on the startup screen are read at render time, so they fill in
1447
+ // once resources finish loading without an explicit refresh here.
1448
+ this.builtInHeader?.invalidate?.();
1449
+ }
1450
+ /**
1451
+ * Every startup diagnostic, grouped by the resource kind that produced it.
1452
+ *
1453
+ * One list feeds both the counted line in the header and the detailed
1454
+ * listing, so the two can never disagree about how many issues exist.
1455
+ */
1456
+ collectDiagnosticGroups() {
1457
+ const extensionDiagnostics = [
1458
+ ...this.session.resourceLoader
1459
+ .getExtensions()
1460
+ .errors.map((error) => ({ type: "error", message: error.error, path: error.path })),
1461
+ ...this.session.extensionRunner.getCommandDiagnostics(),
1462
+ ...this.getBuiltInCommandConflictDiagnostics(this.session.extensionRunner),
1463
+ ...this.session.extensionRunner.getShortcutDiagnostics(),
1464
+ ];
1465
+ return [
1466
+ { label: "Skill conflicts", diagnostics: this.session.resourceLoader.getSkills().diagnostics },
1467
+ { label: "Prompt conflicts", diagnostics: this.session.resourceLoader.getPrompts().diagnostics },
1468
+ { label: "Extension issues", diagnostics: extensionDiagnostics },
1469
+ { label: "Theme conflicts", diagnostics: this.session.resourceLoader.getThemes().diagnostics },
1470
+ ].filter((group) => group.diagnostics.length > 0);
1471
+ }
1472
+ /**
1473
+ * The counted line in the ruled band under the mark.
1474
+ *
1475
+ * The startup screen used to print all 152 skill names across fifteen rows.
1476
+ * A count answers the only question that screen can usefully answer, which
1477
+ * is what got loaded; the names live behind /resources now.
1478
+ */
1479
+ /**
1480
+ * The sigil row under the hint: how to reach commands, bash, and files.
1481
+ *
1482
+ * These are the only three characters that change what the composer does, so
1483
+ * they take the accent and everything around them stays quiet.
1484
+ */
1485
+ /** Live keybindings under the autocomplete dropdown. */
1486
+ buildAutocompleteFooter() {
1487
+ const separator = theme.fg("borderMuted", this.settingsManager.getSymbolPreset() === "ascii" ? " - " : " · ");
1488
+ return [
1489
+ rawKeyHint(`${keyText("tui.select.up")}/${keyText("tui.select.down")}`, "move"),
1490
+ keyHint("tui.select.confirm", "select"),
1491
+ keyHint("tui.input.tab", "complete"),
1492
+ keyHint("tui.select.cancel", "dismiss"),
1493
+ ].join(separator);
1494
+ }
1495
+ buildStartupShortcuts() {
1496
+ const entry = (sigil, label) => `${theme.fg("accent", sigil)} ${theme.fg("muted", label)}`;
1497
+ return [
1498
+ entry("/", "commands"),
1499
+ entry("!", "bash"),
1500
+ entry("@", "files"),
1501
+ `${theme.fg("dim", keyText("app.interrupt"))} ${theme.fg("muted", "interrupt")}`,
1502
+ ].join(" ");
1503
+ }
1504
+ buildStartupInventory() {
1505
+ const counted = (total, singular) => `${total} ${singular}${total === 1 ? "" : "s"}`;
1506
+ const parts = [];
1507
+ const skills = this.session.resourceLoader.getSkills().skills.length;
1508
+ const extensions = this.session.resourceLoader.getExtensions().extensions.filter((e) => !e.hidden).length;
1509
+ const prompts = this.session.resourceLoader.getPrompts().prompts.length;
1510
+ if (skills > 0)
1511
+ parts.push(theme.fg("muted", counted(skills, "skill")));
1512
+ if (extensions > 0)
1513
+ parts.push(theme.fg("muted", counted(extensions, "extension")));
1514
+ if (prompts > 0)
1515
+ parts.push(theme.fg("muted", counted(prompts, "prompt")));
1516
+ const diagnostics = this.collectDiagnosticGroups().flatMap((group) => group.diagnostics);
1517
+ const errors = diagnostics.filter((diagnostic) => diagnostic.type === "error").length;
1518
+ const conflicts = diagnostics.length - errors;
1519
+ if (errors > 0)
1520
+ parts.push(theme.fg("error", counted(errors, "error")));
1521
+ if (conflicts > 0)
1522
+ parts.push(theme.fg("warning", counted(conflicts, "conflict")));
1523
+ if (parts.length === 0)
1524
+ return undefined;
1525
+ const ascii = this.settingsManager.getSymbolPreset() === "ascii";
1526
+ const separator = theme.fg("borderMuted", ascii ? " - " : " · ");
1527
+ return parts.join(separator);
1362
1528
  }
1363
1529
  /**
1364
1530
  * Initialize the extension system with TUI-based UI context.
@@ -1446,7 +1612,7 @@ export class InteractiveMode {
1446
1612
  this.applyFullscreenScrollbarSetting();
1447
1613
  this.footer.setSession(this.session);
1448
1614
  this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
1449
- this.refreshFooterPermissionMode();
1615
+ void this.refreshFooterPermissionMode();
1450
1616
  this.footerDataProvider.setCwd(this.sessionManager.getCwd());
1451
1617
  this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
1452
1618
  this.outputPad = this.settingsManager.getOutputPad();
@@ -1489,7 +1655,7 @@ export class InteractiveMode {
1489
1655
  const message = error instanceof Error ? error.message : String(error);
1490
1656
  this.showError(`${prefix}: ${message}`);
1491
1657
  stopThemeWatcher();
1492
- this.stop();
1658
+ this.stop("transcript");
1493
1659
  process.exit(1);
1494
1660
  }
1495
1661
  renderCurrentSessionState() {
@@ -1499,9 +1665,14 @@ export class InteractiveMode {
1499
1665
  this.compactionQueuedMessages = [];
1500
1666
  this.streamingComponent = undefined;
1501
1667
  this.streamingMessage = undefined;
1502
- this.pendingTools.clear();
1668
+ this.clearPendingTools();
1503
1669
  this.renderInitialMessages();
1504
1670
  }
1671
+ clearPendingTools() {
1672
+ for (const component of this.pendingTools.values())
1673
+ component.dispose();
1674
+ this.pendingTools.clear();
1675
+ }
1505
1676
  /**
1506
1677
  * Get a registered tool definition by name (for custom rendering).
1507
1678
  */
@@ -1884,7 +2055,7 @@ export class InteractiveMode {
1884
2055
  /**
1885
2056
  * Show a selector for extensions.
1886
2057
  */
1887
- showExtensionSelector(title, options, opts) {
2058
+ showExtensionSelector(title, options, opts, enableSearch = false) {
1888
2059
  return new Promise((resolve) => {
1889
2060
  if (opts?.signal?.aborted) {
1890
2061
  resolve(undefined);
@@ -1903,7 +2074,12 @@ export class InteractiveMode {
1903
2074
  opts?.signal?.removeEventListener("abort", onAbort);
1904
2075
  this.hideExtensionSelector();
1905
2076
  resolve(undefined);
1906
- }, { tui: this.ui, timeout: opts?.timeout, onToggleToolsExpanded: () => this.toggleToolOutputExpansion() });
2077
+ }, {
2078
+ tui: this.ui,
2079
+ timeout: opts?.timeout,
2080
+ onToggleToolsExpanded: () => this.toggleToolOutputExpansion(),
2081
+ enableSearch,
2082
+ });
1907
2083
  this.disposeActiveSelector();
1908
2084
  this.editorContainer.clear();
1909
2085
  this.editorContainer.addChild(this.extensionSelector);
@@ -1922,6 +2098,40 @@ export class InteractiveMode {
1922
2098
  this.ui.setFocus(this.editor);
1923
2099
  this.ui.requestRender();
1924
2100
  }
2101
+ async showConfigurationIndex() {
2102
+ const choice = await this.showExtensionSelector("Configuration", [
2103
+ "Settings",
2104
+ "Model",
2105
+ "Model cycling",
2106
+ "Provider login",
2107
+ "Provider logout",
2108
+ "Project trust",
2109
+ "Resources, extensions, and MCP adapters",
2110
+ ], undefined, true);
2111
+ switch (choice) {
2112
+ case "Settings":
2113
+ await this.showSettingsSelector();
2114
+ break;
2115
+ case "Model":
2116
+ this.showModelSelector();
2117
+ break;
2118
+ case "Model cycling":
2119
+ await this.showModelsSelector();
2120
+ break;
2121
+ case "Provider login":
2122
+ await this.handleLoginCommand();
2123
+ break;
2124
+ case "Provider logout":
2125
+ this.showOAuthSelector("logout");
2126
+ break;
2127
+ case "Project trust":
2128
+ this.showTrustSelector();
2129
+ break;
2130
+ case "Resources, extensions, and MCP adapters":
2131
+ this.showStatus("Run apex-code config to manage resources, extensions, and MCP adapters.");
2132
+ break;
2133
+ }
2134
+ }
1925
2135
  /**
1926
2136
  * Show a confirmation dialog for extensions.
1927
2137
  */
@@ -2233,6 +2443,8 @@ export class InteractiveMode {
2233
2443
  const wasBashMode = this.isBashMode;
2234
2444
  this.isBashMode = text.trimStart().startsWith("!");
2235
2445
  if (wasBashMode !== this.isBashMode) {
2446
+ if (this.isBashMode)
2447
+ this.offerFirstUseHint("bash");
2236
2448
  this.updateEditorBorderColor();
2237
2449
  }
2238
2450
  };
@@ -2281,6 +2493,10 @@ export class InteractiveMode {
2281
2493
  // Silently ignore clipboard errors (may not have permission, etc.)
2282
2494
  }
2283
2495
  }
2496
+ handleStartupSubmit(text) {
2497
+ this.editor.setText(text);
2498
+ this.showStatus("Startup is still in progress");
2499
+ }
2284
2500
  setupEditorSubmitHandler() {
2285
2501
  this.defaultEditor.onSubmit = async (text) => {
2286
2502
  text = text.trim();
@@ -2294,6 +2510,11 @@ export class InteractiveMode {
2294
2510
  this.pendingPermissionModeWrite = undefined;
2295
2511
  }
2296
2512
  // Handle commands
2513
+ if (text === "/config") {
2514
+ this.editor.setText("");
2515
+ await this.showConfigurationIndex();
2516
+ return;
2517
+ }
2297
2518
  if (text === "/settings") {
2298
2519
  this.editor.setText("");
2299
2520
  await this.showSettingsSelector();
@@ -2310,6 +2531,12 @@ export class InteractiveMode {
2310
2531
  await this.handleModelCommand(searchTerm);
2311
2532
  return;
2312
2533
  }
2534
+ if (text === "/thinking" || text.startsWith("/thinking ")) {
2535
+ const searchTerm = text.startsWith("/thinking ") ? text.slice(10).trim() : undefined;
2536
+ this.editor.setText("");
2537
+ this.handleThinkingCommand(searchTerm);
2538
+ return;
2539
+ }
2313
2540
  if (text === "/export" || text.startsWith("/export ")) {
2314
2541
  await this.handleExportCommand(text);
2315
2542
  this.editor.setText("");
@@ -2345,6 +2572,11 @@ export class InteractiveMode {
2345
2572
  this.editor.setText("");
2346
2573
  return;
2347
2574
  }
2575
+ if (text === "/resources") {
2576
+ this.showLoadedResources({ force: true });
2577
+ this.editor.setText("");
2578
+ return;
2579
+ }
2348
2580
  if (text === "/hotkeys") {
2349
2581
  this.handleHotkeysCommand();
2350
2582
  this.editor.setText("");
@@ -2485,7 +2717,7 @@ export class InteractiveMode {
2485
2717
  this.footer.invalidate();
2486
2718
  switch (event.type) {
2487
2719
  case "agent_start":
2488
- this.pendingTools.clear();
2720
+ this.clearPendingTools();
2489
2721
  if (this.settingsManager.getShowTerminalProgress()) {
2490
2722
  this.ui.terminal.setProgress(true);
2491
2723
  }
@@ -2537,6 +2769,7 @@ export class InteractiveMode {
2537
2769
  this.streamingMessage = event.message;
2538
2770
  this.chatContainer.addChild(this.streamingComponent);
2539
2771
  this.streamingComponent.updateContent(this.streamingMessage, true);
2772
+ this.updateEditorBorderColor();
2540
2773
  this.ui.requestRender();
2541
2774
  }
2542
2775
  break;
@@ -2554,6 +2787,7 @@ export class InteractiveMode {
2554
2787
  component.setExpanded(this.toolOutputExpanded);
2555
2788
  this.chatContainer.addChild(component);
2556
2789
  this.pendingTools.set(content.id, component);
2790
+ this.offerFirstUseHint("tool-expand");
2557
2791
  }
2558
2792
  else {
2559
2793
  const component = this.pendingTools.get(content.id);
@@ -2591,7 +2825,7 @@ export class InteractiveMode {
2591
2825
  isError: true,
2592
2826
  });
2593
2827
  }
2594
- this.pendingTools.clear();
2828
+ this.clearPendingTools();
2595
2829
  }
2596
2830
  else {
2597
2831
  // Args are now complete - trigger diff computation for edit tools
@@ -2603,6 +2837,7 @@ export class InteractiveMode {
2603
2837
  this.streamingComponent = undefined;
2604
2838
  this.streamingMessage = undefined;
2605
2839
  this.footer.invalidate();
2840
+ this.updateEditorBorderColor();
2606
2841
  }
2607
2842
  this.ui.requestRender();
2608
2843
  break;
@@ -2651,7 +2886,7 @@ export class InteractiveMode {
2651
2886
  this.streamingComponent = undefined;
2652
2887
  this.streamingMessage = undefined;
2653
2888
  }
2654
- this.pendingTools.clear();
2889
+ this.clearPendingTools();
2655
2890
  this.ui.requestRender();
2656
2891
  break;
2657
2892
  case "agent_settled":
@@ -2688,9 +2923,21 @@ export class InteractiveMode {
2688
2923
  }
2689
2924
  }
2690
2925
  else if (event.result) {
2926
+ const entries = this.sessionManager.buildContextEntries();
2927
+ if (entries[0]?.type !== "compaction") {
2928
+ throw new Error("Completed compaction is missing from the session context");
2929
+ }
2691
2930
  this.chatContainer.clear();
2692
- this.rebuildChatFromMessages();
2931
+ // The latest compaction is prepended for model context; append it below at its chronological position.
2932
+ this.renderSessionEntries(entries.slice(1));
2693
2933
  this.addMessageToChat(createCompactionSummaryMessage(event.result.summary, event.result.tokensBefore, new Date().toISOString()));
2934
+ if (event.result.usage) {
2935
+ this.addCompactionCostNotice({
2936
+ type: "compaction_cost",
2937
+ kind: "compaction",
2938
+ usage: event.result.usage,
2939
+ });
2940
+ }
2694
2941
  this.footer.invalidate();
2695
2942
  }
2696
2943
  else if (event.errorMessage) {
@@ -2763,6 +3010,19 @@ export class InteractiveMode {
2763
3010
  : message.content.filter((c) => c.type === "text");
2764
3011
  return textBlocks.map((c) => c.text).join("");
2765
3012
  }
3013
+ /** Show a managed-tool status update in the chat. */
3014
+ showManagedToolStatus(status) {
3015
+ if (!this.managedToolStatusStarted) {
3016
+ this.chatContainer.addChild(new Spacer(1));
3017
+ this.managedToolStatusStarted = true;
3018
+ }
3019
+ const message = status.type === "warning" ? `Warning: ${status.message}` : status.message;
3020
+ const color = status.type === "warning" ? "warning" : "dim";
3021
+ this.chatContainer.addChild(new Text(theme.fg(color, message), 1, 0));
3022
+ this.lastStatusSpacer = undefined;
3023
+ this.lastStatusText = undefined;
3024
+ this.ui.requestRender();
3025
+ }
2766
3026
  /**
2767
3027
  * Show a status message in the chat.
2768
3028
  *
@@ -2883,7 +3143,7 @@ export class InteractiveMode {
2883
3143
  }
2884
3144
  }
2885
3145
  renderSessionItems(items, options = {}) {
2886
- this.pendingTools.clear();
3146
+ this.clearPendingTools();
2887
3147
  const renderedPendingTools = new Map();
2888
3148
  // Cache-miss notices are not persisted; re-derive them from the full entry
2889
3149
  // list and re-inject them after the assistant messages that paid for them.
@@ -2899,6 +3159,10 @@ export class InteractiveMode {
2899
3159
  this.addCustomEntryToChat(item);
2900
3160
  continue;
2901
3161
  }
3162
+ if (isCompactionCostNotice(item)) {
3163
+ this.addCompactionCostNotice(item);
3164
+ continue;
3165
+ }
2902
3166
  const message = item;
2903
3167
  // Assistant messages need special handling for tool calls
2904
3168
  if (message.role === "assistant") {
@@ -2966,10 +3230,28 @@ export class InteractiveMode {
2966
3230
  if (entry.type === "custom") {
2967
3231
  return [entry];
2968
3232
  }
2969
- return sessionEntryToContextMessages(entry);
3233
+ const messages = sessionEntryToContextMessages(entry);
3234
+ if ((entry.type === "compaction" || entry.type === "branch_summary") && entry.usage && messages.length > 0) {
3235
+ return [...messages, { type: "compaction_cost", kind: entry.type, usage: entry.usage }];
3236
+ }
3237
+ return messages;
2970
3238
  });
2971
3239
  this.renderSessionItems(items, options);
2972
3240
  }
3241
+ /**
3242
+ * Render billing usage for a compaction or branch summary. The notice is derived
3243
+ * from persisted summary usage and is not stored as a separate session entry.
3244
+ */
3245
+ addCompactionCostNotice(notice) {
3246
+ if (!this.settingsManager.getShowCacheMissNotices())
3247
+ return;
3248
+ const { usage } = notice;
3249
+ const tokens = usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
3250
+ const cost = usage.cost.total >= 0.01 ? ` (~$${usage.cost.total.toFixed(2)})` : "";
3251
+ const label = notice.kind === "compaction" ? "Compaction" : "Branch summary";
3252
+ this.chatContainer.addChild(new Spacer(1));
3253
+ this.chatContainer.addChild(new Text(theme.fg("warning", `${label}: ${formatTokens(tokens)} tokens billed${cost}`), 1, 0));
3254
+ }
2973
3255
  /**
2974
3256
  * Show a transcript notice when a completed assistant message paid for a
2975
3257
  * significant cache miss. Only states observable facts: the miss itself,
@@ -3241,6 +3523,7 @@ export class InteractiveMode {
3241
3523
  this.editor.addToHistory?.(text);
3242
3524
  this.editor.setText("");
3243
3525
  await this.session.prompt(text, { streamingBehavior: "followUp" });
3526
+ this.offerFirstUseHint("queue");
3244
3527
  this.updatePendingMessagesDisplay();
3245
3528
  this.ui.requestRender();
3246
3529
  }
@@ -3260,6 +3543,7 @@ export class InteractiveMode {
3260
3543
  }
3261
3544
  }
3262
3545
  updateEditorBorderColor() {
3546
+ this.defaultEditor.setModeLabel(this.isBashMode ? "bash" : this.session.isStreaming ? "busy" : undefined);
3263
3547
  if (this.isBashMode) {
3264
3548
  this.editor.borderColor = theme.getBashModeBorderColor();
3265
3549
  }
@@ -3320,6 +3604,7 @@ export class InteractiveMode {
3320
3604
  this.showStatus(`Tool output: ${expanded ? "expanded" : "collapsed"}`);
3321
3605
  }
3322
3606
  toggleThinkingBlockVisibility() {
3607
+ this.offerFirstUseHint("thinking");
3323
3608
  this.hideThinkingBlock = !this.hideThinkingBlock;
3324
3609
  this.settingsManager.setHideThinkingBlock(this.hideThinkingBlock);
3325
3610
  // Rebuild chat from session messages
@@ -3610,11 +3895,12 @@ export class InteractiveMode {
3610
3895
  * between two sessions in one process. A footer left showing the old value
3611
3896
  * would under-report bypassPermissions, which is the one direction that matters.
3612
3897
  */
3613
- refreshFooterPermissionMode() {
3614
- void this.session.getPermissionMode().then((resolution) => {
3615
- this.footer.setPermissionMode(resolution?.mode ?? "default");
3898
+ async refreshFooterPermissionMode() {
3899
+ const resolution = await this.session.getPermissionMode();
3900
+ this.footer.setPermissionMode(resolution?.mode ?? "default");
3901
+ if (this.isInitialized) {
3616
3902
  this.ui.requestRender();
3617
- });
3903
+ }
3618
3904
  }
3619
3905
  /**
3620
3906
  * Writes the mode to user scope, then reports what is actually in force. The
@@ -3651,8 +3937,14 @@ export class InteractiveMode {
3651
3937
  const permissionMode = await this.session.getPermissionMode();
3652
3938
  this.showSelector((done) => {
3653
3939
  let selector;
3940
+ const defaultProvider = this.settingsManager.getDefaultProvider();
3941
+ const defaultModelId = this.settingsManager.getDefaultModel();
3942
+ const defaultModel = defaultProvider && defaultModelId ? `${defaultProvider}/${defaultModelId}` : "not set";
3654
3943
  selector = new SettingsSelectorComponent({
3655
3944
  autoCompact: this.session.autoCompactionEnabled,
3945
+ defaultModel,
3946
+ currentModel: this.session.model,
3947
+ availableDefaultModels: this.session.modelRuntime.getAvailableSnapshot(),
3656
3948
  showImages: this.settingsManager.getShowImages(),
3657
3949
  imageWidthCells: this.settingsManager.getImageWidthCells(),
3658
3950
  autoResizeImages: this.settingsManager.getImageAutoResize(),
@@ -3662,9 +3954,10 @@ export class InteractiveMode {
3662
3954
  followUpMode: this.session.followUpMode,
3663
3955
  transport: this.settingsManager.getTransport(),
3664
3956
  httpIdleTimeoutMs: this.settingsManager.getHttpIdleTimeoutMs(),
3665
- thinkingLevel: this.session.thinkingLevel,
3666
- availableThinkingLevels: this.session.getAvailableThinkingLevels(),
3667
- currentTheme: this.settingsManager.getThemeSetting() || "dark",
3957
+ thinkingLevel: this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL,
3958
+ availableThinkingLevels: [...THINKING_LEVEL_OPTIONS],
3959
+ modelThinkingLevels: this.settingsManager.getAllModelThinkingLevels(),
3960
+ currentTheme: this.themeController.getThemeSelection() || "dark",
3668
3961
  terminalTheme: this.themeController.getTerminalTheme(),
3669
3962
  availableThemes: getAvailableThemes(),
3670
3963
  hideThinkingBlock: this.hideThinkingBlock,
@@ -3684,6 +3977,7 @@ export class InteractiveMode {
3684
3977
  clearOnShrink: this.settingsManager.getClearOnShrink(),
3685
3978
  showTerminalProgress: this.settingsManager.getShowTerminalProgress(),
3686
3979
  tuiMode: this.ui.mode,
3980
+ fullscreenExitOutput: this.settingsManager.getFullscreenExitOutput(),
3687
3981
  fullscreenScrollbar: this.settingsManager.getFullscreenScrollbar(),
3688
3982
  warnings: this.settingsManager.getWarnings(),
3689
3983
  }, {
@@ -3732,14 +4026,30 @@ export class InteractiveMode {
3732
4026
  configureHttpDispatcher(timeoutMs);
3733
4027
  this.showStatus(`HTTP idle timeout: ${formatHttpIdleTimeoutMs(timeoutMs)}`);
3734
4028
  },
3735
- onThinkingLevelChange: (level) => {
3736
- this.session.setThinkingLevel(level);
3737
- this.footer.invalidate();
3738
- this.updateEditorBorderColor();
4029
+ onModelThinkingLevelChange: (provider, modelId, level) => {
4030
+ this.settingsManager.setModelThinkingLevel(provider, modelId, level);
4031
+ // If the override is for the current model, apply it to the session too
4032
+ const current = this.session.model;
4033
+ if (current && current.provider === provider && current.id === modelId) {
4034
+ this.session.setThinkingLevel(level);
4035
+ this.footer.invalidate();
4036
+ this.updateEditorBorderColor();
4037
+ }
4038
+ },
4039
+ onModelThinkingLevelRemove: (provider, modelId) => {
4040
+ this.settingsManager.removeModelThinkingLevel(provider, modelId);
4041
+ // If the override was for the current model, revert to global default
4042
+ const current = this.session.model;
4043
+ if (current && current.provider === provider && current.id === modelId) {
4044
+ const globalDefault = this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;
4045
+ this.session.setThinkingLevel(globalDefault);
4046
+ this.footer.invalidate();
4047
+ this.updateEditorBorderColor();
4048
+ }
3739
4049
  },
3740
4050
  onThemeChange: (themeSetting) => {
3741
4051
  this.settingsManager.setTheme(themeSetting);
3742
- void this.themeController.applyFromSettings();
4052
+ void this.themeController.setThemeSetting(themeSetting);
3743
4053
  },
3744
4054
  onThemePreview: (themeName) => this.themeController.preview(themeName),
3745
4055
  onHideThinkingBlockChange: (hidden) => {
@@ -3841,6 +4151,9 @@ export class InteractiveMode {
3841
4151
  this.statusContainer.clear();
3842
4152
  this.showStatus(`TUI mode: ${mode}`);
3843
4153
  },
4154
+ onFullscreenExitOutputChange: (output) => {
4155
+ this.settingsManager.setFullscreenExitOutput(output);
4156
+ },
3844
4157
  onFullscreenScrollbarChange: (mode) => {
3845
4158
  this.settingsManager.setFullscreenScrollbar(mode);
3846
4159
  this.applyFullscreenScrollbarSetting();
@@ -3856,6 +4169,44 @@ export class InteractiveMode {
3856
4169
  return { component: selector, focus: selector.getSettingsList() };
3857
4170
  });
3858
4171
  }
4172
+ handleThinkingCommand(searchTerm) {
4173
+ const availableLevels = this.session.getAvailableThinkingLevels();
4174
+ if (!searchTerm) {
4175
+ this.showThinkingSelector();
4176
+ return;
4177
+ }
4178
+ const normalized = searchTerm.trim().toLowerCase();
4179
+ const level = availableLevels.find((candidate) => candidate.toLowerCase() === normalized);
4180
+ if (!level) {
4181
+ this.showError(`Unknown thinking level "${searchTerm}". Available levels: ${availableLevels.join(", ")}.`);
4182
+ return;
4183
+ }
4184
+ this.selectThinkingLevel(level, false);
4185
+ }
4186
+ selectThinkingLevel(level, persist) {
4187
+ try {
4188
+ this.session.setThinkingLevel(level, { persist });
4189
+ this.footer.invalidate();
4190
+ this.updateEditorBorderColor();
4191
+ this.showStatus(persist ? `Default thinking level: ${level}` : `Thinking level: ${level}`);
4192
+ }
4193
+ catch (error) {
4194
+ this.showError(error instanceof Error ? error.message : String(error));
4195
+ }
4196
+ }
4197
+ showThinkingSelector() {
4198
+ this.showSelector((done) => {
4199
+ const selectLevel = (level, persist) => {
4200
+ this.selectThinkingLevel(level, persist);
4201
+ done();
4202
+ };
4203
+ const selector = new ThinkingSelectorComponent(this.session.thinkingLevel ?? DEFAULT_THINKING_LEVEL, this.session.getAvailableThinkingLevels(), (level) => selectLevel(level, false), () => {
4204
+ done();
4205
+ this.ui.requestRender();
4206
+ }, (level) => selectLevel(level, true), this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL);
4207
+ return { component: selector, focus: selector };
4208
+ });
4209
+ }
3859
4210
  async handleModelCommand(searchTerm) {
3860
4211
  if (!searchTerm) {
3861
4212
  this.showModelSelector();
@@ -3864,7 +4215,7 @@ export class InteractiveMode {
3864
4215
  const model = await this.findExactModelMatch(searchTerm);
3865
4216
  if (model) {
3866
4217
  try {
3867
- await this.session.setModel(model);
4218
+ await this.session.setModel(model, { persist: false });
3868
4219
  this.footer.invalidate();
3869
4220
  this.updateEditorBorderColor();
3870
4221
  this.showStatus(`Model: ${model.id}`);
@@ -3893,7 +4244,7 @@ export class InteractiveMode {
3893
4244
  controller.abort();
3894
4245
  }, 15_000);
3895
4246
  try {
3896
- const result = await this.session.modelRuntime.refresh({ signal: controller.signal });
4247
+ const result = await refreshModelCatalogs(this.session.modelRuntime, controller.signal);
3897
4248
  if (result.aborted && timedOut) {
3898
4249
  this.showWarning("Model refresh timed out; searching cached models.");
3899
4250
  }
@@ -3981,7 +4332,7 @@ export class InteractiveMode {
3981
4332
  onSelect: (selection) => {
3982
4333
  trustStore.setMany(selection.updates);
3983
4334
  done();
3984
- this.showStatus(`Saved trust decision: ${selection.trusted ? "trusted" : "untrusted"}. Restart pi for this to take effect.`);
4335
+ this.showStatus(`Saved trust decision: ${selection.trusted ? "trusted" : "untrusted"}. Restart ${APP_NAME} for this to take effect.`);
3985
4336
  },
3986
4337
  onCancel: () => {
3987
4338
  done();
@@ -3993,13 +4344,13 @@ export class InteractiveMode {
3993
4344
  }
3994
4345
  showModelSelector(initialSearchInput) {
3995
4346
  this.showSelector((done) => {
3996
- const selector = new ModelSelectorComponent(this.ui, this.session.model, this.settingsManager, this.session.modelRuntime, this.session.scopedModels, async (model) => {
4347
+ const selectModel = async (model, persist) => {
3997
4348
  try {
3998
- await this.session.setModel(model);
4349
+ await this.session.setModel(model, { persist });
3999
4350
  this.footer.invalidate();
4000
4351
  this.updateEditorBorderColor();
4001
4352
  done();
4002
- this.showStatus(`Model: ${model.id}`);
4353
+ this.showStatus(persist ? `Default model: ${model.provider}/${model.id}` : `Model: ${model.id}`);
4003
4354
  void this.maybeWarnAboutAnthropicSubscriptionAuth(model);
4004
4355
  this.checkDaxnutsEasterEgg(model);
4005
4356
  }
@@ -4007,10 +4358,13 @@ export class InteractiveMode {
4007
4358
  done();
4008
4359
  this.showError(error instanceof Error ? error.message : String(error));
4009
4360
  }
4010
- }, () => {
4361
+ };
4362
+ const defaultProvider = this.settingsManager.getDefaultProvider();
4363
+ const defaultModel = this.settingsManager.getDefaultModel();
4364
+ const selector = new ModelSelectorComponent(this.ui, this.session.model, this.session.modelRuntime, this.session.scopedModels, (model) => selectModel(model, false), () => {
4011
4365
  done();
4012
4366
  this.ui.requestRender();
4013
- }, initialSearchInput);
4367
+ }, initialSearchInput, (model) => selectModel(model, true), defaultProvider && defaultModel ? { provider: defaultProvider, id: defaultModel } : undefined);
4014
4368
  return { component: selector, focus: selector, dispose: () => selector.dispose() };
4015
4369
  });
4016
4370
  }
@@ -4081,8 +4435,7 @@ export class InteractiveMode {
4081
4435
  this.ui.requestRender();
4082
4436
  },
4083
4437
  });
4084
- void this.session.modelRuntime
4085
- .refresh({ signal: controller.signal })
4438
+ void refreshModelCatalogs(this.session.modelRuntime, controller.signal)
4086
4439
  .then((result) => {
4087
4440
  if (disposed)
4088
4441
  return;
@@ -4567,7 +4920,11 @@ export class InteractiveMode {
4567
4920
  if (isUnknownModel(previousModel)) {
4568
4921
  const availableModels = this.session.modelRuntime.getAvailableSnapshot();
4569
4922
  const providerModels = availableModels.filter((model) => model.provider === providerId);
4570
- if (!hasDefaultModelProvider(providerId)) {
4923
+ // Matches LLAMA_PROVIDER_ID from extensions/llama/provider.ts; kept inline to avoid coupling interactive mode to the built-in extension.
4924
+ if (providerId === "llama.cpp") {
4925
+ selectionError = llamaCppPostLoginGuidance(actionLabel, providerModels.length);
4926
+ }
4927
+ else if (!hasDefaultModelProvider(providerId)) {
4571
4928
  selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
4572
4929
  }
4573
4930
  else if (providerModels.length === 0) {
@@ -4581,7 +4938,7 @@ export class InteractiveMode {
4581
4938
  }
4582
4939
  else {
4583
4940
  try {
4584
- await this.session.setModel(selectedModel);
4941
+ await this.session.setModel(selectedModel, { persist: true });
4585
4942
  }
4586
4943
  catch (error) {
4587
4944
  selectedModel = undefined;
@@ -4873,7 +5230,9 @@ export class InteractiveMode {
4873
5230
  this.showStatus(`Session exported to: ${filePath}`);
4874
5231
  }
4875
5232
  else {
4876
- const filePath = await this.session.exportToHtml(outputPath);
5233
+ const filePath = await this.session.exportToHtml(outputPath, {
5234
+ themeName: theme.name,
5235
+ });
4877
5236
  this.showStatus(`Session exported to: ${filePath}`);
4878
5237
  }
4879
5238
  }
@@ -5415,7 +5774,7 @@ export class InteractiveMode {
5415
5774
  // Ignore, will be emitted as an event
5416
5775
  }
5417
5776
  }
5418
- stop() {
5777
+ stop(fullscreenExitOutput = this.settingsManager.getFullscreenExitOutput()) {
5419
5778
  this.disposeActiveSelector();
5420
5779
  if (this.settingsManager.getShowTerminalProgress()) {
5421
5780
  this.ui.terminal.setProgress(false);
@@ -5429,7 +5788,7 @@ export class InteractiveMode {
5429
5788
  this.unsubscribe();
5430
5789
  }
5431
5790
  if (this.isInitialized) {
5432
- this.stopInteractiveTui();
5791
+ this.stopInteractiveTui(fullscreenExitOutput);
5433
5792
  this.isInitialized = false;
5434
5793
  }
5435
5794
  this.unregisterSignalHandlers();