mixdog 1.3.1 → 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 (377) hide show
  1. package/README.md +8 -4
  2. package/package.json +2 -2
  3. package/scripts/native-release-status.mjs +238 -0
  4. package/src/defaults/shipped-history.json +5 -1
  5. package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
  6. package/src/defaults/skills/setup/SKILL.md +23 -1
  7. package/src/defaults/skills/setup/references/actions.md +50 -3
  8. package/src/defaults/skills/setup/references/surfaces.md +23 -3
  9. package/src/defaults/skills/setup-guide/SKILL.md +7 -3
  10. package/src/defaults/skills/visualize/SKILL.md +9 -6
  11. package/src/defaults/skills/visualize/references/craft.md +8 -1
  12. package/src/help.mjs +1 -1
  13. package/src/lib/keychain-cjs.cjs +4 -1
  14. package/src/lib/rules-builder.cjs +14 -7
  15. package/src/rules/agent-mode/AGENT_MODE.md +8 -5
  16. package/src/rules/agent-mode/MIXY.md +6 -8
  17. package/src/rules/lead/LEAD.md +2 -2
  18. package/src/rules/shared/10-tool-workflow.md +4 -0
  19. package/src/rules/shared/20-research.md +2 -0
  20. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
  21. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
  22. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
  23. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
  24. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
  25. package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
  26. package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
  27. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
  28. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  29. package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
  30. package/src/runtime/agent/orchestrator/config.mjs +11 -0
  31. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +9 -2
  32. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -5
  33. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
  34. package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
  35. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +80 -31
  36. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
  37. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  38. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +67 -38
  39. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
  40. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
  41. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +27 -8
  42. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
  43. package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
  44. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
  45. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
  46. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +16 -0
  47. package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
  48. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -1
  49. package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +3 -1
  50. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +1 -0
  51. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +5 -5
  52. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +9 -5
  53. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +9 -1
  54. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +2 -1
  55. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +30 -3
  56. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +1 -1
  57. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +3 -1
  58. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +61 -8
  59. package/src/runtime/agent/orchestrator/providers/registry.mjs +9 -0
  60. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
  61. package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
  62. package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
  63. package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
  64. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
  65. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +4 -6
  66. package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
  67. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
  68. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +12 -1
  69. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +6 -5
  70. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +7 -6
  71. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +1 -1
  72. package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
  73. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +2 -0
  74. package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
  75. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
  76. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
  77. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +17 -2
  78. package/src/runtime/agent/orchestrator/session/agent-room.mjs +133 -38
  79. package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +80 -0
  80. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  81. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +14 -11
  82. package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
  83. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +68 -18
  84. package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
  85. package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
  86. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
  87. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +5 -1
  88. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +1 -1
  89. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
  90. package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
  91. package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +230 -0
  92. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +136 -32
  93. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
  94. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +21 -9
  95. package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
  96. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
  97. package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
  98. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
  99. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +189 -105
  100. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
  101. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +5 -5
  102. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +3 -0
  103. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
  104. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +38 -5
  105. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +21 -0
  106. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
  107. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
  108. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
  109. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
  110. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +4 -0
  111. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  112. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  113. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
  114. package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +99 -0
  115. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
  116. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
  117. package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
  118. package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
  119. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +40 -1
  120. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
  121. package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +20 -0
  122. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +55 -27
  123. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +3 -8
  124. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +12 -1
  125. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
  126. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
  127. package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
  128. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +42 -10
  129. package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
  130. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +18 -3
  131. package/src/runtime/agent/orchestrator/session/routine-result.mjs +120 -9
  132. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +5 -1
  133. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
  134. package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
  135. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
  136. package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
  137. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
  138. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +8 -3
  139. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +36 -1
  140. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -1
  141. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
  142. package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
  143. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
  144. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
  145. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
  146. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +35 -5
  147. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +42 -2
  148. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +35 -13
  149. package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
  150. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +4 -3
  151. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
  152. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
  153. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
  154. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
  155. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +6 -14
  156. package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
  157. package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
  158. package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
  159. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
  160. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
  161. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +34 -21
  162. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
  163. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
  164. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
  165. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
  166. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
  167. package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
  168. package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
  169. package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
  170. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
  171. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
  172. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
  173. package/src/runtime/attachments/attachment-reference.mjs +155 -0
  174. package/src/runtime/attachments/limits.mjs +5 -1
  175. package/src/runtime/attachments/office-extract.mjs +3 -6
  176. package/src/runtime/attachments/pdf-extract.mjs +84 -10
  177. package/src/runtime/attachments/store.mjs +49 -30
  178. package/src/runtime/browser-bridge/client.mjs +73 -20
  179. package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
  180. package/src/runtime/channels/lib/scheduler.mjs +14 -7
  181. package/src/runtime/channels/lib/state-file.mjs +15 -15
  182. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
  183. package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
  184. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
  185. package/src/runtime/channels/lib/worker-main.mjs +3 -2
  186. package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
  187. package/src/runtime/effort-judge/judge-client.mjs +5 -2
  188. package/src/runtime/effort-judge/model-install.mjs +16 -8
  189. package/src/runtime/local-provider/asset-installer.mjs +10 -4
  190. package/src/runtime/local-provider/catalog.mjs +5 -2
  191. package/src/runtime/local-provider/registered-models.mjs +64 -27
  192. package/src/runtime/local-provider/server.mjs +2 -3
  193. package/src/runtime/media/adapters/image-api.mjs +89 -0
  194. package/src/runtime/media/auth.mjs +64 -1
  195. package/src/runtime/media/catalog.mjs +66 -4
  196. package/src/runtime/media/jobs.mjs +4 -0
  197. package/src/runtime/media/lanes.mjs +46 -5
  198. package/src/runtime/media/media-usage.mjs +17 -0
  199. package/src/runtime/media/renditions.mjs +15 -15
  200. package/src/runtime/media/store.mjs +36 -35
  201. package/src/runtime/media/tool.mjs +8 -1
  202. package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +8 -2
  203. package/src/runtime/memory/lib/embedding-model-files.mjs +30 -7
  204. package/src/runtime/memory/lib/ingest/transcript-tail.mjs +16 -7
  205. package/src/runtime/memory/lib/pg/process.mjs +8 -4
  206. package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
  207. package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
  208. package/src/runtime/office/core/journal.mjs +7 -14
  209. package/src/runtime/office/design/library/design-library-core.mjs +4 -16
  210. package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
  211. package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
  212. package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
  213. package/src/runtime/office/pdf/pdf-render.mjs +30 -18
  214. package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
  215. package/src/runtime/office/portable/portable-soffice.mjs +4 -0
  216. package/src/runtime/shared/agent-kind-migration.mjs +200 -0
  217. package/src/runtime/shared/agent-message-envelope.d.mts +6 -0
  218. package/src/runtime/shared/agent-message-envelope.mjs +44 -0
  219. package/src/runtime/shared/agent-reap-state.d.mts +4 -0
  220. package/src/runtime/shared/agent-store.mjs +24 -7
  221. package/src/runtime/shared/agent-task-starts.mjs +27 -0
  222. package/src/runtime/shared/atomic-file.d.mts +50 -0
  223. package/src/runtime/shared/atomic-file.mjs +394 -132
  224. package/src/runtime/shared/background-tasks.mjs +7 -4
  225. package/src/runtime/shared/bot-hops.mjs +30 -0
  226. package/src/runtime/shared/bot-split-migration.mjs +1 -1
  227. package/src/runtime/shared/bounded-download.mjs +4 -2
  228. package/src/runtime/shared/config.mjs +39 -9
  229. package/src/runtime/shared/defaults-separation.mjs +6 -2
  230. package/src/runtime/shared/delegated-approval-host.mjs +74 -0
  231. package/src/runtime/shared/err-text.mjs +4 -0
  232. package/src/runtime/shared/error-presentation.mjs +22 -4
  233. package/src/runtime/shared/file-has-data.mjs +28 -0
  234. package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
  235. package/src/runtime/shared/pending-agent-work.mjs +54 -0
  236. package/src/runtime/shared/process-lifecycle.mjs +8 -12
  237. package/src/runtime/shared/projects.mjs +50 -41
  238. package/src/runtime/shared/provider-accounts.mjs +17 -12
  239. package/src/runtime/shared/routine-trigger.d.mts +6 -1
  240. package/src/runtime/shared/routine-trigger.mjs +14 -6
  241. package/src/runtime/shared/run-child.d.mts +3 -0
  242. package/src/runtime/shared/run-child.mjs +24 -3
  243. package/src/runtime/shared/session-baseline-store.mjs +8 -2
  244. package/src/runtime/shared/singleton-owner.mjs +7 -6
  245. package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
  246. package/src/runtime/shared/staged-update.mjs +4 -2
  247. package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
  248. package/src/runtime/shared/tool-execution-contract.mjs +6 -0
  249. package/src/runtime/shared/transcript-metadata.mjs +28 -3
  250. package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
  251. package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
  252. package/src/runtime/shared/turn-snapshot.mjs +44 -2
  253. package/src/runtime/shared/user-cwd.mjs +9 -4
  254. package/src/runtime/shared/user-data-guard.mjs +41 -42
  255. package/src/runtime/shared/utf8-cut.mjs +10 -0
  256. package/src/runtime/shared/worktrees/activity-gate.mjs +269 -61
  257. package/src/runtime/shared/worktrees/registry.mjs +26 -26
  258. package/src/runtime/tidy/core-install.mjs +26 -4
  259. package/src/runtime/tidy/install.mjs +91 -23
  260. package/src/runtime/web-search/lib/config.mjs +4 -16
  261. package/src/session-runtime/agent-home-delivery.mjs +555 -199
  262. package/src/session-runtime/agent-room-delivery.mjs +143 -36
  263. package/src/session-runtime/agent-task-sessions.mjs +667 -165
  264. package/src/session-runtime/agent-tool-routing.mjs +1 -0
  265. package/src/session-runtime/boot/apis.mjs +45 -24
  266. package/src/session-runtime/boot/config.mjs +1 -0
  267. package/src/session-runtime/boot/facade.mjs +11 -1
  268. package/src/session-runtime/boot/infrastructure.mjs +14 -2
  269. package/src/session-runtime/boot/providers.mjs +2 -2
  270. package/src/session-runtime/boot/shared.mjs +12 -6
  271. package/src/session-runtime/boot/tools.mjs +2 -2
  272. package/src/session-runtime/bot-status.mjs +432 -0
  273. package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
  274. package/src/session-runtime/config-lifecycle.mjs +60 -55
  275. package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
  276. package/src/session-runtime/held-requests.mjs +36 -33
  277. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
  278. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
  279. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +19 -4
  280. package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
  281. package/src/session-runtime/lifecycle/teardown.mjs +11 -3
  282. package/src/session-runtime/mcp-glue/connect.mjs +100 -186
  283. package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
  284. package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
  285. package/src/session-runtime/media-api.mjs +5 -2
  286. package/src/session-runtime/model-route/route-tuning.mjs +4 -1
  287. package/src/session-runtime/model-route/set-route.mjs +61 -1
  288. package/src/session-runtime/owned-automation-run.mjs +8 -1
  289. package/src/session-runtime/room-member-gate.mjs +16 -3
  290. package/src/session-runtime/route-resolution.mjs +16 -8
  291. package/src/session-runtime/route-state.mjs +1 -59
  292. package/src/session-runtime/routine-home-turn.mjs +5 -3
  293. package/src/session-runtime/runtime-bootstrap.mjs +5 -3
  294. package/src/session-runtime/runtime-paths.mjs +11 -5
  295. package/src/session-runtime/runtime-tunables.mjs +6 -0
  296. package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
  297. package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
  298. package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
  299. package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
  300. package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
  301. package/src/session-runtime/services/agent-tool/send-flow.mjs +25 -21
  302. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +4 -0
  303. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
  304. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +8 -5
  305. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
  306. package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
  307. package/src/session-runtime/services/agent-tool.mjs +6 -0
  308. package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
  309. package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
  310. package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
  311. package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
  312. package/src/session-runtime/services/plugin-admin.mjs +26 -26
  313. package/src/session-runtime/services/provider-admin.mjs +14 -4
  314. package/src/session-runtime/services/schedule-access.mjs +33 -4
  315. package/src/session-runtime/services/seeds.mjs +4 -3
  316. package/src/session-runtime/services/usage-dashboard.mjs +1 -1
  317. package/src/session-runtime/services/usage-stats-model.mjs +2 -1
  318. package/src/session-runtime/session-create/materialize.mjs +2 -3
  319. package/src/session-runtime/session-create/session-options.mjs +15 -2
  320. package/src/session-runtime/settings-builtin-tools-api.mjs +63 -1
  321. package/src/session-runtime/settings-compaction-api.mjs +86 -39
  322. package/src/session-runtime/setup-tool/executor.mjs +8 -2
  323. package/src/session-runtime/setup-tool/settings-contract.mjs +31 -1
  324. package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
  325. package/src/session-runtime/skills-api.mjs +12 -5
  326. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +265 -0
  327. package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
  328. package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
  329. package/src/session-runtime/tool-policy/policy-stage.mjs +165 -0
  330. package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
  331. package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
  332. package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
  333. package/src/session-runtime/tool-policy-refresh.mjs +47 -369
  334. package/src/session-runtime/tool-surface.mjs +5 -3
  335. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +95 -88
  336. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  337. package/src/session-runtime/turn/turn-run.mjs +2 -1
  338. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
  339. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +519 -333
  340. package/src/session-runtime/workflow-agents-api/agent-route.mjs +46 -46
  341. package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
  342. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
  343. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
  344. package/src/session-runtime/workspace/workspace-state.mjs +7 -6
  345. package/src/standalone/daemon-agent-control.mjs +4 -1
  346. package/src/standalone/daemon-session-control.mjs +51 -10
  347. package/src/standalone/daemon.mjs +28 -10
  348. package/src/standalone/local-session-runtime.mjs +22 -14
  349. package/src/standalone/session-client.mjs +7 -2
  350. package/src/standalone/session-protocol.mjs +7 -0
  351. package/src/standalone/session-service.mjs +11 -0
  352. package/src/standalone/session-state-projection.mjs +19 -9
  353. package/src/standalone/session-tool-output.mjs +35 -9
  354. package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
  355. package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
  356. package/src/tui/app/slash-commands.mjs +1 -1
  357. package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
  358. package/src/tui/dist/index.mjs +529 -333
  359. package/src/tui/prompt-history-store.mjs +16 -21
  360. package/src/tui/session/context-state.mjs +4 -0
  361. package/src/tui/session/session-api/intake/abort.mjs +19 -1
  362. package/src/tui/session/session-api/media.mjs +2 -1
  363. package/src/tui/session/session-api/routes-agent-definitions.mjs +75 -0
  364. package/src/tui/session/session-api/routes-write.mjs +203 -0
  365. package/src/tui/session/session-api/routes.mjs +23 -247
  366. package/src/tui/session/session-api/settings.mjs +14 -1
  367. package/src/tui/session/session-flow/drain.mjs +28 -3
  368. package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
  369. package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
  370. package/src/tui/session/session-flow/steering.mjs +8 -3
  371. package/src/tui/session/tool-approval.mjs +185 -20
  372. package/src/tui/session/transcript-intake.mjs +2 -0
  373. package/src/tui/session/turn.mjs +23 -14
  374. package/src/tui/transcript-tool-failures.mjs +8 -2
  375. package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -8
  376. package/src/vendor/statusline/src/gateway/session-routes.mjs +6 -7
  377. package/src/rules/agent-mode/ROUTINE.md +0 -6
@@ -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.
@@ -67,7 +67,10 @@ export function createRouteTuning(deps, persist) {
67
67
  },
68
68
  async setEffort(value) {
69
69
  const normalized = normalizeEffortInput(value);
70
- 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 } : {}) });
71
74
  const modelMeta = await lookupModelMeta(getRoute().provider, getRoute().model);
72
75
  const fastCapable = selectionFastCapable(getRoute().provider, modelMeta, normalized, getRoute().modelParameters);
73
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,6 +248,12 @@ 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;
198
257
  // The Lead route a conditional fill wrote, until the file confirms it.
199
258
  let filled = null;
200
259
  // Provider readiness may outlast the session the selection was made for.
@@ -222,7 +281,7 @@ async function adoptRoute(deps, persist, next, options, lease) {
222
281
  // The main model changes only on an explicit main-model choice
223
282
  // (keepMainModel: false, e.g. the setup tool); every other selection
224
283
  // — the session surface's model pick, an heir — keeps it.
225
- const leadRoute = persist.persistAdoptedModelSettings(selectedRoute, {
284
+ const leadRoute = persist.persistAdoptedModelSettings(persistedRoute, {
226
285
  keepMainModel: options?.keepMainModel !== false,
227
286
  onlyIfAbsent: options?.onlyIfEmpty === true,
228
287
  });
@@ -340,6 +399,7 @@ export function createSetRoute(deps, persist) {
340
399
  return modelSwitch ? { ...getRoute(), modelSwitch } : getRoute();
341
400
  }
342
401
  persist.applySessionTuning();
402
+ refreshSessionFallbacks(deps, session);
343
403
  return getRoute();
344
404
  }
345
405
  // An addressed runtime keeps its durable id: the empty session is updated
@@ -101,7 +101,7 @@ async function queueResult(row, item, entry, { deliver, recordDelivery, onDelive
101
101
  /**
102
102
  * `row` is `{ name, ownerAgentId }`; `run({ signal })` resolves `{ sessionId,
103
103
  * result }` (`signal` is the caller's cancellation, if any). Resolves the
104
- * outcome, or `{ skipped: true, reason: 'owner' | 'overlap' | 'backoff' }`
104
+ * outcome, or `{ skipped: true, reason: 'owner' | 'overlap' | 'backoff' | 'paused' }`
105
105
  * (an owner skip also names `ownerProblem` and its `message`); a failed run
106
106
  * rejects.
107
107
  */
@@ -141,6 +141,13 @@ export async function runOwnedAutomation(kind, row, run, deps = {}) {
141
141
  const state = await Promise.resolve()
142
142
  .then(() => record.state(name))
143
143
  .catch(() => null);
144
+ // Switched off (by the user, or by the failure limit): the cron claim and
145
+ // the webhook handler already refuse it, but a one-shot timer or a
146
+ // dispatch that raced the pause must not run it either.
147
+ if (state?.enabled === false) {
148
+ log(`${kind} "${name}": skipped, switched off`);
149
+ return await skip('paused');
150
+ }
144
151
  const wait = routineBackoffRemaining(state, now(), limits);
145
152
  if (wait > 0) {
146
153
  log(`${kind} "${name}": skipped, backing off ${Math.ceil(wait / 1000)}s after failures in a row`);
@@ -2,6 +2,9 @@
2
2
  // anything that can change files or state asks the user through the ordinary
3
3
  // approval flow, whatever the agent's own permission or hook rules say — even
4
4
  // an agent that normally runs auto-approved. A hook's deny still wins.
5
+ // Opening a task session (`session create`) runs as freely as in the agent's
6
+ // home thread: it changes nothing itself, and the task session is the user's
7
+ // own coding Lead, whose every side effect follows its usual approval rules.
5
8
  import { analyzeShellCommandEffects } from '../runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs';
6
9
 
7
10
  const READ_ONLY_TOOLS = new Set([
@@ -37,12 +40,22 @@ export async function isReadOnlyRoomCall({ name, args, cwd }, analyze = analyzeS
37
40
  }
38
41
  }
39
42
 
43
+ /** Whether `name(args)` opens a task session (session create). */
44
+ export function isTaskSessionCreate({ name, args }) {
45
+ return (
46
+ name === 'session' &&
47
+ String(args?.action ?? '')
48
+ .trim()
49
+ .toLowerCase() === 'create'
50
+ );
51
+ }
52
+
40
53
  /** The PreToolUse decision for a room member's call: the hook's own `decision`
41
- * when it denies or the call only reads; otherwise an approval ask that keeps
42
- * any rewrite the hook made. */
54
+ * when it denies, the call only reads, or it opens a task session; otherwise
55
+ * an approval ask that keeps any rewrite the hook made. */
43
56
  export async function roomMemberToolDecision(call, decision, analyze) {
44
57
  const action = String(decision?.action || decision?.decision || '').toLowerCase();
45
58
  if (action === 'deny' || action === 'block') return decision;
46
- if (await isReadOnlyRoomCall(call, analyze)) return decision;
59
+ if (isTaskSessionCreate(call) || (await isReadOnlyRoomCall(call, analyze))) return decision;
47
60
  return { ...(decision && typeof decision === 'object' ? decision : {}), action: 'ask', reason: ROOM_APPROVAL_REASON };
48
61
  }
@@ -22,6 +22,10 @@ export function createRouteResolution({ rt, collectProviderModels, ensureProvide
22
22
  if (routeHasModel()) return rt.route;
23
23
  const models = await runAbortable(signal, () => collectProviderModels());
24
24
  throwIfAborted(signal);
25
+ // A model chosen during the catalog read stands: a new task's reservation
26
+ // prewarm starts on the empty route, its draft model lands meanwhile, and
27
+ // the first prompt joins this very prewarm.
28
+ if (routeHasModel()) return rt.route;
25
29
  if (!models?.length) {
26
30
  throw new Error('No provider models available. Open /providers to sign in, then /model to choose a model.');
27
31
  }
@@ -47,15 +51,19 @@ export function createRouteResolution({ rt, collectProviderModels, ensureProvide
47
51
  .find((option) => option?.id === 'context')
48
52
  ?.options?.find((option) => clean(option?.value) === contextValue);
49
53
  const modelDisplay = routeModelDisplay(rt.config, targetRoute, modelMeta);
50
- const selectedContextWindow = [contextState.selectedContextWindow, contextOption?.contextWindow]
54
+ // Without a known window the selection (share, exact tokens, resolved
55
+ // window) is kept as requested — the session sizes it from the provider's
56
+ // window; dropping it silently put the route back on the model's default.
57
+ // With one it is the clamped value, absent when it is the default window.
58
+ const windowKnown = contextState.contextDefaultPercent !== undefined;
59
+ const selectedContextWindow = [
60
+ contextState.selectedContextWindow,
61
+ contextOption?.contextWindow,
62
+ windowKnown ? undefined : targetRoute.selectedContextWindow,
63
+ ]
51
64
  .map(Number)
52
65
  .find((value) => value > 0);
53
- // Without a known window the exact selection is kept as requested; with
54
- // one it is the clamped value, absent when it is the default window.
55
- const contextTokens =
56
- contextState.contextDefaultPercent === undefined
57
- ? normalizeContextTokens(targetRoute.contextTokens)
58
- : contextState.contextTokens;
66
+ const contextTokens = windowKnown ? contextState.contextTokens : normalizeContextTokens(targetRoute.contextTokens);
59
67
  const { contextTokens: _requestedTokens, ...routeFields } = targetRoute;
60
68
  rt.route = {
61
69
  ...routeFields,
@@ -63,7 +71,7 @@ export function createRouteResolution({ rt, collectProviderModels, ensureProvide
63
71
  fastCapable,
64
72
  effectiveEffort,
65
73
  effortOptions: effortItemsFor(rt.route.provider, modelMeta, effectiveEffort),
66
- contextPercent: contextState.contextPercent,
74
+ contextPercent: windowKnown ? contextState.contextPercent : targetRoute.contextPercent,
67
75
  contextDefaultPercent: contextState.contextDefaultPercent,
68
76
  ...(contextTokens ? { contextTokens } : {}),
69
77
  ...(selectedContextWindow ? { selectedContextWindow } : {}),
@@ -7,8 +7,6 @@ import {
7
7
  import { clean, hasOwn } from '../runtime/agent/orchestrator/runtime-core/session-text.mjs';
8
8
  import { coerceEffortFor } from '../runtime/agent/orchestrator/runtime-core/effort.mjs';
9
9
  import { fastCapableFor } from '../runtime/agent/orchestrator/runtime-core/model-capabilities.mjs';
10
- import { providerCachedModelMetadataSync } from '../runtime/agent/orchestrator/providers/provider-catalog-cache.mjs';
11
- import { contextWindowRange, normalizeContextTokens } from '../runtime/shared/llm/default-context-window.mjs';
12
10
 
13
11
  export function resolveRouteEffortState(targetRoute = {}, modelMeta = null) {
14
12
  const requested = hasOwn(targetRoute, 'effort') ? targetRoute.effort : targetRoute.preset?.effort || null;
@@ -36,63 +34,7 @@ export function selectionFastCapable(provider, modelMeta, effort, modelParameter
36
34
  return !modelMetaLooksResolved(modelMeta) || fastCapableFor(provider, modelMeta, effort, modelParameters);
37
35
  }
38
36
 
39
- // The persisted provider model rows are the SAME source the picker sized its
40
- // slider against, so they carry both windows (openai-oauth: 272k default /
41
- // 1M max). Reading them keeps a percentage saved against the picker's scale
42
- // meaningful; anything that only knows a single window would silently rescale
43
- // it (30% of 272k instead of 30% of 1M).
44
- function cachedRouteWindows(provider, model) {
45
- const row = providerCachedModelMetadataSync(provider, model)?.rawProviderModel || null;
46
- if (!row) return null;
47
- const contextWindow = Number(row.contextWindow ?? row.context_window ?? row.max_input_tokens) || 0;
48
- const maxContextWindow = Number(row.maxContextWindow ?? row.max_context_window) || 0;
49
- return contextWindow > 0 || maxContextWindow > 0 ? { contextWindow, maxContextWindow } : null;
50
- }
51
-
52
- export function resolveRouteContextState(targetRoute = {}, modelMeta = null, windowLookup = cachedRouteWindows) {
53
- // Only providers that implement getCachedModelInfo (openai-oauth, cursor,
54
- // openai-compat, opencode-go) hand lookupModelMeta a window; every other one
55
- // (anthropic-oauth, grok-oauth, gemini, …) gets the bare `{ id, provider }`
56
- // placeholder. Reading the window from that alone yields 0, which dropped the
57
- // saved context percentage for the entire session — while resolveSessionContextMeta
58
- // went on to size that session from the provider/catalog window (Claude Opus 5:
59
- // a full 1M rather than the selected 500k). Read the cached provider row here so
60
- // the percentage and the session boundary cannot disagree.
61
- const windowMeta =
62
- Number(modelMeta?.contextWindow) > 0 || Number(modelMeta?.maxContextWindow) > 0
63
- ? modelMeta
64
- : windowLookup?.(clean(targetRoute?.provider), clean(targetRoute?.model)) || modelMeta;
65
- const { defaultWindow, maxWindow } = contextWindowRange({
66
- provider: clean(targetRoute?.provider),
67
- contextWindow: windowMeta?.contextWindow,
68
- maxContextWindow: windowMeta?.maxContextWindow,
69
- });
70
- if (!maxWindow) {
71
- return { contextPercent: undefined, contextDefaultPercent: undefined, selectedContextWindow: undefined };
72
- }
73
- const contextDefaultPercent = Math.max(10, Math.min(100, Math.round((defaultWindow / maxWindow) * 10) * 10));
74
- // An exact token selection overrides the 10% stops. It is clamped to the
75
- // selectable range; one that lands on the default window is the default.
76
- const requestedTokens = normalizeContextTokens(targetRoute?.contextTokens);
77
- if (requestedTokens) {
78
- const contextTokens = Math.max(Math.ceil(maxWindow * 0.1), Math.min(maxWindow, requestedTokens));
79
- if (contextTokens === defaultWindow) {
80
- return { contextPercent: contextDefaultPercent, contextDefaultPercent, selectedContextWindow: defaultWindow };
81
- }
82
- const contextPercent = Math.max(10, Math.min(100, Math.round((contextTokens / maxWindow) * 10) * 10));
83
- return { contextPercent, contextDefaultPercent, selectedContextWindow: contextTokens, contextTokens };
84
- }
85
- const requested = Number(targetRoute?.contextPercent);
86
- const contextPercent =
87
- Number.isFinite(requested) && requested > 0
88
- ? Math.max(10, Math.min(100, Math.round(requested / 10) * 10))
89
- : contextDefaultPercent;
90
- const selectedContextWindow =
91
- contextPercent === contextDefaultPercent
92
- ? defaultWindow
93
- : Math.max(1, Math.floor((maxWindow * contextPercent) / 100));
94
- return { contextPercent, contextDefaultPercent, selectedContextWindow };
95
- }
37
+ export { resolveRouteContextState } from '../runtime/agent/orchestrator/runtime-core/route-context-state.mjs';
96
38
 
97
39
  /**
98
40
  * Human label for the statusline. A display-only user alias
@@ -9,8 +9,10 @@
9
9
  * it rides the thread's queue as its own `routine` entry (one per turn, never
10
10
  * merged or steered into another), shows as a routine marker row, and selects
11
11
  * skills only from the routine's instructions (`skillSelection`), never from a
12
- * webhook payload. While it runs the thread counts as unattended, so its
13
- * approvals are held for the owner (held-requests.mjs). Resolves with the
12
+ * webhook payload. While it runs the thread counts as an unattended run
13
+ * (held-requests.mjs): the saved routine is the user's grant, so it never asks
14
+ * (outbound actions are allowed within the bot's permissions; payments and
15
+ * permanent deletes are refused, human-gate/run-policy.mjs). Resolves with the
14
16
  * reply once the turn ends; rejects when the turn fails, is cancelled, or the
15
17
  * caller's `signal` aborts (which also aborts the turn once it runs).
16
18
  */
@@ -126,7 +128,7 @@ export async function runRoutineTurn(
126
128
  onCommitted: () => {
127
129
  if (started) return;
128
130
  started = true;
129
- // Nobody watches this turn: its approvals are held for the owner.
131
+ // Nobody watches this turn: it never asks (run-policy.mjs).
130
132
  unmark = markUnattendedRun(homeId, { kind, agentId, name });
131
133
  if (aborted) void abortTurn();
132
134
  },
@@ -13,22 +13,24 @@ import {
13
13
  CODE_GRAPH_TOOL_DEFS,
14
14
  STATUSLINE_SESSION_ROUTES,
15
15
  STANDALONE_ROOT,
16
- STANDALONE_DATA_DIR,
16
+ standaloneDataDir,
17
17
  } from './runtime-paths.mjs';
18
18
 
19
19
  export function prepareStandaloneEnvironment() {
20
20
  process.env.MIXDOG_QUIET_SESSION_LOG ??= '1';
21
21
  const startedAt = performance.now();
22
+ // Resolved per runtime start: this start's data folder, not the first one's.
23
+ const dataDir = standaloneDataDir();
22
24
  ensureStandaloneEnvironment({
23
25
  rootDir: STANDALONE_ROOT,
24
- dataDir: STANDALONE_DATA_DIR,
26
+ dataDir,
25
27
  });
26
28
  // Office journals exist only for cross-process crash recovery, so startup just
27
29
  // prunes expired ones in the background. Surfacing them as session context made
28
30
  // every new session (agents included) re-announce unrelated leftovers for the
29
31
  // full 30-day retention window; recovery stays reachable on demand through
30
32
  // office action=transactions / action=recover.
31
- listOfficeJournals(STANDALONE_DATA_DIR).catch(() => {});
33
+ listOfficeJournals(dataDir).catch(() => {});
32
34
  bootProfile('standalone-env:ready', { ms: (performance.now() - startedAt).toFixed(1) });
33
35
  }
34
36
 
@@ -1,6 +1,6 @@
1
- import { homedir } from 'node:os';
2
- import { dirname, join } from 'node:path';
1
+ import { dirname } from 'node:path';
3
2
  import { fileURLToPath } from 'node:url';
3
+ import { resolvePluginData } from '../runtime/shared/plugin-paths.mjs';
4
4
 
5
5
  export const RUNTIME = '../runtime/agent/orchestrator';
6
6
  export const WEB_SEARCH_RUNTIME = '../runtime/web-search/index.mjs';
@@ -12,10 +12,16 @@ export const STATUSLINE_SESSION_ROUTES = '../vendor/statusline/src/gateway/sessi
12
12
 
13
13
  const SESSION_RUNTIME_DIR = dirname(fileURLToPath(import.meta.url));
14
14
  export const STANDALONE_ROOT = dirname(SESSION_RUNTIME_DIR);
15
- const mixdogHome = process.env.MIXDOG_HOME || join(homedir(), '.mixdog');
16
- export const STANDALONE_DATA_DIR = process.env.MIXDOG_DATA_DIR || join(mixdogHome, 'data');
15
+
16
+ /**
17
+ * The standalone data directory, resolved from the environment on every call.
18
+ * A runtime start (a pristine boundary, a second page session) retargets
19
+ * MIXDOG_DATA_DIR, so a load-time constant would point every later start back
20
+ * at the first run's folder.
21
+ */
22
+ export const standaloneDataDir = resolvePluginData;
17
23
 
18
24
  /** The data directory a runtime writes to: its config module's plugin data dir, else the standalone default. */
19
25
  export function pluginDataDir(cfgMod) {
20
- return cfgMod.getPluginData?.() || STANDALONE_DATA_DIR;
26
+ return cfgMod.getPluginData?.() || standaloneDataDir();
21
27
  }
@@ -36,6 +36,12 @@ export function readRuntimeTunables() {
36
36
  // connected tools fold synchronously; in-flight servers join through the
37
37
  // late-tool announcement path. Operators may opt back into a grace window.
38
38
  mcpTurnGraceMs: envDelayMs('MIXDOG_MCP_TURN_GRACE_MS', 0, { min: 0, max: 2_000 }),
39
+ // A spawned agent's first turn may be its only one, so it waits for the
40
+ // initial connect — but only this long: a slow-but-alive server (a
41
+ // 20 s handshake is seen in the wild) must not hold the first model
42
+ // request for its whole startup budget. Later servers join through the
43
+ // late-tool announcement path on the agent's next turn.
44
+ mcpAgentGraceMs: envDelayMs('MIXDOG_MCP_AGENT_GRACE_MS', 3_000, { min: 0, max: 30_000 }),
39
45
  // Boot-time remote start is deferred past the first frame; see the caller.
40
46
  remoteAutoStartDelayMs: envDelayMs('MIXDOG_REMOTE_AUTOSTART_DELAY_MS', 1_500, { min: 0, max: 60_000 }),
41
47
  providerWarmupEnabled:
@@ -5,6 +5,7 @@ import { cancelBackgroundTask, listBackgroundTasks } from '../../../../runtime/s
5
5
  import { presentErrorText } from '../../../../runtime/shared/err-text.mjs';
6
6
  import { clean } from '../helpers.mjs';
7
7
  import { isLeadPoolAgent } from '../worker-rows.mjs';
8
+ import { isUnnotifiedRestartFailure } from '../worker-index/row-liveness.mjs';
8
9
 
9
10
  export function createCloseAll({ registry, close }) {
10
11
  const { tags, tagAgents, tagCwds } = registry;
@@ -17,11 +18,13 @@ export function createCloseAll({ registry, close }) {
17
18
 
18
19
  // Drop the closed workers' rows: every non-Lead row for an unscoped
19
20
  // teardown, only this owner's (plus the rows just closed) for a scoped one.
21
+ // A row whose owner is still owed its restart failure stays (a process exit
22
+ // closes every worker; the next process tells the owner).
20
23
  function purgeWorkerRows(ownerSessionId, closedSessionIds) {
21
24
  registry.flushWorkerIndexMutations();
22
25
  registry.writeWorkerRows((byKey, tombstonesByKey) => {
23
26
  for (const [key, row] of [...byKey.entries()]) {
24
- if (isLeadPoolAgent(row.agent)) continue;
27
+ if (isLeadPoolAgent(row.agent) || isUnnotifiedRestartFailure(row)) continue;
25
28
  if (
26
29
  ownerSessionId &&
27
30
  clean(row.ownerSessionId) !== ownerSessionId &&
@@ -38,15 +41,20 @@ export function createCloseAll({ registry, close }) {
38
41
  // Lead's workers: the worker index and task registry are shared by every
39
42
  // Lead in the process, and an unscoped sweep wiped sibling Leads' idle
40
43
  // workers mid-window.
44
+ // A process exit (`scope.processExit`) closes the workers but keeps their
45
+ // rows: the next process lists the finished ones for their pane window and
46
+ // settles the ones the exit cut off (restart-notify), exactly as after a
47
+ // crash. Dropping them emptied every sub-agent list after a daemon restart.
41
48
  return async function closeAll(reason = 'cli-agent-close-all', scope = {}) {
42
49
  const ownerSessionId = clean(scope.callerSessionId);
50
+ const keepRows = scope.processExit === true;
43
51
  const context = ownerSessionId ? { callerSessionId: ownerSessionId } : {};
44
52
  registry.refreshTagsFromSessions({ scanSessions: false, context });
45
53
  const closed = [];
46
54
  const failed = [];
47
55
  for (const { tag, session } of registry.agentSessionEntries({ scanSessions: false, context })) {
48
56
  try {
49
- closed.push(await close({ sessionId: session.id }, context));
57
+ closed.push(await close({ sessionId: session.id }, context, { keepRow: keepRows }));
50
58
  } catch (err) {
51
59
  failed.push({ tag, error: presentErrorText(err, { surface: 'agent' }) });
52
60
  }
@@ -57,6 +65,7 @@ export function createCloseAll({ registry, close }) {
57
65
  closed.push({ closed: true, tag: task.tag || null, sessionId: task.sessionId || null, task_id: task.task_id });
58
66
  }
59
67
  if (!ownerSessionId) resetLocalTagState();
68
+ if (keepRows) return { closed, failed };
60
69
  const closedSessionIds = new Set(closed.map((row) => clean(row.sessionId)).filter(Boolean));
61
70
  purgeWorkerRows(ownerSessionId, closedSessionIds);
62
71
  return { closed, failed };
@@ -37,7 +37,9 @@ export function createCloseFlow({ mgr, registry, views }) {
37
37
  }
38
38
  }
39
39
 
40
- async function close(args, context = {}) {
40
+ /** `keepRow`: a process exit closes the worker but leaves its row (and its
41
+ * descendants') for the next process (close-all.mjs). */
42
+ async function close(args, context = {}, { keepRow = false } = {}) {
41
43
  const scopedContext = agentScope(args, context);
42
44
  registry.refreshTagsFromSessions({ scanSessions: registry.wantsSessionScan(args), context: scopedContext });
43
45
  const taskId = taskIdFromArgs(args);
@@ -73,7 +75,7 @@ export function createCloseFlow({ mgr, registry, views }) {
73
75
  if (task?.taskId) cancelBackgroundTask(task.taskId, CLOSE_REASON);
74
76
  // Drop the row only once the cancellation is DURABLY recorded: a failed
75
77
  // close that still forgot the row would leave no cancelled state behind.
76
- if (ok) {
78
+ if (ok && !keepRow) {
77
79
  registry.forgetTerminalSession(tag, sessionId);
78
80
  for (const descendantSessionId of descendantSessionIds) {
79
81
  registry.removeWorkerRow({ sessionId: descendantSessionId });
@@ -70,6 +70,7 @@ export function createSendDispatch({ registry, defaultCwd, sendFlow, spawnFlow,
70
70
  async function send(args, { scopedContext, callerCwd, context, notifyContext }) {
71
71
  try {
72
72
  const prepared = await sendFlow.prepareSend(args, scopedContext);
73
+ await sendFlow.adoptCaller(prepared, notifyContext);
73
74
  return sendFlow.dispatchToExistingSession(prepared, notifyContext);
74
75
  } catch (err) {
75
76
  return await respawnDeadTagSend(err, args, { callerCwd, context, notifyContext });
@@ -51,6 +51,7 @@ export function createSpawnDispatch({
51
51
  }
52
52
  if (liveSessionId && registry.getLiveSession(liveSessionId)) {
53
53
  const prepared = await sendFlow.prepareSend({ ...args, tag: explicitTag }, scopedContext);
54
+ await sendFlow.adoptCaller(prepared, notifyContext);
54
55
  const route = requestedRoute(args);
55
56
  return {
56
57
  reused: sendFlow.dispatchToExistingSession(route ? { ...prepared, route } : prepared, notifyContext, {