@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
@@ -28,7 +28,7 @@ import {
28
28
  seedAlreadyExists,
29
29
  summarizeMentalModel,
30
30
  } from "../../hindsight";
31
- import { resolveMemoryBackend } from "../../memory-backend";
31
+ import { memoryStatsUnavailableMessage, resolveMemoryBackend } from "../../memory-backend";
32
32
  import { BashExecutionComponent } from "../../modes/components/bash-execution";
33
33
  import { BorderedLoader } from "../../modes/components/bordered-loader";
34
34
  import { DynamicBorder } from "../../modes/components/dynamic-border";
@@ -658,7 +658,7 @@ export class CommandController {
658
658
  try {
659
659
  const payload = await hook?.(agentDir, this.ctx.sessionManager.getCwd(), this.ctx.session);
660
660
  if (!payload) {
661
- this.ctx.showWarning(`Memory ${action} is not available for the ${backend.id} backend.`);
661
+ this.ctx.showWarning(memoryStatsUnavailableMessage(backend.id, action));
662
662
  return;
663
663
  }
664
664
  showMarkdownPanel(this.ctx, `Memory ${action === "stats" ? "Stats" : "Diagnostics"}`, payload);
@@ -91,6 +91,30 @@ export class EventController {
91
91
  #readToolCallArgs = new Map<string, Record<string, unknown>>();
92
92
  #readToolCallAssistantComponents = new Map<string, AssistantMessageComponent>();
93
93
  #toolTimelineComponents = new Map<string, Component>();
94
+ // Stable identity for a streamed tool call while its assistant message is
95
+ // live: maps the tool-call block's position in the streaming message to the
96
+ // id last seen at that position. A streamed id can CHANGE across cumulative
97
+ // `message_update`s — some providers (GitHub Copilot's `call_id|id`
98
+ // transport, any stream that delivers name/args before the id) emit the
99
+ // block with an empty or partial id first and rewrite it in a later delta
100
+ // (openai-completions sets `id: toolCall.id || ""` then overwrites on the
101
+ // next chunk). Keyed only by id, the changed id reads as a brand-new call
102
+ // and a second card is created — the old-id card orphans as a pending
103
+ // preview while the new-id card takes the result (#6879). Reset per assistant
104
+ // message (indices are per-message).
105
+ #streamedToolCallIdByIndex = new Map<number, string>();
106
+ // A TTSR rewind retracts its uncommitted never-run cards at message_end;
107
+ // retain their ids until agent-loop's synthetic tool_execution_start/end for
108
+ // those calls arrive so the normal no-pending path cannot recreate the
109
+ // retracted card below the rewind's fresh blocks (#6879).
110
+ #retractedToolCallIds = new Set<string>();
111
+ // Cards settled by a synthetic aborted/error `tool_execution_end` (agent-loop
112
+ // emits one per never-run call on a terminal error/abort). They stay visible
113
+ // for a genuinely terminal failure, but if an auto-retry then supersedes the
114
+ // turn (`auto_retry_start`, decided AFTER message_end) they must be removed so
115
+ // the retry's fresh cards do not render the same call twice (Codex review on
116
+ // #6881). Keyed by call id; reset per turn.
117
+ #syntheticFailureCards = new Map<string, ToolExecutionHandle>();
94
118
  // Completions that arrived before any component existed for their call id.
95
119
  // Cursor's server-resolved tools (todo) emit `tool_execution_end` through a
96
120
  // synchronous callback fired mid-parse, while the `toolcall_start` for the
@@ -252,6 +276,7 @@ export class EventController {
252
276
  showContentPreview: this.ctx.settings.get("read.toolResultPreview"),
253
277
  });
254
278
  group.setExpanded(this.ctx.toolOutputExpanded);
279
+ group.setToolActivityVisible(!this.ctx.hideToolActivity);
255
280
  this.ctx.chatContainer.addChild(group);
256
281
  this.#lastReadGroup = group;
257
282
  }
@@ -274,6 +299,71 @@ export class EventController {
274
299
  this.#readToolCallAssistantComponents.delete(toolCallId);
275
300
  }
276
301
 
302
+ #retractToolCardEntry(toolCallId: string, component: ToolExecutionHandle): void {
303
+ component.seal();
304
+ let removeComponent = true;
305
+ if (component instanceof ReadToolGroupComponent) {
306
+ removeComponent = component.removeEntry(toolCallId);
307
+ if (component === this.#lastReadGroup) this.#resetReadGroup();
308
+ }
309
+ if (removeComponent) this.ctx.chatContainer.removeChild(component);
310
+ this.ctx.pendingTools.delete(toolCallId);
311
+ this.#toolTimelineComponents.delete(toolCallId);
312
+ this.#clearReadToolCall(toolCallId);
313
+ }
314
+
315
+ /**
316
+ * Re-key a live streamed tool card whose id changed mid-stream (see
317
+ * {@link #streamedToolCallIdByIndex}). Moves every id-keyed tracker from the
318
+ * old id to the new one so the next cumulative `message_update` reuses the
319
+ * existing card instead of creating a duplicate (#6879). The card component
320
+ * itself is id-agnostic (routing is via `pendingTools`), so only the maps and
321
+ * the shared read group's entry need re-keying.
322
+ */
323
+ #migrateStreamedToolCallId(oldId: string, newId: string): void {
324
+ // `oldId` may be "" (the block streamed before its id): that empty key still
325
+ // owns a live card and must migrate. Skip only a no-op or an empty target.
326
+ if (oldId === newId || !newId) return;
327
+ const pending = this.ctx.pendingTools.get(oldId);
328
+ if (pending && !this.ctx.pendingTools.has(newId)) {
329
+ this.ctx.pendingTools.delete(oldId);
330
+ this.ctx.pendingTools.set(newId, pending);
331
+ }
332
+ const timeline = this.#toolTimelineComponents.get(oldId);
333
+ if (timeline && !this.#toolTimelineComponents.has(newId)) {
334
+ this.#toolTimelineComponents.delete(oldId);
335
+ this.#toolTimelineComponents.set(newId, timeline);
336
+ }
337
+ // The reveal controller is id-keyed; drop the stale target so the loop's
338
+ // setTarget/bind under the new id owns the paced reveal.
339
+ this.#toolArgsReveal.finish(oldId);
340
+ const readArgs = this.#readToolCallArgs.get(oldId);
341
+ if (readArgs !== undefined) {
342
+ this.#readToolCallArgs.delete(oldId);
343
+ this.#readToolCallArgs.set(newId, readArgs);
344
+ }
345
+ const readAssistant = this.#readToolCallAssistantComponents.get(oldId);
346
+ if (readAssistant !== undefined) {
347
+ this.#readToolCallAssistantComponents.delete(oldId);
348
+ this.#readToolCallAssistantComponents.set(newId, readAssistant);
349
+ }
350
+ // A collapsed read renders into a shared group keyed by id; rename its
351
+ // entry so the row isn't duplicated under the new id.
352
+ if (pending instanceof ReadToolGroupComponent) pending.renameEntry(oldId, newId);
353
+ // A server-resolved completion (Cursor/todo) can land under `newId` while
354
+ // the card was still keyed by `oldId`, so it was parked in
355
+ // `#orphanedToolCompletions` instead of settling. Now that the card owns
356
+ // `newId`, apply the held result — the normal creation path that consumes
357
+ // held completions is skipped on a re-key (Codex review on #6881).
358
+ if (pending) {
359
+ const orphan = this.#orphanedToolCompletions.get(newId);
360
+ if (orphan) {
361
+ this.#orphanedToolCompletions.delete(newId);
362
+ this.#settleHeldCompletion(pending, orphan);
363
+ }
364
+ }
365
+ }
366
+
277
367
  #inlineReadToolImages(
278
368
  toolCallId: string,
279
369
  result: { content: Array<{ type: string; data?: string; mimeType?: string }> },
@@ -351,6 +441,9 @@ export class EventController {
351
441
  this.#renderedCustomMessages.clear();
352
442
  this.#lastIntent = undefined;
353
443
  this.#toolTimelineComponents.clear();
444
+ this.#streamedToolCallIdByIndex.clear();
445
+ this.#retractedToolCallIds.clear();
446
+ this.#syntheticFailureCards.clear();
354
447
  this.#orphanedToolCompletions.clear();
355
448
  this.#postToolAssistantComponents.clear();
356
449
  this.#backgroundTaskCallIds.clear();
@@ -440,6 +533,9 @@ export class EventController {
440
533
 
441
534
  async #handleAgentStart(_event: Extract<AgentSessionEvent, { type: "agent_start" }>): Promise<void> {
442
535
  this.#toolTimelineComponents.clear();
536
+ this.#streamedToolCallIdByIndex.clear();
537
+ this.#retractedToolCallIds.clear();
538
+ this.#syntheticFailureCards.clear();
443
539
  this.#orphanedToolCompletions.clear();
444
540
  this.#postToolAssistantComponents.clear();
445
541
  this.#lastIntent = undefined;
@@ -539,6 +635,7 @@ export class EventController {
539
635
  this.ctx.ui.requestRender();
540
636
  } else if (event.message.role === "assistant") {
541
637
  this.#lastVisibleBlockCount = 0;
638
+ this.#streamedToolCallIdByIndex.clear();
542
639
  this.ctx.streamingComponent = createAssistantMessageComponent(this.ctx);
543
640
  this.ctx.streamingMessage = event.message;
544
641
  this.ctx.chatContainer.addChild(this.ctx.streamingComponent);
@@ -750,8 +847,17 @@ export class EventController {
750
847
  if (this.ctx.streamingMessage.content.some(content => content.type === "toolCall")) {
751
848
  this.ctx.streamingComponent.markTranscriptBlockFinalized();
752
849
  }
753
- for (const content of this.ctx.streamingMessage.content) {
850
+ for (let contentIndex = 0; contentIndex < this.ctx.streamingMessage.content.length; contentIndex++) {
851
+ const content = this.ctx.streamingMessage.content[contentIndex]!;
754
852
  if (content.type !== "toolCall") continue;
853
+ // Re-key the live card when a provider rewrites this block's id
854
+ // across deltas, so the changed id reuses the existing card
855
+ // instead of spawning a duplicate (#6879).
856
+ const priorId = this.#streamedToolCallIdByIndex.get(contentIndex);
857
+ if (priorId !== undefined && priorId !== content.id) {
858
+ this.#migrateStreamedToolCallId(priorId, content.id);
859
+ }
860
+ this.#streamedToolCallIdByIndex.set(contentIndex, content.id);
755
861
  if (content.name === "read") {
756
862
  if (!readArgsHaveTarget(content.arguments)) {
757
863
  // Args still streaming — defer until path is parseable so we can route to the
@@ -822,6 +928,7 @@ export class EventController {
822
928
  content.id,
823
929
  );
824
930
  component.setExpanded(this.ctx.toolOutputExpanded);
931
+ component.setToolActivityVisible(!this.ctx.hideToolActivity);
825
932
  this.ctx.chatContainer.addChild(component);
826
933
  this.ctx.pendingTools.set(content.id, component);
827
934
  this.#toolTimelineComponents.set(content.id, component);
@@ -929,17 +1036,40 @@ export class EventController {
929
1036
  component.setArgsComplete(toolCallId);
930
1037
  }
931
1038
  } else {
932
- // The turn ended without running these calls (abort/error/TTSR rewind),
933
- // so they will never produce a result. Seal them so they stop animating
934
- // and freeze instead of pinning the transcript live region while a retry
935
- // streams fresh blocks below them. Background task calls keep updating.
936
- for (const [toolCallId, component] of this.ctx.pendingTools.entries()) {
937
- if (!this.#backgroundTaskCallIds.has(toolCallId) && component instanceof ToolExecutionComponent) {
938
- component.seal();
1039
+ // The turn ended without running these calls. What happens next
1040
+ // decides whether their cards should vanish or stay:
1041
+ // TTSR rewind known NOW via `isTtsrAbortPending` re-runs the
1042
+ // turn and re-streams fresh cards, so retract the uncommitted
1043
+ // never-run cards here and swallow the synthetic completions
1044
+ // agent-loop emits for them, else the call renders twice (#6879).
1045
+ // • A plain terminal error/abort, or an auto-retry (whose
1046
+ // supersession is only known later at `auto_retry_start`), must
1047
+ // NOT be retracted here: agent-loop emits a synthetic
1048
+ // `tool_execution_end` right after this that settles each card
1049
+ // into a visible aborted/error result. Leave them so the terminal
1050
+ // failure stays visible; `#handleAutoRetryStart` removes them only
1051
+ // if a retry actually supersedes them (Codex review on #6881).
1052
+ const supersededByRewind =
1053
+ this.ctx.streamingMessage.stopReason === "aborted" && this.ctx.viewSession.isTtsrAbortPending;
1054
+ if (supersededByRewind) {
1055
+ for (const [toolCallId, component] of Array.from(this.ctx.pendingTools.entries())) {
1056
+ if (this.#backgroundTaskCallIds.has(toolCallId)) continue;
1057
+ if (
1058
+ !(component instanceof ToolExecutionComponent) &&
1059
+ !(component instanceof ReadToolGroupComponent)
1060
+ ) {
1061
+ continue;
1062
+ }
1063
+ if (this.ctx.chatContainer.isBlockUncommitted(component)) {
1064
+ this.#retractToolCardEntry(toolCallId, component);
1065
+ this.#retractedToolCallIds.add(toolCallId);
1066
+ } else {
1067
+ component.seal();
1068
+ }
939
1069
  }
940
1070
  }
941
- // These calls will never produce a result either, so the tracked
942
- // waiting poll cannot be displaced anymore — freeze it in place.
1071
+ // These calls will never run this attempt, so the tracked waiting
1072
+ // poll cannot be displaced anymore — freeze it in place.
943
1073
  this.#resolveDisplaceablePoll();
944
1074
  }
945
1075
  // Surface a prompt-cache invalidation: if the previous turn cached a
@@ -1005,6 +1135,7 @@ export class EventController {
1005
1135
  }
1006
1136
 
1007
1137
  async #handleToolExecutionStart(event: Extract<AgentSessionEvent, { type: "tool_execution_start" }>): Promise<void> {
1138
+ if (this.#retractedToolCallIds.has(event.toolCallId)) return;
1008
1139
  this.#ensureWorkingLoaderWhileStreaming();
1009
1140
  this.#updateWorkingMessageFromIntent(event.intent);
1010
1141
  if (event.toolName === "ask" || this.#toolWillPromptForApproval(event.toolName, event.args)) {
@@ -1047,6 +1178,7 @@ export class EventController {
1047
1178
  event.toolCallId,
1048
1179
  );
1049
1180
  component.setExpanded(this.ctx.toolOutputExpanded);
1181
+ component.setToolActivityVisible(!this.ctx.hideToolActivity);
1050
1182
  this.ctx.chatContainer.addChild(component);
1051
1183
  this.ctx.pendingTools.set(event.toolCallId, component);
1052
1184
  this.#toolTimelineComponents.set(event.toolCallId, component);
@@ -1157,6 +1289,23 @@ export class EventController {
1157
1289
  }
1158
1290
 
1159
1291
  async #handleToolExecutionEnd(event: Extract<AgentSessionEvent, { type: "tool_execution_end" }>): Promise<void> {
1292
+ // `createAbortedToolResult` emits start/end after an error/aborted
1293
+ // assistant message. The matching card was deliberately retracted at
1294
+ // message_end; consume the completion instead of recreating/updating UI.
1295
+ if (this.#retractedToolCallIds.delete(event.toolCallId)) return;
1296
+ // A synthetic aborted/error completion (agent-loop's placeholder for a
1297
+ // never-run call on a terminal error/abort) settles the card in place so a
1298
+ // terminal failure stays visible. Remember it so `#handleAutoRetryStart`
1299
+ // can remove it if an auto-retry then supersedes the turn (Codex review on
1300
+ // #6881). Capture the live component now — the settle path below drops it
1301
+ // from `pendingTools` but leaves it in the transcript.
1302
+ const syntheticFailureDetails = event.result.details as { __synthetic?: boolean; source?: string } | undefined;
1303
+ const syntheticFailureCard =
1304
+ syntheticFailureDetails?.__synthetic === true &&
1305
+ (syntheticFailureDetails.source === "assistant_stop_error" ||
1306
+ syntheticFailureDetails.source === "assistant_stop_aborted")
1307
+ ? this.ctx.pendingTools.get(event.toolCallId)
1308
+ : undefined;
1160
1309
  // A transient overlay (auto-compaction / auto-retry / handoff) that ran
1161
1310
  // between this tool's start and end could have detached the working
1162
1311
  // loader. `tool_execution_update` already reconciles this so the spinner
@@ -1187,6 +1336,17 @@ export class EventController {
1187
1336
  } else {
1188
1337
  let component = this.ctx.pendingTools.get(event.toolCallId);
1189
1338
  if (!component) {
1339
+ // A persisted result can win a mid-stream transcript rebuild
1340
+ // before this live completion handler runs. Rebuild removes the
1341
+ // pending handle and replay owns the completed card, but the
1342
+ // original timeline entry remains as proof that a card already
1343
+ // existed. Do not create a fallback read group beside replay
1344
+ // (#6879); the fallback is only for a completion that genuinely
1345
+ // outran every streamed card.
1346
+ if (this.#toolTimelineComponents.has(event.toolCallId)) {
1347
+ this.#clearReadToolCall(event.toolCallId);
1348
+ return;
1349
+ }
1190
1350
  const group = this.#getReadGroup();
1191
1351
  const args = this.#readToolCallArgs.get(event.toolCallId);
1192
1352
  if (args) {
@@ -1243,6 +1403,7 @@ export class EventController {
1243
1403
  this.#orphanedToolCompletions.set(event.toolCallId, event);
1244
1404
  }
1245
1405
  }
1406
+ if (syntheticFailureCard) this.#syntheticFailureCards.set(event.toolCallId, syntheticFailureCard);
1246
1407
  // Update todo display when todo tool completes
1247
1408
  if (event.toolName === "todo" && !event.isError) {
1248
1409
  const details = event.result.details as { phases?: TodoPhase[] } | undefined;
@@ -1306,6 +1467,20 @@ export class EventController {
1306
1467
  // the loader and finalizes it at its own agent_end (isStreaming === false by
1307
1468
  // then). Mirrors the collab guest's !isStreaming loader reconciler.
1308
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
+ }
1309
1484
  setTerminalTitleState("idle");
1310
1485
 
1311
1486
  await this.#finishAgentEnd(event);
@@ -1349,6 +1524,9 @@ export class EventController {
1349
1524
  this.#readToolCallArgs.clear();
1350
1525
  this.#readToolCallAssistantComponents.clear();
1351
1526
  this.#toolTimelineComponents.clear();
1527
+ this.#streamedToolCallIdByIndex.clear();
1528
+ this.#retractedToolCallIds.clear();
1529
+ this.#syntheticFailureCards.clear();
1352
1530
  this.#orphanedToolCompletions.clear();
1353
1531
  this.#postToolAssistantComponents.clear();
1354
1532
  this.#resetReadGroup();
@@ -1522,6 +1700,17 @@ export class EventController {
1522
1700
  async #handleAutoRetryStart(event: Extract<AgentSessionEvent, { type: "auto_retry_start" }>): Promise<void> {
1523
1701
  this.#retryPending = true;
1524
1702
  this.#trackRetrySupersededAssistantComponent(this.#lastAssistantComponent);
1703
+ // A retry supersedes the just-failed turn: its assistant + tool calls are
1704
+ // pruned from context and re-streamed. Remove the cards that a synthetic
1705
+ // aborted/error completion settled in place at message_end so the retry's
1706
+ // fresh cards don't render the same call twice (#6879). Only uncommitted
1707
+ // cards are removable; one already on the scrollback tape stays as history.
1708
+ for (const [toolCallId, component] of this.#syntheticFailureCards) {
1709
+ if (this.ctx.chatContainer.isBlockUncommitted(component)) {
1710
+ this.#retractToolCardEntry(toolCallId, component);
1711
+ }
1712
+ }
1713
+ this.#syntheticFailureCards.clear();
1525
1714
  this.#stopWorkingLoader();
1526
1715
  this.ctx.statusContainer.disposeChildren();
1527
1716
  if (AIError.is(event.errorId, AIError.Flag.ThinkingLoop)) {
@@ -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";
@@ -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"));
@@ -1845,9 +1853,39 @@ export class InputController {
1845
1853
  }
1846
1854
 
1847
1855
  toggleToolOutputExpansion(): void {
1856
+ if (this.ctx.hideToolActivity) {
1857
+ const visibilityKey = this.ctx.keybindings.getDisplayString("app.tools.toggleVisibility");
1858
+ const visibilityHint = visibilityKey ? `${visibilityKey} or /settings` : "/settings";
1859
+ this.ctx.showStatus(`Tool activity is hidden — show it with ${visibilityHint} before expanding`);
1860
+ return;
1861
+ }
1848
1862
  this.setToolsExpanded(!this.ctx.toolOutputExpanded);
1849
1863
  }
1850
1864
 
1865
+ toggleToolActivityVisibility(): void {
1866
+ this.ctx.hideToolActivity = !this.ctx.hideToolActivity;
1867
+ this.ctx.settings.set("display.hideToolActivity", this.ctx.hideToolActivity);
1868
+
1869
+ if (!this.ctx.hideToolActivity) {
1870
+ this.ctx.toolOutputExpanded = false;
1871
+ }
1872
+
1873
+ for (const child of this.ctx.chatContainer.children) {
1874
+ if (child instanceof ToolExecutionComponent || child instanceof ReadToolGroupComponent) {
1875
+ if (!this.ctx.hideToolActivity) child.setExpanded(false);
1876
+ child.setToolActivityVisible(!this.ctx.hideToolActivity);
1877
+ } else if (child instanceof AssistantMessageComponent) {
1878
+ child.setToolResultImagesVisible(!this.ctx.hideToolActivity);
1879
+ } else if (child instanceof StrippedToolCallsPlaceholder) {
1880
+ child.setToolActivityVisible(!this.ctx.hideToolActivity);
1881
+ }
1882
+ }
1883
+
1884
+ if (this.ctx.hideToolActivity) this.ctx.ui.clearInlineImages();
1885
+ this.ctx.ui.resetDisplay();
1886
+ this.ctx.showStatus(`Tool activity: ${this.ctx.hideToolActivity ? "hidden" : "visible"}`);
1887
+ }
1888
+
1851
1889
  setToolsExpanded(expanded: boolean): void {
1852
1890
  this.ctx.toolOutputExpanded = expanded;
1853
1891
  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"));
@@ -1,4 +1,5 @@
1
1
  import { type AgentToolResult, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
2
+ import type { CompactionOutcome } from "@oh-my-pi/pi-agent-core/compaction";
2
3
  import { PASTE_CODE_LOGIN_PROVIDERS } from "@oh-my-pi/pi-ai";
3
4
  import { getOAuthProviders } from "@oh-my-pi/pi-ai/oauth";
4
5
  import type { OAuthProvider } from "@oh-my-pi/pi-ai/oauth/types";
@@ -91,11 +92,13 @@ import { ModelHubComponent, type ModelRoleSelectionScope } from "../components/m
91
92
  import { ModelPickerComponent } from "../components/model-picker";
92
93
  import { OAuthSelectorComponent } from "../components/oauth-selector";
93
94
  import { PluginSelectorComponent } from "../components/plugin-selector";
95
+ import { ReadToolGroupComponent } from "../components/read-tool-group";
94
96
  import { ResetUsageSelectorComponent } from "../components/reset-usage-selector";
95
97
  import { renderSegmentTrack } from "../components/segment-track";
96
98
  import { SessionAccountSelectorComponent } from "../components/session-account-selector";
97
99
  import { SessionSelectorComponent, type SessionSelectorOptions } from "../components/session-selector";
98
100
  import { SettingsSelectorComponent } from "../components/settings-selector";
101
+ import { StrippedToolCallsPlaceholder } from "../components/stripped-tool-calls-placeholder";
99
102
  import { ToolExecutionComponent } from "../components/tool-execution";
100
103
  import { TranscriptBlock } from "../components/transcript-container";
101
104
  import { TreeSelectorComponent } from "../components/tree-selector";
@@ -473,6 +476,24 @@ export class SelectorController {
473
476
  break;
474
477
 
475
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
+ }
476
497
  case "terminal.showImages":
477
498
  case "showImages": {
478
499
  const visible = value as boolean;
@@ -696,15 +717,38 @@ export class SelectorController {
696
717
  this.ctx.session.modelRegistry,
697
718
  this.ctx.session.scopedModels,
698
719
  {
699
- onPick: async (model, selector) => {
700
- try {
701
- // Session-only: update agent state but don't persist the model to settings.
720
+ onPick: async (model, selector, { overContext }) => {
721
+ // Session-only: update agent state but don't persist the model to settings.
722
+ const applySessionModel = async () => {
702
723
  const roleThinkingLevel = this.ctx.session.resolveTemporaryModelThinkingLevel(model);
703
724
  await this.ctx.session.setModelTemporary(model, roleThinkingLevel);
704
725
  this.ctx.statusLine.invalidate();
705
726
  this.ctx.updateEditorBorderColor();
706
727
  const roleSelectorHint = this.ctx.keybindings.getKeys("app.model.select")[0] ?? "Alt+M";
707
728
  this.ctx.showStatus(`Session-only model: ${selector}. Use ${roleSelectorHint} or /model for roles.`);
729
+ };
730
+ try {
731
+ if (overContext) {
732
+ // Over-context pick: close the picker so the compaction loader is
733
+ // visible, compact with the current model, then switch. The switch
734
+ // runs in the before-flush hook so any prompt queued during
735
+ // compaction executes on the target model, not the old one; the
736
+ // early "nothing to compact" return skips the hook, so the
737
+ // idempotent post-return call covers it. A cancelled or failed
738
+ // compaction keeps the current model — the target still cannot
739
+ // fit the transcript.
740
+ done();
741
+ let switched = false;
742
+ const switchAfterCompaction = async (outcome: CompactionOutcome) => {
743
+ if (switched || outcome !== "ok") return;
744
+ switched = true;
745
+ await applySessionModel();
746
+ };
747
+ const outcome = await this.ctx.handleCompactCommand(undefined, undefined, switchAfterCompaction);
748
+ await switchAfterCompaction(outcome);
749
+ return;
750
+ }
751
+ await applySessionModel();
708
752
  done();
709
753
  } catch (error) {
710
754
  this.ctx.showError(error instanceof Error ? error.message : String(error));
@@ -753,7 +797,6 @@ export class SelectorController {
753
797
  * entry — used when reopening the hub after a /login round-trip.
754
798
  */
755
799
  #showModelHub(hubOptions: { initialProviderId?: string }): void {
756
- const currentContextTokens = this.ctx.session.getContextUsage()?.tokens ?? 0;
757
800
  let overlayHandle: OverlayHandle | undefined;
758
801
  let hub: ModelHubComponent | undefined;
759
802
  let closed = false;
@@ -821,7 +864,6 @@ export class SelectorController {
821
864
  selector,
822
865
  thinkingLevel: isAuto ? ThinkingLevel.Inherit : concreteThinking,
823
866
  persist: targetScope === "global",
824
- currentContextTokens,
825
867
  });
826
868
  if (!switched) return;
827
869
  if (targetScope === "project") {
@@ -922,7 +964,6 @@ export class SelectorController {
922
964
  thinkingLevel: effectiveIsAuto
923
965
  ? ThinkingLevel.Inherit
924
966
  : (concreteThinking ?? ThinkingLevel.Inherit),
925
- currentContextTokens,
926
967
  });
927
968
  if (!switched) return;
928
969
  if (effectiveIsAuto) {
@@ -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
 
@@ -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;
@@ -355,7 +356,11 @@ export interface InteractiveModeContext {
355
356
  handlePythonCommand(code: string, excludeFromContext?: boolean): Promise<void>;
356
357
  handleMCPCommand(text: string): Promise<void>;
357
358
  handleSSHCommand(text: string): Promise<void>;
358
- handleCompactCommand(customInstructions?: string, mode?: CompactMode): Promise<CompactionOutcome>;
359
+ handleCompactCommand(
360
+ customInstructions?: string,
361
+ mode?: CompactMode,
362
+ beforeFlush?: (outcome: CompactionOutcome) => void | Promise<void>,
363
+ ): Promise<CompactionOutcome>;
359
364
  handleHandoffCommand(customInstructions?: string): Promise<void>;
360
365
  handleShakeCommand(mode: ShakeMode): Promise<void>;
361
366
  handleMoveCommand(targetPath?: string): Promise<void>;
@@ -46,6 +46,7 @@ export function buildHotkeysMarkdown(bindings: HotkeysMarkdownBindings): string
46
46
  `| \`${appKey(bindings, "app.plan.toggle")}\` | Toggle plan mode |`,
47
47
  `| \`${appKey(bindings, "app.history.search")}\` | Search prompt history |`,
48
48
  `| \`${appKey(bindings, "app.tools.expand")}\` | Toggle tool output expansion |`,
49
+ `| \`${appKey(bindings, "app.tools.toggleVisibility")}\` | Toggle tool activity visibility |`,
49
50
  `| \`${appKey(bindings, "app.thinking.toggle")}\` | Toggle thinking block visibility |`,
50
51
  `| \`${appKey(bindings, "app.editor.external")}\` | Edit message in external editor |`,
51
52
  `| \`${appKey(bindings, "app.retry")}\` | Retry last failed assistant turn |`,
@@ -25,6 +25,7 @@ export function createAssistantMessageComponent(
25
25
  ctx.proseOnlyThinking,
26
26
  );
27
27
  component.setImagesVisible(ctx.settings.get("terminal.showImages"));
28
+ component.setToolResultImagesVisible(!ctx.hideToolActivity);
28
29
  component.setExpanded(ctx.toolOutputExpanded);
29
30
  return component;
30
31
  }