@oh-my-pi/pi-coding-agent 15.12.3 → 15.13.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 (457) hide show
  1. package/CHANGELOG.md +347 -7
  2. package/dist/cli.js +1615 -1231
  3. package/dist/types/async/job-manager.d.ts +15 -0
  4. package/dist/types/autolearn/controller.d.ts +25 -0
  5. package/dist/types/autolearn/managed-skills.d.ts +45 -0
  6. package/dist/types/autoresearch/state.d.ts +1 -1
  7. package/dist/types/autoresearch/tools/init-experiment.d.ts +1 -1
  8. package/dist/types/autoresearch/tools/log-experiment.d.ts +1 -1
  9. package/dist/types/autoresearch/tools/run-experiment.d.ts +1 -1
  10. package/dist/types/autoresearch/tools/update-notes.d.ts +1 -1
  11. package/dist/types/autoresearch/types.d.ts +1 -1
  12. package/dist/types/cli/args.d.ts +19 -2
  13. package/dist/types/cli/models-cli.d.ts +49 -0
  14. package/dist/types/cli/session-picker.d.ts +1 -1
  15. package/dist/types/cli/setup-cli.d.ts +1 -1
  16. package/dist/types/cli/setup-model-picker.d.ts +14 -0
  17. package/dist/types/collab/protocol.d.ts +1 -1
  18. package/dist/types/commands/launch.d.ts +0 -3
  19. package/dist/types/commands/models.d.ts +33 -0
  20. package/dist/types/commands/say.d.ts +24 -0
  21. package/dist/types/commands/token.d.ts +25 -0
  22. package/dist/types/commit/agentic/tools/analyze-file.d.ts +1 -1
  23. package/dist/types/commit/agentic/tools/git-file-diff.d.ts +1 -1
  24. package/dist/types/commit/agentic/tools/git-hunk.d.ts +1 -1
  25. package/dist/types/commit/agentic/tools/git-overview.d.ts +1 -1
  26. package/dist/types/commit/agentic/tools/propose-changelog.d.ts +1 -1
  27. package/dist/types/commit/agentic/tools/propose-commit.d.ts +1 -1
  28. package/dist/types/commit/agentic/tools/recent-commits.d.ts +1 -1
  29. package/dist/types/commit/agentic/tools/schemas.d.ts +1 -1
  30. package/dist/types/commit/agentic/tools/split-commit.d.ts +1 -1
  31. package/dist/types/commit/changelog/generate.d.ts +1 -1
  32. package/dist/types/commit/shared-llm.d.ts +1 -1
  33. package/dist/types/config/keybindings.d.ts +3 -3
  34. package/dist/types/config/model-registry.d.ts +17 -0
  35. package/dist/types/config/models-config-schema.d.ts +13 -1
  36. package/dist/types/config/models-config.d.ts +8 -2
  37. package/dist/types/config/settings-schema.d.ts +281 -58
  38. package/dist/types/edit/hashline/params.d.ts +1 -1
  39. package/dist/types/edit/modes/apply-patch.d.ts +1 -1
  40. package/dist/types/edit/modes/patch.d.ts +1 -1
  41. package/dist/types/edit/modes/replace.d.ts +1 -1
  42. package/dist/types/export/html/index.d.ts +2 -1
  43. package/dist/types/extensibility/custom-commands/types.d.ts +2 -2
  44. package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
  45. package/dist/types/extensibility/extensions/model-api.d.ts +17 -0
  46. package/dist/types/extensibility/extensions/runner.d.ts +3 -1
  47. package/dist/types/extensibility/extensions/types.d.ts +49 -3
  48. package/dist/types/extensibility/hooks/index.d.ts +2 -1
  49. package/dist/types/extensibility/hooks/types.d.ts +2 -2
  50. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +9 -0
  51. package/dist/types/extensibility/plugins/loader.d.ts +11 -0
  52. package/dist/types/extensibility/shared-events.d.ts +1 -1
  53. package/dist/types/extensibility/skills.d.ts +10 -0
  54. package/dist/types/goals/guided-setup.d.ts +18 -0
  55. package/dist/types/goals/state.d.ts +1 -1
  56. package/dist/types/goals/tools/goal-tool.d.ts +1 -1
  57. package/dist/types/hindsight/transcript.d.ts +1 -1
  58. package/dist/types/index.d.ts +5 -0
  59. package/dist/types/internal-urls/local-protocol.d.ts +4 -2
  60. package/dist/types/lsp/types.d.ts +1 -1
  61. package/dist/types/main.d.ts +4 -3
  62. package/dist/types/mcp/manager.d.ts +8 -0
  63. package/dist/types/mcp/startup-events.d.ts +11 -0
  64. package/dist/types/memories/index.d.ts +7 -0
  65. package/dist/types/memory-backend/local-backend.d.ts +4 -3
  66. package/dist/types/mnemopi/config.d.ts +28 -0
  67. package/dist/types/modes/acp/acp-agent.d.ts +1 -2
  68. package/dist/types/modes/components/agent-hub.d.ts +6 -0
  69. package/dist/types/modes/components/assistant-message.d.ts +1 -2
  70. package/dist/types/modes/components/compaction-summary-message.d.ts +15 -1
  71. package/dist/types/modes/components/custom-editor.d.ts +39 -1
  72. package/dist/types/modes/components/custom-editor.test.d.ts +1 -0
  73. package/dist/types/modes/components/index.d.ts +1 -0
  74. package/dist/types/modes/components/logout-account-selector.d.ts +8 -0
  75. package/dist/types/modes/components/session-selector.d.ts +1 -1
  76. package/dist/types/modes/components/status-line/component.d.ts +9 -5
  77. package/dist/types/modes/components/status-line/types.d.ts +2 -1
  78. package/dist/types/modes/components/tool-execution.d.ts +26 -16
  79. package/dist/types/modes/components/transcript-container.d.ts +23 -2
  80. package/dist/types/modes/components/tree-selector.d.ts +1 -1
  81. package/dist/types/modes/components/usage-row.d.ts +3 -0
  82. package/dist/types/modes/controllers/command-controller.d.ts +2 -2
  83. package/dist/types/modes/controllers/event-controller.d.ts +0 -17
  84. package/dist/types/modes/controllers/input-controller.d.ts +14 -0
  85. package/dist/types/modes/controllers/selector-controller.d.ts +3 -1
  86. package/dist/types/modes/gradient-highlight.d.ts +9 -4
  87. package/dist/types/modes/image-references.d.ts +6 -0
  88. package/dist/types/modes/interactive-mode.d.ts +27 -6
  89. package/dist/types/modes/magic-keywords.d.ts +13 -1
  90. package/dist/types/modes/rpc/rpc-mode.d.ts +35 -1
  91. package/dist/types/modes/rpc/rpc-types.d.ts +9 -1
  92. package/dist/types/modes/runtime-init.d.ts +4 -0
  93. package/dist/types/modes/theme/theme.d.ts +13 -2
  94. package/dist/types/modes/types.d.ts +8 -7
  95. package/dist/types/modes/utils/ui-helpers.d.ts +1 -1
  96. package/dist/types/registry/agent-registry.d.ts +17 -0
  97. package/dist/types/secrets/obfuscator.d.ts +1 -1
  98. package/dist/types/session/agent-session.d.ts +28 -35
  99. package/dist/types/session/agent-storage.d.ts +2 -1
  100. package/dist/types/session/indexed-session-storage.d.ts +3 -3
  101. package/dist/types/session/messages.d.ts +8 -10
  102. package/dist/types/session/session-context.d.ts +39 -0
  103. package/dist/types/session/session-entries.d.ts +159 -0
  104. package/dist/types/session/session-listing.d.ts +69 -0
  105. package/dist/types/session/session-loader.d.ts +16 -0
  106. package/dist/types/session/session-manager.d.ts +85 -462
  107. package/dist/types/session/session-migrations.d.ts +12 -0
  108. package/dist/types/session/session-paths.d.ts +25 -0
  109. package/dist/types/session/session-persistence.d.ts +8 -0
  110. package/dist/types/session/session-storage.d.ts +11 -7
  111. package/dist/types/session/snapcompact-inline.d.ts +12 -1
  112. package/dist/types/session/snapcompact-savings-journal.d.ts +46 -0
  113. package/dist/types/session/tool-choice-queue.d.ts +6 -6
  114. package/dist/types/slash-commands/helpers/logout.d.ts +15 -0
  115. package/dist/types/stt/asr-client.d.ts +90 -0
  116. package/dist/types/stt/asr-protocol.d.ts +97 -0
  117. package/dist/types/stt/asr-worker.d.ts +2 -0
  118. package/dist/types/stt/downloader.d.ts +38 -0
  119. package/dist/types/stt/endpointer.d.ts +59 -0
  120. package/dist/types/stt/index.d.ts +5 -1
  121. package/dist/types/stt/models.d.ts +120 -0
  122. package/dist/types/stt/recorder.d.ts +17 -0
  123. package/dist/types/stt/stt-controller.d.ts +6 -0
  124. package/dist/types/stt/transcriber.d.ts +5 -7
  125. package/dist/types/stt/wav.d.ts +29 -0
  126. package/dist/types/system-prompt.d.ts +4 -0
  127. package/dist/types/task/executor.d.ts +2 -0
  128. package/dist/types/task/index.d.ts +9 -1
  129. package/dist/types/task/types.d.ts +37 -1
  130. package/dist/types/tools/ask.d.ts +1 -1
  131. package/dist/types/tools/ast-edit.d.ts +1 -1
  132. package/dist/types/tools/ast-grep.d.ts +1 -1
  133. package/dist/types/tools/bash.d.ts +3 -3
  134. package/dist/types/tools/browser/cmux/cmux-tab.d.ts +202 -0
  135. package/dist/types/tools/browser/cmux/rpc.d.ts +70 -0
  136. package/dist/types/tools/browser/cmux/socket-client.d.ts +19 -0
  137. package/dist/types/tools/browser/registry.d.ts +16 -3
  138. package/dist/types/tools/browser/render.d.ts +2 -0
  139. package/dist/types/tools/browser/tab-protocol.d.ts +2 -0
  140. package/dist/types/tools/browser/tab-supervisor.d.ts +16 -4
  141. package/dist/types/tools/browser.d.ts +3 -1
  142. package/dist/types/tools/checkpoint.d.ts +1 -1
  143. package/dist/types/tools/debug.d.ts +1 -1
  144. package/dist/types/tools/eval-render.d.ts +1 -1
  145. package/dist/types/tools/eval.d.ts +1 -1
  146. package/dist/types/tools/find.d.ts +1 -1
  147. package/dist/types/tools/gh.d.ts +1 -1
  148. package/dist/types/tools/image-gen.d.ts +1 -1
  149. package/dist/types/tools/index.d.ts +14 -2
  150. package/dist/types/tools/inspect-image.d.ts +1 -1
  151. package/dist/types/tools/irc.d.ts +2 -1
  152. package/dist/types/tools/job.d.ts +1 -1
  153. package/dist/types/tools/learn.d.ts +51 -0
  154. package/dist/types/tools/manage-skill.d.ts +40 -0
  155. package/dist/types/tools/memory-edit.d.ts +1 -1
  156. package/dist/types/tools/memory-recall.d.ts +1 -1
  157. package/dist/types/tools/memory-reflect.d.ts +1 -1
  158. package/dist/types/tools/memory-retain.d.ts +1 -1
  159. package/dist/types/tools/plan-mode-guard.d.ts +10 -0
  160. package/dist/types/tools/read.d.ts +1 -1
  161. package/dist/types/tools/render-mermaid.d.ts +1 -1
  162. package/dist/types/tools/renderers.d.ts +7 -11
  163. package/dist/types/tools/resolve.d.ts +1 -1
  164. package/dist/types/tools/review.d.ts +1 -1
  165. package/dist/types/tools/search-tool-bm25.d.ts +1 -1
  166. package/dist/types/tools/search.d.ts +1 -1
  167. package/dist/types/tools/ssh.d.ts +2 -2
  168. package/dist/types/tools/todo.d.ts +2 -2
  169. package/dist/types/tools/tts.d.ts +26 -1
  170. package/dist/types/tools/write.d.ts +2 -2
  171. package/dist/types/tts/downloader.d.ts +20 -0
  172. package/dist/types/tts/index.d.ts +8 -0
  173. package/dist/types/tts/models.d.ts +82 -0
  174. package/dist/types/tts/player.d.ts +32 -0
  175. package/dist/types/tts/runtime.d.ts +6 -0
  176. package/dist/types/tts/streaming-player.d.ts +41 -0
  177. package/dist/types/tts/tts-client.d.ts +93 -0
  178. package/dist/types/tts/tts-protocol.d.ts +95 -0
  179. package/dist/types/tts/tts-worker.d.ts +2 -0
  180. package/dist/types/tts/vocalizer.d.ts +41 -0
  181. package/dist/types/tts/wav.d.ts +8 -0
  182. package/dist/types/utils/clipboard.d.ts +4 -3
  183. package/dist/types/utils/image-loading.d.ts +18 -1
  184. package/dist/types/utils/thinking-display.d.ts +17 -0
  185. package/dist/types/utils/tool-choice.d.ts +8 -0
  186. package/dist/types/utils/tools-manager.d.ts +2 -1
  187. package/dist/types/utils/tools-manager.test.d.ts +1 -0
  188. package/dist/types/web/scrapers/github.d.ts +1 -1
  189. package/dist/types/web/search/index.d.ts +1 -1
  190. package/package.json +17 -16
  191. package/src/async/job-manager.ts +49 -0
  192. package/src/autolearn/controller.ts +139 -0
  193. package/src/autolearn/managed-skills.ts +257 -0
  194. package/src/autoresearch/state.ts +1 -1
  195. package/src/autoresearch/storage.ts +2 -1
  196. package/src/autoresearch/tools/init-experiment.ts +1 -1
  197. package/src/autoresearch/tools/log-experiment.ts +1 -1
  198. package/src/autoresearch/tools/run-experiment.ts +1 -1
  199. package/src/autoresearch/tools/update-notes.ts +1 -1
  200. package/src/autoresearch/types.ts +1 -1
  201. package/src/cli/args.ts +56 -10
  202. package/src/cli/auth-gateway-cli.ts +1 -1
  203. package/src/cli/bench-cli.ts +1 -1
  204. package/src/cli/dry-balance-cli.ts +1 -1
  205. package/src/cli/models-cli.ts +427 -0
  206. package/src/cli/session-picker.ts +2 -1
  207. package/src/cli/setup-cli.ts +148 -47
  208. package/src/cli/setup-model-picker.ts +43 -0
  209. package/src/cli-commands.ts +3 -0
  210. package/src/cli.ts +45 -13
  211. package/src/collab/host.ts +10 -13
  212. package/src/collab/protocol.ts +1 -1
  213. package/src/commands/launch.ts +0 -3
  214. package/src/commands/models.ts +61 -0
  215. package/src/commands/say.ts +102 -0
  216. package/src/commands/setup.ts +1 -1
  217. package/src/commands/token.ts +89 -0
  218. package/src/commit/agentic/tools/analyze-file.ts +4 -1
  219. package/src/commit/agentic/tools/git-file-diff.ts +1 -1
  220. package/src/commit/agentic/tools/git-hunk.ts +1 -1
  221. package/src/commit/agentic/tools/git-overview.ts +1 -1
  222. package/src/commit/agentic/tools/propose-changelog.ts +1 -1
  223. package/src/commit/agentic/tools/propose-commit.ts +1 -1
  224. package/src/commit/agentic/tools/recent-commits.ts +1 -1
  225. package/src/commit/agentic/tools/schemas.ts +1 -1
  226. package/src/commit/agentic/tools/split-commit.ts +1 -1
  227. package/src/commit/analysis/summary.ts +1 -1
  228. package/src/commit/changelog/generate.ts +1 -1
  229. package/src/commit/shared-llm.ts +1 -1
  230. package/src/config/keybindings.ts +2 -2
  231. package/src/config/model-discovery.ts +11 -5
  232. package/src/config/model-registry.ts +79 -21
  233. package/src/config/model-resolver.ts +2 -2
  234. package/src/config/models-config-schema.ts +5 -2
  235. package/src/config/models-config.ts +2 -1
  236. package/src/config/settings-schema.ts +266 -32
  237. package/src/config/settings.ts +10 -0
  238. package/src/discovery/builtin.ts +23 -1
  239. package/src/discovery/claude-plugins.ts +44 -5
  240. package/src/discovery/helpers.ts +41 -1
  241. package/src/edit/hashline/params.ts +1 -1
  242. package/src/edit/modes/apply-patch.ts +1 -1
  243. package/src/edit/modes/patch.ts +1 -1
  244. package/src/edit/modes/replace.ts +1 -1
  245. package/src/eval/__tests__/budget-bridge.test.ts +1 -1
  246. package/src/eval/agent-bridge.ts +1 -1
  247. package/src/eval/completion-bridge.ts +1 -1
  248. package/src/eval/js/shared/prelude.txt +69 -17
  249. package/src/export/html/index.ts +3 -6
  250. package/src/export/html/template.js +24 -2
  251. package/src/export/html/tool-views.generated.js +2 -2
  252. package/src/extensibility/custom-commands/loader.ts +1 -1
  253. package/src/extensibility/custom-commands/types.ts +2 -2
  254. package/src/extensibility/custom-tools/loader.ts +1 -1
  255. package/src/extensibility/custom-tools/types.ts +2 -2
  256. package/src/extensibility/extensions/loader.ts +2 -2
  257. package/src/extensibility/extensions/model-api.ts +41 -0
  258. package/src/extensibility/extensions/runner.ts +4 -0
  259. package/src/extensibility/extensions/types.ts +54 -3
  260. package/src/extensibility/extensions/wrapper.ts +41 -5
  261. package/src/extensibility/hooks/index.ts +2 -1
  262. package/src/extensibility/hooks/loader.ts +1 -1
  263. package/src/extensibility/hooks/types.ts +2 -2
  264. package/src/extensibility/plugins/legacy-pi-compat.ts +43 -13
  265. package/src/extensibility/plugins/loader.ts +30 -19
  266. package/src/extensibility/plugins/manager.ts +221 -90
  267. package/src/extensibility/shared-events.ts +1 -1
  268. package/src/extensibility/skills.ts +101 -5
  269. package/src/goals/guided-setup.ts +133 -0
  270. package/src/goals/state.ts +1 -1
  271. package/src/goals/tools/goal-tool.ts +1 -1
  272. package/src/hindsight/transcript.ts +1 -1
  273. package/src/index.ts +5 -0
  274. package/src/internal-urls/docs-index.generated.ts +13 -10
  275. package/src/internal-urls/history-protocol.ts +1 -1
  276. package/src/internal-urls/local-protocol.ts +29 -7
  277. package/src/lsp/types.ts +1 -1
  278. package/src/main.ts +27 -32
  279. package/src/mcp/config-writer.ts +7 -3
  280. package/src/mcp/manager.ts +11 -0
  281. package/src/mcp/startup-events.ts +21 -0
  282. package/src/mcp/transports/stdio.ts +2 -1
  283. package/src/memories/index.ts +149 -12
  284. package/src/memories/storage.ts +2 -1
  285. package/src/memory-backend/local-backend.ts +11 -5
  286. package/src/mnemopi/backend.ts +1 -0
  287. package/src/mnemopi/config.ts +112 -12
  288. package/src/modes/acp/acp-agent.ts +8 -53
  289. package/src/modes/acp/acp-event-mapper.ts +5 -1
  290. package/src/modes/components/agent-hub.ts +51 -5
  291. package/src/modes/components/assistant-message.ts +12 -44
  292. package/src/modes/components/compaction-summary-message.ts +125 -26
  293. package/src/modes/components/custom-editor.test.ts +96 -0
  294. package/src/modes/components/custom-editor.ts +164 -8
  295. package/src/modes/components/index.ts +1 -0
  296. package/src/modes/components/logout-account-selector.ts +130 -0
  297. package/src/modes/components/mcp-add-wizard.ts +1 -1
  298. package/src/modes/components/model-selector.ts +2 -2
  299. package/src/modes/components/session-selector.ts +1 -1
  300. package/src/modes/components/settings-defs.ts +7 -0
  301. package/src/modes/components/status-line/component.ts +54 -157
  302. package/src/modes/components/status-line/segments.ts +1 -1
  303. package/src/modes/components/status-line/types.ts +2 -1
  304. package/src/modes/components/tool-execution.ts +82 -43
  305. package/src/modes/components/transcript-container.ts +70 -1
  306. package/src/modes/components/tree-selector.ts +1 -1
  307. package/src/modes/components/usage-row.ts +18 -0
  308. package/src/modes/components/user-message.ts +4 -2
  309. package/src/modes/controllers/command-controller.ts +14 -16
  310. package/src/modes/controllers/event-controller.ts +101 -73
  311. package/src/modes/controllers/extension-ui-controller.ts +6 -0
  312. package/src/modes/controllers/input-controller.ts +311 -57
  313. package/src/modes/controllers/mcp-command-controller.ts +44 -3
  314. package/src/modes/controllers/selector-controller.ts +68 -12
  315. package/src/modes/controllers/streaming-reveal.ts +4 -3
  316. package/src/modes/gradient-highlight.ts +21 -9
  317. package/src/modes/image-references.ts +20 -0
  318. package/src/modes/interactive-mode.ts +288 -48
  319. package/src/modes/magic-keywords.ts +27 -5
  320. package/src/modes/rpc/rpc-mode.ts +146 -14
  321. package/src/modes/rpc/rpc-subagents.ts +2 -2
  322. package/src/modes/rpc/rpc-types.ts +8 -2
  323. package/src/modes/runtime-init.ts +28 -3
  324. package/src/modes/theme/theme.ts +99 -51
  325. package/src/modes/types.ts +6 -7
  326. package/src/modes/utils/hotkeys-markdown.ts +1 -1
  327. package/src/modes/utils/ui-helpers.ts +36 -7
  328. package/src/priority.json +5 -1
  329. package/src/prompts/agents/task.md +1 -0
  330. package/src/prompts/goals/guided-goal-interview.md +8 -0
  331. package/src/prompts/goals/guided-goal-system.md +12 -0
  332. package/src/prompts/memories/read-path.md +6 -0
  333. package/src/prompts/system/autolearn-guidance-learn.md +1 -0
  334. package/src/prompts/system/autolearn-guidance.md +7 -0
  335. package/src/prompts/system/autolearn-nudge.md +3 -0
  336. package/src/prompts/system/eager-task.md +7 -0
  337. package/src/prompts/system/eager-todo.md +11 -6
  338. package/src/prompts/system/empty-stop-retry.md +4 -6
  339. package/src/prompts/system/subagent-system-prompt.md +4 -0
  340. package/src/prompts/system/system-prompt.md +10 -5
  341. package/src/prompts/system/title-marker-instruction.md +1 -0
  342. package/src/prompts/system/title-system-marker.md +16 -0
  343. package/src/prompts/tools/job.md +1 -0
  344. package/src/prompts/tools/learn.md +7 -0
  345. package/src/prompts/tools/manage-skill.md +9 -0
  346. package/src/prompts/tools/task.md +3 -0
  347. package/src/registry/agent-registry.ts +30 -0
  348. package/src/sdk.ts +103 -43
  349. package/src/secrets/obfuscator.ts +1 -1
  350. package/src/session/agent-session.ts +331 -318
  351. package/src/session/agent-storage.ts +18 -9
  352. package/src/session/history-storage.ts +3 -2
  353. package/src/session/indexed-session-storage.ts +7 -10
  354. package/src/session/messages.ts +9 -11
  355. package/src/session/session-context.ts +352 -0
  356. package/src/session/session-dump-format.ts +4 -2
  357. package/src/session/session-entries.ts +194 -0
  358. package/src/session/session-listing.ts +588 -0
  359. package/src/session/session-loader.ts +106 -0
  360. package/src/session/session-manager.ts +968 -3064
  361. package/src/session/session-migrations.ts +78 -0
  362. package/src/session/session-paths.ts +193 -0
  363. package/src/session/session-persistence.ts +131 -0
  364. package/src/session/session-storage.ts +91 -30
  365. package/src/session/snapcompact-inline.ts +21 -1
  366. package/src/session/snapcompact-savings-journal.ts +113 -0
  367. package/src/session/tool-choice-queue.ts +23 -11
  368. package/src/slash-commands/builtin-registry.ts +40 -4
  369. package/src/slash-commands/helpers/logout.ts +88 -0
  370. package/src/stt/asr-client.ts +520 -0
  371. package/src/stt/asr-protocol.ts +65 -0
  372. package/src/stt/asr-worker.ts +790 -0
  373. package/src/stt/downloader.ts +107 -47
  374. package/src/stt/endpointer.ts +259 -0
  375. package/src/stt/index.ts +5 -1
  376. package/src/stt/models.ts +150 -0
  377. package/src/stt/recorder.ts +247 -60
  378. package/src/stt/stt-controller.ts +201 -22
  379. package/src/stt/transcriber.ts +37 -68
  380. package/src/stt/wav.ts +173 -0
  381. package/src/system-prompt.ts +8 -0
  382. package/src/task/agents.ts +1 -2
  383. package/src/task/executor.ts +49 -15
  384. package/src/task/index.ts +60 -6
  385. package/src/task/render.ts +83 -8
  386. package/src/task/types.ts +54 -1
  387. package/src/tools/ask.ts +9 -1
  388. package/src/tools/ast-edit.ts +1 -1
  389. package/src/tools/ast-grep.ts +1 -1
  390. package/src/tools/bash.ts +5 -4
  391. package/src/tools/browser/cmux/cmux-tab.ts +1264 -0
  392. package/src/tools/browser/cmux/rpc.ts +156 -0
  393. package/src/tools/browser/cmux/socket-client.ts +309 -0
  394. package/src/tools/browser/registry.ts +37 -3
  395. package/src/tools/browser/render.ts +6 -1
  396. package/src/tools/browser/tab-protocol.ts +2 -0
  397. package/src/tools/browser/tab-supervisor.ts +189 -18
  398. package/src/tools/browser/tab-worker.ts +1 -1
  399. package/src/tools/browser.ts +16 -1
  400. package/src/tools/checkpoint.ts +1 -1
  401. package/src/tools/debug.ts +1 -1
  402. package/src/tools/eval-render.ts +4 -3
  403. package/src/tools/eval.ts +11 -6
  404. package/src/tools/fetch.ts +13 -2
  405. package/src/tools/find.ts +1 -1
  406. package/src/tools/gh.ts +1 -1
  407. package/src/tools/github-cache.ts +2 -1
  408. package/src/tools/image-gen.ts +1 -1
  409. package/src/tools/index.ts +43 -5
  410. package/src/tools/inspect-image.ts +3 -1
  411. package/src/tools/irc.ts +11 -3
  412. package/src/tools/job.ts +15 -3
  413. package/src/tools/learn.ts +144 -0
  414. package/src/tools/manage-skill.ts +104 -0
  415. package/src/tools/memory-edit.ts +1 -1
  416. package/src/tools/memory-recall.ts +1 -1
  417. package/src/tools/memory-reflect.ts +1 -1
  418. package/src/tools/memory-retain.ts +1 -1
  419. package/src/tools/plan-mode-guard.ts +53 -19
  420. package/src/tools/read.ts +8 -2
  421. package/src/tools/render-mermaid.ts +1 -1
  422. package/src/tools/renderers.ts +7 -11
  423. package/src/tools/report-tool-issue.ts +3 -2
  424. package/src/tools/resolve.ts +1 -1
  425. package/src/tools/review.ts +1 -1
  426. package/src/tools/search-tool-bm25.ts +1 -1
  427. package/src/tools/search.ts +1 -1
  428. package/src/tools/ssh.ts +5 -4
  429. package/src/tools/todo.ts +2 -2
  430. package/src/tools/tts.ts +204 -93
  431. package/src/tools/write.ts +19 -3
  432. package/src/tts/downloader.ts +64 -0
  433. package/src/tts/index.ts +8 -0
  434. package/src/tts/models.ts +137 -0
  435. package/src/tts/player.ts +137 -0
  436. package/src/tts/runtime.ts +21 -0
  437. package/src/tts/streaming-player.ts +266 -0
  438. package/src/tts/tts-client.ts +647 -0
  439. package/src/tts/tts-protocol.ts +60 -0
  440. package/src/tts/tts-worker.ts +497 -0
  441. package/src/tts/vocalizer.ts +162 -0
  442. package/src/tts/wav.ts +58 -0
  443. package/src/utils/clipboard.ts +35 -18
  444. package/src/utils/image-loading.ts +35 -4
  445. package/src/utils/thinking-display.ts +37 -0
  446. package/src/utils/title-generator.ts +48 -5
  447. package/src/utils/tool-choice.ts +16 -0
  448. package/src/utils/tools-manager.test.ts +25 -0
  449. package/src/utils/tools-manager.ts +19 -1
  450. package/src/web/scrapers/github.ts +96 -0
  451. package/src/web/search/index.ts +14 -1
  452. package/src/web/search/providers/searxng.ts +13 -1
  453. package/dist/types/cli/list-models.d.ts +0 -30
  454. package/dist/types/stt/setup.d.ts +0 -18
  455. package/src/cli/list-models.ts +0 -194
  456. package/src/stt/setup.ts +0 -52
  457. package/src/stt/transcribe.py +0 -70
@@ -64,10 +64,12 @@ import type {
64
64
  } from "../extensibility/extensions";
65
65
  import type { CompactOptions } from "../extensibility/extensions/types";
66
66
  import { loadSlashCommands } from "../extensibility/slash-commands";
67
+ import { type GuidedGoalMessage, runGuidedGoalTurn } from "../goals/guided-setup";
67
68
  import type { Goal, GoalModeState } from "../goals/state";
68
69
  import { resolveLocalUrlToPath } from "../internal-urls";
69
70
  import { LSP_STARTUP_EVENT_CHANNEL, type LspStartupEvent } from "../lsp/startup-events";
70
71
  import type { MCPManager } from "../mcp";
72
+ import { formatMCPConnectingMessage, isMcpConnectingEvent, MCP_CONNECTING_EVENT_CHANNEL } from "../mcp/startup-events";
71
73
  import {
72
74
  humanizePlanTitle,
73
75
  type PlanApprovalDetails,
@@ -80,8 +82,9 @@ import planModeCompactInstructionsPrompt from "../prompts/system/plan-mode-compa
80
82
  };
81
83
  import type { AgentSession, AgentSessionEvent, ResolvedRoleModel } from "../session/agent-session";
82
84
  import { HistoryStorage } from "../session/history-storage";
83
- import type { SessionContext, SessionManager } from "../session/session-manager";
84
- import { getRecentSessions } from "../session/session-manager";
85
+ import type { SessionContext } from "../session/session-context";
86
+ import { getRecentSessions } from "../session/session-listing";
87
+ import type { SessionManager } from "../session/session-manager";
85
88
  import type { ShakeMode } from "../session/shake-types";
86
89
  import { BUILTIN_SLASH_COMMAND_RESERVED_NAMES, BUILTIN_SLASH_COMMANDS } from "../slash-commands/builtin-registry";
87
90
  import { formatDuration } from "../slash-commands/helpers/format";
@@ -95,6 +98,7 @@ import { setAutoQaConsentHandler } from "../tools/report-tool-issue";
95
98
  import { type ResolveToolDetails, runResolveInvocation } from "../tools/resolve";
96
99
  import { formatPhaseDisplayName, selectStickyTodoWindow, todoMatchesAnyDescription } from "../tools/todo";
97
100
  import { ToolError } from "../tools/tool-errors";
101
+ import { vocalizer } from "../tts/vocalizer";
98
102
  import type { EventBus } from "../utils/event-bus";
99
103
  import { getEditorCommand, openInEditor } from "../utils/external-editor";
100
104
  import { getSessionAccentAnsi, getSessionAccentHex } from "../utils/session-color";
@@ -210,6 +214,25 @@ const EDITOR_MAX_HEIGHT_MIN = 6;
210
214
  const EDITOR_MAX_HEIGHT_MAX = 18;
211
215
  const EDITOR_RESERVED_ROWS = 12;
212
216
  const EDITOR_FALLBACK_ROWS = 24;
217
+ const EDITOR_MIN_CHROME_ROWS = 4; // rows reserved for transcript + status on small terms
218
+ const EDITOR_MIN_RENDERED_ROWS = 3; // bordered editor floor: top+bottom border + 1 content row
219
+
220
+ /**
221
+ * Editor max-height cap for a terminal of `terminalRows` rows.
222
+ *
223
+ * Roomy terminals get the comfortable [6, 18] band. Small terminals shrink the
224
+ * cap so the editor leaves at least EDITOR_MIN_CHROME_ROWS rows for the
225
+ * transcript + status line. The editor is bordered, so it never renders fewer
226
+ * than EDITOR_MIN_RENDERED_ROWS rows; once the terminal is too small for both
227
+ * (terminalRows < EDITOR_MIN_RENDERED_ROWS + EDITOR_MIN_CHROME_ROWS) the cap is
228
+ * pinned to that floor — returning a smaller number would not shrink the editor
229
+ * any further, it would only misreport the rows it actually occupies.
230
+ */
231
+ export function computeEditorMaxHeight(terminalRows: number): number {
232
+ const rows = Number.isFinite(terminalRows) && terminalRows > 0 ? terminalRows : EDITOR_FALLBACK_ROWS;
233
+ const comfortable = Math.max(EDITOR_MAX_HEIGHT_MIN, Math.min(EDITOR_MAX_HEIGHT_MAX, rows - EDITOR_RESERVED_ROWS));
234
+ return Math.max(EDITOR_MIN_RENDERED_ROWS, Math.min(comfortable, rows - EDITOR_MIN_CHROME_ROWS));
235
+ }
213
236
 
214
237
  const HUD_NOTE_SUP_DIGITS: Record<string, string> = {
215
238
  "0": "\u2070",
@@ -282,6 +305,10 @@ class StatusContainer extends Container implements NativeScrollbackLiveRegion {
282
305
  }
283
306
  }
284
307
 
308
+ /** How long the ctrl+p model-role cycle chip track lingers above the editor
309
+ * before it auto-clears, mirroring the todo HUD's auto-clear timer. */
310
+ const MODEL_CYCLE_TRACK_CLEAR_MS = 4000;
311
+
285
312
  /**
286
313
  * Build the anchored subagent HUD block: a bold accent "Subagents" header plus
287
314
  * one hooked row per running agent in the same `Id: description` shape the
@@ -340,6 +367,7 @@ export class InteractiveMode implements InteractiveModeContext {
340
367
  btwContainer: Container;
341
368
  omfgContainer: Container;
342
369
  errorBannerContainer: Container;
370
+ modelCycleContainer: Container;
343
371
  editor: CustomEditor;
344
372
  editorContainer: Container;
345
373
  hookWidgetContainerAbove: Container;
@@ -360,6 +388,7 @@ export class InteractiveMode implements InteractiveModeContext {
360
388
  loopLimit: LoopLimitRuntime | undefined = undefined;
361
389
  #loopAutoSubmitTimer: NodeJS.Timeout | undefined;
362
390
  #todoAutoClearTimer: NodeJS.Timeout | undefined;
391
+ #modelCycleClearTimer: NodeJS.Timeout | undefined;
363
392
  todoPhases: TodoPhase[] = [];
364
393
  hideThinkingBlock = false;
365
394
  pendingImages: ImageContent[] = [];
@@ -383,8 +412,6 @@ export class InteractiveMode implements InteractiveModeContext {
383
412
  get #defaultWorkingMessage(): string {
384
413
  return `Working…${interruptHint()}`;
385
414
  }
386
- autoCompactionEscapeHandler?: () => void;
387
- retryEscapeHandler?: () => void;
388
415
  unsubscribe?: () => void;
389
416
  onInputCallback?: (input: SubmittedUserInput) => void;
390
417
  optimisticUserMessageSignature: string | undefined = undefined;
@@ -464,6 +491,8 @@ export class InteractiveMode implements InteractiveModeContext {
464
491
  }
465
492
  this.statusContainer.clear();
466
493
  this.pendingMessagesContainer.clear();
494
+ this.#cancelModelCycleClearTimer();
495
+ this.modelCycleContainer.clear();
467
496
  this.compactionQueuedMessages = [];
468
497
  this.streamingComponent = undefined;
469
498
  this.streamingMessage = undefined;
@@ -510,6 +539,15 @@ export class InteractiveMode implements InteractiveModeContext {
510
539
  this.#handleLspStartupEvent(data as LspStartupEvent);
511
540
  }),
512
541
  );
542
+ this.#eventBusUnsubscribers.push(
543
+ eventBus.on(MCP_CONNECTING_EVENT_CHANNEL, data => {
544
+ if (!isMcpConnectingEvent(data)) {
545
+ logger.warn("Ignoring malformed mcp:connecting event", { data });
546
+ return;
547
+ }
548
+ this.showStatus(formatMCPConnectingMessage(data.serverNames));
549
+ }),
550
+ );
513
551
  }
514
552
 
515
553
  this.ui = new TUI(new ProcessTerminal(), settings.get("showHardwareCursor"));
@@ -526,6 +564,7 @@ export class InteractiveMode implements InteractiveModeContext {
526
564
  this.btwContainer = new Container();
527
565
  this.omfgContainer = new Container();
528
566
  this.errorBannerContainer = new Container();
567
+ this.modelCycleContainer = new Container();
529
568
  this.editor = new CustomEditor(getEditorTheme());
530
569
  this.editor.setUseTerminalCursor(this.ui.getShowHardwareCursor());
531
570
  this.editor.setAutocompleteMaxVisible(settings.get("autocompleteMaxVisible"));
@@ -535,6 +574,7 @@ export class InteractiveMode implements InteractiveModeContext {
535
574
  this.editor.onAutocompleteUpdate = () => {
536
575
  this.ui.requestRender();
537
576
  };
577
+ this.editor.setShimmerRepaintHandler(() => this.ui.requestComponentRender(this.editor));
538
578
  this.#syncEditorMaxHeight();
539
579
  this.#resizeHandler = () => {
540
580
  this.#syncEditorMaxHeight();
@@ -694,6 +734,7 @@ export class InteractiveMode implements InteractiveModeContext {
694
734
  this.ui.addChild(this.btwContainer);
695
735
  this.ui.addChild(this.omfgContainer);
696
736
  this.ui.addChild(this.errorBannerContainer);
737
+ this.ui.addChild(this.modelCycleContainer);
697
738
  this.ui.addChild(this.statusLine); // Only renders hook statuses (main status in editor border)
698
739
  this.ui.addChild(this.hookWidgetContainerAbove);
699
740
  this.ui.addChild(this.editorContainer);
@@ -812,8 +853,30 @@ export class InteractiveMode implements InteractiveModeContext {
812
853
  name: cmd.name,
813
854
  description: cmd.description,
814
855
  }));
856
+ // Surface discovered prompt templates in the picker. AgentSession.prompt() expands
857
+ // `expandSlashCommand` before `expandPromptTemplate`, and builtin command
858
+ // execution resolves aliases before template expansion. Mirror that command
859
+ // resolution order by skipping templates whose names already appear in any
860
+ // builtin/hook/custom/skill/file command token.
861
+ const reservedNames = new Set<string>();
862
+ for (const command of this.#pendingSlashCommands) {
863
+ reservedNames.add(command.name);
864
+ for (const alias of command.aliases ?? []) reservedNames.add(alias);
865
+ }
866
+ for (const command of fileSlashCommands) {
867
+ reservedNames.add(command.name);
868
+ for (const alias of command.aliases ?? []) reservedNames.add(alias);
869
+ }
870
+ const promptTemplateCommands: SlashCommand[] = this.session.promptTemplates
871
+ .filter(template => !reservedNames.has(template.name))
872
+ .map(template => ({
873
+ name: template.name,
874
+ // `PromptTemplate.description` from `loadTemplatesFromDir` already includes the
875
+ // source suffix (e.g. "Review code (project)"), so pass it through verbatim.
876
+ description: template.description,
877
+ }));
815
878
  const autocompleteProvider = this.#inputController.createAutocompleteProvider(
816
- [...this.#pendingSlashCommands, ...fileSlashCommands],
879
+ [...this.#pendingSlashCommands, ...fileSlashCommands, ...promptTemplateCommands],
817
880
  basePath,
818
881
  );
819
882
  this.editor.setAutocompleteProvider(autocompleteProvider);
@@ -907,6 +970,14 @@ export class InteractiveMode implements InteractiveModeContext {
907
970
  this.#goalContinuationTimer = undefined;
908
971
  if (!this.onInputCallback) return;
909
972
  if (!this.goalModeEnabled || this.goalModePaused) return;
973
+ // The 800ms timer can outlive the idle window that scheduled it: a
974
+ // `/goal set` taken via the streaming branch (or any extension/hook
975
+ // path that starts a turn while we wait) leaves the agent busy. Firing
976
+ // the continuation now would route through `submitInteractiveInput` →
977
+ // `promptCustomMessage` with no `streamingBehavior` and resurface
978
+ // `AgentBusyError`. Drop this tick; `#handleGoalSessionEvent` reschedules
979
+ // on the next `agent_end`.
980
+ if (this.#isAutoSubmitBlocked()) return;
910
981
  if (this.#pendingSubmittedInput) return;
911
982
  if (this.editor.getText().trim().length > 0) return;
912
983
  if ((this.pendingImages?.length ?? 0) > 0) return;
@@ -930,7 +1001,7 @@ export class InteractiveMode implements InteractiveModeContext {
930
1001
  }
931
1002
  }
932
1003
 
933
- #isLoopAutoSubmitBlocked(): boolean {
1004
+ #isAutoSubmitBlocked(): boolean {
934
1005
  return this.session.isStreaming || this.session.isCompacting || this.session.hasPostPromptWork;
935
1006
  }
936
1007
 
@@ -940,7 +1011,7 @@ export class InteractiveMode implements InteractiveModeContext {
940
1011
  this.disableLoopMode("Loop time limit reached. Loop mode disabled.");
941
1012
  return;
942
1013
  }
943
- if (this.#isLoopAutoSubmitBlocked()) {
1014
+ if (this.#isAutoSubmitBlocked()) {
944
1015
  this.#deferLoopAutoSubmit(() => this.#submitLoopPromptWhenReady(prompt));
945
1016
  return;
946
1017
  }
@@ -949,7 +1020,7 @@ export class InteractiveMode implements InteractiveModeContext {
949
1020
 
950
1021
  async #runLoopIteration(action: "prompt" | "compact" | "reset", prompt: string): Promise<void> {
951
1022
  if (!this.loopModeEnabled || this.loopPrompt !== prompt || !this.onInputCallback) return;
952
- if (this.#isLoopAutoSubmitBlocked()) {
1023
+ if (this.#isAutoSubmitBlocked()) {
953
1024
  this.#deferLoopAutoSubmit(() => {
954
1025
  void this.#runLoopIteration(action, prompt);
955
1026
  });
@@ -1142,10 +1213,7 @@ export class InteractiveMode implements InteractiveModeContext {
1142
1213
  }
1143
1214
 
1144
1215
  #computeEditorMaxHeight(): number {
1145
- const rows = this.ui.terminal.rows;
1146
- const terminalRows = Number.isFinite(rows) && rows > 0 ? rows : EDITOR_FALLBACK_ROWS;
1147
- const maxHeight = terminalRows - EDITOR_RESERVED_ROWS;
1148
- return Math.max(EDITOR_MAX_HEIGHT_MIN, Math.min(EDITOR_MAX_HEIGHT_MAX, maxHeight));
1216
+ return computeEditorMaxHeight(this.ui.terminal.rows);
1149
1217
  }
1150
1218
 
1151
1219
  #syncEditorMaxHeight(): void {
@@ -1345,6 +1413,41 @@ export class InteractiveMode implements InteractiveModeContext {
1345
1413
  this.#todoAutoClearTimer.unref?.();
1346
1414
  }
1347
1415
 
1416
+ /**
1417
+ * Render the ctrl+p model-role cycle chip track into its own anchored
1418
+ * container (just above the editor), mirroring the todo HUD: the container is
1419
+ * cleared and rebuilt in place on every cycle, so rapid presses or concurrent
1420
+ * chat activity can never stack duplicate tracks into the scrollback.
1421
+ */
1422
+ showModelCycleTrack(track: string): void {
1423
+ this.#renderModelCycleTrack(track);
1424
+ this.#syncModelCycleClearTimer();
1425
+ this.ui.requestRender();
1426
+ }
1427
+
1428
+ #renderModelCycleTrack(track: string | null): void {
1429
+ this.modelCycleContainer.clear();
1430
+ if (!track) return;
1431
+ this.modelCycleContainer.addChild(new Spacer(1));
1432
+ this.modelCycleContainer.addChild(new Text(track, 1, 0));
1433
+ }
1434
+
1435
+ #cancelModelCycleClearTimer(): void {
1436
+ if (!this.#modelCycleClearTimer) return;
1437
+ clearTimeout(this.#modelCycleClearTimer);
1438
+ this.#modelCycleClearTimer = undefined;
1439
+ }
1440
+
1441
+ #syncModelCycleClearTimer(): void {
1442
+ this.#cancelModelCycleClearTimer();
1443
+ this.#modelCycleClearTimer = setTimeout(() => {
1444
+ this.#modelCycleClearTimer = undefined;
1445
+ this.#renderModelCycleTrack(null);
1446
+ this.ui.requestRender();
1447
+ }, MODEL_CYCLE_TRACK_CLEAR_MS);
1448
+ this.#modelCycleClearTimer.unref?.();
1449
+ }
1450
+
1348
1451
  #getActivePhase(phases: TodoPhase[]): TodoPhase | undefined {
1349
1452
  const nonEmpty = phases.filter(phase => phase.tasks.length > 0);
1350
1453
  const active = nonEmpty.find(phase =>
@@ -1738,7 +1841,40 @@ export class InteractiveMode implements InteractiveModeContext {
1738
1841
  });
1739
1842
  }
1740
1843
 
1741
- async #exitPlanMode(options?: { silent?: boolean; paused?: boolean }): Promise<void> {
1844
+ async #restorePlanPreviousModel(prev: { model: Model; thinkingLevel?: ThinkingLevel }): Promise<void> {
1845
+ if (modelsAreEqual(this.session.model, prev.model)) {
1846
+ // Same model — only thinking level may differ. Avoid setModelTemporary()
1847
+ // which would reset provider-side sessions and break continuity.
1848
+ this.session.setThinkingLevel(prev.thinkingLevel);
1849
+ } else if (this.session.isStreaming) {
1850
+ this.#pendingModelSwitch = { model: prev.model, thinkingLevel: prev.thinkingLevel };
1851
+ } else {
1852
+ await this.session.setModelTemporary(prev.model, prev.thinkingLevel);
1853
+ }
1854
+ }
1855
+
1856
+ /**
1857
+ * Idempotent post-compaction model transition for the plan-approval compact
1858
+ * path. The deferred pre-plan state is consumed on first application, so a
1859
+ * second call (the before-flush hook vs. the short-circuit fallback) is a
1860
+ * no-op. "failed" intentionally stays on the plan model — the context is
1861
+ * intact and we dispatch best-effort.
1862
+ */
1863
+ async #applyDeferredPlanModelTransition(
1864
+ outcome: CompactionOutcome | undefined,
1865
+ executionModel: ResolvedRoleModel | undefined,
1866
+ ): Promise<void> {
1867
+ const deferredPrev = this.#planModePreviousModelState;
1868
+ if (deferredPrev === undefined || outcome === "failed") return;
1869
+ this.#planModePreviousModelState = undefined;
1870
+ if (executionModel) {
1871
+ await this.#applyPlanExecutionModel(executionModel);
1872
+ } else {
1873
+ await this.#restorePlanPreviousModel(deferredPrev);
1874
+ }
1875
+ }
1876
+
1877
+ async #exitPlanMode(options?: { silent?: boolean; paused?: boolean; deferModelRestore?: boolean }): Promise<void> {
1742
1878
  if (!this.planModeEnabled) {
1743
1879
  return;
1744
1880
  }
@@ -1748,23 +1884,18 @@ export class InteractiveMode implements InteractiveModeContext {
1748
1884
  await this.session.setActiveToolsByName(previousTools);
1749
1885
  }
1750
1886
  if (this.#planModePreviousModelState) {
1751
- const prev = this.#planModePreviousModelState;
1752
- if (modelsAreEqual(this.session.model, prev.model)) {
1753
- // Same model — only thinking level may differ. Avoid setModelTemporary()
1754
- // which would reset provider-side sessions (openai-responses/Codex) and
1755
- // break conversation continuity.
1756
- this.session.setThinkingLevel(prev.thinkingLevel);
1757
- } else if (this.session.isStreaming) {
1758
- this.#pendingModelSwitch = { model: prev.model, thinkingLevel: prev.thinkingLevel };
1759
- } else {
1760
- await this.session.setModelTemporary(prev.model, prev.thinkingLevel);
1887
+ if (!options?.deferModelRestore) {
1888
+ await this.#restorePlanPreviousModel(this.#planModePreviousModelState);
1761
1889
  }
1762
1890
  // If #applyPlanModeModel queued a deferred switch to the plan-role model
1763
1891
  // (because the session was streaming on entry), drop it now: we are
1764
1892
  // leaving plan mode, so flushing it on the next agent_end would land the
1765
1893
  // session on the plan-role model after the user has exited plan mode
1766
- // (issue #816). Only clear when the pending target matches the plan-role
1767
- // model leave any unrelated user-queued switch intact.
1894
+ // (issue #816). This runs even when deferModelRestore is set
1895
+ // (compact-approval path): otherwise the stale plan switch survives and
1896
+ // flushPendingModelSwitch() later clobbers the restored/execution model.
1897
+ // Only clear when the pending target matches the plan-role model — leave
1898
+ // any unrelated user-queued switch intact.
1768
1899
  const pending = this.#pendingModelSwitch;
1769
1900
  if (pending) {
1770
1901
  const planResolution = this.session.resolveRoleModelWithThinking("plan");
@@ -1779,7 +1910,7 @@ export class InteractiveMode implements InteractiveModeContext {
1779
1910
  this.planModePaused = options?.paused ?? false;
1780
1911
  this.planModePlanFilePath = undefined;
1781
1912
  this.#planModePreviousTools = undefined;
1782
- this.#planModePreviousModelState = undefined;
1913
+ if (!options?.deferModelRestore) this.#planModePreviousModelState = undefined;
1783
1914
  this.#updatePlanModeStatus();
1784
1915
  const paused = options?.paused ?? false;
1785
1916
  this.sessionManager.appendModeChange(paused ? "plan_paused" : "none");
@@ -2119,7 +2250,11 @@ export class InteractiveMode implements InteractiveModeContext {
2119
2250
  }
2120
2251
  let compactOutcome: CompactionOutcome | undefined;
2121
2252
  try {
2122
- await this.#exitPlanMode({ silent: true, paused: false });
2253
+ await this.#exitPlanMode({
2254
+ silent: true,
2255
+ paused: false,
2256
+ deferModelRestore: options.compactBeforeExecute === true,
2257
+ });
2123
2258
 
2124
2259
  if (!options.preserveContext) {
2125
2260
  await this.handleClearCommand();
@@ -2148,7 +2283,9 @@ export class InteractiveMode implements InteractiveModeContext {
2148
2283
  // the try/finally is idempotent and kept for the !compactBeforeExecute
2149
2284
  // branch.
2150
2285
  this.session.setPlanReferencePath(options.planFilePath);
2151
- compactOutcome = await this.handleCompactCommand(compactionPrompt);
2286
+ compactOutcome = await this.handleCompactCommand(compactionPrompt, outcome =>
2287
+ this.#applyDeferredPlanModelTransition(outcome, options.executionModel),
2288
+ );
2152
2289
  }
2153
2290
  } finally {
2154
2291
  // Unconditional clear. Idempotent: a no-op when the flag was never set
@@ -2165,22 +2302,33 @@ export class InteractiveMode implements InteractiveModeContext {
2165
2302
  }
2166
2303
  this.session.setPlanReferencePath(options.planFilePath);
2167
2304
 
2305
+ // Resolve the deferred plan-approval model transition. On the compact path
2306
+ // the before-flush hook passed to handleCompactCommand already ran this (so
2307
+ // any input queued during compaction executed on the post-compaction
2308
+ // model); the re-run here is idempotent and covers the short-circuit where
2309
+ // compaction never executed. It runs for "cancelled" too — the operator
2310
+ // aborted only the compaction, not the approval — so the next turn no longer
2311
+ // lands on the plan model. "failed" stays on the plan model (context
2312
+ // intact) and dispatches best-effort.
2313
+ if (options.compactBeforeExecute) {
2314
+ await this.#applyDeferredPlanModelTransition(compactOutcome, options.executionModel);
2315
+ } else {
2316
+ await this.#applyPlanExecutionModel(options.executionModel);
2317
+ }
2318
+
2168
2319
  if (compactOutcome === "cancelled") {
2169
2320
  // Explicit abort: honor it. `executeCompaction` already surfaced
2170
- // `showError("Compaction cancelled")` to the operator; we add the
2171
- // deferred-dispatch warning and exit. `markPlanReferenceSent` is
2172
- // intentionally skipped here: `#planReferenceSent` stays false, so
2173
- // `AgentSession.#buildPlanReferenceMessage` will inject the plan
2174
- // reference on the operator's next `prompt()` call. If we marked it
2175
- // sent here, the executor's first turn would have no plan context.
2321
+ // `showError("Compaction cancelled")`; we add the deferred-dispatch
2322
+ // warning and exit without dispatching the synthetic plan-approved
2323
+ // prompt. `markPlanReferenceSent` stays unset so
2324
+ // `AgentSession.#buildPlanReferenceMessage` injects the plan reference
2325
+ // on the operator's next `prompt()` call.
2176
2326
  this.showWarning(
2177
2327
  "Plan approved, but compaction was cancelled — execution not dispatched. Submit a turn to continue.",
2178
2328
  );
2179
2329
  return;
2180
2330
  }
2181
2331
 
2182
- await this.#applyPlanExecutionModel(options.executionModel);
2183
-
2184
2332
  // Approved plans land in a fresh (or compacted) session whose first user-visible
2185
2333
  // turn is the synthetic plan-approved prompt — that path bypasses the
2186
2334
  // input-controller's title generation. Seed an auto-name from the plan title
@@ -2203,6 +2351,15 @@ export class InteractiveMode implements InteractiveModeContext {
2203
2351
  planFilePath: options.planFilePath,
2204
2352
  contextPreserved: options.preserveContext === true,
2205
2353
  });
2354
+ // The executor's first turn must start on an idle session. The agent may still
2355
+ // be streaming the post-`resolve` continuation (Agent.#emit is fire-and-forget)
2356
+ // or a turn kicked off by the compaction/clear above; prompt() would then throw
2357
+ // AgentBusyError ("Failed to finalize approved plan"). Abort the now-irrelevant
2358
+ // in-flight turn first — abort() bumps the prompt generation and cancels pending
2359
+ // continuations, so nothing re-streams in the synchronous gap before prompt().
2360
+ if (this.session.isStreaming) {
2361
+ await this.session.abort();
2362
+ }
2206
2363
  await this.session.prompt(planModePrompt, { synthetic: true });
2207
2364
  }
2208
2365
 
@@ -2223,6 +2380,19 @@ export class InteractiveMode implements InteractiveModeContext {
2223
2380
  await this.#exitPlanMode({ paused: true });
2224
2381
  return;
2225
2382
  }
2383
+ if (this.planModePaused && !initialPrompt) {
2384
+ // No-arg third toggle: paused → off. Tools, model, and plan state were
2385
+ // already restored by the prior #exitPlanMode({ paused: true }); only the
2386
+ // paused flag, the reentry marker, and the session mode entry remain.
2387
+ // Prompted /plan invocations fall through to #enterPlanMode below so the
2388
+ // supplied prompt is still submitted as the first plan-mode turn.
2389
+ this.planModePaused = false;
2390
+ this.#planModeHasEntered = false;
2391
+ this.#updatePlanModeStatus();
2392
+ this.sessionManager.appendModeChange("none");
2393
+ this.showStatus("Plan mode disabled.");
2394
+ return;
2395
+ }
2226
2396
  if (!this.session.settings.get("plan.enabled")) {
2227
2397
  this.showWarning("Plan mode is disabled. Enable it in settings (plan.enabled).");
2228
2398
  return;
@@ -2304,6 +2474,70 @@ export class InteractiveMode implements InteractiveModeContext {
2304
2474
  this.showError(error instanceof Error ? error.message : String(error));
2305
2475
  }
2306
2476
  }
2477
+ async handleGuidedGoalCommand(rest?: string): Promise<void> {
2478
+ try {
2479
+ if (this.planModeEnabled || this.planModePaused) {
2480
+ this.showWarning("Exit plan mode first.");
2481
+ return;
2482
+ }
2483
+ if (!this.session.settings.get("goal.enabled")) {
2484
+ this.showWarning("Goal mode is disabled. Enable it in settings (goal.enabled).");
2485
+ return;
2486
+ }
2487
+ if (this.goalModeEnabled) {
2488
+ this.showStatus("Goal mode is already active. Use /goal to manage it, or /goal drop to start over.");
2489
+ return;
2490
+ }
2491
+ if (this.#getPausedGoalState()) {
2492
+ this.showWarning("Resume the current goal first, or drop it before setting a new objective.");
2493
+ return;
2494
+ }
2495
+
2496
+ const initial = rest?.trim()
2497
+ ? rest.trim()
2498
+ : (await this.showHookEditor("Guided goal", undefined, undefined, { promptStyle: true }))?.trim();
2499
+ if (!initial) return;
2500
+
2501
+ const messages: GuidedGoalMessage[] = [{ role: "user", content: initial }];
2502
+ let latestDraftObjective: string | undefined;
2503
+ for (let turn = 0; turn < 6; turn++) {
2504
+ const result = await runGuidedGoalTurn(this.session, { messages });
2505
+ if (result.objective?.trim()) latestDraftObjective = result.objective.trim();
2506
+ if (result.kind === "question") {
2507
+ messages.push({ role: "assistant", content: result.question });
2508
+ const answer = (
2509
+ await this.showHookEditor(result.question, undefined, undefined, { promptStyle: true })
2510
+ )?.trim();
2511
+ if (!answer) return;
2512
+ messages.push({ role: "user", content: answer });
2513
+ continue;
2514
+ }
2515
+
2516
+ const finalObjective = (
2517
+ await this.showHookEditor("Review guided goal", result.objective, undefined, { promptStyle: true })
2518
+ )?.trim();
2519
+ if (!finalObjective) return;
2520
+ await this.#startGoalFromObjective(finalObjective);
2521
+ return;
2522
+ }
2523
+
2524
+ // Hit the turn cap without an explicit `ready`. Rather than discard the whole interview,
2525
+ // salvage the latest non-empty model objective draft seen on any earlier turn. A final
2526
+ // question turn may omit `objective`; that must not erase a usable draft.
2527
+ if (latestDraftObjective) {
2528
+ const finalObjective = (
2529
+ await this.showHookEditor("Review guided goal", latestDraftObjective, undefined, { promptStyle: true })
2530
+ )?.trim();
2531
+ if (finalObjective) {
2532
+ await this.#startGoalFromObjective(finalObjective);
2533
+ return;
2534
+ }
2535
+ }
2536
+ this.showWarning("Guided goal setup needs more detail. Run /guided-goal again with a narrower objective.");
2537
+ } catch (error) {
2538
+ this.showError(error instanceof Error ? error.message : String(error));
2539
+ }
2540
+ }
2307
2541
 
2308
2542
  async #dispatchGoalSubcommand(sub: GoalSubcommand, rest: string): Promise<void> {
2309
2543
  switch (sub) {
@@ -2437,7 +2671,7 @@ export class InteractiveMode implements InteractiveModeContext {
2437
2671
  async #startGoalFromObjective(objective: string): Promise<void> {
2438
2672
  await this.#enterGoalMode({ objective, silent: true });
2439
2673
  this.#resetGoalContinuationSuppression();
2440
- if (this.onInputCallback) {
2674
+ if (!this.session.isStreaming && this.onInputCallback) {
2441
2675
  this.onInputCallback(this.startPendingSubmission({ text: objective }));
2442
2676
  }
2443
2677
  }
@@ -2452,7 +2686,7 @@ export class InteractiveMode implements InteractiveModeContext {
2452
2686
  if (this.session.isStreaming) {
2453
2687
  await this.session.sendGoalModeContext({ deliverAs: "steer" });
2454
2688
  }
2455
- if (this.onInputCallback) {
2689
+ if (!this.session.isStreaming && this.onInputCallback) {
2456
2690
  this.onInputCallback(this.startPendingSubmission({ text: objective }));
2457
2691
  }
2458
2692
  }
@@ -2587,11 +2821,13 @@ export class InteractiveMode implements InteractiveModeContext {
2587
2821
  return;
2588
2822
  }
2589
2823
  // Capture the operator's tier choice and hand it to #approvePlan, which
2590
- // applies it AFTER #exitPlanMode. #exitPlanMode restores
2824
+ // applies it AFTER #exitPlanMode. #exitPlanMode normally restores
2591
2825
  // #planModePreviousModelState (the model from before plan mode), so
2592
2826
  // applying the slider choice any earlier would be silently reverted —
2593
2827
  // the bug that made "continue with slow" keep executing on the default
2594
- // model. Deferred application also survives newSession()/compaction.
2828
+ // model. For compact-context approval, the plan model is kept through
2829
+ // compaction, then a successful compaction transitions to the slider model
2830
+ // (or restores the pre-plan model when no slider choice was made).
2595
2831
  // `cycle.currentIndex` is exactly that restored model, so any chosen tier
2596
2832
  // differing from it needs an explicit executionModel — this also covers
2597
2833
  // leaving the slider on its `default` anchor while planning ran elsewhere.
@@ -2792,6 +3028,7 @@ export class InteractiveMode implements InteractiveModeContext {
2792
3028
  nextEditor.onAutocompleteUpdate = () => {
2793
3029
  this.ui.requestRender();
2794
3030
  };
3031
+ nextEditor.setShimmerRepaintHandler(() => this.ui.requestComponentRender(this.editor));
2795
3032
  nextEditor.setMaxHeight(this.#computeEditorMaxHeight());
2796
3033
  if (this.historyStorage) {
2797
3034
  nextEditor.setHistoryStorage(this.historyStorage);
@@ -3025,10 +3262,6 @@ export class InteractiveMode implements InteractiveModeContext {
3025
3262
  this.setWorkingMessage(message);
3026
3263
  }
3027
3264
 
3028
- notifyInterrupting(): void {
3029
- this.#eventController.notifyInterrupting();
3030
- }
3031
-
3032
3265
  showNewVersionNotification(newVersion: string): void {
3033
3266
  this.#uiHelpers.showNewVersionNotification(newVersion);
3034
3267
  }
@@ -3187,7 +3420,11 @@ export class InteractiveMode implements InteractiveModeContext {
3187
3420
  await this.#sttController.toggle(this.editor, {
3188
3421
  showWarning: (msg: string) => this.showWarning(msg),
3189
3422
  showStatus: (msg: string) => this.showStatus(msg),
3423
+ requestRender: () => this.ui.requestRender(),
3190
3424
  onStateChange: (state: SttState) => {
3425
+ // Duck assistant speech while the user is talking (push-to-talk); restore after.
3426
+ if (state === "recording") vocalizer.duck();
3427
+ else vocalizer.unduck();
3191
3428
  if (state === "recording") {
3192
3429
  this.#voicePreviousShowHardwareCursor = this.ui.getShowHardwareCursor();
3193
3430
  this.#voicePreviousUseTerminalCursor = this.editor.getUseTerminalCursor();
@@ -3258,8 +3495,8 @@ export class InteractiveMode implements InteractiveModeContext {
3258
3495
  await this.#selectorController.showDebugSelector();
3259
3496
  }
3260
3497
 
3261
- showAgentHub(): void {
3262
- this.#selectorController.showAgentHub(this.#observerRegistry);
3498
+ showAgentHub(options?: { requireContent?: boolean }): void {
3499
+ this.#selectorController.showAgentHub(this.#observerRegistry, options);
3263
3500
  }
3264
3501
 
3265
3502
  resetObserverRegistry(): void {
@@ -3285,8 +3522,11 @@ export class InteractiveMode implements InteractiveModeContext {
3285
3522
  await controller.handle(text);
3286
3523
  }
3287
3524
 
3288
- handleCompactCommand(customInstructions?: string): Promise<CompactionOutcome> {
3289
- return this.#commandController.handleCompactCommand(customInstructions);
3525
+ handleCompactCommand(
3526
+ customInstructions?: string,
3527
+ beforeFlush?: (outcome: CompactionOutcome) => void | Promise<void>,
3528
+ ): Promise<CompactionOutcome> {
3529
+ return this.#commandController.handleCompactCommand(customInstructions, beforeFlush);
3290
3530
  }
3291
3531
 
3292
3532
  handleHandoffCommand(customInstructions?: string): Promise<void> {
@@ -1,6 +1,6 @@
1
- import { highlightOrchestrate } from "./orchestrate";
2
- import { highlightUltrathink } from "./ultrathink";
3
- import { highlightWorkflow } from "./workflow";
1
+ import { containsOrchestrate, highlightOrchestrate } from "./orchestrate";
2
+ import { containsUltrathink, highlightUltrathink } from "./ultrathink";
3
+ import { containsWorkflow, highlightWorkflow } from "./workflow";
4
4
 
5
5
  /**
6
6
  * Gradient-highlight every magic keyword ("ultrathink", "orchestrate",
@@ -14,7 +14,29 @@ import { highlightWorkflow } from "./workflow";
14
14
  * pass the surrounding text color when decorating already-colored content (e.g.
15
15
  * a themed message bubble) so the gradient does not bleed into the rest of the
16
16
  * line. Defaults to a plain foreground reset for default-colored editor text.
17
+ *
18
+ * `phase` ∈ [0, 1) cyclically rotates each gradient — the editor passes a
19
+ * `Date.now()`-derived value to animate a Claude-Code-style shimmer while a
20
+ * keyword is on screen and the prompt is focused; sent message bubbles omit it
21
+ * to keep the static gradient.
22
+ */
23
+ export function highlightMagicKeywords(text: string, resetTo?: string, phase?: number): string {
24
+ return highlightWorkflow(
25
+ highlightOrchestrate(highlightUltrathink(text, resetTo, phase), resetTo, phase),
26
+ resetTo,
27
+ phase,
28
+ );
29
+ }
30
+
31
+ /**
32
+ * Cheap test for "does this text contain any magic keyword as standalone prose?".
33
+ * Short-circuits on a substring probe before paying for the markdown-aware
34
+ * prose check, so the common "no keyword in buffer" path is just three
35
+ * `String#indexOf`s. Used by the live editor to gate the shimmer timer.
17
36
  */
18
- export function highlightMagicKeywords(text: string, resetTo?: string): string {
19
- return highlightWorkflow(highlightOrchestrate(highlightUltrathink(text, resetTo), resetTo), resetTo);
37
+ export function hasMagicKeyword(text: string): boolean {
38
+ if (!text.includes("ultrathink") && !text.includes("orchestrate") && !text.includes("workflowz")) {
39
+ return false;
40
+ }
41
+ return containsUltrathink(text) || containsOrchestrate(text) || containsWorkflow(text);
20
42
  }