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
@@ -19,6 +19,25 @@ export function isTurnInFlight(entry) {
19
19
  return IN_FLIGHT_STAGES.has(entry.stage);
20
20
  }
21
21
 
22
+ // Sessions whose turn was handed to the runtime but has not claimed its
23
+ // controller yet (spawn dispatched, askSession still loading the session).
24
+ // The row already declares work; without this the pool cannot tell that
25
+ // first-turn gap from a worker it lost track of, and reads `unknown`.
26
+ const dispatchedTurns = new Map();
27
+
28
+ /** Runs `run` (the turn's dispatch) with `id` counted as working until it
29
+ * settles, covering the gap before the turn's own runtime entry is in flight. */
30
+ export async function whileTurnDispatched(id, run) {
31
+ dispatchedTurns.set(id, (dispatchedTurns.get(id) || 0) + 1);
32
+ try {
33
+ return await run();
34
+ } finally {
35
+ const left = dispatchedTurns.get(id) - 1;
36
+ if (left > 0) dispatchedTurns.set(id, left);
37
+ else dispatchedTurns.delete(id);
38
+ }
39
+ }
40
+
22
41
  const cleanLower = (value) =>
23
42
  String(value || '')
24
43
  .trim()
@@ -32,6 +51,7 @@ const cleanLower = (value) =>
32
51
  export function inFlightSessionActivity() {
33
52
  const working = new Set();
34
53
  const agentWorking = new Set();
54
+ for (const id of dispatchedTurns.keys()) working.add(id);
35
55
  for (const [id, entry] of runtimeState) {
36
56
  if (!isTurnInFlight(entry)) continue;
37
57
  working.add(id);
@@ -1,6 +1,7 @@
1
1
  // manager/session-crud.mjs
2
2
  // Session read + mutate CRUD: lookup (getSession/listSessions/findSessionByScopeKey),
3
3
  // message clear (with clear-fork), manual compaction, status update, and metric flush.
4
+ import { createHash } from 'node:crypto';
4
5
  import {
5
6
  loadSession,
6
7
  saveSessionAsync,
@@ -29,6 +30,7 @@ import { hideCompactionArchive } from '../compaction-archive-capture.mjs';
29
30
  import { writeStderrBestEffort } from '../../../../shared/best-effort-stderr.mjs';
30
31
  import { forgetSideQuestion } from '../side-question-store.mjs';
31
32
  import { isAgentHomeSessionId } from '../agent-home.mjs';
33
+ import { messageIdOf } from '../message-ids.mjs';
32
34
 
33
35
  /** Force-flush session metrics to disk. */
34
36
  export async function flushSessionMetrics(sessionId) {
@@ -412,19 +414,36 @@ export async function updateSessionStatus(id, status) {
412
414
  export async function rewindSessionMessagesTo(sessionId, options = {}) {
413
415
  const session = loadSession(sessionId);
414
416
  if (!session || session.closed === true) return null;
417
+ const targetId = String(options?.messageId ?? '').trim();
415
418
  const target = String(options?.text ?? '').trim();
416
- if (!target) return null;
419
+ if (!targetId && !target) return null;
417
420
  const messages = Array.isArray(session.messages) ? session.messages : [];
418
- let start = -1;
421
+ // The stable message id is the identity: the stored prompt is the EXPANDED
422
+ // text (attachment tokens materialized), so display text cannot find it.
423
+ // Text is only the fallback for a row that carries no id.
424
+ const matches = targetId
425
+ ? (message) => messageIdOf(message) === targetId
426
+ : (message) => promptContentText(message.content).trim() === target;
427
+ const found = [];
419
428
  for (let index = messages.length - 1; index >= 0; index -= 1) {
420
- const message = messages[index];
421
- if (message?.role !== 'user') continue;
422
- if (promptContentText(message.content).trim() !== target) continue;
423
- start = index;
424
- break;
429
+ if (messages[index]?.role === 'user' && matches(messages[index])) found.push(index);
425
430
  }
426
- if (start < 0) return null;
431
+ // Without an id, text that several prompts repeat cannot say which one the
432
+ // user picked: refuse rather than rewind the wrong occurrence.
433
+ if (found.length === 0 || (!targetId && found.length > 1)) return null;
434
+ const start = found[0];
427
435
  const removed = messages.length - start;
436
+ const rewoundMessage = messages[start];
437
+ // `dryRun` resolves the target and reports what a rewind would do, changing
438
+ // nothing: the caller prepares the restore payload first and commits after.
439
+ // The marker names the exact history the dry run saw (generation alone only
440
+ // moves on lifecycle changes); the commit passes it back and is refused when
441
+ // the history differs.
442
+ const historyMarker = `${session.generation ?? 0}:${messages.length}:${createHash('sha1')
443
+ .update(JSON.stringify(messages))
444
+ .digest('hex')}`;
445
+ if (options?.dryRun === true) return { removed, remaining: start, message: rewoundMessage, historyMarker };
446
+ if (options?.expectedHistoryMarker != null && options.expectedHistoryMarker !== historyMarker) return null;
428
447
  session.messages = messages.slice(0, start);
429
448
  // The provider cache is keyed on the exact prefix we just truncated;
430
449
  // the prefix-guard snapshot and the /btw fork request describe it too and
@@ -435,5 +454,5 @@ export async function rewindSessionMessagesTo(sessionId, options = {}) {
435
454
  session.updatedAt = Date.now();
436
455
  session.lastUsedAt = Date.now();
437
456
  await saveSessionAsync(session, { expectedGeneration: session.generation });
438
- return { removed, remaining: session.messages.length };
457
+ return { removed, remaining: session.messages.length, message: rewoundMessage };
439
458
  }
@@ -15,10 +15,11 @@ import { loadConfig } from '../../config.mjs';
15
15
  import {
16
16
  buildProviderCacheOpts,
17
17
  cacheCapabilityForProvider,
18
+ isLeadCacheClass,
18
19
  roleProviderCacheOpts,
19
20
  } from '../../agent-runtime/cache-strategy.mjs';
20
21
  import { normalizeAutoClearConfig, resolveAutoClearIdleMs } from '../../runtime-core/config-helpers.mjs';
21
- import { _buildBaseRules } from './rules-cache.mjs';
22
+ import { _buildBaseRules, promptRulesRevision } from './rules-cache.mjs';
22
23
  import { stripProviderReplayMessages } from './provider-replay.mjs';
23
24
  import { composeSessionSystem, seedSessionMessages } from './session-prompt-composition.mjs';
24
25
  import { agentProfileRevision, leadRosterRevision } from '../../context/role-instructions.mjs';
@@ -41,6 +42,7 @@ import { ensureCodexWireSessionId, mintSessionId } from './session-id.mjs';
41
42
  import { providerCacheKey } from './provider-cache-key.mjs';
42
43
  import { buildSessionRecord, normalizeDesktopSessionMetadata, sessionOriginFields } from './session-record.mjs';
43
44
  import { normalizeWorkflowRoute } from '../../runtime-core/workflow.mjs';
45
+ import { ownerRouteFallbacks } from './owner-route-fallbacks.mjs';
44
46
  import { applyPendingWorkspaceReroot, refreshSessionBp3Environment } from './prompt-utils.mjs';
45
47
  import { isWorktreeBound } from '../../../../shared/worktrees/contract.mjs';
46
48
  import { applyAgentToolExposure } from '../loop/tool-exposure-migration.mjs';
@@ -53,17 +55,20 @@ export { recoverSessionAfterProcessRestart } from './turn-checkpoint-recovery.mj
53
55
  // re-exported so prior importers of this module stay unchanged.
54
56
  export { prefetchSession, prepareSessionProjection } from './prepared-resume-cache.mjs';
55
57
 
56
- function buildSessionProviderCacheOpts(providerName, sessionId, agent = null, config = null) {
58
+ // `session` is the record or create options; it classifies agent-mode Leads
59
+ // (bot home threads, routine runs) into the Lead cache class.
60
+ function buildSessionProviderCacheOpts(providerName, sessionId, agent = null, config = null, session = null) {
57
61
  // Keep this in sync with createSession's provider-cache policy: only
58
- // explicit-breakpoint providers get BP cache opts here; OpenAI/key-prefix
59
- // providers use promptCacheKey and request-time strategy instead, plus the
60
- // shared prompt-cache scope of a one-shot role.
62
+ // explicit-breakpoint providers get BP cache opts here; key-prefix
63
+ // providers use promptCacheKey and request-time strategy instead (plus the
64
+ // OpenAI API's Lead-class 12h prefix TTL and a one-shot role's shared
65
+ // prompt-cache scope).
61
66
  if (cacheCapabilityForProvider(providerName) !== 'explicit-breakpoint') {
62
- return roleProviderCacheOpts(providerName, agent);
67
+ return roleProviderCacheOpts(providerName, agent, { session });
63
68
  }
64
69
  try {
65
70
  let autoClear = null;
66
- if (!agent || agent === 'lead') {
71
+ if (isLeadCacheClass(agent, session)) {
67
72
  const loadedConfig = config || loadConfig({ secrets: false });
68
73
  const normalizedAutoClear = normalizeAutoClearConfig(loadedConfig?.autoClear);
69
74
  autoClear = {
@@ -71,20 +76,23 @@ function buildSessionProviderCacheOpts(providerName, sessionId, agent = null, co
71
76
  idleMs: resolveAutoClearIdleMs(loadedConfig, providerName),
72
77
  };
73
78
  }
74
- return buildProviderCacheOpts(providerName, sessionId, agent, { autoClear });
79
+ return buildProviderCacheOpts(providerName, sessionId, agent, { autoClear, session });
75
80
  } catch {
76
81
  return null;
77
82
  }
78
83
  }
79
84
 
80
- // Re-derive a Lead session's builder-made cache policy (messages TTL follows
81
- // the auto-clear idle window). Explicit caller/profile policies, non-Lead
82
- // sessions and non-explicit-breakpoint providers are left untouched.
85
+ // Re-derive a Lead-class session's builder-made cache policy (Anthropic
86
+ // messages TTL follows the auto-clear idle window; the OpenAI API prefix TTL
87
+ // follows the cache class). Explicit caller/profile policies, sub-agents and
88
+ // other providers are left untouched.
83
89
  export function refreshSessionProviderCacheOpts(session, config = null) {
84
90
  if (!session || session.providerCacheOptsOverride === true) return false;
85
- if (session.agent && session.agent !== 'lead') return false;
86
- if (cacheCapabilityForProvider(session.provider) !== 'explicit-breakpoint') return false;
87
- const next = buildSessionProviderCacheOpts(session.provider, session.id, session.agent, config);
91
+ if (!isLeadCacheClass(session.agent, session)) return false;
92
+ if (cacheCapabilityForProvider(session.provider) !== 'explicit-breakpoint' && session.provider !== 'openai') {
93
+ return false;
94
+ }
95
+ const next = buildSessionProviderCacheOpts(session.provider, session.id, session.agent, config, session);
88
96
  if (!next) return false;
89
97
  session.providerCacheOpts = next;
90
98
  return true;
@@ -225,26 +233,35 @@ export function createSession(createOpts) {
225
233
  sessionMode === AGENT_MODE && !createOpts.agent && actingAgent
226
234
  ? { ...createOpts, agent: actingAgent, sessionMode }
227
235
  : { ...createOpts, sessionMode };
228
- const routeFallbacks = normalizeWorkflowRoute({
229
- provider: providerName,
230
- model: modelName,
231
- fallbacks: Object.hasOwn(opts, 'routeFallbacks') ? opts.routeFallbacks : presetObj?.fallbacks,
232
- })?.fallbacks;
236
+ // A Lead-class session (actingAgent set) takes its owner's chain while it
237
+ // runs on the owner's primary model; an agent keeps its preset's.
238
+ const routeFallbacks = actingAgent
239
+ ? Object.hasOwn(opts, 'routeFallbacks')
240
+ ? opts.routeFallbacks
241
+ : ownerRouteFallbacks({ sessionMode, agentId: actingAgent, provider: providerName, model: modelName })
242
+ : normalizeWorkflowRoute({
243
+ provider: providerName,
244
+ model: modelName,
245
+ fallbacks: Object.hasOwn(opts, 'routeFallbacks') ? opts.routeFallbacks : presetObj?.fallbacks,
246
+ })?.fallbacks;
233
247
  // Provider cache strategy — agentRuntime.resolveSync() above is a
234
248
  // best-effort injection point (setAgentRuntime() has no live caller
235
249
  // today, so that branch never fires); build it directly here so every
236
- // session still gets a cache strategy. Lead sessions (opts.agent ===
237
- // 'lead', or no agent at all — raw/CLI callers) get their BP4 message
238
- // tail TTL linked to the user's autoClear idle-sweep config; hidden and
239
- // public agents keep the flat 5m default (see cache-strategy.mjs docs).
240
- // Scoped to explicit-breakpoint (Anthropic-family) providers only.
241
- const providerCacheOpts = resolved.providerCacheOpts || buildSessionProviderCacheOpts(providerName, id, opts.agent);
250
+ // session still gets a cache strategy. Lead-class sessions (the Lead, raw
251
+ // callers, bot home threads and other agent-mode Leads) get their BP4
252
+ // message tail TTL linked to the user's autoClear idle-sweep config and,
253
+ // on the OpenAI API, the 12h stable-prefix TTL; sub-agents and hidden roles
254
+ // keep the agent defaults (see cache-strategy.mjs docs).
255
+ const providerCacheOpts =
256
+ resolved.providerCacheOpts || buildSessionProviderCacheOpts(providerName, id, opts.agent, null, { ...opts, id });
242
257
  const surface = resolveSessionToolSurface(opts, { profile, toolPreset, modelName });
243
258
  // The agent profile the prompt is composed from, read first: an edit landing
244
259
  // during composition leaves the stamp behind, never ahead (refreshAgentProfile).
245
260
  const agentProfile = surface.ownerIsAgent
246
261
  ? ''
247
262
  : agentProfileRevision(surface.resolvedAgent, { mixy: sessionMode === AGENT_MODE });
263
+ // A bot thread lives on across releases: the rules it was built from.
264
+ const rulesRevision = agentProfile && sessionMode === AGENT_MODE ? promptRulesRevision() : '';
248
265
  const prompt = composeSessionSystem(opts, { profile, providerName, modelName, surface, sessionMode });
249
266
  const messages = seedSessionMessages(prompt, opts.files);
250
267
  logSessionSurface(surface);
@@ -268,6 +285,7 @@ export function createSession(createOpts) {
268
285
  session.agentProfileRevision = agentProfile;
269
286
  // The bot roster its prompt lists (a Lead's catalog slices).
270
287
  session.agentRosterRevision = leadRosterRevision(opts.leadWorkflowParts);
288
+ if (rulesRevision) session.rulesRevision = rulesRevision;
271
289
  }
272
290
  if (routeFallbacks?.length) session.routeFallbacks = routeFallbacks;
273
291
  // A non-native agent sends core + bridge, built from its filtered tools.
@@ -340,6 +358,13 @@ function applyRouteFields(session, route) {
340
358
  else delete session.autoEffort;
341
359
  }
342
360
  if (Object.hasOwn(route, 'effort')) session.effort = route.effort || null;
361
+ // A deliberate route choice restarts the chain (a fallback hop never passes this).
362
+ if (Object.hasOwn(route, 'routeFallbacks')) {
363
+ if (route.routeFallbacks?.length) session.routeFallbacks = route.routeFallbacks;
364
+ else delete session.routeFallbacks;
365
+ delete session.routeFallbackIndex;
366
+ delete session.routeFallbackFailures;
367
+ }
343
368
  ensureCodexWireSessionId(session);
344
369
  if (Object.hasOwn(route, 'modelParameters')) {
345
370
  session.modelParameters =
@@ -387,7 +412,8 @@ function applyRouteContextWindow(session, routeChanged, selectedContextWindowPro
387
412
  // surface: the previous route's readings and prepared tools no longer apply.
388
413
  function resetSessionForRouteChange(id, session, previousModel, previousProvider) {
389
414
  session.promptCacheKey = providerCacheKey(session.provider);
390
- session.providerCacheOpts = buildSessionProviderCacheOpts(session.provider, session.id, session.agent) || null;
415
+ session.providerCacheOpts =
416
+ buildSessionProviderCacheOpts(session.provider, session.id, session.agent, null, session) || null;
391
417
  session.providerCacheOptsOverride = false;
392
418
  session.lastInputTokens = 0;
393
419
  session.lastOutputTokens = 0;
@@ -437,7 +463,9 @@ function stripProviderReplay(session) {
437
463
  // auto-compacts) against the new model's window.
438
464
  export function updateSessionRoute(id, route = {}, options = {}) {
439
465
  if (!id) return null;
440
- const session = loadSession(id);
466
+ // options.session: the live object of a running turn, changed in place; the id's
467
+ // stored copy (a disk reload after another process took the generation) is not.
468
+ const session = options.session?.id === id ? options.session : loadSession(id);
441
469
  if (!session || session.closed === true) return null;
442
470
  const previousProvider = session.provider || null;
443
471
  const previousModel = session.model || null;
@@ -5,7 +5,7 @@
5
5
  import { buildSkillManifest, composeSystemPrompt } from '../../context/collect.mjs';
6
6
  import { configuredInstructionFilesMode, loadInstructionFilesContext } from '../../context/instruction-files.mjs';
7
7
  import { agentModeIdentityLine } from '../../context/role-instructions.mjs';
8
- import { AGENT_MODE, isRoutineSource, sessionModeOf } from '../agent-mode.mjs';
8
+ import { AGENT_MODE, sessionModeOf } from '../agent-mode.mjs';
9
9
  import { MIXY_AGENT_ID } from '../agent-home.mjs';
10
10
  import {
11
11
  _buildBaseRules,
@@ -98,12 +98,9 @@ export function buildWorkspacePromptContext(session, cwd) {
98
98
  // A Lead's BP3 rule slices, ordered for prefix sharing (composeSystemPrompt):
99
99
  // LEAD.md's common lines first; then the mode block (coding: LEAD.md's two
100
100
  // passages; agent mode: their conversational variants and AGENT_MODE.md);
101
- // then Mixy's MIXY.md; last the per-session tail — routine line, identity.
101
+ // then Mixy's MIXY.md; last the per-session tail — identity.
102
102
  // Agent mode is a Lead created as an agent (agent-mode.mjs).
103
- export function sessionRolePromptFields(
104
- identity,
105
- { ownerIsAgent, toolFreeRole, delegationFree, sessionMode, routine = false }
106
- ) {
103
+ export function sessionRolePromptFields(identity, { ownerIsAgent, toolFreeRole, delegationFree, sessionMode }) {
107
104
  const skipRules = identity.skipAgentRules || toolFreeRole;
108
105
  const lead = !ownerIsAgent && !skipRules;
109
106
  const agentMode = lead && sessionMode === AGENT_MODE;
@@ -131,7 +128,6 @@ export function sessionRolePromptFields(
131
128
  ? {
132
129
  agentModeRules: _buildAgentModeRules({ part: 'conduct' }),
133
130
  ...(mixy ? { mixyRules: _buildAgentModeRules({ part: 'mixy' }) } : {}),
134
- ...(routine ? { routineRules: _buildAgentModeRules({ part: 'routine' }) } : {}),
135
131
  }
136
132
  : {}),
137
133
  userPrompt: identity.systemPrompt,
@@ -163,7 +159,7 @@ export function composeSessionSystem(opts, { profile, providerName, modelName, s
163
159
  provider: providerName,
164
160
  model: modelName,
165
161
  });
166
- const delegationFree = delegationDisabled(opts, ownerIsAgent);
162
+ const delegationFree = delegationDisabled({ ...opts, sessionMode }, ownerIsAgent);
167
163
  const promptIdentity = {
168
164
  systemPrompt: opts.systemPrompt || '',
169
165
  skipAgentRules: opts.skipAgentRules === true,
@@ -176,7 +172,6 @@ export function composeSessionSystem(opts, { profile, providerName, modelName, s
176
172
  toolFreeRole,
177
173
  delegationFree,
178
174
  sessionMode,
179
- routine: isRoutineSource(opts.sourceType),
180
175
  });
181
176
  const leadOnly = !skipAgentRules && !ownerIsAgent;
182
177
  const metaContext = leadOnly ? _buildLeadMetaContext({ includeOutputStyle: !agentMode }) : '';
@@ -2,8 +2,10 @@
2
2
  // What a persisted session row contains and where each field comes from: the
3
3
  // compaction budget, the origin/permission/cache metadata analytics slice on,
4
4
  // the desktop classification, and the record assembled from them. Everything
5
- // here is field derivation — no I/O, no ownership, no prompt text.
5
+ // here is field derivation — no ownership, no prompt text; the only read is the
6
+ // parent's record a delegated session takes its root from.
6
7
  import { toSessionWorkflowMeta } from '../../runtime-core/workflow.mjs';
8
+ import { delegationForNewSession, runForNewSession } from '../human-gate/delegation.mjs';
7
9
  import { sessionOrchestrationMode } from '../../../../shared/orchestration.mjs';
8
10
  import { preserveBufferConfigFields } from './context-meta.mjs';
9
11
  import { positiveInt } from '../../../../shared/numbers.mjs';
@@ -101,9 +103,20 @@ export function sessionOriginFields(opts, { profile, presetObj, providerCacheOpt
101
103
  ownerSessionId: opts.ownerSessionId || null,
102
104
  visibility: opts.visibility || null,
103
105
  clientHostPid: opts.clientHostPid || null,
106
+ // A sub-agent's root and the run (if any) that started a sub-agent or a
107
+ // bot-created task session, derived from its creator — the one read here —
108
+ // never taken from the options (human-gate/delegation.mjs).
109
+ ...delegationFields(opts),
104
110
  };
105
111
  }
106
112
 
113
+ function delegationFields(opts) {
114
+ const delegation = delegationForNewSession(opts);
115
+ // Work a schedule/webhook run started (a sub-agent, a task session) stays the run's.
116
+ const run = runForNewSession(opts);
117
+ return { ...(delegation ? { delegation } : {}), ...(run ? { run } : {}) };
118
+ }
119
+
107
120
  // The persisted session record.
108
121
  export function buildSessionRecord({ opts, route, presetObj, surface, prompt, messages, contextMeta, origin }) {
109
122
  const {
@@ -9,15 +9,19 @@ import { collectPromptSkillsCached } from '../../context/collect.mjs';
9
9
  import { isAgentOwner } from '../../agent-owner.mjs';
10
10
  import { getHiddenAgent } from '../../internal-agents.mjs';
11
11
  import { workflowDisallowsAgentTool } from '../../runtime-core/workflow.mjs';
12
+ import { isAgentModeSession } from '../agent-mode.mjs';
12
13
  import { finalizeSessionToolList, resolveSessionTools, permissionFromToolSpec } from './tool-resolution.mjs';
13
14
  import { unusedModelEditToolName } from '../../../../shared/edit-tool-dialect.mjs';
14
15
  import { getAgentRuntimeSync } from './agent-runtime-singleton.mjs';
15
16
 
16
17
  // A lead session may not delegate when its workflow forbids the agent tool
17
- // (no delegatable agents); Solo keeps it deferred for explicit user requests.
18
- // An agent-owned session never gets the tool anyway.
18
+ // (an agent-mode Lead without delegatable agents); Solo keeps it deferred for
19
+ // explicit user requests. An agent-owned session never gets the tool anyway.
19
20
  export function delegationDisabled(source, ownerIsAgent) {
20
- return !ownerIsAgent && workflowDisallowsAgentTool(source?.workflow);
21
+ return (
22
+ !ownerIsAgent &&
23
+ workflowDisallowsAgentTool(source?.workflow, { agentMode: Boolean(source) && isAgentModeSession(source) })
24
+ );
21
25
  }
22
26
 
23
27
  // Exactly two schema surfaces exist: Lead and Agent. Every Agent role gets
@@ -35,10 +35,9 @@
35
35
  // keeps the durable prefix.
36
36
  import { appendFileSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
37
37
  import { appendFile, unlink, writeFile } from 'node:fs/promises';
38
- import { randomBytes } from 'node:crypto';
39
38
  import { join } from 'node:path';
40
39
  import { getPluginData } from '../../config.mjs';
41
- import { renameWithRetrySync } from '../../../../shared/atomic-file.mjs';
40
+ import { writeFileAtomicSync } from '../../../../shared/atomic-file.mjs';
42
41
  import { sanitizeContentForStoredHistory } from '../../providers/media-normalization.mjs';
43
42
  import { promptContentText } from './prompt-utils.mjs';
44
43
 
@@ -145,19 +144,11 @@ export function writeCheckpointHeader(checkpoint) {
145
144
  contextState: checkpoint.contextState ?? null,
146
145
  updatedAt: Date.now(),
147
146
  };
148
- const temp = `${target}.${randomBytes(6).toString('hex')}.tmp`;
149
- try {
150
- writeFileSync(temp, JSON.stringify(payload), 'utf8');
151
- renameWithRetrySync(temp, target);
152
- return true;
153
- } catch (error) {
154
- try {
155
- unlinkSync(temp);
156
- } catch {
157
- /* best-effort */
158
- }
159
- throw error;
160
- }
147
+ // Durable (fsynced through its handle, then renamed): a crash must not
148
+ // publish a header whose bytes never landed. A damaged header already reads
149
+ // as "no checkpoint" (parseTurnCheckpointHeader) and the next turn rewrites it.
150
+ writeFileAtomicSync(target, JSON.stringify(payload));
151
+ return true;
161
152
  }
162
153
 
163
154
  export function readTurnCheckpointHeader(sessionId) {
@@ -20,7 +20,15 @@ const INTERRUPTED_TOOL_RESULT = 'Cancelled';
20
20
  // PRESERVE the just-sent user turn: nothing restores the prompt there, and
21
21
  // rewinding erases the user's message from the persisted transcript (the
22
22
  // exact loss seen when the desktop app quits mid-turn).
23
- const USER_CANCEL_ABORT_REASONS = new Set(['cli-abort', 'user-cancel', 'turn-abort']);
23
+ //
24
+ // A user cancel the host will NOT answer with the prompt back in the input box
25
+ // (the desktop Stop button, a prompt carrying attachments cancelled from
26
+ // another device) names `USER_CANCEL_KEEP_PROMPT_REASON`: it is still a user
27
+ // cancel, but the unanswered prompt stays in the stored transcript. Rewinding
28
+ // it left a first message with zero stored messages — a session every catalog
29
+ // hides — and its text nowhere.
30
+ export const USER_CANCEL_KEEP_PROMPT_REASON = 'user-cancel-keep-prompt';
31
+ const USER_CANCEL_ABORT_REASONS = new Set(['cli-abort', 'user-cancel', 'turn-abort', USER_CANCEL_KEEP_PROMPT_REASON]);
24
32
 
25
33
  function provisionalUserTurnIndex(messages, currentUserContent) {
26
34
  const currentText = promptContentText(currentUserContent);
@@ -78,7 +86,17 @@ function finalizeInterruptedTurn({
78
86
  // turn and its side effects from history.
79
87
  const preserveResponse = responseStarted && (producedOutput || !userCancelled);
80
88
  if (!preserveResponse) {
81
- if (!userCancelled) {
89
+ if (!userCancelled || abortReason === USER_CANCEL_KEEP_PROMPT_REASON) {
90
+ // A kept prompt gets the generic interruption row (rendered as the
91
+ // Cancelled status, unlike the hidden by-user control row): without it a
92
+ // rebuilt transcript shows the stopped prompt as if it were still
93
+ // awaiting an answer, directly above the next one.
94
+ if (
95
+ abortReason === USER_CANCEL_KEEP_PROMPT_REASON &&
96
+ provisionalUserTurnIndex(messages, currentUserContent) >= 0
97
+ ) {
98
+ messages.push({ role: 'user', content: SESSION_INTERRUPT_MESSAGE });
99
+ }
82
100
  return {
83
101
  messages,
84
102
  responsePreserved: false,
@@ -145,18 +163,16 @@ function appendObservedTurnOutput(
145
163
  if (!result?.toolCallId || representedToolResultIds.has(result.toolCallId)) continue;
146
164
  messages.push({
147
165
  role: 'tool',
148
- content: result.content == null ? '' : result.content,
166
+ content: result.content ?? '',
149
167
  toolCallId: result.toolCallId,
150
168
  ...(result.toolKind ? { toolKind: result.toolKind } : {}),
151
169
  });
152
170
  representedToolResultIds.add(result.toolCallId);
153
171
  }
154
172
 
155
- const allToolCallIds = [];
173
+ const allToolCallIds = new Set();
156
174
  for (const message of messages) {
157
- for (const id of assistantToolCallIds(message)) {
158
- if (!allToolCallIds.includes(id)) allToolCallIds.push(id);
159
- }
175
+ for (const id of assistantToolCallIds(message)) allToolCallIds.add(id);
160
176
  }
161
177
  for (const callId of allToolCallIds) {
162
178
  if (representedToolResultIds.has(callId)) continue;
@@ -283,7 +299,7 @@ export function createTurnInterruptionTracker() {
283
299
  if (message.__earlyNotify === true) {
284
300
  state.observedToolResults.set(callId, {
285
301
  role: 'tool',
286
- content: message.content == null ? '' : message.content,
302
+ content: message.content ?? '',
287
303
  toolCallId: callId,
288
304
  toolKind: message.toolKind || (message.isError ? 'error' : null),
289
305
  });
@@ -1,20 +1,97 @@
1
1
  // manager/workflow-switch-notice.mjs
2
2
  // A mid-conversation workflow switch (session-runtime/tool-policy-refresh.mjs
3
- // switchSessionWorkflow) rebuilds the session's prompt and tool surface and
4
- // stages `session.workflowSwitch`, the one-time notice the next user turn
5
- // carries in its reminder block. Lifecycle mirrors the working-folder notice
6
- // (prompt-utils snapshotPendingWorkspaceNotice): snapshot when the turn is
7
- // built, acknowledge once the provider accepted it, so a failed turn re-sends
8
- // it. The state object is replaced, never mutated:
9
- // from — the workflow the model was last told about (kept across switches
10
- // made before a notice reached it);
11
- // to — the workflow the prompt carries now.
3
+ // switchSessionWorkflow) leaves the request prefix — system blocks and tool
4
+ // schemas — byte-identical, so the provider cache survives. The switch lives
5
+ // in two pieces of session state:
6
+ // session.workflowOverlay — while the session's workflow differs from the
7
+ // one its system prompt carries: the prompt's workflow (`prompt`, and the
8
+ // orchestration mode its tool surface was built with), the new workflow,
9
+ // its tools that are refused at call time (`deniedTools`), the tools it
10
+ // allows that this session cannot reach (`unavailableTools`) and the
11
+ // self-contained reminder (`content`) with the new workflow's rules,
12
+ // orchestration and agents. It is folded into the prompt (and dropped) at
13
+ // the next point where the cache is cold anyway (tool-policy-refresh.mjs).
14
+ // session.workflowSwitch — the notice the next user turn carries in its
15
+ // reminder block. Lifecycle mirrors the working-folder notice
16
+ // (prompt-utils snapshotPendingWorkspaceNotice): snapshot when the turn is
17
+ // built, acknowledge once the provider accepted it, so a failed turn
18
+ // re-sends it. Delivered, it stays in the conversation. Replaced, never
19
+ // mutated:
20
+ // from — the workflow the model was last told about (kept across
21
+ // switches made before a notice reached it);
22
+ // to — the session's workflow now.
23
+ // Every notice says it replaces the earlier ones, so the latest one wins.
12
24
 
13
25
  function workflowRef(workflow) {
14
26
  const id = String(workflow?.id || '').trim();
15
27
  return id ? { id, name: String(workflow?.name || id).trim() } : null;
16
28
  }
17
29
 
30
+ /** The workflow the session's system prompt and tool surface were built with. */
31
+ export function promptWorkflowOf(session) {
32
+ return session?.workflowOverlay?.prompt || session?.workflow || null;
33
+ }
34
+
35
+ const lower = (name) => String(name || '').toLowerCase();
36
+
37
+ /** The refusal of a call to a tool the session's current workflow denies
38
+ * while its schema (built for the prompt's workflow) still carries it; null
39
+ * for any other call. */
40
+ export function workflowOverlayDenial(session, name) {
41
+ const overlay = session?.workflowOverlay;
42
+ if (!overlay?.deniedTools?.some((denied) => lower(denied) === lower(name))) return null;
43
+ return `Error: tool "${name}" is not available in workflow ${overlay.workflow.name}: this conversation switched to it. Do the work without it.`;
44
+ }
45
+
46
+ const listed = (names) => names.map((name) => `\`${name}\``).join(', ');
47
+
48
+ /** The reminder that makes the Lead follow `workflow` while its system prompt
49
+ * still carries `promptName`'s: self-contained (rules, orchestration, agents,
50
+ * tool changes) and superseding every earlier one. */
51
+ export function workflowOverlayContent({
52
+ promptName,
53
+ workflow,
54
+ instructions,
55
+ deniedTools = [],
56
+ loadableTools = [],
57
+ unavailableTools = [],
58
+ }) {
59
+ const name = workflow.name;
60
+ return [
61
+ '<system-reminder>',
62
+ '# Workflow Changed',
63
+ `This conversation now runs the ${name} workflow. The system prompt's Active Workflow, orchestration mode and available agents are still ${promptName}'s: they no longer apply; follow ${name}'s below instead. This replaces every earlier Workflow Changed reminder in this conversation.`,
64
+ ...(deniedTools.length ? [`Not available in ${name} (calls are refused): ${listed(deniedTools)}.`] : []),
65
+ ...(loadableTools.length
66
+ ? [
67
+ `${name} adds ${listed(loadableTools)}: not among your directly defined tools yet; load ${loadableTools.length === 1 ? 'it' : 'them'} with \`load_tool\` before the first call.`,
68
+ ]
69
+ : []),
70
+ ...(unavailableTools.length
71
+ ? [`${name} allows ${listed(unavailableTools)}, but this session cannot use them; a new session can.`]
72
+ : []),
73
+ `Agents already started under ${promptName} keep running with their own instructions; new delegation follows ${name}.`,
74
+ '',
75
+ instructions,
76
+ '</system-reminder>',
77
+ ].join('\n');
78
+ }
79
+
80
+ function messageText(message) {
81
+ if (typeof message?.content === 'string') return message.content;
82
+ if (!Array.isArray(message?.content)) return '';
83
+ return message.content.map((part) => (typeof part === 'string' ? part : part?.text || '')).join('');
84
+ }
85
+
86
+ /** The conversation still holds `content` (a delivered reminder); false once
87
+ * a compaction or a clear dropped it. */
88
+ export function conversationHolds(session, content) {
89
+ if (!content) return false;
90
+ return (Array.isArray(session?.messages) ? session.messages : []).some(
91
+ (message) => message?.role !== 'system' && messageText(message).includes(content)
92
+ );
93
+ }
94
+
18
95
  /** Stage the notice of a switch from `from` to `to`; a switch back to what the
19
96
  * model was last told drops it. Returns the staged state or null. */
20
97
  export function stageWorkflowSwitchNotice(session, from, to) {
@@ -29,13 +106,14 @@ export function stageWorkflowSwitchNotice(session, from, to) {
29
106
  return session.workflowSwitch;
30
107
  }
31
108
 
32
- function workflowNoticeContent({ from, to }) {
109
+ // The prompt itself carries `to` (a switch back to it, or the overlay folded in).
110
+ function promptWorkflowNoticeContent({ from, to }) {
33
111
  const before = from?.name || 'the previous workflow';
34
112
  return [
35
113
  '<system-reminder>',
36
114
  '# Workflow Changed',
37
- `The workflow changed from ${before} to ${to.name}. Earlier turns of this conversation ran under ${before}; the system prompt was updated in place, so its Active Workflow rules, delegation instructions, available agents and tools are now those of ${to.name}. Follow them from this turn on.`,
38
- `Agents already started under ${before} keep running with their own instructions; new delegation uses the current tools and agents.`,
115
+ `This conversation now runs the ${to.name} workflow, the one the system prompt carries: its Active Workflow, orchestration mode, available agents and tools apply. This replaces every earlier Workflow Changed reminder in this conversation.`,
116
+ `Agents already started under ${before} keep running with their own instructions; new delegation follows ${to.name}.`,
39
117
  '</system-reminder>',
40
118
  ].join('\n');
41
119
  }
@@ -47,7 +125,10 @@ function workflowNoticeContent({ from, to }) {
47
125
  export function snapshotPendingWorkflowNotice(session) {
48
126
  const pending = session?.workflowSwitch;
49
127
  if (!pending?.to?.id) return null;
50
- return { revision: pending.revision, content: workflowNoticeContent(pending) };
128
+ const overlay = session.workflowOverlay;
129
+ const content =
130
+ overlay?.workflow?.id === pending.to.id && overlay.content ? overlay.content : promptWorkflowNoticeContent(pending);
131
+ return { revision: pending.revision, content };
51
132
  }
52
133
 
53
134
  /** The notice of `revision` reached the model: nothing is due any more. */