@oh-my-pi/pi-coding-agent 17.0.8 → 17.1.0

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 (340) hide show
  1. package/CHANGELOG.md +87 -1
  2. package/README.md +1 -1
  3. package/dist/cli.js +5495 -5021
  4. package/dist/types/async/job-manager.d.ts +37 -1
  5. package/dist/types/cli/args.d.ts +2 -0
  6. package/dist/types/cli/bench-cli.d.ts +41 -0
  7. package/dist/types/commands/bench.d.ts +15 -3
  8. package/dist/types/commands/launch.d.ts +4 -0
  9. package/dist/types/config/__tests__/model-registry.test.d.ts +1 -0
  10. package/dist/types/config/model-registry.d.ts +30 -1
  11. package/dist/types/config/models-config-schema.d.ts +18 -6
  12. package/dist/types/config/models-config.d.ts +14 -5
  13. package/dist/types/config/provider-globals.d.ts +2 -2
  14. package/dist/types/config/service-tier.d.ts +5 -1
  15. package/dist/types/config/settings-schema.d.ts +350 -50
  16. package/dist/types/config/settings.d.ts +7 -0
  17. package/dist/types/dap/client.d.ts +20 -0
  18. package/dist/types/exec/bash-executor.d.ts +38 -0
  19. package/dist/types/exec/direnv.d.ts +33 -0
  20. package/dist/types/export/html/args.d.ts +15 -0
  21. package/dist/types/export/html/index.d.ts +11 -26
  22. package/dist/types/export/html/web-palette.d.ts +55 -116
  23. package/dist/types/extensibility/extensions/loader.d.ts +3 -0
  24. package/dist/types/extensibility/extensions/types.d.ts +17 -2
  25. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -0
  26. package/dist/types/extensibility/legacy-pi-tui-shim.d.ts +10 -0
  27. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +14 -1
  28. package/dist/types/internal-urls/registry-helpers.d.ts +11 -0
  29. package/dist/types/live/controller.d.ts +40 -0
  30. package/dist/types/live/protocol.d.ts +85 -0
  31. package/dist/types/live/protocol.test.d.ts +1 -0
  32. package/dist/types/live/transport.d.ts +48 -0
  33. package/dist/types/live/visualizer.d.ts +33 -0
  34. package/dist/types/lsp/client.d.ts +18 -1
  35. package/dist/types/mcp/config-writer.test.d.ts +1 -0
  36. package/dist/types/mcp/manager.d.ts +6 -2
  37. package/dist/types/mcp/oauth-discovery.d.ts +2 -0
  38. package/dist/types/mcp/render.d.ts +2 -2
  39. package/dist/types/mcp/smithery-auth.d.ts +1 -1
  40. package/dist/types/mcp/smithery-auth.test.d.ts +1 -0
  41. package/dist/types/mcp/tool-bridge.d.ts +7 -3
  42. package/dist/types/mcp/types.d.ts +6 -0
  43. package/dist/types/memory-backend/tool-names.d.ts +2 -0
  44. package/dist/types/modes/components/login-dialog.d.ts +2 -2
  45. package/dist/types/modes/components/oauth-selector.d.ts +8 -0
  46. package/dist/types/modes/components/plan-review-overlay.d.ts +26 -0
  47. package/dist/types/modes/components/settings-defs.d.ts +8 -1
  48. package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +1 -0
  49. package/dist/types/modes/components/tool-execution.d.ts +9 -1
  50. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  51. package/dist/types/modes/controllers/event-controller.d.ts +6 -1
  52. package/dist/types/modes/controllers/live-command-controller.d.ts +14 -0
  53. package/dist/types/modes/controllers/mcp-command-controller.d.ts +3 -0
  54. package/dist/types/modes/interactive-mode.d.ts +5 -0
  55. package/dist/types/modes/rpc/rpc-client.d.ts +10 -1
  56. package/dist/types/modes/rpc/rpc-frame.d.ts +16 -0
  57. package/dist/types/modes/rpc/rpc-messages.d.ts +26 -0
  58. package/dist/types/modes/rpc/rpc-types.d.ts +40 -0
  59. package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +1 -1
  60. package/dist/types/modes/setup-wizard/scenes/types.d.ts +9 -1
  61. package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +1 -1
  62. package/dist/types/modes/theme/theme.d.ts +1 -1
  63. package/dist/types/modes/types.d.ts +3 -1
  64. package/dist/types/modes/utils/context-usage.d.ts +12 -2
  65. package/dist/types/plan-mode/plan-files.d.ts +10 -0
  66. package/dist/types/registry/agent-lifecycle.d.ts +21 -9
  67. package/dist/types/registry/agent-registry.d.ts +11 -4
  68. package/dist/types/sdk.d.ts +15 -1
  69. package/dist/types/secrets/index.d.ts +15 -2
  70. package/dist/types/secrets/obfuscator.d.ts +113 -15
  71. package/dist/types/session/acp-permission-gate.d.ts +19 -0
  72. package/dist/types/session/agent-session-events.d.ts +84 -0
  73. package/dist/types/session/agent-session-types.d.ts +314 -0
  74. package/dist/types/session/agent-session.d.ts +120 -750
  75. package/dist/types/session/async-job-delivery.d.ts +28 -0
  76. package/dist/types/session/bash-runner.d.ts +75 -0
  77. package/dist/types/session/blob-store.d.ts +11 -1
  78. package/dist/types/session/blob-store.test.d.ts +1 -0
  79. package/dist/types/session/checkpoint-entries.d.ts +28 -0
  80. package/dist/types/session/eval-runner.d.ts +49 -0
  81. package/dist/types/session/irc-bridge.d.ts +46 -0
  82. package/dist/types/session/messages.d.ts +22 -0
  83. package/dist/types/session/model-controls.d.ts +180 -0
  84. package/dist/types/session/prewalk.d.ts +46 -0
  85. package/dist/types/session/queued-messages.d.ts +22 -0
  86. package/dist/types/session/redis-session-storage.d.ts +1 -0
  87. package/dist/types/session/retry-fallback-chains.d.ts +74 -0
  88. package/dist/types/session/role-models.d.ts +15 -0
  89. package/dist/types/session/session-advisors.d.ts +228 -0
  90. package/dist/types/session/session-entries.d.ts +8 -0
  91. package/dist/types/session/session-handoff.d.ts +70 -0
  92. package/dist/types/session/session-history-format.d.ts +6 -1
  93. package/dist/types/session/session-loader.d.ts +6 -4
  94. package/dist/types/session/session-maintenance.d.ts +246 -0
  95. package/dist/types/session/session-manager.d.ts +41 -0
  96. package/dist/types/session/session-memory.d.ts +56 -0
  97. package/dist/types/session/session-provider-boundary.d.ts +62 -0
  98. package/dist/types/session/session-stats.d.ts +48 -0
  99. package/dist/types/session/session-tools.d.ts +143 -0
  100. package/dist/types/session/session-workspace.d.ts +32 -0
  101. package/dist/types/session/stream-guards.d.ts +44 -0
  102. package/dist/types/session/todo-tracker.d.ts +56 -0
  103. package/dist/types/session/ttsr-coordinator.d.ts +51 -0
  104. package/dist/types/session/turn-recovery.d.ts +225 -0
  105. package/dist/types/system-prompt.d.ts +2 -0
  106. package/dist/types/task/executor.d.ts +4 -1
  107. package/dist/types/task/output-manager.d.ts +3 -1
  108. package/dist/types/task/types.d.ts +14 -0
  109. package/dist/types/tools/approval.d.ts +1 -0
  110. package/dist/types/tools/browser/launch.d.ts +5 -0
  111. package/dist/types/tools/hub/jobs.d.ts +1 -1
  112. package/dist/types/tools/image-gen.d.ts +4 -4
  113. package/dist/types/tools/image-providers.d.ts +38 -0
  114. package/dist/types/tools/index.d.ts +8 -0
  115. package/dist/types/tools/path-utils.d.ts +8 -0
  116. package/dist/types/tools/report-tool-issue.d.ts +24 -9
  117. package/dist/types/tools/todo.d.ts +10 -4
  118. package/dist/types/tools/xdev.d.ts +5 -1
  119. package/dist/types/tts/streaming-player.d.ts +14 -0
  120. package/dist/types/tts/vocalizer.d.ts +5 -0
  121. package/dist/types/utils/jj.d.ts +29 -0
  122. package/dist/types/utils/lang-from-path.d.ts +1 -0
  123. package/dist/types/utils/title-generator.d.ts +30 -0
  124. package/dist/types/vibe/runtime.d.ts +36 -8
  125. package/dist/types/web/search/index.d.ts +1 -1
  126. package/dist/types/web/search/provider.d.ts +15 -5
  127. package/dist/types/web/search/providers/firecrawl.d.ts +9 -0
  128. package/dist/types/web/search/types.d.ts +95 -1
  129. package/package.json +13 -12
  130. package/scripts/generate-share-viewer.ts +4 -6
  131. package/scripts/legacy-pi-virtual-module.ts +1 -1
  132. package/src/advisor/__tests__/advisor.test.ts +643 -0
  133. package/src/advisor/runtime.ts +144 -43
  134. package/src/async/job-manager.ts +90 -2
  135. package/src/cli/args.ts +2 -0
  136. package/src/cli/auth-gateway-cli.ts +18 -3
  137. package/src/cli/bench-cli.ts +395 -29
  138. package/src/cli/flag-tables.ts +3 -0
  139. package/src/cli/gc-cli.ts +2 -2
  140. package/src/cli/grievances-cli.ts +2 -2
  141. package/src/cli/usage-cli.ts +11 -2
  142. package/src/collab/host.ts +3 -2
  143. package/src/commands/bench.ts +18 -4
  144. package/src/commands/launch.ts +4 -0
  145. package/src/config/__tests__/model-registry.test.ts +147 -0
  146. package/src/config/model-registry.ts +50 -2
  147. package/src/config/model-resolver.ts +0 -1
  148. package/src/config/models-config-schema.ts +15 -4
  149. package/src/config/provider-globals.ts +9 -9
  150. package/src/config/service-tier.ts +26 -1
  151. package/src/config/settings-schema.ts +250 -54
  152. package/src/config/settings.ts +81 -2
  153. package/src/dap/client.ts +37 -4
  154. package/src/edit/index.ts +49 -12
  155. package/src/eval/py/executor.ts +103 -33
  156. package/src/exec/bash-executor.ts +89 -6
  157. package/src/exec/direnv.ts +145 -0
  158. package/src/export/html/args.ts +20 -0
  159. package/src/export/html/index.ts +45 -47
  160. package/src/export/html/template.css +19 -1
  161. package/src/export/html/template.html +6 -0
  162. package/src/export/html/template.js +21 -0
  163. package/src/export/html/tool-views.generated.js +31 -31
  164. package/src/export/html/web-palette.ts +116 -132
  165. package/src/export/share.ts +249 -49
  166. package/src/extensibility/extensions/loader.ts +29 -1
  167. package/src/extensibility/extensions/runner.ts +6 -0
  168. package/src/extensibility/extensions/types.ts +33 -2
  169. package/src/extensibility/legacy-pi-coding-agent-shim.ts +1 -0
  170. package/src/extensibility/legacy-pi-tui-shim.ts +10 -0
  171. package/src/extensibility/plugins/legacy-pi-compat.ts +870 -311
  172. package/src/hindsight/state.ts +64 -6
  173. package/src/internal-urls/local-protocol.ts +2 -1
  174. package/src/internal-urls/memory-protocol.ts +2 -2
  175. package/src/internal-urls/registry-helpers.ts +40 -0
  176. package/src/internal-urls/skill-protocol.ts +2 -2
  177. package/src/internal-urls/ssh-protocol.ts +2 -1
  178. package/src/internal-urls/vault-protocol.ts +2 -1
  179. package/src/live/audio-worklet.txt +59 -0
  180. package/src/live/browser-runtime.txt +221 -0
  181. package/src/live/controller.ts +464 -0
  182. package/src/live/prompts/agent-final-message.md +3 -0
  183. package/src/live/prompts/live-instructions.md +23 -0
  184. package/src/live/protocol.test.ts +140 -0
  185. package/src/live/protocol.ts +233 -0
  186. package/src/live/transport.ts +490 -0
  187. package/src/live/visualizer.ts +234 -0
  188. package/src/lsp/client.ts +52 -9
  189. package/src/lsp/clients/biome-client.ts +3 -4
  190. package/src/lsp/index.ts +44 -11
  191. package/src/main.ts +9 -1
  192. package/src/mcp/config-writer.test.ts +43 -0
  193. package/src/mcp/config-writer.ts +109 -82
  194. package/src/mcp/manager.ts +39 -8
  195. package/src/mcp/oauth-discovery.ts +10 -2
  196. package/src/mcp/render.ts +94 -35
  197. package/src/mcp/smithery-auth.test.ts +29 -0
  198. package/src/mcp/smithery-auth.ts +3 -2
  199. package/src/mcp/tool-bridge.ts +107 -11
  200. package/src/mcp/types.ts +7 -0
  201. package/src/memories/index.ts +40 -20
  202. package/src/memory-backend/tool-names.ts +2 -0
  203. package/src/mnemopi/backend.ts +24 -7
  204. package/src/modes/acp/acp-agent.ts +56 -10
  205. package/src/modes/acp/acp-event-mapper.ts +8 -1
  206. package/src/modes/components/agent-hub.ts +27 -10
  207. package/src/modes/components/login-dialog.ts +14 -4
  208. package/src/modes/components/oauth-selector.ts +24 -7
  209. package/src/modes/components/plan-review-overlay.ts +350 -35
  210. package/src/modes/components/settings-defs.ts +28 -3
  211. package/src/modes/components/settings-selector.ts +178 -6
  212. package/src/modes/components/status-line/component.jj-cache.test.ts +229 -0
  213. package/src/modes/components/status-line/component.ts +123 -2
  214. package/src/modes/components/tool-execution.test.ts +63 -2
  215. package/src/modes/components/tool-execution.ts +11 -2
  216. package/src/modes/controllers/command-controller.ts +70 -22
  217. package/src/modes/controllers/event-controller.ts +111 -16
  218. package/src/modes/controllers/extension-ui-controller.test.ts +1 -0
  219. package/src/modes/controllers/extension-ui-controller.ts +20 -4
  220. package/src/modes/controllers/live-command-controller.ts +178 -0
  221. package/src/modes/controllers/mcp-command-controller.ts +80 -41
  222. package/src/modes/controllers/omfg-controller.ts +44 -40
  223. package/src/modes/controllers/selector-controller.ts +18 -10
  224. package/src/modes/controllers/session-focus-controller.ts +6 -1
  225. package/src/modes/interactive-mode.ts +160 -38
  226. package/src/modes/rpc/rpc-client.ts +94 -3
  227. package/src/modes/rpc/rpc-frame.ts +164 -4
  228. package/src/modes/rpc/rpc-messages.ts +127 -0
  229. package/src/modes/rpc/rpc-mode.ts +79 -7
  230. package/src/modes/rpc/rpc-types.ts +34 -2
  231. package/src/modes/runtime-init.ts +2 -0
  232. package/src/modes/setup-wizard/scenes/model.ts +5 -7
  233. package/src/modes/setup-wizard/scenes/providers.ts +3 -2
  234. package/src/modes/setup-wizard/scenes/sign-in.ts +8 -2
  235. package/src/modes/setup-wizard/scenes/theme.ts +13 -3
  236. package/src/modes/setup-wizard/scenes/types.ts +9 -1
  237. package/src/modes/setup-wizard/scenes/web-search.ts +23 -10
  238. package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
  239. package/src/modes/theme/theme.ts +1 -1
  240. package/src/modes/types.ts +3 -1
  241. package/src/modes/utils/context-usage.ts +23 -7
  242. package/src/modes/utils/ui-helpers.ts +10 -5
  243. package/src/plan-mode/plan-files.ts +40 -0
  244. package/src/prompts/bench/cache-prefix-chunk.md +1 -0
  245. package/src/prompts/bench/cache-prefix.md +3 -0
  246. package/src/prompts/bench/cache-suffix.md +1 -0
  247. package/src/prompts/goals/guided-goal-system.md +24 -3
  248. package/src/prompts/system/custom-system-prompt.md +1 -1
  249. package/src/prompts/system/project-prompt.md +8 -1
  250. package/src/prompts/system/subagent-async-pending.md +6 -0
  251. package/src/prompts/system/system-prompt.md +1 -1
  252. package/src/prompts/system/workflow-notice.md +48 -40
  253. package/src/prompts/system/xdev-mount-notice.md +4 -0
  254. package/src/prompts/tools/task.md +12 -2
  255. package/src/prompts/tools/todo.md +3 -1
  256. package/src/registry/agent-lifecycle.ts +116 -46
  257. package/src/registry/agent-registry.ts +35 -8
  258. package/src/registry/persisted-agents.ts +26 -2
  259. package/src/sdk.ts +255 -139
  260. package/src/secrets/index.ts +127 -4
  261. package/src/secrets/obfuscator.ts +2328 -105
  262. package/src/session/acp-permission-gate.ts +165 -0
  263. package/src/session/agent-session-error-log.test.ts +1 -1
  264. package/src/session/agent-session-events.ts +66 -0
  265. package/src/session/agent-session-types.ts +334 -0
  266. package/src/session/agent-session.ts +1910 -12213
  267. package/src/session/artifacts.ts +6 -5
  268. package/src/session/async-job-delivery.ts +74 -0
  269. package/src/session/bash-runner.ts +326 -0
  270. package/src/session/blob-store.test.ts +56 -0
  271. package/src/session/blob-store.ts +18 -2
  272. package/src/session/checkpoint-entries.ts +81 -0
  273. package/src/session/eval-runner.ts +212 -0
  274. package/src/session/indexed-session-storage.ts +9 -2
  275. package/src/session/irc-bridge.ts +203 -0
  276. package/src/session/messages.ts +263 -1
  277. package/src/session/model-controls.ts +714 -0
  278. package/src/session/prewalk.ts +252 -0
  279. package/src/session/queued-messages.ts +93 -0
  280. package/src/session/redis-session-storage.ts +51 -11
  281. package/src/session/retry-fallback-chains.ts +455 -0
  282. package/src/session/role-models.ts +85 -0
  283. package/src/session/session-advisors.ts +1679 -0
  284. package/src/session/session-context.test.ts +224 -1
  285. package/src/session/session-context.ts +47 -5
  286. package/src/session/session-entries.ts +8 -0
  287. package/src/session/session-handoff.ts +308 -0
  288. package/src/session/session-history-format.ts +20 -9
  289. package/src/session/session-loader.ts +10 -51
  290. package/src/session/session-maintenance.ts +2983 -0
  291. package/src/session/session-manager.ts +390 -30
  292. package/src/session/session-memory.ts +222 -0
  293. package/src/session/session-provider-boundary.ts +307 -0
  294. package/src/session/session-stats.ts +293 -0
  295. package/src/session/session-tools.ts +943 -0
  296. package/src/session/session-workspace.ts +53 -0
  297. package/src/session/stream-guards.ts +417 -0
  298. package/src/session/todo-tracker.ts +380 -0
  299. package/src/session/ttsr-coordinator.ts +496 -0
  300. package/src/session/turn-recovery.ts +1629 -0
  301. package/src/slash-commands/builtin-registry.ts +92 -11
  302. package/src/slash-commands/helpers/usage-report.ts +25 -4
  303. package/src/system-prompt.ts +41 -17
  304. package/src/task/executor.ts +288 -70
  305. package/src/task/index.ts +148 -111
  306. package/src/task/output-manager.ts +25 -3
  307. package/src/task/persisted-revive.ts +4 -3
  308. package/src/task/structured-subagent.ts +4 -1
  309. package/src/task/types.ts +16 -0
  310. package/src/tools/approval.ts +55 -10
  311. package/src/tools/bash-interactive.ts +26 -0
  312. package/src/tools/bash-skill-urls.ts +28 -2
  313. package/src/tools/bash.ts +339 -123
  314. package/src/tools/browser/launch.ts +9 -1
  315. package/src/tools/browser/tab-supervisor.ts +8 -5
  316. package/src/tools/browser.ts +94 -47
  317. package/src/tools/hub/index.ts +1 -1
  318. package/src/tools/hub/jobs.ts +67 -8
  319. package/src/tools/image-gen.ts +20 -28
  320. package/src/tools/image-providers.ts +50 -0
  321. package/src/tools/index.ts +12 -0
  322. package/src/tools/path-utils.ts +49 -2
  323. package/src/tools/read.ts +156 -88
  324. package/src/tools/renderers.ts +7 -1
  325. package/src/tools/report-tool-issue.ts +79 -28
  326. package/src/tools/todo.ts +101 -11
  327. package/src/tools/vibe.ts +1 -2
  328. package/src/tools/write.ts +51 -1
  329. package/src/tools/xdev.ts +67 -3
  330. package/src/tts/streaming-player.ts +62 -4
  331. package/src/tts/vocalizer.ts +18 -1
  332. package/src/utils/jj.ts +125 -4
  333. package/src/utils/lang-from-path.ts +7 -0
  334. package/src/utils/title-generator.ts +137 -1
  335. package/src/vibe/runtime.ts +852 -72
  336. package/src/web/search/index.ts +3 -6
  337. package/src/web/search/provider.ts +31 -16
  338. package/src/web/search/providers/firecrawl.ts +46 -13
  339. package/src/web/search/providers/xai.ts +7 -1
  340. package/src/web/search/types.ts +8 -1
@@ -0,0 +1,178 @@
1
+ import { logger } from "@oh-my-pi/pi-utils";
2
+ import { LiveSessionController } from "../../live/controller";
3
+ import { LiveVisualizer } from "../../live/visualizer";
4
+ import { vocalizer } from "../../tts/vocalizer";
5
+ import type { CustomEditor } from "../components/custom-editor";
6
+ import type { InteractiveModeContext } from "../types";
7
+
8
+ const ANIMATION_INTERVAL_MS = 80;
9
+
10
+ function errorFrom(cause: unknown): Error {
11
+ return cause instanceof Error ? cause : new Error(String(cause));
12
+ }
13
+
14
+ /** Owns the editor-replacing visualizer and realtime session lifecycle for `/live`. */
15
+ export class LiveCommandController {
16
+ readonly #ctx: InteractiveModeContext;
17
+
18
+ #session: LiveSessionController | undefined;
19
+ #settling: Promise<void> | undefined;
20
+ #visualizer: LiveVisualizer | undefined;
21
+ #detachedEditor: CustomEditor | undefined;
22
+ #animationInterval: NodeJS.Timeout | undefined;
23
+ #previousShowHardwareCursor: boolean | undefined;
24
+ #previousUseTerminalCursor: boolean | undefined;
25
+ #resumeVocalizer: (() => void) | undefined;
26
+
27
+ constructor(ctx: InteractiveModeContext) {
28
+ this.#ctx = ctx;
29
+ }
30
+
31
+ /** Whether a live session is connected, connecting, or closing. */
32
+ get active(): boolean {
33
+ return this.#session !== undefined || this.#settling !== undefined;
34
+ }
35
+
36
+ /** Start live mode, or stop the currently active session. */
37
+ async handleCommand(): Promise<void> {
38
+ if (this.#session) {
39
+ await this.stop();
40
+ return;
41
+ }
42
+ if (this.#settling) await this.#settling;
43
+ await this.#start();
44
+ }
45
+
46
+ /** Stop the active live session and restore the editor. */
47
+ async stop(): Promise<void> {
48
+ const session = this.#session;
49
+ if (!session) {
50
+ if (this.#settling) await this.#settling;
51
+ return;
52
+ }
53
+ try {
54
+ await session.stop();
55
+ } catch (cause) {
56
+ this.#finish(session, errorFrom(cause));
57
+ } finally {
58
+ this.#finish(session);
59
+ }
60
+ }
61
+
62
+ /** Release UI resources during synchronous InteractiveMode teardown. */
63
+ dispose(): void {
64
+ const session = this.#session;
65
+ if (session) {
66
+ this.#finish(session);
67
+ void session.stop().catch(cause => {
68
+ logger.debug("Live session teardown failed", { error: errorFrom(cause).message });
69
+ });
70
+ } else {
71
+ this.#restoreEditor();
72
+ }
73
+ }
74
+
75
+ async #start(): Promise<void> {
76
+ const visualizer = new LiveVisualizer({
77
+ onStop: () => {
78
+ void this.stop().catch(cause => this.#ctx.showError(errorFrom(cause).message));
79
+ },
80
+ onToggleMute: () => this.#session?.toggleMute(),
81
+ });
82
+ this.#mountVisualizer(visualizer);
83
+
84
+ let session: LiveSessionController;
85
+ session = new LiveSessionController({
86
+ session: this.#ctx.session,
87
+ extractAssistantText: message => this.#ctx.extractAssistantText(message),
88
+ callbacks: {
89
+ onPhase: phase => {
90
+ if (this.#visualizer !== visualizer) return;
91
+ visualizer.setPhase(phase);
92
+ this.#ctx.ui.requestComponentRender(visualizer);
93
+ },
94
+ onLevels: (input, output) => {
95
+ if (this.#visualizer !== visualizer) return;
96
+ visualizer.setLevels(input, output);
97
+ this.#ctx.ui.requestComponentRender(visualizer);
98
+ },
99
+ onTranscript: transcript => {
100
+ if (this.#visualizer !== visualizer) return;
101
+ visualizer.setTranscript(transcript);
102
+ this.#ctx.ui.requestComponentRender(visualizer);
103
+ },
104
+ onTerminal: error => this.#finish(session, error),
105
+ },
106
+ });
107
+ this.#session = session;
108
+
109
+ try {
110
+ await session.start();
111
+ } catch (cause) {
112
+ if (this.#session === session) {
113
+ await session.stop();
114
+ this.#finish(session, errorFrom(cause));
115
+ }
116
+ }
117
+ }
118
+
119
+ #mountVisualizer(visualizer: LiveVisualizer): void {
120
+ this.#visualizer = visualizer;
121
+ this.#detachedEditor = this.#ctx.editor;
122
+ this.#previousShowHardwareCursor = this.#ctx.ui.getShowHardwareCursor();
123
+ this.#previousUseTerminalCursor = this.#ctx.editor.getUseTerminalCursor();
124
+ this.#ctx.ui.setShowHardwareCursor(false);
125
+ this.#ctx.editor.setUseTerminalCursor(false);
126
+ this.#ctx.editorContainer.clear();
127
+ this.#ctx.editorContainer.addChild(visualizer);
128
+ this.#ctx.ui.setFocus(visualizer);
129
+ this.#resumeVocalizer = vocalizer.suspend();
130
+ let frame = 0;
131
+ this.#animationInterval = setInterval(() => {
132
+ if (this.#visualizer !== visualizer) return;
133
+ frame += 1;
134
+ visualizer.setFrame(frame);
135
+ this.#ctx.ui.requestComponentRender(visualizer);
136
+ }, ANIMATION_INTERVAL_MS);
137
+ this.#ctx.ui.requestRender();
138
+ }
139
+
140
+ #finish(session: LiveSessionController, error?: Error): void {
141
+ if (this.#session !== session) return;
142
+ this.#session = undefined;
143
+ this.#restoreEditor();
144
+ if (error) this.#ctx.showError(error.message);
145
+ const settling = session.stop().catch(cause => {
146
+ logger.debug("Live session cleanup failed", { error: errorFrom(cause).message });
147
+ });
148
+ this.#settling = settling;
149
+ void settling.finally(() => {
150
+ if (this.#settling === settling) this.#settling = undefined;
151
+ });
152
+ }
153
+
154
+ #restoreEditor(): void {
155
+ if (this.#animationInterval) {
156
+ clearInterval(this.#animationInterval);
157
+ this.#animationInterval = undefined;
158
+ }
159
+ this.#resumeVocalizer?.();
160
+ this.#resumeVocalizer = undefined;
161
+ const editor = this.#detachedEditor;
162
+ this.#detachedEditor = undefined;
163
+ this.#visualizer = undefined;
164
+ if (!editor) return;
165
+ this.#ctx.editorContainer.clear();
166
+ this.#ctx.editorContainer.addChild(editor);
167
+ if (this.#previousShowHardwareCursor !== undefined) {
168
+ this.#ctx.ui.setShowHardwareCursor(this.#previousShowHardwareCursor);
169
+ }
170
+ if (this.#previousUseTerminalCursor !== undefined) {
171
+ editor.setUseTerminalCursor(this.#previousUseTerminalCursor);
172
+ }
173
+ this.#previousShowHardwareCursor = undefined;
174
+ this.#previousUseTerminalCursor = undefined;
175
+ this.#ctx.ui.setFocus(editor);
176
+ this.#ctx.ui.requestRender();
177
+ }
178
+ }
@@ -38,6 +38,7 @@ import {
38
38
  getSmitheryApiKey,
39
39
  getSmitheryLoginUrl,
40
40
  pollSmitheryCliAuthSession,
41
+ type SmitheryCliPollResponse,
41
42
  saveSmitheryApiKey,
42
43
  } from "../../mcp/smithery-auth";
43
44
  import { SmitheryConnectError } from "../../mcp/smithery-connect";
@@ -47,10 +48,11 @@ import {
47
48
  searchSmitheryRegistry,
48
49
  toConfigName,
49
50
  } from "../../mcp/smithery-registry";
50
- import type { MCPAuthConfig, MCPServerConfig, MCPServerConnection } from "../../mcp/types";
51
+ import type { MCPAuthChallenge, MCPAuthConfig, MCPServerConfig, MCPServerConnection } from "../../mcp/types";
51
52
  import { shortenPath } from "../../tools/render-utils";
52
53
  import { urlHyperlinkAlways } from "../../tui";
53
54
  import { copyToClipboard } from "../../utils/clipboard";
55
+ import { isTimeoutError } from "../../utils/fetch-timeout";
54
56
  import { openPath } from "../../utils/open";
55
57
  import { ChatBlock } from "../components/chat-block";
56
58
  import { MCPAddWizard } from "../components/mcp-add-wizard";
@@ -1042,7 +1044,10 @@ export class MCPCommandController {
1042
1044
  return next;
1043
1045
  }
1044
1046
 
1045
- async #resolveOAuthEndpointsFromServer(config: MCPServerConfig): Promise<OAuthEndpoints> {
1047
+ async #resolveOAuthEndpointsFromServer(
1048
+ config: MCPServerConfig,
1049
+ authChallenge?: MCPAuthChallenge,
1050
+ ): Promise<OAuthEndpoints> {
1046
1051
  // Stdio servers manage credentials inside the child process; OMP's OAuth
1047
1052
  // flow only applies to http/sse transports. Without this guard the
1048
1053
  // unauthenticated preflight below spawns the child, which happily reuses
@@ -1068,13 +1073,20 @@ export class MCPCommandController {
1068
1073
  connectionError = error as Error;
1069
1074
  }
1070
1075
 
1071
- // Server connected fine without auth — reauth is not needed
1072
- if (connectionSucceeded) {
1076
+ // Server connected fine without auth — reauth is not needed. A tool-level
1077
+ // challenge overrides this: servers may allow the anonymous handshake yet
1078
+ // protect individual tool calls with `_meta["mcp/www_authenticate"]`.
1079
+ if (connectionSucceeded && !authChallenge) {
1073
1080
  throw new Error("Server connection succeeded without OAuth; reauthorization is not required.");
1074
1081
  }
1075
1082
 
1076
- // Analyze the connection error to extract OAuth endpoints
1077
- const authResult = analyzeAuthError(connectionError!, "url" in config ? config.url : undefined);
1083
+ // Tool calls can carry richer RFC 6750/RFC 9728 hints than the original
1084
+ // connection error. Feed those hints through the same analyzer so
1085
+ // resource_metadata and scope reach protected-resource discovery.
1086
+ const authError = authChallenge
1087
+ ? new Error(`${connectionError?.message ?? "HTTP 401"}\n${authChallenge.wwwAuthenticate.join("\n")}`)
1088
+ : connectionError!;
1089
+ const authResult = analyzeAuthError(authError, "url" in config ? config.url : undefined);
1078
1090
  let oauth = authResult.authType === "oauth" ? (authResult.oauth ?? null) : null;
1079
1091
 
1080
1092
  if (!oauth && (config.type === "http" || config.type === "sse") && config.url) {
@@ -1678,21 +1690,29 @@ export class MCPCommandController {
1678
1690
  }
1679
1691
  }
1680
1692
 
1681
- async #handleReauth(name: string | undefined): Promise<void> {
1693
+ /** Reauthorize a server after a tool-level OAuth challenge. */
1694
+ async handleMCPAuthChallenge(name: string, challenge: MCPAuthChallenge): Promise<MCPServerConfig | undefined> {
1695
+ return this.#handleReauth(name, { silent: true, reload: false, authChallenge: challenge });
1696
+ }
1697
+
1698
+ async #handleReauth(
1699
+ name: string | undefined,
1700
+ options: { silent?: boolean; reload?: boolean; authChallenge?: MCPAuthChallenge } = {},
1701
+ ): Promise<MCPServerConfig | undefined> {
1682
1702
  if (!name) {
1683
- this.ctx.showError("Server name required. Usage: /mcp reauth <name>");
1703
+ if (!options.silent) this.ctx.showError("Server name required. Usage: /mcp reauth <name>");
1684
1704
  return;
1685
1705
  }
1686
1706
 
1687
1707
  try {
1688
1708
  const found = await this.#resolveServerForAuth(name);
1689
1709
  if (!found) {
1690
- this.ctx.showError(`Server "${name}" not found.`);
1710
+ if (!options.silent) this.ctx.showError(`Server "${name}" not found.`);
1691
1711
  return;
1692
1712
  }
1693
1713
 
1694
1714
  if (found.config.enabled === false) {
1695
- this.ctx.showError(`Server "${name}" is disabled. Run /mcp enable ${name} first.`);
1715
+ if (!options.silent) this.ctx.showError(`Server "${name}" is disabled. Run /mcp enable ${name} first.`);
1696
1716
  return;
1697
1717
  }
1698
1718
 
@@ -1705,7 +1725,7 @@ export class MCPCommandController {
1705
1725
  // happened yet if the server turns out not to need (or support) OAuth.
1706
1726
  // Use the same env-expanded config shape runtime discovery passes to
1707
1727
  // MCPManager; the raw file value may contain `${...}` placeholders.
1708
- const oauth = await this.#resolveOAuthEndpointsFromServer(runtimeBaseConfig);
1728
+ const oauth = await this.#resolveOAuthEndpointsFromServer(runtimeBaseConfig, options.authChallenge);
1709
1729
  const serverUrl =
1710
1730
  runtimeBaseConfig.type === "http" || runtimeBaseConfig.type === "sse" ? runtimeBaseConfig.url : undefined;
1711
1731
  // A user-supplied client secret may live in either block (the wizard
@@ -1719,7 +1739,9 @@ export class MCPCommandController {
1719
1739
  const userClientSecret = found.config.oauth?.clientSecret ?? currentAuth?.clientSecret;
1720
1740
  const flowClientSecret = userClientSecret ?? storedClientSecret ?? "";
1721
1741
 
1722
- this.#showMessage(["", theme.fg("muted", `Reauthorizing "${name}"...`), ""].join("\n"));
1742
+ if (!options.silent) {
1743
+ this.#showMessage(["", theme.fg("muted", `Reauthorizing "${name}"...`), ""].join("\n"));
1744
+ }
1723
1745
 
1724
1746
  const currentAuthResource = currentAuth?.resource ? expandEnvVarsDeep(currentAuth.resource) : undefined;
1725
1747
  const oauthResource =
@@ -1755,39 +1777,49 @@ export class MCPCommandController {
1755
1777
  // Definition-only entries resolve through the url-keyed binding alone;
1756
1778
  // skip the write-back so a committed project mcp.json stays clean.
1757
1779
  const urlKeyedId = serverUrl ? mcpOAuthCredentialId(serverUrl) : undefined;
1758
- if (currentAuth || oauthResult.credentialId !== urlKeyedId) {
1759
- const updated = this.#persistOAuthResult(baseConfig, oauthResult, {
1760
- tokenUrl: oauth.tokenUrl,
1761
- clientId: oauth.clientId,
1762
- userClientSecret,
1763
- resource: oauthResource,
1764
- stripSameOriginResource: oauthResourceIsFallback,
1765
- });
1766
- await updateMCPServer(found.filePath, name, updated);
1767
- }
1768
- await this.#reloadMCP();
1769
- const state = await this.#waitForServerConnectionWithAnimation(name);
1780
+ const shouldPersist = currentAuth || oauthResult.credentialId !== urlKeyedId;
1781
+ const updatedConfig = shouldPersist
1782
+ ? this.#persistOAuthResult(baseConfig, oauthResult, {
1783
+ tokenUrl: oauth.tokenUrl,
1784
+ clientId: oauth.clientId,
1785
+ userClientSecret,
1786
+ resource: oauthResource,
1787
+ stripSameOriginResource: oauthResourceIsFallback,
1788
+ })
1789
+ : baseConfig;
1790
+ if (shouldPersist) {
1791
+ await updateMCPServer(found.filePath, name, updatedConfig);
1792
+ }
1793
+ if (options.reload !== false) {
1794
+ await this.#reloadMCP();
1795
+ const state = await this.#waitForServerConnectionWithAnimation(name);
1770
1796
 
1771
- const lines = [
1772
- "",
1773
- theme.fg("success", `✓ Reauthorized "${name}" (${found.scope} config)`),
1774
- "",
1775
- ` Status: ${
1776
- state === "connected"
1777
- ? theme.fg("success", "connected")
1778
- : state === "connecting"
1779
- ? theme.fg("muted", "connecting")
1780
- : theme.fg("warning", "not connected")
1781
- }`,
1782
- "",
1783
- ];
1784
- this.#showMessage(lines.join("\n"));
1797
+ const lines = [
1798
+ "",
1799
+ theme.fg("success", `✓ Reauthorized "${name}" (${found.scope} config)`),
1800
+ "",
1801
+ ` Status: ${
1802
+ state === "connected"
1803
+ ? theme.fg("success", "connected")
1804
+ : state === "connecting"
1805
+ ? theme.fg("muted", "connecting")
1806
+ : theme.fg("warning", "not connected")
1807
+ }`,
1808
+ "",
1809
+ ];
1810
+ this.#showMessage(lines.join("\n"));
1811
+ }
1812
+ return updatedConfig;
1785
1813
  } catch (error) {
1786
1814
  if (error instanceof MCPOAuthCancelledError) {
1787
- this.ctx.showStatus(`Reauthorization cancelled for "${name}"`);
1815
+ if (!options.silent) this.ctx.showStatus(`Reauthorization cancelled for "${name}"`);
1788
1816
  return;
1789
1817
  }
1790
- this.ctx.showError(`Failed to reauthorize server: ${error instanceof Error ? error.message : String(error)}`);
1818
+ if (!options.silent) {
1819
+ this.ctx.showError(
1820
+ `Failed to reauthorize server: ${error instanceof Error ? error.message : String(error)}`,
1821
+ );
1822
+ }
1791
1823
  }
1792
1824
  }
1793
1825
 
@@ -2091,7 +2123,14 @@ export class MCPCommandController {
2091
2123
  if (Date.now() - startedAt >= timeoutMs) {
2092
2124
  throw new Error("Smithery authorization timed out after 5 minutes.");
2093
2125
  }
2094
- const response = await pollSmitheryCliAuthSession(sessionId, signal);
2126
+ let response: SmitheryCliPollResponse;
2127
+ try {
2128
+ response = await pollSmitheryCliAuthSession(sessionId, signal);
2129
+ } catch (error) {
2130
+ // A single hung/slow poll aborts with TimeoutError; retry until the deadline.
2131
+ if (isTimeoutError(error)) continue;
2132
+ throw error;
2133
+ }
2095
2134
  if (response.status === "success" && response.apiKey) {
2096
2135
  return response.apiKey;
2097
2136
  }
@@ -192,56 +192,60 @@ export class OmfgController {
192
192
 
193
193
  async #saveCandidate(request: OmfgRequest, candidate: OmfgCandidate): Promise<SaveCandidateResult> {
194
194
  if (this.#shouldStop(request)) return { kind: "aborted" };
195
- request.component.setStatus("saving", "Choose where to save or amend the TTSR rule…");
196
- const location = await this.ctx.showHookSelector("Save TTSR rule where?", [
197
- PROJECT_OPTION,
198
- GLOBAL_OPTION,
199
- AMEND_OPTION,
200
- ]);
201
- if (!this.#isActiveRequest(request)) return { kind: "aborted" };
202
- if (!location) {
203
- request.component.markAborted();
204
- this.#closeActiveRequest({ abort: false });
205
- return { kind: "aborted" };
206
- }
207
195
 
208
- if (location === AMEND_OPTION) {
209
- request.component.setStatus("confirming", "Describe how to amend the rule…");
210
- const amendment = await this.ctx.showHookInput(
211
- "Amend TTSR rule",
212
- "e.g. Make it specific to Ruby string eval in tool:write(*.rb)",
213
- );
196
+ for (;;) {
197
+ request.component.setStatus("saving", "Choose where to save or amend the TTSR rule…");
198
+ const location = await this.ctx.showHookSelector("Save TTSR rule where?", [
199
+ PROJECT_OPTION,
200
+ GLOBAL_OPTION,
201
+ AMEND_OPTION,
202
+ ]);
214
203
  if (!this.#isActiveRequest(request)) return { kind: "aborted" };
215
- const feedback = amendment?.trim();
216
- if (!feedback) {
204
+ if (!location) {
217
205
  request.component.markAborted();
218
206
  this.#closeActiveRequest({ abort: false });
219
207
  return { kind: "aborted" };
220
208
  }
221
- return { kind: "amend", feedback };
222
- }
223
209
 
224
- const target = this.#resolveTarget(location, candidate.rule.name);
225
- if (await Bun.file(target.filePath).exists()) {
226
- const shouldOverwrite = await this.ctx.showHookConfirm(
227
- "Overwrite TTSR rule?",
228
- `${shortenPath(target.filePath)} already exists. Overwrite it?`,
229
- );
230
- if (!this.#isActiveRequest(request)) return { kind: "aborted" };
231
- if (!shouldOverwrite) {
232
- request.component.markRejected();
233
- return { kind: "rejected" };
210
+ if (location === AMEND_OPTION) {
211
+ request.component.setStatus("confirming", "Describe how to amend the rule…");
212
+ const amendment = await this.ctx.showHookInput(
213
+ "Amend TTSR rule",
214
+ "e.g. Make it specific to Ruby string eval in tool:write(*.rb)",
215
+ );
216
+ if (!this.#isActiveRequest(request)) return { kind: "aborted" };
217
+ const feedback = amendment?.trim();
218
+ if (!feedback) continue;
219
+ return { kind: "amend", feedback };
234
220
  }
235
- }
236
221
 
237
- request.component.setStatus("saving", `Saving ${candidate.rule.name}…`);
238
- await Bun.write(target.filePath, candidate.fileContent);
239
- if (!this.#isActiveRequest(request)) return { kind: "aborted" };
222
+ const target = this.#resolveTarget(location, candidate.rule.name);
223
+ if (await Bun.file(target.filePath).exists()) {
224
+ const shouldOverwrite = await this.ctx.showHookConfirm(
225
+ "Overwrite TTSR rule?",
226
+ `${shortenPath(target.filePath)} already exists. Overwrite it?`,
227
+ );
228
+ if (!this.#isActiveRequest(request)) return { kind: "aborted" };
229
+ if (!shouldOverwrite) {
230
+ request.component.markRejected();
231
+ return { kind: "rejected" };
232
+ }
233
+ }
240
234
 
241
- const savedRule = buildOmfgRuleForPath(candidate.rule.name, candidate.fileContent, target.filePath, target.level);
242
- this.#registerLive(savedRule);
243
- request.component.markSaved(shortenPath(target.filePath));
244
- return { kind: "saved" };
235
+ request.component.setStatus("saving", `Saving ${candidate.rule.name}…`);
236
+ await Bun.write(target.filePath, candidate.fileContent);
237
+ if (!this.#isActiveRequest(request)) return { kind: "aborted" };
238
+
239
+ const savedRule = buildOmfgRuleForPath(
240
+ candidate.rule.name,
241
+ candidate.fileContent,
242
+ target.filePath,
243
+ target.level,
244
+ );
245
+ this.#registerLive(savedRule);
246
+ request.component.markSaved(shortenPath(target.filePath));
247
+ return { kind: "saved" };
248
+ }
245
249
  }
246
250
 
247
251
  #resolveTarget(location: string, ruleName: string): { filePath: string; level: OmfgRuleSourceLevel } {
@@ -57,12 +57,10 @@ import {
57
57
  parseConfiguredThinkingLevel,
58
58
  } from "../../thinking";
59
59
  import {
60
- isImageProviderPreference,
61
60
  isSearchProviderId,
62
- isSearchProviderPreference,
63
61
  setExcludedSearchProviders,
64
- setPreferredImageProvider,
65
- setPreferredSearchProvider,
62
+ setImageProviderOrder,
63
+ setSearchProviderOrder,
66
64
  type ToolSession,
67
65
  } from "../../tools";
68
66
  import { AskTool, type AskToolDetails, type AskToolInput } from "../../tools/ask";
@@ -439,6 +437,16 @@ export class SelectorController {
439
437
  this.ctx.showError(`Failed to apply personality: ${err}`);
440
438
  });
441
439
  break;
440
+ case "tools.xdevDocs":
441
+ void this.ctx.session.refreshBaseSystemPrompt().catch(err => {
442
+ this.ctx.showError(`Failed to apply xd:// prompt docs setting: ${err}`);
443
+ });
444
+ break;
445
+ case "memory.backend":
446
+ void this.ctx.session.applyMemoryBackend().catch(err => {
447
+ this.ctx.showError(`Failed to apply memory backend: ${err}`);
448
+ });
449
+ break;
442
450
 
443
451
  case "autocompleteMaxVisible":
444
452
  this.ctx.editor.setAutocompleteMaxVisible(typeof value === "number" ? value : Number(value));
@@ -609,9 +617,9 @@ export class SelectorController {
609
617
  }
610
618
 
611
619
  // Provider settings - update runtime preferences
612
- case "providers.webSearch":
613
- if (typeof value === "string" && isSearchProviderPreference(value)) {
614
- setPreferredSearchProvider(value);
620
+ case "providers.webSearchOrder":
621
+ if (Array.isArray(value)) {
622
+ setSearchProviderOrder(value.filter(isSearchProviderId));
615
623
  }
616
624
  break;
617
625
  case "providers.webSearchExclude":
@@ -619,9 +627,9 @@ export class SelectorController {
619
627
  setExcludedSearchProviders(value.filter(isSearchProviderId));
620
628
  }
621
629
  break;
622
- case "providers.image":
623
- if (isImageProviderPreference(value)) {
624
- setPreferredImageProvider(value);
630
+ case "providers.imageOrder":
631
+ if (Array.isArray(value)) {
632
+ setImageProviderOrder(value.filter((entry): entry is string => typeof entry === "string"));
625
633
  }
626
634
  break;
627
635
 
@@ -12,6 +12,7 @@
12
12
  import { AgentLifecycleManager } from "../../registry/agent-lifecycle";
13
13
  import { AgentRegistry, MAIN_AGENT_ID, type RegistryEvent } from "../../registry/agent-registry";
14
14
  import type { AgentSession } from "../../session/agent-session";
15
+ import { setTerminalTitleState } from "../../utils/title-generator";
15
16
  import type { InteractiveModeContext } from "../types";
16
17
 
17
18
  export class SessionFocusController {
@@ -104,8 +105,12 @@ export class SessionFocusController {
104
105
  });
105
106
  this.ctx.statusLine.setSession(target, this.#focusedAgentId);
106
107
  this.ctx.renderInitialMessages({ clearTerminalHistory: true });
107
- // Mid-turn attach: no agent_start will arrive; arm the loader/turn state manually.
108
+ // Sync the run-state title to the attached target: a streaming target has no
109
+ // agent_start incoming, so arm the loader/working title manually; an idle
110
+ // target would otherwise inherit the previous session's stuck spinner, so
111
+ // reset it to idle (agent_end teardown already ran via clearTransientSessionUi).
108
112
  if (target.isStreaming) await this.ctx.eventController.handleEvent({ type: "agent_start" });
113
+ else setTerminalTitleState("idle");
109
114
  this.ctx.updateEditorBorderColor();
110
115
  this.ctx.ui.requestRender();
111
116
  }