@oh-my-pi/pi-coding-agent 17.2.4 → 17.2.6

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 (348) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/dist/{CHANGELOG-bpmhv26t.md → CHANGELOG-gs76k6wc.md} +69 -0
  3. package/dist/cli.js +4438 -3962
  4. package/dist/{template-8vdv6xb4.js → template-c2hyaytt.js} +8 -26
  5. package/dist/types/cli/args.d.ts +4 -1
  6. package/dist/types/cli/browser-relay-cli.d.ts +15 -0
  7. package/dist/types/cli/command-help.d.ts +99 -0
  8. package/dist/types/cli/gc-cli.d.ts +1 -0
  9. package/dist/types/cli/help-extra.d.ts +2 -0
  10. package/dist/types/cli/thinking-levels.d.ts +5 -0
  11. package/dist/types/cli/update-cli.d.ts +13 -0
  12. package/dist/types/commands/browser-relay.d.ts +38 -0
  13. package/dist/types/commands/commit.d.ts +3 -0
  14. package/dist/types/commands/complete.d.ts +1 -1
  15. package/dist/types/commands/completions.d.ts +6 -0
  16. package/dist/types/commands/grep.d.ts +3 -0
  17. package/dist/types/commands/join.d.ts +4 -0
  18. package/dist/types/commands/launch-help.d.ts +171 -0
  19. package/dist/types/commands/launch.d.ts +5 -1
  20. package/dist/types/commands/models.d.ts +3 -0
  21. package/dist/types/commands/say.d.ts +10 -0
  22. package/dist/types/commands/worktree.d.ts +3 -0
  23. package/dist/types/config/keybindings.d.ts +5 -0
  24. package/dist/types/config/model-discovery.d.ts +2 -2
  25. package/dist/types/config/models-config-schema-bundle.d.ts +3 -0
  26. package/dist/types/config/models-config-schema.d.ts +2 -0
  27. package/dist/types/config/models-config.d.ts +1 -0
  28. package/dist/types/config/service-tier.d.ts +2 -0
  29. package/dist/types/config/settings-schema.d.ts +69 -21
  30. package/dist/types/cursor-bridge-tools.d.ts +1 -1
  31. package/dist/types/cursor.d.ts +7 -4
  32. package/dist/types/edit/index.d.ts +3 -3
  33. package/dist/types/edit/modes/replace.d.ts +22 -15
  34. package/dist/types/edit/renderer.d.ts +1 -1
  35. package/dist/types/eval/py/kernel.d.ts +3 -1
  36. package/dist/types/eval/py/spawn-options.d.ts +12 -16
  37. package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +8 -1
  38. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -1
  39. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +2 -0
  40. package/dist/types/launch/broker.d.ts +1 -1
  41. package/dist/types/launch/client.d.ts +15 -4
  42. package/dist/types/launch/ensure.d.ts +8 -0
  43. package/dist/types/launch/paths.d.ts +1 -1
  44. package/dist/types/launch/protocol.d.ts +19 -2
  45. package/dist/types/lsp/client.d.ts +2 -0
  46. package/dist/types/lsp/mux/daemon.d.ts +15 -0
  47. package/dist/types/lsp/mux/protocol.d.ts +57 -0
  48. package/dist/types/lsp/mux/server.d.ts +18 -0
  49. package/dist/types/lsp/types.d.ts +22 -2
  50. package/dist/types/main.d.ts +2 -0
  51. package/dist/types/mnemopi/embed-client.d.ts +1 -1
  52. package/dist/types/mnemopi/state.d.ts +0 -23
  53. package/dist/types/modes/components/assistant-message.d.ts +2 -0
  54. package/dist/types/modes/components/btw-panel.d.ts +3 -0
  55. package/dist/types/modes/components/custom-editor.d.ts +2 -1
  56. package/dist/types/modes/components/read-tool-group.d.ts +2 -0
  57. package/dist/types/modes/components/stripped-tool-calls-placeholder.d.ts +12 -0
  58. package/dist/types/modes/components/tool-execution.d.ts +2 -0
  59. package/dist/types/modes/controllers/btw-controller.d.ts +2 -0
  60. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  61. package/dist/types/modes/controllers/input-controller.d.ts +1 -0
  62. package/dist/types/modes/interactive-mode.d.ts +5 -1
  63. package/dist/types/modes/types.d.ts +4 -1
  64. package/dist/types/modes/workflow.d.ts +2 -1
  65. package/dist/types/sdk.d.ts +3 -1
  66. package/dist/types/secrets/index.d.ts +1 -1
  67. package/dist/types/security/contracts/schemas.d.ts +405 -403
  68. package/dist/types/session/agent-session-types.d.ts +8 -1
  69. package/dist/types/session/agent-session.d.ts +26 -2
  70. package/dist/types/session/indexed-session-storage.d.ts +1 -0
  71. package/dist/types/session/launch-completion.d.ts +10 -0
  72. package/dist/types/session/session-advisors.d.ts +1 -1
  73. package/dist/types/session/session-context.d.ts +2 -1
  74. package/dist/types/session/session-entries.d.ts +15 -1
  75. package/dist/types/session/session-maintenance.d.ts +1 -0
  76. package/dist/types/session/session-manager.d.ts +23 -11
  77. package/dist/types/session/session-stats.d.ts +10 -0
  78. package/dist/types/session/session-storage.d.ts +15 -3
  79. package/dist/types/session/yield-queue.d.ts +6 -1
  80. package/dist/types/system-prompt.d.ts +4 -1
  81. package/dist/types/task/index.d.ts +2 -1
  82. package/dist/types/task/spawn-policy.d.ts +5 -0
  83. package/dist/types/thinking.d.ts +1 -7
  84. package/dist/types/tools/ast-grep.d.ts +1 -1
  85. package/dist/types/tools/browser/registry.d.ts +2 -1
  86. package/dist/types/tools/browser/relay/bridge.d.ts +35 -0
  87. package/dist/types/tools/browser/relay/daemon.d.ts +16 -0
  88. package/dist/types/tools/browser/relay/kind.d.ts +19 -0
  89. package/dist/types/tools/browser/relay/protocol.d.ts +97 -0
  90. package/dist/types/tools/browser/relay/server.d.ts +35 -0
  91. package/dist/types/tools/browser/render.d.ts +1 -0
  92. package/dist/types/tools/browser.d.ts +3 -0
  93. package/dist/types/tools/computer/exposure.d.ts +9 -7
  94. package/dist/types/tools/computer/protocol.d.ts +70 -17
  95. package/dist/types/tools/computer/supervisor.d.ts +23 -10
  96. package/dist/types/tools/computer/worker-entry.d.ts +1 -0
  97. package/dist/types/tools/computer/worker.d.ts +42 -7
  98. package/dist/types/tools/computer-renderer.d.ts +7 -5
  99. package/dist/types/tools/computer.d.ts +25 -44
  100. package/dist/types/tools/context.d.ts +3 -0
  101. package/dist/types/tools/glob.d.ts +1 -1
  102. package/dist/types/tools/grep.d.ts +1 -1
  103. package/dist/types/tools/index.d.ts +9 -0
  104. package/dist/types/tools/render-utils.d.ts +2 -0
  105. package/dist/types/tools/{browser/run-cancellation.d.ts → run-scope.d.ts} +8 -8
  106. package/dist/types/tools/tool-timeouts.d.ts +5 -0
  107. package/dist/types/tools/xdev.d.ts +8 -0
  108. package/dist/types/utils/clipboard.d.ts +2 -2
  109. package/dist/types/web/kagi.d.ts +1 -0
  110. package/dist/types/web/search/index.d.ts +1 -1
  111. package/dist/types/web/search/providers/anthropic.d.ts +1 -0
  112. package/dist/types/web/search/providers/base.d.ts +2 -0
  113. package/dist/types/web/search/providers/brave.d.ts +1 -0
  114. package/dist/types/web/search/providers/browser-page.d.ts +1 -0
  115. package/dist/types/web/search/providers/codex.d.ts +1 -0
  116. package/dist/types/web/search/providers/exa.d.ts +1 -0
  117. package/dist/types/web/search/providers/firecrawl.d.ts +1 -0
  118. package/dist/types/web/search/providers/gemini.d.ts +1 -0
  119. package/dist/types/web/search/providers/jina.d.ts +1 -0
  120. package/dist/types/web/search/providers/kagi.d.ts +1 -0
  121. package/dist/types/web/search/providers/kimi.d.ts +1 -0
  122. package/dist/types/web/search/providers/parallel.d.ts +1 -0
  123. package/dist/types/web/search/providers/perplexity.d.ts +1 -0
  124. package/dist/types/web/search/providers/searxng.d.ts +1 -0
  125. package/dist/types/web/search/providers/tavily.d.ts +1 -0
  126. package/dist/types/web/search/providers/tinyfish.d.ts +1 -0
  127. package/dist/types/web/search/providers/utils.d.ts +6 -9
  128. package/dist/types/web/search/providers/zai.d.ts +1 -0
  129. package/dist/types/web/search/types.d.ts +4 -0
  130. package/package.json +12 -12
  131. package/src/cli/args.ts +7 -87
  132. package/src/cli/browser-relay-cli.ts +119 -0
  133. package/src/cli/command-help.ts +103 -0
  134. package/src/cli/completion-gen.ts +5 -5
  135. package/src/cli/flag-tables.ts +9 -0
  136. package/src/cli/gc-cli.ts +641 -21
  137. package/src/cli/help-extra.ts +89 -0
  138. package/src/cli/setup-cli.ts +13 -21
  139. package/src/cli/thinking-levels.ts +7 -0
  140. package/src/cli/update-cli.ts +26 -7
  141. package/src/cli-commands.ts +170 -33
  142. package/src/cli.ts +17 -5
  143. package/src/commands/acp.ts +3 -1
  144. package/src/commands/agents.ts +3 -2
  145. package/src/commands/auth-broker.ts +3 -2
  146. package/src/commands/auth-gateway.ts +3 -2
  147. package/src/commands/bench.ts +2 -3
  148. package/src/commands/browser-relay.ts +53 -0
  149. package/src/commands/cleanse.ts +2 -2
  150. package/src/commands/commit.ts +3 -2
  151. package/src/commands/complete.ts +2 -1
  152. package/src/commands/completions.ts +3 -2
  153. package/src/commands/config.ts +3 -2
  154. package/src/commands/dry-balance.ts +2 -2
  155. package/src/commands/gallery.ts +3 -2
  156. package/src/commands/gc.ts +3 -2
  157. package/src/commands/grep.ts +3 -2
  158. package/src/commands/grievances.ts +3 -2
  159. package/src/commands/install.ts +2 -2
  160. package/src/commands/join.ts +3 -2
  161. package/src/commands/launch-help.ts +116 -0
  162. package/src/commands/launch.ts +7 -191
  163. package/src/commands/models.ts +3 -2
  164. package/src/commands/plugin.ts +3 -2
  165. package/src/commands/read.ts +3 -2
  166. package/src/commands/say.ts +3 -2
  167. package/src/commands/setup.ts +3 -2
  168. package/src/commands/shell.ts +3 -2
  169. package/src/commands/ssh.ts +3 -2
  170. package/src/commands/stats.ts +3 -2
  171. package/src/commands/tiny-models.ts +2 -2
  172. package/src/commands/token.ts +2 -2
  173. package/src/commands/ttsr.ts +2 -2
  174. package/src/commands/update.ts +3 -2
  175. package/src/commands/usage.ts +3 -2
  176. package/src/commands/web-search.ts +3 -2
  177. package/src/commands/worktree.ts +3 -2
  178. package/src/config/keybindings.ts +5 -0
  179. package/src/config/model-discovery.ts +44 -27
  180. package/src/config/model-registry.ts +19 -13
  181. package/src/config/models-config-schema-bundle.ts +9 -0
  182. package/src/config/resolve-config-value.ts +2 -1
  183. package/src/config/service-tier.ts +5 -0
  184. package/src/config/settings-schema.ts +72 -20
  185. package/src/config/settings.ts +1 -1
  186. package/src/cursor-bridge-tools.ts +1 -1
  187. package/src/cursor.ts +10 -14
  188. package/src/edit/diff.ts +17 -8
  189. package/src/edit/index.ts +20 -7
  190. package/src/edit/modes/replace.ts +93 -40
  191. package/src/edit/renderer.ts +1 -1
  192. package/src/edit/streaming.ts +14 -17
  193. package/src/eval/py/kernel.ts +2 -1
  194. package/src/eval/py/spawn-options.ts +24 -29
  195. package/src/export/html/index.ts +10 -3
  196. package/src/export/html/template.js +8 -26
  197. package/src/export/share.ts +4 -0
  198. package/src/extensibility/extensions/wrapper.ts +1 -0
  199. package/src/extensibility/legacy-pi-ai-shim.ts +22 -1
  200. package/src/extensibility/legacy-pi-coding-agent-shim.ts +7 -6
  201. package/src/extensibility/plugins/legacy-pi-compat.ts +124 -57
  202. package/src/extensibility/skills.ts +11 -0
  203. package/src/extensibility/utils.ts +10 -2
  204. package/src/launch/broker.ts +223 -7
  205. package/src/launch/client.ts +192 -21
  206. package/src/launch/ensure.ts +73 -0
  207. package/src/launch/paths.ts +1 -1
  208. package/src/launch/protocol.ts +54 -2
  209. package/src/lsp/client.ts +23 -2
  210. package/src/lsp/index.ts +8 -0
  211. package/src/lsp/mux/daemon.ts +348 -0
  212. package/src/lsp/mux/protocol.ts +96 -0
  213. package/src/lsp/mux/server.ts +797 -0
  214. package/src/lsp/types.ts +28 -2
  215. package/src/main.ts +20 -8
  216. package/src/mcp/config-writer.ts +1 -1
  217. package/src/mcp/oauth-flow.ts +11 -1
  218. package/src/mnemopi/embed-client.ts +49 -2
  219. package/src/mnemopi/state.ts +57 -23
  220. package/src/modes/components/assistant-message.ts +11 -0
  221. package/src/modes/components/btw-panel.ts +41 -4
  222. package/src/modes/components/chat-transcript-builder.ts +4 -0
  223. package/src/modes/components/custom-editor.ts +9 -0
  224. package/src/modes/components/read-tool-group.ts +10 -0
  225. package/src/modes/components/status-line/component.ts +1 -1
  226. package/src/modes/components/stripped-tool-calls-placeholder.ts +35 -0
  227. package/src/modes/components/tool-execution.ts +8 -0
  228. package/src/modes/components/tree-selector.ts +46 -48
  229. package/src/modes/controllers/btw-controller.ts +55 -7
  230. package/src/modes/controllers/command-controller.ts +31 -0
  231. package/src/modes/controllers/event-controller.ts +17 -0
  232. package/src/modes/controllers/input-controller.ts +63 -8
  233. package/src/modes/controllers/mcp-command-controller.ts +11 -5
  234. package/src/modes/controllers/selector-controller.ts +20 -0
  235. package/src/modes/interactive-mode.ts +18 -2
  236. package/src/modes/types.ts +9 -1
  237. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  238. package/src/modes/utils/interactive-context-helpers.ts +1 -0
  239. package/src/modes/utils/ui-helpers.ts +6 -10
  240. package/src/modes/workflow.ts +8 -2
  241. package/src/prompts/agents/init.md +1 -1
  242. package/src/prompts/session/launch-completion.md +1 -0
  243. package/src/prompts/system/plan-mode-active.md +2 -2
  244. package/src/prompts/system/system-prompt.md +26 -52
  245. package/src/prompts/system/workflow-notice.md +3 -3
  246. package/src/prompts/tools/ast-grep.md +1 -1
  247. package/src/prompts/tools/bash.md +1 -1
  248. package/src/prompts/tools/browser.md +1 -0
  249. package/src/prompts/tools/computer.md +21 -21
  250. package/src/prompts/tools/glob.md +1 -1
  251. package/src/prompts/tools/grep.md +1 -1
  252. package/src/prompts/tools/replace.md +5 -4
  253. package/src/prompts/tools/task.md +4 -4
  254. package/src/sdk.ts +49 -6
  255. package/src/secrets/index.ts +1 -1
  256. package/src/security/contracts/schemas.ts +205 -183
  257. package/src/security/contracts/validation.ts +5 -1
  258. package/src/security/store.ts +2 -2
  259. package/src/session/agent-session-types.ts +9 -1
  260. package/src/session/agent-session.ts +204 -15
  261. package/src/session/indexed-session-storage.ts +9 -0
  262. package/src/session/launch-completion.ts +37 -0
  263. package/src/session/prewalk.ts +24 -2
  264. package/src/session/session-advisors.ts +2 -2
  265. package/src/session/session-context.ts +42 -12
  266. package/src/session/session-dump-format.ts +1 -1
  267. package/src/session/session-entries.ts +17 -1
  268. package/src/session/session-maintenance.ts +38 -4
  269. package/src/session/session-manager.ts +123 -67
  270. package/src/session/session-paths.ts +118 -55
  271. package/src/session/session-stats.ts +48 -3
  272. package/src/session/session-storage.ts +30 -22
  273. package/src/session/stream-guards.ts +1 -1
  274. package/src/session/yield-queue.ts +121 -16
  275. package/src/slash-commands/builtin-registry.ts +37 -24
  276. package/src/system-prompt.ts +8 -3
  277. package/src/task/index.ts +23 -8
  278. package/src/task/spawn-policy.ts +14 -0
  279. package/src/thinking.ts +2 -8
  280. package/src/tools/ast-grep.ts +10 -4
  281. package/src/tools/browser/cmux/cmux-tab.ts +97 -14
  282. package/src/tools/browser/cmux/rpc.ts +2 -8
  283. package/src/tools/browser/registry.ts +53 -2
  284. package/src/tools/browser/relay/bridge.ts +945 -0
  285. package/src/tools/browser/relay/daemon.ts +117 -0
  286. package/src/tools/browser/relay/extension-assets/background.js.txt +242 -0
  287. package/src/tools/browser/relay/extension-assets/manifest.json.txt +14 -0
  288. package/src/tools/browser/relay/extension-assets/options.html.txt +53 -0
  289. package/src/tools/browser/relay/extension-assets/options.js.txt +23 -0
  290. package/src/tools/browser/relay/kind.ts +41 -0
  291. package/src/tools/browser/relay/protocol.ts +54 -0
  292. package/src/tools/browser/relay/server.ts +141 -0
  293. package/src/tools/browser/render.ts +4 -1
  294. package/src/tools/browser/shared-daemon.ts +7 -58
  295. package/src/tools/browser/tab-supervisor.ts +5 -4
  296. package/src/tools/browser/tab-worker.ts +33 -12
  297. package/src/tools/browser.ts +26 -1
  298. package/src/tools/computer/exposure.ts +11 -35
  299. package/src/tools/computer/protocol.ts +51 -10
  300. package/src/tools/computer/supervisor.ts +227 -124
  301. package/src/tools/computer/worker-entry.ts +11 -6
  302. package/src/tools/computer/worker.ts +695 -81
  303. package/src/tools/computer-renderer.ts +104 -65
  304. package/src/tools/computer.ts +134 -428
  305. package/src/tools/context.ts +3 -0
  306. package/src/tools/glob.ts +9 -2
  307. package/src/tools/grep.ts +12 -6
  308. package/src/tools/hub/launch.ts +122 -6
  309. package/src/tools/index.ts +9 -0
  310. package/src/tools/inspect-image.ts +32 -6
  311. package/src/tools/render-utils.ts +2 -0
  312. package/src/tools/{browser/run-cancellation.ts → run-scope.ts} +11 -11
  313. package/src/tools/tool-timeouts.ts +1 -0
  314. package/src/tools/xdev.ts +29 -2
  315. package/src/utils/clipboard.ts +40 -14
  316. package/src/utils/command-args.ts +26 -28
  317. package/src/utils/tool-choice.ts +0 -13
  318. package/src/web/kagi.ts +2 -1
  319. package/src/web/search/index.ts +18 -2
  320. package/src/web/search/providers/anthropic.ts +4 -1
  321. package/src/web/search/providers/base.ts +2 -0
  322. package/src/web/search/providers/brave.ts +3 -1
  323. package/src/web/search/providers/browser-page.ts +6 -4
  324. package/src/web/search/providers/codex.ts +4 -1
  325. package/src/web/search/providers/duckduckgo.ts +2 -1
  326. package/src/web/search/providers/ecosia.ts +2 -1
  327. package/src/web/search/providers/exa.ts +4 -2
  328. package/src/web/search/providers/firecrawl.ts +3 -1
  329. package/src/web/search/providers/gemini.ts +8 -2
  330. package/src/web/search/providers/google.ts +2 -1
  331. package/src/web/search/providers/jina.ts +12 -2
  332. package/src/web/search/providers/kagi.ts +3 -0
  333. package/src/web/search/providers/kimi.ts +5 -1
  334. package/src/web/search/providers/mojeek.ts +2 -1
  335. package/src/web/search/providers/parallel.ts +5 -1
  336. package/src/web/search/providers/perplexity.ts +6 -3
  337. package/src/web/search/providers/public.ts +1 -1
  338. package/src/web/search/providers/searxng.ts +11 -5
  339. package/src/web/search/providers/startpage.ts +10 -4
  340. package/src/web/search/providers/synthetic.ts +11 -5
  341. package/src/web/search/providers/tavily.ts +3 -1
  342. package/src/web/search/providers/tinyfish.ts +3 -1
  343. package/src/web/search/providers/utils.ts +12 -10
  344. package/src/web/search/providers/xai.ts +2 -2
  345. package/src/web/search/providers/zai.ts +18 -3
  346. package/src/web/search/types.ts +6 -0
  347. package/dist/types/config/file-lock.d.ts +0 -29
  348. package/src/config/file-lock.ts +0 -164
@@ -13,7 +13,13 @@ import {
13
13
  } from "@oh-my-pi/pi-tui";
14
14
  import type { TreeFilterMode } from "../../config/settings-schema";
15
15
  import { theme } from "../../modes/theme/theme";
16
- import { matchesAppInterrupt, matchesSelectDown, matchesSelectUp } from "../../modes/utils/keybinding-matchers";
16
+ import {
17
+ matchesAppInterrupt,
18
+ matchesSelectDown,
19
+ matchesSelectPageDown,
20
+ matchesSelectPageUp,
21
+ matchesSelectUp,
22
+ } from "../../modes/utils/keybinding-matchers";
17
23
  import type { SessionTreeNode } from "../../session/session-entries";
18
24
  import { toPathList } from "../../tools/path-utils";
19
25
  import { shortenPath } from "../../tools/render-utils";
@@ -144,13 +150,12 @@ class TreeList implements Component {
144
150
  const result: FlatNode[] = [];
145
151
  this.#toolCallMap.clear();
146
152
 
147
- // Indentation rules:
148
- // - At indent 0: stay at 0 unless parent has >1 children (then +1)
149
- // - At indent 1: children always go to indent 2 (visual grouping of subtree)
150
- // - At indent 2+: stay flat for single-child chains, +1 only if parent branches
153
+ // A real branch point adds one indentation level. Linear conversation
154
+ // chains retain that level so their text stays aligned with the branch
155
+ // head instead of drifting right after every fork.
151
156
 
152
- // Stack items: [node, indent, justBranched, showConnector, isLast, gutters, isVirtualRootChild]
153
- type StackItem = [SessionTreeNode, number, boolean, boolean, boolean, GutterInfo[], boolean];
157
+ // Stack items: [node, indent, showConnector, isLast, gutters, isVirtualRootChild]
158
+ type StackItem = [SessionTreeNode, number, boolean, boolean, GutterInfo[], boolean];
154
159
  const stack: StackItem[] = [];
155
160
 
156
161
  // Determine which subtrees contain the active leaf (to sort current branch first)
@@ -188,11 +193,11 @@ class TreeList implements Component {
188
193
  const orderedRoots = [...roots].sort((a, b) => Number(containsActive.get(b)) - Number(containsActive.get(a)));
189
194
  for (let i = orderedRoots.length - 1; i >= 0; i--) {
190
195
  const isLast = i === orderedRoots.length - 1;
191
- stack.push([orderedRoots[i], multipleRoots ? 1 : 0, multipleRoots, multipleRoots, isLast, [], multipleRoots]);
196
+ stack.push([orderedRoots[i], multipleRoots ? 1 : 0, multipleRoots, isLast, [], multipleRoots]);
192
197
  }
193
198
 
194
199
  while (stack.length > 0) {
195
- const [node, indent, justBranched, showConnector, isLast, gutters, isVirtualRootChild] = stack.pop()!;
200
+ const [node, indent, showConnector, isLast, gutters, isVirtualRootChild] = stack.pop()!;
196
201
 
197
202
  // Extract tool calls from assistant messages for later lookup
198
203
  const entry = node.entry;
@@ -227,18 +232,10 @@ class TreeList implements Component {
227
232
  return [...prioritized, ...rest];
228
233
  })();
229
234
 
230
- // Calculate child indent
231
- let childIndent: number;
232
- if (multipleChildren) {
233
- // Parent branches: children get +1
234
- childIndent = indent + 1;
235
- } else if (justBranched && indent > 0) {
236
- // First generation after a branch: +1 for visual grouping
237
- childIndent = indent + 1;
238
- } else {
239
- // Single-child chain: stay flat
240
- childIndent = indent;
241
- }
235
+ // Real branch points add visual depth, and a virtual root's direct
236
+ // children (the session roots) nest one level under the shared column-0
237
+ // root. Linear continuations otherwise stay aligned with their head.
238
+ const childIndent = multipleChildren || isVirtualRootChild ? indent + 1 : indent;
242
239
 
243
240
  // Build gutters for children
244
241
  // If this node showed a connector, add a gutter entry for descendants
@@ -255,15 +252,7 @@ class TreeList implements Component {
255
252
  // Add children in reverse order
256
253
  for (let i = orderedChildren.length - 1; i >= 0; i--) {
257
254
  const childIsLast = i === orderedChildren.length - 1;
258
- stack.push([
259
- orderedChildren[i],
260
- childIndent,
261
- multipleChildren,
262
- multipleChildren,
263
- childIsLast,
264
- childGutters,
265
- false,
266
- ]);
255
+ stack.push([orderedChildren[i], childIndent, multipleChildren, childIsLast, childGutters, false]);
267
256
  }
268
257
  }
269
258
 
@@ -516,16 +505,8 @@ class TreeList implements Component {
516
505
  const renderedIndent = Math.min(displayIndent, maxIndentLevels);
517
506
  const scrollOffset = displayIndent - renderedIndent;
518
507
  const connectorPositionDisplay = hasConnector ? renderedIndent - 1 : -1;
519
- // Chain rows (no connector of their own) under a last-sibling (`└─`)
520
- // branch stay anchored by a vertical drawn one level RIGHT of the
521
- // suppressed gutter — the column where the row's own connector would
522
- // sit, directly below the branch head's content. Drawing it in the
523
- // `└─` column itself contradicts the corner and leaves dangling,
524
- // drifting verticals once the chain branches deeper (#2298, #2325).
525
- // Chains under `├─` heads need no extra anchor: the sibling line
526
- // (`show: true` gutter) already ties them to their branch.
527
- const nearestGutter = !hasConnector ? flatNode.gutters[flatNode.gutters.length - 1] : undefined;
528
- const chainAnchorLevel = nearestGutter && !nearestGutter.show ? nearestGutter.position + 1 : -1;
508
+ // Linear rows reuse their branch head's depth. Existing sibling
509
+ // gutters remain visible; terminal gutters remain terminated.
529
510
 
530
511
  // Build prefix char by char, placing gutters and connector at their positions
531
512
  const totalChars = renderedIndent * 3;
@@ -545,9 +526,6 @@ class TreeList implements Component {
545
526
  } else {
546
527
  prefixChars.push(" ");
547
528
  }
548
- } else if (originalLevel === chainAnchorLevel) {
549
- // Chain anchor for rows under a `└─` branch head.
550
- prefixChars.push(posInLevel === 0 ? theme.tree.vertical : " ");
551
529
  } else if (hasConnector && level === connectorPositionDisplay) {
552
530
  // Connector at this level
553
531
  if (posInLevel === 0) {
@@ -781,16 +759,36 @@ class TreeList implements Component {
781
759
  }
782
760
  }
783
761
 
762
+ #moveToAdjacentTurn(direction: -1 | 1): void {
763
+ for (
764
+ let index = this.#selectedIndex + direction;
765
+ index >= 0 && index < this.#filteredNodes.length;
766
+ index += direction
767
+ ) {
768
+ const entry = this.#filteredNodes[index]?.node.entry;
769
+ if (entry?.type === "message" && (entry.message.role === "user" || entry.message.role === "assistant")) {
770
+ this.#selectedIndex = index;
771
+ return;
772
+ }
773
+ }
774
+ }
775
+
784
776
  handleInput(keyData: string): void {
785
777
  if (matchesSelectUp(keyData)) {
786
778
  this.#selectedIndex = this.#selectedIndex === 0 ? this.#filteredNodes.length - 1 : this.#selectedIndex - 1;
787
779
  } else if (matchesSelectDown(keyData)) {
788
780
  this.#selectedIndex = this.#selectedIndex === this.#filteredNodes.length - 1 ? 0 : this.#selectedIndex + 1;
789
- } else if (matchesKey(keyData, "left")) {
790
- // Page up
781
+ } else if (matchesKey(keyData, "alt+up")) {
782
+ this.#moveToAdjacentTurn(-1);
783
+ } else if (matchesKey(keyData, "alt+down")) {
784
+ this.#moveToAdjacentTurn(1);
785
+ } else if (matchesKey(keyData, "home")) {
786
+ this.#selectedIndex = 0;
787
+ } else if (matchesKey(keyData, "end")) {
788
+ this.#selectedIndex = Math.max(0, this.#filteredNodes.length - 1);
789
+ } else if (matchesSelectPageUp(keyData) || matchesKey(keyData, "left")) {
791
790
  this.#selectedIndex = Math.max(0, this.#selectedIndex - this.maxVisibleLines);
792
- } else if (matchesKey(keyData, "right")) {
793
- // Page down
791
+ } else if (matchesSelectPageDown(keyData) || matchesKey(keyData, "right")) {
794
792
  this.#selectedIndex = Math.min(this.#filteredNodes.length - 1, this.#selectedIndex + this.maxVisibleLines);
795
793
  } else if (matchesKey(keyData, "shift+enter") || matchesKey(keyData, "shift+return")) {
796
794
  // Summarize-and-switch: fork with a branch summary without the extra prompt.
@@ -954,7 +952,7 @@ export class TreeSelectorComponent extends Container {
954
952
  new TruncatedText(
955
953
  theme.fg(
956
954
  "muted",
957
- "Enter: switch. Shift+Enter: summarize & switch. Shift+L: label. Ctrl+O: filter. Alt+D/T/U/L/A: filter. Type to search",
955
+ "Enter: switch. Alt+↑/↓: previous/next turn. PgUp/PgDn (←/→): page. Home/End: first/last item. Shift+Enter: summarize & switch. Shift+L: label. Ctrl+O: filter. Alt+D/T/U/L/A: filter. Type to search",
958
956
  ),
959
957
  0,
960
958
  0,
@@ -10,6 +10,7 @@ interface BtwRequest {
10
10
  abortController: AbortController;
11
11
  question: string;
12
12
  leafId: string | null;
13
+ sessionId: string;
13
14
  }
14
15
 
15
16
  function assistantMessageWithReplyText(assistantMessage: AssistantMessage, replyText: string): AssistantMessage {
@@ -39,6 +40,7 @@ export class BtwController {
39
40
  #lastReplyText: string | undefined;
40
41
  #lastAssistantMessage: AssistantMessage | undefined;
41
42
  #lastLeafId: string | null | undefined;
43
+ #lastSessionId: string | undefined;
42
44
  #branchInFlight = false;
43
45
  #lastCopyText: string | undefined;
44
46
  #copyInFlight = false;
@@ -50,17 +52,39 @@ export class BtwController {
50
52
  }
51
53
 
52
54
  canBranch(): boolean {
55
+ return this.#branchUnavailableReason() === undefined;
56
+ }
57
+
58
+ /** Whether plain `b` is currently reserved for a completed or pending branch action. */
59
+ handlesBranchKey(): boolean {
60
+ if (this.#branchInFlight) return true;
61
+ if (this.#activeRequest?.component.isBranchable() !== true) return false;
53
62
  return (
54
- !this.#branchInFlight &&
55
- this.#activeRequest?.component.isBranchable() === true &&
56
63
  this.#lastQuestion !== undefined &&
57
64
  this.#lastReplyText !== undefined &&
58
65
  this.#lastAssistantMessage !== undefined &&
59
- this.#lastLeafId !== null &&
60
- this.#lastLeafId === this.ctx.sessionManager.getLeafId()
66
+ this.#lastLeafId !== undefined &&
67
+ this.#lastSessionId !== undefined
61
68
  );
62
69
  }
63
70
 
71
+ #branchUnavailableReason(): string | undefined {
72
+ if (this.#branchInFlight) return "a branch is already in progress";
73
+ if (this.#activeRequest?.component.isBranchable() !== true) return "the answer is not ready";
74
+ if (!this.#lastQuestion || !this.#lastReplyText || !this.#lastAssistantMessage) {
75
+ return "the answer is unavailable";
76
+ }
77
+ if (!this.#lastLeafId) return "the session has no branch point";
78
+ if (
79
+ this.#lastSessionId !== this.ctx.sessionManager.getSessionId() ||
80
+ this.#lastLeafId !== this.ctx.sessionManager.getLeafId()
81
+ ) {
82
+ return "the session changed since /btw started";
83
+ }
84
+ if (this.ctx.session.isStreaming) return "a turn is still running";
85
+ return undefined;
86
+ }
87
+
64
88
  canCopy(): boolean {
65
89
  return (
66
90
  !this.#copyInFlight && this.#activeRequest?.component.isCopyable() === true && this.#lastCopyText !== undefined
@@ -83,17 +107,34 @@ export class BtwController {
83
107
  }
84
108
 
85
109
  async handleBranch(): Promise<boolean> {
86
- if (!this.canBranch() || !this.#lastQuestion || !this.#lastAssistantMessage) return false;
110
+ const unavailableReason = this.#branchUnavailableReason();
111
+ if (unavailableReason) {
112
+ this.ctx.showStatus(`/btw branch unavailable: ${unavailableReason}`, { dim: true });
113
+ return false;
114
+ }
115
+ const request = this.#activeRequest;
116
+ const question = this.#lastQuestion;
117
+ const assistantMessage = this.#lastAssistantMessage;
118
+ const leafId = this.#lastLeafId;
119
+ const sessionId = this.#lastSessionId;
120
+ if (!request || !question || !assistantMessage || !leafId || !sessionId) return false;
121
+
87
122
  this.#branchInFlight = true;
123
+ request.component.markBranching();
88
124
  try {
89
- await this.ctx.handleBtwBranch(this.#lastQuestion, this.#lastAssistantMessage);
125
+ await this.ctx.handleBtwBranch(question, assistantMessage, leafId, sessionId);
90
126
  return true;
91
127
  } finally {
92
128
  this.#branchInFlight = false;
129
+ if (this.#activeRequest === request) request.component.markComplete();
93
130
  }
94
131
  }
95
132
 
96
133
  handleEscape(): boolean {
134
+ if (this.#branchInFlight) {
135
+ this.ctx.showStatus("/btw branch is in progress", { dim: true });
136
+ return true;
137
+ }
97
138
  if (!this.#activeRequest) return false;
98
139
  this.#closeActiveRequest({ abort: this.#activeRequest.abortController.signal.aborted === false });
99
140
  return true;
@@ -119,10 +160,15 @@ export class BtwController {
119
160
  this.#closeActiveRequest({ abort: true });
120
161
 
121
162
  const request: BtwRequest = {
122
- component: new BtwPanelComponent({ question: trimmedQuestion, tui: this.ctx.ui }),
163
+ component: new BtwPanelComponent({
164
+ question: trimmedQuestion,
165
+ tui: this.ctx.ui,
166
+ canBranch: () => this.canBranch(),
167
+ }),
123
168
  abortController: new AbortController(),
124
169
  question: trimmedQuestion,
125
170
  leafId: this.ctx.sessionManager.getLeafId(),
171
+ sessionId: this.ctx.sessionManager.getSessionId(),
126
172
  };
127
173
  this.ctx.btwContainer.clear();
128
174
  this.ctx.btwContainer.addChild(request.component);
@@ -156,6 +202,7 @@ export class BtwController {
156
202
  this.#lastCopyText = copyText;
157
203
  this.#lastAssistantMessage = assistantMessageWithReplyText(assistantMessage, replyText);
158
204
  this.#lastLeafId = request.leafId;
205
+ this.#lastSessionId = request.sessionId;
159
206
  } else {
160
207
  this.#clearCompletedState();
161
208
  }
@@ -190,6 +237,7 @@ export class BtwController {
190
237
  this.#lastAssistantMessage = undefined;
191
238
  this.#lastCopyText = undefined;
192
239
  this.#lastLeafId = undefined;
240
+ this.#lastSessionId = undefined;
193
241
  }
194
242
 
195
243
  #isActiveRequest(request: BtwRequest): boolean {
@@ -961,6 +961,37 @@ export class CommandController {
961
961
  this.ctx.showStatus(`Fresh provider session started (${result.closedProviderSessions} ${stateLabel} pruned).`);
962
962
  }
963
963
 
964
+ async handleResetContextCommand(): Promise<void> {
965
+ if (this.ctx.session.isCompacting) {
966
+ this.ctx.session.abortCompaction();
967
+ while (this.ctx.session.isCompacting) {
968
+ await Bun.sleep(10);
969
+ }
970
+ }
971
+ const result = await this.ctx.session.resetSessionContext();
972
+ if (!result) {
973
+ this.ctx.showWarning("Wait for the current response to finish or abort it before resetting the context.");
974
+ return;
975
+ }
976
+ // Drop the rendered transcript so the UI matches the now-empty model
977
+ // context (mirrors #runNewSessionFlow's teardown, minus the new session —
978
+ // the session id, title, and transcript file all survive).
979
+ this.ctx.clearTransientSessionUi();
980
+ this.ctx.resetTranscript();
981
+ this.ctx.statusLine.invalidate();
982
+ this.ctx.updateEditorBorderColor();
983
+ const noun = result.droppedCount === 1 ? "message" : "messages";
984
+ this.ctx.present([
985
+ new Spacer(1),
986
+ new Text(
987
+ `${theme.fg("accent", `${theme.status.success} Context reset — ${result.droppedCount} ${noun} dropped; session continues.`)}`,
988
+ 1,
989
+ 1,
990
+ ),
991
+ ]);
992
+ this.ctx.ui.requestRender(true, { clearScrollback: true });
993
+ }
994
+
964
995
  async handleDropCommand(): Promise<void> {
965
996
  if (!this.ctx.sessionManager.getSessionFile()) {
966
997
  this.ctx.showError("Nothing to drop (in-memory session)");
@@ -276,6 +276,7 @@ export class EventController {
276
276
  showContentPreview: this.ctx.settings.get("read.toolResultPreview"),
277
277
  });
278
278
  group.setExpanded(this.ctx.toolOutputExpanded);
279
+ group.setToolActivityVisible(!this.ctx.hideToolActivity);
279
280
  this.ctx.chatContainer.addChild(group);
280
281
  this.#lastReadGroup = group;
281
282
  }
@@ -927,6 +928,7 @@ export class EventController {
927
928
  content.id,
928
929
  );
929
930
  component.setExpanded(this.ctx.toolOutputExpanded);
931
+ component.setToolActivityVisible(!this.ctx.hideToolActivity);
930
932
  this.ctx.chatContainer.addChild(component);
931
933
  this.ctx.pendingTools.set(content.id, component);
932
934
  this.#toolTimelineComponents.set(content.id, component);
@@ -1176,6 +1178,7 @@ export class EventController {
1176
1178
  event.toolCallId,
1177
1179
  );
1178
1180
  component.setExpanded(this.ctx.toolOutputExpanded);
1181
+ component.setToolActivityVisible(!this.ctx.hideToolActivity);
1179
1182
  this.ctx.chatContainer.addChild(component);
1180
1183
  this.ctx.pendingTools.set(event.toolCallId, component);
1181
1184
  this.#toolTimelineComponents.set(event.toolCallId, component);
@@ -1464,6 +1467,20 @@ export class EventController {
1464
1467
  // the loader and finalizes it at its own agent_end (isStreaming === false by
1465
1468
  // then). Mirrors the collab guest's !isStreaming loader reconciler.
1466
1469
  if (this.ctx.session.isStreaming) return;
1470
+ // A non-terminal settle (`isTerminal: false`) is a scheduling pause, not the
1471
+ // end of the run: an unsuppressed async job (a `/vibe` worker turn, a bash
1472
+ // `async` job, etc.) will re-wake the loop when its result is delivered.
1473
+ // `AgentSession` tags this on the deferred event (see `#hasPendingAsyncWake`
1474
+ // in agent-session.ts). Skip the idle title/loader teardown so the tab keeps
1475
+ // reading "working"; the later terminal `agent_end` performs it. Still flush
1476
+ // a deferred model switch — the plan-mode reconciler queues it to apply once
1477
+ // the current stream ends, and `#finishAgentEnd` is otherwise its only flush
1478
+ // site, so the automatic continuation would otherwise run on the old
1479
+ // model/thinking level until the terminal settle.
1480
+ if (event.isTerminal === false) {
1481
+ await this.ctx.flushPendingModelSwitch();
1482
+ return;
1483
+ }
1467
1484
  setTerminalTitleState("idle");
1468
1485
 
1469
1486
  await this.#finishAgentEnd(event);
@@ -8,8 +8,11 @@ import { isSettingsInitialized, settings } from "../../config/settings";
8
8
  import { resolveLocalRoot } from "../../internal-urls";
9
9
  import { AssistantMessageComponent } from "../../modes/components/assistant-message";
10
10
  import { extractImagePathFromText } from "../../modes/components/custom-editor";
11
+ import { ReadToolGroupComponent } from "../../modes/components/read-tool-group";
11
12
  import { renderSegmentTrack } from "../../modes/components/segment-track";
13
+ import { StrippedToolCallsPlaceholder } from "../../modes/components/stripped-tool-calls-placeholder";
12
14
  import { TinyTitleDownloadProgressComponent } from "../../modes/components/tiny-title-download-progress";
15
+ import { ToolExecutionComponent } from "../../modes/components/tool-execution";
13
16
  import { expandEmoticons } from "../../modes/emoji-autocomplete";
14
17
  import { materializeImageReferenceLinks, shiftImageMarkers } from "../../modes/image-references";
15
18
  import { createPromptActionAutocompleteProvider } from "../../modes/prompt-action-autocomplete";
@@ -247,7 +250,7 @@ export class InputController {
247
250
  this.#btwBranchListenerInstalled = true;
248
251
  this.ctx.ui.addInputListener(data => {
249
252
  if (!matchesKey(data, "b")) return undefined;
250
- if (!this.ctx.canBranchBtw()) return undefined;
253
+ if (!this.ctx.handlesBtwBranchKey()) return undefined;
251
254
  if (this.ctx.ui.getFocused() !== this.ctx.editor) return undefined;
252
255
  if (this.ctx.editor.getText().trim()) return undefined;
253
256
  void this.ctx.handleBtwBranchKey();
@@ -327,10 +330,10 @@ export class InputController {
327
330
  }
328
331
 
329
332
  if (this.ctx.loopModeEnabled) {
330
- this.ctx.pauseLoop();
331
333
  if (this.ctx.session.isStreaming) {
332
334
  this.#abortStreamingTurn();
333
335
  } else {
336
+ this.ctx.pauseLoop();
334
337
  this.ctx.cancelPendingSubmission();
335
338
  }
336
339
  return;
@@ -455,6 +458,11 @@ export class InputController {
455
458
  this.ctx.editor.onCopyPrompt = () => this.handleCopyPrompt();
456
459
  this.ctx.editor.setActionKeys("app.tools.expand", this.ctx.keybindings.getKeys("app.tools.expand"));
457
460
  this.ctx.editor.onExpandTools = () => this.toggleToolOutputExpansion();
461
+ this.ctx.editor.setActionKeys(
462
+ "app.tools.toggleVisibility",
463
+ this.ctx.keybindings.getKeys("app.tools.toggleVisibility"),
464
+ );
465
+ this.ctx.editor.onToggleToolActivity = () => this.toggleToolActivityVisibility();
458
466
  this.ctx.editor.setActionKeys("app.message.dequeue", this.ctx.keybindings.getKeys("app.message.dequeue"));
459
467
  this.ctx.editor.onDequeue = () => this.handleDequeue();
460
468
  this.ctx.editor.setActionKeys("app.retry", this.ctx.keybindings.getKeys("app.retry"));
@@ -793,6 +801,19 @@ export class InputController {
793
801
  this.ctx.queueCompactionMessage(text, "steer", images);
794
802
  return;
795
803
  }
804
+ // Extension commands are local actions. Execute them before the normal
805
+ // submission path creates an optimistic user message; otherwise a
806
+ // consumed command remains rendered like a prompt sent to the model.
807
+ if (this.#isLocalExtensionCommand(text)) {
808
+ this.ctx.editor.clearDraft(text);
809
+ try {
810
+ await this.ctx.session.prompt(text, { images: inputImages });
811
+ } catch (error) {
812
+ this.ctx.editor.setText(text);
813
+ this.ctx.showError(error instanceof Error ? error.message : String(error));
814
+ }
815
+ return;
816
+ }
796
817
 
797
818
  // If streaming, use prompt() with steer behavior
798
819
  // This handles extension commands (execute immediately), prompt template expansion, and queueing
@@ -906,14 +927,18 @@ export class InputController {
906
927
  * Local extension commands are consumed before reaching the shared session
907
928
  * title gate and must not name the conversation.
908
929
  */
909
- #maybeStartTitleGeneration(text: string): void {
910
- const runner = this.ctx.session.extensionRunner;
930
+ #isLocalExtensionCommand(text: string): boolean {
911
931
  const extensionCommandSpace = text.indexOf(" ");
912
- const isLocalExtensionCommand =
932
+ return (
913
933
  text.startsWith("/") &&
914
- runner?.getCommand(extensionCommandSpace === -1 ? text.slice(1) : text.slice(1, extensionCommandSpace)) !==
915
- undefined;
916
- if (isLocalExtensionCommand) {
934
+ this.ctx.session.extensionRunner?.getCommand(
935
+ extensionCommandSpace === -1 ? text.slice(1) : text.slice(1, extensionCommandSpace),
936
+ ) !== undefined
937
+ );
938
+ }
939
+
940
+ #maybeStartTitleGeneration(text: string): void {
941
+ if (this.#isLocalExtensionCommand(text)) {
917
942
  return;
918
943
  }
919
944
  this.ctx.session.maybeStartTitleGeneration(text, () => {
@@ -1845,9 +1870,39 @@ export class InputController {
1845
1870
  }
1846
1871
 
1847
1872
  toggleToolOutputExpansion(): void {
1873
+ if (this.ctx.hideToolActivity) {
1874
+ const visibilityKey = this.ctx.keybindings.getDisplayString("app.tools.toggleVisibility");
1875
+ const visibilityHint = visibilityKey ? `${visibilityKey} or /settings` : "/settings";
1876
+ this.ctx.showStatus(`Tool activity is hidden — show it with ${visibilityHint} before expanding`);
1877
+ return;
1878
+ }
1848
1879
  this.setToolsExpanded(!this.ctx.toolOutputExpanded);
1849
1880
  }
1850
1881
 
1882
+ toggleToolActivityVisibility(): void {
1883
+ this.ctx.hideToolActivity = !this.ctx.hideToolActivity;
1884
+ this.ctx.settings.set("display.hideToolActivity", this.ctx.hideToolActivity);
1885
+
1886
+ if (!this.ctx.hideToolActivity) {
1887
+ this.ctx.toolOutputExpanded = false;
1888
+ }
1889
+
1890
+ for (const child of this.ctx.chatContainer.children) {
1891
+ if (child instanceof ToolExecutionComponent || child instanceof ReadToolGroupComponent) {
1892
+ if (!this.ctx.hideToolActivity) child.setExpanded(false);
1893
+ child.setToolActivityVisible(!this.ctx.hideToolActivity);
1894
+ } else if (child instanceof AssistantMessageComponent) {
1895
+ child.setToolResultImagesVisible(!this.ctx.hideToolActivity);
1896
+ } else if (child instanceof StrippedToolCallsPlaceholder) {
1897
+ child.setToolActivityVisible(!this.ctx.hideToolActivity);
1898
+ }
1899
+ }
1900
+
1901
+ if (this.ctx.hideToolActivity) this.ctx.ui.clearInlineImages();
1902
+ this.ctx.ui.resetDisplay();
1903
+ this.ctx.showStatus(`Tool activity: ${this.ctx.hideToolActivity ? "hidden" : "visible"}`);
1904
+ }
1905
+
1851
1906
  setToolsExpanded(expanded: boolean): void {
1852
1907
  this.ctx.toolOutputExpanded = expanded;
1853
1908
  for (const child of this.ctx.chatContainer.children) {
@@ -1793,16 +1793,22 @@ export class MCPCommandController {
1793
1793
  const oauth = await this.#resolveOAuthEndpointsFromServer(runtimeBaseConfig, options.authChallenge);
1794
1794
  const serverUrl =
1795
1795
  runtimeBaseConfig.type === "http" || runtimeBaseConfig.type === "sse" ? runtimeBaseConfig.url : undefined;
1796
- // A user-supplied client secret may live in either block (the wizard
1797
- // writes it to auth.clientSecret); DCR secrets are embedded in the
1798
- // stored credential and never echoed back into config files.
1799
- const configuredClientId = found.config.oauth?.clientId ?? currentAuth?.clientId;
1796
+ // Client credentials drive the token exchange, so they must come from the
1797
+ // env-expanded runtime config; `found.config`/`currentAuth` may still hold
1798
+ // `${...}` placeholders (the wizard writes the secret to auth.clientSecret).
1799
+ // DCR secrets are embedded in the stored credential and never echoed back
1800
+ // into config files.
1801
+ const runtimeAuth = currentAuth ? expandEnvVarsDeep(currentAuth) : undefined;
1802
+ const configuredClientId = runtimeBaseConfig.oauth?.clientId ?? runtimeAuth?.clientId;
1800
1803
  const existingCredential = lookupMcpOAuthCredentialForServer(authStorage, currentAuth, serverUrl)?.credential;
1801
1804
  const flowClientId = oauth.clientId ?? configuredClientId ?? existingCredential?.clientId ?? "";
1802
1805
  const storedClientSecret =
1803
1806
  existingCredential?.clientId === flowClientId ? existingCredential.clientSecret : undefined;
1807
+ const flowClientSecret =
1808
+ runtimeBaseConfig.oauth?.clientSecret ?? runtimeAuth?.clientSecret ?? storedClientSecret ?? "";
1809
+ // Persisted separately below: keep the raw `${...}` placeholder in the file
1810
+ // rather than writing the resolved secret back to (possibly shared) config.
1804
1811
  const userClientSecret = found.config.oauth?.clientSecret ?? currentAuth?.clientSecret;
1805
- const flowClientSecret = userClientSecret ?? storedClientSecret ?? "";
1806
1812
 
1807
1813
  if (!options.silent) {
1808
1814
  this.#showMessage(["", theme.fg("muted", `Reauthorizing "${name}"...`), ""].join("\n"));
@@ -92,11 +92,13 @@ import { ModelHubComponent, type ModelRoleSelectionScope } from "../components/m
92
92
  import { ModelPickerComponent } from "../components/model-picker";
93
93
  import { OAuthSelectorComponent } from "../components/oauth-selector";
94
94
  import { PluginSelectorComponent } from "../components/plugin-selector";
95
+ import { ReadToolGroupComponent } from "../components/read-tool-group";
95
96
  import { ResetUsageSelectorComponent } from "../components/reset-usage-selector";
96
97
  import { renderSegmentTrack } from "../components/segment-track";
97
98
  import { SessionAccountSelectorComponent } from "../components/session-account-selector";
98
99
  import { SessionSelectorComponent, type SessionSelectorOptions } from "../components/session-selector";
99
100
  import { SettingsSelectorComponent } from "../components/settings-selector";
101
+ import { StrippedToolCallsPlaceholder } from "../components/stripped-tool-calls-placeholder";
100
102
  import { ToolExecutionComponent } from "../components/tool-execution";
101
103
  import { TranscriptBlock } from "../components/transcript-container";
102
104
  import { TreeSelectorComponent } from "../components/tree-selector";
@@ -474,6 +476,24 @@ export class SelectorController {
474
476
  break;
475
477
 
476
478
  // Settings with UI side effects
479
+ case "display.hideToolActivity": {
480
+ const hidden = value as boolean;
481
+ this.ctx.hideToolActivity = hidden;
482
+ if (!hidden) this.ctx.toolOutputExpanded = false;
483
+ for (const child of this.ctx.chatContainer.children) {
484
+ if (child instanceof ToolExecutionComponent || child instanceof ReadToolGroupComponent) {
485
+ if (!hidden) child.setExpanded(false);
486
+ child.setToolActivityVisible(!hidden);
487
+ } else if (child instanceof AssistantMessageComponent) {
488
+ child.setToolResultImagesVisible(!hidden);
489
+ } else if (child instanceof StrippedToolCallsPlaceholder) {
490
+ child.setToolActivityVisible(!hidden);
491
+ }
492
+ }
493
+ if (hidden) this.ctx.ui.clearInlineImages();
494
+ this.ctx.ui.resetDisplay();
495
+ break;
496
+ }
477
497
  case "terminal.showImages":
478
498
  case "showImages": {
479
499
  const visible = value as boolean;
@@ -460,6 +460,7 @@ export class InteractiveMode implements InteractiveModeContext {
460
460
  initialChatRendered = false;
461
461
  isBashMode = false;
462
462
  toolOutputExpanded = false;
463
+ hideToolActivity = false;
463
464
  todoExpanded = false;
464
465
  planModeEnabled = false;
465
466
  planModePaused = false;
@@ -778,6 +779,7 @@ export class InteractiveMode implements InteractiveModeContext {
778
779
  // hot path where the render never gets to paint the result.
779
780
  this.editor.setTopBorderProvider(availableWidth => this.statusLine.getTopBorder(availableWidth));
780
781
 
782
+ this.hideToolActivity = settings.get("display.hideToolActivity");
781
783
  this.hideThinkingBlock = settings.get("hideThinkingBlock");
782
784
  this.proseOnlyThinking = settings.get("proseOnlyThinking");
783
785
 
@@ -4511,6 +4513,10 @@ export class InteractiveMode implements InteractiveModeContext {
4511
4513
  return this.#commandController.handleFreshCommand();
4512
4514
  }
4513
4515
 
4516
+ handleResetContextCommand(): Promise<void> {
4517
+ return this.#commandController.handleResetContextCommand();
4518
+ }
4519
+
4514
4520
  async handleDropCommand(): Promise<void> {
4515
4521
  if (this.#vibeSessionTransitionBlocked()) return;
4516
4522
  this.#prepareSessionSwitch();
@@ -4842,6 +4848,11 @@ export class InteractiveMode implements InteractiveModeContext {
4842
4848
  return this.#btwController.canBranch();
4843
4849
  }
4844
4850
 
4851
+ /** Reserves plain `b` only after /btw has a completed branch action to handle. */
4852
+ handlesBtwBranchKey(): boolean {
4853
+ return this.#btwController.handlesBranchKey();
4854
+ }
4855
+
4845
4856
  handleBtwBranchKey(): Promise<boolean> {
4846
4857
  return this.#btwController.handleBranch();
4847
4858
  }
@@ -4854,9 +4865,14 @@ export class InteractiveMode implements InteractiveModeContext {
4854
4865
  return this.#btwController.handleCopy();
4855
4866
  }
4856
4867
 
4857
- async handleBtwBranch(question: string, assistantMessage: AssistantMessage): Promise<void> {
4868
+ async handleBtwBranch(
4869
+ question: string,
4870
+ assistantMessage: AssistantMessage,
4871
+ leafId: string,
4872
+ sessionId: string,
4873
+ ): Promise<void> {
4858
4874
  try {
4859
- const result = await this.session.branchFromBtw(question, assistantMessage);
4875
+ const result = await this.session.branchFromBtw(question, assistantMessage, leafId, sessionId);
4860
4876
  if (result.cancelled) {
4861
4877
  this.showStatus("/btw branch cancelled", { dim: true });
4862
4878
  return;
@@ -162,6 +162,7 @@ export interface InteractiveModeContext {
162
162
  initialChatRendered: boolean;
163
163
  isBashMode: boolean;
164
164
  toolOutputExpanded: boolean;
165
+ hideToolActivity: boolean;
165
166
  todoExpanded: boolean;
166
167
  planModeEnabled: boolean;
167
168
  vibeModeEnabled: boolean;
@@ -349,6 +350,7 @@ export interface InteractiveModeContext {
349
350
  handleDebugTranscriptCommand(): Promise<void>;
350
351
  handleClearCommand(): Promise<void>;
351
352
  handleFreshCommand(): Promise<void>;
353
+ handleResetContextCommand(): Promise<void>;
352
354
  handleDropCommand(): Promise<void>;
353
355
  handleForkCommand(): Promise<void>;
354
356
  handleBashCommand(command: string, excludeFromContext?: boolean): Promise<void>;
@@ -417,9 +419,15 @@ export interface InteractiveModeContext {
417
419
  handleBtwEscape(): boolean;
418
420
  handleBtwBranchKey(): Promise<boolean>;
419
421
  canBranchBtw(): boolean;
422
+ handlesBtwBranchKey(): boolean;
420
423
  canCopyBtw(): boolean;
421
424
  handleBtwCopyKey(): Promise<boolean>;
422
- handleBtwBranch(question: string, assistantMessage: AssistantMessage): Promise<void>;
425
+ handleBtwBranch(
426
+ question: string,
427
+ assistantMessage: AssistantMessage,
428
+ leafId: string,
429
+ sessionId: string,
430
+ ): Promise<void>;
423
431
  handleOmfgCommand(complaint: string): Promise<void>;
424
432
  hasActiveOmfg(): boolean;
425
433
  handleOmfgEscape(): boolean;