mixdog 1.3.1 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (468) hide show
  1. package/README.md +8 -4
  2. package/package.json +8 -8
  3. package/scripts/native-release-status.mjs +238 -0
  4. package/scripts/verify-runtime-dependency-versions.mjs +158 -0
  5. package/src/agents/reviewer/AGENT.md +5 -7
  6. package/src/agents/reviewer/agent.json +1 -1
  7. package/src/defaults/shipped-history.json +9 -1
  8. package/src/defaults/skills/browser-use/SKILL.md +6 -3
  9. package/src/defaults/skills/local-provider/SKILL.md +33 -6
  10. package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
  11. package/src/defaults/skills/setup/SKILL.md +23 -1
  12. package/src/defaults/skills/setup/references/actions.md +50 -3
  13. package/src/defaults/skills/setup/references/surfaces.md +23 -3
  14. package/src/defaults/skills/setup-guide/SKILL.md +7 -3
  15. package/src/defaults/skills/visualize/SKILL.md +9 -6
  16. package/src/defaults/skills/visualize/references/craft.md +8 -1
  17. package/src/help.mjs +1 -1
  18. package/src/lib/keychain-cjs.cjs +4 -1
  19. package/src/lib/rules-builder.cjs +14 -7
  20. package/src/rules/agent-mode/AGENT_MODE.md +8 -5
  21. package/src/rules/agent-mode/MIXY.md +6 -8
  22. package/src/rules/lead/LEAD.md +7 -10
  23. package/src/rules/shared/10-tool-workflow.md +4 -0
  24. package/src/rules/shared/20-research.md +2 -0
  25. package/src/rules/shared/45-destructive.md +5 -1
  26. package/src/rules/shared/60-verification.md +6 -4
  27. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
  28. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
  29. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
  30. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
  31. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
  32. package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
  33. package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
  34. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
  35. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  36. package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
  37. package/src/runtime/agent/orchestrator/config.mjs +11 -0
  38. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +9 -2
  39. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -5
  40. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
  41. package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
  42. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +84 -32
  43. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +38 -11
  44. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +15 -3
  45. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
  46. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  47. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +84 -42
  48. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
  49. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +14 -1
  50. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
  51. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +27 -8
  52. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  53. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
  54. package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
  55. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +14 -1
  56. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
  57. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  58. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
  59. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +16 -0
  60. package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
  61. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -1
  62. package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +3 -1
  63. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +1 -0
  64. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +5 -5
  65. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +9 -5
  66. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +9 -1
  67. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +2 -1
  68. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +30 -3
  69. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +1 -1
  70. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +3 -1
  71. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +61 -8
  72. package/src/runtime/agent/orchestrator/providers/registry.mjs +19 -0
  73. package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +13 -1
  74. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
  75. package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
  76. package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
  77. package/src/runtime/agent/orchestrator/runtime-core/goal-runtime.mjs +5 -0
  78. package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
  79. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
  80. package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +10 -1
  81. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +4 -6
  82. package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
  83. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
  84. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +52 -6
  85. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +35 -13
  86. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +7 -6
  87. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +1 -1
  88. package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
  89. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +8 -3
  90. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -1
  91. package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
  92. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
  93. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
  94. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -2
  95. package/src/runtime/agent/orchestrator/session/agent-room.mjs +196 -45
  96. package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +80 -0
  97. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  98. package/src/runtime/agent/orchestrator/session/compact/continuation.mjs +15 -6
  99. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +45 -19
  100. package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
  101. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +44 -0
  102. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +185 -37
  103. package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
  104. package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
  105. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
  106. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +6 -2
  107. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +4 -1
  108. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
  109. package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
  110. package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +240 -0
  111. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +102 -35
  112. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
  113. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +19 -9
  114. package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
  115. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
  116. package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
  117. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
  118. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +199 -109
  119. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +5 -0
  120. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +2 -0
  121. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/empty-nudge.mjs +1 -1
  122. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +62 -13
  123. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +22 -6
  124. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
  125. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +5 -5
  126. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -0
  127. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
  128. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +33 -8
  129. package/src/runtime/agent/orchestrator/session/loop/tool-exec/call-prep.mjs +5 -0
  130. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +27 -0
  131. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
  132. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
  133. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
  134. package/src/runtime/agent/orchestrator/session/manager/agent-listing-notice.mjs +102 -0
  135. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +2 -0
  136. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +13 -1
  137. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
  138. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +27 -14
  139. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +14 -0
  140. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  141. package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +1 -0
  142. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
  143. package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +32 -1
  144. package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +115 -0
  145. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
  146. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +6 -0
  147. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
  148. package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
  149. package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
  150. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +36 -0
  151. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +40 -1
  152. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
  153. package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +20 -0
  154. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +28 -9
  155. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +55 -27
  156. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +4 -9
  157. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +14 -1
  158. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +7 -3
  159. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
  160. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
  161. package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
  162. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +52 -11
  163. package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
  164. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +18 -3
  165. package/src/runtime/agent/orchestrator/session/routine-result.mjs +126 -12
  166. package/src/runtime/agent/orchestrator/session/runtime-user-context.mjs +3 -1
  167. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +16 -3
  168. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
  169. package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
  170. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
  171. package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
  172. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
  173. package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +5 -4
  174. package/src/runtime/agent/orchestrator/session/store/sweep/sub-agent-retention.mjs +140 -0
  175. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +5 -0
  176. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +15 -5
  177. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +49 -1
  178. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +80 -1
  179. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
  180. package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
  181. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +33 -1
  182. package/src/runtime/agent/orchestrator/session/store-summary-visibility.d.mts +5 -0
  183. package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +37 -0
  184. package/src/runtime/agent/orchestrator/session/store.mjs +1 -0
  185. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
  186. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
  187. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
  188. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +35 -5
  189. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +42 -2
  190. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +47 -14
  191. package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
  192. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +6 -3
  193. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
  194. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
  195. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
  196. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
  197. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +127 -20
  198. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +557 -0
  199. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +10 -12
  200. package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
  201. package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
  202. package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
  203. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
  204. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
  205. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +42 -23
  206. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
  207. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
  208. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
  209. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
  210. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
  211. package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
  212. package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
  213. package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
  214. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
  215. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
  216. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
  217. package/src/runtime/attachments/attachment-reference.mjs +155 -0
  218. package/src/runtime/attachments/legacy-office.mjs +110 -0
  219. package/src/runtime/attachments/limits.mjs +5 -1
  220. package/src/runtime/attachments/office-extract.mjs +3 -6
  221. package/src/runtime/attachments/pdf-extract.mjs +84 -10
  222. package/src/runtime/attachments/prompt-restore.mjs +70 -0
  223. package/src/runtime/attachments/store.mjs +109 -33
  224. package/src/runtime/browser-bridge/client.mjs +73 -20
  225. package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
  226. package/src/runtime/channels/lib/scheduler.mjs +14 -7
  227. package/src/runtime/channels/lib/state-file.mjs +15 -15
  228. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
  229. package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
  230. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
  231. package/src/runtime/channels/lib/worker-main.mjs +3 -2
  232. package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
  233. package/src/runtime/effort-judge/judge-client.mjs +66 -16
  234. package/src/runtime/effort-judge/model-install.mjs +47 -7
  235. package/src/runtime/local-provider/asset-installer.mjs +10 -4
  236. package/src/runtime/local-provider/catalog.mjs +5 -2
  237. package/src/runtime/local-provider/registered-models.mjs +64 -27
  238. package/src/runtime/local-provider/server.mjs +2 -3
  239. package/src/runtime/media/adapters/image-api.mjs +89 -0
  240. package/src/runtime/media/auth.mjs +64 -1
  241. package/src/runtime/media/catalog.mjs +66 -4
  242. package/src/runtime/media/jobs.mjs +4 -0
  243. package/src/runtime/media/lanes.mjs +46 -5
  244. package/src/runtime/media/media-usage.mjs +17 -0
  245. package/src/runtime/media/renditions.mjs +15 -15
  246. package/src/runtime/media/store.mjs +36 -35
  247. package/src/runtime/media/tool.mjs +8 -1
  248. package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +8 -2
  249. package/src/runtime/memory/lib/embedding-model-files.mjs +30 -7
  250. package/src/runtime/memory/lib/ingest/transcript-tail.mjs +16 -7
  251. package/src/runtime/memory/lib/memory-chunk-quality.mjs +14 -2
  252. package/src/runtime/memory/lib/pg/process.mjs +8 -4
  253. package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
  254. package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
  255. package/src/runtime/office/core/journal.mjs +7 -14
  256. package/src/runtime/office/design/library/design-library-core.mjs +4 -16
  257. package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
  258. package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
  259. package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
  260. package/src/runtime/office/pdf/pdf-render.mjs +30 -18
  261. package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
  262. package/src/runtime/office/portable/portable-soffice.mjs +135 -29
  263. package/src/runtime/shared/agent-avatar-sprites.mjs +23 -8
  264. package/src/runtime/shared/agent-avatar.mjs +3 -2
  265. package/src/runtime/shared/agent-kind-migration.mjs +200 -0
  266. package/src/runtime/shared/agent-message-envelope.d.mts +6 -0
  267. package/src/runtime/shared/agent-message-envelope.mjs +44 -0
  268. package/src/runtime/shared/agent-reap-state.d.mts +4 -0
  269. package/src/runtime/shared/agent-room-host.d.mts +31 -0
  270. package/src/runtime/shared/agent-room-host.mjs +58 -0
  271. package/src/runtime/shared/agent-store.mjs +24 -7
  272. package/src/runtime/shared/agent-task-starts.mjs +27 -0
  273. package/src/runtime/shared/atomic-file.d.mts +50 -0
  274. package/src/runtime/shared/atomic-file.mjs +394 -132
  275. package/src/runtime/shared/background-tasks.mjs +7 -4
  276. package/src/runtime/shared/bot-hops.mjs +35 -0
  277. package/src/runtime/shared/bot-split-migration.mjs +1 -1
  278. package/src/runtime/shared/bounded-download.mjs +4 -2
  279. package/src/runtime/shared/config.mjs +39 -9
  280. package/src/runtime/shared/defaults-separation.mjs +6 -2
  281. package/src/runtime/shared/delegated-approval-host.mjs +74 -0
  282. package/src/runtime/shared/err-text.mjs +4 -0
  283. package/src/runtime/shared/error-presentation.mjs +22 -4
  284. package/src/runtime/shared/file-has-data.mjs +28 -0
  285. package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
  286. package/src/runtime/shared/pending-agent-work.mjs +54 -0
  287. package/src/runtime/shared/process-lifecycle.mjs +8 -12
  288. package/src/runtime/shared/projects.mjs +50 -41
  289. package/src/runtime/shared/provider-accounts.mjs +17 -12
  290. package/src/runtime/shared/redact-directories.mjs +18 -0
  291. package/src/runtime/shared/remove-directory.mjs +28 -0
  292. package/src/runtime/shared/routine-trigger.d.mts +6 -1
  293. package/src/runtime/shared/routine-trigger.mjs +14 -6
  294. package/src/runtime/shared/run-child.d.mts +3 -0
  295. package/src/runtime/shared/run-child.mjs +24 -3
  296. package/src/runtime/shared/session-baseline-store.mjs +8 -2
  297. package/src/runtime/shared/session-sidebar-host.d.mts +11 -2
  298. package/src/runtime/shared/session-sidebar-host.mjs +16 -3
  299. package/src/runtime/shared/singleton-owner.mjs +7 -6
  300. package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
  301. package/src/runtime/shared/staged-update.mjs +4 -2
  302. package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
  303. package/src/runtime/shared/tool-execution-contract.mjs +10 -0
  304. package/src/runtime/shared/tool-work-units.mjs +12 -9
  305. package/src/runtime/shared/transcript-metadata.mjs +28 -3
  306. package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
  307. package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
  308. package/src/runtime/shared/turn-snapshot.mjs +277 -124
  309. package/src/runtime/shared/user-cwd.mjs +9 -4
  310. package/src/runtime/shared/user-data-guard.mjs +92 -61
  311. package/src/runtime/shared/utf8-cut.mjs +10 -0
  312. package/src/runtime/shared/worktrees/activity-gate.mjs +251 -61
  313. package/src/runtime/shared/worktrees/contract.mjs +11 -3
  314. package/src/runtime/shared/worktrees/maintenance.mjs +11 -2
  315. package/src/runtime/shared/worktrees/manager.mjs +419 -52
  316. package/src/runtime/shared/worktrees/registry.mjs +29 -27
  317. package/src/runtime/shared/worktrees/tool-host.mjs +39 -0
  318. package/src/runtime/tidy/core-install.mjs +26 -4
  319. package/src/runtime/tidy/install.mjs +91 -23
  320. package/src/runtime/tidy/process.mjs +41 -8
  321. package/src/runtime/tidy/resolve.mjs +10 -7
  322. package/src/runtime/web-search/lib/config.mjs +4 -16
  323. package/src/session-runtime/agent-home-delivery.mjs +542 -204
  324. package/src/session-runtime/agent-profile-changes.mjs +16 -0
  325. package/src/session-runtime/agent-room-delivery.mjs +81 -35
  326. package/src/session-runtime/agent-task-sessions.mjs +811 -179
  327. package/src/session-runtime/agent-tool-routing.mjs +1 -0
  328. package/src/session-runtime/boot/apis.mjs +48 -24
  329. package/src/session-runtime/boot/config.mjs +1 -0
  330. package/src/session-runtime/boot/facade.mjs +16 -7
  331. package/src/session-runtime/boot/infrastructure.mjs +14 -2
  332. package/src/session-runtime/boot/providers.mjs +2 -2
  333. package/src/session-runtime/boot/shared.mjs +12 -6
  334. package/src/session-runtime/boot/tools.mjs +23 -3
  335. package/src/session-runtime/bot-status.mjs +432 -0
  336. package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
  337. package/src/session-runtime/config-lifecycle.mjs +60 -55
  338. package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
  339. package/src/session-runtime/held-requests.mjs +31 -33
  340. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
  341. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
  342. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +24 -26
  343. package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
  344. package/src/session-runtime/lifecycle/teardown.mjs +11 -3
  345. package/src/session-runtime/mcp-glue/connect.mjs +100 -186
  346. package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
  347. package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
  348. package/src/session-runtime/media-api.mjs +5 -2
  349. package/src/session-runtime/model-recency.mjs +1 -0
  350. package/src/session-runtime/model-route/route-tuning.mjs +4 -1
  351. package/src/session-runtime/model-route/set-route.mjs +61 -1
  352. package/src/session-runtime/owned-automation-run.mjs +8 -1
  353. package/src/session-runtime/provider-models/row-hydration.mjs +8 -1
  354. package/src/session-runtime/quick-model-rows/model-meta.mjs +2 -0
  355. package/src/session-runtime/quick-model-rows/provider-rows.mjs +7 -1
  356. package/src/session-runtime/route-resolution.mjs +16 -8
  357. package/src/session-runtime/route-state.mjs +1 -59
  358. package/src/session-runtime/routine-home-turn.mjs +5 -3
  359. package/src/session-runtime/runtime-bootstrap.mjs +5 -3
  360. package/src/session-runtime/runtime-paths.mjs +11 -5
  361. package/src/session-runtime/runtime-review-api.mjs +4 -1
  362. package/src/session-runtime/runtime-tunables.mjs +6 -0
  363. package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
  364. package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
  365. package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
  366. package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
  367. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +34 -19
  368. package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
  369. package/src/session-runtime/services/agent-tool/send-flow.mjs +25 -21
  370. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +7 -0
  371. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
  372. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +8 -5
  373. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
  374. package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
  375. package/src/session-runtime/services/agent-tool.mjs +6 -0
  376. package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
  377. package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
  378. package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
  379. package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
  380. package/src/session-runtime/services/plugin-admin.mjs +26 -26
  381. package/src/session-runtime/services/provider-admin.mjs +26 -6
  382. package/src/session-runtime/services/schedule-access.mjs +33 -4
  383. package/src/session-runtime/services/seeds.mjs +4 -3
  384. package/src/session-runtime/services/usage-dashboard.mjs +1 -1
  385. package/src/session-runtime/services/usage-stats-model.mjs +2 -1
  386. package/src/session-runtime/services/worktree-access.mjs +27 -0
  387. package/src/session-runtime/session-create/materialize.mjs +2 -3
  388. package/src/session-runtime/session-create/session-options.mjs +15 -2
  389. package/src/session-runtime/session-hooks.mjs +9 -17
  390. package/src/session-runtime/settings-builtin-tools-api.mjs +65 -1
  391. package/src/session-runtime/settings-compaction-api.mjs +86 -39
  392. package/src/session-runtime/setup-tool/executor.mjs +12 -2
  393. package/src/session-runtime/setup-tool/settings-contract.mjs +34 -1
  394. package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
  395. package/src/session-runtime/skills-api.mjs +12 -5
  396. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +349 -0
  397. package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
  398. package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
  399. package/src/session-runtime/tool-policy/policy-stage.mjs +183 -0
  400. package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
  401. package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
  402. package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
  403. package/src/session-runtime/tool-policy-refresh.mjs +48 -369
  404. package/src/session-runtime/tool-surface.mjs +10 -4
  405. package/src/session-runtime/turn/session-ops.mjs +8 -0
  406. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +95 -88
  407. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  408. package/src/session-runtime/turn/turn-run.mjs +7 -2
  409. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
  410. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +527 -333
  411. package/src/session-runtime/workflow-agents-api/agent-route.mjs +85 -63
  412. package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
  413. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
  414. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
  415. package/src/session-runtime/workspace/transition-steps.mjs +1 -18
  416. package/src/session-runtime/workspace/transitions.mjs +279 -67
  417. package/src/session-runtime/workspace/workspace-state.mjs +7 -6
  418. package/src/session-runtime/workspace/worktree-tool.mjs +194 -0
  419. package/src/standalone/daemon-agent-control.mjs +4 -1
  420. package/src/standalone/daemon-lag-profiler.mjs +3 -1
  421. package/src/standalone/daemon-session-control.mjs +173 -15
  422. package/src/standalone/daemon-telemetry.mjs +9 -0
  423. package/src/standalone/daemon.mjs +33 -10
  424. package/src/standalone/local-session-runtime.mjs +33 -16
  425. package/src/standalone/session-client.mjs +7 -2
  426. package/src/standalone/session-protocol.mjs +8 -0
  427. package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +2 -0
  428. package/src/standalone/session-service/agent-route-follow.mjs +69 -0
  429. package/src/standalone/session-service/session-calls/session-turns.mjs +33 -4
  430. package/src/standalone/session-service.mjs +21 -0
  431. package/src/standalone/session-state-projection.mjs +19 -9
  432. package/src/standalone/session-tool-output.mjs +36 -9
  433. package/src/standalone/staged-import.mjs +47 -0
  434. package/src/tui/App.jsx +2 -0
  435. package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
  436. package/src/tui/app/message-selector.mjs +15 -2
  437. package/src/tui/app/model-options.mjs +16 -2
  438. package/src/tui/app/model-picker.mjs +2 -1
  439. package/src/tui/app/route-pickers.mjs +3 -1
  440. package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
  441. package/src/tui/app/slash-commands.mjs +1 -1
  442. package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
  443. package/src/tui/dist/index.mjs +617 -369
  444. package/src/tui/paste-attachments.mjs +0 -1
  445. package/src/tui/paste-text-policy.mjs +4 -5
  446. package/src/tui/prompt-history-store.mjs +16 -21
  447. package/src/tui/session/agent-job-feed/execution-delivery.mjs +4 -1
  448. package/src/tui/session/context-state.mjs +4 -0
  449. package/src/tui/session/notice-surface.mjs +3 -0
  450. package/src/tui/session/session-api/intake/abort.mjs +52 -18
  451. package/src/tui/session/session-api/intake/rewind.mjs +39 -4
  452. package/src/tui/session/session-api/media.mjs +2 -1
  453. package/src/tui/session/session-api/routes-agent-definitions.mjs +76 -0
  454. package/src/tui/session/session-api/routes-write.mjs +203 -0
  455. package/src/tui/session/session-api/routes.mjs +23 -247
  456. package/src/tui/session/session-api/settings.mjs +14 -1
  457. package/src/tui/session/session-flow/drain.mjs +28 -3
  458. package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
  459. package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
  460. package/src/tui/session/session-flow/steering.mjs +8 -3
  461. package/src/tui/session/tool-approval.mjs +142 -14
  462. package/src/tui/session/transcript-intake.mjs +2 -0
  463. package/src/tui/session/turn.mjs +23 -14
  464. package/src/tui/transcript-tool-failures.mjs +8 -2
  465. package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -8
  466. package/src/vendor/statusline/src/gateway/session-routes.mjs +6 -7
  467. package/src/rules/agent-mode/ROUTINE.md +0 -6
  468. package/src/session-runtime/room-member-gate.mjs +0 -48
@@ -86,7 +86,6 @@ export {
86
86
  formatPastedTextRef,
87
87
  pastedTextLineCount,
88
88
  PASTE_TOKEN_MIN_CHARS,
89
- PASTE_TOKEN_MIN_LINES,
90
89
  shouldFoldPastedText,
91
90
  } from './paste-text-policy.mjs';
92
91
 
@@ -3,8 +3,9 @@
3
3
  // A big paste is replaced in the prompt by a compact token so the editor does
4
4
  // not choke on thousands of raw characters; the original text lives in the
5
5
  // surface's pasted-text map and is expanded back on submit.
6
- export const PASTE_TOKEN_MIN_LINES = 10;
7
- export const PASTE_TOKEN_MIN_CHARS = 200;
6
+ // Same rule as Claude Code / Codex (LARGE_PASTE_CHAR_THRESHOLD): only a paste
7
+ // longer than this many characters folds; the line count never matters.
8
+ export const PASTE_TOKEN_MIN_CHARS = 1000;
8
9
 
9
10
  // VISIBLE lines: a copy that ends in a newline ("a\nb\n") is two lines, not
10
11
  // three. Counting the empty trailing chunk folded ordinary two-line copies
@@ -18,9 +19,7 @@ export function pastedTextLineCount(text) {
18
19
  }
19
20
 
20
21
  export function shouldFoldPastedText(text) {
21
- const value = String(text ?? '');
22
- if (!value) return false;
23
- return pastedTextLineCount(value) >= PASTE_TOKEN_MIN_LINES || value.length > PASTE_TOKEN_MIN_CHARS;
22
+ return String(text ?? '').length > PASTE_TOKEN_MIN_CHARS;
24
23
  }
25
24
 
26
25
  export function formatPastedTextRef(id, text) {
@@ -3,9 +3,10 @@
3
3
  * Stored under MIXDOG data dir (not the repo working tree).
4
4
  */
5
5
  import { createHash } from 'node:crypto';
6
- import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
7
- import { chmod, mkdir, rename, writeFile } from 'node:fs/promises';
6
+ import { mkdirSync } from 'node:fs';
7
+ import { mkdir } from 'node:fs/promises';
8
8
  import { dirname, join, resolve } from 'node:path';
9
+ import { readJsonStateSync, writeFileAtomicAsync, writeFileAtomicSync } from '../runtime/shared/atomic-file.mjs';
9
10
  import { resolvePluginData } from '../runtime/shared/plugin-paths.mjs';
10
11
 
11
12
  export const PROMPT_HISTORY_LIMIT = 50;
@@ -33,10 +34,18 @@ function historyFilePath(cwd) {
33
34
  }
34
35
 
35
36
  function readEntries(filePath) {
36
- if (!filePath || !existsSync(filePath)) return [];
37
+ if (!filePath) return [];
37
38
  try {
38
- const parsed = JSON.parse(readFileSync(filePath, 'utf8'));
39
- const entries = Array.isArray(parsed?.entries) ? parsed.entries : [];
39
+ // A damaged file (NUL, truncated) is kept aside as *.corrupt-<ts> and the
40
+ // history starts empty; the next flush writes a fresh file.
41
+ const parsed = readJsonStateSync(filePath, {
42
+ mode: 0o600,
43
+ validate: (value) => {
44
+ if (!Array.isArray(value?.entries)) throw new TypeError('prompt history has no entries');
45
+ return value;
46
+ },
47
+ });
48
+ const entries = parsed ? parsed.entries : [];
40
49
  return entries.map((entry) => String(entry || '').trim()).filter((entry) => promptHistoryKey(entry));
41
50
  } catch {
42
51
  return [];
@@ -52,14 +61,7 @@ function writeEntriesSync(filePath, entries) {
52
61
  try {
53
62
  // Prompt text may be sensitive; restrict directory/file to the owning user.
54
63
  mkdirSync(dirname(filePath), { recursive: true, mode: 0o700 });
55
- const tmp = `${filePath}.${process.pid}.tmp`;
56
- writeFileSync(tmp, buildPayload(entries), { encoding: 'utf8', mode: 0o600 });
57
- renameSync(tmp, filePath);
58
- try {
59
- chmodSync(filePath, 0o600);
60
- } catch {
61
- /* best-effort; Windows may ignore mode bits */
62
- }
64
+ writeFileAtomicSync(filePath, buildPayload(entries), { mode: 0o600 });
63
65
  return true;
64
66
  } catch {
65
67
  return false;
@@ -72,14 +74,7 @@ async function writeEntriesAsync(filePath, entries) {
72
74
  if (!filePath) return false;
73
75
  try {
74
76
  await mkdir(dirname(filePath), { recursive: true, mode: 0o700 });
75
- const tmp = `${filePath}.${process.pid}.tmp`;
76
- await writeFile(tmp, buildPayload(entries), { encoding: 'utf8', mode: 0o600 });
77
- await rename(tmp, filePath);
78
- try {
79
- await chmod(filePath, 0o600);
80
- } catch {
81
- /* best-effort; Windows may ignore mode bits */
82
- }
77
+ await writeFileAtomicAsync(filePath, buildPayload(entries), { mode: 0o600 });
83
78
  return true;
84
79
  } catch {
85
80
  return false;
@@ -86,10 +86,13 @@ export function createExecutionDelivery({
86
86
  // active loop can attach them after the next tool batch. The immediate
87
87
  // response card was already pushed above, so the queued twin stays
88
88
  // model-visible but suppresses its drain-time transcript card.
89
- const { execution } = markCompletionEntry(resumeBody, { executionId, meta: event?.meta });
89
+ const { execution, hop } = markCompletionEntry(resumeBody, { executionId, meta: event?.meta });
90
90
  const enqueued = enqueue(resumeBody, {
91
91
  mode: 'task-notification',
92
92
  ...(execution ? { execution } : {}),
93
+ // The launching turn's bot-chain hop: the turn this wakes, or the running
94
+ // turn this joins, resumes at it (bot-hops.mjs).
95
+ ...(hop ? { transcriptMeta: { hop } } : {}),
93
96
  priority: 'next',
94
97
  key: notificationKey || undefined,
95
98
  abortDiscardOnAbort: true,
@@ -77,6 +77,10 @@ export function createContextState({
77
77
  autoClear: autoClearState(),
78
78
  workflow: runtime.workflow || null,
79
79
  orchestrationMode: runtime.orchestrationMode || 'none',
80
+ // A workflow / orchestration mode chosen during a turn: shown above,
81
+ // applied when the next turn starts (tool-policy-refresh.mjs).
82
+ pendingWorkflowSwitch: runtime.session?.pendingWorkflowId !== undefined,
83
+ pendingOrchestrationModeSwitch: runtime.session?.pendingOrchestrationMode !== undefined,
80
84
  // Every `set({ ...routeState() })` (the 2s runtime pulse, model/effort
81
85
  // switches, turn end, resume) republishes the Goal. It must read it
82
86
  // through the goal-continuation mask: the raw record still holds a
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * src/tui/session/notice-surface.mjs - toasts, transcript notices, progress hint.
3
3
  */
4
+ import { roomRowSender } from '../../runtime/agent/orchestrator/session/agent-room.mjs';
4
5
  import { polishNoticeText } from './notice-text.mjs';
5
6
  import { nextId } from './transcript-spill.mjs';
6
7
 
@@ -59,6 +60,8 @@ export function createNoticeSurface({ getState, set, isDisposed, pushItem, repla
59
60
  // A schedule result posted into a home thread (routine-result.mjs).
60
61
  ...(options.routine ? { routine: options.routine } : {}),
61
62
  ...(options.room ? { room: options.room } : {}),
63
+ // A room message another session sent shows its sender, as restored.
64
+ ...(userRow ? roomRowSender(options.room) : {}),
62
65
  // A room user row's files, in the fields a prompt row draws them from;
63
66
  // a task report's or member speech's returned files likewise.
64
67
  ...(Array.isArray(options.attachments) && options.attachments.length ? { attachments: options.attachments } : {}),
@@ -2,12 +2,13 @@
2
2
  // during a turn cancels it and, unless a steering prompt is already queued,
3
3
  // reclaims the in-flight prompt, its history slot and its requeue entries.
4
4
  import { hydratePastedAttachments, hydrateRestorableFileParts } from '../../../../runtime/attachments/store.mjs';
5
+ import { USER_CANCEL_KEEP_PROMPT_REASON } from '../../../../runtime/agent/orchestrator/session/manager/turn-interruption.mjs';
5
6
  import { abortGoalTurn } from '../../goal-turn-state.mjs';
6
7
  import { promptHistoryWithout } from '../../prompt-history.mjs';
7
8
  import { isQueuedEntryEditable } from '../../queue-helpers.mjs';
8
9
 
9
10
  // An attachment whose blob is gone is dropped alone; the prompt is kept.
10
- function hydrateTolerantly(pastedImages, pastedTexts, content = null) {
11
+ export function hydrateTolerantly(pastedImages, pastedTexts, content = null) {
11
12
  let unreadable = 0;
12
13
  const onUnreadable = () => {
13
14
  unreadable += 1;
@@ -56,9 +57,13 @@ export function createAbortAction(bag) {
56
57
 
57
58
  // Pull the interrupted prompt back out of the transcript and history and
58
59
  // put its unsent requeue entries at the front of the queue.
59
- const reclaimInFlightPrompt = (restoreState, { restoreText, requeueEntries, handoff = false }) => {
60
- restoreState.reclaimed = true;
61
- const idSet = new Set((restoreState.submittedIds || []).filter((id) => id != null));
60
+ // `keepPrompt` only requeues: the submitted rows stay in the transcript.
61
+ const reclaimInFlightPrompt = (
62
+ restoreState,
63
+ { restoreText, requeueEntries, handoff = false, keepPrompt = false }
64
+ ) => {
65
+ if (!keepPrompt) restoreState.reclaimed = true;
66
+ const idSet = new Set((keepPrompt ? [] : restoreState.submittedIds || []).filter((id) => id != null));
62
67
  const patch = { spinner: null, thinking: null, lastTurn: null };
63
68
  // Another device cancelled this turn: the prompt travels to the device that
64
69
  // sent it through the state, never through the canceller's reply.
@@ -124,14 +129,26 @@ export function createAbortAction(bag) {
124
129
  const canceller = typeof options?.device === 'string' ? options.device : '';
125
130
  const crossDevice = Boolean(canceller && restoreState?.device && canceller !== restoreState.device);
126
131
  const hasAttachments = Boolean(restoreState?.pastedImages || restoreState?.pastedTexts || restoreState?.content);
127
- const canRestore =
132
+ const wantsRestore =
128
133
  options?.restorePrompt !== false &&
129
134
  restoreState?.restorable &&
130
135
  !hasPendingSteering &&
131
136
  !(crossDevice && hasAttachments);
137
+ // Hydrate BEFORE the turn is aborted or the prompt reclaimed: an attachment
138
+ // read error then cannot strand a prompt that left the transcript with no
139
+ // draft to land in. On failure the prompt is kept (like a Stop that hands
140
+ // nothing back) and the error is surfaced as the notice.
141
+ let restored = null;
142
+ let hydrateFailure = '';
143
+ if (wantsRestore) {
144
+ try {
145
+ restored = hydrateTolerantly(restoreState?.pastedImages, restoreState?.pastedTexts, restoreState?.content);
146
+ } catch (error) {
147
+ hydrateFailure = `Could not restore the attachments (${error?.message || error}); the prompt was kept.`;
148
+ }
149
+ }
150
+ const canRestore = wantsRestore && !hydrateFailure;
132
151
  const restoreText = canRestore ? restoreState.text : '';
133
- const restorePastedImages = canRestore && restoreState?.pastedImages ? restoreState.pastedImages : null;
134
- const restorePastedTexts = canRestore && restoreState?.pastedTexts ? restoreState.pastedTexts : null;
135
152
  // When steering suppresses the restore, the interrupted prompt's pasted
136
153
  // images never get committed (onCommitted won't fire) nor re-installed into
137
154
  // the draft, so hand them back for cleanup to avoid a stale `[Image #id]`
@@ -146,34 +163,51 @@ export function createAbortAction(bag) {
146
163
  (entry) => entry?.abortDiscardOnAbort !== true && entry?.mode !== 'pending-resume'
147
164
  )
148
165
  : [];
149
- const aborted = abortGoalTurn(runtime, flags, hasPendingSteering, reason);
166
+ // A user Stop that hands the prompt back is rewound by the runtime (the
167
+ // prompt returns to the input box); one that does not keeps it in the
168
+ // stored transcript, as it stays in the visible one. A turn that already
169
+ // committed (output produced) needs neither.
170
+ const keepsPrompt = reason === 'user-cancel' && !canRestore && restoreState?.restorable && !restoreState.committed;
171
+ const aborted = abortGoalTurn(
172
+ runtime,
173
+ flags,
174
+ hasPendingSteering,
175
+ keepsPrompt ? USER_CANCEL_KEEP_PROMPT_REASON : reason
176
+ );
150
177
  if (restoreState) {
178
+ // A user Stop consumes this input even when no draft is requested.
179
+ // Explicit draft reclamation also releases it; automatic interruptions
180
+ // such as the watchdog must retain input that has not been committed.
181
+ if (aborted !== false && !restoreState.committed && (reason === 'user-cancel' || restoreText)) {
182
+ restoreState.onAborted?.();
183
+ restoreState.onAborted = null;
184
+ }
151
185
  if (aborted !== false && Array.isArray(restoreState.discardExecutionPendingResumeKeys)) {
152
186
  discardExecutionPendingResume?.(restoreState.discardExecutionPendingResumeKeys);
153
187
  }
154
188
  if ((restoreText || requeueEntries.length > 0) && aborted !== false) {
155
- reclaimInFlightPrompt(restoreState, { restoreText, requeueEntries, handoff: crossDevice });
189
+ reclaimInFlightPrompt(restoreState, {
190
+ restoreText,
191
+ requeueEntries,
192
+ handoff: crossDevice,
193
+ keepPrompt: Boolean(hydrateFailure),
194
+ });
156
195
  }
157
196
  restoreState.restorable = false;
158
197
  restoreState.requeueEntries = [];
159
198
  restoreState.discardExecutionPendingResumeKeys = [];
160
199
  }
161
200
  kickDrainAfterAbort();
162
- const restored = hydrateTolerantly(
163
- restorePastedImages,
164
- restorePastedTexts,
165
- canRestore ? restoreState?.content : null
166
- );
167
201
  // The canceller never receives another device's prompt.
168
202
  const handedOver = crossDevice && Boolean(restoreText) && aborted !== false;
169
203
  return {
170
204
  aborted,
171
205
  restoreText: handedOver ? '' : restoreText,
172
- ...(restored.content ? { content: restored.content } : {}),
173
- ...(restored.notice ? { notice: restored.notice } : {}),
174
- pastedImages: restored.pastedImages,
206
+ ...(restored?.content ? { content: restored.content } : {}),
207
+ ...(restored?.notice || hydrateFailure ? { notice: restored?.notice || hydrateFailure } : {}),
208
+ pastedImages: restored?.pastedImages ?? null,
175
209
  discardPastedImages,
176
- pastedTexts: restored.pastedTexts,
210
+ pastedTexts: restored?.pastedTexts ?? null,
177
211
  discardPastedTexts,
178
212
  restoredSubmissionIds:
179
213
  restoreText && !handedOver ? (restoreState?.submittedIds || []).map(String).filter(Boolean) : [],
@@ -1,13 +1,17 @@
1
1
  // Message selector ("jump back to a previous message"): rewind the
2
2
  // conversation to just before a user prompt and hand its text back for
3
3
  // editing. Idle-only — a live turn must be interrupted first.
4
+ import { restorableAttachmentsFromPrompt } from '../../../../runtime/attachments/prompt-restore.mjs';
4
5
  import { promptHistoryWithout } from '../../prompt-history.mjs';
6
+ import { hydrateTolerantly } from './abort.mjs';
5
7
 
6
8
  export function createRewindAction(bag) {
7
9
  const { runtime, getState, set, flushEmitImmediate, replaceItems, syncContextStats } = bag;
8
10
 
9
11
  return {
10
- rewindToItem: async (itemId) => {
12
+ // `restoreFiles: false` is a composer that cannot hold PDF/Office file
13
+ // parts (the terminal one): a prompt carrying any is refused untouched.
14
+ rewindToItem: async (itemId, { restoreFiles = true } = {}) => {
11
15
  const target = String(itemId ?? '').trim();
12
16
  if (!target) return null;
13
17
  if (getState().busy || getState().commandBusy) return null;
@@ -19,8 +23,31 @@ export function createRewindAction(bag) {
19
23
  if (!text) return null;
20
24
  set({ commandBusy: true });
21
25
  try {
22
- const rewound = await runtime.rewindMessages?.({ text });
23
- if (!rewound) return null;
26
+ // Matched by the row's stable message id (the stored prompt is the
27
+ // expanded text); rows without one fall back to the text.
28
+ const messageId = String(items[index]?.messageId || '');
29
+ const rewindOptions = { text, ...(messageId ? { messageId } : {}) };
30
+ // Resolve the prompt and build the whole restore payload BEFORE any
31
+ // history changes: a refusal or an attachment read error then leaves the
32
+ // model history and the transcript both untouched.
33
+ const preview = await runtime.rewindMessages?.({ ...rewindOptions, dryRun: true });
34
+ if (!preview) return null;
35
+ // The stored prompt's attachment references become the restore payload
36
+ // the abort/queue restores use (pastedTexts / pastedImages / file parts).
37
+ const content = preview.message?.content;
38
+ if (!restoreFiles && Array.isArray(content) && content.some((part) => part?.type === 'file')) {
39
+ throw new Error(
40
+ 'this message has PDF/Office attachments the terminal composer cannot restore; edit it in the desktop app'
41
+ );
42
+ }
43
+ const refs = restorableAttachmentsFromPrompt(content, text);
44
+ const attachments = hydrateTolerantly(refs.pastedImages, refs.pastedTexts, content);
45
+ // Refused when the history changed since the preview.
46
+ const rewound = await runtime.rewindMessages?.({
47
+ ...rewindOptions,
48
+ ...(preview.historyMarker ? { expectedHistoryMarker: preview.historyMarker } : {}),
49
+ });
50
+ if (!rewound) throw new Error('the conversation changed; select the message again');
24
51
  set({
25
52
  items: replaceItems(items.slice(0, index), {
26
53
  preserveSpill: true,
@@ -36,7 +63,15 @@ export function createRewindAction(bag) {
36
63
  syncContextStats({ allowEstimated: true });
37
64
  set({ stats: { ...getState().stats } });
38
65
  flushEmitImmediate?.();
39
- return { text, removed: items.length - index, messages: rewound.removed };
66
+ return {
67
+ text,
68
+ removed: items.length - index,
69
+ messages: rewound.removed,
70
+ ...(attachments.pastedImages ? { pastedImages: attachments.pastedImages } : {}),
71
+ ...(attachments.pastedTexts ? { pastedTexts: attachments.pastedTexts } : {}),
72
+ ...(attachments.content ? { content: attachments.content } : {}),
73
+ ...(attachments.notice ? { notice: attachments.notice } : {}),
74
+ };
40
75
  } finally {
41
76
  set({ commandBusy: false });
42
77
  }
@@ -119,7 +119,8 @@ export function createSessionMediaApi(bag) {
119
119
  // Media studio (image/video generation). Reads stay quiet, and there is no
120
120
  // notice on start either: the Studio surfaces progress on its own pending
121
121
  // tile, and a toast for a user-initiated generation is pure noise.
122
- listMediaLanes: () => runtime.listMediaLanes?.(),
122
+ // `{ budgetMs }` bounds the per-lane catalog wait (Studio, Settings).
123
+ listMediaLanes: (options) => runtime.listMediaLanes?.(options),
123
124
  getMediaDefault: (kind) => runtime.getMediaDefault?.(kind),
124
125
  setMediaDefault: (input) => runtime.setMediaDefault?.(input),
125
126
  getMediaPins: () => runtime.getMediaPins?.(),
@@ -0,0 +1,76 @@
1
+ /**
2
+ * routes-agent-definitions.mjs — the session route surface's passthroughs to
3
+ * the runtime's agent, workflow-pack, connector and lead-identity services.
4
+ */
5
+ export function createAgentDefinitionApi(runtime) {
6
+ return {
7
+ listAgents: () => {
8
+ return runtime.listAgents?.() || [];
9
+ },
10
+ listWorkflows: () => {
11
+ return runtime.listWorkflows?.() || [];
12
+ },
13
+ // Workflow pack editing (desktop Workflows page): direct passthroughs —
14
+ // the runtime owns validation, user-pack persistence, and delete guards.
15
+ getWorkflowPack: (workflowId) => runtime.getWorkflowPack?.(workflowId) ?? null,
16
+ saveWorkflowPack: async (payload) => runtime.saveWorkflowPack?.(payload) ?? null,
17
+ createWorkflow: async (payload) => runtime.createWorkflow?.(payload) ?? null,
18
+ deleteWorkflow: async (workflowId) => runtime.deleteWorkflow?.(workflowId) ?? null,
19
+ getAgentDefinition: (agentId) => runtime.getAgentDefinition?.(agentId) ?? null,
20
+ getAgentAvatarImage: (agentId) => runtime.getAgentAvatarImage?.(agentId) ?? null,
21
+ getAgentConnectors: (agentId) => runtime.getAgentConnectors?.(agentId) ?? [],
22
+ listConnectors: () => runtime.listConnectors?.() ?? [],
23
+ listConnectorTools: (connectorId) => runtime.listConnectorTools?.(connectorId) ?? [],
24
+ // `options.noWiden` is only ever added by the desktop's remote method table: the
25
+ // change is refused if it would give the agent more authority (checked atomically with the write).
26
+ setAgentConnectors: async (agentId, connectors, options) =>
27
+ runtime.setAgentConnectors?.(agentId, connectors, options) ?? null,
28
+ setAgentConnectorEnabled: async (agentId, connectorId, enabled, options) =>
29
+ runtime.setAgentConnectorEnabled?.(agentId, connectorId, enabled, options) ?? [],
30
+ getProjectConnectors: (projectPath) => runtime.getProjectConnectors?.(projectPath) ?? [],
31
+ setProjectConnectorEnabled: async (projectPath, connectorId, enabled, options) =>
32
+ runtime.setProjectConnectorEnabled?.(projectPath, connectorId, enabled, options) ?? [],
33
+ setAgentConnectorToolPermission: async (agentId, connectorId, toolName, permission, options) =>
34
+ runtime.setAgentConnectorToolPermission?.(agentId, connectorId, toolName, permission, options) ?? null,
35
+ getAgentAlwaysAllowedTools: (agentId) => runtime.getAgentAlwaysAllowedTools?.(agentId) ?? [],
36
+ // Per-agent action-kind permissions (ask / allow / block) for the profile.
37
+ getAgentPermissions: async (agentId) =>
38
+ (await import('../../../runtime/agent/orchestrator/session/human-gate/store.mjs')).agentPermissions(agentId),
39
+ setAgentPermission: async (agentId, action, mode, options) =>
40
+ (await import('../../../runtime/agent/orchestrator/session/human-gate/store.mjs')).setAgentPermission(
41
+ agentId,
42
+ action,
43
+ mode,
44
+ options
45
+ ),
46
+ recordAgentApprovalAudit: async (agentId, action, decision) =>
47
+ (await import('../../../runtime/agent/orchestrator/session/human-gate/store.mjs')).recordAgentApprovalAudit(
48
+ agentId,
49
+ action,
50
+ decision
51
+ ),
52
+ getLeadPersona: () => runtime.getLeadPersona?.() ?? '',
53
+ saveLeadPersona: async (text) => runtime.saveLeadPersona?.(text) ?? '',
54
+ getLeadAgentBody: () => runtime.getLeadAgentBody?.() ?? '',
55
+ saveLeadAgentBody: async (text) => runtime.saveLeadAgentBody?.(text) ?? '',
56
+ getLeadStatus: () =>
57
+ runtime.getLeadStatus?.() ?? { created: null, name: '', avatar: null, homeChatted: false, route: null },
58
+ getLeadHomeRoute: () => runtime.getLeadHomeRoute?.() ?? null,
59
+ saveLeadIdentity: async (payload) => runtime.saveLeadIdentity?.(payload) ?? null,
60
+ setLeadCreated: async (created, description) => runtime.setLeadCreated?.(created, description) ?? null,
61
+ saveAgentDefinition: async (payload, options) => runtime.saveAgentDefinition?.(payload, options) ?? null,
62
+ deleteAgentDefinition: async (agentId) => runtime.deleteAgentDefinition?.(agentId) ?? null,
63
+ agentDefinitionPresence: (agentIds) => runtime.agentDefinitionPresence?.(agentIds) ?? null,
64
+ restoreAgentDefinition: async (agentId) => runtime.restoreAgentDefinition?.(agentId) ?? null,
65
+ purgeAgentDefinition: async (agentId) => runtime.purgeAgentDefinition?.(agentId) ?? null,
66
+ archivedAgentIdentities: (agentIds) => runtime.archivedAgentIdentities?.(agentIds) ?? null,
67
+ // Group-room rows posted by the desktop host into a room's log session.
68
+ appendRoomRows: async (sessionId, rows) =>
69
+ (await import('../../../session-runtime/agent-room-delivery.mjs')).appendRoomRows(sessionId, rows),
70
+ // One group-room supervisor step on the Maintainer route.
71
+ decideRoomTurn: async (input) =>
72
+ (await import('../../../runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs')).decideRoomTurn(
73
+ input
74
+ ),
75
+ };
76
+ }
@@ -0,0 +1,203 @@
1
+ /**
2
+ * routes-write.mjs — the serialized, optimistic route write behind the
3
+ * session's setRoute (every model picker, presets, agent child routes).
4
+ */
5
+ import { modelSwitchItem } from '../../../runtime/agent/orchestrator/session/transcript-restore/restore.mjs';
6
+ import { selectedModel } from '../../../session-runtime/model-route/model-switch-target.mjs';
7
+
8
+ // Optimistic route preview: the requested provider/model/effort/fast are
9
+ // published before the runtime write settles. What is NOT previewed about the
10
+ // route (effort options, Fast capability, context window) is provider
11
+ // metadata the runtime resolves while the preview is already on screen.
12
+ const ROUTE_PREVIEW_KEYS = [
13
+ 'provider',
14
+ 'model',
15
+ 'effort',
16
+ 'fast',
17
+ 'autoEffort',
18
+ 'modelParameters',
19
+ 'contextPercent',
20
+ 'contextTokens',
21
+ ];
22
+
23
+ /**
24
+ * The patch that publishes a requested route before the runtime write settles.
25
+ * Returns null when the request carries nothing previewable.
26
+ */
27
+ function optimisticRoutePatch(requested = {}, current = {}) {
28
+ const route = requested && typeof requested === 'object' ? requested : {};
29
+ const has = (key) => Object.hasOwn(route, key);
30
+ const patch = {};
31
+ for (const key of ROUTE_PREVIEW_KEYS) {
32
+ if (has(key)) patch[key] = route[key];
33
+ }
34
+ if (!has('provider') && !has('model')) {
35
+ return Object.keys(patch).length > 0 ? patch : null;
36
+ }
37
+ const sameModel =
38
+ String(patch.provider ?? current.provider ?? '') === String(current.provider ?? '') &&
39
+ String(patch.model ?? current.model ?? '') === String(current.model ?? '');
40
+ // A different model carries none of the previous model's tuning: an omitted
41
+ // key means "this model has no such control", not "keep the old value".
42
+ if (!sameModel) {
43
+ if (!has('effort')) patch.effort = null;
44
+ if (!has('fast')) patch.fast = false;
45
+ if (!has('modelParameters')) patch.modelParameters = {};
46
+ }
47
+ return patch;
48
+ }
49
+
50
+ const SESSION_ONLY_KEYS = new Set(['provider', 'model', 'effort', 'fast', 'autoEffort']);
51
+
52
+ /**
53
+ * A session-only request (an agent child's route) may name just the model, just
54
+ * the tuning, or both. Completed here, inside the serialized route write,
55
+ * against the latest selected route — so a Fast-only change keeps the effort a
56
+ * previous write selected. `unchanged` when it asks for nothing the selected
57
+ * route does not already have.
58
+ */
59
+ function completeSessionOnlyRequest(request, selected) {
60
+ const named = Boolean(request.provider || request.model);
61
+ const target = named ? request : { ...request, provider: selected.provider, model: selected.model };
62
+ const sameModel = target.provider === selected.provider && target.model === selected.model;
63
+ if (!sameModel) return { request: target, unchanged: false };
64
+ const unchanged =
65
+ Object.keys(request).every((key) => SESSION_ONLY_KEYS.has(key)) &&
66
+ (request.effort === undefined || request.effort === selected.effort) &&
67
+ (request.fast === undefined || request.fast === (selected.fast === true)) &&
68
+ (request.autoEffort === undefined || request.autoEffort === (selected.autoEffort !== false));
69
+ return {
70
+ unchanged,
71
+ request: {
72
+ ...(selected.effort ? { effort: selected.effort } : {}),
73
+ fast: selected.fast === true,
74
+ autoEffort: selected.autoEffort !== false,
75
+ ...target,
76
+ },
77
+ };
78
+ }
79
+
80
+ /** The live divider of an in-place model switch (restored from message meta
81
+ * after a reload); every switch path publishes it the same way. */
82
+ export function publishModelSwitch(bag, modelSwitch) {
83
+ if (modelSwitch) bag.pushItem(modelSwitchItem(bag.nextId(), modelSwitch));
84
+ }
85
+
86
+ export function createRouteWrite(
87
+ bag,
88
+ { runtime, getState, set, flushEmitImmediate, routeState, syncContextStats, publishRoute }
89
+ ) {
90
+ let routeWrite = null;
91
+ let routeSequence = 0;
92
+ // Whether this API (not another command) is the one holding commandBusy.
93
+ let routeLocked = false;
94
+
95
+ // A preset selector previews as the preset's model, never as its id.
96
+ const previewedRequest = (routeOpts) => {
97
+ if (routeOpts.provider || !routeOpts.model) return routeOpts;
98
+ const target = selectedModel(runtime, routeOpts, '', '');
99
+ return target.provider ? { ...routeOpts, ...target } : routeOpts;
100
+ };
101
+
102
+ const setRoute = async (opts) => {
103
+ const routeOpts = opts && typeof opts === 'object' ? opts : {};
104
+ // All model pickers save a selection for the conversation's next turn; a
105
+ // running turn keeps the session model it started on. A selection is never
106
+ // refused: another command's lock neither blocks it nor is released by it.
107
+ const token = ++routeSequence;
108
+ const previousWrite = routeWrite;
109
+ // The session generation this selection was made under. The write below
110
+ // waits behind earlier ones, so "current" is never read later: the runtime
111
+ // validates this token before every route side effect, and `settle` below
112
+ // is the only place the display is touched once the write has started.
113
+ const originGeneration = runtime.sessionGeneration;
114
+ const owned = () => runtime.sessionGeneration === originGeneration;
115
+ // A conditional fill (only when the route is still empty) may not apply, so
116
+ // it never previews: the runtime's answer is what gets published.
117
+ const onlyIfEmpty = routeOpts.onlyIfEmpty === true;
118
+ const preview = onlyIfEmpty ? null : optimisticRoutePatch(previewedRequest(routeOpts), getState());
119
+ // Preview immediately, serialize persistence without dropping a later
120
+ // click. Earlier completions cannot replace the newest visible choice.
121
+ if (!getState().commandBusy) routeLocked = true;
122
+ set({ ...(routeLocked ? { commandBusy: true } : {}), ...(preview || {}) });
123
+ flushEmitImmediate();
124
+ const write = (async () => {
125
+ if (previousWrite) await previousWrite.catch(() => {});
126
+ const previousRoute = routeState();
127
+ // A stale selection only republishes the route that is actually
128
+ // selected; it never restores or announces its own session's state.
129
+ const settle = ({ failed = false, sync = false, modelSwitch = null } = {}) => {
130
+ const stillOwned = owned();
131
+ if (stillOwned) publishModelSwitch(bag, modelSwitch);
132
+ // A newer selection publishes the final route itself.
133
+ if (token !== routeSequence) return;
134
+ if (stillOwned && failed) {
135
+ if (preview) set({ ...previousRoute, stats: { ...getState().stats } });
136
+ return;
137
+ }
138
+ if (stillOwned && sync) syncContextStats({ allowEstimated: true });
139
+ publishRoute();
140
+ };
141
+ try {
142
+ // Keep the durable session address while deferring route application.
143
+ const applyToCurrentSession = routeOpts.applyToCurrentSession === true;
144
+ const keepMainModel = routeOpts.keepMainModel !== false;
145
+ const sessionOnly = routeOpts.sessionOnly === true;
146
+ const {
147
+ applyToCurrentSession: _drop,
148
+ keepMainModel: _keep,
149
+ sessionOnly: _scope,
150
+ onlyIfEmpty: _fill,
151
+ ...requested
152
+ } = routeOpts;
153
+ let nextRoute = requested;
154
+ if (sessionOnly) {
155
+ const completed = completeSessionOnlyRequest(requested, previousRoute);
156
+ if (completed.unchanged) {
157
+ // An earlier failed write skipped its rollback because this one is
158
+ // newer, so the shown route may still be that write's preview.
159
+ settle();
160
+ return previousRoute;
161
+ }
162
+ nextRoute = completed.request;
163
+ }
164
+ const resolvedRoute = await runtime.setRoute(nextRoute, {
165
+ applyToCurrentSession,
166
+ keepMainModel,
167
+ ...(sessionOnly ? { sessionOnly } : {}),
168
+ ...(onlyIfEmpty ? { onlyIfEmpty } : {}),
169
+ ...(originGeneration !== undefined ? { originGeneration } : {}),
170
+ switchEstablished: true,
171
+ deferUntilNextTurn: true,
172
+ });
173
+ settle({
174
+ sync: applyToCurrentSession || Boolean(resolvedRoute?.modelSwitch),
175
+ modelSwitch: resolvedRoute?.modelSwitch,
176
+ });
177
+ return resolvedRoute;
178
+ } catch (error) {
179
+ settle({ failed: true });
180
+ throw error;
181
+ } finally {
182
+ if (token === routeSequence) {
183
+ if (routeLocked) set({ commandBusy: false });
184
+ routeLocked = false;
185
+ // Publish the resolved route (or rollback) before an RPC reply
186
+ // reads getState(), even while the display-frame clock is busy.
187
+ flushEmitImmediate();
188
+ }
189
+ }
190
+ })();
191
+ routeWrite = write;
192
+ try {
193
+ return await write;
194
+ } finally {
195
+ if (routeWrite === write) routeWrite = null;
196
+ }
197
+ };
198
+ // The settings surface's setModel (/model, presets) is the same user
199
+ // model choice and runs through this one path.
200
+ bag.setSessionRoute = setRoute;
201
+
202
+ return setRoute;
203
+ }