@oh-my-pi/pi-coding-agent 17.0.9 → 17.1.1

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 (349) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +1 -1
  3. package/dist/cli.js +4685 -4614
  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/model-registry.d.ts +3 -1
  10. package/dist/types/config/models-config-schema.d.ts +18 -6
  11. package/dist/types/config/models-config.d.ts +14 -5
  12. package/dist/types/config/provider-globals.d.ts +2 -2
  13. package/dist/types/config/service-tier.d.ts +5 -1
  14. package/dist/types/config/settings-schema.d.ts +374 -47
  15. package/dist/types/config/settings.d.ts +7 -0
  16. package/dist/types/dap/client.d.ts +20 -0
  17. package/dist/types/exec/bash-executor.d.ts +38 -0
  18. package/dist/types/exec/direnv.d.ts +33 -0
  19. package/dist/types/export/html/args.d.ts +15 -0
  20. package/dist/types/export/html/index.d.ts +11 -26
  21. package/dist/types/export/html/web-palette.d.ts +55 -116
  22. package/dist/types/extensibility/extensions/loader.d.ts +3 -0
  23. package/dist/types/extensibility/extensions/types.d.ts +17 -2
  24. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +3 -0
  25. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +13 -0
  26. package/dist/types/live/attestation.d.ts +2 -0
  27. package/dist/types/live/controller.d.ts +48 -0
  28. package/dist/types/live/protocol.d.ts +85 -0
  29. package/dist/types/live/protocol.test.d.ts +1 -0
  30. package/dist/types/live/transport.d.ts +35 -0
  31. package/dist/types/live/visualizer.d.ts +30 -0
  32. package/dist/types/lsp/client.d.ts +16 -1
  33. package/dist/types/mcp/config-writer.test.d.ts +1 -0
  34. package/dist/types/mcp/oauth-discovery.d.ts +2 -0
  35. package/dist/types/mcp/smithery-auth.d.ts +1 -1
  36. package/dist/types/mcp/smithery-auth.test.d.ts +1 -0
  37. package/dist/types/memory-backend/tool-names.d.ts +2 -0
  38. package/dist/types/modes/components/assistant-message.d.ts +1 -0
  39. package/dist/types/modes/components/custom-message.d.ts +1 -1
  40. package/dist/types/modes/components/login-dialog.d.ts +2 -2
  41. package/dist/types/modes/components/message-frame.d.ts +8 -4
  42. package/dist/types/modes/components/plan-review-overlay.d.ts +26 -0
  43. package/dist/types/modes/components/session-account-selector.d.ts +11 -0
  44. package/dist/types/modes/components/settings-defs.d.ts +7 -1
  45. package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +1 -0
  46. package/dist/types/modes/components/tool-execution.d.ts +9 -1
  47. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  48. package/dist/types/modes/controllers/event-controller.d.ts +6 -1
  49. package/dist/types/modes/controllers/live-command-controller.d.ts +14 -0
  50. package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
  51. package/dist/types/modes/interactive-mode.d.ts +6 -0
  52. package/dist/types/modes/theme/theme.d.ts +1 -1
  53. package/dist/types/modes/types.d.ts +4 -1
  54. package/dist/types/modes/utils/context-usage.d.ts +12 -2
  55. package/dist/types/plan-mode/plan-files.d.ts +10 -0
  56. package/dist/types/registry/agent-lifecycle.d.ts +21 -9
  57. package/dist/types/registry/agent-registry.d.ts +11 -4
  58. package/dist/types/sdk.d.ts +15 -1
  59. package/dist/types/secrets/index.d.ts +15 -2
  60. package/dist/types/secrets/obfuscator.d.ts +113 -15
  61. package/dist/types/session/acp-permission-gate.d.ts +19 -0
  62. package/dist/types/session/agent-session-events.d.ts +84 -0
  63. package/dist/types/session/agent-session-types.d.ts +321 -0
  64. package/dist/types/session/agent-session.d.ts +135 -753
  65. package/dist/types/session/async-job-delivery.d.ts +28 -0
  66. package/dist/types/session/auth-storage.d.ts +1 -1
  67. package/dist/types/session/bash-runner.d.ts +75 -0
  68. package/dist/types/session/blob-store.d.ts +11 -1
  69. package/dist/types/session/blob-store.test.d.ts +1 -0
  70. package/dist/types/session/checkpoint-entries.d.ts +28 -0
  71. package/dist/types/session/eval-runner.d.ts +51 -0
  72. package/dist/types/session/irc-bridge.d.ts +46 -0
  73. package/dist/types/session/messages.d.ts +24 -0
  74. package/dist/types/session/model-controls.d.ts +180 -0
  75. package/dist/types/session/prewalk.d.ts +46 -0
  76. package/dist/types/session/queued-messages.d.ts +22 -0
  77. package/dist/types/session/redis-session-storage.d.ts +1 -0
  78. package/dist/types/session/retry-fallback-chains.d.ts +74 -0
  79. package/dist/types/session/role-models.d.ts +15 -0
  80. package/dist/types/session/session-advisors.d.ts +228 -0
  81. package/dist/types/session/session-entries.d.ts +8 -0
  82. package/dist/types/session/session-handoff.d.ts +70 -0
  83. package/dist/types/session/session-history-format.d.ts +6 -1
  84. package/dist/types/session/session-maintenance.d.ts +246 -0
  85. package/dist/types/session/session-manager.d.ts +41 -0
  86. package/dist/types/session/session-memory.d.ts +56 -0
  87. package/dist/types/session/session-provider-boundary.d.ts +62 -0
  88. package/dist/types/session/session-stats.d.ts +48 -0
  89. package/dist/types/session/session-tools.d.ts +158 -0
  90. package/dist/types/session/session-workspace.d.ts +32 -0
  91. package/dist/types/session/stream-guards.d.ts +44 -0
  92. package/dist/types/session/streaming-output.d.ts +8 -0
  93. package/dist/types/session/todo-tracker.d.ts +56 -0
  94. package/dist/types/session/ttsr-coordinator.d.ts +51 -0
  95. package/dist/types/session/turn-recovery.d.ts +225 -0
  96. package/dist/types/slash-commands/helpers/session-pin.d.ts +9 -0
  97. package/dist/types/stt/index.d.ts +0 -2
  98. package/dist/types/stt/stt-controller.d.ts +7 -0
  99. package/dist/types/system-prompt.d.ts +2 -0
  100. package/dist/types/task/executor.d.ts +4 -1
  101. package/dist/types/task/output-manager.d.ts +3 -1
  102. package/dist/types/tiny/title-client.d.ts +10 -0
  103. package/dist/types/tools/approval.d.ts +1 -0
  104. package/dist/types/tools/browser/launch.d.ts +5 -0
  105. package/dist/types/tools/builtin-names.d.ts +1 -1
  106. package/dist/types/tools/computer/protocol.d.ts +43 -0
  107. package/dist/types/tools/computer/supervisor.d.ts +32 -0
  108. package/dist/types/tools/computer/worker-entry.d.ts +1 -0
  109. package/dist/types/tools/computer/worker.d.ts +15 -0
  110. package/dist/types/tools/computer-renderer.d.ts +22 -0
  111. package/dist/types/tools/computer.d.ts +71 -0
  112. package/dist/types/tools/context.d.ts +2 -0
  113. package/dist/types/tools/default-renderer.d.ts +21 -0
  114. package/dist/types/tools/image-gen.d.ts +4 -4
  115. package/dist/types/tools/image-providers.d.ts +38 -0
  116. package/dist/types/tools/index.d.ts +7 -0
  117. package/dist/types/tools/path-utils.d.ts +8 -0
  118. package/dist/types/tools/todo.d.ts +10 -4
  119. package/dist/types/tools/xdev.d.ts +5 -1
  120. package/dist/types/tts/streaming-player.d.ts +10 -29
  121. package/dist/types/tts/vocalizer.d.ts +5 -0
  122. package/dist/types/utils/jj.d.ts +29 -0
  123. package/dist/types/utils/lang-from-path.d.ts +1 -0
  124. package/dist/types/utils/title-generator.d.ts +30 -0
  125. package/dist/types/utils/tools-manager.d.ts +1 -2
  126. package/dist/types/vibe/runtime.d.ts +36 -8
  127. package/dist/types/web/search/index.d.ts +1 -1
  128. package/dist/types/web/search/provider.d.ts +15 -5
  129. package/dist/types/web/search/types.d.ts +94 -0
  130. package/package.json +12 -12
  131. package/scripts/generate-share-viewer.ts +4 -6
  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 +3 -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/setup-cli.ts +2 -14
  141. package/src/cli/usage-cli.ts +10 -1
  142. package/src/cli.ts +8 -0
  143. package/src/collab/host.ts +3 -2
  144. package/src/commands/bench.ts +18 -4
  145. package/src/commands/launch.ts +4 -0
  146. package/src/config/model-registry.ts +16 -2
  147. package/src/config/models-config-schema.ts +15 -4
  148. package/src/config/provider-globals.ts +9 -9
  149. package/src/config/service-tier.ts +26 -1
  150. package/src/config/settings-schema.ts +270 -51
  151. package/src/config/settings.ts +81 -2
  152. package/src/dap/client.ts +37 -4
  153. package/src/edit/index.ts +49 -12
  154. package/src/eval/executor-base.ts +1 -0
  155. package/src/eval/js/executor.ts +2 -0
  156. package/src/eval/py/executor.ts +103 -33
  157. package/src/exec/bash-executor.ts +90 -6
  158. package/src/exec/direnv.ts +145 -0
  159. package/src/export/html/args.ts +20 -0
  160. package/src/export/html/index.ts +45 -47
  161. package/src/export/html/template.css +19 -1
  162. package/src/export/html/template.html +6 -0
  163. package/src/export/html/template.js +21 -0
  164. package/src/export/html/tool-views.generated.js +31 -31
  165. package/src/export/html/web-palette.ts +116 -132
  166. package/src/export/share.ts +249 -49
  167. package/src/extensibility/extensions/loader.ts +29 -1
  168. package/src/extensibility/extensions/runner.ts +6 -0
  169. package/src/extensibility/extensions/types.ts +33 -2
  170. package/src/extensibility/extensions/wrapper.ts +68 -12
  171. package/src/extensibility/legacy-pi-coding-agent-shim.ts +7 -1
  172. package/src/extensibility/plugins/legacy-pi-compat.ts +19 -1
  173. package/src/hindsight/state.ts +64 -6
  174. package/src/internal-urls/local-protocol.ts +2 -1
  175. package/src/internal-urls/memory-protocol.ts +2 -2
  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/attestation.ts +91 -0
  180. package/src/live/controller.ts +517 -0
  181. package/src/live/prompts/agent-final-message.md +3 -0
  182. package/src/live/prompts/live-instructions.md +23 -0
  183. package/src/live/protocol.test.ts +140 -0
  184. package/src/live/protocol.ts +233 -0
  185. package/src/live/transport.ts +422 -0
  186. package/src/live/visualizer.ts +214 -0
  187. package/src/lsp/client.ts +29 -9
  188. package/src/lsp/index.ts +19 -4
  189. package/src/main.ts +8 -1
  190. package/src/mcp/config-writer.test.ts +43 -0
  191. package/src/mcp/config-writer.ts +109 -82
  192. package/src/mcp/oauth-discovery.ts +10 -2
  193. package/src/mcp/smithery-auth.test.ts +29 -0
  194. package/src/mcp/smithery-auth.ts +3 -2
  195. package/src/memories/index.ts +40 -20
  196. package/src/memory-backend/tool-names.ts +2 -0
  197. package/src/mnemopi/backend.ts +24 -7
  198. package/src/modes/acp/acp-agent.ts +56 -10
  199. package/src/modes/acp/acp-event-mapper.ts +8 -1
  200. package/src/modes/components/agent-hub.ts +1 -1
  201. package/src/modes/components/assistant-message.ts +7 -2
  202. package/src/modes/components/custom-message.ts +4 -1
  203. package/src/modes/components/login-dialog.ts +14 -4
  204. package/src/modes/components/message-frame.ts +14 -8
  205. package/src/modes/components/plan-review-overlay.ts +350 -35
  206. package/src/modes/components/session-account-selector.ts +62 -0
  207. package/src/modes/components/settings-defs.ts +23 -1
  208. package/src/modes/components/settings-selector.ts +169 -1
  209. package/src/modes/components/status-line/component.jj-cache.test.ts +229 -0
  210. package/src/modes/components/status-line/component.ts +123 -2
  211. package/src/modes/components/tool-execution.ts +28 -112
  212. package/src/modes/controllers/command-controller.ts +56 -15
  213. package/src/modes/controllers/event-controller.ts +111 -16
  214. package/src/modes/controllers/extension-ui-controller.test.ts +1 -0
  215. package/src/modes/controllers/extension-ui-controller.ts +20 -4
  216. package/src/modes/controllers/input-controller.ts +47 -39
  217. package/src/modes/controllers/live-command-controller.ts +255 -0
  218. package/src/modes/controllers/mcp-command-controller.ts +10 -1
  219. package/src/modes/controllers/omfg-controller.ts +44 -40
  220. package/src/modes/controllers/selector-controller.ts +80 -10
  221. package/src/modes/controllers/session-focus-controller.ts +6 -1
  222. package/src/modes/interactive-mode.ts +176 -38
  223. package/src/modes/runtime-init.ts +2 -0
  224. package/src/modes/setup-wizard/scenes/web-search.ts +17 -9
  225. package/src/modes/theme/theme.ts +1 -1
  226. package/src/modes/types.ts +4 -1
  227. package/src/modes/utils/context-usage.ts +23 -7
  228. package/src/modes/utils/ui-helpers.ts +10 -5
  229. package/src/plan-mode/plan-files.ts +40 -0
  230. package/src/prompts/bench/cache-prefix-chunk.md +1 -0
  231. package/src/prompts/bench/cache-prefix.md +3 -0
  232. package/src/prompts/bench/cache-suffix.md +1 -0
  233. package/src/prompts/system/computer-safety.md +14 -0
  234. package/src/prompts/system/custom-system-prompt.md +1 -1
  235. package/src/prompts/system/project-prompt.md +8 -1
  236. package/src/prompts/system/subagent-async-pending.md +6 -0
  237. package/src/prompts/system/system-prompt.md +1 -1
  238. package/src/prompts/system/workflow-notice.md +48 -40
  239. package/src/prompts/system/xdev-mount-notice.md +4 -0
  240. package/src/prompts/tools/computer.md +26 -0
  241. package/src/prompts/tools/todo.md +3 -1
  242. package/src/registry/agent-lifecycle.ts +116 -46
  243. package/src/registry/agent-registry.ts +35 -8
  244. package/src/registry/persisted-agents.ts +26 -2
  245. package/src/sdk.ts +215 -136
  246. package/src/secrets/index.ts +127 -4
  247. package/src/secrets/obfuscator.ts +2328 -105
  248. package/src/session/acp-permission-gate.ts +165 -0
  249. package/src/session/agent-session-error-log.test.ts +1 -1
  250. package/src/session/agent-session-events.ts +66 -0
  251. package/src/session/agent-session-types.ts +343 -0
  252. package/src/session/agent-session.ts +1940 -12218
  253. package/src/session/artifacts.ts +6 -5
  254. package/src/session/async-job-delivery.ts +74 -0
  255. package/src/session/auth-storage.ts +1 -0
  256. package/src/session/bash-runner.ts +326 -0
  257. package/src/session/blob-store.test.ts +56 -0
  258. package/src/session/blob-store.ts +18 -2
  259. package/src/session/checkpoint-entries.ts +81 -0
  260. package/src/session/eval-runner.ts +217 -0
  261. package/src/session/indexed-session-storage.ts +9 -2
  262. package/src/session/irc-bridge.ts +203 -0
  263. package/src/session/messages.ts +266 -1
  264. package/src/session/model-controls.ts +714 -0
  265. package/src/session/prewalk.ts +252 -0
  266. package/src/session/queued-messages.ts +93 -0
  267. package/src/session/redis-session-storage.ts +51 -11
  268. package/src/session/retry-fallback-chains.ts +455 -0
  269. package/src/session/role-models.ts +85 -0
  270. package/src/session/session-advisors.ts +1679 -0
  271. package/src/session/session-context.ts +6 -3
  272. package/src/session/session-entries.ts +8 -0
  273. package/src/session/session-handoff.ts +308 -0
  274. package/src/session/session-history-format.ts +20 -9
  275. package/src/session/session-loader.ts +0 -46
  276. package/src/session/session-maintenance.ts +2983 -0
  277. package/src/session/session-manager.ts +390 -30
  278. package/src/session/session-memory.ts +222 -0
  279. package/src/session/session-provider-boundary.ts +307 -0
  280. package/src/session/session-stats.ts +293 -0
  281. package/src/session/session-tools.ts +980 -0
  282. package/src/session/session-workspace.ts +53 -0
  283. package/src/session/stream-guards.ts +417 -0
  284. package/src/session/streaming-output.ts +52 -5
  285. package/src/session/todo-tracker.ts +380 -0
  286. package/src/session/ttsr-coordinator.ts +496 -0
  287. package/src/session/turn-recovery.ts +1629 -0
  288. package/src/slash-commands/builtin-registry.ts +257 -20
  289. package/src/slash-commands/helpers/session-pin.ts +44 -0
  290. package/src/slash-commands/helpers/usage-report.ts +22 -3
  291. package/src/stt/downloader.ts +0 -2
  292. package/src/stt/index.ts +0 -2
  293. package/src/stt/stt-controller.ts +57 -146
  294. package/src/system-prompt.ts +45 -17
  295. package/src/task/executor.ts +285 -70
  296. package/src/task/index.ts +98 -105
  297. package/src/task/output-manager.ts +25 -3
  298. package/src/task/persisted-revive.ts +4 -3
  299. package/src/task/structured-subagent.ts +1 -0
  300. package/src/tiny/title-client.ts +22 -0
  301. package/src/tools/approval.ts +55 -10
  302. package/src/tools/bash-interactive.ts +116 -86
  303. package/src/tools/bash-skill-urls.ts +28 -2
  304. package/src/tools/bash.ts +339 -123
  305. package/src/tools/browser/launch.ts +9 -1
  306. package/src/tools/browser/tab-supervisor.ts +8 -5
  307. package/src/tools/browser.ts +94 -47
  308. package/src/tools/builtin-names.ts +1 -0
  309. package/src/tools/computer/protocol.ts +28 -0
  310. package/src/tools/computer/supervisor.ts +258 -0
  311. package/src/tools/computer/worker-entry.ts +25 -0
  312. package/src/tools/computer/worker.ts +135 -0
  313. package/src/tools/computer-renderer.ts +108 -0
  314. package/src/tools/computer.ts +433 -0
  315. package/src/tools/context.ts +2 -0
  316. package/src/tools/default-renderer.ts +139 -0
  317. package/src/tools/essential-tools.ts +1 -0
  318. package/src/tools/image-gen.ts +20 -28
  319. package/src/tools/image-providers.ts +50 -0
  320. package/src/tools/index.ts +14 -0
  321. package/src/tools/path-utils.ts +49 -2
  322. package/src/tools/read.ts +156 -88
  323. package/src/tools/renderers.ts +9 -1
  324. package/src/tools/todo.ts +101 -11
  325. package/src/tools/vibe.ts +1 -2
  326. package/src/tools/write.ts +51 -1
  327. package/src/tools/xdev.ts +121 -29
  328. package/src/tts/streaming-player.ts +82 -283
  329. package/src/tts/vocalizer.ts +18 -1
  330. package/src/utils/clipboard.ts +1 -30
  331. package/src/utils/jj.ts +125 -4
  332. package/src/utils/lang-from-path.ts +7 -0
  333. package/src/utils/mac-file-urls.applescript +37 -0
  334. package/src/utils/title-generator.ts +137 -1
  335. package/src/utils/tool-choice.ts +14 -0
  336. package/src/utils/tools-manager.ts +1 -19
  337. package/src/vibe/runtime.ts +852 -72
  338. package/src/web/search/index.ts +3 -6
  339. package/src/web/search/provider.ts +31 -16
  340. package/src/web/search/providers/xai.ts +7 -1
  341. package/src/web/search/types.ts +3 -0
  342. package/dist/types/stt/recorder.d.ts +0 -30
  343. package/dist/types/stt/transcriber.d.ts +0 -14
  344. package/dist/types/stt/wav.d.ts +0 -29
  345. package/dist/types/tts/player.d.ts +0 -32
  346. package/src/stt/recorder.ts +0 -551
  347. package/src/stt/transcriber.ts +0 -60
  348. package/src/stt/wav.ts +0 -173
  349. package/src/tts/player.ts +0 -137
@@ -39,6 +39,7 @@ import {
39
39
  } from "@oh-my-pi/pi-tui";
40
40
  import { isInsideTerminalMultiplexer } from "@oh-my-pi/pi-tui/terminal-capabilities";
41
41
  import {
42
+ $env,
42
43
  APP_NAME,
43
44
  adjustHsv,
44
45
  formatNumber,
@@ -55,7 +56,7 @@ import { reset as resetCapabilities } from "../capability";
55
56
  import type { CollabGuestLink } from "../collab/guest";
56
57
  import type { CollabHost } from "../collab/host";
57
58
  import { KeybindingsManager } from "../config/keybindings";
58
- import type { ResolvedModelRoleValue } from "../config/model-resolver";
59
+ import { formatModelString, type ResolvedModelRoleValue } from "../config/model-resolver";
59
60
  import { applyProviderGlobalsFromSettings } from "../config/provider-globals";
60
61
  import {
61
62
  isSettingsInitialized,
@@ -113,6 +114,7 @@ import { STTController, type SttState } from "../stt";
113
114
  import { discoverTitleSystemPromptFile, resolvePromptInput } from "../system-prompt";
114
115
  import { formatTaskId } from "../task/render";
115
116
  import type { ConfiguredThinkingLevel } from "../thinking";
117
+ import { tinyTitleClient } from "../tiny/title-client";
116
118
  import type { LspStartupServerInfo } from "../tools";
117
119
  import { normalizeLocalScheme } from "../tools/path-utils";
118
120
  import { replaceTabs, TRUNCATE_LENGTHS, truncateToWidth } from "../tools/render-utils";
@@ -130,8 +132,19 @@ import type { EventBus } from "../utils/event-bus";
130
132
  import { getEditorCommand, openInEditor } from "../utils/external-editor";
131
133
  import { getSessionAccentAnsi, getSessionAccentHex } from "../utils/session-color";
132
134
  import { messageHasDisplayableThinking } from "../utils/thinking-display";
133
- import { popTerminalTitle, pushTerminalTitle, setSessionTerminalTitle } from "../utils/title-generator";
134
- import { aggregateVibeWorkerTokensPerSecond, VibeSessionRegistry } from "../vibe/runtime";
135
+ import {
136
+ disposeTerminalTitleState,
137
+ popTerminalTitle,
138
+ pushTerminalTitle,
139
+ setSessionTerminalTitle,
140
+ setTerminalTitleStateEnabled,
141
+ } from "../utils/title-generator";
142
+ import {
143
+ aggregateVibeWorkerTokensPerSecond,
144
+ type VibeOwnerScope,
145
+ type VibeParentSession,
146
+ VibeSessionRegistry,
147
+ } from "../vibe/runtime";
135
148
  import type { AssistantMessageComponent } from "./components/assistant-message";
136
149
  import type { BashExecutionComponent } from "./components/bash-execution";
137
150
  import { ChatBlock, type ChatBlockHost } from "./components/chat-block";
@@ -142,7 +155,7 @@ import type { EvalExecutionComponent } from "./components/eval-execution";
142
155
  import type { HookEditorComponent } from "./components/hook-editor";
143
156
  import type { HookInputComponent } from "./components/hook-input";
144
157
  import type { HookSelectorComponent, HookSelectorSlider } from "./components/hook-selector";
145
- import { PlanReviewOverlay } from "./components/plan-review-overlay";
158
+ import { type PlanReviewAnnotationState, PlanReviewOverlay } from "./components/plan-review-overlay";
146
159
  import { StatusLineComponent } from "./components/status-line";
147
160
  import type { ToolExecutionHandle } from "./components/tool-execution";
148
161
  import { TranscriptContainer } from "./components/transcript-container";
@@ -152,6 +165,7 @@ import { CommandController } from "./controllers/command-controller";
152
165
  import { EventController } from "./controllers/event-controller";
153
166
  import { ExtensionUiController } from "./controllers/extension-ui-controller";
154
167
  import { InputController } from "./controllers/input-controller";
168
+ import { LiveCommandController } from "./controllers/live-command-controller";
155
169
  import { MCPCommandController } from "./controllers/mcp-command-controller";
156
170
  import { OmfgController } from "./controllers/omfg-controller";
157
171
  import { SelectorController } from "./controllers/selector-controller";
@@ -543,6 +557,8 @@ export class InteractiveMode implements InteractiveModeContext {
543
557
  #planModePreviousTools: string[] | undefined;
544
558
  #goalModePreviousTools: string[] | undefined;
545
559
  #vibeModePreviousTools: string[] | undefined;
560
+ #vibeModeOwnerScope: VibeOwnerScope | undefined;
561
+ #vibeScopeSuspendedForSwitch = false;
546
562
  #goalContinuationTimer: NodeJS.Timeout | undefined;
547
563
  #goalTurnHadToolCalls = false;
548
564
  #goalContinuationTurnInFlight = false;
@@ -555,6 +571,10 @@ export class InteractiveMode implements InteractiveModeContext {
555
571
  #planReviewOverlay: PlanReviewOverlay | undefined;
556
572
  #planReviewOverlayHandle: OverlayHandle | undefined;
557
573
  #planReviewCancel: (() => void) | undefined;
574
+ /** Serializable review annotations keyed by the resolved plan file path. */
575
+ #planReviewAnnotationState = new Map<string, PlanReviewAnnotationState>();
576
+ /** Annotation state held until the associated queued refinement actually starts. */
577
+ #planReviewAnnotationStateBySubmission = new WeakMap<SubmittedUserInput, string>();
558
578
  readonly lspServers: LspStartupServerInfo[] | undefined = undefined;
559
579
  mcpManager?: MCPManager;
560
580
  readonly #toolUiContextSetter: (uiContext: ExtensionUIContext, hasUI: boolean) => void;
@@ -564,6 +584,7 @@ export class InteractiveMode implements InteractiveModeContext {
564
584
  readonly #omfgController: OmfgController;
565
585
  readonly #commandController: CommandController;
566
586
  readonly #todoCommandController: TodoCommandController;
587
+ readonly #liveCommandController: LiveCommandController;
567
588
  readonly #eventController: EventController;
568
589
  get eventController(): EventController {
569
590
  return this.#eventController;
@@ -773,6 +794,7 @@ export class InteractiveMode implements InteractiveModeContext {
773
794
  this.#eventController = new EventController(this);
774
795
  this.#commandController = new CommandController(this);
775
796
  this.#todoCommandController = new TodoCommandController(this);
797
+ this.#liveCommandController = new LiveCommandController(this);
776
798
  this.#selectorController = new SelectorController(this);
777
799
  this.#focusController = new SessionFocusController(this);
778
800
  this.#inputController = new InputController(this);
@@ -976,6 +998,7 @@ export class InteractiveMode implements InteractiveModeContext {
976
998
  // the initial welcome frame does not append over the previous run's scrollback.
977
999
  this.ui.start({ clearScrollback: options.clearInitialTerminalHistory === true });
978
1000
  pushTerminalTitle();
1001
+ setTerminalTitleStateEnabled(this.settings.get("tui.titleState"));
979
1002
  setSessionTerminalTitle(this.sessionManager.getSessionName(), this.sessionManager.getCwd());
980
1003
  this.updateEditorBorderColor();
981
1004
  // Single side-effect point for title changes: every setSessionName caller
@@ -993,10 +1016,27 @@ export class InteractiveMode implements InteractiveModeContext {
993
1016
  this.isInitialized = true;
994
1017
  this.ui.requestRender(true);
995
1018
 
1019
+ // Prewarm the local tiny-title worker off the submit hot path: spawn it
1020
+ // now, idle and unref'd, so the first submit reuses a live subprocess
1021
+ // instead of paying spawn latency ahead of the first frame (issue #6462).
1022
+ // No-ops for the online default and for already-named sessions that will
1023
+ // not be titled. Deferred via setImmediate so it runs AFTER the render
1024
+ // callback requestRender(true) queued above (immediates are FIFO) — the
1025
+ // spawn syscall never lands in the same loop turn ahead of the first paint.
1026
+ setImmediate(() => {
1027
+ if (!$env.PI_NO_TITLE && !this.sessionManager.getSessionName()) {
1028
+ tinyTitleClient.prewarm(this.settings.get("providers.tinyModel"));
1029
+ }
1030
+ });
1031
+
996
1032
  // Initialize hooks with TUI-based UI context
997
1033
  await this.initHooksAndCustomTools();
998
1034
 
999
1035
  // Restore mode from session (e.g. plan mode on resume)
1036
+ this.session.setSessionBeforeSwitchReconciler?.(async () => {
1037
+ await this.#liveCommandController.stop();
1038
+ await this.#quiesceVibeForSessionSwitch();
1039
+ });
1000
1040
  this.session.setSessionSwitchReconciler?.(() => this.#reconcileModeFromSession({ preserveActiveGoal: true }));
1001
1041
  await this.#reconcileModeFromSession();
1002
1042
 
@@ -1566,6 +1606,11 @@ export class InteractiveMode implements InteractiveModeContext {
1566
1606
  return false;
1567
1607
  }
1568
1608
  input.started = true;
1609
+ const annotationStateKey = this.#planReviewAnnotationStateBySubmission.get(input);
1610
+ if (annotationStateKey) {
1611
+ this.#planReviewAnnotationStateBySubmission.delete(input);
1612
+ this.#planReviewAnnotationState.delete(annotationStateKey);
1613
+ }
1569
1614
  return true;
1570
1615
  }
1571
1616
 
@@ -1756,6 +1801,8 @@ export class InteractiveMode implements InteractiveModeContext {
1756
1801
  return theme.fg("accent", `${prefix}${checkbox.unchecked} ${todo.content}`) + marker;
1757
1802
  case "abandoned":
1758
1803
  return theme.fg("error", `${prefix}${checkbox.unchecked} ${chalk.strikethrough(todo.content)}`) + marker;
1804
+ case "blocked":
1805
+ return theme.fg("warning", `${prefix}${checkbox.unchecked} ${todo.content} (blocked)`) + marker;
1759
1806
  default:
1760
1807
  if (matched) return theme.fg("accent", `${prefix}${checkbox.unchecked} ${todo.content}`) + marker;
1761
1808
  return theme.fg("dim", `${prefix}${checkbox.unchecked} ${todo.content}`) + marker;
@@ -1775,12 +1822,15 @@ export class InteractiveMode implements InteractiveModeContext {
1775
1822
  }
1776
1823
 
1777
1824
  /**
1778
- * Auto-complete any pending/in_progress todo whose content matches a
1779
- * subagent that has finished successfully. Fires on every observer
1825
+ * Auto-complete any open todo (pending/in_progress/blocked) whose content
1826
+ * matches a subagent that has finished successfully. Fires on every observer
1780
1827
  * `onChange` so the visual state stays in sync with subagent lifecycle
1781
- * without requiring the agent to issue a follow-up `todo`. Failed
1782
- * and aborted subagents are intentionally NOT auto-completed those
1783
- * stay open so the user (or the next agent turn) can decide what to do.
1828
+ * without requiring the agent to issue a follow-up `todo`. A todo `block`ed
1829
+ * while waiting on a detached subagent is included: that subagent completing
1830
+ * is exactly the unblock signal, and blocked todos are excluded from the stop
1831
+ * reminder, so leaving it blocked would strand it silently. Failed and aborted
1832
+ * subagents are intentionally NOT auto-completed — those stay open so the user
1833
+ * (or the next agent turn) can decide what to do.
1784
1834
  *
1785
1835
  * Idempotent: only flips open tasks, never re-touches completed ones.
1786
1836
  */
@@ -1799,10 +1849,14 @@ export class InteractiveMode implements InteractiveModeContext {
1799
1849
  const next: TodoPhase[] = this.todoPhases.map(phase => ({
1800
1850
  name: phase.name,
1801
1851
  tasks: phase.tasks.map(task => {
1802
- if (task.status !== "pending" && task.status !== "in_progress") return task;
1852
+ if (task.status !== "pending" && task.status !== "in_progress" && task.status !== "blocked") {
1853
+ return task;
1854
+ }
1803
1855
  if (!todoMatchesAnyDescription(task.content, completedDescs)) return task;
1804
1856
  mutated = true;
1805
- return { ...task, status: "completed" as const };
1857
+ // Drop any blocker note along with the blocked status the wait the
1858
+ // note described is over.
1859
+ return { content: task.content, status: "completed" as const };
1806
1860
  }),
1807
1861
  }));
1808
1862
  if (!mutated) return;
@@ -2058,6 +2112,29 @@ export class InteractiveMode implements InteractiveModeContext {
2058
2112
  this.ui.requestRender();
2059
2113
  }
2060
2114
 
2115
+ #vibeParentSession(): VibeParentSession {
2116
+ return {
2117
+ getAgentId: () => this.session.getAgentId() ?? null,
2118
+ getSessionId: () => this.sessionManager.getSessionId(),
2119
+ getSessionFile: () => this.sessionManager.getSessionFile() ?? null,
2120
+ sessionManager: this.sessionManager,
2121
+ asyncJobManager: this.session.asyncJobManager,
2122
+ settings: this.session.settings,
2123
+ // Resolve restored/switched-to workers against this session's active model
2124
+ // (same as the spawn-path ToolSession), not the settings default. This is
2125
+ // the primary fallback in resolveAgentModelPatterns, so the `good` worker's
2126
+ // pi/task inheritance tracks the reopened session's model.
2127
+ getActiveModelString: () => (this.session.model ? formatModelString(this.session.model) : undefined),
2128
+ };
2129
+ }
2130
+
2131
+ async #quiesceVibeForSessionSwitch(): Promise<void> {
2132
+ const ownerScope = this.#vibeModeOwnerScope;
2133
+ if (!this.vibeModeEnabled || !ownerScope) return;
2134
+ await VibeSessionRegistry.global().suspendScope(ownerScope, this.session.asyncJobManager);
2135
+ this.#vibeScopeSuspendedForSwitch = true;
2136
+ }
2137
+
2061
2138
  #updateGoalModeStatus(): void {
2062
2139
  const status =
2063
2140
  this.goalModeEnabled || this.goalModePaused
@@ -2241,7 +2318,10 @@ export class InteractiveMode implements InteractiveModeContext {
2241
2318
  }
2242
2319
  }
2243
2320
 
2244
- async #clearTransientModeState(): Promise<void> {
2321
+ async #clearTransientModeState(options?: {
2322
+ preserveVibe?: boolean;
2323
+ vibeScopeAlreadySuspended?: boolean;
2324
+ }): Promise<void> {
2245
2325
  if (this.planModeEnabled || this.planModePaused) {
2246
2326
  this.session.setPlanModeState(undefined);
2247
2327
  try {
@@ -2277,23 +2357,40 @@ export class InteractiveMode implements InteractiveModeContext {
2277
2357
  this.#updateGoalModeStatus();
2278
2358
  }
2279
2359
 
2280
- if (this.vibeModeEnabled) {
2281
- await this.session.deactivateVibeTools(this.#vibeModePreviousTools ?? []);
2360
+ if (this.vibeModeEnabled && !options?.preserveVibe) {
2361
+ const ownerScope = this.#vibeModeOwnerScope;
2362
+ // This runs only from #reconcileModeFromSession, i.e. after switchSession
2363
+ // already loaded and restored the target session's active tools. The
2364
+ // #vibeModePreviousTools snapshot belongs to the SOURCE session, so
2365
+ // applying it here would clobber the target's tools — strip only the
2366
+ // transient vibe tools and keep the target's active set intact.
2367
+ await this.session.removeVibeToolsPreservingActive();
2282
2368
  this.session.setVibeModeState(undefined);
2283
2369
  this.vibeModeEnabled = false;
2284
2370
  this.#vibeModePreviousTools = undefined;
2285
- await VibeSessionRegistry.global().killAll(
2286
- this.session.getAgentId() ?? MAIN_AGENT_ID,
2287
- this.session.asyncJobManager,
2288
- );
2371
+ this.#vibeModeOwnerScope = undefined;
2372
+ if (ownerScope && !options?.vibeScopeAlreadySuspended) {
2373
+ await VibeSessionRegistry.global().suspendScope(ownerScope, this.session.asyncJobManager);
2374
+ }
2289
2375
  this.#updateVibeModeStatus();
2290
2376
  }
2291
2377
  }
2292
2378
 
2293
2379
  /** Reconcile mode state from session entries on resume/switch. */
2294
2380
  async #reconcileModeFromSession(options?: { preserveActiveGoal?: boolean }): Promise<void> {
2295
- await this.#clearTransientModeState();
2381
+ const vibeScopeAlreadySuspended = this.#vibeScopeSuspendedForSwitch;
2382
+ this.#vibeScopeSuspendedForSwitch = false;
2296
2383
  const sessionContext = this.sessionManager.buildSessionContext();
2384
+ const vibeSession = this.#vibeParentSession();
2385
+ const targetVibeScope = VibeSessionRegistry.global().ownerScope(vibeSession);
2386
+ const preserveVibe =
2387
+ this.vibeModeEnabled &&
2388
+ sessionContext.mode === "vibe" &&
2389
+ this.#vibeModeOwnerScope?.ownerId === targetVibeScope.ownerId &&
2390
+ this.#vibeModeOwnerScope.parentSessionId === targetVibeScope.parentSessionId &&
2391
+ this.#vibeModeOwnerScope.parentSessionFile === targetVibeScope.parentSessionFile;
2392
+ await this.#clearTransientModeState({ preserveVibe, vibeScopeAlreadySuspended });
2393
+ await VibeSessionRegistry.global().rehydrate(vibeSession);
2297
2394
  const goalEnabled = this.session.settings.get("goal.enabled");
2298
2395
  if (!goalEnabled && (sessionContext.mode === "goal" || sessionContext.mode === "goal_paused")) {
2299
2396
  this.session.goalRuntime.clearAccounting();
@@ -2328,7 +2425,7 @@ export class InteractiveMode implements InteractiveModeContext {
2328
2425
  }
2329
2426
  this.session.goalRuntime.clearAccounting();
2330
2427
  if (sessionContext.mode === "vibe") {
2331
- await this.#enterVibeMode();
2428
+ if (!preserveVibe) await this.#enterVibeMode({ persistModeChange: false });
2332
2429
  return;
2333
2430
  }
2334
2431
  if (!this.session.settings.get("plan.enabled")) {
@@ -2646,6 +2743,8 @@ export class InteractiveMode implements InteractiveModeContext {
2646
2743
  onExternalEditor?: () => void;
2647
2744
  onPlanEdited?: (content: string) => void;
2648
2745
  onFeedbackChange?: (feedback: string) => void;
2746
+ annotationState?: PlanReviewAnnotationState;
2747
+ onAnnotationStateChange?: (state: PlanReviewAnnotationState) => void;
2649
2748
  initialIndex?: number;
2650
2749
  },
2651
2750
  extra?: { slider?: HookSelectorSlider },
@@ -2669,6 +2768,7 @@ export class InteractiveMode implements InteractiveModeContext {
2669
2768
  initialIndex: dialogOptions?.initialIndex,
2670
2769
  slider: extra?.slider,
2671
2770
  externalEditorLabel: this.keybindings.getDisplayString("app.editor.external") || undefined,
2771
+ annotationState: dialogOptions?.annotationState,
2672
2772
  },
2673
2773
  {
2674
2774
  onPick: choice => finish(choice),
@@ -2678,6 +2778,7 @@ export class InteractiveMode implements InteractiveModeContext {
2678
2778
  onAnnotationExternalEditor: (draft, commit) => void this.#openPlanAnnotationInExternalEditor(draft, commit),
2679
2779
  onPlanEdited: dialogOptions?.onPlanEdited,
2680
2780
  onFeedbackChange: dialogOptions?.onFeedbackChange,
2781
+ onAnnotationStateChange: dialogOptions?.onAnnotationStateChange,
2681
2782
  },
2682
2783
  );
2683
2784
  this.#planReviewOverlay = overlay;
@@ -2907,7 +3008,7 @@ export class InteractiveMode implements InteractiveModeContext {
2907
3008
  compactBeforeExecute?: boolean;
2908
3009
  executionModel?: ResolvedRoleModel;
2909
3010
  },
2910
- ): Promise<void> {
3011
+ ): Promise<boolean> {
2911
3012
  const previousTools = this.#planModePreviousTools ?? this.session.getEnabledToolNames();
2912
3013
 
2913
3014
  // Mark the pending abort caused by the plan-mode → compaction transition as
@@ -3006,7 +3107,7 @@ export class InteractiveMode implements InteractiveModeContext {
3006
3107
  this.showWarning(
3007
3108
  "Plan approved, but compaction was cancelled — execution not dispatched. Submit a turn to continue.",
3008
3109
  );
3009
- return;
3110
+ return false;
3010
3111
  }
3011
3112
 
3012
3113
  // Approved plans land in a fresh (or compacted) session whose first user-visible
@@ -3048,14 +3149,15 @@ export class InteractiveMode implements InteractiveModeContext {
3048
3149
  // noted below), catch `AgentBusyError` and fall back to the same queue.
3049
3150
  if (this.session.isStreaming) {
3050
3151
  await this.session.followUp(planModePrompt, undefined, { synthetic: true });
3051
- return;
3052
- }
3053
- try {
3054
- await this.session.prompt(planModePrompt, { synthetic: true });
3055
- } catch (error) {
3056
- if (!(error instanceof AgentBusyError)) throw error;
3057
- await this.session.followUp(planModePrompt, undefined, { synthetic: true });
3152
+ } else {
3153
+ try {
3154
+ await this.session.prompt(planModePrompt, { synthetic: true });
3155
+ } catch (error) {
3156
+ if (!(error instanceof AgentBusyError)) throw error;
3157
+ await this.session.followUp(planModePrompt, undefined, { synthetic: true });
3158
+ }
3058
3159
  }
3160
+ return true;
3059
3161
  }
3060
3162
  async #abortPlanApprovalTurnSilently(): Promise<void> {
3061
3163
  this.session.markPlanInternalAbortPending();
@@ -3135,7 +3237,7 @@ export class InteractiveMode implements InteractiveModeContext {
3135
3237
  }
3136
3238
  }
3137
3239
 
3138
- async #enterVibeMode(): Promise<void> {
3240
+ async #enterVibeMode(options?: { persistModeChange?: boolean }): Promise<void> {
3139
3241
  if (this.vibeModeEnabled) {
3140
3242
  return;
3141
3243
  }
@@ -3148,9 +3250,13 @@ export class InteractiveMode implements InteractiveModeContext {
3148
3250
  return;
3149
3251
  }
3150
3252
 
3253
+ const vibeRegistry = VibeSessionRegistry.global();
3254
+ const ownerScope = vibeRegistry.ownerScope(this.#vibeParentSession());
3255
+ vibeRegistry.activateScope(ownerScope);
3151
3256
  const previousTools = this.session.getEnabledToolNames();
3152
3257
  await this.session.activateVibeTools(["read"]);
3153
3258
  this.#vibeModePreviousTools = previousTools;
3259
+ this.#vibeModeOwnerScope = ownerScope;
3154
3260
  this.vibeModeEnabled = true;
3155
3261
  // Suppress cache-miss marker on the next turn: vibe mode changes the
3156
3262
  // injected context, which predictably invalidates the cache.
@@ -3160,7 +3266,7 @@ export class InteractiveMode implements InteractiveModeContext {
3160
3266
  await this.session.sendVibeModeContext({ deliverAs: "steer" });
3161
3267
  }
3162
3268
  this.#updateVibeModeStatus();
3163
- this.sessionManager.appendModeChange("vibe");
3269
+ if (options?.persistModeChange !== false) this.sessionManager.appendModeChange("vibe");
3164
3270
  this.showStatus("Vibe mode enabled. You direct fast/good worker sessions; toolset is read + vibe tools.");
3165
3271
  }
3166
3272
 
@@ -3168,17 +3274,15 @@ export class InteractiveMode implements InteractiveModeContext {
3168
3274
  if (!this.vibeModeEnabled) {
3169
3275
  return;
3170
3276
  }
3277
+ const ownerScope = this.#vibeModeOwnerScope;
3278
+ const killed = await VibeSessionRegistry.global().killAll(this.#vibeParentSession(), ownerScope);
3171
3279
  await this.session.deactivateVibeTools(this.#vibeModePreviousTools ?? []);
3172
3280
  this.session.setVibeModeState(undefined);
3173
3281
  this.vibeModeEnabled = false;
3174
3282
  this.#vibeModePreviousTools = undefined;
3283
+ this.#vibeModeOwnerScope = undefined;
3175
3284
  this.lastAssistantUsage = undefined;
3176
- const killed = await VibeSessionRegistry.global().killAll(
3177
- this.session.getAgentId() ?? MAIN_AGENT_ID,
3178
- this.session.asyncJobManager,
3179
- );
3180
3285
  this.#updateVibeModeStatus();
3181
- this.sessionManager.appendModeChange("none");
3182
3286
  this.showStatus(
3183
3287
  killed > 0
3184
3288
  ? `Vibe mode disabled. Killed ${killed} worker session${killed === 1 ? "" : "s"}.`
@@ -3582,6 +3686,7 @@ export class InteractiveMode implements InteractiveModeContext {
3582
3686
  // that the Refine branch re-prompts the model with.
3583
3687
  let editedContent: string | undefined;
3584
3688
  let feedback = "";
3689
+ const annotationStateKey = this.#resolvePlanFilePath(planFilePath);
3585
3690
 
3586
3691
  const choice = await this.showPlanReview(
3587
3692
  planContent,
@@ -3597,6 +3702,11 @@ export class InteractiveMode implements InteractiveModeContext {
3597
3702
  onFeedbackChange: value => {
3598
3703
  feedback = value;
3599
3704
  },
3705
+ annotationState: this.#planReviewAnnotationState.get(annotationStateKey),
3706
+ onAnnotationStateChange: state => {
3707
+ if (state.annotations.length > 0) this.#planReviewAnnotationState.set(annotationStateKey, state);
3708
+ else this.#planReviewAnnotationState.delete(annotationStateKey);
3709
+ },
3600
3710
  disabledIndices: keepContextDisabled ? [PLAN_KEEP_CONTEXT_OPTION_INDEX] : undefined,
3601
3711
  },
3602
3712
  { slider },
@@ -3648,13 +3758,14 @@ export class InteractiveMode implements InteractiveModeContext {
3648
3758
  : -1;
3649
3759
  const executionModel =
3650
3760
  slider && cycle && selectedTierIndex !== restoredIndex ? cycle.models[selectedTierIndex] : undefined;
3651
- await this.#approvePlan(latestPlanContent, {
3761
+ const executionDispatched = await this.#approvePlan(latestPlanContent, {
3652
3762
  planFilePath,
3653
3763
  title: details.title,
3654
3764
  preserveContext: choice !== "Approve and execute",
3655
3765
  compactBeforeExecute: choice === "Approve and compact context",
3656
3766
  executionModel,
3657
3767
  });
3768
+ if (executionDispatched) this.#planReviewAnnotationState.delete(annotationStateKey);
3658
3769
  } catch (error) {
3659
3770
  this.showError(
3660
3771
  `Failed to finalize approved plan: ${error instanceof Error ? error.message : String(error)}`,
@@ -3669,9 +3780,12 @@ export class InteractiveMode implements InteractiveModeContext {
3669
3780
  try {
3670
3781
  if (refinement) {
3671
3782
  if (this.onInputCallback) {
3672
- this.onInputCallback(this.startPendingSubmission({ text: feedback }));
3783
+ const input = this.startPendingSubmission({ text: feedback });
3784
+ this.#planReviewAnnotationStateBySubmission.set(input, annotationStateKey);
3785
+ this.onInputCallback(input);
3673
3786
  } else {
3674
3787
  await this.session.prompt(feedback);
3788
+ this.#planReviewAnnotationState.delete(annotationStateKey);
3675
3789
  }
3676
3790
  } else {
3677
3791
  this.showStatus("Refine plan: enter a follow-up prompt.");
@@ -3746,6 +3860,7 @@ export class InteractiveMode implements InteractiveModeContext {
3746
3860
  this.#stopLoadingAnimation(false);
3747
3861
  }
3748
3862
  this.#cleanupMicAnimation();
3863
+ this.#liveCommandController.dispose();
3749
3864
  this.#cancelTodoAutoClearTimer();
3750
3865
  this.#cancelObserverUiSyncTimer();
3751
3866
  this.#cancelGoalContinuation();
@@ -3788,6 +3903,8 @@ export class InteractiveMode implements InteractiveModeContext {
3788
3903
  if (this.#isShuttingDown) return;
3789
3904
  this.#isShuttingDown = true;
3790
3905
 
3906
+ await this.#liveCommandController.stop();
3907
+
3791
3908
  this.#btwController.dispose();
3792
3909
  this.#omfgController.dispose();
3793
3910
  this.#focusController.dispose();
@@ -3825,6 +3942,10 @@ export class InteractiveMode implements InteractiveModeContext {
3825
3942
  // Drain any in-flight Kitty key release events before stopping.
3826
3943
  // This prevents escape sequences from leaking to the parent shell over slow SSH.
3827
3944
  await this.ui.terminal.drainInput(1000);
3945
+ // Stop the run-state spinner interval BEFORE restoring the shell title, so a
3946
+ // pending tick cannot re-emit an OSC title after `popTerminalTitle` hands the
3947
+ // terminal back (which would leave the parent shell with a `π ⠋ …` tab).
3948
+ disposeTerminalTitleState();
3828
3949
  popTerminalTitle();
3829
3950
  this.stop();
3830
3951
 
@@ -4297,6 +4418,10 @@ export class InteractiveMode implements InteractiveModeContext {
4297
4418
  }
4298
4419
 
4299
4420
  async handleSTTToggle(): Promise<void> {
4421
+ if (this.#liveCommandController.active) {
4422
+ this.showWarning("End live mode before using push-to-talk speech input.");
4423
+ return;
4424
+ }
4300
4425
  if (!settings.get("stt.enabled")) {
4301
4426
  this.showWarning("Speech-to-text is disabled. Enable it in settings: stt.enabled");
4302
4427
  return;
@@ -4329,6 +4454,15 @@ export class InteractiveMode implements InteractiveModeContext {
4329
4454
  });
4330
4455
  }
4331
4456
 
4457
+ /** Start or stop the Codex-backed realtime voice surface. */
4458
+ async handleLiveCommand(): Promise<void> {
4459
+ if (this.#sttController && this.#sttController.state !== "idle") {
4460
+ this.showWarning("Finish the current speech-to-text capture before starting live mode.");
4461
+ return;
4462
+ }
4463
+ await this.#liveCommandController.handleCommand();
4464
+ }
4465
+
4332
4466
  #setMicCursor(color: { r: number; g: number; b: number }): void {
4333
4467
  this.editor.cursorOverride = `\x1b[38;2;${color.r};${color.g};${color.b}m${theme.icon.mic}\x1b[0m`;
4334
4468
  // Theme symbols can be wide (for example, 🎤), so measure the rendered override.
@@ -4505,6 +4639,10 @@ export class InteractiveMode implements InteractiveModeContext {
4505
4639
  return this.#selectorController.showOAuthSelector(mode, providerId);
4506
4640
  }
4507
4641
 
4642
+ showSessionPinSelector(): Promise<void> {
4643
+ return this.#selectorController.showSessionPinSelector();
4644
+ }
4645
+
4508
4646
  showResetUsageSelector(): Promise<void> {
4509
4647
  return this.#selectorController.showResetUsageSelector();
4510
4648
  }
@@ -90,6 +90,8 @@ export async function initializeExtensions(session: AgentSession, options: Initi
90
90
  setModel: model => runExtensionSetModel(session, model),
91
91
  getThinkingLevel: () => session.thinkingLevel,
92
92
  setThinkingLevel: level => session.setThinkingLevel(level),
93
+ getServiceTiers: () => session.serviceTierByFamily,
94
+ setServiceTier: (family, tier) => session.setServiceTierFamily(family, tier),
93
95
  getSessionName: () => session.sessionManager.getSessionName(),
94
96
  setSessionName: async name => {
95
97
  await session.sessionManager.setSessionName(name, "user");
@@ -5,16 +5,20 @@ import {
5
5
  type SgrMouseEvent,
6
6
  truncateToWidth,
7
7
  } from "@oh-my-pi/pi-tui";
8
- import { SETTINGS_SCHEMA } from "../../../config/settings-schema";
9
- import { getSearchProvider, setPreferredSearchProvider } from "../../../web/search/provider";
10
- import { isSearchProviderPreference, type SearchProviderId } from "../../../web/search/types";
8
+ import { getSearchProvider, setSearchProviderOrder } from "../../../web/search/provider";
9
+ import {
10
+ isSearchProviderId,
11
+ SEARCH_PROVIDER_OPTIONS,
12
+ SEARCH_PROVIDER_ORDER,
13
+ type SearchProviderId,
14
+ } from "../../../web/search/types";
11
15
  import { getSelectListTheme, theme } from "../../theme/theme";
12
16
  import type { SetupSceneHost, SetupTab } from "./types";
13
17
 
14
18
  const MAX_VISIBLE = 8;
15
19
 
16
- /** Reuse the settings schema as the single source of truth for labels/descriptions. */
17
- const WEB_SEARCH_ITEMS: readonly SelectItem[] = SETTINGS_SCHEMA["providers.webSearch"].ui.options.map(option => ({
20
+ /** Reuse the shared provider options as the single source of truth for labels/descriptions. */
21
+ const WEB_SEARCH_ITEMS: readonly SelectItem[] = SEARCH_PROVIDER_OPTIONS.map(option => ({
18
22
  value: option.value,
19
23
  label: option.label,
20
24
  description: option.description,
@@ -42,7 +46,8 @@ export class WebSearchTab implements SetupTab {
42
46
 
43
47
  constructor(private readonly host: SetupSceneHost) {
44
48
  this.#list = new SelectList(WEB_SEARCH_ITEMS, MAX_VISIBLE, getSelectListTheme());
45
- const current = host.ctx.settings.get("providers.webSearch");
49
+ const order = host.ctx.settings.get("providers.webSearchOrder");
50
+ const current = Array.isArray(order) && typeof order[0] === "string" ? order[0] : "auto";
46
51
  const index = WEB_SEARCH_ITEMS.findIndex(item => item.value === current);
47
52
  if (index >= 0) this.#list.setSelectedIndex(index);
48
53
  this.#list.onSelectionChange = item => this.#onHighlight(item.value);
@@ -119,9 +124,12 @@ export class WebSearchTab implements SetupTab {
119
124
  }
120
125
 
121
126
  #apply(value: string): void {
122
- if (!isSearchProviderPreference(value)) return;
123
- this.host.ctx.settings.set("providers.webSearch", value);
124
- setPreferredSearchProvider(value);
127
+ if (value !== "auto" && !isSearchProviderId(value)) return;
128
+ // The wizard picks one favorite; persist it as the head of the priority
129
+ // list with the remaining providers in their built-in order (auto = reset).
130
+ const order = value === "auto" ? [] : [value, ...SEARCH_PROVIDER_ORDER.filter(id => id !== value)];
131
+ this.host.ctx.settings.set("providers.webSearchOrder", order);
132
+ setSearchProviderOrder(order);
125
133
  const label = WEB_SEARCH_ITEMS.find(item => item.value === value)?.label ?? value;
126
134
  this.#status = [theme.fg("success", `${theme.status.success} Web search set to ${label}`)];
127
135
  if (value !== "auto" && this.#availability.get(value as SearchProviderId) === false) {
@@ -20,7 +20,7 @@ import { defaultThemes } from "./defaults";
20
20
  import lightThemeJson from "./light.json" with { type: "json" };
21
21
  import { resolveMermaidAscii } from "./mermaid-cache";
22
22
 
23
- export { getLanguageFromPath } from "../../utils/lang-from-path";
23
+ export { getLanguageFromPath, isMarkdownPath } from "../../utils/lang-from-path";
24
24
 
25
25
  // ============================================================================
26
26
  // Symbol Presets
@@ -71,7 +71,7 @@ export type SubmittedUserInput = {
71
71
  started: boolean;
72
72
  };
73
73
 
74
- export type TodoStatus = "pending" | "in_progress" | "completed" | "abandoned";
74
+ export type TodoStatus = "pending" | "in_progress" | "completed" | "abandoned" | "blocked";
75
75
 
76
76
  export type TodoItem = {
77
77
  content: string;
@@ -356,6 +356,8 @@ export interface InteractiveModeContext {
356
356
  handleRenameCommand(title: string): Promise<void>;
357
357
  handleMemoryCommand(text: string): Promise<void>;
358
358
  handleSTTToggle(): Promise<void>;
359
+ /** Start or stop the Codex-backed realtime voice session. */
360
+ handleLiveCommand(): Promise<void>;
359
361
  executeCompaction(
360
362
  customInstructionsOrOptions?: string | CompactOptions,
361
363
  isAuto?: boolean,
@@ -381,6 +383,7 @@ export interface InteractiveModeContext {
381
383
  handleResumeSession(sessionPath: string): Promise<void>;
382
384
  handleSessionDeleteCommand(): Promise<void>;
383
385
  showOAuthSelector(mode: "login" | "logout", providerId?: string): Promise<void>;
386
+ showSessionPinSelector(): Promise<void>;
384
387
  showResetUsageSelector(): Promise<void>;
385
388
  showProviderSetup(): Promise<void>;
386
389
  showHookConfirm(title: string, message: string): Promise<boolean>;