mixdog 1.2.3 → 1.2.5

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 (469) hide show
  1. package/package.json +1 -1
  2. package/scripts/gate-local.mjs +6 -4
  3. package/scripts/lib/gate-scheduler.mjs +29 -11
  4. package/scripts/mobile-release.mjs +5 -4
  5. package/scripts/probe-provider-catalog.mjs +31 -0
  6. package/scripts/provider-pingpong-live.mjs +128 -38
  7. package/scripts/session-sweep.mjs +5 -25
  8. package/src/agents/maintainer/AGENT.md +1 -1
  9. package/src/agents/maintainer/agent.json +1 -1
  10. package/src/agents/reviewer/AGENT.md +2 -4
  11. package/src/agents/security/AGENT.md +1 -2
  12. package/src/agents/writer/AGENT.md +2 -3
  13. package/src/defaults/shipped-history.json +7 -1
  14. package/src/defaults/skills/image/SKILL.md +21 -23
  15. package/src/lib/keychain-cjs.cjs +13 -11
  16. package/src/output-styles/common.md +2 -1
  17. package/src/rules/agent/AGENT.md +7 -5
  18. package/src/rules/lead/LEAD.md +13 -13
  19. package/src/rules/shared/45-destructive.md +4 -0
  20. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -0
  21. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +28 -7
  22. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -11
  23. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +101 -23
  24. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +2 -0
  25. package/src/runtime/agent/orchestrator/providers/anthropic-api-key-fetch.mjs +36 -0
  26. package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +1 -1
  27. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -3
  28. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  29. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +26 -44
  30. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +2 -5
  31. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +4 -0
  32. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +41 -12
  33. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +13 -1
  34. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +81 -10
  35. package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +109 -5
  36. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +2 -1
  37. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +107 -37
  38. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +4 -1
  39. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +3 -1
  40. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  41. package/src/runtime/agent/orchestrator/providers/cursor-native-context.mjs +48 -0
  42. package/src/runtime/agent/orchestrator/providers/cursor-wire-exec.mjs +144 -34
  43. package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +28 -4
  44. package/src/runtime/agent/orchestrator/providers/cursor-wire-request.mjs +8 -4
  45. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +41 -11
  46. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-messages.mjs +67 -5
  47. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +14 -4
  48. package/src/runtime/agent/orchestrator/providers/cursor.mjs +14 -1
  49. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +9 -6
  50. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +7 -2
  51. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +26 -4
  52. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +12 -6
  53. package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +14 -6
  54. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +4 -4
  55. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +1 -0
  56. package/src/runtime/agent/orchestrator/providers/gemini-rest-stream/watchdogs.mjs +16 -5
  57. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +6 -1
  58. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +22 -10
  59. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +4 -2
  60. package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +33 -7
  61. package/src/runtime/agent/orchestrator/providers/gemini.mjs +11 -2
  62. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  63. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +9 -2
  64. package/src/runtime/agent/orchestrator/providers/lib/claude-bridge-schema.mjs +9 -5
  65. package/src/runtime/agent/orchestrator/providers/lib/request-compression.mjs +86 -0
  66. package/src/runtime/agent/orchestrator/providers/lib/wire-pairing.mjs +4 -2
  67. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -5
  68. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +7 -1
  69. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +60 -16
  70. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +11 -13
  71. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +30 -19
  72. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
  73. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +5 -0
  74. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +42 -5
  75. package/src/runtime/agent/orchestrator/providers/openai-compat-reasoning-field.mjs +13 -9
  76. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +1 -3
  77. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-parity.fixtures.mjs +18 -7
  78. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +36 -3
  79. package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +51 -3
  80. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +11 -1
  81. package/src/runtime/agent/orchestrator/providers/openai-http-sse-request.mjs +64 -52
  82. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +3 -1
  83. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +2 -1
  84. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +7 -1
  85. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +1 -4
  86. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/tool-calls.mjs +0 -1
  87. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +7 -6
  88. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +13 -2
  89. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +4 -1
  90. package/src/runtime/agent/orchestrator/providers/openrouter.mjs +1 -2
  91. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +5 -4
  92. package/src/runtime/agent/orchestrator/providers/registry.mjs +9 -1
  93. package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +2 -1
  94. package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +11 -4
  95. package/src/runtime/agent/orchestrator/providers/wire-route.mjs +14 -4
  96. package/src/runtime/agent/orchestrator/providers/xai-tool-search.fixtures.mjs +15 -3
  97. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +6 -2
  98. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +8 -6
  99. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +4 -1
  100. package/src/runtime/agent/orchestrator/runtime-core/goal-store.mjs +30 -2
  101. package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +3 -2
  102. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +8 -3
  103. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +3 -1
  104. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge-contract.mjs +6 -0
  105. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +10 -10
  106. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +3 -1
  107. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +46 -8
  108. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +10 -8
  109. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -2
  110. package/src/runtime/agent/orchestrator/session/agent-pool-progress.mjs +24 -0
  111. package/src/runtime/agent/orchestrator/session/cache/tool-result-reuse.mjs +10 -2
  112. package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +2 -3
  113. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +3 -11
  114. package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +6 -22
  115. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +2 -0
  116. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +16 -2
  117. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +28 -0
  118. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
  119. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +0 -6
  120. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +0 -6
  121. package/src/runtime/agent/orchestrator/session/eager-dispatch/admission.mjs +1 -1
  122. package/src/runtime/agent/orchestrator/session/historical-session-closure.mjs +10 -0
  123. package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +2 -5
  124. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +2 -4
  125. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +3 -3
  126. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +0 -27
  127. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +1 -1
  128. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +4 -10
  129. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +2 -9
  130. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +4 -1
  131. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +4 -1
  132. package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +1 -4
  133. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +76 -0
  134. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +5 -1
  135. package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +8 -9
  136. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +6 -0
  137. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +15 -2
  138. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +1 -4
  139. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +3 -4
  140. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +33 -3
  141. package/src/runtime/agent/orchestrator/session/manager/ask-support.mjs +0 -4
  142. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +21 -1
  143. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +38 -13
  144. package/src/runtime/agent/orchestrator/session/manager/ask-turn-failure.mjs +33 -0
  145. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +5 -0
  146. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +19 -1
  147. package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +57 -13
  148. package/src/runtime/agent/orchestrator/session/manager/failed-turn-rewind.mjs +16 -0
  149. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +6 -120
  150. package/src/runtime/agent/orchestrator/session/manager/pending-persist-queue.mjs +1 -1
  151. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +29 -8
  152. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +42 -49
  153. package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +50 -0
  154. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +32 -14
  155. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +1 -1
  156. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +17 -33
  157. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +29 -13
  158. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +3 -3
  159. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-recovery.mjs +45 -0
  160. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -9
  161. package/src/runtime/agent/orchestrator/session/send-instrumentation.mjs +2 -1
  162. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +5 -2
  163. package/src/runtime/agent/orchestrator/session/side-question.mjs +12 -7
  164. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +5 -12
  165. package/src/runtime/agent/orchestrator/session/store/lifecycle-read.mjs +1 -1
  166. package/src/runtime/agent/orchestrator/session/store/listing.mjs +2 -6
  167. package/src/runtime/agent/orchestrator/session/store/live-cache-eviction.mjs +1 -1
  168. package/src/runtime/agent/orchestrator/session/store/liveness-veto.mjs +46 -37
  169. package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +1 -1
  170. package/src/runtime/agent/orchestrator/session/store/load-session.mjs +1 -1
  171. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +5 -11
  172. package/src/runtime/agent/orchestrator/session/store/session-paths.mjs +59 -0
  173. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +1 -1
  174. package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +1 -1
  175. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +27 -32
  176. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +0 -8
  177. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +6 -18
  178. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +33 -106
  179. package/src/runtime/agent/orchestrator/session/store/write-admission.mjs +1 -1
  180. package/src/runtime/agent/orchestrator/session/store-agent-worker-pool.mjs +5 -8
  181. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +160 -212
  182. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +50 -116
  183. package/src/runtime/agent/orchestrator/session/store-summary-locations.mjs +3 -29
  184. package/src/runtime/agent/orchestrator/session/store-summary-reader-catalog-fixture.mjs +1 -1
  185. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +7 -8
  186. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +9 -20
  187. package/src/runtime/agent/orchestrator/session/store.mjs +3 -16
  188. package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +11 -12
  189. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +37 -13
  190. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +15 -4
  191. package/src/runtime/agent/orchestrator/session/tool-schema-rejection.mjs +4 -2
  192. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +72 -15
  193. package/src/runtime/agent/orchestrator/session/turn-failure.mjs +45 -0
  194. package/src/runtime/agent/orchestrator/stall-policy.mjs +50 -0
  195. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +5 -1
  196. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/inline-hoist.mjs +6 -2
  197. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +204 -10
  198. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +160 -39
  199. package/src/runtime/agent/orchestrator/tools/builtin/git-partial-stage.mjs +7 -5
  200. package/src/runtime/agent/orchestrator/tools/builtin/grep-formatting.mjs +14 -5
  201. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-context-expander.mjs +4 -3
  202. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-output.mjs +2 -1
  203. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-path-fanout.mjs +48 -44
  204. package/src/runtime/agent/orchestrator/tools/builtin/lib/search-input-helpers.mjs +10 -3
  205. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +200 -17
  206. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +5 -4
  207. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +6 -1
  208. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +20 -0
  209. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +101 -2
  210. package/src/runtime/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +1 -1
  211. package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +67 -0
  212. package/src/runtime/agent/orchestrator/tools/lib/process-table-fixture.mjs +50 -0
  213. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +303 -63
  214. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +2 -0
  215. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +108 -18
  216. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +77 -11
  217. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +81 -5
  218. package/src/runtime/attachments/store.d.mts +9 -0
  219. package/src/runtime/attachments/store.mjs +80 -9
  220. package/src/runtime/browser-bridge/browser-action-contract.d.mts +0 -1
  221. package/src/runtime/browser-bridge/browser-action-contract.mjs +0 -2
  222. package/src/runtime/computer-bridge/action-schema.mjs +178 -178
  223. package/src/runtime/computer-bridge/linux-readiness.d.mts +0 -2
  224. package/src/runtime/computer-bridge/linux-readiness.mjs +0 -16
  225. package/src/runtime/effort-judge/judge-client.mjs +21 -4
  226. package/src/runtime/effort-judge/judge-worker.mjs +4 -1
  227. package/src/runtime/github/tool.mjs +118 -17
  228. package/src/runtime/media/adapters/codex-image.mjs +140 -21
  229. package/src/runtime/media/catalog.mjs +4 -4
  230. package/src/runtime/media/chatgpt-usage.mjs +55 -0
  231. package/src/runtime/media/defaults.mjs +37 -0
  232. package/src/runtime/media/jobs.mjs +12 -2
  233. package/src/runtime/media/lanes.mjs +12 -0
  234. package/src/runtime/media/tool-defs.mjs +15 -4
  235. package/src/runtime/media/tool.mjs +41 -12
  236. package/src/runtime/media/upstream-error.mjs +72 -6
  237. package/src/runtime/memory/lib/ingest/transcript-rows.mjs +1 -0
  238. package/src/runtime/memory/lib/pg/process.mjs +5 -7
  239. package/src/runtime/memory/lib/pg/supervisor.mjs +4 -1
  240. package/src/runtime/office/authoring/docx-html-runner.mjs +1 -1
  241. package/src/runtime/office/authoring/html-browser.mjs +18 -8
  242. package/src/runtime/office/authoring/pdf-html-runner.mjs +1 -1
  243. package/src/runtime/office/authoring/pptx-html-measure.mjs +1 -1
  244. package/src/runtime/office/authoring/xlsx-html-runner.mjs +1 -1
  245. package/src/runtime/office/core/office-write-ledger.mjs +1 -5
  246. package/src/runtime/office/index.mjs +2 -3
  247. package/src/runtime/office/pdf/pdf-analysis.mjs +0 -68
  248. package/src/runtime/session-search/session-search-ingest.mjs +1 -1
  249. package/src/runtime/shared/agent-reap-state.mjs +13 -0
  250. package/src/runtime/shared/atomic-file.d.mts +1 -1
  251. package/src/runtime/shared/background-tasks.mjs +6 -1
  252. package/src/runtime/shared/bounded-line-diff.mjs +11 -2
  253. package/src/runtime/shared/browser-launch.mjs +15 -0
  254. package/src/runtime/shared/err-text.mjs +42 -4
  255. package/src/runtime/shared/file-lock.mjs +6 -5
  256. package/src/runtime/shared/llm/anthropic-betas.mjs +1 -2
  257. package/src/runtime/shared/llm/provider-model-identities.mjs +1 -0
  258. package/src/runtime/shared/model-route-chain.d.mts +8 -0
  259. package/src/runtime/shared/model-route-chain.mjs +48 -0
  260. package/src/runtime/shared/model-route-fallback.mjs +22 -0
  261. package/src/runtime/shared/pid-liveness.mjs +12 -0
  262. package/src/runtime/shared/provider-api-key.mjs +6 -0
  263. package/src/runtime/shared/provider-timeout-phase.mjs +17 -0
  264. package/src/runtime/shared/quiet-progress.d.mts +35 -0
  265. package/src/runtime/shared/quiet-progress.mjs +101 -0
  266. package/src/runtime/shared/quota-resume-store.mjs +32 -1
  267. package/src/runtime/shared/read-row-numbers.mjs +44 -0
  268. package/src/runtime/shared/remote-intent.mjs +2 -42
  269. package/src/runtime/shared/slow-span.mjs +8 -1
  270. package/src/runtime/shared/statusline/statusline-segments.mjs +24 -2
  271. package/src/runtime/shared/tool-card-model/card-header.mjs +2 -1
  272. package/src/runtime/shared/tool-output-budget.mjs +2 -0
  273. package/src/runtime/shared/tool-result-summary.mjs +16 -2
  274. package/src/runtime/shared/tool-status.mjs +3 -2
  275. package/src/runtime/shared/tool-surface.mjs +2 -3
  276. package/src/runtime/shared/tool-work-units.mjs +22 -15
  277. package/src/runtime/shared/transcript-metadata.mjs +49 -1
  278. package/src/runtime/shared/transcript-turn-keys.mjs +33 -0
  279. package/src/runtime/shared/transcript-writer/transcript-file.mjs +10 -1
  280. package/src/runtime/shared/transcript-writer.mjs +14 -4
  281. package/src/runtime/shared/turn-changes.mjs +2 -1
  282. package/src/runtime/shared/turn-snapshot.mjs +11 -3
  283. package/src/runtime/shared/worktrees/activity-gate.mjs +175 -0
  284. package/src/runtime/shared/worktrees/branch-management.mjs +179 -0
  285. package/src/runtime/shared/worktrees/carry.mjs +6 -4
  286. package/src/runtime/shared/worktrees/contract.mjs +27 -1
  287. package/src/runtime/shared/worktrees/dependencies.mjs +456 -0
  288. package/src/runtime/shared/worktrees/file-recovery.mjs +29 -13
  289. package/src/runtime/shared/worktrees/git-head.mjs +22 -13
  290. package/src/runtime/shared/worktrees/include.mjs +125 -7
  291. package/src/runtime/shared/worktrees/lock.mjs +1 -1
  292. package/src/runtime/shared/worktrees/maintenance.mjs +20 -2
  293. package/src/runtime/shared/worktrees/manager.mjs +378 -32
  294. package/src/runtime/shared/worktrees/owner.mjs +2 -1
  295. package/src/runtime/shared/worktrees/preparation-report.mjs +79 -0
  296. package/src/runtime/shared/worktrees/setup-commands.mjs +232 -0
  297. package/src/runtime/shared/worktrees/workspace-visibility.mjs +17 -0
  298. package/src/runtime/terminal-bridge/client.mjs +14 -10
  299. package/src/session-runtime/boot/apis.mjs +7 -1
  300. package/src/session-runtime/boot/config.mjs +2 -0
  301. package/src/session-runtime/boot/facade.mjs +3 -0
  302. package/src/session-runtime/boot/session.mjs +3 -2
  303. package/src/session-runtime/boot/tools.mjs +3 -0
  304. package/src/session-runtime/conversation-import/service.mjs +1 -1
  305. package/src/session-runtime/goal-facade-api.mjs +2 -2
  306. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +44 -18
  307. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +4 -1
  308. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +22 -8
  309. package/src/session-runtime/internal-tool-executor.mjs +2 -0
  310. package/src/session-runtime/lifecycle/cold-heir.mjs +0 -1
  311. package/src/session-runtime/lifecycle/session-catalog.mjs +14 -16
  312. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +18 -53
  313. package/src/session-runtime/lifecycle/session-switch.mjs +1 -1
  314. package/src/session-runtime/lifecycle/shared.mjs +5 -6
  315. package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -5
  316. package/src/session-runtime/lifecycle/teardown.mjs +7 -16
  317. package/src/session-runtime/media-api.mjs +8 -0
  318. package/src/session-runtime/model-route/route-ownership.mjs +60 -0
  319. package/src/session-runtime/model-route/route-persist.mjs +3 -2
  320. package/src/session-runtime/model-route/set-route.mjs +114 -34
  321. package/src/session-runtime/model-route/web-search-route.mjs +7 -2
  322. package/src/session-runtime/model-route-api.mjs +4 -2
  323. package/src/session-runtime/native-web-search/routes.mjs +37 -24
  324. package/src/session-runtime/native-web-search.mjs +9 -8
  325. package/src/session-runtime/provider-auth/setup.mjs +4 -0
  326. package/src/session-runtime/provider-models/catalog-load.mjs +9 -0
  327. package/src/session-runtime/provider-models/web-search-catalog.mjs +23 -3
  328. package/src/session-runtime/provider-models.mjs +1 -0
  329. package/src/session-runtime/provider-usage.mjs +4 -0
  330. package/src/session-runtime/route-resolution.mjs +3 -1
  331. package/src/session-runtime/runtime-core.mjs +4 -0
  332. package/src/session-runtime/runtime-facade.mjs +6 -0
  333. package/src/session-runtime/services/agent-task-status.mjs +2 -0
  334. package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +0 -3
  335. package/src/session-runtime/services/agent-tool/close-flow.mjs +6 -7
  336. package/src/session-runtime/services/agent-tool/execute-send.mjs +2 -2
  337. package/src/session-runtime/services/agent-tool/execute-spawn.mjs +36 -3
  338. package/src/session-runtime/services/agent-tool/execute.mjs +9 -1
  339. package/src/session-runtime/services/agent-tool/helpers.mjs +30 -7
  340. package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +7 -2
  341. package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +6 -3
  342. package/src/session-runtime/services/agent-tool/lead-worker-index/lead-rows.mjs +3 -4
  343. package/src/session-runtime/services/agent-tool/lead-worker-index/reap-timers.mjs +9 -3
  344. package/src/session-runtime/services/agent-tool/lead-worker-index/row-settlement.mjs +7 -18
  345. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +9 -6
  346. package/src/session-runtime/services/agent-tool/model-select.mjs +82 -0
  347. package/src/session-runtime/services/agent-tool/send-flow.mjs +24 -6
  348. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +3 -1
  349. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +19 -5
  350. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +5 -4
  351. package/src/session-runtime/services/agent-tool/spawn-flow.mjs +2 -1
  352. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +3 -0
  353. package/src/session-runtime/services/agent-tool/tag-registry/tag-lookup.mjs +1 -3
  354. package/src/session-runtime/services/agent-tool/tag-registry/tag-refresh.mjs +1 -3
  355. package/src/session-runtime/services/agent-tool/tag-registry.mjs +3 -9
  356. package/src/session-runtime/services/agent-tool/terminal-reaper/scan-admission.mjs +8 -41
  357. package/src/session-runtime/services/agent-tool/terminal-reaper/stale-rows.mjs +3 -3
  358. package/src/session-runtime/services/agent-tool/terminal-reaper.mjs +6 -23
  359. package/src/session-runtime/services/agent-tool/terminal-trace.mjs +0 -1
  360. package/src/session-runtime/services/agent-tool/tool-def.mjs +7 -0
  361. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +32 -20
  362. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +5 -2
  363. package/src/session-runtime/services/agent-tool/worker-index.mjs +28 -8
  364. package/src/session-runtime/services/agent-tool.mjs +4 -0
  365. package/src/session-runtime/services/agent-watchdog-registry.mjs +2 -0
  366. package/src/session-runtime/services/provider-admin.mjs +77 -0
  367. package/src/session-runtime/session-create/session-options.mjs +7 -5
  368. package/src/session-runtime/session-create/session-start-hook.mjs +2 -1
  369. package/src/session-runtime/session-transcript.mjs +4 -5
  370. package/src/session-runtime/session-turn-api.mjs +3 -9
  371. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -6
  372. package/src/session-runtime/setup-tool/executor.mjs +23 -5
  373. package/src/session-runtime/setup-tool/extended-actions.mjs +6 -6
  374. package/src/session-runtime/setup-tool/settings-contract.mjs +7 -2
  375. package/src/session-runtime/setup-tool/tool-defs.mjs +13 -1
  376. package/src/session-runtime/tool-policy-refresh.mjs +83 -32
  377. package/src/session-runtime/turn/agent-ops.mjs +49 -2
  378. package/src/session-runtime/turn/side-question-ops.mjs +1 -4
  379. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +2 -0
  380. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +4 -0
  381. package/src/session-runtime/turn/turn-run/turn-settle.mjs +8 -2
  382. package/src/session-runtime/turn/turn-run.mjs +1 -36
  383. package/src/session-runtime/usage-stats-api.mjs +4 -1
  384. package/src/session-runtime/workflow-agents-api/agent-route.mjs +16 -1
  385. package/src/session-runtime/workspace/admission-gate.mjs +44 -15
  386. package/src/session-runtime/workspace/project-branches.mjs +28 -36
  387. package/src/session-runtime/workspace/transition-steps.mjs +96 -95
  388. package/src/session-runtime/workspace/transitions.mjs +248 -68
  389. package/src/standalone/channel-transport.mjs +3 -4
  390. package/src/standalone/daemon-desktop-runtime.mjs +32 -1
  391. package/src/standalone/daemon-pg-stop.mjs +1 -3
  392. package/src/standalone/daemon-session-control.mjs +88 -0
  393. package/src/standalone/daemon-stored-session-views.mjs +23 -3
  394. package/src/standalone/daemon.mjs +2 -0
  395. package/src/standalone/desktop-service-registry.mjs +32 -5
  396. package/src/standalone/local-session-runtime.mjs +11 -12
  397. package/src/standalone/session-protocol.mjs +7 -0
  398. package/src/standalone/session-runtime-inline-host.mjs +2 -0
  399. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +91 -49
  400. package/src/standalone/session-service/agent-tree/agent-child.mjs +19 -1
  401. package/src/standalone/session-service/agent-tree/agent-registry.mjs +25 -0
  402. package/src/standalone/session-service/agent-tree/agent-turns.mjs +9 -1
  403. package/src/standalone/session-service/agent-tree.mjs +28 -2
  404. package/src/standalone/session-service/entries/session-loads.mjs +16 -5
  405. package/src/standalone/session-service/entries.mjs +1 -0
  406. package/src/standalone/session-service/projection/transcript-window.mjs +39 -0
  407. package/src/standalone/session-service/quota-resume.mjs +28 -13
  408. package/src/standalone/session-service/session-calls/session-turns.mjs +17 -6
  409. package/src/standalone/session-service/session-calls/session-views.mjs +6 -0
  410. package/src/standalone/session-tool-output.mjs +82 -0
  411. package/src/tui/app/app-view.jsx +2 -1
  412. package/src/tui/app/provider-setup/provider-items.mjs +8 -0
  413. package/src/tui/app/provider-setup-picker.mjs +15 -3
  414. package/src/tui/app/slash-commands.mjs +6 -0
  415. package/src/tui/app/slash-dispatch/session-commands.mjs +49 -0
  416. package/src/tui/components/Spinner.jsx +4 -0
  417. package/src/tui/components/SpinnerByline.jsx +21 -2
  418. package/src/tui/components/status-line/local-line.mjs +10 -2
  419. package/src/tui/dist/index.mjs +529 -270
  420. package/src/tui/session/active-tool-summary.mjs +1 -1
  421. package/src/tui/session/context-state.mjs +0 -3
  422. package/src/tui/session/goal-continuation/continuation-queue.mjs +2 -3
  423. package/src/tui/session/goal-continuation/deadline-delivery.mjs +4 -2
  424. package/src/tui/session/goal-continuation/session-boundary.mjs +21 -0
  425. package/src/tui/session/quota-resume.mjs +140 -61
  426. package/src/tui/session/session-api/commands.mjs +4 -10
  427. package/src/tui/session/session-api/controls.mjs +12 -0
  428. package/src/tui/session/session-api/lifecycle/dispose.mjs +2 -9
  429. package/src/tui/session/session-api/lifecycle/resume.mjs +2 -7
  430. package/src/tui/session/session-api/lifecycle.mjs +13 -11
  431. package/src/tui/session/session-api/media.mjs +2 -0
  432. package/src/tui/session/session-api/routes.mjs +81 -57
  433. package/src/tui/session/session-api/settings.mjs +3 -3
  434. package/src/tui/session/session-api/worktree.mjs +3 -2
  435. package/src/tui/session/session-flow/auto-clear.mjs +1 -3
  436. package/src/tui/session/session-flow/drain.mjs +6 -4
  437. package/src/tui/session/session-flow/queue/entry-shape.mjs +2 -0
  438. package/src/tui/session/session-flow/queue/take-entries.mjs +2 -1
  439. package/src/tui/session/session-flow/queue.mjs +1 -1
  440. package/src/tui/session/tool-approval.mjs +17 -8
  441. package/src/tui/session/transcript-store/streaming-tail.mjs +1 -1
  442. package/src/tui/session/tui-steering-persist.mjs +3 -5
  443. package/src/tui/session/turn-quiet.mjs +53 -0
  444. package/src/tui/session/turn-stream.mjs +5 -1
  445. package/src/tui/session/turn.mjs +38 -31
  446. package/src/tui/session/worktree-setup-notice.mjs +38 -0
  447. package/src/tui/spinner-meta.mjs +10 -0
  448. package/src/tui/spinner-verbs.mjs +3 -0
  449. package/src/ui/statusline.mjs +13 -3
  450. package/src/ui/streaming-markdown-heal.mjs +70 -18
  451. package/src/runtime/agent/orchestrator/providers/model-pricing-rates.mjs +0 -1
  452. package/src/runtime/agent/orchestrator/session/manager/foreign-pending-messages.mjs +0 -346
  453. package/src/runtime/agent/orchestrator/session/manager/session-owner-liveness.mjs +0 -117
  454. package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +0 -302
  455. package/src/session-runtime/services/agent-tool/terminal-reaper/reap-timers.mjs +0 -142
  456. package/src/session-runtime/turn/share-ops.mjs +0 -77
  457. package/src/tui/session/cross-surface-share.mjs +0 -406
  458. package/src/tui/session/live-share/delta.mjs +0 -153
  459. package/src/tui/session/live-share/mirror.mjs +0 -106
  460. package/src/tui/session/live-share/owner.mjs +0 -254
  461. package/src/tui/session/live-share/retry.mjs +0 -54
  462. package/src/tui/session/live-share/viewer/frame-router.mjs +0 -32
  463. package/src/tui/session/live-share/viewer/pipe-client.mjs +0 -108
  464. package/src/tui/session/live-share/viewer/submit-acks.mjs +0 -65
  465. package/src/tui/session/live-share/viewer/sync-waiters.mjs +0 -53
  466. package/src/tui/session/live-share/viewer.mjs +0 -99
  467. package/src/tui/session/live-share/wire.mjs +0 -44
  468. package/src/tui/session/live-share.mjs +0 -154
  469. package/src/tui/session/shared-dir-watch.mjs +0 -69
@@ -4,8 +4,7 @@
4
4
  * definitions, output style and orchestration mode.
5
5
  */
6
6
  import { modelSwitchItem } from '../../../runtime/agent/orchestrator/session/transcript-restore/restore.mjs';
7
- import { selectedModel, switchesSessionModel } from '../../../session-runtime/model-route/model-switch-target.mjs';
8
- import { latestQuotaFailure } from '../quota-resume.mjs';
7
+ import { selectedModel } from '../../../session-runtime/model-route/model-switch-target.mjs';
9
8
  import { createApiHelpers } from './shared.mjs';
10
9
 
11
10
  // Optimistic route preview: the requested provider/model/effort/fast are
@@ -50,6 +49,36 @@ function optimisticRoutePatch(requested = {}, current = {}) {
50
49
  return patch;
51
50
  }
52
51
 
52
+ const SESSION_ONLY_KEYS = new Set(['provider', 'model', 'effort', 'fast', 'autoEffort']);
53
+
54
+ /**
55
+ * A session-only request (an agent child's route) may name just the model, just
56
+ * the tuning, or both. Completed here, inside the serialized route write,
57
+ * against the latest selected route — so a Fast-only change keeps the effort a
58
+ * previous write selected. `unchanged` when it asks for nothing the selected
59
+ * route does not already have.
60
+ */
61
+ function completeSessionOnlyRequest(request, selected) {
62
+ const named = Boolean(request.provider || request.model);
63
+ const target = named ? request : { ...request, provider: selected.provider, model: selected.model };
64
+ const sameModel = target.provider === selected.provider && target.model === selected.model;
65
+ if (!sameModel) return { request: target, unchanged: false };
66
+ const unchanged =
67
+ Object.keys(request).every((key) => SESSION_ONLY_KEYS.has(key)) &&
68
+ (request.effort === undefined || request.effort === selected.effort) &&
69
+ (request.fast === undefined || request.fast === (selected.fast === true)) &&
70
+ (request.autoEffort === undefined || request.autoEffort === (selected.autoEffort !== false));
71
+ return {
72
+ unchanged,
73
+ request: {
74
+ ...(selected.effort ? { effort: selected.effort } : {}),
75
+ fast: selected.fast === true,
76
+ autoEffort: selected.autoEffort !== false,
77
+ ...target,
78
+ },
79
+ };
80
+ }
81
+
53
82
  /** The live divider of an in-place model switch (restored from message meta
54
83
  * after a reload); every switch path publishes it the same way. */
55
84
  export function publishModelSwitch(bag, modelSwitch) {
@@ -76,6 +105,8 @@ export function createSessionRouteApi(bag) {
76
105
  });
77
106
  let routeWrite = null;
78
107
  let routeSequence = 0;
108
+ // Whether this API (not another command) is the one holding commandBusy.
109
+ let routeLocked = false;
79
110
 
80
111
  // A preset selector previews as the preset's model, never as its id.
81
112
  const previewedRequest = (routeOpts) => {
@@ -84,86 +115,79 @@ export function createSessionRouteApi(bag) {
84
115
  return target.provider ? { ...routeOpts, ...target } : routeOpts;
85
116
  };
86
117
 
87
- // Auto-compact on model change (default on): compact on the CURRENT model,
88
- // whose cache and window still hold the history, before the switch. A model
89
- // stopped on its quota cannot run it; the next turn's auto-compaction on the
90
- // new model fits the history instead. A failure never blocks the switch.
91
- const compactBeforeModelSwitch = async () => {
92
- if (runtime.getCompactionSettings?.()?.onModelChange === false) return;
93
- if (latestQuotaFailure(getState().items)) return;
94
- if (typeof bag.compactBeforeModelSwitch !== 'function') return;
95
- set({
96
- commandStatus: { active: true, verb: 'Compacting conversation', startedAt: Date.now(), mode: 'compacting' },
97
- });
98
- flushEmitImmediate();
99
- try {
100
- await bag.compactBeforeModelSwitch();
101
- } catch {
102
- /* the switch goes ahead; next-turn auto-compaction fits the window */
103
- } finally {
104
- set({ commandStatus: null });
105
- }
106
- };
107
-
108
118
  const setRoute = async (opts) => {
109
- if (getState().commandBusy && !routeWrite) return false;
110
119
  const routeOpts = opts && typeof opts === 'object' ? opts : {};
111
- // Every user model choice on this surface (composer picker, /model,
112
- // presets, the TUI picker) switches the conversation itself in place.
113
- const switchesModel = switchesSessionModel(runtime, routeOpts);
114
- // A running turn keeps the route it started on.
115
- if (switchesModel && getState().busy) {
116
- throw new Error('The model cannot change while a response is running. Try again when it finishes.');
117
- }
118
- // Any route change supersedes waiting for the old route's quota reset.
119
- bag.supersedeQuotaResume?.(getState().sessionId);
120
+ // All model pickers save a selection for the conversation's next turn; a
121
+ // running turn keeps the session model it started on. A selection is never
122
+ // refused: another command's lock neither blocks it nor is released by it.
120
123
  const token = ++routeSequence;
121
124
  const previousWrite = routeWrite;
125
+ // The session generation this selection was made under. The write below
126
+ // waits behind earlier ones, so "current" is never read later: the runtime
127
+ // validates this token before every route side effect, and `settle` below
128
+ // is the only place the display is touched once the write has started.
129
+ const originGeneration = runtime.sessionGeneration;
130
+ const owned = () => runtime.sessionGeneration === originGeneration;
122
131
  const preview = optimisticRoutePatch(previewedRequest(routeOpts), getState());
123
132
  // Preview immediately, serialize persistence without dropping a later
124
133
  // click. Earlier completions cannot replace the newest visible choice.
125
- // A model switch previews once its pre-compaction (which republishes the
126
- // current route) is done.
127
- set({ commandBusy: true, ...(switchesModel ? {} : preview || {}) });
134
+ if (!getState().commandBusy) routeLocked = true;
135
+ set({ ...(routeLocked ? { commandBusy: true } : {}), ...(preview || {}) });
128
136
  flushEmitImmediate();
129
137
  const write = (async () => {
130
138
  if (previousWrite) await previousWrite.catch(() => {});
131
139
  const previousRoute = routeState();
140
+ // A stale selection only republishes the route that is actually
141
+ // selected; it never restores or announces its own session's state.
142
+ const settle = ({ failed = false, sync = false, modelSwitch = null } = {}) => {
143
+ const stillOwned = owned();
144
+ if (stillOwned) publishModelSwitch(bag, modelSwitch);
145
+ // A newer selection publishes the final route itself.
146
+ if (token !== routeSequence) return;
147
+ if (stillOwned && failed) {
148
+ if (preview) set({ ...previousRoute, stats: { ...getState().stats } });
149
+ return;
150
+ }
151
+ if (stillOwned && sync) syncContextStats({ allowEstimated: true });
152
+ publishRoute();
153
+ };
132
154
  try {
133
- // Explicit addressing applies the route to this session: an empty
134
- // one is initialized in place. Either way an established
135
- // conversation follows the user's choice in place.
155
+ // Keep the durable session address while deferring route application.
136
156
  const applyToCurrentSession = routeOpts.applyToCurrentSession === true;
137
- const keepMainModel = routeOpts.keepMainModel === true;
138
- const { applyToCurrentSession: _drop, keepMainModel: _keep, ...nextRoute } = routeOpts;
139
- if (switchesModel) {
140
- // The destination must be accepted before the history is compacted.
141
- await runtime.setRoute(nextRoute, { validateOnly: true });
142
- await compactBeforeModelSwitch();
143
- if (token === routeSequence && preview) {
144
- set(preview);
145
- flushEmitImmediate();
157
+ const keepMainModel = routeOpts.keepMainModel !== false;
158
+ const sessionOnly = routeOpts.sessionOnly === true;
159
+ const { applyToCurrentSession: _drop, keepMainModel: _keep, sessionOnly: _scope, ...requested } = routeOpts;
160
+ let nextRoute = requested;
161
+ if (sessionOnly) {
162
+ const completed = completeSessionOnlyRequest(requested, previousRoute);
163
+ if (completed.unchanged) {
164
+ // An earlier failed write skipped its rollback because this one is
165
+ // newer, so the shown route may still be that write's preview.
166
+ settle();
167
+ return previousRoute;
146
168
  }
169
+ nextRoute = completed.request;
147
170
  }
148
171
  const resolvedRoute = await runtime.setRoute(nextRoute, {
149
172
  applyToCurrentSession,
150
173
  keepMainModel,
174
+ ...(sessionOnly ? { sessionOnly } : {}),
175
+ ...(originGeneration !== undefined ? { originGeneration } : {}),
151
176
  switchEstablished: true,
177
+ deferUntilNextTurn: true,
178
+ });
179
+ settle({
180
+ sync: applyToCurrentSession || Boolean(resolvedRoute?.modelSwitch),
181
+ modelSwitch: resolvedRoute?.modelSwitch,
152
182
  });
153
- if (token === routeSequence) {
154
- if (applyToCurrentSession || resolvedRoute?.modelSwitch) syncContextStats({ allowEstimated: true });
155
- publishRoute();
156
- }
157
- publishModelSwitch(bag, resolvedRoute?.modelSwitch);
158
183
  return resolvedRoute;
159
184
  } catch (error) {
160
- if (token === routeSequence && preview) {
161
- set({ ...previousRoute, stats: { ...getState().stats } });
162
- }
185
+ settle({ failed: true });
163
186
  throw error;
164
187
  } finally {
165
188
  if (token === routeSequence) {
166
- set({ commandBusy: false });
189
+ if (routeLocked) set({ commandBusy: false });
190
+ routeLocked = false;
167
191
  // Publish the resolved route (or rollback) before an RPC reply
168
192
  // reads getState(), even while the display-frame clock is busy.
169
193
  flushEmitImmediate();
@@ -29,9 +29,9 @@ export function createSessionSettingsApi(bag) {
29
29
 
30
30
  return {
31
31
  // A model selector (/model, a preset) is the user's model choice like any
32
- // picker selection: the route surface switches an established
33
- // conversation in place (pre-compaction, busy refusal, divider). False
34
- // while another command holds the lock.
32
+ // picker selection: the route surface saves it for the conversation's
33
+ // next turn (a running turn keeps its model) and applies it there. Never
34
+ // refused: another command's lock does not block a selection.
35
35
  setModel: async (m) => (await bag.setSessionRoute({ model: m })) !== false,
36
36
  setEffort: withCommandLock(
37
37
  async (value) => {
@@ -23,8 +23,7 @@
23
23
  * (expectedRevision: WORKTREE_REVISION_CONFLICT when the session moved on)
24
24
  * (WORKTREE_REMOVAL_INCOMPLETE: partly removed; call again to finish)
25
25
  * listWorktrees() → every managed worktree with its retention
26
- * pruneWorktrees() → reconcile report (never a retained worktree)
27
- */
26
+ * pruneWorktrees() → reconcile report (never a retained worktree) */
28
27
  import { worktreeError } from '../../../runtime/shared/worktrees/contract.mjs';
29
28
 
30
29
  /** Session actions of the Lead worktree feature (settings live in settings.mjs). */
@@ -34,6 +33,8 @@ export const SESSION_WORKTREE_ACTIONS = Object.freeze([
34
33
  'selectBranch',
35
34
  'selectProjectBranch',
36
35
  'mergeProjectBranch',
36
+ 'inspectProjectBranch',
37
+ 'manageProjectBranch',
37
38
  'requestWorktree',
38
39
  'mergeWorkspace',
39
40
  'removeWorktree',
@@ -196,10 +196,8 @@ export function createAutoClearOps(bag, { reset, kickDrain }) {
196
196
 
197
197
  // Same eligibility as a user submit, run before a completion wake turn so a
198
198
  // cold cache after a long idle is compacted first. Never throws: a skipped or
199
- // failed auto-clear must still let the wake run. A viewer mirroring another
200
- // surface's owner leaves compaction to that owner.
199
+ // failed auto-clear must still let the wake run.
201
200
  async function autoClearBeforeWake() {
202
- if (bag.liveShareMirroring?.() === true) return false;
203
201
  try {
204
202
  return await autoClearBeforeSubmit({ ignorePending: true });
205
203
  } catch {
@@ -89,6 +89,8 @@ export function turnOptionsForBatch(batch, steering) {
89
89
  return {
90
90
  promptSource: batchPromptSource(batch),
91
91
  retryFailedTurn: batch[0]?.retryFailedTurn === true,
92
+ retryTurnId: batch[0]?.retryTurnId || null,
93
+ retryAttemptId: batch[0]?.retryAttemptId || null,
92
94
  displayText: batch
93
95
  .map((entry) => entry.text)
94
96
  .filter((text) => String(text || '').trim())
@@ -148,7 +150,7 @@ export function createDrainLoop(
148
150
  // matching message under its own lock).
149
151
  function rewindFailedTurnRows(batch) {
150
152
  const entry = batch[0];
151
- if (entry?.retryFailedTurn !== true) return;
153
+ if (entry?.retryFailedTurn !== true || entry.retryTurnId) return;
152
154
  const rewound = rewoundFailedTurnItems(getState().items, entry.text);
153
155
  if (!rewound) return;
154
156
  set({
@@ -217,7 +219,7 @@ export function createDrainLoop(
217
219
  // but so do setModel/newSession/resume/etc). Running a turn concurrently
218
220
  // with a command that swaps or reroutes the live session is a race; the
219
221
  // commandBusy-release hook re-kicks drain once the command finishes.
220
- if (flags.autoClearRunning || getState().commandBusy) {
222
+ if (flags.autoClearRunning || flags.quotaResumePending || getState().commandBusy) {
221
223
  scheduleBlockedDrainRetry();
222
224
  return;
223
225
  }
@@ -253,7 +255,7 @@ export function createDrainLoop(
253
255
  // its pre-compaction, a reset) runs before the next turn starts. The
254
256
  // finally below re-enters drain(), which waits on the same gate, and
255
257
  // the commandBusy release re-kicks the queue.
256
- if (flags.autoClearRunning || getState().commandBusy) return;
258
+ if (flags.autoClearRunning || flags.quotaResumePending || getState().commandBusy) return;
257
259
  // Drain one priority/mode bucket at a time (unified command queue):
258
260
  // prompt steering stays editable and task notifications stay
259
261
  // non-editable but model-visible.
@@ -331,7 +333,7 @@ export function createDrainLoop(
331
333
  submissions.remember(submissionId);
332
334
  pending.push(entry);
333
335
  const needsDurableSteering =
334
- (getState().busy || getState().commandBusy || flags.autoClearRunning) &&
336
+ (getState().busy || getState().commandBusy || flags.autoClearRunning || flags.quotaResumePending) &&
335
337
  steering.shouldMirrorSteeringEntry(entry);
336
338
  const persistence = needsDurableSteering ? appendTuiSteeringPersist(steering.leadSessionId(), entry) : null;
337
339
  if (isQueuedEntryVisible(entry)) {
@@ -49,5 +49,7 @@ export function makeQueueEntry(text, options = {}, nextId) {
49
49
  // Retry of a failed turn: the transcript and the session rewind the
50
50
  // failed turn's unanswered prompt before this entry runs.
51
51
  retryFailedTurn: options.retryFailedTurn === true,
52
+ retryTurnId: options.retryTurnId || null,
53
+ retryAttemptId: options.retryAttemptId || null,
52
54
  };
53
55
  }
@@ -39,10 +39,11 @@ export function createTakeEntriesOps({ pending, pendingNotificationKeys, removeQ
39
39
  (entry.mode || 'prompt') === targetMode &&
40
40
  queuePriorityValue(entry.priority) === bestPriority
41
41
  ) {
42
+ if (batch.length > 0 && entry.retryTurnId) break;
42
43
  batch.push(entry);
43
44
  pending.splice(i, 1);
44
45
  if (entry.mode === 'task-notification' && entry.key) pendingNotificationKeys.delete(entry.key);
45
- if (batch.length >= limit) break;
46
+ if (entry.retryTurnId || batch.length >= limit) break;
46
47
  } else {
47
48
  i += 1;
48
49
  }
@@ -14,7 +14,7 @@ import { createTakeEntriesOps } from './queue/take-entries.mjs';
14
14
 
15
15
  // Submission-id memory for idempotent re-delivery. A prompt can legitimately
16
16
  // reach this queue TWICE when its transport retries across a failed response
17
- // (daemon view retry, live-share spool fallback after a missing ack). The id
17
+ // (daemon view retry after a missing ack). The id
18
18
  // the surface minted is the identity of the message, so a repeat of an id we
19
19
  // already queued is dropped instead of posting the prompt twice.
20
20
  const SUBMISSION_ID_MEMORY = 256;
@@ -12,8 +12,14 @@ export function createToolApproval({ getState, set, nextId, getDisposed, timeout
12
12
 
13
13
  function normalizeToolApprovalRequest(input = {}, id = nextId()) {
14
14
  const now = Date.now();
15
+ // An explicit `timeoutMs: null` means no wait limit (no timer, no expiry);
16
+ // any other missing/invalid value falls back to the session default.
15
17
  const requestTimeoutMs =
16
- Number.isFinite(Number(input?.timeoutMs)) && Number(input.timeoutMs) > 0 ? Number(input.timeoutMs) : timeoutMs;
18
+ input?.timeoutMs === null
19
+ ? null
20
+ : Number.isFinite(Number(input?.timeoutMs)) && Number(input.timeoutMs) > 0
21
+ ? Number(input.timeoutMs)
22
+ : timeoutMs;
17
23
  return {
18
24
  id,
19
25
  name: String(input?.name || input?.tool_name || 'tool'),
@@ -25,14 +31,14 @@ export function createToolApproval({ getState, set, nextId, getDisposed, timeout
25
31
  reason: String(input?.reason || input?.message || 'approval requested by hook').trim(),
26
32
  requestedAt: now,
27
33
  timeoutMs: requestTimeoutMs,
28
- expiresAt: now + requestTimeoutMs,
34
+ expiresAt: requestTimeoutMs === null ? null : now + requestTimeoutMs,
29
35
  };
30
36
  }
31
37
  // Every request expires from ITS OWN requestedAt, queued or active. Arming
32
38
  // the timer only on presentation let a queued request sit past expiresAt
33
39
  // (retaining its promise and surfacing an obsolete approval later).
34
40
  function armToolApprovalTimeout(entry) {
35
- if (!entry || entry.timer) return;
41
+ if (!entry || entry.timer || entry.request?.expiresAt === null) return;
36
42
  const remainingMs = Math.max(0, Number(entry.request?.expiresAt || 0) - Date.now());
37
43
  entry.timer = setTimeout(() => {
38
44
  finishToolApproval(entry.id, false, 'approval timed out');
@@ -42,10 +48,13 @@ export function createToolApproval({ getState, set, nextId, getDisposed, timeout
42
48
  // Settle one entry: drop its timeout, then hand the waiter its answer. The
43
49
  // resolve stays guarded — a waiter whose caller already went away must never
44
50
  // take the rest of the queue down with it.
45
- function settleToolApproval(entry, approved, reason, answers) {
51
+ function settleToolApproval(entry, approved, reason, answers, reply) {
46
52
  if (entry?.timer) clearTimeout(entry.timer);
53
+ const result = { approved, reason };
54
+ if (answers !== undefined) result.answers = answers;
55
+ if (reply !== undefined) result.reply = reply;
47
56
  try {
48
- entry.resolve(answers === undefined ? { approved, reason } : { approved, reason, answers });
57
+ entry.resolve(result);
49
58
  } catch {}
50
59
  }
51
60
  function presentNextToolApproval() {
@@ -59,7 +68,7 @@ export function createToolApproval({ getState, set, nextId, getDisposed, timeout
59
68
  armToolApprovalTimeout(entry);
60
69
  set({ toolApproval: entry.request });
61
70
  }
62
- function finishToolApproval(id, approved, reason = '', device = '', answers = undefined) {
71
+ function finishToolApproval(id, approved, reason = '', device = '', answers = undefined, reply = undefined) {
63
72
  const targetId = String(id || '');
64
73
  if (activeToolApproval && activeToolApproval.id === targetId) {
65
74
  const entry = activeToolApproval;
@@ -70,14 +79,14 @@ export function createToolApproval({ getState, set, nextId, getDisposed, timeout
70
79
  ? { id: targetId, approved: approved === true, device: String(device), at: Date.now() }
71
80
  : null;
72
81
  set({ toolApproval: null, toolApprovalResult });
73
- settleToolApproval(entry, approved === true, String(reason || ''), answers);
82
+ settleToolApproval(entry, approved === true, String(reason || ''), answers, reply);
74
83
  presentNextToolApproval();
75
84
  return true;
76
85
  }
77
86
  const index = toolApprovalQueue.findIndex((entry) => entry.id === targetId);
78
87
  if (index >= 0) {
79
88
  const [entry] = toolApprovalQueue.splice(index, 1);
80
- settleToolApproval(entry, approved === true, String(reason || ''), answers);
89
+ settleToolApproval(entry, approved === true, String(reason || ''), answers, reply);
81
90
  return true;
82
91
  }
83
92
  return false;
@@ -4,7 +4,7 @@
4
4
  * that lets a host prove a longer text is an append of the same stream.
5
5
  */
6
6
  // Non-enumerable so the marker never enters persisted transcript items,
7
- // live-share JSON, or renderer snapshots. The desktop host reads the shared
7
+ // wire JSON, or renderer snapshots. The desktop host reads the shared
8
8
  // Symbol.for key before cloning and uses it to prove that a growing text is
9
9
  // an append, avoiding an O(total text) startsWith check on every frame.
10
10
  const streamingTailTextEpochKey = Symbol.for('mixdog.streaming-tail-text-epoch');
@@ -99,11 +99,9 @@ function normalizePendingStore(raw) {
99
99
  // FOREIGN bucket. This file is SHARED with the runtime/manager pending
100
100
  // -message spool, and every TUI write passes the whole store through
101
101
  // here. Flattening those rows to plain strings destroyed id,
102
- // enqueuedAt, options, structured content, the notification markers —
103
- // and the handoffAt/handoffPid parking stamp that is the only reason an
104
- // accepted-but-undelivered user message survives an owner crash. Rows
105
- // this process does not own are copied through UNCHANGED (array copy
106
- // only, so nothing here can alias-mutate them either).
102
+ // enqueuedAt, options, structured content and the notification markers.
103
+ // Rows this process does not own are copied through UNCHANGED (array
104
+ // copy only, so nothing here can alias-mutate them either).
107
105
  out.sessions[sid] = value.slice();
108
106
  const foreignTouched = Number(touchedRaw[sid]);
109
107
  // Carry an existing stamp only: inventing one would reset the runtime's
@@ -0,0 +1,53 @@
1
+ // The lead turn's quiet-time cue: polls the runtime's turn liveness while the
2
+ // turn runs and publishes `spinner.quiet` ({ since, wait, tool?, warnMs })
3
+ // once the turn has gone QUIET_PROGRESS_CUE_MS without counted progress, and
4
+ // clears it when progress resumes. The value is frozen for a whole silence, so
5
+ // a silence costs two publications, never one per tick. Display only.
6
+ import { displayProgressStage, quietProgress, lastProgressAt } from '../../runtime/shared/quiet-progress.mjs';
7
+ import { resolveAgentStallThresholds } from '../../runtime/agent/orchestrator/stall-policy.mjs';
8
+
9
+ const QUIET_POLL_MS = 1_000;
10
+
11
+ /** The lead's stall-watchdog warn point, in ms. */
12
+ export function leadStallWarnMs() {
13
+ return resolveAgentStallThresholds('lead').warn * 1000;
14
+ }
15
+
16
+ function sameQuiet(left, right) {
17
+ if (!left || !right) return !left && !right;
18
+ return left.since === right.since && left.wait === right.wait && left.tool === right.tool;
19
+ }
20
+
21
+ /** The quiet state a liveness snapshot implies at `now` (null: not quiet). */
22
+ export function turnQuietState(liveness, now, warnMs) {
23
+ if (!liveness || typeof liveness !== 'object' || typeof liveness.then === 'function') return null;
24
+ const quiet = quietProgress({
25
+ progressAt: lastProgressAt(liveness),
26
+ stage: displayProgressStage(liveness),
27
+ tool: liveness.currentTool,
28
+ now,
29
+ });
30
+ return quiet ? { ...quiet, warnMs } : null;
31
+ }
32
+
33
+ export function startQuietWatch({ runtime, getState, set, isCurrentTurn, warnMs = leadStallWarnMs() }) {
34
+ const timer = setInterval(() => {
35
+ if (!isCurrentTurn()) {
36
+ clearInterval(timer);
37
+ return;
38
+ }
39
+ const spinner = getState().spinner;
40
+ if (!spinner) return;
41
+ let liveness = null;
42
+ try {
43
+ liveness = runtime?.getTurnLiveness?.() ?? null;
44
+ } catch {
45
+ return;
46
+ }
47
+ const next = turnQuietState(liveness, Date.now(), warnMs);
48
+ if (sameQuiet(spinner.quiet ?? null, next)) return;
49
+ set({ spinner: { ...spinner, quiet: next } });
50
+ }, QUIET_POLL_MS);
51
+ timer.unref?.();
52
+ return () => clearInterval(timer);
53
+ }
@@ -6,6 +6,7 @@
6
6
  // scalars); every function here takes it as its first argument.
7
7
  import { TUI_FRAME_MS, yieldToRenderer } from './render-timing.mjs';
8
8
  import { retryStatusLine } from '../../runtime/shared/err-text.mjs';
9
+ import { MODEL_THINKING_STAGE } from '../../runtime/shared/quiet-progress.mjs';
9
10
  import { newTranscriptMessageId } from '../../runtime/shared/transcript-metadata.mjs';
10
11
 
11
12
  // Share Ink's exact 120fps cadence with the frame-batched store. A separate
@@ -590,9 +591,12 @@ export async function applyStageChange(stream, stage, detail = null) {
590
591
  await yieldToRenderer();
591
592
  return;
592
593
  }
593
- if (value === 'requesting' || value === 'streaming') stream.compactingActive = false;
594
+ if (value === 'requesting' || value === 'streaming' || value === MODEL_THINKING_STAGE) {
595
+ stream.compactingActive = false;
596
+ }
594
597
  let mode = null;
595
598
  if (value === 'requesting') mode = 'requesting';
599
+ else if (value === MODEL_THINKING_STAGE) mode = 'model-thinking';
596
600
  else if (value === 'streaming') mode = getState().spinner.thinking ? 'thinking' : 'responding';
597
601
  if (!mode || getState().spinner.mode === mode) return;
598
602
  set({ spinner: { ...getState().spinner, mode } });
@@ -7,15 +7,20 @@
7
7
  * turn record below that finalization publishes as the turndone item.
8
8
  */
9
9
  import { applyUsageDelta } from './session-stats.mjs';
10
+ import {
11
+ createTranscriptRouteMetadata,
12
+ transcriptRouteMetadataFields,
13
+ } from '../../runtime/shared/transcript-metadata.mjs';
14
+ import { publishModelSwitch } from './session-api/routes.mjs';
10
15
  import { compactEventLabel } from '../../runtime/shared/compact-event-label.mjs';
11
16
  import { pickVerb, pickDoneVerb, compactEventDetail } from './labels.mjs';
12
- import { toolErrorDisplay } from './tool-result-text.mjs';
13
- import { errText, isCancelLikeError } from '../../runtime/shared/err-text.mjs';
17
+ import { isCancelLikeError } from '../../runtime/shared/err-text.mjs';
14
18
  import { preserveGoalStateAfterTurn } from './goal-turn-state.mjs';
15
19
  export { preserveGoalStateAfterTurn } from './goal-turn-state.mjs';
16
20
  export { transcriptToolCallDisplayMode } from './turn-tool-cards.mjs';
17
- import { isRejected429, safeErrorDetails } from '../../runtime/shared/error-presentation.mjs';
21
+ import { isRejected429 } from '../../runtime/shared/error-presentation.mjs';
18
22
  import { QUOTA_KINDS, providerErrorKind } from '../../runtime/agent/orchestrator/providers/provider-error-kind.mjs';
23
+ import { turnFailureRecord } from '../../runtime/agent/orchestrator/session/turn-failure.mjs';
19
24
  import { registerTaskWaitSteeringCheck } from '../../runtime/agent/orchestrator/session/task-wait-control.mjs';
20
25
  import { promptDisplayText, STEERING_SUPPRESSED_DISPLAY } from './queue-helpers.mjs';
21
26
  import { yieldToRenderer } from './render-timing.mjs';
@@ -47,6 +52,7 @@ import {
47
52
  collectClosingCards,
48
53
  startLiveTail,
49
54
  } from './turn-tool-cards.mjs';
55
+ import { startQuietWatch } from './turn-quiet.mjs';
50
56
 
51
57
  // A usage limit is exactly a spent quota, a plan-gated feature or a billing
52
58
  // refusal; overloads, rate limits and server faults recover by waiting.
@@ -94,6 +100,13 @@ function streamingTailStore({ updateStreamingTail, settleStreamingTail, clearStr
94
100
 
95
101
  // The turn record: identity, transcript metadata, and the outcome fields
96
102
  // finalization publishes.
103
+ export function applyTurnRouteMetadata(ctx, session) {
104
+ const metadata = transcriptRouteMetadataFields(createTranscriptRouteMetadata(session));
105
+ Object.assign(ctx.turn.transcriptMeta, metadata);
106
+ Object.assign(ctx.turn.routeMeta, metadata);
107
+ for (const id of ctx.turn.submittedIds) ctx.patchItem(id, metadata);
108
+ }
109
+
97
110
  function beginTurn({ flags, getState, transcriptRouteMetadata }, options) {
98
111
  const index = getState().stats.turns || 0;
99
112
  const startedAt = Date.now();
@@ -102,6 +115,7 @@ function beginTurn({ flags, getState, transcriptRouteMetadata }, options) {
102
115
  ...(flags.pendingTranscriptMeta || transcriptRouteMetadata?.(startedAt) || { at: startedAt }),
103
116
  ...(options.transcriptMeta && typeof options.transcriptMeta === 'object' ? options.transcriptMeta : {}),
104
117
  completionVerb,
118
+ attemptId: globalThis.crypto.randomUUID(),
105
119
  };
106
120
  flags.pendingTranscriptMeta = null;
107
121
  // The prompt's own time and stable id stay on the prompt row.
@@ -457,34 +471,13 @@ export function failTurn(ctx, error) {
457
471
  }
458
472
  turn.failed = true;
459
473
  cards.aggregates.finalizeToolHeaders();
460
- // Presentation only: legacy untyped failure text may still name a usage limit.
461
- const errorKind = providerErrorKind(error, { legacyQuotaWording: true });
474
+ const record = turnFailureRecord(error, { provider: turn.routeMeta?.provider, model: turn.routeMeta?.model });
462
475
  // The UI reads this descriptor instead of re-parsing the error text.
463
- turn.errorKind = {
464
- kind: errorKind.kind,
465
- code: errorKind.code,
466
- status: errorKind.status,
467
- retryAfterMs: errorKind.retryAfterMs,
468
- resetAt: errorKind.resetAt,
469
- };
470
- turn.usageLimited = isUsageLimitError(error, errorKind);
471
- const exhausted = error?.quotaExhausted;
472
- // Quota metadata: the pool's validated exhaustion, or a spent quota whose
473
- // provider named the real reset time.
474
- const resetKnown = errorKind.kind === 'quota_exhausted' && Number.isFinite(errorKind.resetAt);
475
- if ((exhausted && typeof exhausted === 'object') || resetKnown) {
476
- const source = exhausted && typeof exhausted === 'object' ? exhausted : {};
477
- let resetAt = null;
478
- if (Number.isFinite(source.resetAt)) resetAt = source.resetAt;
479
- else if (Number.isFinite(errorKind.resetAt)) resetAt = errorKind.resetAt;
480
- turn.quota = {
481
- provider: String(source.provider || turn.routeMeta?.provider || ''),
482
- model: String(source.model || turn.routeMeta?.model || ''),
483
- resetAt,
484
- };
485
- }
486
- turn.failureDetail = toolErrorDisplay(error, 'turn').replace(/^Error:\s*/i, '');
487
- turn.failureDiagnostic = safeErrorDetails(errText(error));
476
+ turn.errorKind = record.errorKind;
477
+ turn.usageLimited = isUsageLimitError(error, record.providerKind);
478
+ if (record.quota) turn.quota = record.quota;
479
+ turn.failureDetail = record.detail;
480
+ turn.failureDiagnostic = record.errorDetails;
488
481
  pushNotice(turn.failureDetail, 'error', { owner: 'transcript' });
489
482
  }
490
483
 
@@ -683,7 +676,12 @@ export function createRunTurn(bag) {
683
676
  routeMeta: turn.routeMeta,
684
677
  sealToolBlock: cards.aggregates.clearAggregateContinuation,
685
678
  });
686
- const stopLiveTail = startLiveTail(cards);
679
+ const stopToolTail = startLiveTail(cards);
680
+ const stopQuietWatch = startQuietWatch({ runtime, getState, set, isCurrentTurn });
681
+ const stopLiveTail = () => {
682
+ stopToolTail();
683
+ stopQuietWatch();
684
+ };
687
685
  const ctx = {
688
686
  ...bag,
689
687
  bag,
@@ -703,11 +701,20 @@ export function createRunTurn(bag) {
703
701
  submittedAt: options.submittedAt,
704
702
  promptSource: options.promptSource,
705
703
  retryFailedTurn: options.retryFailedTurn === true,
704
+ retryTurnId: options.retryTurnId,
705
+ retryAttemptId: options.retryAttemptId,
706
706
  transcriptMeta: turn.transcriptMeta,
707
707
  context: options.context || null,
708
708
  ...turnHandlers(ctx),
709
709
  ...toolHandlers(ctx),
710
710
  ...streamHandlers(ctx),
711
+ onRouteApplied: (session) => {
712
+ if (isCurrentTurn()) applyTurnRouteMetadata(ctx, session);
713
+ },
714
+ onModelSwitch: (boundary) => {
715
+ if (!isCurrentTurn()) return;
716
+ publishModelSwitch(bag, boundary);
717
+ },
711
718
  });
712
719
  if (!isCurrentTurn()) turn.cancelled = true;
713
720
  else completeTurn(ctx, result, session);