mixdog 1.1.2 → 1.2.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 (424) hide show
  1. package/README.md +8 -5
  2. package/package.json +2 -1
  3. package/scripts/lib/run-node-tests.mjs +9 -0
  4. package/scripts/prune-desktop-runtime.mjs +3 -0
  5. package/scripts/test-direct.mjs +4 -2
  6. package/src/defaults/shipped-history.json +10 -0
  7. package/src/defaults/skills/pptx/SKILL.md +6 -6
  8. package/src/defaults/skills/pptx/references/review.md +6 -4
  9. package/src/defaults/skills/pptx/scripts/source-extract.mjs +1 -1
  10. package/src/defaults/skills/setup/references/actions.md +18 -5
  11. package/src/defaults/skills/visualize/SKILL.md +160 -0
  12. package/src/defaults/skills/visualize/references/craft.md +88 -0
  13. package/src/headless-command.mjs +56 -3
  14. package/src/headless-exec.mjs +17 -1
  15. package/src/help.mjs +2 -0
  16. package/src/rules/shared/60-verification.md +3 -1
  17. package/src/rules/shared/70-delivery.md +4 -2
  18. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -0
  19. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +1 -0
  20. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +2 -0
  21. package/src/runtime/agent/orchestrator/cache-mtime.mjs +26 -5
  22. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  23. package/src/runtime/agent/orchestrator/config-providers-merge.mjs +17 -11
  24. package/src/runtime/agent/orchestrator/config-storage.mjs +1 -0
  25. package/src/runtime/agent/orchestrator/config.mjs +19 -5
  26. package/src/runtime/agent/orchestrator/context/collect.mjs +2 -1
  27. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +11 -5
  28. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +74 -21
  29. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +133 -15
  30. package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +55 -8
  31. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +86 -8
  32. package/src/runtime/agent/orchestrator/mcp/client.mjs +474 -164
  33. package/src/runtime/agent/orchestrator/mcp/lazy-output-validator.mjs +25 -0
  34. package/src/runtime/agent/orchestrator/mcp/plugin-placeholders.mjs +54 -0
  35. package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +4 -0
  36. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +108 -17
  37. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  38. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +3 -0
  39. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +15 -1
  40. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -12
  41. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -0
  42. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +9 -0
  43. package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +337 -0
  44. package/src/runtime/agent/orchestrator/providers/command-code-oauth-tokens.mjs +159 -0
  45. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +444 -0
  46. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +2 -0
  47. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  48. package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +2 -1
  49. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +7 -4
  50. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +21 -6
  51. package/src/runtime/agent/orchestrator/providers/effort-rejection.mjs +244 -0
  52. package/src/runtime/agent/orchestrator/providers/gemini.mjs +41 -28
  53. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  54. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
  55. package/src/runtime/agent/orchestrator/providers/lib/gemini-model-catalog.mjs +7 -1
  56. package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +8 -40
  57. package/src/runtime/agent/orchestrator/providers/mimo-oauth-tokens.mjs +2 -0
  58. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +51 -12
  59. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +37 -2
  60. package/src/runtime/agent/orchestrator/providers/model-proxy-bridge.mjs +1080 -0
  61. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +82 -0
  62. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +556 -0
  63. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +251 -0
  64. package/src/runtime/agent/orchestrator/providers/muse-code-oauth-tokens.mjs +2 -0
  65. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +69 -102
  66. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +15 -0
  67. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +6 -5
  68. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -1
  69. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +6 -2
  70. package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +11 -0
  71. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +2 -1
  72. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +15 -4
  73. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs +1 -1
  74. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +5 -2
  75. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +3 -0
  76. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +4 -4
  77. package/src/runtime/agent/orchestrator/providers/registry.mjs +47 -7
  78. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +10 -0
  79. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +36 -3
  80. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +15 -11
  81. package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +15 -55
  82. package/src/runtime/agent/orchestrator/runtime-core/goal-control.mjs +15 -2
  83. package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +16 -1
  84. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +278 -84
  85. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +4 -1
  86. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +21 -0
  87. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +54 -3
  88. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +10 -3
  89. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +10 -0
  90. package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +9 -5
  91. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +18 -8
  92. package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +284 -0
  93. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +268 -0
  94. package/src/runtime/agent/orchestrator/session/catalog-db/sqlite-gate.mjs +27 -0
  95. package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +628 -0
  96. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +10 -3
  97. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +24 -13
  98. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +13 -5
  99. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +362 -0
  100. package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +253 -0
  101. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +184 -0
  102. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +136 -0
  103. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +128 -0
  104. package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +127 -0
  105. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +101 -0
  106. package/src/runtime/agent/orchestrator/session/explicit-skills.mjs +83 -5
  107. package/src/runtime/agent/orchestrator/session/imported-message.mjs +11 -0
  108. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +2 -1
  109. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -1
  110. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +1 -1
  111. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/diagnostic.mjs +1 -7
  112. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/refusal.mjs +1 -1
  113. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +3 -2
  114. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +5 -3
  115. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +7 -4
  116. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +21 -10
  117. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -0
  118. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +3 -0
  119. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -3
  120. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +15 -3
  121. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +22 -12
  122. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +9 -2
  123. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +9 -3
  124. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +5 -2
  125. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +37 -16
  126. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +4 -0
  127. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +17 -4
  128. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +90 -0
  129. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +22 -12
  130. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +32 -3
  131. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +27 -0
  132. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -1
  133. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +1 -0
  134. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +2 -0
  135. package/src/runtime/agent/orchestrator/session/manager.mjs +0 -1
  136. package/src/runtime/agent/orchestrator/session/message-ids.mjs +123 -0
  137. package/src/runtime/agent/orchestrator/session/message-ids.test-support.mjs +29 -0
  138. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +33 -22
  139. package/src/runtime/agent/orchestrator/session/store/listing.mjs +6 -2
  140. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +3 -0
  141. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +6 -0
  142. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +10 -3
  143. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +11 -0
  144. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +817 -0
  145. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +36 -0
  146. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +23 -12
  147. package/src/runtime/agent/orchestrator/session/store-summary-reader-catalog-fixture.mjs +158 -0
  148. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +202 -374
  149. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +20 -3
  150. package/src/runtime/agent/orchestrator/session/synthetic-user-envelope.mjs +3 -1
  151. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +1 -1
  152. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +48 -9
  153. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +161 -0
  154. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +17 -1
  155. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -0
  156. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +52 -2
  157. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +32 -22
  158. package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +10 -3
  159. package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +2 -2
  160. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +6 -6
  161. package/src/runtime/agent/orchestrator/tools/builtin/read-cache-policy.mjs +8 -0
  162. package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +203 -33
  163. package/src/runtime/agent/orchestrator/tools/builtin/read-single-fast-paths.mjs +32 -35
  164. package/src/runtime/agent/orchestrator/tools/builtin/read-single-guards.mjs +8 -4
  165. package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +11 -12
  166. package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +9 -6
  167. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +83 -68
  168. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +8 -3
  169. package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +20 -3
  170. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +16 -7
  171. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +2 -1
  172. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +137 -0
  173. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +11 -10
  174. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +21 -11
  175. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +12 -1
  176. package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +13 -10
  177. package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +1 -1
  178. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +17 -12
  179. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +56 -13
  180. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +58 -0
  181. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +2 -2
  182. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +1 -1
  183. package/src/runtime/agent/orchestrator/tools/code-graph/trusted-roots.mjs +25 -1
  184. package/src/runtime/agent/orchestrator/tools/patch/apply-patch/codex-batch.mjs +4 -4
  185. package/src/runtime/agent/orchestrator/tools/patch/apply-patch/route.mjs +2 -2
  186. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +1 -1
  187. package/src/runtime/agent/orchestrator/tools/patch/read-redirects.mjs +34 -30
  188. package/src/runtime/agent/orchestrator/tools/patch/sequence/units.mjs +7 -7
  189. package/src/runtime/agent/orchestrator/tools/patch/sequence.mjs +1 -1
  190. package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +78 -5
  191. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +2 -0
  192. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -0
  193. package/src/runtime/attachments/store.d.mts +3 -0
  194. package/src/runtime/attachments/store.mjs +38 -0
  195. package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +1 -1
  196. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +16 -4
  197. package/src/runtime/channels/lib/worker-main.mjs +17 -14
  198. package/src/runtime/computer-bridge/linux-readiness.mjs +0 -2
  199. package/src/runtime/effort-judge/judge-client.mjs +56 -18
  200. package/src/runtime/memory/lib/memory.mjs +1 -1
  201. package/src/runtime/memory/lib/session-ingest.mjs +2 -2
  202. package/src/runtime/shared/atomic-file.mjs +33 -6
  203. package/src/runtime/shared/best-effort-stderr.mjs +8 -0
  204. package/src/runtime/shared/code-graph-state.mjs +12 -0
  205. package/src/runtime/shared/defaults-separation.mjs +2 -0
  206. package/src/runtime/shared/extension-scopes.mjs +5 -3
  207. package/src/runtime/shared/file-lock.mjs +37 -11
  208. package/src/runtime/shared/git-runner-worker.mjs +3 -2
  209. package/src/runtime/shared/git-runner.mjs +4 -3
  210. package/src/runtime/shared/llm/anthropic-effort.mjs +14 -0
  211. package/src/runtime/shared/llm/default-context-window.mjs +6 -0
  212. package/src/runtime/shared/llm/model-catalog-projection.mjs +5 -4
  213. package/src/runtime/shared/llm/model-catalog.mjs +240 -130
  214. package/src/runtime/shared/llm/provider-catalog-cache.mjs +36 -28
  215. package/src/runtime/shared/llm/provider-catalog-registry.mjs +162 -0
  216. package/src/runtime/shared/llm/provider-seed-rosters.mjs +130 -0
  217. package/src/runtime/shared/model-proxy-mode.d.mts +3 -0
  218. package/src/runtime/shared/model-proxy-mode.mjs +19 -0
  219. package/src/runtime/shared/plugin-manifest.mjs +18 -9
  220. package/src/runtime/shared/pristine-execution-contract.json +9 -2
  221. package/src/runtime/shared/pristine-execution.mjs +28 -4
  222. package/src/runtime/shared/provider-accounts.mjs +125 -21
  223. package/src/runtime/shared/provider-api-key.mjs +33 -1
  224. package/src/runtime/shared/schedule-model-ref.mjs +11 -2
  225. package/src/runtime/shared/session-usage-totals.mjs +19 -0
  226. package/src/runtime/shared/sharp-electron-linux.mjs +25 -0
  227. package/src/runtime/shared/shell-display.mjs +9 -3
  228. package/src/runtime/shared/singleton-owner.mjs +36 -6
  229. package/src/runtime/shared/skill-mentions.d.mts +10 -0
  230. package/src/runtime/shared/skill-mentions.mjs +133 -0
  231. package/src/runtime/shared/skill-script-runtime.mjs +264 -0
  232. package/src/runtime/shared/statusline/statusline-segments.mjs +68 -14
  233. package/src/runtime/shared/tool-primitives.mjs +35 -2
  234. package/src/runtime/shared/tool-surface.mjs +7 -0
  235. package/src/runtime/shared/tool-work-units.mjs +14 -0
  236. package/src/runtime/shared/transcript-metadata.mjs +17 -1
  237. package/src/runtime/shared/turn-changes.mjs +791 -0
  238. package/src/runtime/shared/turn-snapshot-store.mjs +346 -12
  239. package/src/runtime/shared/turn-snapshot.mjs +690 -81
  240. package/src/runtime/shared/turn-worktree-snapshot.mjs +185 -15
  241. package/src/runtime/shared/worker-requests.mjs +15 -2
  242. package/src/runtime/shared/worktrees/cache-cleanups.mjs +52 -0
  243. package/src/runtime/shared/worktrees/contract.mjs +551 -0
  244. package/src/runtime/shared/worktrees/disk.mjs +57 -0
  245. package/src/runtime/shared/worktrees/git.mjs +247 -0
  246. package/src/runtime/shared/worktrees/include.mjs +480 -0
  247. package/src/runtime/shared/worktrees/lock.mjs +46 -0
  248. package/src/runtime/shared/worktrees/maintenance.mjs +96 -0
  249. package/src/runtime/shared/worktrees/manager.mjs +1215 -0
  250. package/src/runtime/shared/worktrees/merge.mjs +182 -0
  251. package/src/runtime/shared/worktrees/owner.mjs +46 -0
  252. package/src/runtime/shared/worktrees/paths.mjs +62 -0
  253. package/src/runtime/shared/worktrees/registry.mjs +110 -0
  254. package/src/runtime/shared/worktrees/settings.mjs +84 -0
  255. package/src/runtime/shared/worktrees/slug.mjs +62 -0
  256. package/src/runtime/shared/worktrees/test-support.mjs +96 -0
  257. package/src/runtime/visualize-bridge/client.mjs +181 -0
  258. package/src/runtime/visualize-bridge/tool-defs.mjs +97 -0
  259. package/src/session-runtime/automation-agents.mjs +8 -2
  260. package/src/session-runtime/boot/apis.mjs +90 -0
  261. package/src/session-runtime/boot/config.mjs +7 -2
  262. package/src/session-runtime/boot/facade.mjs +21 -0
  263. package/src/session-runtime/boot/infrastructure.mjs +30 -18
  264. package/src/session-runtime/boot/session.mjs +1 -0
  265. package/src/session-runtime/boot/tools.mjs +4 -0
  266. package/src/session-runtime/config-lifecycle.mjs +10 -1
  267. package/src/session-runtime/conversation-import/discovery.mjs +698 -0
  268. package/src/session-runtime/conversation-import/service.mjs +369 -0
  269. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +21 -6
  270. package/src/session-runtime/cwd-plugins/plugin-status.mjs +47 -42
  271. package/src/session-runtime/global-extensions.mjs +2 -2
  272. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +27 -2
  273. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +32 -1
  274. package/src/session-runtime/internal-tool-executor.mjs +9 -1
  275. package/src/session-runtime/lifecycle/session-catalog.mjs +8 -1
  276. package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +5 -0
  277. package/src/session-runtime/lifecycle/session-switch/resume.mjs +21 -3
  278. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +16 -0
  279. package/src/session-runtime/mcp-glue/connect.mjs +68 -16
  280. package/src/session-runtime/mcp-glue/plugin-servers.mjs +499 -0
  281. package/src/session-runtime/mcp-glue/server-catalog.mjs +122 -33
  282. package/src/session-runtime/mcp-glue/server-input.mjs +29 -0
  283. package/src/session-runtime/mcp-glue.mjs +22 -2
  284. package/src/session-runtime/model-route/route-persist.mjs +3 -0
  285. package/src/session-runtime/model-route/set-route.mjs +15 -2
  286. package/src/session-runtime/new-session-config.mjs +3 -2
  287. package/src/session-runtime/notification-bus.mjs +18 -3
  288. package/src/session-runtime/provider-auth/accounts.mjs +33 -15
  289. package/src/session-runtime/provider-models/row-hydration.mjs +2 -0
  290. package/src/session-runtime/provider-models/web-search-catalog.mjs +5 -1
  291. package/src/session-runtime/provider-readiness.mjs +16 -10
  292. package/src/session-runtime/resource-extension-sync.mjs +45 -7
  293. package/src/session-runtime/resource-mcp-api.mjs +121 -84
  294. package/src/session-runtime/resource-plugins-api.mjs +29 -41
  295. package/src/session-runtime/resource-scope-decorate.mjs +6 -8
  296. package/src/session-runtime/resource-skills-api.mjs +23 -2
  297. package/src/session-runtime/route-resolution.mjs +10 -1
  298. package/src/session-runtime/route-state.mjs +16 -2
  299. package/src/session-runtime/runtime-core.mjs +5 -0
  300. package/src/session-runtime/runtime-facade.mjs +8 -0
  301. package/src/session-runtime/runtime-review-api.mjs +9 -0
  302. package/src/session-runtime/schedule-session-run.mjs +1 -1
  303. package/src/session-runtime/services/agent-tool/helpers.mjs +5 -0
  304. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +3 -0
  305. package/src/session-runtime/services/agent-tool/job-views/job-lookup.mjs +1 -0
  306. package/src/session-runtime/services/agent-tool/job-views/spawn-meta.mjs +1 -0
  307. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +15 -0
  308. package/src/session-runtime/services/agent-tool/send-flow.mjs +1 -0
  309. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +10 -2
  310. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +19 -7
  311. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +3 -0
  312. package/src/session-runtime/services/agent-tool/tag-registry.mjs +2 -1
  313. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +5 -0
  314. package/src/session-runtime/services/agent-tool/worker-rows.mjs +1 -0
  315. package/src/session-runtime/services/agent-tool.mjs +1 -0
  316. package/src/session-runtime/services/channel-session-router.mjs +4 -1
  317. package/src/session-runtime/services/daemon-candidate.mjs +3 -2
  318. package/src/session-runtime/services/daemon-crash-capture/spawn-capture.mjs +55 -0
  319. package/src/session-runtime/services/hook-bus/command-handler.mjs +4 -1
  320. package/src/session-runtime/services/hook-bus/handlers.mjs +7 -3
  321. package/src/session-runtime/services/provider-admin.mjs +64 -18
  322. package/src/session-runtime/services/usage-dashboard-model.mjs +3 -0
  323. package/src/session-runtime/session-create/session-options.mjs +10 -1
  324. package/src/session-runtime/session-turn-api.mjs +1 -0
  325. package/src/session-runtime/settings-api.mjs +4 -2
  326. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -2
  327. package/src/session-runtime/settings-worktree-api.mjs +17 -0
  328. package/src/session-runtime/setup-tool/executor.mjs +23 -5
  329. package/src/session-runtime/setup-tool/extended-actions.mjs +1 -0
  330. package/src/session-runtime/setup-tool/settings-contract.mjs +4 -1
  331. package/src/session-runtime/setup-tool/tool-defs.mjs +38 -4
  332. package/src/session-runtime/skills-api.mjs +7 -3
  333. package/src/session-runtime/turn/agent-ops.mjs +12 -1
  334. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +14 -3
  335. package/src/session-runtime/turn/turn-run/snapshot-tracker.mjs +20 -15
  336. package/src/session-runtime/turn/turn-run/turn-open.mjs +4 -5
  337. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +29 -1
  338. package/src/session-runtime/turn/turn-run/turn-settle.mjs +6 -1
  339. package/src/session-runtime/turn/turn-run.mjs +6 -7
  340. package/src/session-runtime/webhook-session-run.mjs +16 -4
  341. package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -3
  342. package/src/session-runtime/workspace/admission-gate.mjs +197 -0
  343. package/src/session-runtime/workspace/project-branches.mjs +136 -0
  344. package/src/session-runtime/workspace/transition-steps.mjs +364 -0
  345. package/src/session-runtime/workspace/transitions.mjs +651 -0
  346. package/src/session-runtime/workspace/workspace-state.mjs +112 -0
  347. package/src/skill-script-entry.mjs +36 -0
  348. package/src/standalone/channel-transport/http-routes.mjs +17 -0
  349. package/src/standalone/channel-transport.mjs +13 -1
  350. package/src/standalone/daemon-desktop-runtime.mjs +1 -0
  351. package/src/standalone/daemon-owner.mjs +372 -0
  352. package/src/standalone/daemon-stored-session-views.mjs +15 -4
  353. package/src/standalone/daemon.mjs +179 -8
  354. package/src/standalone/desktop-service-registry.mjs +20 -0
  355. package/src/standalone/local-session-runtime.mjs +11 -0
  356. package/src/standalone/session-client/attachment-calls.mjs +3 -1
  357. package/src/standalone/session-client/daemon-unavailable.mjs +62 -0
  358. package/src/standalone/session-client/lease-heartbeat.mjs +47 -0
  359. package/src/standalone/session-client.mjs +104 -13
  360. package/src/standalone/session-protocol.mjs +21 -0
  361. package/src/standalone/session-proxy.mjs +3 -0
  362. package/src/standalone/session-service/agent-tree/agent-child.mjs +4 -0
  363. package/src/standalone/session-service/agent-tree/agent-rehydrate.mjs +1 -0
  364. package/src/standalone/session-service/entries/lifecycle.mjs +6 -0
  365. package/src/standalone/session-service/projection/revisions.mjs +2 -0
  366. package/src/standalone/session-service/projection/transcript-window.mjs +149 -19
  367. package/src/standalone/session-service/projection/workspace.mjs +14 -0
  368. package/src/standalone/session-service/session-calls/session-action.mjs +17 -4
  369. package/src/standalone/session-transport/call-route.mjs +13 -0
  370. package/src/standalone/session-transport/client-registry/client-grace.mjs +45 -3
  371. package/src/standalone/session-transport/client-registry/registration-replays.mjs +1 -0
  372. package/src/standalone/session-transport/client-registry.mjs +20 -3
  373. package/src/standalone/session-transport/routes.mjs +79 -2
  374. package/src/standalone/session-transport/stack.mjs +2 -0
  375. package/src/standalone/session-transport.mjs +24 -1
  376. package/src/tui/App.jsx +9 -1
  377. package/src/tui/app/app-view/floating-panel.jsx +9 -1
  378. package/src/tui/app/app-view.jsx +6 -0
  379. package/src/tui/app/extension-pickers/plugins-pickers.mjs +35 -16
  380. package/src/tui/app/message-selector.mjs +2 -1
  381. package/src/tui/app/model-options.mjs +37 -5
  382. package/src/tui/app/model-picker/provider-models-list.mjs +2 -2
  383. package/src/tui/app/model-picker.mjs +4 -2
  384. package/src/tui/app/skill-mention-palette.mjs +88 -0
  385. package/src/tui/app/slash-palette.mjs +23 -7
  386. package/src/tui/app/transcript-row-estimate.mjs +11 -3
  387. package/src/tui/app/use-prompt-draft-flow.mjs +7 -2
  388. package/src/tui/app/use-prompt-state.mjs +5 -0
  389. package/src/tui/components/Message.jsx +22 -1
  390. package/src/tui/components/PromptInput.jsx +21 -3
  391. package/src/tui/components/SlashCommandPalette.jsx +12 -3
  392. package/src/tui/components/TranscriptItem.jsx +9 -1
  393. package/src/tui/components/prompt-input/key-handler.mjs +21 -8
  394. package/src/tui/components/user-message-skills.mjs +38 -0
  395. package/src/tui/dist/index.mjs +839 -207
  396. package/src/tui/index.jsx +10 -2
  397. package/src/tui/session/compaction-view.mjs +147 -0
  398. package/src/tui/session/context-state.mjs +6 -0
  399. package/src/tui/session/initial-state.mjs +3 -0
  400. package/src/tui/session/queue-helpers.mjs +7 -2
  401. package/src/tui/session/quota-resume.mjs +2 -1
  402. package/src/tui/session/session-api/commands.mjs +10 -2
  403. package/src/tui/session/session-api/controls.mjs +4 -0
  404. package/src/tui/session/session-api/extensions.mjs +4 -2
  405. package/src/tui/session/session-api/intake/rewind.mjs +2 -1
  406. package/src/tui/session/session-api/integrations.mjs +17 -0
  407. package/src/tui/session/session-api/lifecycle/dispose.mjs +6 -1
  408. package/src/tui/session/session-api/lifecycle/inheritance.mjs +2 -0
  409. package/src/tui/session/session-api/lifecycle/resume.mjs +3 -0
  410. package/src/tui/session/session-api/routes.mjs +10 -1
  411. package/src/tui/session/session-api/settings.mjs +9 -0
  412. package/src/tui/session/session-api/worktree.mjs +60 -0
  413. package/src/tui/session/session-api.mjs +2 -0
  414. package/src/tui/session/session-flow/auto-clear.mjs +31 -12
  415. package/src/tui/session/session-flow/drain.mjs +27 -3
  416. package/src/tui/session/session-flow/session-reset.mjs +4 -3
  417. package/src/tui/session/session-flow.mjs +2 -0
  418. package/src/tui/session/transcript-intake.mjs +14 -5
  419. package/src/tui/session/tui-steering-persist.mjs +25 -9
  420. package/src/tui/session/turn-stream.mjs +32 -4
  421. package/src/tui/session/turn-tool-cards.mjs +10 -2
  422. package/src/tui/session/turn.mjs +56 -10
  423. package/src/runtime/effort-judge/previous-human-request.mjs +0 -162
  424. package/src/runtime/effort-judge/verified-context-input.mjs +0 -10
@@ -12,10 +12,14 @@ import {
12
12
  unlinkSync,
13
13
  writeFileSync,
14
14
  } from 'node:fs';
15
- import { isAbsolute, join } from 'node:path';
15
+ import { join, relative, sep } from 'node:path';
16
16
  import { clean } from './session-text.mjs';
17
- import { readJsonSafe } from './fs-utils.mjs';
18
- import { pluginManifest, pluginSkillsRoots, resolveContainedPluginPath } from '../../../shared/plugin-manifest.mjs';
17
+ import {
18
+ PLUGIN_MANIFEST_FILES,
19
+ pluginManifest,
20
+ pluginSkillsRoots,
21
+ resolveContainedPluginPath,
22
+ } from '../../../shared/plugin-manifest.mjs';
19
23
  import { isPlainObject } from '../../../shared/object.mjs';
20
24
 
21
25
  const MCP_TRANSPORT_FIELDS = new Set([
@@ -33,9 +37,20 @@ const MCP_TRANSPORT_FIELDS = new Set([
33
37
  'autoDetect',
34
38
  ]);
35
39
 
40
+ // Every spelling resolveMcpStartupTimeoutMs / resolveMcpCallTimeoutMs reads.
41
+ // A newly supplied value must replace the whole group, or a stale alias that
42
+ // the resolver prefers would keep winning.
43
+ export const MCP_TIMEOUT_FIELD_GROUPS = [
44
+ ['startupTimeoutMs', 'startup_timeout_ms', 'startupTimeoutSec', 'startup_timeout_sec'],
45
+ ['timeoutMs', 'timeout_ms', 'callTimeoutMs', 'call_timeout_ms'],
46
+ ];
47
+
36
48
  export function mergeMcpServerConfig(existing, next) {
37
49
  const merged = isPlainObject(existing) ? { ...existing } : {};
38
50
  for (const field of MCP_TRANSPORT_FIELDS) delete merged[field];
51
+ for (const group of MCP_TIMEOUT_FIELD_GROUPS) {
52
+ if (group.some((field) => Object.hasOwn(next, field))) for (const field of group) delete merged[field];
53
+ }
39
54
  return { ...merged, ...next };
40
55
  }
41
56
 
@@ -127,17 +142,15 @@ export function saveProjectMcpServer(cwd, { originalName = '', name, config }) {
127
142
  // discovery and this status surface read the same roots.
128
143
  export { pluginManifest, resolveContainedPluginPath };
129
144
 
130
- function mcpServersMapFromJson(raw) {
131
- if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null;
132
- if (isPlainObject(raw.mcpServers)) return raw.mcpServers;
133
- return raw;
134
- }
135
-
136
145
  function isSkillMdFile(name) {
137
146
  return /^(SKILL|skill)\.md$/i.test(name);
138
147
  }
139
148
 
140
- const PLUGIN_INLINE_MCP_SCRIPT = 'plugin.json';
149
+ const PLUGIN_MANIFEST_FORMATS = {
150
+ '.codex-plugin/plugin.json': 'codex',
151
+ 'plugin.json': 'mixdog',
152
+ '.claude-plugin/plugin.json': 'claude',
153
+ };
141
154
 
142
155
  export function countSkillFiles(root) {
143
156
  let count = 0;
@@ -166,102 +179,283 @@ export function countSkillFiles(root) {
166
179
  return count;
167
180
  }
168
181
 
169
- export function discoverPluginMcp(root) {
170
- const manifest = pluginManifest(root);
171
- const mcp = manifest.mcpServers;
172
- if (typeof mcp === 'string' && mcp.trim()) {
173
- const rel = mcp.trim();
174
- const abs = resolveContainedPluginPath(root, rel);
175
- if (abs && existsSync(abs)) return { mcpScript: rel, mcpInline: false };
176
- } else if (isPlainObject(mcp)) {
177
- const keys = Object.keys(mcp).filter((k) => isPlainObject(mcp[k]));
178
- if (keys.length) return { mcpScript: null, mcpInline: true };
182
+ const MCP_STDIO_TYPES = new Set(['stdio']);
183
+ const MCP_URL_TYPES = new Set(['http', 'streamable-http', 'sse', 'ws']);
184
+
185
+ function manifestFormat(root) {
186
+ for (const rel of PLUGIN_MANIFEST_FILES) {
187
+ const path = join(root, ...rel.split('/'));
188
+ if (existsSync(path)) return { path, format: PLUGIN_MANIFEST_FORMATS[rel] };
179
189
  }
180
- const candidates = ['.mcp.json', 'scripts/run-mcp.mjs', 'mcp/server.mjs', 'server.mjs'];
181
- for (const rel of candidates) {
182
- const abs = resolveContainedPluginPath(root, rel);
183
- if (abs && existsSync(abs)) return { mcpScript: rel, mcpInline: false };
190
+ return null;
191
+ }
192
+
193
+ /** Transport a declared server asks for, inferred the way the MCP client does
194
+ * when `type` is absent: `transport: "http"` or a `url` -> http, else stdio.
195
+ * Null for an unrecognized explicit type. */
196
+ export function declaredMcpTransport(config) {
197
+ if (config?.type !== undefined && config?.type !== null && config?.type !== '') {
198
+ if (typeof config.type !== 'string') return null;
199
+ const type = config.type.toLowerCase();
200
+ if (type === 'streamablehttp') return 'streamable-http';
201
+ return MCP_STDIO_TYPES.has(type) || MCP_URL_TYPES.has(type) ? type : null;
184
202
  }
185
- return { mcpScript: null, mcpInline: false };
203
+ if (config?.transport === 'http') return 'http';
204
+ if (config?.command !== undefined) return 'stdio';
205
+ if (config?.url !== undefined) return 'http';
206
+ return 'stdio';
186
207
  }
187
208
 
188
- export function mcpScriptForPlugin(root) {
189
- return discoverPluginMcp(root).mcpScript;
209
+ // One server entry -> a message when it cannot be launched, else null. Messages
210
+ // name fields and expected types only, never a configured value.
211
+ function mcpServerProblem(config) {
212
+ if (!isPlainObject(config)) return 'server entry must be an object';
213
+ const type = declaredMcpTransport(config);
214
+ if (!type) return '"type" must be one of stdio, http, streamable-http, sse, ws';
215
+ if (MCP_STDIO_TYPES.has(type)) {
216
+ return typeof config.command === 'string' && config.command.trim() ? null : 'stdio server requires "command"';
217
+ }
218
+ return typeof config.url === 'string' && config.url.trim() ? null : `${type} server requires "url"`;
190
219
  }
191
220
 
192
- export function pluginMcpEnableScript(root, plugin = {}) {
193
- if (plugin.mcpInline) return PLUGIN_INLINE_MCP_SCRIPT;
194
- const fromPlugin = clean(plugin.mcpScript);
195
- if (fromPlugin) return fromPlugin;
196
- const discovered = discoverPluginMcp(root);
197
- if (discovered.mcpInline) return PLUGIN_INLINE_MCP_SCRIPT;
198
- return discovered.mcpScript || null;
221
+ // A JSON parse failure as a location only: engines may quote the offending
222
+ // source text, which can hold credentials.
223
+ function jsonErrorMessage(error) {
224
+ const text = String(error?.message || '');
225
+ const lineColumn = /line (\d+) column (\d+)/.exec(text);
226
+ if (lineColumn) return `invalid JSON at line ${lineColumn[1]} column ${lineColumn[2]}`;
227
+ const position = /position (\d+)/.exec(text);
228
+ if (position) return `invalid JSON at position ${position[1]}`;
229
+ return /end of (JSON|data) input/i.test(text) ? 'invalid JSON: unexpected end of input' : 'invalid JSON';
199
230
  }
200
231
 
201
- function substitutePluginRootTokens(value, root) {
202
- if (typeof value !== 'string') return value;
203
- return value.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, root).replace(/\$\{CODEX_PLUGIN_ROOT\}/g, root);
232
+ function readJsonFile(path, errors) {
233
+ let text;
234
+ try {
235
+ text = readFileSync(path, 'utf8');
236
+ } catch (error) {
237
+ errors.push({ source: path, message: `cannot read file${error?.code ? ` (${error.code})` : ''}` });
238
+ return { ok: false };
239
+ }
240
+ try {
241
+ return { ok: true, value: JSON.parse(text) };
242
+ } catch (error) {
243
+ errors.push({ source: path, message: jsonErrorMessage(error) });
244
+ return { ok: false };
245
+ }
204
246
  }
205
247
 
206
- function substitutePluginRootTokensDeep(value, root) {
207
- if (typeof value === 'string') return substitutePluginRootTokens(value, root);
208
- if (Array.isArray(value)) return value.map((v) => substitutePluginRootTokensDeep(v, root));
209
- if (value && typeof value === 'object') {
210
- const out = {};
211
- for (const [k, v] of Object.entries(value)) out[k] = substitutePluginRootTokensDeep(v, root);
212
- return out;
248
+ // A `{ mcpServers: {...} }` wrapper or a bare server map -> the server map.
249
+ function mcpServerMap(doc, source, errors, where = '') {
250
+ if (!isPlainObject(doc)) {
251
+ errors.push({ source, message: `${where}MCP declaration must be an object` });
252
+ return null;
253
+ }
254
+ if (!Object.hasOwn(doc, 'mcpServers')) return doc;
255
+ if (!isPlainObject(doc.mcpServers)) {
256
+ errors.push({ source, message: `${where}"mcpServers" must be an object` });
257
+ return null;
213
258
  }
214
- return value;
259
+ return doc.mcpServers;
215
260
  }
216
261
 
217
- export function normalizePluginMcpServerConfig(cfg, root) {
218
- const substituted = substitutePluginRootTokensDeep(cfg, root) || {};
219
- const out = { ...substituted };
220
- if (typeof out.cwd === 'string' && out.cwd) {
221
- out.cwd = isAbsolute(out.cwd) ? out.cwd : join(root, out.cwd);
222
- } else {
223
- out.cwd = root;
262
+ function collectMcpServers(map, source, into, errors) {
263
+ for (const [key, config] of Object.entries(map)) {
264
+ const problem = mcpServerProblem(config);
265
+ if (problem) errors.push({ source, key, message: problem });
266
+ else into.set(key, { key, config, source });
224
267
  }
225
- return out;
226
268
  }
227
269
 
228
- export function pluginRawMcpServers(root, script) {
229
- const rel = clean(script);
230
- if (!rel) return null;
231
- if (rel === PLUGIN_INLINE_MCP_SCRIPT) {
232
- const manifest = pluginManifest(root);
233
- const rawServers = manifest.mcpServers;
234
- if (!isPlainObject(rawServers)) {
235
- throw new Error('plugin.json missing inline mcpServers object');
270
+ function loadMcpJsonFile(path, into, errors) {
271
+ const read = readJsonFile(path, errors);
272
+ if (!read.ok) return;
273
+ const map = mcpServerMap(read.value, path, errors);
274
+ if (map) collectMcpServers(map, path, into, errors);
275
+ }
276
+
277
+ // `where` locates the manifest field (e.g. `mcpServers[1]: `); the declared
278
+ // path itself is never echoed.
279
+ function loadDeclaredPath(root, rel, manifestPath, into, errors, touched, where) {
280
+ if (typeof rel !== 'string' || !rel.trim()) {
281
+ errors.push({ source: manifestPath, message: `${where}MCP path must be a non-empty string` });
282
+ return;
283
+ }
284
+ const abs = resolveContainedPluginPath(root, rel);
285
+ if (!abs || rel.split(/[\\/]/).includes('..')) {
286
+ errors.push({ source: manifestPath, message: `${where}MCP path must be relative and stay inside the plugin root` });
287
+ return;
288
+ }
289
+ touched.add(abs);
290
+ if (!existsSync(abs)) {
291
+ errors.push({ source: manifestPath, message: `${where}MCP file not found` });
292
+ return;
293
+ }
294
+ loadMcpJsonFile(abs, into, errors);
295
+ }
296
+
297
+ function readPluginMcpDeclarations(root, touched) {
298
+ const errors = [];
299
+ const into = new Map();
300
+ for (const rel of PLUGIN_MANIFEST_FILES) touched.add(join(root, ...rel.split('/')));
301
+ const found = manifestFormat(root);
302
+ const mcpJson = join(root, '.mcp.json');
303
+ touched.add(mcpJson);
304
+ const hasMcpJson = existsSync(mcpJson);
305
+ let format = 'none';
306
+ if (found) format = found.format;
307
+ else if (hasMcpJson) format = 'claude';
308
+
309
+ if (hasMcpJson) loadMcpJsonFile(mcpJson, into, errors);
310
+
311
+ let declaredRef;
312
+ if (found) {
313
+ const read = readJsonFile(found.path, errors);
314
+ const declared = read.ok && isPlainObject(read.value) ? read.value.mcpServers : undefined;
315
+ declaredRef = declared;
316
+ if (declared === undefined) {
317
+ /* nothing declared */
318
+ } else if (typeof declared === 'string') {
319
+ loadDeclaredPath(root, declared, found.path, into, errors, touched, '"mcpServers": ');
320
+ } else if (Array.isArray(declared)) {
321
+ // Members merge in order: a path to a JSON file, or an inline server
322
+ // map (bare or `{ mcpServers }`-wrapped).
323
+ declared.forEach((entry, index) => {
324
+ const where = `"mcpServers"[${index}]: `;
325
+ if (isPlainObject(entry)) {
326
+ const map = mcpServerMap(entry, found.path, errors, where);
327
+ if (map) collectMcpServers(map, found.path, into, errors);
328
+ } else {
329
+ loadDeclaredPath(root, entry, found.path, into, errors, touched, where);
330
+ }
331
+ });
332
+ } else if (isPlainObject(declared)) {
333
+ const map = mcpServerMap(declared, found.path, errors);
334
+ if (map) collectMcpServers(map, found.path, into, errors);
335
+ } else {
336
+ errors.push({ source: found.path, message: '"mcpServers" must be a path, a list, or an object' });
236
337
  }
237
- const keys = Object.keys(rawServers).filter((k) => isPlainObject(rawServers[k]));
238
- if (!keys.length) throw new Error('plugin.json has no mcpServers');
239
- return { rawServers, mcpRoot: root };
240
338
  }
241
- if (!/\.json$/i.test(rel)) return null;
242
- const mcpJsonPath = resolveContainedPluginPath(root, rel);
243
- if (!mcpJsonPath || !existsSync(mcpJsonPath)) {
244
- throw new Error(`plugin MCP manifest not found: ${join(root, rel)}`);
339
+ const declaredPath = typeof declaredRef === 'string' ? declaredRef : null;
340
+ return { format, servers: [...into.values()], errors, declaredPath };
341
+ }
342
+
343
+ // Parsed declarations per plugin root, valid while every file the parse looked
344
+ // at (present or absent) still has the same identity, size and timestamps.
345
+ const DECLARATION_CACHE_MAX = 256;
346
+ const declarationCache = new Map();
347
+ const declarationCacheStats = { hits: 0, misses: 0 };
348
+
349
+ function fileStamp(path) {
350
+ try {
351
+ const st = statSync(path, { bigint: true });
352
+ return `${st.ino}:${st.size}:${st.mtimeNs}:${st.ctimeNs}`;
353
+ } catch {
354
+ return '-';
355
+ }
356
+ }
357
+
358
+ function copyDeclarations(result) {
359
+ return {
360
+ format: result.format,
361
+ servers: result.servers.map((server) => ({ ...server, config: structuredClone(server.config) })),
362
+ errors: result.errors.map((error) => ({ ...error })),
363
+ };
364
+ }
365
+
366
+ /** What a plugin declares for MCP, read raw: `<root>/.mcp.json` first, then the
367
+ * manifest `mcpServers` (a path, an inline object, or a list mixing both).
368
+ * Later sources override earlier ones by key. Never throws for plugin
369
+ * content; errors carry locations and field names, never values. Unchanged
370
+ * declaration files are not parsed again. */
371
+ export function loadPluginMcpDeclarations(root) {
372
+ return copyDeclarations(cachedDeclarations(root));
373
+ }
374
+
375
+ function cachedDeclarations(root) {
376
+ const key = String(root || '');
377
+ const cached = declarationCache.get(key);
378
+ if (cached?.signature.every(([path, stamp]) => fileStamp(path) === stamp)) {
379
+ declarationCacheStats.hits += 1;
380
+ return cached.result;
245
381
  }
246
- const rawServers = mcpServersMapFromJson(readJsonSafe(mcpJsonPath) || {});
247
- if (!isPlainObject(rawServers)) {
248
- throw new Error(`plugin MCP manifest missing mcpServers object: ${mcpJsonPath}`);
382
+ declarationCacheStats.misses += 1;
383
+ // Stamps are taken before reading, so a write racing the parse invalidates.
384
+ const before = new Map();
385
+ const result = readPluginMcpDeclarations(root, {
386
+ add(path) {
387
+ if (!before.has(path)) before.set(path, fileStamp(path));
388
+ },
389
+ });
390
+ declarationCache.delete(key);
391
+ declarationCache.set(key, { signature: [...before], result });
392
+ if (declarationCache.size > DECLARATION_CACHE_MAX) declarationCache.delete(declarationCache.keys().next().value);
393
+ return result;
394
+ }
395
+
396
+ /** Test seam: cache hit/miss counters. */
397
+ export function pluginMcpDeclarationCacheStats() {
398
+ return { ...declarationCacheStats };
399
+ }
400
+
401
+ // Adapter: where the declarations live, in the shape older callers expect.
402
+ export function discoverPluginMcp(root) {
403
+ const { servers, declaredPath } = cachedDeclarations(root);
404
+ if (!servers.length) return { mcpScript: null, mcpInline: false };
405
+ const inline = servers.some((s) => PLUGIN_MANIFEST_FILES.some((rel) => s.source === join(root, ...rel.split('/'))));
406
+ if (inline) return { mcpScript: null, mcpInline: true };
407
+ if (declaredPath?.trim()) {
408
+ const abs = resolveContainedPluginPath(root, declaredPath);
409
+ if (servers.some((s) => s.source === abs)) return { mcpScript: declaredPath.trim(), mcpInline: false };
249
410
  }
250
- const keys = Object.keys(rawServers).filter((k) => isPlainObject(rawServers[k]));
251
- if (!keys.length) throw new Error(`plugin MCP manifest has no mcpServers: ${mcpJsonPath}`);
252
- return { rawServers, mcpRoot: root };
411
+ const mcpJson = join(root, '.mcp.json');
412
+ const declaredFile = servers.find((s) => s.source !== mcpJson) || servers[0];
413
+ return { mcpScript: relative(root, declaredFile.source).split(sep).join('/'), mcpInline: false };
253
414
  }
254
415
 
255
- /** Every MCP entry a plugin owns: `<serverName>` and `<serverName>--<key>`. */
256
- export function pluginServerMatcher(serverName) {
257
- const prefix = `${serverName}--`;
258
- return (name) => name === serverName || name.startsWith(prefix);
416
+ // User intent for plugin-declared servers, stored apart from the declarations
417
+ // as `pluginMcp[pluginId][declaredKey]`: `enabled: false`, a kept `alias`
418
+ // name, and connection overrides layered over the declaration.
419
+ export const PLUGIN_MCP_OVERRIDE_MAP_FIELDS = Object.freeze(['env', 'headers', 'env_http_headers']);
420
+ export const PLUGIN_MCP_OVERRIDE_VALUE_FIELDS = Object.freeze([
421
+ 'command',
422
+ 'args',
423
+ 'url',
424
+ 'bearer_token_env_var',
425
+ 'env_vars',
426
+ ...MCP_TIMEOUT_FIELD_GROUPS.flat(),
427
+ ]);
428
+
429
+ function normalizePluginMcpIntent(value) {
430
+ if (!isPlainObject(value)) return null;
431
+ const out = {};
432
+ // `enabled: true` is kept too: it turns on a server declared `enabled: false`.
433
+ if (typeof value.enabled === 'boolean') out.enabled = value.enabled;
434
+ if (typeof value.alias === 'string' && value.alias.trim()) out.alias = value.alias.trim();
435
+ for (const field of PLUGIN_MCP_OVERRIDE_MAP_FIELDS) {
436
+ if (!isPlainObject(value[field])) continue;
437
+ const map = Object.fromEntries(Object.entries(value[field]).filter(([, v]) => v !== null && v !== undefined));
438
+ if (Object.keys(map).length) out[field] = map;
439
+ }
440
+ for (const field of PLUGIN_MCP_OVERRIDE_VALUE_FIELDS) {
441
+ if (value[field] !== undefined && value[field] !== null) out[field] = value[field];
442
+ }
443
+ return Object.keys(out).length ? out : null;
259
444
  }
260
445
 
261
- export function pluginMcpServerName(plugin = {}) {
262
- const base = clean(plugin.name || plugin.title || 'plugin')
263
- .toLowerCase()
264
- .replace(/[^a-z0-9_.-]+/g, '-')
265
- .replace(/^-+|-+$/g, '');
266
- return base ? `plugin-${base}` : 'plugin-mcp';
446
+ /** `{ [pluginId]: { [declaredKey]: intent } }` with empty records dropped. */
447
+ export function normalizePluginMcpIntents(value) {
448
+ const out = {};
449
+ if (!isPlainObject(value)) return out;
450
+ for (const [pluginId, servers] of Object.entries(value)) {
451
+ const id = clean(pluginId);
452
+ if (!id || !isPlainObject(servers)) continue;
453
+ const kept = {};
454
+ for (const [key, intent] of Object.entries(servers)) {
455
+ const normalized = normalizePluginMcpIntent(intent);
456
+ if (key && normalized) kept[key] = normalized;
457
+ }
458
+ if (Object.keys(kept).length) out[id] = kept;
459
+ }
460
+ return out;
267
461
  }
@@ -2,6 +2,7 @@
2
2
  // definitions out of the eager prefix; selection never changes permission policy.
3
3
  import { deferredCatalogUnion, selectDeferredTools, toolSearchNativePayload } from './tool-catalog.mjs';
4
4
  import { preDispatchDenyForSession } from '../session/loop/pre-dispatch-deny.mjs';
5
+ import { parseMcpToolName } from '../../../shared/tool-primitives.mjs';
5
6
 
6
7
  export function loadSkillToolDependencies(envelope, session, mode) {
7
8
  if (!envelope?.__toolEnvelope) return envelope;
@@ -15,7 +16,9 @@ export function loadSkillToolDependencies(envelope, session, mode) {
15
16
  for (const { type, value } of dependencies) {
16
17
  if (type === 'tool') names.add(value);
17
18
  else if (type === 'mcp') {
18
- const matches = [...catalog.keys()].filter((name) => name.startsWith(`mcp__${value}__`));
19
+ const matches = [...catalog.values()]
20
+ .filter((tool) => parseMcpToolName(tool)?.server === value)
21
+ .map((tool) => tool.name);
19
22
  if (matches.length)
20
23
  matches.forEach((name) => {
21
24
  names.add(name);
@@ -145,11 +145,32 @@ export function sortedCatalogByMeasuredUsage(catalog) {
145
145
  .map((entry) => entry.tool);
146
146
  }
147
147
 
148
+ const IMMUTABLE_SURFACE_TOOLS = new WeakSet();
149
+
150
+ function deepFreeze(value) {
151
+ if (value && typeof value === 'object' && !Object.isFrozen(value)) {
152
+ Object.freeze(value);
153
+ for (const key of Object.keys(value)) deepFreeze(value[key]);
154
+ }
155
+ return value;
156
+ }
157
+
148
158
  export function activeToolForSurface(tool) {
149
159
  if (!tool || typeof tool !== 'object') return tool;
160
+ if (IMMUTABLE_SURFACE_TOOLS.has(tool)) return tool;
150
161
  return JSON.parse(JSON.stringify(tool));
151
162
  }
152
163
 
164
+ // Clone once into a deep-frozen definition that activeToolForSurface passes
165
+ // through without re-cloning. Safe to share: it can never be mutated in place.
166
+ export function immutableToolForSurface(tool) {
167
+ if (!tool || typeof tool !== 'object') return tool;
168
+ if (IMMUTABLE_SURFACE_TOOLS.has(tool)) return tool;
169
+ const frozen = deepFreeze(JSON.parse(JSON.stringify(tool)));
170
+ IMMUTABLE_SURFACE_TOOLS.add(frozen);
171
+ return frozen;
172
+ }
173
+
153
174
  export function deferredProviderMode(provider) {
154
175
  const p = clean(provider).toLowerCase();
155
176
  if (p === 'gemini') return 'manifest';
@@ -4,7 +4,7 @@
4
4
  import { clean } from './session-text.mjs';
5
5
  import { mergePendingDeferredToolDelta } from './deferred-tool-delta.mjs';
6
6
  import { applyInitialDeferredToolManifestToBp2, stripDeferredToolManifestBlock } from '../context/collect.mjs';
7
- import { getMcpServerInstructionsMap, getMcpTools } from '../mcp/client.mjs';
7
+ import { getMcpServerInstructionsMap, getMcpTools, getMcpToolGeneration } from '../mcp/client.mjs';
8
8
  import { filterMcpToolsForSession } from './extension-scopes.mjs';
9
9
  import { isDeferredToolAvailable } from './deferred-tool-availability.mjs';
10
10
  import { isResponsesFreeformTool, toResponsesCustomTool } from '../providers/custom-tool-wire.mjs';
@@ -23,6 +23,7 @@ import {
23
23
  routeToolRank,
24
24
  sortedCatalogByMeasuredUsage,
25
25
  activeToolForSurface,
26
+ immutableToolForSurface,
26
27
  deferredProviderMode,
27
28
  nativeProviderFamily,
28
29
  } from './tool-catalog-schema.mjs';
@@ -464,6 +465,56 @@ export function refreshInitialDeferredMcpSurface(session, liveMcpTools) {
464
465
  */
465
466
  export function reconcileDeferredMcpToolCatalog(session, liveMcpTools) {
466
467
  if (!session || !Array.isArray(session.tools)) return null;
468
+ const cached = RECONCILE_STATE.get(session);
469
+ if (cached && reconcileStateMatches(cached, reconcileStateOf(session, liveMcpTools))) return null;
470
+ const result = reconcileDeferredMcpToolCatalogUncached(session, liveMcpTools);
471
+ RECONCILE_STATE.set(session, reconcileStateOf(session, liveMcpTools));
472
+ return result;
473
+ }
474
+
475
+ // Last reconciled inputs per session. A matching state means the surface that
476
+ // reconciliation would rebuild is identical, so it is skipped outright.
477
+ const RECONCILE_STATE = new WeakMap();
478
+ const LIVE_CLONES = new WeakMap();
479
+
480
+ function liveToolClone(tool) {
481
+ if (!tool || typeof tool !== 'object') return tool;
482
+ const generation = getMcpToolGeneration();
483
+ const hit = LIVE_CLONES.get(tool);
484
+ if (hit && hit.generation === generation) return hit.clone;
485
+ const clone = immutableToolForSurface(tool);
486
+ LIVE_CLONES.set(tool, { generation, clone });
487
+ return clone;
488
+ }
489
+
490
+ function reconcileStateOf(session, liveMcpTools) {
491
+ return {
492
+ generation: getMcpToolGeneration(),
493
+ live: Array.isArray(liveMcpTools) ? liveMcpTools.slice() : [],
494
+ tools: session.tools.slice(),
495
+ lateCatalog: session.deferredLateToolCatalog,
496
+ scope: clean(session.mcpScopeId),
497
+ cwd: clean(session.cwd),
498
+ mode: clean(session.deferredSurfaceMode),
499
+ providerMode: clean(session.deferredProviderMode),
500
+ provider: clean(session.provider),
501
+ model: clean(session.model),
502
+ disallowed: JSON.stringify(session.disallowedTools || []),
503
+ callable: JSON.stringify(parseToolSelection(session.deferredCallableTools)),
504
+ discovered: JSON.stringify(parseToolSelection(session.deferredDiscoveredTools)),
505
+ };
506
+ }
507
+
508
+ function reconcileStateMatches(a, b) {
509
+ for (const key of Object.keys(a)) {
510
+ if (key === 'live' || key === 'tools') {
511
+ if (a[key].length !== b[key].length || a[key].some((tool, i) => tool !== b[key][i])) return false;
512
+ } else if (a[key] !== b[key]) return false;
513
+ }
514
+ return true;
515
+ }
516
+
517
+ function reconcileDeferredMcpToolCatalogUncached(session, liveMcpTools) {
467
518
  const live = filterDisallowedTools(Array.isArray(liveMcpTools) ? liveMcpTools : [], session.disallowedTools);
468
519
  const hadSnapshot = Array.isArray(session.deferredMcpToolNames);
469
520
  const previousNames = new Set(
@@ -482,7 +533,7 @@ export function reconcileDeferredMcpToolCatalog(session, liveMcpTools) {
482
533
  }
483
534
  for (const tool of live) {
484
535
  const name = clean(tool?.name);
485
- if (name && isMcpToolName(name)) byName.set(name, activeToolForSurface(tool));
536
+ if (name && isMcpToolName(name)) byName.set(name, liveToolClone(tool));
486
537
  }
487
538
  const catalog = sortedCatalogByMeasuredUsage([...byName.values()]);
488
539
  const next = catalog.filter((tool) => session.deferredSurfaceMode !== 'readonly' || isReadonlySelectable(tool));
@@ -505,7 +556,7 @@ export function reconcileDeferredMcpToolCatalog(session, liveMcpTools) {
505
556
  for (const tool of live) {
506
557
  const name = clean(tool?.name);
507
558
  if (!name || !isMcpToolName(name) || liveMcpByName.has(name)) continue;
508
- liveMcpByName.set(name, activeToolForSurface(tool));
559
+ liveMcpByName.set(name, liveToolClone(tool));
509
560
  }
510
561
 
511
562
  // Rebuild the LATE pool only (boot catalog stays frozen). It holds live MCP
@@ -40,17 +40,21 @@ export const CWD_TOOL = {
40
40
  },
41
41
  trigger: 'Show or switch the active Project; not a shell-local cd.',
42
42
  description:
43
- 'Show or switch the active Project. list returns the registered projects (name, path); a project name instead of a path → list first, then set the matching path, ask only if several match. path must be an existing directory. A shell-local cd does not change the Project.',
43
+ 'Show or switch the active Project. list returns the registered projects (name, path); a project name instead of a path → list first, then set the matching path, ask only if several match. path must be an existing directory. A shell-local cd does not change the Project. merge-worktree / remove-worktree act on this session’s git worktree only when the user explicitly asks.',
44
44
  inputSchema: {
45
45
  type: 'object',
46
46
  properties: {
47
47
  action: {
48
48
  type: 'string',
49
- enum: ['get', 'set', 'list'],
49
+ enum: ['get', 'set', 'list', 'merge-worktree', 'remove-worktree', 'list-worktrees', 'prune-worktrees'],
50
50
  description:
51
- 'get shows the active Project (default without path), set switches to path, list returns registered projects.',
51
+ 'get shows the active Project (default without path), set switches to path, list returns registered projects; merge-worktree merges this session’s worktree into the project folder’s checked-out branch, remove-worktree deletes it (returning to the project folder; call again if it reports WORKTREE_REMOVAL_INCOMPLETE); list-worktrees lists every managed worktree and who retains it; prune-worktrees removes clean, merged ones no session retains.',
52
52
  },
53
53
  path: { type: 'string', description: 'Existing directory to switch to (implies action=set).' },
54
+ force: {
55
+ type: 'boolean',
56
+ description: 'remove-worktree: delete even with uncommitted or unmerged work (only when the user says so).',
57
+ },
54
58
  },
55
59
  additionalProperties: false,
56
60
  },
@@ -93,6 +97,7 @@ const TERMINAL_RUNTIME_TOOLS = ['terminal'];
93
97
  const OFFICE_RUNTIME_TOOLS = ['office'];
94
98
  const MEDIA_RUNTIME_TOOLS = ['media'];
95
99
  const TIDY_RUNTIME_TOOLS = ['tidy'];
100
+ const VISUALIZE_RUNTIME_TOOLS = ['visualize'];
96
101
 
97
102
  const admitted = (tools, names) => (tools || []).filter((tool) => names.includes(tool?.name));
98
103
 
@@ -114,6 +119,7 @@ export function collectStandaloneToolDefs({
114
119
  officeToolDefs = [],
115
120
  mediaToolDefs = [],
116
121
  tidyToolDefs = [],
122
+ visualizeToolDefs = [],
117
123
  setupToolDefs = [],
118
124
  goalTools = [],
119
125
  agentTools = [],
@@ -133,6 +139,7 @@ export function collectStandaloneToolDefs({
133
139
  ...admitted(officeToolDefs, OFFICE_RUNTIME_TOOLS),
134
140
  ...admitted(mediaToolDefs, MEDIA_RUNTIME_TOOLS),
135
141
  ...admitted(tidyToolDefs, TIDY_RUNTIME_TOOLS),
142
+ ...admitted(visualizeToolDefs, VISUALIZE_RUNTIME_TOOLS),
136
143
  ...setupToolDefs,
137
144
  ...goalTools,
138
145
  ...agentTools,
@@ -6,6 +6,7 @@ import { clean } from './session-text.mjs';
6
6
  import { normalizeEffortInput } from './effort.mjs';
7
7
  import { isLikelyRawModelId } from './config-helpers.mjs';
8
8
  import { configuredAgentRouteCandidates } from '../../../shared/agent-route-config.mjs';
9
+ import { normalizeContextTokens } from '../../../shared/llm/default-context-window.mjs';
9
10
  import { normalizeAgentId, normalizeWorkflowId } from './workflow-ids.mjs';
10
11
  import { createWorkflowPacks } from './workflow/packs.mjs';
11
12
  import { createWorkflowAgents } from './workflow/agents.mjs';
@@ -186,19 +187,27 @@ export function normalizeWorkflowRoute(routeLike, fallback = {}) {
186
187
  if (!isLikelyRawModelId(model)) return null;
187
188
  const effort = normalizeEffortInput(routeLike?.effort ?? fallback.effort);
188
189
  const fast = routeLike?.fast ?? fallback.fast;
190
+ // Absent means Auto effort is on; only the off state is carried.
191
+ const autoEffort = routeLike?.autoEffort ?? fallback.autoEffort;
189
192
  const modelParameters = routeLike?.modelParameters ?? fallback.modelParameters;
190
193
  const requestedPercent = Number(routeLike?.contextPercent ?? fallback.contextPercent);
191
194
  const contextPercent =
192
195
  Number.isFinite(requestedPercent) && requestedPercent >= 10 && requestedPercent <= 100
193
196
  ? Math.round(requestedPercent / 10) * 10
194
197
  : null;
198
+ // A percent on the route replaces the fallback's exact window.
199
+ const contextTokens = normalizeContextTokens(
200
+ routeLike?.contextTokens ?? (routeLike?.contextPercent == null ? fallback.contextTokens : undefined)
201
+ );
195
202
  return {
196
203
  provider,
197
204
  model,
198
205
  ...(effort ? { effort } : {}),
199
206
  ...(typeof fast === 'boolean' ? { fast } : {}),
207
+ ...(autoEffort === false ? { autoEffort: false } : {}),
200
208
  ...(modelParameters && typeof modelParameters === 'object' ? { modelParameters: { ...modelParameters } } : {}),
201
209
  ...(contextPercent ? { contextPercent } : {}),
210
+ ...(contextTokens ? { contextTokens } : {}),
202
211
  };
203
212
  }
204
213
 
@@ -214,6 +223,7 @@ export function upsertWorkflowPreset(presets, slot, routeLike) {
214
223
  model: route.model,
215
224
  ...(route.effort ? { effort: route.effort } : {}),
216
225
  ...(route.fast === true ? { fast: true } : {}),
226
+ ...(route.autoEffort === false ? { autoEffort: false } : {}),
217
227
  ...(route.modelParameters ? { modelParameters: route.modelParameters } : {}),
218
228
  tools: 'full',
219
229
  };