mixdog 1.4.2 → 1.4.3

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 (417) hide show
  1. package/package.json +2 -2
  2. package/scripts/build-shipped-history.mjs +15 -5
  3. package/scripts/gate-local.mjs +65 -19
  4. package/scripts/graph-parity.mjs +8 -3
  5. package/scripts/lib/gate-scheduler.mjs +2 -1
  6. package/scripts/lib/run-node-tests.mjs +17 -5
  7. package/scripts/lib/test-failure-summary.mjs +1 -8
  8. package/scripts/lib/test-spawn-ledger.mjs +33 -2
  9. package/scripts/post-release-check.mjs +26 -16
  10. package/scripts/provider-pingpong-live.mjs +1 -1
  11. package/scripts/prune-embedding-runtime.mjs +3 -1
  12. package/scripts/release-paths.mjs +7 -1
  13. package/src/defaults/shipped-history.json +20 -0
  14. package/src/defaults/skills/setup/SKILL.md +2 -2
  15. package/src/defaults/skills/setup/references/actions.md +16 -3
  16. package/src/defaults/skills/setup/references/surfaces.md +3 -0
  17. package/src/defaults/skills/setup-guide/SKILL.md +6 -1
  18. package/src/lib/instruction-text.cjs +36 -0
  19. package/src/lib/rules-builder.cjs +34 -20
  20. package/src/orchestration/balanced.md +23 -0
  21. package/src/orchestration/focused.md +22 -0
  22. package/src/orchestration/none.md +5 -0
  23. package/src/orchestration/swarm.md +24 -0
  24. package/src/rules/agent-mode/AGENT_MODE.md +3 -1
  25. package/src/rules/agent-mode/MIXY.md +2 -2
  26. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +3 -3
  27. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +0 -1
  28. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +4 -2
  29. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +0 -9
  30. package/src/runtime/agent/orchestrator/agent-runtime/heartbeat-completion.mjs +2 -2
  31. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +67 -0
  32. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +1 -0
  33. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +4 -4
  34. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +3 -1
  35. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +199 -46
  36. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +3 -5
  37. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +9 -3
  38. package/src/runtime/agent/orchestrator/mcp/client.mjs +3 -5
  39. package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +1 -1
  40. package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +8 -2
  41. package/src/runtime/agent/orchestrator/mcp/oauth-probe.mjs +1 -1
  42. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +14 -0
  43. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +1 -1
  44. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  45. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-stop.mjs +3 -3
  46. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  47. package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +2 -2
  48. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +4 -6
  49. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-head-parity.json +2784 -2872
  50. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +5 -8
  51. package/src/runtime/agent/orchestrator/providers/openai-responses-payload-parity.fixture.json +288 -300
  52. package/src/runtime/agent/orchestrator/providers/vertex-ai.mjs +10 -1
  53. package/src/runtime/agent/orchestrator/providers/xai-tool-search-head-parity.json +10 -30
  54. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +30 -0
  55. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +0 -11
  56. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +8 -0
  57. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-delta.mjs +17 -3
  58. package/src/runtime/agent/orchestrator/runtime-core/goal-control.mjs +47 -27
  59. package/src/runtime/agent/orchestrator/runtime-core/goal-deadlines.mjs +141 -10
  60. package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +26 -63
  61. package/src/runtime/agent/orchestrator/runtime-core/goal-reminder.mjs +28 -3
  62. package/src/runtime/agent/orchestrator/runtime-core/goal-runtime.mjs +36 -11
  63. package/src/runtime/agent/orchestrator/runtime-core/goal-state.mjs +88 -56
  64. package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +4 -1
  65. package/src/runtime/agent/orchestrator/runtime-core/goal-store.mjs +51 -32
  66. package/src/runtime/agent/orchestrator/runtime-core/goal-text.mjs +45 -145
  67. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-defs.mjs +15 -98
  68. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +32 -99
  69. package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +23 -78
  70. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +69 -48
  71. package/src/runtime/agent/orchestrator/runtime-core/todo-list.mjs +367 -0
  72. package/src/runtime/agent/orchestrator/runtime-core/todo-tool-defs.mjs +47 -0
  73. package/src/runtime/agent/orchestrator/runtime-core/tool-availability.mjs +155 -0
  74. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +9 -0
  75. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +119 -35
  76. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +1 -1
  77. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +1 -1
  78. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +28 -22
  79. package/src/runtime/agent/orchestrator/session/agent-pool-progress.mjs +30 -0
  80. package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +1 -1
  81. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +2 -2
  82. package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +2 -2
  83. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +1 -1
  84. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +1 -1
  85. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +3 -0
  86. package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +1 -1
  87. package/src/runtime/agent/orchestrator/session/context-utils.mjs +8 -12
  88. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +1 -1
  89. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +1 -1
  90. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +1 -1
  91. package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +7 -3
  92. package/src/runtime/agent/orchestrator/session/human-gate/held-message.mjs +1 -1
  93. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +1 -2
  94. package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +1 -1
  95. package/src/runtime/agent/orchestrator/session/image-strip-recovery.mjs +1 -1
  96. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +11 -1
  97. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +1 -1
  98. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +8 -1
  99. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/todo-check.mjs +196 -0
  100. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +116 -50
  101. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +8 -1
  102. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +1 -1
  103. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +1 -1
  104. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -1
  105. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +5 -6
  106. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +6 -1
  107. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +16 -2
  108. package/src/runtime/agent/orchestrator/session/loop/turn-report.mjs +35 -0
  109. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +1 -1
  110. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +57 -26
  111. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +9 -7
  112. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +21 -3
  113. package/src/runtime/agent/orchestrator/session/manager/ask-turn-failure.mjs +27 -11
  114. package/src/runtime/agent/orchestrator/session/manager/ask-turn-open.mjs +37 -34
  115. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +15 -0
  116. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +25 -15
  117. package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +79 -24
  118. package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +3 -0
  119. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +12 -1
  120. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +1 -1
  121. package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +6 -1
  122. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +9 -1
  123. package/src/runtime/agent/orchestrator/session/manager/pending-spool-path.mjs +2 -2
  124. package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +1 -1
  125. package/src/runtime/agent/orchestrator/session/manager/prompt-settings.mjs +598 -0
  126. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +19 -0
  127. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +19 -10
  128. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +5 -0
  129. package/src/runtime/agent/orchestrator/session/manager/session-fallback-title.mjs +1 -1
  130. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +31 -0
  131. package/src/runtime/agent/orchestrator/session/manager/session-lock.mjs +31 -0
  132. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +42 -6
  133. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +2 -0
  134. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +11 -1
  135. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-context.mjs +8 -9
  136. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +339 -88
  137. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-recovery.mjs +135 -11
  138. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +377 -104
  139. package/src/runtime/agent/orchestrator/session/manager/turn-durability.mjs +144 -0
  140. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +1 -1
  141. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +4 -0
  142. package/src/runtime/agent/orchestrator/session/manager.mjs +6 -0
  143. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +1 -1
  144. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +8 -1
  145. package/src/runtime/agent/orchestrator/session/save-session-worker.mjs +5 -1
  146. package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +1 -1
  147. package/src/runtime/agent/orchestrator/session/store/liveness-veto.mjs +2 -2
  148. package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +23 -2
  149. package/src/runtime/agent/orchestrator/session/store/sweep/sub-agent-retention.mjs +3 -3
  150. package/src/runtime/agent/orchestrator/session/store/transcript-durability.mjs +88 -0
  151. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +5 -0
  152. package/src/runtime/agent/orchestrator/session/store-summary-log.mjs +1 -1
  153. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +14 -5
  154. package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +2 -2
  155. package/src/runtime/agent/orchestrator/session/store.mjs +5 -0
  156. package/src/runtime/agent/orchestrator/session/synthetic-user-envelope.mjs +7 -1
  157. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +9 -1
  158. package/src/runtime/agent/orchestrator/session/transcript-restore/agent-envelope.mjs +1 -1
  159. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +3 -1
  160. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +2 -2
  161. package/src/runtime/agent/orchestrator/stall-policy.mjs +3 -5
  162. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +4 -1
  163. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/misuse-guard.mjs +21 -3
  164. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +1 -1
  165. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/worktree-add-guard.mjs +77 -0
  166. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +0 -1
  167. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +7 -3
  168. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +27 -9
  169. package/src/runtime/agent/orchestrator/tools/builtin/lib/search-input-helpers.mjs +2 -2
  170. package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +1 -1
  171. package/src/runtime/agent/orchestrator/tools/builtin/read-single-fast-paths.mjs +0 -1
  172. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +0 -10
  173. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +1 -2
  174. package/src/runtime/agent/orchestrator/tools/builtin.mjs +1 -24
  175. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary/child-kill.mjs +4 -3
  176. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +1 -2
  177. package/src/runtime/agent/orchestrator/tools/code-graph/modes/calls.mjs +1 -1
  178. package/src/runtime/agent/orchestrator/tools/code-graph/modes/symbols.mjs +2 -2
  179. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +4 -5
  180. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +3 -4
  181. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4 -22
  182. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +2 -2
  183. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +1 -1
  184. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +0 -4
  185. package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +0 -1
  186. package/src/runtime/attachments/attachment-reference.mjs +1 -1
  187. package/src/runtime/attachments/pdf-extract.mjs +1 -1
  188. package/src/runtime/attachments/store.d.mts +1 -0
  189. package/src/runtime/attachments/store.mjs +2 -2
  190. package/src/runtime/channels/lib/config-reload-watch.mjs +42 -0
  191. package/src/runtime/channels/lib/owned-runtime/config-reload.mjs +29 -9
  192. package/src/runtime/channels/lib/scheduler.mjs +33 -0
  193. package/src/runtime/channels/lib/schedules-db.mjs +11 -4
  194. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +4 -1
  195. package/src/runtime/channels/lib/webhooks-db.mjs +9 -3
  196. package/src/runtime/channels/lib/whisper-server.mjs +12 -12
  197. package/src/runtime/channels/lib/worker-main.mjs +9 -31
  198. package/src/runtime/computer-bridge/linux-readiness.mjs +10 -14
  199. package/src/runtime/connectors/connector-accounts-mcp.mjs +169 -33
  200. package/src/runtime/connectors/gmail-app-password.mjs +2 -2
  201. package/src/runtime/connectors/google-mcp.mjs +1 -1
  202. package/src/runtime/effort-judge/judge-client.mjs +2 -2
  203. package/src/runtime/effort-judge/judge-input.mjs +4 -4
  204. package/src/runtime/explore/explorer.mjs +0 -5
  205. package/src/runtime/explore/tool.mjs +3 -3
  206. package/src/runtime/local-provider/hardware.mjs +1 -1
  207. package/src/runtime/media/adapters/codex-image.mjs +3 -3
  208. package/src/runtime/media/adapters/gemini-video.mjs +1 -1
  209. package/src/runtime/media/media-usage.mjs +1 -1
  210. package/src/runtime/media/store.mjs +1 -1
  211. package/src/runtime/memory/index.mjs +11 -1
  212. package/src/runtime/memory/lib/embedding-reindex.mjs +27 -0
  213. package/src/runtime/memory/lib/memory-embed.mjs +83 -14
  214. package/src/runtime/memory/lib/memory-process-lock.mjs +159 -36
  215. package/src/runtime/memory/lib/memory-text-utils.mjs +6 -1
  216. package/src/runtime/memory/lib/memory.mjs +17 -0
  217. package/src/runtime/memory/lib/pg/process.mjs +1 -1
  218. package/src/runtime/memory/lib/pg/supervisor.mjs +16 -96
  219. package/src/runtime/memory/lib/recall-hybrid/hybrid-sql.mjs +12 -3
  220. package/src/runtime/office/authoring/docx-html-runner.mjs +1 -1
  221. package/src/runtime/office/authoring/pptx-author-action.mjs +1 -1
  222. package/src/runtime/office/authoring/xlsx-html-build.mjs +1 -1
  223. package/src/runtime/office/authoring/xlsx-html-runner.mjs +1 -1
  224. package/src/runtime/office/capabilities.mjs +1 -1
  225. package/src/runtime/office/core/office-core.mjs +1 -1
  226. package/src/runtime/office/design/content-model.mjs +3 -3
  227. package/src/runtime/office/design/docx/design-docx/section.mjs +6 -6
  228. package/src/runtime/office/design/xlsx/design-xlsx-components.mjs +1 -1
  229. package/src/runtime/office/design/xlsx/design-xlsx.mjs +1 -1
  230. package/src/runtime/office/pdf/ocr-engine.mjs +1 -1
  231. package/src/runtime/office/pdf/pdf-writer.mjs +4 -4
  232. package/src/runtime/office/portable/portable-docx-operations.mjs +2 -2
  233. package/src/runtime/office/portable/portable-soffice.mjs +8 -6
  234. package/src/runtime/office/portable/xlsx-contract.mjs +1 -1
  235. package/src/runtime/shared/atomic-file.d.mts +8 -0
  236. package/src/runtime/shared/background-tasks.mjs +22 -4
  237. package/src/runtime/shared/background-wait.mjs +66 -0
  238. package/src/runtime/shared/child-guardian.mjs +17 -7
  239. package/src/runtime/shared/config.mjs +313 -16
  240. package/src/runtime/shared/connector-catalog.d.mts +4 -5
  241. package/src/runtime/shared/connector-catalog.mjs +11 -1
  242. package/src/runtime/shared/file-lock.mjs +8 -3
  243. package/src/runtime/shared/git-runner.mjs +7 -2
  244. package/src/runtime/shared/idle-gc.mjs +30 -7
  245. package/src/runtime/shared/llm/http-agent.mjs +8 -5
  246. package/src/runtime/shared/llm/system-proxy.mjs +2 -1
  247. package/src/runtime/shared/llm/usage-ledger-quota.mjs +31 -12
  248. package/src/runtime/shared/llm/usage-ledger-rollup.mjs +84 -8
  249. package/src/runtime/shared/llm/usage-ledger.mjs +3 -0
  250. package/src/runtime/shared/llm/usage-rollup.mjs +48 -0
  251. package/src/runtime/shared/maintenance-schedule.mjs +109 -0
  252. package/src/runtime/shared/process-runner.mjs +8 -4
  253. package/src/runtime/shared/routine-trigger.d.mts +13 -0
  254. package/src/runtime/shared/routine-trigger.mjs +34 -0
  255. package/src/runtime/shared/run-child.d.mts +2 -1
  256. package/src/runtime/shared/run-child.mjs +70 -18
  257. package/src/runtime/shared/session-scratch.mjs +14 -2
  258. package/src/runtime/shared/shipped-definitions.mjs +7 -1
  259. package/src/runtime/shared/tool-card-model.golden.json +3 -12
  260. package/src/runtime/shared/tool-execution-contract.mjs +2 -0
  261. package/src/runtime/shared/tool-work-units.mjs +31 -6
  262. package/src/runtime/shared/turn-snapshot.mjs +26 -4
  263. package/src/runtime/shared/turn-worktree-snapshot.mjs +6 -11
  264. package/src/runtime/shared/worker-requests.mjs +17 -3
  265. package/src/runtime/shared/worktrees/branch-management.mjs +1 -5
  266. package/src/runtime/shared/worktrees/contract.mjs +16 -6
  267. package/src/runtime/shared/worktrees/dependencies.mjs +127 -89
  268. package/src/runtime/shared/worktrees/disk.mjs +79 -2
  269. package/src/runtime/shared/worktrees/git.mjs +44 -11
  270. package/src/runtime/shared/worktrees/include.mjs +6 -4
  271. package/src/runtime/shared/worktrees/maintenance.mjs +422 -30
  272. package/src/runtime/shared/worktrees/manager.mjs +772 -75
  273. package/src/runtime/shared/worktrees/settings.mjs +33 -7
  274. package/src/runtime/shared/worktrees/test-support.mjs +5 -1
  275. package/src/runtime/shared/worktrees/worker-snapshots.mjs +82 -0
  276. package/src/runtime/tidy/process.mjs +2 -1
  277. package/src/runtime/web-search/index.mjs +3 -22
  278. package/src/runtime/web-search/lib/web-tools.mjs +2 -2
  279. package/src/runtime/web-search/tool-defs.mjs +17 -26
  280. package/src/session-runtime/agent-profile-changes.mjs +55 -4
  281. package/src/session-runtime/boot/apis.mjs +27 -4
  282. package/src/session-runtime/boot/begin.mjs +2 -0
  283. package/src/session-runtime/boot/config.mjs +10 -1
  284. package/src/session-runtime/boot/facade.mjs +1 -0
  285. package/src/session-runtime/boot/providers.mjs +70 -3
  286. package/src/session-runtime/boot/session.mjs +2 -0
  287. package/src/session-runtime/boot/tools.mjs +69 -25
  288. package/src/session-runtime/channel-config-api.mjs +1 -0
  289. package/src/session-runtime/config-change-effects.mjs +179 -0
  290. package/src/session-runtime/config-lifecycle/config-writers.mjs +1 -0
  291. package/src/session-runtime/config-lifecycle.mjs +160 -24
  292. package/src/session-runtime/conversation-import/discovery.mjs +86 -7
  293. package/src/session-runtime/conversation-import/service.mjs +3 -2
  294. package/src/session-runtime/goal-facade-api.mjs +49 -3
  295. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +3 -0
  296. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +3 -2
  297. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +3 -11
  298. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +18 -4
  299. package/src/session-runtime/internal-tool-executor.mjs +12 -19
  300. package/src/session-runtime/lifecycle/cold-heir.mjs +7 -0
  301. package/src/session-runtime/lifecycle/memory-ingest.mjs +3 -3
  302. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +53 -5
  303. package/src/session-runtime/lifecycle/session-switch.mjs +13 -2
  304. package/src/session-runtime/lifecycle/teardown/session-close.mjs +4 -4
  305. package/src/session-runtime/lifecycle-api.mjs +1 -0
  306. package/src/session-runtime/mcp-glue/server-catalog.mjs +14 -3
  307. package/src/session-runtime/mcp-glue/server-connection.mjs +34 -8
  308. package/src/session-runtime/model-route/set-route.mjs +40 -7
  309. package/src/session-runtime/native-web-search/routes.mjs +3 -3
  310. package/src/session-runtime/provider-auth/refresh.mjs +6 -3
  311. package/src/session-runtime/provider-auth/setup.mjs +1 -1
  312. package/src/session-runtime/provider-readiness.mjs +53 -4
  313. package/src/session-runtime/resource-api.mjs +1 -1
  314. package/src/session-runtime/resource-extension-sync.mjs +4 -8
  315. package/src/session-runtime/resource-mcp-connector-accounts.mjs +5 -1
  316. package/src/session-runtime/resource-mcp-secrets.mjs +3 -3
  317. package/src/session-runtime/resource-plugins-api.mjs +4 -0
  318. package/src/session-runtime/routine-executor.mjs +152 -0
  319. package/src/session-runtime/routine-home-turn.mjs +46 -5
  320. package/src/session-runtime/runtime-facade.mjs +5 -0
  321. package/src/session-runtime/runtime-feature-gates.mjs +2 -11
  322. package/src/session-runtime/schedule-session-run.mjs +1 -0
  323. package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +6 -1
  324. package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +7 -0
  325. package/src/session-runtime/services/agent-tool/send-flow.mjs +110 -6
  326. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +12 -0
  327. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +5 -0
  328. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +24 -6
  329. package/src/session-runtime/services/agent-tool.mjs +51 -1
  330. package/src/session-runtime/services/channel-admin.mjs +18 -0
  331. package/src/session-runtime/services/hook-bus/command-handler.mjs +7 -11
  332. package/src/session-runtime/services/plugin-admin.mjs +4 -0
  333. package/src/session-runtime/services/seeds.mjs +7 -0
  334. package/src/session-runtime/services/usage-stats-model.mjs +32 -11
  335. package/src/session-runtime/session-create/materialize.mjs +4 -2
  336. package/src/session-runtime/session-create/session-options.mjs +2 -0
  337. package/src/session-runtime/settings-builtin-tools-api.mjs +1 -9
  338. package/src/session-runtime/setup-tool/connector-widen-approval.mjs +1 -1
  339. package/src/session-runtime/setup-tool/executor.mjs +8 -1
  340. package/src/session-runtime/setup-tool/extended-actions.mjs +2 -0
  341. package/src/session-runtime/setup-tool/settings-contract.mjs +28 -0
  342. package/src/session-runtime/setup-tool/tool-defs.mjs +26 -1
  343. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +40 -43
  344. package/src/session-runtime/tool-policy/mode-switch.mjs +1 -1
  345. package/src/session-runtime/tool-policy/policy-stage.mjs +106 -8
  346. package/src/session-runtime/tool-policy/workflow-overlay.mjs +2 -1
  347. package/src/session-runtime/tool-policy-refresh.mjs +182 -5
  348. package/src/session-runtime/tool-surface.mjs +38 -1
  349. package/src/session-runtime/turn/agent-ops.mjs +10 -2
  350. package/src/session-runtime/turn/session-ops.mjs +2 -0
  351. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +5 -0
  352. package/src/session-runtime/turn/turn-run.mjs +1 -1
  353. package/src/session-runtime/webhook-session-run.mjs +2 -0
  354. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +1 -4
  355. package/src/session-runtime/workflow-agents-api/agent-route.mjs +3 -10
  356. package/src/session-runtime/workflow-agents-api/instruction-files.mjs +165 -0
  357. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +5 -4
  358. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +2 -3
  359. package/src/session-runtime/workflow-agents-api.mjs +2 -0
  360. package/src/session-runtime/workspace/project-branches.mjs +1 -1
  361. package/src/session-runtime/workspace/session-view.mjs +1 -1
  362. package/src/session-runtime/workspace/transitions.mjs +20 -4
  363. package/src/session-runtime/workspace/worktree-maintenance.mjs +126 -6
  364. package/src/session-runtime/workspace/worktree-setup.mjs +13 -1
  365. package/src/session-runtime/workspace/worktree-tool.mjs +16 -0
  366. package/src/standalone/daemon-agent-control.mjs +25 -8
  367. package/src/standalone/daemon-desktop-runtime.mjs +6 -2
  368. package/src/standalone/daemon-lag-profiler.mjs +7 -7
  369. package/src/standalone/daemon.mjs +54 -2
  370. package/src/standalone/local-session-runtime.mjs +16 -0
  371. package/src/standalone/session-client/daemon-unavailable.mjs +1 -1
  372. package/src/standalone/session-protocol.mjs +11 -0
  373. package/src/standalone/session-service/agent-route-follow.mjs +23 -20
  374. package/src/standalone/session-service/agent-tree/agent-turns.mjs +18 -6
  375. package/src/standalone/session-service/quota-resume.mjs +9 -0
  376. package/src/standalone/session-service/session-calls/session-action.mjs +51 -1
  377. package/src/standalone/session-service/session-calls/session-turns.mjs +37 -18
  378. package/src/standalone/session-service/session-calls/turn-gate.mjs +34 -0
  379. package/src/standalone/session-service/session-calls.mjs +2 -1
  380. package/src/tui/App.jsx +8 -0
  381. package/src/tui/app/clipboard.mjs +1 -1
  382. package/src/tui/app/create-app-pickers.mjs +2 -0
  383. package/src/tui/app/doctor.mjs +2 -2
  384. package/src/tui/app/panel-signature.mjs +1 -1
  385. package/src/tui/app/settings-picker.mjs +53 -8
  386. package/src/tui/app/shell-layout/row-budgets.mjs +2 -2
  387. package/src/tui/app/slash-commands.mjs +1 -1
  388. package/src/tui/app/slash-dispatch/route-commands.mjs +1 -1
  389. package/src/tui/app/slash-dispatch/session-commands.mjs +1 -1
  390. package/src/tui/app/use-prompt-queue-history.mjs +2 -2
  391. package/src/tui/app/use-transcript-activity.mjs +1 -1
  392. package/src/tui/app/use-transcript-window/geometry.mjs +1 -1
  393. package/src/tui/components/prompt-input/escape-policy.d.mts +0 -1
  394. package/src/tui/components/prompt-input/escape-policy.mjs +1 -1
  395. package/src/tui/components/question-panel-model.mjs +4 -4
  396. package/src/tui/components/tool-execution/standard-card.jsx +3 -4
  397. package/src/tui/components/user-message-skills.mjs +1 -1
  398. package/src/tui/dist/index.mjs +309 -30
  399. package/src/tui/markdown/render-ansi.mjs +1 -1
  400. package/src/tui/session/agent-job-feed/notification-router.mjs +10 -0
  401. package/src/tui/session/agent-job-feed.mjs +2 -0
  402. package/src/tui/session/context-state.mjs +3 -0
  403. package/src/tui/session/quota-resume.mjs +1 -1
  404. package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
  405. package/src/tui/session/session-api/lifecycle/session-reset.mjs +7 -0
  406. package/src/tui/session/session-api/routes-agent-definitions.mjs +5 -0
  407. package/src/tui/session/session-api/worktree.mjs +13 -1
  408. package/src/tui/session/session-flow/drain.mjs +11 -14
  409. package/src/tui/session/tool-card-results/grouped-result.mjs +2 -2
  410. package/src/tui/session/turn-quiet.mjs +1 -1
  411. package/src/tui/session/turn.mjs +3 -0
  412. package/src/tui/session/worktree-setup-notice.mjs +1 -1
  413. package/src/tui/spinner-meta.mjs +3 -3
  414. package/src/tui/spinner-verbs.mjs +2 -2
  415. package/src/defaults/skills/goal-management/SKILL.md +0 -169
  416. package/src/runtime/agent/orchestrator/runtime-core/goal-tasks.mjs +0 -156
  417. package/src/session-runtime/goal-tasks.mjs +0 -1
@@ -6,6 +6,7 @@
6
6
  * to channel tools.
7
7
  */
8
8
  import { clean } from '../runtime/agent/orchestrator/runtime-core/session-text.mjs';
9
+ import { settingDisabledIn } from '../runtime/agent/orchestrator/runtime-core/tool-availability.mjs';
9
10
  import { createAskUserToolHandlers } from './internal-tool-executor/ask-user-tool.mjs';
10
11
  import { createBridgeToolHandlers } from './internal-tool-executor/bridge-tools.mjs';
11
12
  import { createFeatureToolHandlers } from './internal-tool-executor/feature-tools.mjs';
@@ -19,11 +20,7 @@ export function createInternalToolExecutor({
19
20
  goalRuntime,
20
21
  agentTool,
21
22
  setupTool,
22
- webSearchEnabled,
23
- memoryToolsEnabled,
24
- officeToolsEnabled,
25
- mediaToolEnabled,
26
- tidyToolEnabled = () => true,
23
+ featureDisallowedTools = () => [],
27
24
  getWebSearchModule,
28
25
  getMemoryModule,
29
26
  getCodeGraphModule,
@@ -42,7 +39,7 @@ export function createInternalToolExecutor({
42
39
  ...createBridgeToolHandlers({ rt }),
43
40
  ...createAskUserToolHandlers({ rt }),
44
41
  ...createReactionToolHandlers({ rt }),
45
- ...createFeatureToolHandlers({ rt, setupTool, officeToolsEnabled, mediaToolEnabled, tidyToolEnabled }),
42
+ ...createFeatureToolHandlers({ rt, setupTool }),
46
43
  ...createKnowledgeToolHandlers({
47
44
  rt,
48
45
  getWebSearchModule,
@@ -67,24 +64,20 @@ export function createInternalToolExecutor({
67
64
  }),
68
65
  };
69
66
 
70
- // Settings-disabled features are refused for model-initiated calls before
71
- // any handler runs; the tool list is refreshed by a new session.
72
- const guardModelCall = (name) => {
73
- if ((name === 'web_search' || name === 'web_fetch') && !webSearchEnabled()) {
74
- throw new Error('web search is disabled in settings; start a new session to refresh the tool list');
75
- }
76
- if ((name === 'memory' || name === 'recall') && !memoryToolsEnabled()) {
77
- throw new Error(
78
- 'memory tools are disabled in settings; background memory and manual core memory remain available'
79
- );
80
- }
81
- };
67
+ // A model call of a tool a setting turned off is refused at dispatch
68
+ // (tool-exec/dispatch.mjs settingDisabledTool, built-ins such as git
69
+ // included); a model-tool call reaching this executor directly takes the
70
+ // same check against this runtime's live gate list.
82
71
 
83
72
  return async (name, args, callerCtx = {}) => {
84
73
  const callerCwd = clean(callerCtx?.callerCwd) || rt.currentCwd;
85
74
  // The Reactions setting gates the tool whoever calls it, not only the model.
86
75
  if (name === 'react' && rt.config?.reactions === false) throw new Error('reactions are turned off in settings');
87
- if (callerCtx?.invocationSource === 'model-tool') guardModelCall(name);
76
+ const disabled =
77
+ callerCtx?.invocationSource === 'model-tool' ? settingDisabledIn(name, featureDisallowedTools()) : null;
78
+ if (disabled) {
79
+ throw new Error(`${disabled} is disabled in settings by the user; do not retry unless the user turns it back on`);
80
+ }
88
81
  if (Object.hasOwn(handlers, name)) return handlers[name](args, { name, callerCtx, callerCwd });
89
82
  if (channels.isChannelTool(name)) {
90
83
  return await channels.execute(name, args || {});
@@ -35,9 +35,16 @@ const SOURCE_RUNTIME_FIELDS = [
35
35
  'toolApprovalHook',
36
36
  '_providerPrefixGuardState',
37
37
  'activeTurnCheckpoint',
38
+ // The source's durable transcript identity (store/transcript-durability.mjs).
39
+ 'transcriptEpoch',
40
+ 'turnCoverage',
38
41
  'deferredInitialRefreshPending',
39
42
  'pendingGoalReminder',
40
43
  'goalReminderRevision',
44
+ 'goalStalledNotice',
45
+ // The source's own step list and its reminder cycle.
46
+ 'todo',
47
+ 'todoCheck',
41
48
  'pendingCacheBreakIntent',
42
49
  'contextPressureUnanchoredAfterRestart',
43
50
  'contextPressureUnanchoredReason',
@@ -4,9 +4,9 @@
4
4
  */
5
5
  const INGEST_DEADLINE_MS = 2500;
6
6
 
7
- // Sessions that never compacted had NO session-sourced rows (ingest_session
8
- // previously ran only inside compaction), so recall could not reconstruct
9
- // the most recent conversations. Deadline-capped: the row inserts are fast
7
+ // Sessions that never compacted have no session-sourced rows unless ingested
8
+ // here, and recall could not reconstruct the most recent conversations
9
+ // without them. Deadline-capped: the row inserts are fast
10
10
  // and committed even if the bounded wait elapses during the trailing
11
11
  // embedding flush (the raw-embedding backlog sweep embeds them later). Never
12
12
  // loads the memory runtime just for this — a null module promise means
@@ -14,6 +14,7 @@ import {
14
14
  import { SESSION_ID_PATTERN, closeManagedSession } from '../shared.mjs';
15
15
  import { replacementSessionCwd } from '../../workspace/workspace-state.mjs';
16
16
  import { isHistoricalAutomaticClosure } from '../../../runtime/agent/orchestrator/session/historical-session-closure.mjs';
17
+ import { readSessionSidebarState } from '../../../runtime/shared/session-sidebar-host.mjs';
17
18
 
18
19
  export function createSessionDelete(deps, { cancelBackgroundTasks }) {
19
20
  const {
@@ -38,12 +39,13 @@ export function createSessionDelete(deps, { cancelBackgroundTasks }) {
38
39
  // Availability is one strict read of the session's own file, filtered like
39
40
  // the session catalog (an unreadable, foreign, closed or non-catalogued
40
41
  // record is not deletable). No catalog rebuild, no folder scan.
41
- function isAvailableSession(sessionId) {
42
+ function availableRecord(sessionId) {
42
43
  const stored = _storedSessionFromFile(getStoreDir(), `${sessionId}.json`);
43
- if (!stored || stored === STORED_SESSION_UNREADABLE) return false;
44
- if (stored.closed === true && !isHistoricalAutomaticClosure(stored)) return false;
45
- return leadSessionRow(stored) !== null;
44
+ if (!stored || stored === STORED_SESSION_UNREADABLE) return null;
45
+ if (stored.closed === true && !isHistoricalAutomaticClosure(stored)) return null;
46
+ return leadSessionRow(stored) !== null ? stored : null;
46
47
  }
48
+ const isAvailableSession = (sessionId) => availableRecord(sessionId) !== null;
47
49
 
48
50
  // The deleted task's worktree: the autoCleanup setting decides (clean and
49
51
  // merged → removed; otherwise kept). Off the deletion path; never fails it.
@@ -102,5 +104,51 @@ export function createSessionDelete(deps, { cancelBackgroundTasks }) {
102
104
  return true;
103
105
  }
104
106
 
105
- return { releaseSessionWork, deleteSession };
107
+ /**
108
+ * The desktop's auto-delete of an archived session: only one that is still
109
+ * archived, not a favorite, idle (no turn running, waiting for its ask lock
110
+ * or queued) and unchanged since the caller looked (`expected.lastUsedAt`,
111
+ * which every turn moves). It holds the session's ask lock, which every
112
+ * turn takes, and runs its last checks, the close and the delete in one
113
+ * synchronous stretch, so no turn can start in between. A refusal closes
114
+ * and deletes nothing: `{ deleted: false, reason }`. `isHostedBusy` (the
115
+ * session service) reports queued input in a runtime hosting the session.
116
+ */
117
+ async function deleteSessionIfIdle(id, expected = {}, { isHostedBusy = null } = {}) {
118
+ const sessionId = clean(id);
119
+ const refuse = (reason) => ({ deleted: false, reason });
120
+ if (!sessionId || !SESSION_ID_PATTERN.test(sessionId)) return refuse('invalid');
121
+ if (typeof isHostedBusy !== 'function') return refuse('unguarded');
122
+ if (getSession()?.id === sessionId) return refuse('current');
123
+ const unlock = mgr.tryAcquireSessionLock(sessionId);
124
+ if (!unlock) return refuse('busy');
125
+ try {
126
+ // The archive and favorite flags are the desktop's.
127
+ const sidebar = await readSessionSidebarState();
128
+ if (!sidebar || !Object.hasOwn(sidebar.archived ?? {}, sessionId)) return refuse('not-archived');
129
+ if (Object.hasOwn(sidebar.favorites ?? {}, sessionId)) return refuse('favorite');
130
+ // From here to the delete: synchronous.
131
+ if (mgr.sessionLockContended(sessionId)) return refuse('turn-waiting');
132
+ if (mgr.isSessionTurnInFlight(sessionId) || mgr.hasUnsettledTurn(sessionId)) return refuse('busy');
133
+ if (mgr.hasPendingMessages(sessionId) || isHostedBusy(sessionId)) return refuse('queued');
134
+ const stored = availableRecord(sessionId);
135
+ if (!stored) return refuse('missing');
136
+ if ((Number(stored.lastUsedAt) || 0) !== (Number(expected?.lastUsedAt) || 0)) return refuse('changed');
137
+ const reason = 'desktop-auto-delete';
138
+ // The blocking close: its barrier lands before this stretch yields.
139
+ if (closeManagedSession(mgr, sessionId, reason, { tombstone: false }, false) !== true) {
140
+ return refuse('close-failed');
141
+ }
142
+ agentTool?.forgetLeadSession?.(sessionId);
143
+ releaseSessionWork(sessionId, reason);
144
+ if (mgr.deleteSession(sessionId, { fenceWrites: true }) !== true) return refuse('delete-failed');
145
+ void forgetSessionSearch(sessionId);
146
+ void releaseDeletedWorktree(sessionId);
147
+ return { deleted: true };
148
+ } finally {
149
+ unlock();
150
+ }
151
+ }
152
+
153
+ return { releaseSessionWork, deleteSession, deleteSessionIfIdle };
106
154
  }
@@ -15,7 +15,9 @@ export { resolveResumeCwd } from './session-switch/resume.mjs';
15
15
 
16
16
  export function createSessionSwitching(deps, { ingestSessionIntoMemory, closeSurfaceSession, cancelBackgroundTasks }) {
17
17
  const { mgr, getMode } = deps;
18
- const { releaseSessionWork, deleteSession } = createSessionDelete(deps, { cancelBackgroundTasks });
18
+ const { releaseSessionWork, deleteSession, deleteSessionIfIdle } = createSessionDelete(deps, {
19
+ cancelBackgroundTasks,
20
+ });
19
21
  const { switchContext, newSession } = createContextSwitch(deps, {
20
22
  ingestSessionIntoMemory,
21
23
  closeSurfaceSession,
@@ -41,5 +43,14 @@ export function createSessionSwitching(deps, { ingestSessionIntoMemory, closeSur
41
43
  return mgr.prefetchSession?.(id, toolSpecForMode(getMode())) === true;
42
44
  }
43
45
 
44
- return { listSessions, sessionStoreDir, deleteSession, switchContext, newSession, prefetchSession, resume };
46
+ return {
47
+ listSessions,
48
+ sessionStoreDir,
49
+ deleteSession,
50
+ deleteSessionIfIdle,
51
+ switchContext,
52
+ newSession,
53
+ prefetchSession,
54
+ resume,
55
+ };
45
56
  }
@@ -35,10 +35,10 @@ export async function dispatchSessionEnd(deps, reason) {
35
35
  }
36
36
  }
37
37
 
38
- // Runtime stop/exit (TUI Ctrl-C, process exit) previously always tombstoned
39
- // the current session, so a session you were mid-conversation in vanished
40
- // from the Resume list the instant you quit and was hard-deleted by the 24h
41
- // tombstone sweep. Only tombstone truly-empty scratch sessions.
38
+ // Runtime stop/exit (TUI Ctrl-C, process exit) must not tombstone a session
39
+ // you were mid-conversation in: it would vanish from the Resume list the
40
+ // instant you quit and be hard-deleted by the 24h tombstone sweep. Only
41
+ // tombstone truly-empty scratch sessions.
42
42
  export function closeOwnSession(deps, closeSurfaceSession, reason) {
43
43
  const {
44
44
  getSession,
@@ -38,6 +38,7 @@ export function createLifecycleApi(deps) {
38
38
  listSessions: switching.listSessions,
39
39
  sessionStoreDir: switching.sessionStoreDir,
40
40
  deleteSession: switching.deleteSession,
41
+ deleteSessionIfIdle: switching.deleteSessionIfIdle,
41
42
  switchContext: switching.switchContext,
42
43
  newSession: switching.newSession,
43
44
  prefetchSession: switching.prefetchSession,
@@ -4,12 +4,16 @@
4
4
  import { clean } from '../../runtime/agent/orchestrator/runtime-core/session-text.mjs';
5
5
  import {
6
6
  connectorAccountEnabled,
7
+ connectorAccountId,
7
8
  connectorAccountServer,
8
9
  connectorAccountsOf,
9
10
  connectorOfServer,
10
11
  gmailAppPasswordAccount,
11
12
  } from '../../runtime/shared/connector-catalog.mjs';
12
- import { connectorAccountHealth, connectorAccountInUse } from '../../runtime/connectors/connector-accounts-mcp.mjs';
13
+ import {
14
+ connectorAccountHealth,
15
+ connectorAccountInUseRecord,
16
+ } from '../../runtime/connectors/connector-accounts-mcp.mjs';
13
17
  import { googleOAuthClient } from '../../runtime/shared/google-oauth-client.mjs';
14
18
  import { envFlag } from '../../runtime/shared/env.mjs';
15
19
  import { resolveMcpServerCatalog } from './plugin-servers.mjs';
@@ -158,8 +162,15 @@ export function createMcpServerCatalog({
158
162
  const stored = entry.owner ? [] : connectorAccountsOf(cfg);
159
163
  // The connector's last-used account (none when that one was removed),
160
164
  // else its topmost switched-on one.
161
- const recorded = stored.length ? connectorAccountInUse(connectorOfServer(cfg)) : '';
162
- const inUse = recorded || stored.find(connectorAccountEnabled)?.name;
165
+ // A record names an account only while that account is still it (a new
166
+ // account given a removed one's name is not).
167
+ const record = stored.length ? connectorAccountInUseRecord(connectorOfServer(cfg)) : null;
168
+ const recorded = record
169
+ ? stored.find(
170
+ (account) => account.name === record.name && record.id && connectorAccountId(cfg, account) === record.id
171
+ )?.name
172
+ : '';
173
+ const inUse = record ? recorded : stored.find(connectorAccountEnabled)?.name;
163
174
  const accounts = stored.map((account) =>
164
175
  accountRow(connectorAccountServer(cfg, account), connectorAccountEnabled(account), account.name === inUse)
165
176
  );
@@ -18,20 +18,23 @@ export function createServerConnection({
18
18
  // change never triggers a full disconnectAll()/reconnect freeze. Only an
19
19
  // explicit sign-in passes `login` ({ openBrowser }): every other connect is
20
20
  // non-interactive and reports a server that needs one as sign-in required.
21
+ // Answers the connect's failure (recorded in state.mcpFailures), null once
22
+ // it connected or disconnected. The server's fingerprint is recorded only
23
+ // once it connected, so one that failed is connected again by the next
24
+ // reconcile although its config is unchanged.
21
25
  async function applyMcpServerConnection(name, enabled, servers = resolveEffectiveMcpServers().servers, login = null) {
22
26
  const target = clean(name);
23
- if (!target) return;
27
+ if (!target) return null;
24
28
  // Changing this server's state clears any stale failure record for it.
25
29
  if (Array.isArray(state.mcpFailures)) {
26
30
  state.mcpFailures = state.mcpFailures.filter((row) => row.name !== target);
27
31
  }
28
32
  const cfg = servers[target];
33
+ applied().delete(target);
29
34
  if (enabled === false || !cfg || cfg.enabled === false) {
30
- applied().delete(target);
31
35
  await mcpClient.disconnectMcpServer?.(target, scopeOptions());
32
- return;
36
+ return null;
33
37
  }
34
- applied().set(target, fingerprint(cfg));
35
38
  // Close-then-connect as one generation-guarded op: a shutdown landing
36
39
  // between the close and the connect never resurrects the server.
37
40
  try {
@@ -42,29 +45,52 @@ export function createServerConnection({
42
45
  cfg,
43
46
  login ? { interactive: true, openBrowser: login.openBrowser } : {}
44
47
  );
48
+ applied().set(target, fingerprint(cfg));
49
+ return null;
45
50
  } catch (error) {
46
51
  const failure = { name: target, msg: error?.message || String(error) };
47
52
  if (error?.authRequired) failure.authRequired = true;
48
53
  const failures = Array.isArray(error?.failures) ? error.failures : [failure];
49
54
  state.mcpFailures = [...(state.mcpFailures || []), ...failures];
55
+ return failure;
50
56
  }
51
57
  }
52
58
 
53
59
  // Bring the registry in line with the effective set: drop servers that are
54
60
  // gone or disabled, (re)connect servers that are new or whose resolved
55
- // config changed; untouched servers keep their connection.
61
+ // config changed; untouched servers keep their connection. A server whose
62
+ // last connect failed is retried too (one the full connect recorded as
63
+ // connected included): reconcile also runs when a sign-in or a stored
64
+ // credential changed (possibly in another process), which leaves its
65
+ // config, and so its fingerprint, as it was; so is a connection the client
66
+ // reports dead. Answers the failures of this run that are not a needed
67
+ // sign-in (that waits on the user, whose sign-in reconciles again); they
68
+ // stay in the status, which the change step checks to retry them.
56
69
  async function reconcileServers() {
57
70
  syncScopeRoot();
58
71
  const { servers } = resolveEffectiveMcpServers();
59
72
  const known = applied();
60
- const drops = [...known.keys()].filter((name) => !servers[name] || servers[name].enabled === false);
73
+ const dead = (mcpClient.getMcpServerStatus?.(getMcpScopeId()) || [])
74
+ .filter((row) => row.alive === false || row.connected === false)
75
+ .map((row) => row.name);
76
+ const failed = new Set([
77
+ ...(Array.isArray(state.mcpFailures) ? state.mcpFailures : []).map((row) => row.name),
78
+ ...dead,
79
+ ]);
80
+ const drops = [...new Set([...known.keys(), ...failed])].filter(
81
+ (name) => !servers[name] || servers[name].enabled === false
82
+ );
61
83
  const connects = Object.entries(servers)
62
- .filter(([name, cfg]) => cfg?.enabled !== false && known.get(name) !== fingerprint(cfg))
84
+ .filter(([name, cfg]) => cfg?.enabled !== false && (known.get(name) !== fingerprint(cfg) || failed.has(name)))
63
85
  .map(([name]) => name);
64
- await Promise.allSettled([
86
+ const results = await Promise.allSettled([
65
87
  ...drops.map((name) => applyMcpServerConnection(name, false, servers)),
66
88
  ...connects.map((name) => applyMcpServerConnection(name, true, servers)),
67
89
  ]);
90
+ return results.flatMap((one) => {
91
+ if (one.status === 'rejected') return [{ msg: one.reason?.message || String(one.reason) }];
92
+ return one.value && !one.value.authRequired ? [one.value] : [];
93
+ });
68
94
  }
69
95
 
70
96
  return { applyMcpServerConnection, reconcileServers };
@@ -136,6 +136,23 @@ function refreshSessionFallbacks(deps, session) {
136
136
  deps.mgr.updateSessionRoute?.(session.id, fields, { session });
137
137
  }
138
138
 
139
+ /** The route fields a model pick held for the next turn keeps on its session. */
140
+ function heldRoutePick(route) {
141
+ const pick = {
142
+ provider: route.provider,
143
+ model: route.model,
144
+ ...(route.preset ? { preset: route.preset } : {}),
145
+ ...(route.effort !== undefined ? { effort: route.effort } : {}),
146
+ fast: route.fast === true,
147
+ ...(route.autoEffort === false ? { autoEffort: false } : {}),
148
+ ...(route.modelParameters ? { modelParameters: route.modelParameters } : {}),
149
+ ...(route.contextPercent !== undefined ? { contextPercent: route.contextPercent } : {}),
150
+ ...(route.contextTokens ? { contextTokens: route.contextTokens } : {}),
151
+ ...(route.selectedContextWindow ? { selectedContextWindow: route.selectedContextWindow } : {}),
152
+ };
153
+ return pick;
154
+ }
155
+
139
156
  /** Rewrite the addressed empty session to the route it was asked to run on. */
140
157
  function updateSessionInPlace(deps, session, updateOptions) {
141
158
  const { getRoute, getSession, setSession, mgr, statusRoutes } = deps;
@@ -386,9 +403,16 @@ export function createSetRoute(deps, persist) {
386
403
  const session = getSession();
387
404
  if (options.deferUntilNextTurn === true) {
388
405
  pending = session?.id ? { generation: lease.token } : null;
406
+ // Held on the session too (saved like a pending workflow switch), so a
407
+ // restart before the next turn still applies it.
408
+ if (session?.id) {
409
+ session.pendingRoute = heldRoutePick(getRoute());
410
+ deps.persistSession?.(session);
411
+ }
389
412
  return getRoute();
390
413
  }
391
414
  pending = null;
415
+ if (session?.pendingRoute) delete session.pendingRoute;
392
416
  // A session the user has already talked in or compacted keeps its route
393
417
  // unless the selection was addressed to it. A current session with no
394
418
  // model reply, no compaction summary and no in-flight first turn — empty,
@@ -436,21 +460,30 @@ export function createSetRoute(deps, persist) {
436
460
  return getRoute();
437
461
  }
438
462
  setRoute.applyPendingRoute = () => {
439
- if (!pending) return null;
440
- const selection = pending;
441
- if (selection.generation !== getSessionGeneration()) {
463
+ const session = getSession();
464
+ const held = session?.pendingRoute;
465
+ if (session && held) delete session.pendingRoute;
466
+ if (pending) {
467
+ const selection = pending;
442
468
  pending = null;
469
+ if (selection.generation !== getSessionGeneration()) return null;
470
+ // The live route is the latest selection: effort/Fast tuned after the
471
+ // deferred model choice replace it.
472
+ } else if (held?.provider && held.model) {
473
+ // Held by an earlier process (a restart in between): the saved pick
474
+ // becomes the live route again.
475
+ deps.setRouteState(deps.resolveRoute(deps.getConfig(), held));
476
+ } else {
443
477
  return null;
444
478
  }
445
- // The live route is the latest selection: effort/Fast tuned after the
446
- // deferred model choice replace it, so no snapshot is kept.
447
- const modelSwitch = switchSessionInPlace(deps, getSession());
479
+ const modelSwitch = switchSessionInPlace(deps, session);
448
480
  invalidateContextStatusCache();
449
- pending = null;
450
481
  return modelSwitch;
451
482
  };
452
483
  setRoute.clearPendingRoute = () => {
453
484
  pending = null;
485
+ const session = getSession();
486
+ if (session?.pendingRoute) delete session.pendingRoute;
454
487
  };
455
488
  return setRoute;
456
489
  }
@@ -46,9 +46,9 @@ export function createWebSearchRouteCandidates({
46
46
  await awaitKeychainPrewarm();
47
47
  const cfg = ensureFullConfig();
48
48
  // An unset webSearchRoute IS the default ("follow the Main Model") — the
49
- // sidebar has always presented it that way. Materialize it here instead of
50
- // treating a missing key as "not configured", which used to fail the search
51
- // before the Main Model was ever consulted.
49
+ // sidebar presents it that way. Materialize it here instead of treating a
50
+ // missing key as "not configured", which would fail the search before the
51
+ // Main Model is consulted.
52
52
  const webSearchRoute = webSearchRouteOrDefault(cfg.webSearchRoute, getWebSearchRoute());
53
53
  setWebSearchRoute(webSearchRoute);
54
54
  const candidates = [];
@@ -3,7 +3,7 @@
3
3
  import { resetProviderAdmissionCooldowns } from '../../runtime/agent/orchestrator/providers/admission-scheduler.mjs';
4
4
 
5
5
  export function createAuthRefresh({
6
- reloadFullConfig,
6
+ applyConfigChange,
7
7
  invalidateProviderCaches,
8
8
  warmProviderModelCache,
9
9
  refreshProviderCatalogs,
@@ -42,14 +42,17 @@ export function createAuthRefresh({
42
42
  warmProviderModelCache();
43
43
  }
44
44
 
45
+ // The change step (reload + effects keyed by the config/secret change) runs
46
+ // here at once rather than on the notification that follows, so this
47
+ // runtime's notification finds the change already applied.
45
48
  function afterCredentialChange() {
46
- reloadFullConfig();
49
+ applyConfigChange();
47
50
  adoptCredentials();
48
51
  }
49
52
 
50
53
  /** Usage-dashboard credentials affect no admission lane or catalog. */
51
54
  function afterUsageAuthChange() {
52
- reloadFullConfig();
55
+ applyConfigChange();
53
56
  invalidateProviderCaches();
54
57
  }
55
58
 
@@ -48,7 +48,7 @@ export function createSetupApi({
48
48
  return cachedProviderSetup({ force: force || options?.recheckKeychain === true });
49
49
  }
50
50
  // An unforced read never blocks: the authoritative setup waits on the OS
51
- // keychain, which can take seconds on a cold start and used to stall the
51
+ // keychain, which can take seconds on a cold start and would stall the
52
52
  // whole settings sweep behind it. Serve
53
53
  // the no-secrets snapshot until the real one is cached — flagged, so the
54
54
  // caller shows "checking" instead of a wrong "not connected" — and let the
@@ -51,6 +51,13 @@ export function createProviderReadiness({ rt, keychain, getReg, getWarmProviderM
51
51
  const providerModelCaches = { ...emptyModelCaches(), providerModelsLoadSeq: 0 };
52
52
  const providerUsageCaches = emptyUsageCaches();
53
53
  const providerInitPromises = new Map();
54
+ // Whether this runtime has started its providers: a rebuild after a change
55
+ // follows it, a failed initialization (whose gate is dropped) included.
56
+ let providersStarted = false;
57
+ // Advances whenever the init gates are cleared (a provider config or
58
+ // credential change): an initialization registered before it is superseded
59
+ // and never writes the shared registry.
60
+ let providerInitGeneration = 0;
54
61
 
55
62
  function invalidateProviderCaches(options = {}) {
56
63
  // Auth/account/config changes alter what providers list: the process-wide
@@ -59,7 +66,10 @@ export function createProviderReadiness({ rt, keychain, getReg, getWarmProviderM
59
66
  Object.assign(providerModelCaches, emptyModelCaches());
60
67
  providerModelCaches.providerModelsLoadSeq += 1;
61
68
  Object.assign(providerUsageCaches, emptyUsageCaches());
62
- if (options.preserveProviderInit !== true) providerInitPromises.clear();
69
+ if (options.preserveProviderInit !== true) {
70
+ providerInitPromises.clear();
71
+ providerInitGeneration += 1;
72
+ }
63
73
  modelMetaByRoute.clear();
64
74
  }
65
75
 
@@ -67,19 +77,44 @@ export function createProviderReadiness({ rt, keychain, getReg, getWarmProviderM
67
77
  // absent (provider setup, init gates) is stale.
68
78
  keychain.onKeychainRecovered?.(() => invalidateProviderCaches());
69
79
 
70
- async function ensureProvidersReady(providerConfig = rt.config.providers || {}) {
80
+ // The one edit a caller makes to the provider config it passes is turning a
81
+ // route's provider on (ensureProviderEnabled); those providers, judged
82
+ // against the runtime's config when the request was made.
83
+ function forcedProviders(requested, base) {
84
+ return Object.keys(requested || {}).filter(
85
+ (name) => requested[name]?.enabled === true && base?.[name]?.enabled !== true
86
+ );
87
+ }
88
+
89
+ // Waits for the keychain, then initializes the registry with
90
+ // `providerConfig` unless a change superseded it meanwhile: a superseded
91
+ // request is replayed on the latest config (with the providers it turned on),
92
+ // so the latest config always wins.
93
+ async function initializeProviders(providerConfig) {
94
+ const generation = providerInitGeneration;
95
+ const forced = forcedProviders(providerConfig, rt.config.providers);
71
96
  await awaitKeychainPrewarm();
72
97
  if (rt.closeRequested) throw new Error('runtime is closing');
98
+ if (generation === providerInitGeneration) return getReg().initProviders(providerConfig);
99
+ const latest = { ...(rt.config.providers || {}) };
100
+ for (const name of forced) latest[name] = { ...(latest[name] || {}), enabled: true };
101
+ return ensureProvidersReady(latest);
102
+ }
103
+
104
+ async function ensureProvidersReady(providerConfig = rt.config.providers || {}) {
105
+ if (rt.closeRequested) throw new Error('runtime is closing');
106
+ providersStarted = true;
73
107
  const initKey = providerInitCacheKey(providerConfig);
74
108
  // Provider initialization is idempotent for one normalized config. Keep
75
109
  // the fulfilled promise, not only the in-flight one: session resume used
76
110
  // to rerun registry/keychain setup on every click even though neither the
77
111
  // provider config nor the registry had changed. Auth/config mutations call
78
112
  // invalidateProviderCaches(), which clears this gate and preserves the
79
- // existing refresh semantics.
113
+ // existing refresh semantics. Registered before any await, so a change
114
+ // applied while it waits on the keychain sees (and supersedes) it.
80
115
  let providerInitPromise = providerInitPromises.get(initKey);
81
116
  if (!providerInitPromise) {
82
- providerInitPromise = Promise.resolve().then(() => getReg().initProviders(providerConfig));
117
+ providerInitPromise = initializeProviders(providerConfig);
83
118
  providerInitPromises.set(initKey, providerInitPromise);
84
119
  }
85
120
  let result;
@@ -125,9 +160,23 @@ export function createProviderReadiness({ rt, keychain, getReg, getWarmProviderM
125
160
  return result;
126
161
  }
127
162
 
163
+ // Provider config or secrets changed: drop everything keyed to the old ones
164
+ // and, when this runtime had initialized providers, rebuild the shared
165
+ // registry from its reloaded config now, so no runtime's stale copy is the
166
+ // last one to have initialized it. Rejects when that rebuild failed.
167
+ function reinitProviders() {
168
+ invalidateProviderCaches();
169
+ if (!providersStarted || rt.closeRequested) return Promise.resolve();
170
+ return ensureProvidersReady().catch((error) => {
171
+ bootProfile('providers:reinit-failed', { error: error?.message || String(error) });
172
+ throw error;
173
+ });
174
+ }
175
+
128
176
  return {
129
177
  awaitKeychainPrewarm,
130
178
  invalidateProviderCaches,
179
+ reinitProviders,
131
180
  ensureProvidersReady,
132
181
  modelMetaByRoute,
133
182
  providerModelCaches,
@@ -31,7 +31,7 @@ export function createResourceApi(deps) {
31
31
  throw new Error(`extension scope kind must be one of ${EXTENSION_SCOPE_KINDS.join(', ')}`);
32
32
  }
33
33
  // A connector is on in every project unless a project switches it off
34
- // (setProjectConnectorEnabled); "only in this project" no longer exists.
34
+ // (setProjectConnectorEnabled); there is no "only in this project" scope.
35
35
  if (scopeKind === 'mcp') {
36
36
  throw new Error('MCP servers are switched off per project in Project settings (setProjectConnectorEnabled)');
37
37
  }
@@ -7,7 +7,6 @@ export function createExtensionSync({
7
7
  connectConfiguredMcp,
8
8
  cancelMcpLogin,
9
9
  invalidatePreSessionToolSurface,
10
- refreshEmptySessionToolPolicy,
11
10
  reloadFullConfig,
12
11
  invalidateSkills,
13
12
  }) {
@@ -19,14 +18,11 @@ export function createExtensionSync({
19
18
  // session.
20
19
  const mcpToggleChains = new Map(); // name -> { desired, running }
21
20
 
22
- async function refreshSurface(kind) {
21
+ // The invalidation moves the surface generation: an empty session rebuilds
22
+ // at its next turn start (reconcileTurnSettings); MCP has its own
23
+ // first-turn/late-tool reconciliation against the live connection registry.
24
+ async function refreshSurface() {
23
25
  invalidatePreSessionToolSurface();
24
- // Existing conversations retain their stable prompt/session identity.
25
- // Empty sessions can safely rebuild their policy in place; MCP has its own
26
- // first-turn/late-tool reconciliation against the live connection registry.
27
- if ((kind === 'skills' || kind === 'plugins') && typeof refreshEmptySessionToolPolicy === 'function') {
28
- await refreshEmptySessionToolPolicy();
29
- }
30
26
  }
31
27
 
32
28
  // Apply per-server MCP changes in order, then reconcile: a configured server
@@ -80,7 +80,9 @@ export function createConnectorAccountEdits({
80
80
  throw nameTakenError(servers, name);
81
81
  }
82
82
  const accountName = serverName ? freeName(servers, name) : name;
83
- const account = connectorAccountEntry(accountName, accountCfg);
83
+ // A fresh identity: a conversation that used a removed account of this
84
+ // name never takes this one for it (connector-accounts-mcp.mjs).
85
+ const account = { ...connectorAccountEntry(accountName, accountCfg), id: randomUUID() };
84
86
  // A sign-in is stored per exact URL: another account signing in at the
85
87
  // same URL gets its own key, so the two never share tokens.
86
88
  const asServer = connectorAccountServer({ connector }, account);
@@ -141,6 +143,8 @@ export function createConnectorAccountEdits({
141
143
  entry.name === account.name
142
144
  ? {
143
145
  ...connectorAccountEntry(account.name, next),
146
+ // An edit keeps the account's identity.
147
+ ...(entry.id ? { id: entry.id } : {}),
144
148
  ...(connectorAccountEnabled(entry) ? {} : { enabled: false }),
145
149
  }
146
150
  : entry