mixdog 1.3.1 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (377) hide show
  1. package/README.md +8 -4
  2. package/package.json +2 -2
  3. package/scripts/native-release-status.mjs +238 -0
  4. package/src/defaults/shipped-history.json +5 -1
  5. package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
  6. package/src/defaults/skills/setup/SKILL.md +23 -1
  7. package/src/defaults/skills/setup/references/actions.md +50 -3
  8. package/src/defaults/skills/setup/references/surfaces.md +23 -3
  9. package/src/defaults/skills/setup-guide/SKILL.md +7 -3
  10. package/src/defaults/skills/visualize/SKILL.md +9 -6
  11. package/src/defaults/skills/visualize/references/craft.md +8 -1
  12. package/src/help.mjs +1 -1
  13. package/src/lib/keychain-cjs.cjs +4 -1
  14. package/src/lib/rules-builder.cjs +14 -7
  15. package/src/rules/agent-mode/AGENT_MODE.md +8 -5
  16. package/src/rules/agent-mode/MIXY.md +6 -8
  17. package/src/rules/lead/LEAD.md +2 -2
  18. package/src/rules/shared/10-tool-workflow.md +4 -0
  19. package/src/rules/shared/20-research.md +2 -0
  20. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
  21. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
  22. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
  23. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
  24. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
  25. package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
  26. package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
  27. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
  28. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  29. package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
  30. package/src/runtime/agent/orchestrator/config.mjs +11 -0
  31. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +9 -2
  32. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -5
  33. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
  34. package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
  35. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +80 -31
  36. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
  37. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  38. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +67 -38
  39. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
  40. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
  41. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +27 -8
  42. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
  43. package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
  44. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
  45. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
  46. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +16 -0
  47. package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
  48. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -1
  49. package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +3 -1
  50. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +1 -0
  51. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +5 -5
  52. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +9 -5
  53. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +9 -1
  54. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +2 -1
  55. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +30 -3
  56. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +1 -1
  57. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +3 -1
  58. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +61 -8
  59. package/src/runtime/agent/orchestrator/providers/registry.mjs +9 -0
  60. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
  61. package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
  62. package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
  63. package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
  64. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
  65. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +4 -6
  66. package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
  67. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
  68. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +12 -1
  69. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +6 -5
  70. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +7 -6
  71. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +1 -1
  72. package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
  73. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +2 -0
  74. package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
  75. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
  76. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
  77. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +17 -2
  78. package/src/runtime/agent/orchestrator/session/agent-room.mjs +133 -38
  79. package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +80 -0
  80. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  81. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +14 -11
  82. package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
  83. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +68 -18
  84. package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
  85. package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
  86. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
  87. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +5 -1
  88. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +1 -1
  89. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
  90. package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
  91. package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +230 -0
  92. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +136 -32
  93. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
  94. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +21 -9
  95. package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
  96. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
  97. package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
  98. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
  99. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +189 -105
  100. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
  101. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +5 -5
  102. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +3 -0
  103. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
  104. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +38 -5
  105. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +21 -0
  106. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
  107. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
  108. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
  109. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
  110. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +4 -0
  111. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  112. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  113. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
  114. package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +99 -0
  115. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
  116. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
  117. package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
  118. package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
  119. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +40 -1
  120. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
  121. package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +20 -0
  122. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +55 -27
  123. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +3 -8
  124. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +12 -1
  125. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
  126. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
  127. package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
  128. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +42 -10
  129. package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
  130. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +18 -3
  131. package/src/runtime/agent/orchestrator/session/routine-result.mjs +120 -9
  132. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +5 -1
  133. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
  134. package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
  135. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
  136. package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
  137. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
  138. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +8 -3
  139. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +36 -1
  140. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -1
  141. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
  142. package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
  143. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
  144. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
  145. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
  146. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +35 -5
  147. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +42 -2
  148. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +35 -13
  149. package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
  150. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +4 -3
  151. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
  152. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
  153. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
  154. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
  155. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +6 -14
  156. package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
  157. package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
  158. package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
  159. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
  160. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
  161. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +34 -21
  162. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
  163. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
  164. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
  165. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
  166. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
  167. package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
  168. package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
  169. package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
  170. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
  171. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
  172. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
  173. package/src/runtime/attachments/attachment-reference.mjs +155 -0
  174. package/src/runtime/attachments/limits.mjs +5 -1
  175. package/src/runtime/attachments/office-extract.mjs +3 -6
  176. package/src/runtime/attachments/pdf-extract.mjs +84 -10
  177. package/src/runtime/attachments/store.mjs +49 -30
  178. package/src/runtime/browser-bridge/client.mjs +73 -20
  179. package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
  180. package/src/runtime/channels/lib/scheduler.mjs +14 -7
  181. package/src/runtime/channels/lib/state-file.mjs +15 -15
  182. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
  183. package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
  184. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
  185. package/src/runtime/channels/lib/worker-main.mjs +3 -2
  186. package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
  187. package/src/runtime/effort-judge/judge-client.mjs +5 -2
  188. package/src/runtime/effort-judge/model-install.mjs +16 -8
  189. package/src/runtime/local-provider/asset-installer.mjs +10 -4
  190. package/src/runtime/local-provider/catalog.mjs +5 -2
  191. package/src/runtime/local-provider/registered-models.mjs +64 -27
  192. package/src/runtime/local-provider/server.mjs +2 -3
  193. package/src/runtime/media/adapters/image-api.mjs +89 -0
  194. package/src/runtime/media/auth.mjs +64 -1
  195. package/src/runtime/media/catalog.mjs +66 -4
  196. package/src/runtime/media/jobs.mjs +4 -0
  197. package/src/runtime/media/lanes.mjs +46 -5
  198. package/src/runtime/media/media-usage.mjs +17 -0
  199. package/src/runtime/media/renditions.mjs +15 -15
  200. package/src/runtime/media/store.mjs +36 -35
  201. package/src/runtime/media/tool.mjs +8 -1
  202. package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +8 -2
  203. package/src/runtime/memory/lib/embedding-model-files.mjs +30 -7
  204. package/src/runtime/memory/lib/ingest/transcript-tail.mjs +16 -7
  205. package/src/runtime/memory/lib/pg/process.mjs +8 -4
  206. package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
  207. package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
  208. package/src/runtime/office/core/journal.mjs +7 -14
  209. package/src/runtime/office/design/library/design-library-core.mjs +4 -16
  210. package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
  211. package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
  212. package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
  213. package/src/runtime/office/pdf/pdf-render.mjs +30 -18
  214. package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
  215. package/src/runtime/office/portable/portable-soffice.mjs +4 -0
  216. package/src/runtime/shared/agent-kind-migration.mjs +200 -0
  217. package/src/runtime/shared/agent-message-envelope.d.mts +6 -0
  218. package/src/runtime/shared/agent-message-envelope.mjs +44 -0
  219. package/src/runtime/shared/agent-reap-state.d.mts +4 -0
  220. package/src/runtime/shared/agent-store.mjs +24 -7
  221. package/src/runtime/shared/agent-task-starts.mjs +27 -0
  222. package/src/runtime/shared/atomic-file.d.mts +50 -0
  223. package/src/runtime/shared/atomic-file.mjs +394 -132
  224. package/src/runtime/shared/background-tasks.mjs +7 -4
  225. package/src/runtime/shared/bot-hops.mjs +30 -0
  226. package/src/runtime/shared/bot-split-migration.mjs +1 -1
  227. package/src/runtime/shared/bounded-download.mjs +4 -2
  228. package/src/runtime/shared/config.mjs +39 -9
  229. package/src/runtime/shared/defaults-separation.mjs +6 -2
  230. package/src/runtime/shared/delegated-approval-host.mjs +74 -0
  231. package/src/runtime/shared/err-text.mjs +4 -0
  232. package/src/runtime/shared/error-presentation.mjs +22 -4
  233. package/src/runtime/shared/file-has-data.mjs +28 -0
  234. package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
  235. package/src/runtime/shared/pending-agent-work.mjs +54 -0
  236. package/src/runtime/shared/process-lifecycle.mjs +8 -12
  237. package/src/runtime/shared/projects.mjs +50 -41
  238. package/src/runtime/shared/provider-accounts.mjs +17 -12
  239. package/src/runtime/shared/routine-trigger.d.mts +6 -1
  240. package/src/runtime/shared/routine-trigger.mjs +14 -6
  241. package/src/runtime/shared/run-child.d.mts +3 -0
  242. package/src/runtime/shared/run-child.mjs +24 -3
  243. package/src/runtime/shared/session-baseline-store.mjs +8 -2
  244. package/src/runtime/shared/singleton-owner.mjs +7 -6
  245. package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
  246. package/src/runtime/shared/staged-update.mjs +4 -2
  247. package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
  248. package/src/runtime/shared/tool-execution-contract.mjs +6 -0
  249. package/src/runtime/shared/transcript-metadata.mjs +28 -3
  250. package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
  251. package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
  252. package/src/runtime/shared/turn-snapshot.mjs +44 -2
  253. package/src/runtime/shared/user-cwd.mjs +9 -4
  254. package/src/runtime/shared/user-data-guard.mjs +41 -42
  255. package/src/runtime/shared/utf8-cut.mjs +10 -0
  256. package/src/runtime/shared/worktrees/activity-gate.mjs +269 -61
  257. package/src/runtime/shared/worktrees/registry.mjs +26 -26
  258. package/src/runtime/tidy/core-install.mjs +26 -4
  259. package/src/runtime/tidy/install.mjs +91 -23
  260. package/src/runtime/web-search/lib/config.mjs +4 -16
  261. package/src/session-runtime/agent-home-delivery.mjs +555 -199
  262. package/src/session-runtime/agent-room-delivery.mjs +143 -36
  263. package/src/session-runtime/agent-task-sessions.mjs +667 -165
  264. package/src/session-runtime/agent-tool-routing.mjs +1 -0
  265. package/src/session-runtime/boot/apis.mjs +45 -24
  266. package/src/session-runtime/boot/config.mjs +1 -0
  267. package/src/session-runtime/boot/facade.mjs +11 -1
  268. package/src/session-runtime/boot/infrastructure.mjs +14 -2
  269. package/src/session-runtime/boot/providers.mjs +2 -2
  270. package/src/session-runtime/boot/shared.mjs +12 -6
  271. package/src/session-runtime/boot/tools.mjs +2 -2
  272. package/src/session-runtime/bot-status.mjs +432 -0
  273. package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
  274. package/src/session-runtime/config-lifecycle.mjs +60 -55
  275. package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
  276. package/src/session-runtime/held-requests.mjs +36 -33
  277. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
  278. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
  279. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +19 -4
  280. package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
  281. package/src/session-runtime/lifecycle/teardown.mjs +11 -3
  282. package/src/session-runtime/mcp-glue/connect.mjs +100 -186
  283. package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
  284. package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
  285. package/src/session-runtime/media-api.mjs +5 -2
  286. package/src/session-runtime/model-route/route-tuning.mjs +4 -1
  287. package/src/session-runtime/model-route/set-route.mjs +61 -1
  288. package/src/session-runtime/owned-automation-run.mjs +8 -1
  289. package/src/session-runtime/room-member-gate.mjs +16 -3
  290. package/src/session-runtime/route-resolution.mjs +16 -8
  291. package/src/session-runtime/route-state.mjs +1 -59
  292. package/src/session-runtime/routine-home-turn.mjs +5 -3
  293. package/src/session-runtime/runtime-bootstrap.mjs +5 -3
  294. package/src/session-runtime/runtime-paths.mjs +11 -5
  295. package/src/session-runtime/runtime-tunables.mjs +6 -0
  296. package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
  297. package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
  298. package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
  299. package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
  300. package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
  301. package/src/session-runtime/services/agent-tool/send-flow.mjs +25 -21
  302. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +4 -0
  303. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
  304. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +8 -5
  305. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
  306. package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
  307. package/src/session-runtime/services/agent-tool.mjs +6 -0
  308. package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
  309. package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
  310. package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
  311. package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
  312. package/src/session-runtime/services/plugin-admin.mjs +26 -26
  313. package/src/session-runtime/services/provider-admin.mjs +14 -4
  314. package/src/session-runtime/services/schedule-access.mjs +33 -4
  315. package/src/session-runtime/services/seeds.mjs +4 -3
  316. package/src/session-runtime/services/usage-dashboard.mjs +1 -1
  317. package/src/session-runtime/services/usage-stats-model.mjs +2 -1
  318. package/src/session-runtime/session-create/materialize.mjs +2 -3
  319. package/src/session-runtime/session-create/session-options.mjs +15 -2
  320. package/src/session-runtime/settings-builtin-tools-api.mjs +63 -1
  321. package/src/session-runtime/settings-compaction-api.mjs +86 -39
  322. package/src/session-runtime/setup-tool/executor.mjs +8 -2
  323. package/src/session-runtime/setup-tool/settings-contract.mjs +31 -1
  324. package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
  325. package/src/session-runtime/skills-api.mjs +12 -5
  326. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +265 -0
  327. package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
  328. package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
  329. package/src/session-runtime/tool-policy/policy-stage.mjs +165 -0
  330. package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
  331. package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
  332. package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
  333. package/src/session-runtime/tool-policy-refresh.mjs +47 -369
  334. package/src/session-runtime/tool-surface.mjs +5 -3
  335. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +95 -88
  336. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  337. package/src/session-runtime/turn/turn-run.mjs +2 -1
  338. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
  339. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +519 -333
  340. package/src/session-runtime/workflow-agents-api/agent-route.mjs +46 -46
  341. package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
  342. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
  343. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
  344. package/src/session-runtime/workspace/workspace-state.mjs +7 -6
  345. package/src/standalone/daemon-agent-control.mjs +4 -1
  346. package/src/standalone/daemon-session-control.mjs +51 -10
  347. package/src/standalone/daemon.mjs +28 -10
  348. package/src/standalone/local-session-runtime.mjs +22 -14
  349. package/src/standalone/session-client.mjs +7 -2
  350. package/src/standalone/session-protocol.mjs +7 -0
  351. package/src/standalone/session-service.mjs +11 -0
  352. package/src/standalone/session-state-projection.mjs +19 -9
  353. package/src/standalone/session-tool-output.mjs +35 -9
  354. package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
  355. package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
  356. package/src/tui/app/slash-commands.mjs +1 -1
  357. package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
  358. package/src/tui/dist/index.mjs +529 -333
  359. package/src/tui/prompt-history-store.mjs +16 -21
  360. package/src/tui/session/context-state.mjs +4 -0
  361. package/src/tui/session/session-api/intake/abort.mjs +19 -1
  362. package/src/tui/session/session-api/media.mjs +2 -1
  363. package/src/tui/session/session-api/routes-agent-definitions.mjs +75 -0
  364. package/src/tui/session/session-api/routes-write.mjs +203 -0
  365. package/src/tui/session/session-api/routes.mjs +23 -247
  366. package/src/tui/session/session-api/settings.mjs +14 -1
  367. package/src/tui/session/session-flow/drain.mjs +28 -3
  368. package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
  369. package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
  370. package/src/tui/session/session-flow/steering.mjs +8 -3
  371. package/src/tui/session/tool-approval.mjs +185 -20
  372. package/src/tui/session/transcript-intake.mjs +2 -0
  373. package/src/tui/session/turn.mjs +23 -14
  374. package/src/tui/transcript-tool-failures.mjs +8 -2
  375. package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -8
  376. package/src/vendor/statusline/src/gateway/session-routes.mjs +6 -7
  377. package/src/rules/agent-mode/ROUTINE.md +0 -6
@@ -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,
@@ -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 } 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,18 @@ 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 or a bot-created task session's root (who it works for, and
107
+ // how that root was acting), derived from the parent's record — the one
108
+ // read here — 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
+ return delegation ? { delegation } : {};
116
+ }
117
+
107
118
  // The persisted session record.
108
119
  export function buildSessionRecord({ opts, route, presetObj, surface, prompt, messages, contextMeta, origin }) {
109
120
  const {
@@ -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. */
@@ -19,6 +19,7 @@ import {
19
19
  import { previewFreshContextCompaction, runFreshContextCompact } from './loop/fresh-context.mjs';
20
20
  import { estimateMessagesTokensSafe } from './loop/compact-debug.mjs';
21
21
  import { textPdfAllowanceDiscount } from './context-utils.mjs';
22
+ import { referenceOversizedAttachments } from './attachment-preflight.mjs';
22
23
  import { providerNativePdf } from '../providers/registry.mjs';
23
24
  import { messagesArrayChanged } from './loop/tool-helpers.mjs';
24
25
  import { normalizeUsage, addUsage } from './loop/usage.mjs';
@@ -274,6 +275,9 @@ async function compactTranscript(ctx, run) {
274
275
  goalReminderText: run.inlineGoalReminder?.content || '',
275
276
  activeTurn: true,
276
277
  requireReduction: decision.requireReduction,
278
+ // The provider refused this request as too long: whatever the estimate
279
+ // said, the latest request's attachments now travel by reference.
280
+ referenceLatestAttachments: decision.compactTrigger === 'reactive',
277
281
  });
278
282
  const freshMessages = Array.isArray(run.freshContextResult?.messages) ? run.freshContextResult.messages : null;
279
283
  if (!freshMessages) throw new Error('fresh-context compact produced no messages');
@@ -290,6 +294,9 @@ function acknowledgeGoalReminder(sessionRef, run) {
290
294
  }
291
295
  }
292
296
 
297
+ // Detection is narrow on purpose — signal.aborted or a true AbortError — so
298
+ // the recall pipeline's SYNTHETIC "…aborted: memory … ; head preserved"
299
+ // failure (a real compact failure, message text aside) still escalates.
293
300
  function isCompactAbort(signal, err) {
294
301
  return signal?.aborted === true || err?.name === 'AbortError' || err?.code === 'ABORT_ERR' || err?.code === 'ABORT';
295
302
  }
@@ -326,16 +333,6 @@ function reportCompactFailure(ctx, run, compactErr) {
326
333
  });
327
334
  }
328
335
 
329
- // A failed compact pass ends the send. A genuine cancellation/abort surfaced
330
- // from the compact pipeline is NOT a context overflow: the fresh-context
331
- // pipeline deliberately rethrows the original abort error unchanged so the
332
- // session records a clean cancellation — and the manual/auto-clear runner
333
- // (manager/compaction-runner.mjs) likewise never fabricates an
334
- // AGENT_CONTEXT_OVERFLOW for an aborted compact. Mirror that here: preserve
335
- // the real error (code/name/cause intact) instead of masking it as overflow.
336
- // Detection is narrow on purpose — signal.aborted or a true AbortError — so
337
- // the recall pipeline's SYNTHETIC "…aborted: memory … ; head preserved"
338
- // failure (a real compact failure, message text aside) still escalates.
339
336
  function isGenuineCompactOverflow(compactErr) {
340
337
  return (
341
338
  compactErr?.code === 'AGENT_CONTEXT_OVERFLOW' ||
@@ -362,6 +359,13 @@ function retryCompactQuietly(ctx, run, compactErr) {
362
359
  return true;
363
360
  }
364
361
 
362
+ // A failed compact pass ends the send. A genuine cancellation/abort surfaced
363
+ // from the compact pipeline is NOT a context overflow: the fresh-context
364
+ // pipeline deliberately rethrows the original abort error unchanged so the
365
+ // session records a clean cancellation — and the manual/auto-clear runner
366
+ // (manager/compaction-runner.mjs) likewise never fabricates an
367
+ // AGENT_CONTEXT_OVERFLOW for an aborted compact. Mirror that here: preserve
368
+ // the real error (code/name/cause intact) instead of masking it as overflow.
365
369
  function throwCompactFailure(ctx, run, compactErr) {
366
370
  const { state, compactPolicy, decision } = ctx;
367
371
  const { sessionRef, sessionId, signal, model } = state;
@@ -555,6 +559,33 @@ async function compactBeforeSend(ctx) {
555
559
  await runCompactHook(ctx, 'postCompactHook');
556
560
  }
557
561
 
562
+ // Attachments that would crowd the window travel by reference from this send
563
+ // on (session/attachment-preflight.mjs). Runs whether or not auto-compaction
564
+ // is on: it is what keeps an oversized attachment message from ever reaching
565
+ // the provider. A rewrite changes the transcript the provider chain was built
566
+ // on, so the continuation is dropped and the send is tagged exactly like an
567
+ // automatic compaction: the prefix guard accepts that boundary, whereas a
568
+ // transcript-repair tag would refuse the rewritten earlier message.
569
+ function preflightAttachments(ctx, compactPolicy) {
570
+ const { state } = ctx;
571
+ const contextWindow = compactPolicy?.contextWindow || state.sessionRef?.contextWindow;
572
+ const outcome = referenceOversizedAttachments(state.messages, {
573
+ contextWindow,
574
+ nativePdf: providerNativePdf(state.sessionRef?.provider),
575
+ });
576
+ if (!outcome.changed) return;
577
+ ctx.providerState = undefined;
578
+ ctx.providerStateCleared = true;
579
+ ctx.compactChanged = true;
580
+ if (state.opts && !String(state.opts.cacheBreakIntent || '').endsWith('_compaction')) {
581
+ state.opts.cacheBreakIntent = 'automatic_compaction';
582
+ }
583
+ writeStderrBestEffort(
584
+ `[loop] attachments sent by reference (sess=${state.sessionId || 'unknown'}): ` +
585
+ `${outcome.referencedMessages} message(s), inline estimate ${outcome.inlineTokensBefore} > budget ${outcome.budgetTokens}\n`
586
+ );
587
+ }
588
+
558
589
  export async function runPreSendCompactPass(state) {
559
590
  const ctx = {
560
591
  state,
@@ -567,6 +598,7 @@ export async function runPreSendCompactPass(state) {
567
598
  compactChanged: false,
568
599
  };
569
600
  const compactPolicy = resolveWorkerCompactPolicy(state.sessionRef, state.requestTools);
601
+ preflightAttachments(ctx, compactPolicy);
570
602
  if (compactPolicy?.auto) {
571
603
  ctx.compactPolicy = compactPolicy;
572
604
  ctx.decision = preSendCompactDecision(state, compactPolicy);
@@ -1,5 +1,6 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { hasPlainPrototype } from '../../../shared/object.mjs';
3
+ import { withoutMessageIdsReplacer } from './message-ids.mjs';
3
4
 
4
5
  const COMPACTION_INTENTS = new Set([
5
6
  'automatic_compaction',
@@ -8,8 +9,8 @@ const COMPACTION_INTENTS = new Set([
8
9
  'post_turn_compaction',
9
10
  ]);
10
11
 
11
- function digest(value) {
12
- const encoded = JSON.stringify(value);
12
+ function digest(value, replacer) {
13
+ const encoded = JSON.stringify(value, replacer);
13
14
  if (typeof encoded !== 'string') {
14
15
  throw new TypeError('provider prefix value is not serializable');
15
16
  }
@@ -82,11 +83,28 @@ function matchShape(value, tokens, cursor) {
82
83
  return count === tokens[cursor + 1] ? next : -1;
83
84
  }
84
85
 
86
+ // A message's digest covers what the provider can see of it. The stable
87
+ // message id (`meta.transcript.messageId`, message-ids.mjs) is display
88
+ // bookkeeping that is stamped onto live messages in place — a compaction pass
89
+ // stamps every unstamped message before it runs, whatever it then decides —
90
+ // and no provider request builder reads or sends it (the rest of `meta` is
91
+ // not bookkeeping: `meta.source` and `meta.effortConfiguration` steer the
92
+ // wire and stay in the digest). Hashing it would make a pass that changes
93
+ // nothing look like a rewritten prefix on the next send.
94
+ //
95
+ // Only the message root's own `meta` is projected: a `meta` nested in content
96
+ // (e.g. a structured tool result) is provider-visible and stays in the digest.
97
+ function rootMessageIdsReplacer(message) {
98
+ return function replacer(key, value) {
99
+ return this === message ? withoutMessageIdsReplacer(key, value) : value;
100
+ };
101
+ }
102
+
85
103
  function messageDigest(message) {
86
104
  if (message === null || typeof message !== 'object') return digest(message);
87
105
  const memo = messageDigestMemo.get(message);
88
106
  if (memo && matchShape(message, memo.tokens, 0) === memo.tokens.length) return memo.hash;
89
- const hash = digest(message);
107
+ const hash = digest(message, rootMessageIdsReplacer(message));
90
108
  const tokens = [];
91
109
  if (recordShape(message, tokens)) messageDigestMemo.set(message, { hash, tokens });
92
110
  else messageDigestMemo.delete(message);