mixdog 1.3.0 → 1.3.2

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 (423) hide show
  1. package/NOTICE.md +52 -9
  2. package/README.md +8 -4
  3. package/package.json +2 -2
  4. package/scripts/native-release-status.mjs +238 -0
  5. package/src/defaults/shipped-history.json +8 -1
  6. package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
  7. package/src/defaults/skills/setup/SKILL.md +23 -1
  8. package/src/defaults/skills/setup/references/actions.md +54 -4
  9. package/src/defaults/skills/setup/references/surfaces.md +23 -3
  10. package/src/defaults/skills/setup-guide/SKILL.md +17 -10
  11. package/src/defaults/skills/visualize/SKILL.md +9 -6
  12. package/src/defaults/skills/visualize/references/craft.md +8 -1
  13. package/src/help.mjs +1 -1
  14. package/src/lib/keychain-cjs.cjs +4 -1
  15. package/src/lib/rules-builder.cjs +14 -7
  16. package/src/output-styles/common.md +13 -8
  17. package/src/rules/agent-mode/AGENT_MODE.md +9 -6
  18. package/src/rules/agent-mode/MIXY.md +11 -6
  19. package/src/rules/lead/LEAD.md +2 -2
  20. package/src/rules/shared/10-tool-workflow.md +4 -0
  21. package/src/rules/shared/20-research.md +2 -0
  22. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
  23. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
  24. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
  25. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
  26. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
  27. package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
  28. package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
  29. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
  30. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  31. package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
  32. package/src/runtime/agent/orchestrator/config.mjs +11 -0
  33. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +81 -16
  34. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -4
  35. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
  36. package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
  37. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +80 -31
  38. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
  39. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  40. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +67 -38
  41. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
  42. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +7 -2
  43. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
  44. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +34 -16
  45. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
  46. package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
  47. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
  48. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
  49. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +19 -1
  50. package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
  51. package/src/runtime/agent/orchestrator/providers/lib/tool-call-wire-ids.mjs +65 -0
  52. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -1
  53. package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +3 -1
  54. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +1 -0
  55. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +5 -5
  56. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +9 -5
  57. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +9 -1
  58. package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +8 -2
  59. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +10 -2
  60. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +30 -3
  61. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +1 -1
  62. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +3 -1
  63. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +3 -1
  64. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +61 -8
  65. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +14 -2
  66. package/src/runtime/agent/orchestrator/providers/registry.mjs +9 -0
  67. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +17 -0
  68. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
  69. package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
  70. package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
  71. package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
  72. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
  73. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +5 -5
  74. package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
  75. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
  76. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +12 -1
  77. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +17 -6
  78. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +41 -10
  79. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +6 -4
  80. package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
  81. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +2 -0
  82. package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
  83. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
  84. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
  85. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +21 -2
  86. package/src/runtime/agent/orchestrator/session/agent-room.mjs +149 -45
  87. package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +80 -0
  88. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  89. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +14 -11
  90. package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
  91. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +68 -18
  92. package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
  93. package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
  94. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
  95. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +5 -1
  96. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +1 -1
  97. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
  98. package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
  99. package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +230 -0
  100. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +156 -25
  101. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
  102. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +21 -9
  103. package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
  104. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
  105. package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
  106. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
  107. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +189 -105
  108. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +9 -0
  109. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
  110. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +111 -9
  111. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +11 -14
  112. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
  113. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +38 -5
  114. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +21 -0
  115. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
  116. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
  117. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
  118. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +46 -4
  119. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
  120. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +18 -8
  121. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +4 -0
  122. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  123. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  124. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
  125. package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +99 -0
  126. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
  127. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
  128. package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
  129. package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
  130. package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +50 -0
  131. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +40 -1
  132. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
  133. package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +20 -0
  134. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +62 -47
  135. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +6 -8
  136. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +12 -1
  137. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
  138. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +7 -1
  139. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
  140. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +1 -1
  141. package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
  142. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +5 -2
  143. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +42 -10
  144. package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
  145. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +35 -4
  146. package/src/runtime/agent/orchestrator/session/routine-result.mjs +179 -16
  147. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +5 -1
  148. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
  149. package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
  150. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
  151. package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
  152. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
  153. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +8 -3
  154. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +36 -1
  155. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -1
  156. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
  157. package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
  158. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
  159. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
  160. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
  161. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +49 -14
  162. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +134 -17
  163. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +35 -13
  164. package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
  165. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +4 -3
  166. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
  167. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
  168. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
  169. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
  170. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +6 -14
  171. package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
  172. package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
  173. package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
  174. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
  175. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
  176. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +34 -21
  177. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
  178. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
  179. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
  180. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
  181. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
  182. package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
  183. package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
  184. package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
  185. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
  186. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
  187. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
  188. package/src/runtime/attachments/attachment-reference.mjs +155 -0
  189. package/src/runtime/attachments/limits.mjs +5 -1
  190. package/src/runtime/attachments/office-extract.mjs +3 -6
  191. package/src/runtime/attachments/pdf-extract.mjs +84 -10
  192. package/src/runtime/attachments/store.mjs +55 -30
  193. package/src/runtime/browser-bridge/client.mjs +73 -20
  194. package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
  195. package/src/runtime/channels/lib/scheduler.mjs +14 -7
  196. package/src/runtime/channels/lib/state-file.mjs +15 -15
  197. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
  198. package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
  199. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
  200. package/src/runtime/channels/lib/worker-main.mjs +3 -2
  201. package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
  202. package/src/runtime/effort-judge/judge-client.mjs +6 -3
  203. package/src/runtime/effort-judge/model-install.mjs +16 -8
  204. package/src/runtime/effort-judge/model-manifest.json +7 -7
  205. package/src/runtime/local-provider/asset-installer.mjs +10 -4
  206. package/src/runtime/local-provider/catalog.mjs +5 -2
  207. package/src/runtime/local-provider/registered-models.mjs +64 -27
  208. package/src/runtime/local-provider/server.mjs +2 -3
  209. package/src/runtime/media/adapters/image-api.mjs +89 -0
  210. package/src/runtime/media/auth.mjs +64 -1
  211. package/src/runtime/media/catalog.mjs +66 -4
  212. package/src/runtime/media/jobs.mjs +4 -0
  213. package/src/runtime/media/lanes.mjs +46 -5
  214. package/src/runtime/media/media-usage.mjs +17 -0
  215. package/src/runtime/media/renditions.mjs +15 -15
  216. package/src/runtime/media/store.mjs +36 -35
  217. package/src/runtime/media/tool.mjs +8 -1
  218. package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +8 -2
  219. package/src/runtime/memory/lib/embedding-model-files.mjs +30 -7
  220. package/src/runtime/memory/lib/ingest/transcript-tail.mjs +16 -7
  221. package/src/runtime/memory/lib/pg/process.mjs +8 -4
  222. package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
  223. package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
  224. package/src/runtime/office/core/journal.mjs +7 -14
  225. package/src/runtime/office/design/library/design-library-core.mjs +4 -16
  226. package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
  227. package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
  228. package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
  229. package/src/runtime/office/pdf/pdf-render.mjs +30 -18
  230. package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
  231. package/src/runtime/office/portable/portable-soffice.mjs +4 -0
  232. package/src/runtime/shared/agent-kind-migration.mjs +200 -0
  233. package/src/runtime/shared/agent-kind.mjs +152 -0
  234. package/src/runtime/shared/agent-message-envelope.d.mts +6 -0
  235. package/src/runtime/shared/agent-message-envelope.mjs +44 -0
  236. package/src/runtime/shared/agent-reap-state.d.mts +4 -0
  237. package/src/runtime/shared/agent-route-config.d.mts +1 -0
  238. package/src/runtime/shared/agent-route-config.mjs +22 -8
  239. package/src/runtime/shared/agent-store.mjs +149 -0
  240. package/src/runtime/shared/agent-task-starts.mjs +27 -0
  241. package/src/runtime/shared/atomic-file.d.mts +50 -0
  242. package/src/runtime/shared/atomic-file.mjs +394 -132
  243. package/src/runtime/shared/background-tasks.mjs +7 -4
  244. package/src/runtime/shared/bot-hops.mjs +30 -0
  245. package/src/runtime/shared/bot-split-migration.mjs +457 -0
  246. package/src/runtime/shared/bounded-download.mjs +4 -2
  247. package/src/runtime/shared/config-patch.mjs +20 -1
  248. package/src/runtime/shared/config.mjs +39 -9
  249. package/src/runtime/shared/connector-catalog.d.mts +1 -2
  250. package/src/runtime/shared/connector-catalog.mjs +2 -4
  251. package/src/runtime/shared/connector-triggers.mjs +2 -0
  252. package/src/runtime/shared/defaults-separation.mjs +6 -2
  253. package/src/runtime/shared/delegated-approval-host.mjs +74 -0
  254. package/src/runtime/shared/err-text.mjs +7 -1
  255. package/src/runtime/shared/error-presentation.mjs +113 -10
  256. package/src/runtime/shared/file-has-data.mjs +28 -0
  257. package/src/runtime/shared/handoff-artifacts.d.mts +20 -0
  258. package/src/runtime/shared/handoff-artifacts.mjs +61 -0
  259. package/src/runtime/shared/llm/provider-model-identities.mjs +6 -1
  260. package/src/runtime/shared/llm/usage-accounting.mjs +7 -1
  261. package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
  262. package/src/runtime/shared/model-route-fallback.mjs +79 -7
  263. package/src/runtime/shared/pending-agent-work.mjs +54 -0
  264. package/src/runtime/shared/plain-preview.mjs +22 -17
  265. package/src/runtime/shared/process-lifecycle.mjs +8 -12
  266. package/src/runtime/shared/projects.mjs +50 -41
  267. package/src/runtime/shared/provider-accounts.mjs +17 -12
  268. package/src/runtime/shared/routine-trigger.d.mts +6 -1
  269. package/src/runtime/shared/routine-trigger.mjs +14 -6
  270. package/src/runtime/shared/run-child.d.mts +3 -0
  271. package/src/runtime/shared/run-child.mjs +24 -3
  272. package/src/runtime/shared/sensitive-path-rules.d.mts +15 -0
  273. package/src/runtime/shared/sensitive-path-rules.mjs +256 -0
  274. package/src/runtime/shared/session-baseline-store.mjs +8 -2
  275. package/src/runtime/shared/singleton-owner.mjs +7 -6
  276. package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
  277. package/src/runtime/shared/staged-update.mjs +4 -2
  278. package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
  279. package/src/runtime/shared/tool-execution-contract.mjs +6 -0
  280. package/src/runtime/shared/transcript-metadata.mjs +28 -3
  281. package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
  282. package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
  283. package/src/runtime/shared/turn-snapshot.mjs +44 -2
  284. package/src/runtime/shared/usage-readout-totals.mjs +15 -13
  285. package/src/runtime/shared/user-cwd.mjs +9 -4
  286. package/src/runtime/shared/user-data-guard.mjs +46 -44
  287. package/src/runtime/shared/utf8-cut.mjs +10 -0
  288. package/src/runtime/shared/worktrees/activity-gate.mjs +269 -61
  289. package/src/runtime/shared/worktrees/registry.mjs +26 -26
  290. package/src/runtime/tidy/core-install.mjs +26 -4
  291. package/src/runtime/tidy/install.mjs +91 -23
  292. package/src/runtime/web-search/lib/config.mjs +4 -16
  293. package/src/session-runtime/agent-home-delivery.mjs +859 -136
  294. package/src/session-runtime/agent-room-delivery.mjs +167 -19
  295. package/src/session-runtime/agent-task-sessions.mjs +831 -69
  296. package/src/session-runtime/agent-tool-routing.mjs +1 -0
  297. package/src/session-runtime/boot/apis.mjs +58 -24
  298. package/src/session-runtime/boot/config.mjs +1 -0
  299. package/src/session-runtime/boot/facade.mjs +11 -1
  300. package/src/session-runtime/boot/infrastructure.mjs +14 -2
  301. package/src/session-runtime/boot/providers.mjs +16 -6
  302. package/src/session-runtime/boot/shared.mjs +12 -6
  303. package/src/session-runtime/boot/tools.mjs +2 -2
  304. package/src/session-runtime/bot-status.mjs +432 -0
  305. package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
  306. package/src/session-runtime/config-lifecycle.mjs +78 -32
  307. package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
  308. package/src/session-runtime/handoff-attachments.mjs +593 -0
  309. package/src/session-runtime/held-requests.mjs +36 -33
  310. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
  311. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
  312. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +45 -6
  313. package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
  314. package/src/session-runtime/lifecycle/teardown.mjs +11 -3
  315. package/src/session-runtime/mcp-glue/connect.mjs +100 -186
  316. package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
  317. package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
  318. package/src/session-runtime/media-api.mjs +5 -2
  319. package/src/session-runtime/model-route/route-persist.mjs +2 -2
  320. package/src/session-runtime/model-route/route-tuning.mjs +11 -2
  321. package/src/session-runtime/model-route/set-route.mjs +82 -2
  322. package/src/session-runtime/native-web-search.mjs +2 -2
  323. package/src/session-runtime/new-session-config.mjs +6 -1
  324. package/src/session-runtime/owned-automation-run.mjs +8 -1
  325. package/src/session-runtime/room-member-gate.mjs +16 -3
  326. package/src/session-runtime/route-resolution.mjs +21 -19
  327. package/src/session-runtime/route-state.mjs +1 -59
  328. package/src/session-runtime/routine-home-turn.mjs +5 -3
  329. package/src/session-runtime/runtime-bootstrap.mjs +5 -3
  330. package/src/session-runtime/runtime-paths.mjs +11 -5
  331. package/src/session-runtime/runtime-tunables.mjs +6 -0
  332. package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
  333. package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
  334. package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
  335. package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
  336. package/src/session-runtime/services/agent-tool/helpers.mjs +24 -5
  337. package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
  338. package/src/session-runtime/services/agent-tool/send-flow.mjs +36 -24
  339. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +4 -0
  340. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
  341. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +12 -6
  342. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +5 -8
  343. package/src/session-runtime/services/agent-tool/tool-def.mjs +7 -0
  344. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
  345. package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
  346. package/src/session-runtime/services/agent-tool.mjs +6 -0
  347. package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
  348. package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
  349. package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
  350. package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
  351. package/src/session-runtime/services/plugin-admin.mjs +26 -26
  352. package/src/session-runtime/services/provider-admin.mjs +14 -4
  353. package/src/session-runtime/services/schedule-access.mjs +33 -4
  354. package/src/session-runtime/services/seeds.mjs +4 -3
  355. package/src/session-runtime/services/usage-dashboard.mjs +1 -1
  356. package/src/session-runtime/services/usage-stats-model.mjs +2 -1
  357. package/src/session-runtime/session-create/materialize.mjs +2 -3
  358. package/src/session-runtime/session-create/session-options.mjs +15 -2
  359. package/src/session-runtime/settings-builtin-tools-api.mjs +63 -1
  360. package/src/session-runtime/settings-compaction-api.mjs +86 -39
  361. package/src/session-runtime/setup-tool/executor.mjs +20 -3
  362. package/src/session-runtime/setup-tool/extended-actions.mjs +19 -7
  363. package/src/session-runtime/setup-tool/settings-contract.mjs +37 -1
  364. package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
  365. package/src/session-runtime/skills-api.mjs +12 -5
  366. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +265 -0
  367. package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
  368. package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
  369. package/src/session-runtime/tool-policy/policy-stage.mjs +165 -0
  370. package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
  371. package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
  372. package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
  373. package/src/session-runtime/tool-policy-refresh.mjs +47 -358
  374. package/src/session-runtime/tool-surface.mjs +5 -3
  375. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +99 -81
  376. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  377. package/src/session-runtime/turn/turn-run.mjs +2 -1
  378. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
  379. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +578 -301
  380. package/src/session-runtime/workflow-agents-api/agent-route.mjs +113 -53
  381. package/src/session-runtime/workflow-agents-api/onboarding.mjs +11 -4
  382. package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
  383. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
  384. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
  385. package/src/session-runtime/workspace/workspace-state.mjs +7 -6
  386. package/src/standalone/daemon-agent-control.mjs +4 -1
  387. package/src/standalone/daemon-session-control.mjs +60 -11
  388. package/src/standalone/daemon.mjs +34 -4
  389. package/src/standalone/local-session-runtime.mjs +22 -14
  390. package/src/standalone/session-client.mjs +7 -2
  391. package/src/standalone/session-protocol.mjs +7 -0
  392. package/src/standalone/session-service/agent-tree/agent-turns.mjs +46 -3
  393. package/src/standalone/session-service/agent-tree.mjs +4 -3
  394. package/src/standalone/session-service/session-calls/session-turns.mjs +19 -0
  395. package/src/standalone/session-service.mjs +11 -0
  396. package/src/standalone/session-state-projection.mjs +19 -9
  397. package/src/standalone/session-tool-output.mjs +35 -9
  398. package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
  399. package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
  400. package/src/tui/app/slash-commands.mjs +1 -1
  401. package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
  402. package/src/tui/dist/index.mjs +599 -342
  403. package/src/tui/prompt-history-store.mjs +16 -21
  404. package/src/tui/session/context-state.mjs +4 -0
  405. package/src/tui/session/notice-surface.mjs +3 -4
  406. package/src/tui/session/session-api/intake/abort.mjs +19 -1
  407. package/src/tui/session/session-api/media.mjs +2 -1
  408. package/src/tui/session/session-api/routes-agent-definitions.mjs +75 -0
  409. package/src/tui/session/session-api/routes-write.mjs +203 -0
  410. package/src/tui/session/session-api/routes.mjs +23 -236
  411. package/src/tui/session/session-api/settings.mjs +14 -1
  412. package/src/tui/session/session-flow/drain.mjs +28 -3
  413. package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
  414. package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
  415. package/src/tui/session/session-flow/steering.mjs +8 -3
  416. package/src/tui/session/tool-approval.mjs +185 -20
  417. package/src/tui/session/transcript-intake.mjs +4 -0
  418. package/src/tui/session/turn-live-call.mjs +11 -3
  419. package/src/tui/session/turn.mjs +36 -17
  420. package/src/tui/transcript-tool-failures.mjs +8 -2
  421. package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -8
  422. package/src/vendor/statusline/src/gateway/session-routes.mjs +6 -7
  423. package/src/rules/agent-mode/ROUTINE.md +0 -6
@@ -3,7 +3,9 @@
3
3
  // connect. All mutable progress lives on the caller-owned `state`.
4
4
  import { clean } from '../../runtime/agent/orchestrator/runtime-core/session-text.mjs';
5
5
  import { envFlag } from '../../runtime/shared/env.mjs';
6
+ import { awaitInitialConnect } from './initial-connect-gate.mjs';
6
7
  import { ensurePluginServerData } from './plugin-servers.mjs';
8
+ import { createServerConnection } from './server-connection.mjs';
7
9
 
8
10
  export function createMcpConnect({
9
11
  mcpClient,
@@ -29,58 +31,100 @@ export function createMcpConnect({
29
31
  // Answer it with the redirect URL pasted from the browser.
30
32
  const completeMcpLogin = (name, callbackUrl) => mcpClient.completeMcpLogin(clean(name), callbackUrl, scopeOptions());
31
33
 
32
- // Connect/disconnect exactly one server in the live registry, leaving all
33
- // others untouched. Used by the enable/disable toggle so a single-server
34
- // change never triggers a full disconnectAll()/reconnect freeze. Only an
35
- // explicit sign-in passes `login` ({ openBrowser }): every other connect is
36
- // non-interactive and reports a server that needs one as sign-in required.
37
- async function applyMcpServerConnection(name, enabled, servers = resolveEffectiveMcpServers().servers, login = null) {
38
- const target = clean(name);
39
- if (!target) return;
40
- // Changing this server's state clears any stale failure record for it.
41
- if (Array.isArray(state.mcpFailures)) {
42
- state.mcpFailures = state.mcpFailures.filter((row) => row.name !== target);
43
- }
44
- const cfg = servers[target];
45
- if (enabled === false || !cfg || cfg.enabled === false) {
46
- applied().delete(target);
47
- await mcpClient.disconnectMcpServer?.(target, scopeOptions());
48
- return;
34
+ const { applyMcpServerConnection, reconcileServers } = createServerConnection({
35
+ mcpClient,
36
+ state,
37
+ resolveEffectiveMcpServers,
38
+ getMcpScopeId,
39
+ scopeOptions,
40
+ applied,
41
+ fingerprint,
42
+ syncScopeRoot,
43
+ });
44
+
45
+ // Wait for a prior run (its failures were already captured in state).
46
+ async function settled(prior) {
47
+ if (!prior) return;
48
+ try {
49
+ await prior;
50
+ } catch {
51
+ /* prior run's failures already captured */
49
52
  }
50
- applied().set(target, fingerprint(cfg));
51
- // Close-then-connect as one generation-guarded op: a shutdown landing
52
- // between the close and the connect never resurrects the server.
53
+ }
54
+
55
+ // Register `run` as the in-flight connect so a later reset serializes
56
+ // behind it instead of interleaving disconnect/connect.
57
+ async function trackInFlight(run) {
58
+ state.mcpConnectInFlight = run;
53
59
  try {
54
- ensurePluginServerData(cfg);
55
- await mcpClient.reconnectSingleMcpServer(
56
- getMcpScopeId(),
57
- target,
58
- cfg,
59
- login ? { interactive: true, openBrowser: login.openBrowser } : {}
60
- );
61
- } catch (error) {
62
- const failure = { name: target, msg: error?.message || String(error) };
63
- if (error?.authRequired) failure.authRequired = true;
64
- const failures = Array.isArray(error?.failures) ? error.failures : [failure];
65
- state.mcpFailures = [...(state.mcpFailures || []), ...failures];
60
+ await run;
61
+ } finally {
62
+ if (state.mcpConnectInFlight === run) state.mcpConnectInFlight = null;
66
63
  }
67
64
  }
68
65
 
69
- // Bring the registry in line with the effective set: drop servers that are
70
- // gone or disabled, (re)connect servers that are new or whose resolved
71
- // config changed; untouched servers keep their connection.
72
- async function reconcileServers() {
73
- syncScopeRoot();
74
- const { servers } = resolveEffectiveMcpServers();
75
- const known = applied();
76
- const drops = [...known.keys()].filter((name) => !servers[name] || servers[name].enabled === false);
77
- const connects = Object.entries(servers)
78
- .filter(([name, cfg]) => cfg?.enabled !== false && known.get(name) !== fingerprint(cfg))
79
- .map(([name]) => name);
80
- await Promise.allSettled([
81
- ...drops.map((name) => applyMcpServerConnection(name, false, servers)),
82
- ...connects.map((name) => applyMcpServerConnection(name, true, servers)),
83
- ]);
66
+ // Scoped single-server toggle (or reconcile): non-superseding. It must NEVER
67
+ // cancel a pending full {reset} (cwd-change/boot). So do not bump the
68
+ // generation; just wait for any in-flight run, then bail if a newer full
69
+ // reset has been requested in the meantime.
70
+ async function connectScoped({ only, enabled, reconcile, login }) {
71
+ // A disable/remove must not wait out an interactive login of the
72
+ // connect it queues behind: cancel that login first.
73
+ if (only && enabled === false) cancelMcpLogin(only);
74
+ // Atomically capture the current generation AND the prior in-flight
75
+ // promise in the same synchronous step, then chain our op onto it. No
76
+ // await sits between the capture and the `state.mcpConnectInFlight = run`
77
+ // assignment, so concurrent {only} calls queue FIFO instead of resuming
78
+ // together and clobbering the in-flight slot. We never bump the
79
+ // generation; a {reset} does, so any {only} queued behind a reset sees
80
+ // the newer generation when its turn comes and bails.
81
+ const gen = state.mcpConnectGeneration;
82
+ const prev = state.mcpConnectInFlight;
83
+ await trackInFlight(
84
+ (async () => {
85
+ await settled(prev);
86
+ if (gen !== state.mcpConnectGeneration) return;
87
+ if (reconcile) await reconcileServers();
88
+ else await applyMcpServerConnection(only, enabled, undefined, login);
89
+ })()
90
+ );
91
+ }
92
+
93
+ // Serialize reconnects: boot connect, cwd-change reset, and rapid cwd
94
+ // switches must never interleave their disconnect/connect phases, or an
95
+ // older run finishing after a newer reset could re-add stale servers into
96
+ // the shared client registry. Approach: a generation token + a single
97
+ // in-flight promise. Each call bumps the generation, waits for any prior
98
+ // run to finish, then bails if a newer call has superseded it — leaving the
99
+ // latest requested effective-server-set in the registry.
100
+ async function connectAll({ reset }) {
101
+ const gen = ++state.mcpConnectGeneration;
102
+ await settled(state.mcpConnectInFlight);
103
+ if (gen !== state.mcpConnectGeneration) return;
104
+ await trackInFlight(
105
+ (async () => {
106
+ if (reset) {
107
+ await mcpClient.disconnectAll?.(scopeOptions());
108
+ applied().clear();
109
+ }
110
+ state.mcpFailures = [];
111
+ syncScopeRoot();
112
+ const { servers } = resolveEffectiveMcpServers();
113
+ for (const [name, cfg] of Object.entries(servers)) {
114
+ if (cfg?.enabled === false) applied().delete(name);
115
+ else applied().set(name, fingerprint(cfg));
116
+ }
117
+ if (Object.keys(servers).length === 0) return;
118
+ try {
119
+ for (const cfg of Object.values(servers)) if (cfg?.enabled !== false) ensurePluginServerData(cfg);
120
+ await mcpClient.connectMcpServers(servers, scopeOptions());
121
+ } catch (error) {
122
+ state.mcpFailures = Array.isArray(error?.failures)
123
+ ? error.failures
124
+ : [{ name: 'mcp', msg: error?.message || String(error) }];
125
+ }
126
+ })()
127
+ );
84
128
  }
85
129
 
86
130
  async function connectConfiguredMcp({
@@ -98,146 +142,16 @@ export function createMcpConnect({
98
142
  else await mcpClient.disconnectAll?.(scopeOptions());
99
143
  return mcpStatus();
100
144
  }
101
- // Scoped single-server toggle: non-superseding. It must NEVER cancel a
102
- // pending full {reset} (cwd-change/boot). So do not bump the generation;
103
- // just wait for any in-flight run, then bail if a newer full reset has
104
- // been requested in the meantime. Registering as in-flight makes a later
105
- // reset serialize behind us instead of interleaving disconnect/connect.
106
- if (only || reconcile) {
107
- // A disable/remove must not wait out an interactive login of the
108
- // connect it queues behind: cancel that login first.
109
- if (only && enabled === false) cancelMcpLogin(only);
110
- // Atomically capture the current generation AND the prior in-flight
111
- // promise in the same synchronous step, then chain our op onto it. No
112
- // await sits between the capture and the `state.mcpConnectInFlight = run`
113
- // assignment, so concurrent {only} calls queue FIFO instead of resuming
114
- // together and clobbering the in-flight slot. We never bump the
115
- // generation; a {reset} does, so any {only} queued behind a reset sees
116
- // the newer generation when its turn comes and bails.
117
- const gen = state.mcpConnectGeneration;
118
- const prev = state.mcpConnectInFlight;
119
- const run = (async () => {
120
- if (prev) {
121
- try {
122
- await prev;
123
- } catch {
124
- /* prior run's failures already captured */
125
- }
126
- }
127
- if (gen !== state.mcpConnectGeneration) return;
128
- if (reconcile) await reconcileServers();
129
- else await applyMcpServerConnection(only, enabled, undefined, login);
130
- })();
131
- state.mcpConnectInFlight = run;
132
- try {
133
- await run;
134
- } finally {
135
- if (state.mcpConnectInFlight === run) state.mcpConnectInFlight = null;
136
- }
137
- return mcpStatus();
138
- }
139
- // Serialize reconnects: boot connect, cwd-change reset, and rapid cwd
140
- // switches must never interleave their disconnect/connect phases, or an
141
- // older run finishing after a newer reset could re-add stale servers into
142
- // the shared client registry. Approach: a generation token + a single
143
- // in-flight promise. Each call bumps the generation, waits for any prior
144
- // run to finish, then bails if a newer call has superseded it — leaving the
145
- // latest requested effective-server-set in the registry.
146
- const gen = ++state.mcpConnectGeneration;
147
- if (state.mcpConnectInFlight) {
148
- try {
149
- await state.mcpConnectInFlight;
150
- } catch {
151
- /* prior run's failures already captured */
152
- }
153
- }
154
- if (gen !== state.mcpConnectGeneration) return mcpStatus();
155
- const run = (async () => {
156
- if (reset) {
157
- await mcpClient.disconnectAll?.(scopeOptions());
158
- applied().clear();
159
- }
160
- state.mcpFailures = [];
161
- syncScopeRoot();
162
- const { servers } = resolveEffectiveMcpServers();
163
- for (const [name, cfg] of Object.entries(servers)) {
164
- if (cfg?.enabled === false) applied().delete(name);
165
- else applied().set(name, fingerprint(cfg));
166
- }
167
- if (Object.keys(servers).length === 0) return;
168
- try {
169
- for (const cfg of Object.values(servers)) if (cfg?.enabled !== false) ensurePluginServerData(cfg);
170
- await mcpClient.connectMcpServers(servers, scopeOptions());
171
- } catch (error) {
172
- state.mcpFailures = Array.isArray(error?.failures)
173
- ? error.failures
174
- : [{ name: 'mcp', msg: error?.message || String(error) }];
175
- }
176
- })();
177
- state.mcpConnectInFlight = run;
178
- try {
179
- await run;
180
- } finally {
181
- if (state.mcpConnectInFlight === run) state.mcpConnectInFlight = null;
182
- }
145
+ if (only || reconcile) await connectScoped({ only, enabled, reconcile, login });
146
+ else await connectAll({ reset });
183
147
  return mcpStatus();
184
148
  }
185
149
 
186
- // Turn gate: await the in-flight INITIAL connect, bounded by both the global
187
- // server startup budget and the caller's TTFT grace. A server still
188
- // connecting after the grace flows through the existing late-tool deferred
189
- // announcement path unchanged.
190
- async function awaitInitialMcpConnect(maxWaitMs = undefined) {
191
- const inFlight = state.mcpConnectInFlight;
192
- if (!inFlight) return;
193
- let budgetMs = 10000;
194
- try {
195
- // The gate must outlast the slowest enabled server's own startup budget;
196
- // a server with its budget disabled (0) means the connect may never
197
- // settle, so the whole gate is disabled (handled below).
198
- const configs = Object.values(resolveEffectiveMcpServers().servers).filter((cfg) => cfg?.enabled !== false);
199
- const budgets = (configs.length > 0 ? configs : [{}])
200
- .map((cfg) => mcpClient.resolveMcpStartupTimeoutMs?.(cfg))
201
- .filter((value) => Number.isFinite(value));
202
- if (budgets.length > 0) budgetMs = budgets.includes(0) ? 0 : Math.max(...budgets);
203
- } catch {
204
- /* fall back to default budget */
205
- }
206
- if (maxWaitMs !== null && maxWaitMs !== undefined) {
207
- const requestedMaxWaitMs = Number(maxWaitMs);
208
- if (Number.isFinite(requestedMaxWaitMs) && requestedMaxWaitMs >= 0) {
209
- budgetMs = Math.min(budgetMs, requestedMaxWaitMs);
210
- }
211
- }
212
- // Budget disabled (0/off) = no per-server startup timeout, so the connect
213
- // promise may never settle; never gate the turn on it — fall back to the
214
- // legacy fire-and-forget behavior (late servers use the deferred path).
215
- if (!(budgetMs > 0)) return;
216
- let timer = null;
217
- let expired = false;
218
- const budget = new Promise((resolveBudget) => {
219
- timer = setTimeout(() => {
220
- expired = true;
221
- resolveBudget();
222
- }, budgetMs);
223
- });
224
- try {
225
- // Follow replacement connects (a reconnect queued behind the captured
226
- // run installs a new in-flight promise) inside ONE fixed deadline.
227
- // Failures are already captured in state.mcpFailures, so rejections are
228
- // swallowed: this gate must never reject the turn.
229
- let current = inFlight;
230
- while (current && !expired) {
231
- await Promise.race([Promise.resolve(current).catch(() => {}), budget]);
232
- if (expired) break;
233
- // Let a queued reconnect install its promise before re-checking.
234
- await Promise.race([new Promise((resolveTick) => setImmediate(resolveTick)), budget]);
235
- current = state.mcpConnectInFlight;
236
- }
237
- } finally {
238
- if (timer) clearTimeout(timer);
239
- }
240
- }
241
-
242
- return { connectConfiguredMcp, awaitInitialMcpConnect, cancelMcpLogin, completeMcpLogin };
150
+ return {
151
+ connectConfiguredMcp,
152
+ awaitInitialMcpConnect: (maxWaitMs) =>
153
+ awaitInitialConnect({ state, mcpClient, resolveEffectiveMcpServers }, maxWaitMs),
154
+ cancelMcpLogin,
155
+ completeMcpLogin,
156
+ };
243
157
  }
@@ -0,0 +1,57 @@
1
+ // The turn gate on the initial MCP connect: a bounded wait, never a rejection.
2
+
3
+ // Turn gate: await the in-flight INITIAL connect, bounded by both the global
4
+ // server startup budget and the caller's TTFT grace. A server still
5
+ // connecting after the grace flows through the existing late-tool deferred
6
+ // announcement path unchanged.
7
+ export async function awaitInitialConnect({ state, mcpClient, resolveEffectiveMcpServers }, maxWaitMs = undefined) {
8
+ const inFlight = state.mcpConnectInFlight;
9
+ if (!inFlight) return;
10
+ let budgetMs = 10000;
11
+ try {
12
+ // The gate must outlast the slowest enabled server's own startup budget;
13
+ // a server with its budget disabled (0) means the connect may never
14
+ // settle, so the whole gate is disabled (handled below).
15
+ const configs = Object.values(resolveEffectiveMcpServers().servers).filter((cfg) => cfg?.enabled !== false);
16
+ const budgets = (configs.length > 0 ? configs : [{}])
17
+ .map((cfg) => mcpClient.resolveMcpStartupTimeoutMs?.(cfg))
18
+ .filter((value) => Number.isFinite(value));
19
+ if (budgets.length > 0) budgetMs = budgets.includes(0) ? 0 : Math.max(...budgets);
20
+ } catch {
21
+ /* fall back to default budget */
22
+ }
23
+ if (maxWaitMs !== null && maxWaitMs !== undefined) {
24
+ const requestedMaxWaitMs = Number(maxWaitMs);
25
+ if (Number.isFinite(requestedMaxWaitMs) && requestedMaxWaitMs >= 0) {
26
+ budgetMs = Math.min(budgetMs, requestedMaxWaitMs);
27
+ }
28
+ }
29
+ // Budget disabled (0/off) = no per-server startup timeout, so the connect
30
+ // promise may never settle; never gate the turn on it — fall back to the
31
+ // legacy fire-and-forget behavior (late servers use the deferred path).
32
+ if (!(budgetMs > 0)) return;
33
+ let timer = null;
34
+ let expired = false;
35
+ const budget = new Promise((resolveBudget) => {
36
+ timer = setTimeout(() => {
37
+ expired = true;
38
+ resolveBudget();
39
+ }, budgetMs);
40
+ });
41
+ try {
42
+ // Follow replacement connects (a reconnect queued behind the captured
43
+ // run installs a new in-flight promise) inside ONE fixed deadline.
44
+ // Failures are already captured in state.mcpFailures, so rejections are
45
+ // swallowed: this gate must never reject the turn.
46
+ let current = inFlight;
47
+ while (current && !expired) {
48
+ await Promise.race([Promise.resolve(current).catch(() => {}), budget]);
49
+ if (expired) break;
50
+ // Let a queued reconnect install its promise before re-checking.
51
+ await Promise.race([new Promise((resolveTick) => setImmediate(resolveTick)), budget]);
52
+ current = state.mcpConnectInFlight;
53
+ }
54
+ } finally {
55
+ if (timer) clearTimeout(timer);
56
+ }
57
+ }
@@ -0,0 +1,71 @@
1
+ // Connecting, disconnecting and reconciling individual MCP servers in the live
2
+ // registry. The caller (connect.mjs) owns the serialization and the shared state.
3
+ import { clean } from '../../runtime/agent/orchestrator/runtime-core/session-text.mjs';
4
+ import { ensurePluginServerData } from './plugin-servers.mjs';
5
+
6
+ export function createServerConnection({
7
+ mcpClient,
8
+ state,
9
+ resolveEffectiveMcpServers,
10
+ getMcpScopeId,
11
+ scopeOptions,
12
+ applied,
13
+ fingerprint,
14
+ syncScopeRoot,
15
+ }) {
16
+ // Connect/disconnect exactly one server in the live registry, leaving all
17
+ // others untouched. Used by the enable/disable toggle so a single-server
18
+ // change never triggers a full disconnectAll()/reconnect freeze. Only an
19
+ // explicit sign-in passes `login` ({ openBrowser }): every other connect is
20
+ // non-interactive and reports a server that needs one as sign-in required.
21
+ async function applyMcpServerConnection(name, enabled, servers = resolveEffectiveMcpServers().servers, login = null) {
22
+ const target = clean(name);
23
+ if (!target) return;
24
+ // Changing this server's state clears any stale failure record for it.
25
+ if (Array.isArray(state.mcpFailures)) {
26
+ state.mcpFailures = state.mcpFailures.filter((row) => row.name !== target);
27
+ }
28
+ const cfg = servers[target];
29
+ if (enabled === false || !cfg || cfg.enabled === false) {
30
+ applied().delete(target);
31
+ await mcpClient.disconnectMcpServer?.(target, scopeOptions());
32
+ return;
33
+ }
34
+ applied().set(target, fingerprint(cfg));
35
+ // Close-then-connect as one generation-guarded op: a shutdown landing
36
+ // between the close and the connect never resurrects the server.
37
+ try {
38
+ ensurePluginServerData(cfg);
39
+ await mcpClient.reconnectSingleMcpServer(
40
+ getMcpScopeId(),
41
+ target,
42
+ cfg,
43
+ login ? { interactive: true, openBrowser: login.openBrowser } : {}
44
+ );
45
+ } catch (error) {
46
+ const failure = { name: target, msg: error?.message || String(error) };
47
+ if (error?.authRequired) failure.authRequired = true;
48
+ const failures = Array.isArray(error?.failures) ? error.failures : [failure];
49
+ state.mcpFailures = [...(state.mcpFailures || []), ...failures];
50
+ }
51
+ }
52
+
53
+ // Bring the registry in line with the effective set: drop servers that are
54
+ // gone or disabled, (re)connect servers that are new or whose resolved
55
+ // config changed; untouched servers keep their connection.
56
+ async function reconcileServers() {
57
+ syncScopeRoot();
58
+ const { servers } = resolveEffectiveMcpServers();
59
+ const known = applied();
60
+ const drops = [...known.keys()].filter((name) => !servers[name] || servers[name].enabled === false);
61
+ const connects = Object.entries(servers)
62
+ .filter(([name, cfg]) => cfg?.enabled !== false && known.get(name) !== fingerprint(cfg))
63
+ .map(([name]) => name);
64
+ await Promise.allSettled([
65
+ ...drops.map((name) => applyMcpServerConnection(name, false, servers)),
66
+ ...connects.map((name) => applyMcpServerConnection(name, true, servers)),
67
+ ]);
68
+ }
69
+
70
+ return { applyMcpServerConnection, reconcileServers };
71
+ }
@@ -41,8 +41,11 @@ export function createMediaApi() {
41
41
  });
42
42
  return { status: await module.getVoiceStatus(), error };
43
43
  },
44
- async listMediaLanes() {
45
- return (await lanes()).listMediaLanes();
44
+ // `budgetMs` (Studio) caps the per-lane catalog wait; slower lanes come
45
+ // back `catalogPending`. Capped so a caller cannot park the call.
46
+ async listMediaLanes(options) {
47
+ const budgetMs = Math.min(30_000, Math.max(0, Math.trunc(Number(options?.budgetMs) || 0)));
48
+ return (await lanes()).listMediaLanes({ budgetMs });
46
49
  },
47
50
  // The Studio's current selection is the runtime's default: a tool call
48
51
  // that omits lane/model follows it. Studio pushes on every settled change.
@@ -35,7 +35,7 @@ export function createRoutePersistence(deps) {
35
35
  hasSecrets: getConfigHasSecrets(),
36
36
  });
37
37
  },
38
- persistAdoptedModelSettings(route, { keepMainModel } = {}) {
38
+ persistAdoptedModelSettings(route, { keepMainModel, onlyIfAbsent = false } = {}) {
39
39
  // saveModelSettings is in-memory only. persistLeadRoute debounce-writes
40
40
  // the adopted config (including modelSettings). If the lead preset cannot
41
41
  // be normalized, still debounce-persist so effort/fast are not memory-only.
@@ -43,7 +43,7 @@ export function createRoutePersistence(deps) {
43
43
  keepMainModel === true ||
44
44
  (keepMainModel !== false && tunesNonMainModel(getRoute(), route, resolveRoute(getConfig(), {})))
45
45
  ? null
46
- : persistLeadRoute(route);
46
+ : persistLeadRoute(route, { onlyIfAbsent });
47
47
  if (!leadRoute) saveConfigAndAdopt(getConfig());
48
48
  return leadRoute;
49
49
  },
@@ -10,7 +10,13 @@ export function createRouteTuning(deps, persist) {
10
10
  const { getConfig, getRoute, setRouteState, resolveRoute, lookupModelMeta, refreshRouteEffort } = deps;
11
11
 
12
12
  /** Save the live route's modelSettings, re-resolve through the lead preset
13
- * when one exists, refresh effort and tune the current session. */
13
+ * when one exists, refresh effort and tune the current session.
14
+ *
15
+ * This is not a Main-model write and needs no compare-and-set: it persists
16
+ * the LIVE route (read after every await), and only when that route's model
17
+ * is already the Main model (persistAdoptedModelSettings: tunesNonMainModel),
18
+ * so the lead preset's model is rewritten with itself and never changes. A
19
+ * live route without a model normalizes to nothing and writes no preset. */
14
20
  async function commitRouteTuning(fastCapable, modelMeta) {
15
21
  persist.saveRouteModelSettings(getRoute(), fastCapable);
16
22
  const leadRoute = persist.persistAdoptedModelSettings(getRoute());
@@ -61,7 +67,10 @@ export function createRouteTuning(deps, persist) {
61
67
  },
62
68
  async setEffort(value) {
63
69
  const normalized = normalizeEffortInput(value);
64
- setRouteState({ ...getRoute(), effort: normalized });
70
+ // An explicit level is the user's choice for this route: Auto effort
71
+ // would replace it on the next turn (its base is fixed), so the pick
72
+ // turns the route's Auto off until it is switched back on.
73
+ setRouteState({ ...getRoute(), effort: normalized, ...(normalized ? { autoEffort: false } : {}) });
65
74
  const modelMeta = await lookupModelMeta(getRoute().provider, getRoute().model);
66
75
  const fastCapable = selectionFastCapable(getRoute().provider, modelMeta, normalized, getRoute().modelParameters);
67
76
  await commitRouteTuning(fastCapable, modelMeta);
@@ -24,6 +24,11 @@ import {
24
24
  } from '../../runtime/agent/orchestrator/runtime-core/session-route-policy.mjs';
25
25
  import { rebuildToolSurfaceForProviderWithMigration } from '../../runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs';
26
26
  import { UNOWNED_LEASE, openRouteLease } from './route-ownership.mjs';
27
+ import { resolveSessionMode } from '../../runtime/agent/orchestrator/session/agent-mode.mjs';
28
+ import { ownerRouteFallbacks } from '../../runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs';
29
+ import { normalizeWorkflowRoute } from '../../runtime/agent/orchestrator/runtime-core/workflow.mjs';
30
+ import { routeChain, validateRouteChainProviders } from '../../runtime/shared/model-route-chain.mjs';
31
+ import { resolveChainEntry } from '../workflow-agents-api/agent-route.mjs';
27
32
 
28
33
  /** The request, completed from the live route where the caller left gaps. */
29
34
  function completeRequest(deps, next) {
@@ -105,6 +110,32 @@ async function recreateEmptySession(deps, lease) {
105
110
  if (closed) await createCurrentSession('model-switch-empty');
106
111
  }
107
112
 
113
+ /** The fallback chain a Lead-class session follows on `route`: its owner's chain
114
+ * while the route is the owner's primary model, none otherwise (see
115
+ * owner-route-fallbacks.mjs). Nothing for an agent's session, which keeps its own. */
116
+ export function sessionRouteFallbackFields(deps, session, route) {
117
+ const { mode, agentId } = resolveSessionMode({ session });
118
+ if (!agentId) return {};
119
+ return {
120
+ routeFallbacks: ownerRouteFallbacks({
121
+ sessionMode: mode,
122
+ agentId,
123
+ provider: route.provider,
124
+ model: route.model,
125
+ config: deps.getConfig(),
126
+ }),
127
+ };
128
+ }
129
+
130
+ /** A same-model selection (a bot profile saved with new fallbacks) restarts the
131
+ * addressed session's chain when the owner's chain no longer matches it. */
132
+ function refreshSessionFallbacks(deps, session) {
133
+ const fields = sessionRouteFallbackFields(deps, session, deps.getRoute());
134
+ if (!fields.routeFallbacks) return;
135
+ if (JSON.stringify(fields.routeFallbacks) === JSON.stringify(session.routeFallbacks || [])) return;
136
+ deps.mgr.updateSessionRoute?.(session.id, fields, { session });
137
+ }
138
+
108
139
  /** Rewrite the addressed empty session to the route it was asked to run on. */
109
140
  function updateSessionInPlace(deps, session, updateOptions) {
110
141
  const { getRoute, getSession, setSession, mgr, statusRoutes } = deps;
@@ -119,6 +150,7 @@ function updateSessionInPlace(deps, session, updateOptions) {
119
150
  contextPercent: route.contextPercent,
120
151
  contextTokens: route.contextTokens || null,
121
152
  selectedContextWindow: route.selectedContextWindow || null,
153
+ ...sessionRouteFallbackFields(deps, session, route),
122
154
  };
123
155
  const updated = mgr.updateSessionRoute?.(session.id, fields, updateOptions);
124
156
  if (updated) setSession(updated);
@@ -183,6 +215,27 @@ function switchSessionInPlace(deps, session) {
183
215
  return recorded ? boundary : null;
184
216
  }
185
217
 
218
+ /** The Lead's fallback models after this selection, checked like an agent's
219
+ * chain (leaf routes, distinct providers): an explicit list replaces the
220
+ * stored one ([] clears it), a selection without one keeps it. */
221
+ async function leadFallbackChain(deps, next, primary) {
222
+ const storedPreset = findPreset(deps.getConfig(), workflowPresetId('lead'));
223
+ const requested = hasOwn(next || {}, 'fallbacks') ? next.fallbacks : storedPreset?.fallbacks;
224
+ if (!Array.isArray(requested) || !requested.length) return [];
225
+ const chain = routeChain({ provider: primary.provider, model: primary.model, fallbacks: requested });
226
+ validateRouteChainProviders(chain);
227
+ const storedChain = routeChain(storedPreset || {});
228
+ // Each entry goes through the agents' normalizer (fast clamp, context window).
229
+ const resolved = [];
230
+ for (const entry of chain.slice(1)) {
231
+ const previous = storedChain.find((c) => c.provider === entry.provider && c.model === entry.model);
232
+ resolved.push(await resolveChainEntry(deps, entry, previous || {}));
233
+ }
234
+ return (
235
+ normalizeWorkflowRoute({ provider: primary.provider, model: primary.model, fallbacks: resolved })?.fallbacks || []
236
+ );
237
+ }
238
+
186
239
  /** Validate, persist and adopt the requested route as the runtime's route. */
187
240
  async function adoptRoute(deps, persist, next, options, lease) {
188
241
  const {
@@ -195,9 +248,29 @@ async function adoptRoute(deps, persist, next, options, lease) {
195
248
  scheduleStatuslineUsageRefresh,
196
249
  } = deps;
197
250
  const { selectedRoute, fastCapable, modelMeta } = await resolveSelectedRoute(deps, next);
251
+ // The Lead's fallback chain travels with an explicit main-model choice only
252
+ // (the Lead editor, the setup tool); a session's own pick never touches it.
253
+ const persistedRoute =
254
+ options?.keepMainModel === false && options?.sessionOnly !== true
255
+ ? { ...selectedRoute, fallbacks: await leadFallbackChain(deps, next, selectedRoute) }
256
+ : selectedRoute;
257
+ // The Lead route a conditional fill wrote, until the file confirms it.
258
+ let filled = null;
198
259
  // Provider readiness may outlast the session the selection was made for.
199
260
  const adopted = lease.run(
200
261
  () => {
262
+ // Compare-and-set: a route that already has a model (picked while this
263
+ // selection was resolving) is never replaced. Checked synchronously with
264
+ // the adopt below, so nothing can set a route in between. A Lead route
265
+ // another process persisted may not have reached this runtime yet (the
266
+ // change notification is debounced): the config is re-read from disk
267
+ // right here, which adopts it and fills the empty route, and that route
268
+ // is the winner this selection answers with.
269
+ if (options?.onlyIfEmpty === true) {
270
+ if (clean(getRoute()?.model)) return false;
271
+ deps.reloadFullConfig?.();
272
+ if (clean(getRoute()?.model)) return false;
273
+ }
201
274
  if (options?.sessionOnly === true) {
202
275
  // An agent child's route belongs to that session alone: the shared
203
276
  // modelSettings bucket (saved effort/Fast per model) and the main
@@ -208,9 +281,11 @@ async function adoptRoute(deps, persist, next, options, lease) {
208
281
  // The main model changes only on an explicit main-model choice
209
282
  // (keepMainModel: false, e.g. the setup tool); every other selection
210
283
  // — the session surface's model pick, an heir — keeps it.
211
- const leadRoute = persist.persistAdoptedModelSettings(selectedRoute, {
284
+ const leadRoute = persist.persistAdoptedModelSettings(persistedRoute, {
212
285
  keepMainModel: options?.keepMainModel !== false,
286
+ onlyIfAbsent: options?.onlyIfEmpty === true,
213
287
  });
288
+ filled = leadRoute;
214
289
  setRouteState(resolveRoute(getConfig(), leadRoute ? { model: workflowPresetId('lead') } : selectedRoute));
215
290
  }
216
291
  return true;
@@ -218,9 +293,13 @@ async function adoptRoute(deps, persist, next, options, lease) {
218
293
  () => false
219
294
  );
220
295
  if (!adopted) return false;
296
+ // A conditional fill reaches the file only while no Lead route is stored
297
+ // there (judged under the config file lock). When another process wrote one
298
+ // after the re-read above, the file's route replaces the fill in the runtime.
299
+ const replaced = filled && (await deps.confirmLeadFill?.(filled)) === true;
221
300
  // The refresh completes against the route just adopted and only while the
222
301
  // selection still owns it.
223
- await refreshRouteEffort(modelMeta, getRoute(), null, lease);
302
+ await refreshRouteEffort(replaced ? null : modelMeta, getRoute(), null, lease);
224
303
  return lease.run(
225
304
  () => {
226
305
  refreshStatuslineUsageSnapshot(getRoute());
@@ -320,6 +399,7 @@ export function createSetRoute(deps, persist) {
320
399
  return modelSwitch ? { ...getRoute(), modelSwitch } : getRoute();
321
400
  }
322
401
  persist.applySessionTuning();
402
+ refreshSessionFallbacks(deps, session);
323
403
  return getRoute();
324
404
  }
325
405
  // An addressed runtime keeps its durable id: the empty session is updated