@oh-my-pi/pi-coding-agent 17.2.3 → 17.2.5

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 (375) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/dist/{CHANGELOG-c5hpqt9r.md → CHANGELOG-q2w43aw3.md} +92 -0
  3. package/dist/cli.js +3979 -3548
  4. package/dist/{template-8vdv6xb4.js → template-c2hyaytt.js} +8 -26
  5. package/dist/types/advisor/advise-tool.d.ts +6 -0
  6. package/dist/types/advisor/runtime.d.ts +4 -4
  7. package/dist/types/capability/mcp.d.ts +9 -0
  8. package/dist/types/cli/args.d.ts +4 -1
  9. package/dist/types/cli/browser-relay-cli.d.ts +15 -0
  10. package/dist/types/cli/command-help.d.ts +99 -0
  11. package/dist/types/cli/help-extra.d.ts +2 -0
  12. package/dist/types/cli/models-cli.d.ts +1 -1
  13. package/dist/types/cli/thinking-levels.d.ts +5 -0
  14. package/dist/types/cli/update-cli.d.ts +13 -0
  15. package/dist/types/commands/browser-relay.d.ts +38 -0
  16. package/dist/types/commands/commit.d.ts +3 -0
  17. package/dist/types/commands/complete.d.ts +1 -1
  18. package/dist/types/commands/completions.d.ts +6 -0
  19. package/dist/types/commands/grep.d.ts +3 -0
  20. package/dist/types/commands/join.d.ts +4 -0
  21. package/dist/types/commands/launch-help.d.ts +171 -0
  22. package/dist/types/commands/launch.d.ts +5 -1
  23. package/dist/types/commands/models.d.ts +3 -0
  24. package/dist/types/commands/say.d.ts +10 -0
  25. package/dist/types/commands/worktree.d.ts +3 -0
  26. package/dist/types/config/keybindings.d.ts +6 -1
  27. package/dist/types/config/model-discovery.d.ts +2 -2
  28. package/dist/types/config/models-config-schema-bundle.d.ts +3 -0
  29. package/dist/types/config/models-config-schema.d.ts +2 -0
  30. package/dist/types/config/models-config.d.ts +1 -0
  31. package/dist/types/config/service-tier.d.ts +2 -0
  32. package/dist/types/config/settings-schema.d.ts +70 -22
  33. package/dist/types/cursor-bridge-tools.d.ts +1 -1
  34. package/dist/types/cursor.d.ts +7 -4
  35. package/dist/types/discovery/helpers.d.ts +6 -0
  36. package/dist/types/discovery/omp-extension-roots.d.ts +21 -2
  37. package/dist/types/edit/index.d.ts +3 -3
  38. package/dist/types/edit/modes/replace.d.ts +22 -15
  39. package/dist/types/edit/renderer.d.ts +1 -1
  40. package/dist/types/eval/py/kernel.d.ts +3 -1
  41. package/dist/types/eval/py/spawn-options.d.ts +12 -16
  42. package/dist/types/extensibility/extensions/loader.d.ts +6 -2
  43. package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +8 -1
  44. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -1
  45. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +2 -0
  46. package/dist/types/extensibility/plugins/marketplace/registry.d.ts +2 -2
  47. package/dist/types/launch/broker.d.ts +1 -1
  48. package/dist/types/launch/client.d.ts +6 -3
  49. package/dist/types/launch/ensure.d.ts +8 -0
  50. package/dist/types/launch/paths.d.ts +3 -2
  51. package/dist/types/launch/protocol.d.ts +2 -2
  52. package/dist/types/lsp/client.d.ts +2 -0
  53. package/dist/types/lsp/mux/daemon.d.ts +15 -0
  54. package/dist/types/lsp/mux/protocol.d.ts +57 -0
  55. package/dist/types/lsp/mux/server.d.ts +18 -0
  56. package/dist/types/lsp/types.d.ts +22 -2
  57. package/dist/types/main.d.ts +2 -0
  58. package/dist/types/mcp/request-id.d.ts +19 -0
  59. package/dist/types/mcp/types.d.ts +14 -0
  60. package/dist/types/memory-backend/index.d.ts +1 -0
  61. package/dist/types/memory-backend/messages.d.ts +15 -0
  62. package/dist/types/mnemopi/embed-client.d.ts +1 -1
  63. package/dist/types/mnemopi/state.d.ts +0 -23
  64. package/dist/types/modes/components/assistant-message.d.ts +2 -0
  65. package/dist/types/modes/components/custom-editor.d.ts +2 -1
  66. package/dist/types/modes/components/model-browser.d.ts +7 -5
  67. package/dist/types/modes/components/model-picker.d.ts +9 -3
  68. package/dist/types/modes/components/read-tool-group.d.ts +11 -0
  69. package/dist/types/modes/components/stripped-tool-calls-placeholder.d.ts +12 -0
  70. package/dist/types/modes/components/tool-execution.d.ts +2 -0
  71. package/dist/types/modes/controllers/input-controller.d.ts +1 -0
  72. package/dist/types/modes/interactive-mode.d.ts +1 -0
  73. package/dist/types/modes/types.d.ts +2 -1
  74. package/dist/types/modes/workflow.d.ts +2 -1
  75. package/dist/types/registry/agent-lifecycle.d.ts +14 -5
  76. package/dist/types/registry/agent-registry.d.ts +4 -3
  77. package/dist/types/sdk.d.ts +4 -2
  78. package/dist/types/secrets/index.d.ts +7 -9
  79. package/dist/types/session/agent-session-types.d.ts +3 -1
  80. package/dist/types/session/agent-session.d.ts +6 -1
  81. package/dist/types/session/indexed-session-storage.d.ts +1 -0
  82. package/dist/types/session/model-controls.d.ts +0 -1
  83. package/dist/types/session/session-advisors.d.ts +8 -1
  84. package/dist/types/session/session-context.d.ts +2 -1
  85. package/dist/types/session/session-maintenance.d.ts +2 -0
  86. package/dist/types/session/session-manager.d.ts +16 -5
  87. package/dist/types/session/session-stats.d.ts +10 -0
  88. package/dist/types/session/session-storage.d.ts +15 -3
  89. package/dist/types/system-prompt.d.ts +6 -1
  90. package/dist/types/task/index.d.ts +2 -1
  91. package/dist/types/task/spawn-policy.d.ts +5 -0
  92. package/dist/types/thinking.d.ts +1 -7
  93. package/dist/types/tools/ast-grep.d.ts +1 -1
  94. package/dist/types/tools/browser/registry.d.ts +2 -1
  95. package/dist/types/tools/browser/relay/bridge.d.ts +35 -0
  96. package/dist/types/tools/browser/relay/daemon.d.ts +16 -0
  97. package/dist/types/tools/browser/relay/kind.d.ts +19 -0
  98. package/dist/types/tools/browser/relay/protocol.d.ts +97 -0
  99. package/dist/types/tools/browser/relay/server.d.ts +35 -0
  100. package/dist/types/tools/browser/render.d.ts +1 -0
  101. package/dist/types/tools/browser.d.ts +3 -0
  102. package/dist/types/tools/computer/exposure.d.ts +9 -7
  103. package/dist/types/tools/computer/protocol.d.ts +70 -17
  104. package/dist/types/tools/computer/supervisor.d.ts +23 -10
  105. package/dist/types/tools/computer/worker-entry.d.ts +1 -0
  106. package/dist/types/tools/computer/worker.d.ts +42 -7
  107. package/dist/types/tools/computer-renderer.d.ts +7 -5
  108. package/dist/types/tools/computer.d.ts +25 -44
  109. package/dist/types/tools/context.d.ts +3 -0
  110. package/dist/types/tools/default-renderer.d.ts +3 -0
  111. package/dist/types/tools/glob.d.ts +1 -1
  112. package/dist/types/tools/grep.d.ts +1 -1
  113. package/dist/types/tools/render-utils.d.ts +2 -0
  114. package/dist/types/tools/{browser/run-cancellation.d.ts → run-scope.d.ts} +8 -8
  115. package/dist/types/tools/tool-timeouts.d.ts +5 -0
  116. package/dist/types/tools/xdev.d.ts +8 -0
  117. package/dist/types/utils/clipboard.d.ts +2 -2
  118. package/dist/types/web/kagi.d.ts +1 -0
  119. package/dist/types/web/search/index.d.ts +1 -1
  120. package/dist/types/web/search/providers/anthropic.d.ts +1 -0
  121. package/dist/types/web/search/providers/base.d.ts +2 -0
  122. package/dist/types/web/search/providers/brave.d.ts +1 -0
  123. package/dist/types/web/search/providers/browser-page.d.ts +1 -0
  124. package/dist/types/web/search/providers/codex.d.ts +1 -0
  125. package/dist/types/web/search/providers/exa.d.ts +1 -0
  126. package/dist/types/web/search/providers/firecrawl.d.ts +1 -0
  127. package/dist/types/web/search/providers/gemini.d.ts +1 -0
  128. package/dist/types/web/search/providers/jina.d.ts +1 -0
  129. package/dist/types/web/search/providers/kagi.d.ts +1 -0
  130. package/dist/types/web/search/providers/kimi.d.ts +1 -0
  131. package/dist/types/web/search/providers/parallel.d.ts +1 -0
  132. package/dist/types/web/search/providers/perplexity.d.ts +1 -0
  133. package/dist/types/web/search/providers/searxng.d.ts +1 -0
  134. package/dist/types/web/search/providers/tavily.d.ts +1 -0
  135. package/dist/types/web/search/providers/tinyfish.d.ts +1 -0
  136. package/dist/types/web/search/providers/utils.d.ts +6 -9
  137. package/dist/types/web/search/providers/zai.d.ts +1 -0
  138. package/dist/types/web/search/types.d.ts +4 -0
  139. package/package.json +12 -12
  140. package/scripts/legacy-pi-virtual-module.ts +12 -2
  141. package/src/advisor/advise-tool.ts +18 -0
  142. package/src/advisor/runtime.ts +6 -6
  143. package/src/capability/mcp.ts +7 -0
  144. package/src/cli/args.ts +7 -87
  145. package/src/cli/browser-relay-cli.ts +119 -0
  146. package/src/cli/command-help.ts +103 -0
  147. package/src/cli/completion-gen.ts +5 -5
  148. package/src/cli/flag-tables.ts +9 -0
  149. package/src/cli/help-extra.ts +89 -0
  150. package/src/cli/models-cli.ts +13 -16
  151. package/src/cli/setup-cli.ts +13 -21
  152. package/src/cli/thinking-levels.ts +7 -0
  153. package/src/cli/update-cli.ts +26 -7
  154. package/src/cli-commands.ts +170 -33
  155. package/src/cli.ts +17 -5
  156. package/src/collab/host.ts +1 -1
  157. package/src/commands/acp.ts +3 -1
  158. package/src/commands/agents.ts +3 -2
  159. package/src/commands/auth-broker.ts +3 -2
  160. package/src/commands/auth-gateway.ts +3 -2
  161. package/src/commands/bench.ts +2 -3
  162. package/src/commands/browser-relay.ts +53 -0
  163. package/src/commands/cleanse.ts +2 -2
  164. package/src/commands/commit.ts +3 -2
  165. package/src/commands/complete.ts +2 -1
  166. package/src/commands/completions.ts +3 -2
  167. package/src/commands/config.ts +3 -2
  168. package/src/commands/dry-balance.ts +2 -2
  169. package/src/commands/gallery.ts +3 -2
  170. package/src/commands/gc.ts +3 -2
  171. package/src/commands/grep.ts +3 -2
  172. package/src/commands/grievances.ts +3 -2
  173. package/src/commands/install.ts +2 -2
  174. package/src/commands/join.ts +3 -2
  175. package/src/commands/launch-help.ts +116 -0
  176. package/src/commands/launch.ts +7 -191
  177. package/src/commands/models.ts +3 -2
  178. package/src/commands/plugin.ts +3 -2
  179. package/src/commands/read.ts +3 -2
  180. package/src/commands/say.ts +3 -2
  181. package/src/commands/setup.ts +3 -2
  182. package/src/commands/shell.ts +3 -2
  183. package/src/commands/ssh.ts +3 -2
  184. package/src/commands/stats.ts +3 -2
  185. package/src/commands/tiny-models.ts +2 -2
  186. package/src/commands/token.ts +2 -2
  187. package/src/commands/ttsr.ts +2 -2
  188. package/src/commands/update.ts +3 -2
  189. package/src/commands/usage.ts +3 -2
  190. package/src/commands/web-search.ts +3 -2
  191. package/src/commands/worktree.ts +3 -2
  192. package/src/config/keybindings.ts +19 -9
  193. package/src/config/mcp-schema.json +5 -0
  194. package/src/config/model-discovery.ts +30 -13
  195. package/src/config/model-registry.ts +16 -6
  196. package/src/config/models-config-schema-bundle.ts +9 -0
  197. package/src/config/resolve-config-value.ts +2 -1
  198. package/src/config/service-tier.ts +5 -0
  199. package/src/config/settings-schema.ts +73 -21
  200. package/src/cursor-bridge-tools.ts +1 -1
  201. package/src/cursor.ts +10 -14
  202. package/src/discovery/builtin.ts +10 -0
  203. package/src/discovery/helpers.ts +10 -0
  204. package/src/discovery/mcp-json.ts +11 -1
  205. package/src/discovery/omp-extension-roots.ts +79 -20
  206. package/src/discovery/omp-plugins.ts +10 -1
  207. package/src/edit/diff.ts +17 -8
  208. package/src/edit/index.ts +20 -7
  209. package/src/edit/modes/replace.ts +93 -40
  210. package/src/edit/renderer.ts +1 -1
  211. package/src/edit/streaming.ts +14 -17
  212. package/src/eval/py/kernel.ts +2 -1
  213. package/src/eval/py/spawn-options.ts +24 -29
  214. package/src/export/html/template.js +8 -26
  215. package/src/extensibility/extensions/loader.ts +66 -28
  216. package/src/extensibility/extensions/wrapper.ts +1 -0
  217. package/src/extensibility/legacy-pi-ai-shim.ts +22 -1
  218. package/src/extensibility/legacy-pi-coding-agent-shim.ts +7 -6
  219. package/src/extensibility/plugins/legacy-pi-compat.ts +124 -57
  220. package/src/extensibility/plugins/marketplace/registry.ts +4 -8
  221. package/src/extensibility/skills.ts +11 -0
  222. package/src/extensibility/utils.ts +10 -2
  223. package/src/launch/broker.ts +1 -1
  224. package/src/launch/client.ts +32 -13
  225. package/src/launch/ensure.ts +73 -0
  226. package/src/launch/paths.ts +3 -6
  227. package/src/launch/protocol.ts +2 -2
  228. package/src/lsp/client.ts +23 -2
  229. package/src/lsp/index.ts +8 -0
  230. package/src/lsp/mux/daemon.ts +348 -0
  231. package/src/lsp/mux/protocol.ts +96 -0
  232. package/src/lsp/mux/server.ts +797 -0
  233. package/src/lsp/types.ts +28 -2
  234. package/src/main.ts +31 -24
  235. package/src/mcp/config.ts +1 -0
  236. package/src/mcp/oauth-flow.ts +11 -1
  237. package/src/mcp/request-id.ts +24 -0
  238. package/src/mcp/transports/http.ts +4 -2
  239. package/src/mcp/transports/sse.ts +4 -2
  240. package/src/mcp/transports/stdio.ts +4 -2
  241. package/src/mcp/types.ts +15 -0
  242. package/src/memory-backend/index.ts +1 -0
  243. package/src/memory-backend/messages.ts +19 -0
  244. package/src/mnemopi/embed-client.ts +49 -2
  245. package/src/mnemopi/state.ts +57 -23
  246. package/src/modes/components/agent-hub.ts +1 -1
  247. package/src/modes/components/assistant-message.ts +11 -0
  248. package/src/modes/components/chat-transcript-builder.ts +4 -0
  249. package/src/modes/components/custom-editor.ts +10 -1
  250. package/src/modes/components/model-browser.ts +25 -17
  251. package/src/modes/components/model-picker.ts +10 -6
  252. package/src/modes/components/read-tool-group.ts +36 -0
  253. package/src/modes/components/status-line/component.ts +1 -1
  254. package/src/modes/components/stripped-tool-calls-placeholder.ts +35 -0
  255. package/src/modes/components/tool-execution.ts +85 -20
  256. package/src/modes/components/tree-selector.ts +46 -48
  257. package/src/modes/controllers/command-controller.ts +2 -2
  258. package/src/modes/controllers/event-controller.ts +199 -10
  259. package/src/modes/controllers/input-controller.ts +39 -1
  260. package/src/modes/controllers/mcp-command-controller.ts +11 -5
  261. package/src/modes/controllers/selector-controller.ts +47 -6
  262. package/src/modes/interactive-mode.ts +2 -0
  263. package/src/modes/types.ts +6 -1
  264. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  265. package/src/modes/utils/interactive-context-helpers.ts +1 -0
  266. package/src/modes/utils/ui-helpers.ts +6 -10
  267. package/src/modes/workflow.ts +8 -2
  268. package/src/prompts/agents/init.md +1 -1
  269. package/src/prompts/system/plan-mode-active.md +2 -2
  270. package/src/prompts/system/system-prompt.md +26 -52
  271. package/src/prompts/system/workflow-notice.md +3 -3
  272. package/src/prompts/tools/ast-grep.md +1 -1
  273. package/src/prompts/tools/bash.md +1 -1
  274. package/src/prompts/tools/browser.md +1 -0
  275. package/src/prompts/tools/computer.md +21 -21
  276. package/src/prompts/tools/glob.md +1 -1
  277. package/src/prompts/tools/grep.md +1 -1
  278. package/src/prompts/tools/replace.md +5 -4
  279. package/src/prompts/tools/task.md +4 -4
  280. package/src/registry/agent-lifecycle.ts +36 -11
  281. package/src/registry/agent-registry.ts +12 -5
  282. package/src/sdk.ts +102 -35
  283. package/src/secrets/index.ts +16 -18
  284. package/src/session/agent-session-types.ts +3 -1
  285. package/src/session/agent-session.ts +23 -2
  286. package/src/session/agent-storage.ts +6 -4
  287. package/src/session/history-storage.ts +4 -2
  288. package/src/session/indexed-session-storage.ts +9 -0
  289. package/src/session/messages.ts +31 -7
  290. package/src/session/model-controls.ts +0 -1
  291. package/src/session/prewalk.ts +24 -2
  292. package/src/session/session-advisors.ts +21 -3
  293. package/src/session/session-context.ts +16 -12
  294. package/src/session/session-dump-format.ts +1 -1
  295. package/src/session/session-maintenance.ts +41 -4
  296. package/src/session/session-manager.ts +181 -94
  297. package/src/session/session-paths.ts +118 -55
  298. package/src/session/session-stats.ts +48 -3
  299. package/src/session/session-storage.ts +30 -22
  300. package/src/session/session-tools.ts +25 -23
  301. package/src/session/stream-guards.ts +1 -1
  302. package/src/slash-commands/builtin-registry.ts +29 -26
  303. package/src/system-prompt.ts +14 -4
  304. package/src/task/executor.ts +8 -1
  305. package/src/task/index.ts +23 -8
  306. package/src/task/spawn-policy.ts +14 -0
  307. package/src/thinking.ts +2 -8
  308. package/src/tiny/text.ts +12 -0
  309. package/src/tools/ast-grep.ts +10 -4
  310. package/src/tools/browser/cmux/cmux-tab.ts +5 -10
  311. package/src/tools/browser/cmux/rpc.ts +2 -8
  312. package/src/tools/browser/registry.ts +53 -2
  313. package/src/tools/browser/relay/bridge.ts +945 -0
  314. package/src/tools/browser/relay/daemon.ts +117 -0
  315. package/src/tools/browser/relay/extension-assets/background.js.txt +242 -0
  316. package/src/tools/browser/relay/extension-assets/manifest.json.txt +14 -0
  317. package/src/tools/browser/relay/extension-assets/options.html.txt +53 -0
  318. package/src/tools/browser/relay/extension-assets/options.js.txt +23 -0
  319. package/src/tools/browser/relay/kind.ts +41 -0
  320. package/src/tools/browser/relay/protocol.ts +54 -0
  321. package/src/tools/browser/relay/server.ts +141 -0
  322. package/src/tools/browser/render.ts +4 -1
  323. package/src/tools/browser/shared-daemon.ts +7 -58
  324. package/src/tools/browser/tab-supervisor.ts +5 -4
  325. package/src/tools/browser/tab-worker.ts +33 -12
  326. package/src/tools/browser.ts +26 -1
  327. package/src/tools/computer/exposure.ts +11 -35
  328. package/src/tools/computer/protocol.ts +51 -10
  329. package/src/tools/computer/supervisor.ts +227 -124
  330. package/src/tools/computer/worker-entry.ts +11 -6
  331. package/src/tools/computer/worker.ts +695 -81
  332. package/src/tools/computer-renderer.ts +104 -65
  333. package/src/tools/computer.ts +134 -428
  334. package/src/tools/context.ts +3 -0
  335. package/src/tools/default-renderer.ts +19 -4
  336. package/src/tools/glob.ts +9 -2
  337. package/src/tools/grep.ts +12 -6
  338. package/src/tools/index.ts +10 -6
  339. package/src/tools/inspect-image.ts +32 -6
  340. package/src/tools/render-utils.ts +2 -0
  341. package/src/tools/{browser/run-cancellation.ts → run-scope.ts} +11 -11
  342. package/src/tools/tool-timeouts.ts +1 -0
  343. package/src/tools/xdev.ts +29 -2
  344. package/src/utils/clipboard.ts +40 -14
  345. package/src/utils/command-args.ts +26 -28
  346. package/src/utils/tool-choice.ts +0 -13
  347. package/src/web/kagi.ts +2 -1
  348. package/src/web/search/index.ts +18 -2
  349. package/src/web/search/providers/anthropic.ts +7 -2
  350. package/src/web/search/providers/base.ts +2 -0
  351. package/src/web/search/providers/brave.ts +3 -1
  352. package/src/web/search/providers/browser-page.ts +6 -4
  353. package/src/web/search/providers/codex.ts +4 -1
  354. package/src/web/search/providers/duckduckgo.ts +2 -1
  355. package/src/web/search/providers/ecosia.ts +2 -1
  356. package/src/web/search/providers/exa.ts +4 -2
  357. package/src/web/search/providers/firecrawl.ts +3 -1
  358. package/src/web/search/providers/gemini.ts +8 -2
  359. package/src/web/search/providers/google.ts +2 -1
  360. package/src/web/search/providers/jina.ts +12 -2
  361. package/src/web/search/providers/kagi.ts +3 -0
  362. package/src/web/search/providers/kimi.ts +5 -1
  363. package/src/web/search/providers/mojeek.ts +2 -1
  364. package/src/web/search/providers/parallel.ts +5 -1
  365. package/src/web/search/providers/perplexity.ts +6 -3
  366. package/src/web/search/providers/public.ts +1 -1
  367. package/src/web/search/providers/searxng.ts +11 -5
  368. package/src/web/search/providers/startpage.ts +10 -4
  369. package/src/web/search/providers/synthetic.ts +11 -5
  370. package/src/web/search/providers/tavily.ts +3 -1
  371. package/src/web/search/providers/tinyfish.ts +3 -1
  372. package/src/web/search/providers/utils.ts +12 -10
  373. package/src/web/search/providers/xai.ts +2 -2
  374. package/src/web/search/providers/zai.ts +18 -3
  375. package/src/web/search/types.ts +6 -0
@@ -190,6 +190,7 @@ export class ChatTranscriptBuilder {
190
190
  this.#readGroup = new ReadToolGroupComponent({
191
191
  showContentPreview: settings.get("read.toolResultPreview"),
192
192
  });
193
+ this.#readGroup.setToolActivityVisible(!settings.get("display.hideToolActivity"));
193
194
  this.#trackExpandable(this.#readGroup);
194
195
  this.container.addChild(this.#readGroup);
195
196
  }
@@ -321,6 +322,7 @@ export class ChatTranscriptBuilder {
321
322
  proseOnlyThinking,
322
323
  );
323
324
  assistantComponent.setImagesVisible(settings.get("terminal.showImages"));
325
+ assistantComponent.setToolResultImagesVisible(!settings.get("display.hideToolActivity"));
324
326
  this.#trackExpandable(assistantComponent);
325
327
  this.container.addChild(assistantComponent);
326
328
 
@@ -353,6 +355,7 @@ export class ChatTranscriptBuilder {
353
355
  proseOnlyThinking,
354
356
  );
355
357
  component.setImagesVisible(settings.get("terminal.showImages"));
358
+ component.setToolResultImagesVisible(!settings.get("display.hideToolActivity"));
356
359
  this.#trackExpandable(component);
357
360
  this.container.addChild(component);
358
361
  };
@@ -401,6 +404,7 @@ export class ChatTranscriptBuilder {
401
404
  this.deps.cwd,
402
405
  content.id,
403
406
  );
407
+ component.setToolActivityVisible(!settings.get("display.hideToolActivity"));
404
408
  this.#trackExpandable(component);
405
409
  this.container.addChild(component);
406
410
 
@@ -31,6 +31,7 @@ type ConfigurableEditorAction = Extract<
31
31
  | "app.model.select"
32
32
  | "app.model.selectTemporary"
33
33
  | "app.tools.expand"
34
+ | "app.tools.toggleVisibility"
34
35
  | "app.thinking.toggle"
35
36
  | "app.editor.external"
36
37
  | "app.history.search"
@@ -53,10 +54,11 @@ const DEFAULT_ACTION_KEYS: Record<ConfigurableEditorAction, KeyId[]> = {
53
54
  "app.model.select": ["alt+m"],
54
55
  "app.model.selectTemporary": ["alt+p"],
55
56
  "app.tools.expand": ["ctrl+o"],
57
+ "app.tools.toggleVisibility": ["ctrl+shift+o"],
56
58
  "app.thinking.toggle": ["ctrl+t"],
57
59
  "app.editor.external": ["ctrl+g"],
58
60
  "app.history.search": ["ctrl+r"],
59
- "app.message.dequeue": ["alt+up"],
61
+ "app.message.dequeue": ["alt+up", "shift+up"],
60
62
  "app.retry": ["alt+r"],
61
63
  "app.clipboard.pasteImage": ["ctrl+v"],
62
64
  "app.clipboard.pasteTextRaw": ["ctrl+shift+v", "alt+shift+v"],
@@ -550,6 +552,7 @@ export class CustomEditor extends Editor {
550
552
  onCycleModelBackward?: () => void;
551
553
  onSelectModel?: () => void;
552
554
  onExpandTools?: () => void;
555
+ onToggleToolActivity?: () => void;
553
556
  onToggleThinking?: () => void;
554
557
  onExternalEditor?: () => void;
555
558
  onHistorySearch?: () => void;
@@ -910,6 +913,12 @@ export class CustomEditor extends Editor {
910
913
  return;
911
914
  }
912
915
 
916
+ // Intercept configured tool activity visibility toggle
917
+ if (this.#matchesAction(canonical, "app.tools.toggleVisibility") && this.onToggleToolActivity) {
918
+ this.onToggleToolActivity();
919
+ return;
920
+ }
921
+
913
922
  // Intercept configured backward model cycling (check before forward cycling)
914
923
  if (this.#matchesAction(canonical, "app.model.cycleBackward") && this.onCycleModelBackward) {
915
924
  this.onCycleModelBackward();
@@ -314,10 +314,10 @@ function padLeftVisible(text: string, width: number): string {
314
314
  export interface ModelBrowserOptions {
315
315
  /** Render the dim `provider/` prefix before model ids. Default true. */
316
316
  showProvider?: boolean;
317
- /** Session token count used to disable models whose context window is exceeded. */
317
+ /** Session token count used to flag models whose context window is exceeded. */
318
318
  currentContextTokens?: number;
319
- /** When true, rows over the current context are unselectable (session-switch mode). */
320
- disableOverContext?: boolean;
319
+ /** When true, over-context rows render grayed; picking one compacts first (session-switch mode). */
320
+ markOverContext?: boolean;
321
321
  /** Host-provided empty-state text (e.g. provider discovery status). */
322
322
  emptyText?: () => string | undefined;
323
323
  }
@@ -351,7 +351,7 @@ export class ModelBrowser implements Component {
351
351
  #maxVisible = 10;
352
352
  #showProvider: boolean;
353
353
  #currentContextTokens: number;
354
- #disableOverContext: boolean;
354
+ #markOverContext: boolean;
355
355
  #emptyText?: () => string | undefined;
356
356
  /** Keep role-like virtual rows in their host-defined order during search. */
357
357
  #preserveQueryOrder = false;
@@ -375,7 +375,7 @@ export class ModelBrowser implements Component {
375
375
  this.#showProvider = options.showProvider ?? true;
376
376
  const tokens = options.currentContextTokens ?? 0;
377
377
  this.#currentContextTokens = Number.isFinite(tokens) && tokens > 0 ? Math.floor(tokens) : 0;
378
- this.#disableOverContext = options.disableOverContext ?? false;
378
+ this.#markOverContext = options.markOverContext ?? false;
379
379
  this.#emptyText = options.emptyText;
380
380
  }
381
381
 
@@ -420,9 +420,9 @@ export class ModelBrowser implements Component {
420
420
  setPreserveQueryOrder(preserve: boolean): void {
421
421
  this.#preserveQueryOrder = preserve;
422
422
  }
423
- /** Allow hosts to toggle context-window eligibility between browser modes. */
424
- setDisableOverContext(disable: boolean): void {
425
- this.#disableOverContext = disable;
423
+ /** Allow hosts to toggle context-window flagging between browser modes. */
424
+ setMarkOverContext(mark: boolean): void {
425
+ this.#markOverContext = mark;
426
426
  }
427
427
  /** Focused: accent cursor + selected-row background band. Unfocused: dim cursor, no band. */
428
428
  setFocused(focused: boolean): void {
@@ -461,8 +461,13 @@ export class ModelBrowser implements Component {
461
461
  }
462
462
 
463
463
  #isDisabled(item: ModelBrowserItem): boolean {
464
- if (item.id === "separator") return true;
465
- if (!this.#disableOverContext || this.#currentContextTokens <= 0) return false;
464
+ return item.id === "separator";
465
+ }
466
+
467
+ /** True when `item`'s context window is smaller than the live session token count (grayed row; hosts compact before switching). */
468
+ isOverContext(item: ModelBrowserItem): boolean {
469
+ if (item.id === "separator") return false;
470
+ if (!this.#markOverContext || this.#currentContextTokens <= 0) return false;
466
471
  const contextWindow = item.model.contextWindow ?? 0;
467
472
  return contextWindow > 0 && this.#currentContextTokens > contextWindow;
468
473
  }
@@ -718,7 +723,7 @@ export class ModelBrowser implements Component {
718
723
  const line = theme.fg("muted", "─".repeat(dashCount));
719
724
  return ` ${line} `;
720
725
  }
721
- const disabled = this.#isDisabled(item);
726
+ const overContext = this.isOverContext(item);
722
727
  const prefix = selected && this.#focused ? `${theme.fg("accent", theme.nav.cursor)} ` : " ";
723
728
  const providerPrefix = this.#showProvider ? theme.fg("dim", `${item.provider}/`) : "";
724
729
  const name = item.labelColor
@@ -728,7 +733,7 @@ export class ModelBrowser implements Component {
728
733
  : item.id;
729
734
  const currentMark =
730
735
  item.selector === this.#currentSelector ? ` ${theme.fg("success", theme.status.enabled)}` : "";
731
- const overLimit = disabled
736
+ const overLimit = overContext
732
737
  ? ` ${theme.status.disabled} context>${formatNumber(item.model.contextWindow ?? 0).toLowerCase()}`
733
738
  : "";
734
739
  let left = `${prefix}${providerPrefix}${name}${currentMark}${overLimit}`;
@@ -743,12 +748,15 @@ export class ModelBrowser implements Component {
743
748
  const gap = Math.max(0, available - visibleWidth(left));
744
749
 
745
750
  let line = `${left}${" ".repeat(gap)} ${meta}`;
746
- if (disabled) {
747
- line = theme.fg("dim", Bun.stripANSI(line));
751
+ if (overContext) {
752
+ // Gray the whole row but keep the selection cursor visible: over-context
753
+ // models stay selectable (the host compacts before switching).
754
+ const plainPrefix = Bun.stripANSI(prefix);
755
+ line = `${prefix}${theme.fg("dim", Bun.stripANSI(line).slice(plainPrefix.length))}`;
748
756
  }
749
757
  // The bg band is reserved for the mouse: it marks hover, nothing else.
750
758
  // Keyboard selection is the cursor glyph + accent name.
751
- if (hovered && !disabled) {
759
+ if (hovered) {
752
760
  line = theme.bg("selectedBg", line);
753
761
  }
754
762
  return line;
@@ -772,8 +780,8 @@ export class ModelBrowser implements Component {
772
780
  }
773
781
  const line1 = truncateToWidth(theme.fg("muted", ` ${facts.join(" · ")}`), width);
774
782
 
775
- if (this.#isDisabled(selected)) {
776
- const warning = ` ${theme.status.disabled} current context ${formatNumber(this.#currentContextTokens).toLowerCase()} exceeds ${formatNumber(model.contextWindow ?? 0).toLowerCase()} limit`;
783
+ if (this.isOverContext(selected)) {
784
+ const warning = ` ${theme.status.disabled} context ${formatNumber(this.#currentContextTokens).toLowerCase()} exceeds ${formatNumber(model.contextWindow ?? 0).toLowerCase()} limit · compacts with current model, then switches`;
777
785
  return [line1, truncateToWidth(theme.fg("warning", warning), width)];
778
786
  }
779
787
 
@@ -22,8 +22,12 @@ import { bottomBorder, row, topBorder } from "./overlay-box";
22
22
  import { resolveSegmentPalette } from "./segment-track";
23
23
 
24
24
  export interface ModelPickerCallbacks {
25
- /** A model was chosen for a session-only switch. `selector` is `provider/id`. */
26
- onPick: (model: Model, selector: string) => void;
25
+ /**
26
+ * A model was chosen for a session-only switch. `selector` is `provider/id`.
27
+ * `overContext` is true when the session transcript exceeds the model's
28
+ * context window — the host must compact before switching.
29
+ */
30
+ onPick: (model: Model, selector: string, meta: { overContext: boolean }) => void;
27
31
  /** A configured ctrl+p quick role was chosen. */
28
32
  onPickRole?: (entry: ResolvedRoleModel) => void;
29
33
  /** The picker was dismissed. */
@@ -31,7 +35,7 @@ export interface ModelPickerCallbacks {
31
35
  }
32
36
 
33
37
  export interface ModelPickerOptions {
34
- /** Session token count; models with smaller context windows are disabled. */
38
+ /** Session token count; models with smaller context windows are grayed and compact-first on pick. */
35
39
  currentContextTokens?: number;
36
40
  /** `provider/id` of the session's active model; highlighted and preselected. */
37
41
  currentSelector?: string;
@@ -97,7 +101,7 @@ export class ModelPickerComponent implements Component {
97
101
 
98
102
  this.#browser = new ModelBrowser(settings, {
99
103
  currentContextTokens: options.currentContextTokens,
100
- disableOverContext: true,
104
+ markOverContext: true,
101
105
  emptyText: () => (this.#roleMode ? " No quick roles in the Ctrl+P cycle" : undefined),
102
106
  });
103
107
  this.#browser.onActivate = item => {
@@ -106,7 +110,7 @@ export class ModelPickerComponent implements Component {
106
110
  callbacks.onPickRole?.(quickRole);
107
111
  return;
108
112
  }
109
- callbacks.onPick(item.model, item.selector);
113
+ callbacks.onPick(item.model, item.selector, { overContext: this.#browser.isOverContext(item) });
110
114
  };
111
115
  this.#browser.onCancel = () => callbacks.onCancel();
112
116
  this.#browser.onQueryChange = query => this.#syncItemsForQuery(query);
@@ -193,7 +197,7 @@ export class ModelPickerComponent implements Component {
193
197
 
194
198
  this.#roleMode = roleMode;
195
199
  this.#browser.setShowProvider(!roleMode);
196
- this.#browser.setDisableOverContext(!roleMode);
200
+ this.#browser.setMarkOverContext(!roleMode);
197
201
  this.#browser.setPreserveQueryOrder(roleMode);
198
202
  const currentSelector = roleMode ? this.#currentQuickRoleSelector : this.#currentSelector;
199
203
  this.#browser.setCurrentSelector(currentSelector);
@@ -332,6 +332,7 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
332
332
  #usageBatchByToolCallId = new Map<string, string>();
333
333
  #text: Text;
334
334
  #expanded = false;
335
+ #toolActivityVisible = true;
335
336
  #showContentPreview: boolean;
336
337
  // A read group accretes entries across multiple assistant completions for as
337
338
  // long as the run of reads is uninterrupted. While it is the active group it
@@ -353,6 +354,10 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
353
354
  this.#updateDisplay();
354
355
  }
355
356
 
357
+ override render(width: number): readonly string[] {
358
+ if (!this.#toolActivityVisible) return [];
359
+ return super.render(width);
360
+ }
356
361
  isTranscriptBlockFinalized(): boolean {
357
362
  if (this.#sealed) return true;
358
363
  if (!this.#finalized) return false;
@@ -397,6 +402,32 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
397
402
  this.#updateDisplay();
398
403
  }
399
404
 
405
+ /**
406
+ * Re-key an entry whose streamed tool-call id changed mid-stream (a provider
407
+ * rewriting the id across deltas; see EventController's
408
+ * `#streamedToolCallIdByIndex`). Preserves row order so a sibling read run is
409
+ * not visibly reshuffled, and no-ops when the rename would collide.
410
+ */
411
+ renameEntry(oldId: string, newId: string): void {
412
+ if (oldId === newId || !newId) return;
413
+ const entry = this.#entries.get(oldId);
414
+ if (!entry || this.#entries.has(newId)) return;
415
+ entry.toolCallId = newId;
416
+ const reordered = [...this.#entries].map(([key, value]): [string, ReadEntry] => [
417
+ key === oldId ? newId : key,
418
+ value,
419
+ ]);
420
+ this.#entries.clear();
421
+ for (const [key, value] of reordered) this.#entries.set(key, value);
422
+ this.#updateDisplay();
423
+ }
424
+ /** Remove one call without discarding successful siblings in the shared group. */
425
+ removeEntry(toolCallId: string): boolean {
426
+ if (!this.#entries.delete(toolCallId)) return this.#entries.size === 0;
427
+ this.#updateDisplay();
428
+ return this.#entries.size === 0;
429
+ }
430
+
400
431
  updateResult(
401
432
  result: { content: Array<{ type: string; text?: string }>; details?: unknown; isError?: boolean },
402
433
  isPartial = false,
@@ -470,6 +501,11 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
470
501
  this.#updateDisplay();
471
502
  }
472
503
 
504
+ setToolActivityVisible(visible: boolean): void {
505
+ this.#toolActivityVisible = visible;
506
+ super.invalidate();
507
+ }
508
+
473
509
  getComponent(): Component {
474
510
  return this;
475
511
  }
@@ -1785,7 +1785,7 @@ export class StatusLineComponent implements Component {
1785
1785
  : separatorDef.endCaps.left
1786
1786
  : "";
1787
1787
  const capPrefix = separatorDef.endCaps?.useBgAsFg ? bgAnsi.replace("\x1b[48;", "\x1b[38;") : bgAnsi + sepAnsi;
1788
- const capText = cap ? `${capPrefix}${cap}\x1b[0m` : "";
1788
+ const capText = cap ? `${capPrefix}${this.#focusedAgentId ? "\x1b[22m" : ""}${cap}\x1b[0m` : "";
1789
1789
 
1790
1790
  let content = bgAnsi + fgAnsi;
1791
1791
  content += ` ${parts.join(` ${sepAnsi}${sep}${fgAnsi} `)} `;
@@ -0,0 +1,35 @@
1
+ import { Text } from "@oh-my-pi/pi-tui";
2
+ import { theme } from "../../modes/theme/theme";
3
+
4
+ /**
5
+ * Dim transcript marker for tool calls stripped from the resolved branch
6
+ * (failed/retried turns, results on sibling branches). It is tool activity,
7
+ * so it hides and reappears with the `display.hideToolActivity` toggle.
8
+ */
9
+ export class StrippedToolCallsPlaceholder extends Text {
10
+ #toolActivityVisible: boolean;
11
+
12
+ constructor(strippedToolCalls: number, toolActivityVisible: boolean) {
13
+ super(
14
+ theme.fg(
15
+ "dim",
16
+ theme.italic(
17
+ `${strippedToolCalls} tool call${strippedToolCalls === 1 ? "" : "s"} elided — no result on this branch`,
18
+ ),
19
+ ),
20
+ 1,
21
+ 0,
22
+ );
23
+ this.#toolActivityVisible = toolActivityVisible;
24
+ }
25
+
26
+ setToolActivityVisible(visible: boolean): void {
27
+ this.#toolActivityVisible = visible;
28
+ this.invalidate();
29
+ }
30
+
31
+ override render(width: number): readonly string[] {
32
+ if (!this.#toolActivityVisible) return [];
33
+ return super.render(width);
34
+ }
35
+ }
@@ -246,6 +246,7 @@ export interface ToolExecutionHandle extends Component {
246
246
  ): void;
247
247
  setArgsComplete(toolCallId?: string): void;
248
248
  setExpanded(expanded: boolean): void;
249
+ setToolActivityVisible(visible: boolean): void;
249
250
  /** Freeze the block as final history: stop spinners and let it commit to scrollback. */
250
251
  seal(): void;
251
252
  }
@@ -276,6 +277,9 @@ let toolExecutionInstanceSeq = 0;
276
277
  export class ToolExecutionComponent extends Container implements NativeScrollbackLiveRegion {
277
278
  #contentBox: Box; // Used for custom tools and bash visual truncation
278
279
  #contentText: WidthAwareText; // Generic fallback (no custom/built-in renderer)
280
+ // Which container the constructor mounted: bespoke/built-in renderers use
281
+ // #contentBox, everything else the generic #contentText fallback.
282
+ #usesContentBox = false;
279
283
  #multiFileBoxes: (Box | Spacer)[] = []; // Extra boxes for multi-file edit results
280
284
  #imageComponents: Image[] = [];
281
285
  #imageSpacers: Spacer[] = [];
@@ -284,6 +288,7 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
284
288
  #toolLabel: string;
285
289
  #args: any;
286
290
  #expanded = false;
291
+ #toolActivityVisible = true;
287
292
  #showImages: boolean;
288
293
  #editFuzzyThreshold: number | undefined;
289
294
  #editAllowFuzzy: boolean | undefined;
@@ -411,26 +416,13 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
411
416
  // strips PLAIN-blank edges, so framed/minimal blocks (no bg set) drop these
412
417
  // lines and keep their tight spacing — only tinted lines survive.
413
418
  this.#contentBox = new Box(0, 1);
414
- this.#contentText = new WidthAwareText(
415
- contentWidth =>
416
- formatDefaultToolExecution(
417
- {
418
- label: this.#toolLabel,
419
- args: this.#args,
420
- result: this.#result ? { output: this.#getTextOutput(), isError: this.#result.isError } : undefined,
421
- options: this.#renderState,
422
- },
423
- contentWidth,
424
- theme,
425
- ),
426
- 1,
427
- 1,
428
- );
419
+ this.#contentText = new WidthAwareText(contentWidth => this.#renderDefaultCard(contentWidth), 1, 1);
429
420
 
430
421
  // Use Box for custom tools or built-in tools that have renderers
431
422
  const hasRenderer = toolName in toolRenderers;
432
423
  const hasCustomRenderer = !!(tool?.renderCall || tool?.renderResult);
433
- if (hasCustomRenderer || hasRenderer) {
424
+ this.#usesContentBox = hasCustomRenderer || hasRenderer;
425
+ if (this.#usesContentBox) {
434
426
  this.addChild(this.#contentBox);
435
427
  } else {
436
428
  this.addChild(this.#contentText);
@@ -911,6 +903,11 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
911
903
  this.#updateDisplay();
912
904
  }
913
905
 
906
+ setToolActivityVisible(visible: boolean): void {
907
+ this.#toolActivityVisible = visible;
908
+ super.invalidate();
909
+ }
910
+
914
911
  setShowImages(show: boolean): void {
915
912
  this.#showImages = show;
916
913
  this.#updateDisplay();
@@ -970,6 +967,7 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
970
967
  }
971
968
 
972
969
  override render(width: number): readonly string[] {
970
+ if (!this.#toolActivityVisible) return [];
973
971
  const lines = super.render(width);
974
972
  // Update the paint-tracking flags after `super.render(width)` — the
975
973
  // override runs on every compose the parent Container performs, so a
@@ -998,12 +996,21 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
998
996
 
999
997
  // Non-self-framing tools (custom/extension renderers and the generic
1000
998
  // fallback) get a padded, state-tinted block — built-ins that draw their
1001
- // own frame opt out below via the framed-component mark.
1002
- const stateBgKey = this.#isPartial ? "toolPendingBg" : this.#result?.isError ? "toolErrorBg" : "toolSuccessBg";
999
+ // own frame opt out below via the framed-component mark. A benign skip
1000
+ // (steering/peer interrupt aborted a still-pending call) never ran, so it
1001
+ // gets the neutral pending tint rather than the error tint (#7199).
1002
+ const benignSkip = this.#isBenignSkip();
1003
+ const stateBgKey =
1004
+ this.#isPartial || benignSkip ? "toolPendingBg" : this.#result?.isError ? "toolErrorBg" : "toolSuccessBg";
1003
1005
  const stateBgFn = (t: string) => theme.bg(stateBgKey, t);
1004
1006
 
1005
- // Check for custom tool rendering
1006
- if (this.#tool && (this.#tool.renderCall || this.#tool.renderResult)) {
1007
+ // A benign skip is a synthetic placeholder for a call that never executed,
1008
+ // so bypass any bespoke error frame and draw the neutral generic card —
1009
+ // the per-tool ✘/red-border would misread normal mid-turn steering as a
1010
+ // failure (#7199).
1011
+ if (benignSkip) {
1012
+ this.#renderBenignSkipCard(stateBgFn);
1013
+ } else if (this.#tool && (this.#tool.renderCall || this.#tool.renderResult)) {
1007
1014
  const tool = this.#tool;
1008
1015
  const mergeCallAndResult = Boolean((tool as { mergeCallAndResult?: boolean }).mergeCallAndResult);
1009
1016
  // Custom tools use Box for flexible component rendering
@@ -1399,4 +1406,62 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
1399
1406
 
1400
1407
  return output;
1401
1408
  }
1409
+
1410
+ /**
1411
+ * Format the generic call/result card at `contentWidth`. Shared by the
1412
+ * #contentText fallback and the benign-skip path so both render identically.
1413
+ */
1414
+ #renderDefaultCard(contentWidth: number): string {
1415
+ return formatDefaultToolExecution(
1416
+ {
1417
+ label: this.#toolLabel,
1418
+ args: this.#args,
1419
+ result: this.#result
1420
+ ? { output: this.#getTextOutput(), isError: this.#result.isError, skipped: this.#isBenignSkip() }
1421
+ : undefined,
1422
+ options: this.#renderState,
1423
+ },
1424
+ contentWidth,
1425
+ theme,
1426
+ );
1427
+ }
1428
+
1429
+ /**
1430
+ * True for a steering/peer-interrupt placeholder. A synthetic placeholder
1431
+ * identifies a call that never entered `tool.execute`; an interrupted
1432
+ * placeholder identifies one that started but threw before returning usable
1433
+ * output. Both are normal steering control flow and render neutrally (#7199).
1434
+ */
1435
+ #isBenignSkip(): boolean {
1436
+ if (this.#isPartial || !this.#result) return false;
1437
+ const details = this.#result.details as
1438
+ | { __synthetic?: boolean; __interrupted?: boolean; source?: string; execution?: string }
1439
+ | undefined;
1440
+ if (details?.source !== "interrupt_skipped") return false;
1441
+ return details.__synthetic === true || (details.__interrupted === true && details.execution === "started");
1442
+ }
1443
+
1444
+ /**
1445
+ * Render a benign skip as the neutral generic card, replacing any bespoke
1446
+ * renderer's error frame. Generic-fallback tools already route through
1447
+ * {@link #renderDefaultCard} (which emits the info card for a skip); they
1448
+ * only need the neutral tint. Bespoke-renderer tools get their content box
1449
+ * swapped for the same neutral card.
1450
+ */
1451
+ #renderBenignSkipCard(stateBgFn: (text: string) => string): void {
1452
+ if (!this.#usesContentBox) {
1453
+ this.#contentText.setCustomBgFn(stateBgFn);
1454
+ this.#contentText.invalidate();
1455
+ return;
1456
+ }
1457
+ for (const box of this.#multiFileBoxes) {
1458
+ this.removeChild(box);
1459
+ }
1460
+ this.#multiFileBoxes = [];
1461
+ this.#contentBox.setBgFn(undefined);
1462
+ this.#contentBox.clear();
1463
+ this.#contentBox.setPaddingX(1);
1464
+ this.#contentBox.setBgFn(stateBgFn);
1465
+ this.#contentBox.addChild(new WidthAwareText(contentWidth => this.#renderDefaultCard(contentWidth), 0, 0));
1466
+ }
1402
1467
  }
@@ -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,