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
@@ -5,7 +5,6 @@ import { getProvider, initProviders } from '../../providers/registry.mjs';
5
5
  import { resolveMaintenanceRoute } from '../../agent-runtime/maintenance-route.mjs';
6
6
  import { resolveSessionContextMeta } from '../manager/context-meta.mjs';
7
7
  import { isAccountQuotaError } from '../../providers/account-pool.mjs';
8
- import { builtinFeatureActive } from '../../runtime-core/builtin-features.mjs';
9
8
  import { positiveInt } from '../../../../shared/numbers.mjs';
10
9
  import { estimateMessagesTokens, providerTokenCalibration } from '../context-utils.mjs';
11
10
  import {
@@ -16,6 +15,8 @@ import {
16
15
  COMPACT_TARGET_MIN_TOKENS,
17
16
  } from '../compact.mjs';
18
17
  import { summarizeOnSharedPrefix } from '../compact/forked-summary.mjs';
18
+ import { normalizeCompactionSummaryMode } from '../compact/summary-mode.mjs';
19
+ import { compactionCacheWarm, lighterSubscriptionModel } from '../compact/summary-route.mjs';
19
20
  import { retireCompactionFork } from '../side-question-store.mjs';
20
21
 
21
22
  // Conversation summarization triggers independently of the post-compact target:
@@ -24,52 +25,103 @@ import { retireCompactionFork } from '../side-question-store.mjs';
24
25
  const CONVERSATION_COMPACT_TRIGGER_RATIO = 0.05;
25
26
  const CONVERSATION_COMPACT_TRIGGER_MAX_TOKENS = 20_000;
26
27
 
27
- // Select an explicitly configured, enabled maintenance route only. An absent
28
- // or disabled maintenance role falls back to the conversation's own model,
29
- // not an unrelated global default. Provider failures/cancellation are not
30
- // absence and must propagate without silently routing around them.
31
- export async function resolveCompactionRoute({
28
+ // One summary route: the provider, its model and the window the summary
29
+ // request must fit. `source` names the step: 'session' (the conversation's
30
+ // own model), 'maintenance' (the Maintainer) or 'lighter' (the subscription
31
+ // provider's lighter model).
32
+ function summaryRoute({ sessionRef, provider, providerName, model, fast, source }) {
33
+ if (!provider || typeof provider.send !== 'function' || !model) {
34
+ throw new Error(`compact summary route unavailable: ${providerName || 'unknown'}/${model || 'unknown'}`);
35
+ }
36
+ const sameModel = providerName === sessionRef?.provider && model === sessionRef?.model;
37
+ const contextWindow =
38
+ sameModel && positiveInt(sessionRef?.contextWindow)
39
+ ? positiveInt(sessionRef.contextWindow)
40
+ : resolveSessionContextMeta(provider, model, {}, { wholeWindow: true }).contextWindow;
41
+ return { provider, providerName, model, contextWindow, fast, source };
42
+ }
43
+
44
+ // The Maintainer's own model when one is set and enabled, whatever the memory
45
+ // or recap settings; null otherwise (an unrelated global default is never
46
+ // borrowed). Provider failures/cancellation are not absence and propagate.
47
+ async function resolveMaintainerRoute({ sessionRef, config, signal, getProviderFn, initProvidersFn }) {
48
+ const route = resolveMaintenanceRoute({ agent: 'cycle1-agent', config, includeDefault: false });
49
+ if (!(route?.provider && route?.model && config.providers?.[route.provider]?.enabled !== false)) return null;
50
+ await initProvidersFn(config.providers || {}, { signal });
51
+ signal?.throwIfAborted();
52
+ return summaryRoute({
53
+ sessionRef,
54
+ provider: getProviderFn(route.provider),
55
+ providerName: route.provider,
56
+ model: route.model,
57
+ fast: route.fast === true,
58
+ source: 'maintenance',
59
+ });
60
+ }
61
+
62
+ function resolveSessionRoute({ sessionRef, provider, model, getProviderFn }) {
63
+ const providerName = sessionRef?.provider || provider?.name;
64
+ return summaryRoute({
65
+ sessionRef,
66
+ provider: provider || getProviderFn(providerName),
67
+ providerName,
68
+ model: model || sessionRef?.model,
69
+ source: 'session',
70
+ });
71
+ }
72
+
73
+ // The newest lighter model of a subscription provider's saved account
74
+ // catalog; null on any other provider, without a saved catalog, when none is
75
+ // listed, or when the conversation already runs on it.
76
+ async function resolveLighterRoute(input) {
77
+ const session = resolveSessionRoute(input);
78
+ const lighter = await lighterSubscriptionModel(session.providerName);
79
+ input.signal?.throwIfAborted();
80
+ if (!lighter || lighter === session.model) return null;
81
+ return summaryRoute({
82
+ sessionRef: input.sessionRef,
83
+ provider: session.provider,
84
+ providerName: session.providerName,
85
+ model: lighter,
86
+ source: 'lighter',
87
+ });
88
+ }
89
+
90
+ /**
91
+ * The compaction summary plan (compact/summary-mode.mjs): the saved mode,
92
+ * whether the session's prompt cache is warm, and the routes in the order
93
+ * they are tried, each resolved only when reached (null = unavailable, the
94
+ * next one runs). The conversation's own model is always the last step.
95
+ */
96
+ export function compactionSummaryPlan({
32
97
  sessionRef,
98
+ sessionId = sessionRef?.id,
33
99
  provider,
34
100
  model,
35
101
  config = loadConfig(),
36
102
  signal,
37
103
  getProviderFn = getProvider,
38
104
  initProvidersFn = initProviders,
39
- allowMaintenance = true,
105
+ now = Date.now(),
40
106
  } = {}) {
41
107
  signal?.throwIfAborted();
42
- const route =
43
- allowMaintenance && builtinFeatureActive(config, 'memory') && config.recap?.enabled !== false
44
- ? resolveMaintenanceRoute({ agent: 'cycle1-agent', config, includeDefault: false })
45
- : null;
46
- const useMaintenance = !!(route?.provider && route?.model && config.providers?.[route.provider]?.enabled !== false);
47
- const providerName = useMaintenance ? route.provider : sessionRef?.provider || provider?.name;
48
- const selectedModel = useMaintenance ? route.model : model || sessionRef?.model;
49
- if (useMaintenance) await initProvidersFn(config.providers || {}, { signal });
50
- signal?.throwIfAborted();
51
- const selectedProvider = useMaintenance ? getProviderFn(providerName) : provider || getProviderFn(providerName);
52
- if (!selectedProvider || typeof selectedProvider.send !== 'function' || !selectedModel) {
53
- throw new Error(`compact summary route unavailable: ${providerName || 'unknown'}/${selectedModel || 'unknown'}`);
54
- }
55
- const sameModel = providerName === sessionRef?.provider && selectedModel === sessionRef?.model;
56
- const contextWindow =
57
- sameModel && positiveInt(sessionRef?.contextWindow)
58
- ? positiveInt(sessionRef.contextWindow)
59
- : resolveSessionContextMeta(selectedProvider, selectedModel, {}, { wholeWindow: true }).contextWindow;
60
- return {
61
- provider: selectedProvider,
62
- providerName,
63
- model: selectedModel,
64
- contextWindow,
65
- fast: useMaintenance ? route.fast === true : undefined,
66
- source: useMaintenance ? 'maintenance' : 'session',
67
- };
108
+ const input = { sessionRef, provider, model, config, signal, getProviderFn, initProvidersFn };
109
+ const mode = normalizeCompactionSummaryMode(config.compaction);
110
+ const warm = compactionCacheWarm(sessionId, now);
111
+ const session = async () => resolveSessionRoute(input);
112
+ const maintainer = () => resolveMaintainerRoute(input);
113
+ const lighter = () => resolveLighterRoute(input);
114
+ let steps = [session];
115
+ if (mode === 'maintainer') steps = [maintainer, session];
116
+ else if (mode === 'smart' && !warm) steps = [maintainer, lighter, session];
117
+ return { mode, warm, steps };
68
118
  }
69
119
 
70
- // The isolated summary request on one route. Its input budget belongs to
71
- // its OWN model, whereas the rebuilt conversation must fit the original
72
- // session's target/window.
120
+ // The summary request on one route: the conversation alone (earlier summary,
121
+ // dialogue, runtime conversation rows and one bounded line per tool call;
122
+ // skills and injections excluded), never the cached context. Its input budget belongs to its OWN model, split into
123
+ // batches when the conversation is larger, whereas the rebuilt conversation
124
+ // must fit the original session's target/window.
73
125
  function summarizeConversation(target, ctx) {
74
126
  const { sessionRef, sessionId, signal, sendOpts, compactPolicy } = ctx;
75
127
  const summaryWindow = positiveInt(target.contextWindow) || ctx.contextWindow;
@@ -107,7 +159,6 @@ function summarizeConversation(target, ctx) {
107
159
  fast: target.fast,
108
160
  timeoutMs: compactPolicy.handoffTimeoutMs,
109
161
  force: true,
110
- filterOldHistoryForIngest: true,
111
162
  }
112
163
  );
113
164
  }
@@ -117,24 +168,24 @@ function sumUsage(a, b) {
117
168
  return Object.fromEntries(Object.keys(a).map((key) => [key, (a[key] || 0) + (Number(b[key]) || 0)]));
118
169
  }
119
170
 
120
- // Summary policy. On the session's own provider and model the summary forks
121
- // the latest cached request (compact/forked-summary.mjs): the context is
122
- // already in the prompt cache, so reading it costs a tenth to a twentieth of
123
- // sending the text-only transcript uncached, and the summary sees the full
124
- // execution context. A different summary route (the maintenance model)
125
- // cannot share that cache; there the isolated text-only request runs: on
126
- // the usage ledger it cost a median $0.16 and 24.5 s against a projected
127
- // $0.19 and 27 s for a cache-read fork of the main model's median 500k-token
128
- // context (the fork is cheaper than a same-model isolated request, $0.20).
129
- // A fork that cannot run or fails falls back to the isolated request.
130
- async function summarizeOnRoute(route, ctx) {
131
- const shared = await summarizeOnSharedPrefix({
132
- sessionId: ctx.sessionId,
133
- route,
134
- conversationInput: ctx.conversationInput,
135
- signal: ctx.signal,
136
- timeoutMs: ctx.compactPolicy.handoffTimeoutMs,
137
- });
171
+ // The summary on one route. While the session's prompt cache is warm, the
172
+ // summary forks the latest cached request on the conversation's own model
173
+ // (compact/forked-summary.mjs): reading the cached context costs a tenth to a
174
+ // twentieth of sending the text-only transcript uncached, and the summary
175
+ // sees the full execution context. A cold cache would be rewritten in full,
176
+ // so the isolated text-only request runs instead; so does any other route
177
+ // (a cache is never shared across models). A fork that cannot run or fails
178
+ // falls back to the isolated request on the same route.
179
+ async function summarizeOnRoute(route, ctx, warm) {
180
+ const shared = warm
181
+ ? await summarizeOnSharedPrefix({
182
+ sessionId: ctx.sessionId,
183
+ route,
184
+ conversationInput: ctx.conversationInput,
185
+ signal: ctx.signal,
186
+ timeoutMs: ctx.compactPolicy.handoffTimeoutMs,
187
+ })
188
+ : { fallback: 'cold_cache' };
138
189
  if (shared.generated) return shared.generated;
139
190
  ctx.signal?.throwIfAborted();
140
191
  const generated = await summarizeConversation(route, ctx);
@@ -145,34 +196,45 @@ async function summarizeOnRoute(route, ctx) {
145
196
  };
146
197
  }
147
198
 
148
- // A maintenance summary model that is out of quota must not take the whole
149
- // turn down: the conversation's own model still has budget and can
150
- // summarize. Only exhaustion reroutes — every other compaction failure
151
- // (overflow, malformed summary, transport) keeps its existing handling, and
152
- // an aborted turn stays aborted.
153
- async function summarizeWithQuotaFallback(ctx, routeInput) {
154
- const route = await resolveCompactionRoute(routeInput);
155
- try {
156
- return { route, generated: await summarizeOnRoute(route, ctx) };
157
- } catch (error) {
158
- ctx.signal?.throwIfAborted();
159
- const status = Number(error?.status || error?.httpStatus || error?.response?.status || 0);
160
- const exhausted = error?.code === 'provider_accounts_exhausted' || status === 429 || isAccountQuotaError(error);
161
- const fallback =
162
- route.source === 'maintenance' && exhausted
163
- ? await resolveCompactionRoute({ ...routeInput, allowMaintenance: false })
164
- : null;
165
- if (!fallback || (fallback.providerName === route.providerName && fallback.model === route.model)) {
166
- error.compactRoute = { provider: route.providerName, model: route.model };
167
- throw error;
168
- }
199
+ // A Maintainer or lighter model that is out of quota or not available to the
200
+ // account must not take the whole turn down: the next route still can
201
+ // summarize. Every other compaction failure (overflow, malformed summary,
202
+ // transport) keeps its existing handling, and an aborted turn stays aborted.
203
+ function fallsToNextRoute(error) {
204
+ const status = Number(error?.status || error?.httpStatus || error?.response?.status || 0);
205
+ return (
206
+ error?.code === 'provider_accounts_exhausted' ||
207
+ error?.code === 'model_not_found' ||
208
+ status === 429 ||
209
+ status === 404 ||
210
+ isAccountQuotaError(error)
211
+ );
212
+ }
213
+
214
+ // Resolving a route (provider init, catalog lookup) is part of its step: a
215
+ // quota or availability failure there moves to the next route like one from
216
+ // the summary request. The conversation's own model is the last step, so its
217
+ // failure always ends the compaction.
218
+ async function summarizeOnPlan(ctx, plan) {
219
+ const tried = new Set();
220
+ let failure = null;
221
+ for (const step of plan.steps) {
222
+ let route = null;
169
223
  try {
170
- return { route: fallback, generated: await summarizeOnRoute(fallback, ctx) };
171
- } catch (fallbackError) {
172
- fallbackError.compactRoute = { provider: fallback.providerName, model: fallback.model };
173
- throw fallbackError;
224
+ route = await step();
225
+ ctx.signal?.throwIfAborted();
226
+ const key = route && `${route.providerName}\u0000${route.model}`;
227
+ if (!route || tried.has(key)) continue;
228
+ tried.add(key);
229
+ return { route, generated: await summarizeOnRoute(route, ctx, plan.warm) };
230
+ } catch (error) {
231
+ ctx.signal?.throwIfAborted();
232
+ if (route) error.compactRoute = { provider: route.providerName, model: route.model };
233
+ if (route?.source === 'session' || !fallsToNextRoute(error)) throw error;
234
+ failure = error;
174
235
  }
175
236
  }
237
+ throw failure;
176
238
  }
177
239
 
178
240
  // The window this compaction must fit (calibrated to the provider's token
@@ -200,7 +262,10 @@ function freshContextBudget({ compactPolicy, sessionRef, provider, compactBudget
200
262
  return { contextWindow, hardBudget, conversationInput, conversationTokens, conversationThresholdTokens };
201
263
  }
202
264
 
203
- function freshContextBuildOptions({ compactPolicy = {}, sessionId, goalReminderText, activeTurn }, budget) {
265
+ function freshContextBuildOptions(
266
+ { compactPolicy = {}, sessionId, goalReminderText, activeTurn, referenceLatestAttachments },
267
+ budget
268
+ ) {
204
269
  return {
205
270
  reserveTokens: compactPolicy.reserveTokens,
206
271
  maxBudgetTokens: budget.hardBudget,
@@ -209,6 +274,7 @@ function freshContextBuildOptions({ compactPolicy = {}, sessionId, goalReminderT
209
274
  sessionId,
210
275
  latestUserPrefix: goalReminderText,
211
276
  activeTurn,
277
+ referenceLatestAttachments: referenceLatestAttachments === true,
212
278
  };
213
279
  }
214
280
 
@@ -230,6 +296,27 @@ export function previewFreshContextCompaction(input) {
230
296
  return { beforeTokens, afterTokens, reducesTokens: afterTokens < beforeTokens, summaryTriggered };
231
297
  }
232
298
 
299
+ function buildFreshContextResult(
300
+ { messages, compactBudgetTokens, buildOptions, requireReduction, sessionId },
301
+ handoffText
302
+ ) {
303
+ const result = freshContextCompactMessages(messages, compactBudgetTokens, { ...buildOptions, handoffText });
304
+ // A generated summary can differ from its prediction. Never replace the
305
+ // original with an automatic result that is equal-sized or larger.
306
+ if (requireReduction && estimateMessagesTokens(result.messages) >= estimateMessagesTokens(messages)) {
307
+ return {
308
+ ...result,
309
+ messages,
310
+ freshContext: false,
311
+ skipped: true,
312
+ diagnostics: { ...result.diagnostics, noOp: true, reason: 'no_token_reduction' },
313
+ };
314
+ }
315
+ // The rewritten transcript is no longer what the saved request carried.
316
+ if (result.freshContext) retireCompactionFork(sessionId);
317
+ return result;
318
+ }
319
+
233
320
  export async function runFreshContextCompact({
234
321
  sessionRef,
235
322
  messages,
@@ -246,35 +333,24 @@ export async function runFreshContextCompact({
246
333
  getProviderFn,
247
334
  initProvidersFn,
248
335
  requireReduction = false,
336
+ referenceLatestAttachments = false,
249
337
  } = {}) {
250
338
  const startedAt = Date.now();
251
339
  signal?.throwIfAborted();
252
- const { contextWindow, hardBudget, conversationInput, conversationTokens, conversationThresholdTokens } =
253
- freshContextBudget({ compactPolicy, sessionRef, provider, compactBudgetTokens, messages });
340
+ const { contextWindow, hardBudget, conversationTokens, conversationThresholdTokens } = freshContextBudget({
341
+ compactPolicy,
342
+ sessionRef,
343
+ provider,
344
+ compactBudgetTokens,
345
+ messages,
346
+ });
254
347
  const summaryTriggered = conversationTokens >= conversationThresholdTokens;
255
- const build = (handoffText) => {
256
- const result = freshContextCompactMessages(messages, compactBudgetTokens, {
257
- ...freshContextBuildOptions(
258
- { compactPolicy, sessionId, goalReminderText, activeTurn },
259
- { contextWindow, hardBudget }
260
- ),
261
- handoffText,
262
- });
263
- // A generated summary can differ from its prediction. Never replace the
264
- // original with an automatic result that is equal-sized or larger.
265
- if (requireReduction && estimateMessagesTokens(result.messages) >= estimateMessagesTokens(messages)) {
266
- return {
267
- ...result,
268
- messages,
269
- freshContext: false,
270
- skipped: true,
271
- diagnostics: { ...result.diagnostics, noOp: true, reason: 'no_token_reduction' },
272
- };
273
- }
274
- // The rewritten transcript is no longer what the saved request carried.
275
- if (result.freshContext) retireCompactionFork(sessionId);
276
- return result;
277
- };
348
+ const buildOptions = freshContextBuildOptions(
349
+ { compactPolicy, sessionId, goalReminderText, activeTurn, referenceLatestAttachments },
350
+ { contextWindow, hardBudget }
351
+ );
352
+ const build = (handoffText) =>
353
+ buildFreshContextResult({ messages, compactBudgetTokens, buildOptions, requireReduction, sessionId }, handoffText);
278
354
  const pipeline = {
279
355
  mode: summaryTriggered ? 'conversation-summary' : 'rules',
280
356
  conversationTokens,
@@ -291,7 +367,17 @@ export async function runFreshContextCompact({
291
367
  pipeline: { ...pipeline, totalMs: Date.now() - startedAt },
292
368
  });
293
369
  }
294
- const { route, generated } = await summarizeWithQuotaFallback(
370
+ const plan = compactionSummaryPlan({
371
+ sessionRef,
372
+ sessionId,
373
+ provider,
374
+ model,
375
+ config,
376
+ signal,
377
+ getProviderFn,
378
+ initProvidersFn,
379
+ });
380
+ const { route, generated } = await summarizeOnPlan(
295
381
  {
296
382
  sessionRef,
297
383
  sessionId,
@@ -301,9 +387,11 @@ export async function runFreshContextCompact({
301
387
  compactBudgetTokens,
302
388
  hardBudget,
303
389
  contextWindow,
304
- conversationInput,
390
+ // What the summary is written from: the measured conversation plus one
391
+ // bounded line per tool call (conversationCompactionInput).
392
+ conversationInput: conversationCompactionInput(messages, { summary: true }),
305
393
  },
306
- { sessionRef, provider, model, config, signal, getProviderFn, initProvidersFn }
394
+ plan
307
395
  );
308
396
  signal?.throwIfAborted();
309
397
  const result = build(generated.summary);
@@ -315,6 +403,8 @@ export async function runFreshContextCompact({
315
403
  pipeline: {
316
404
  ...pipeline,
317
405
  handoffSource: 'session-local',
406
+ summaryMode: plan.mode,
407
+ cacheWarm: plan.warm,
318
408
  summaryRoute: route.source,
319
409
  summaryProvider: route.providerName,
320
410
  summaryModel: route.model,
@@ -58,6 +58,7 @@ export function createLoopState({ provider, messages, model, tools, cwd, sendOpt
58
58
  lastUsage: undefined,
59
59
  firstTurnUsage: undefined,
60
60
  response: undefined,
61
+ lastStreamedChars: 0,
61
62
  lastSendTools: tools,
62
63
  contextOverflowRetryUsed: false,
63
64
  // Set when a provider context-overflow refusal triggers the in-turn
@@ -133,6 +134,10 @@ export function createLoopState({ provider, messages, model, tools, cwd, sendOpt
133
134
  sessionId,
134
135
  sessionAgent: state.sessionAgent,
135
136
  drainSteering: state.drainSteering,
137
+ // Only a provider that can offer a higher max_tokens implements this.
138
+ escalateMaxTokens: () => state.provider?.escalatedMaxTokens?.(state.model, state.opts, state.lastSendTools) ?? null,
139
+ // Characters the latest send put on screen through the live text stream.
140
+ streamedChars: () => state.lastStreamedChars,
136
141
  });
137
142
  return state;
138
143
  }
@@ -6,6 +6,7 @@
6
6
  // continuations with no intervening tool batch, the current text is accepted
7
7
  // as the final answer.
8
8
  import { writeLoopDiagnostic } from './diagnostic.mjs';
9
+ import { resetOutputLimitRecovery } from './output-limit.mjs';
9
10
 
10
11
  const PROVIDER_CONTINUATION_NO_TOOL_LIMIT = Math.max(
11
12
  1,
@@ -38,6 +39,7 @@ export function resumeContinuation(
38
39
  state.providerContinuationCount += 1;
39
40
  state.continuationsSinceToolBatch += 1;
40
41
  state.emptyNudgeStreak = 0;
42
+ resetOutputLimitRecovery(state);
41
43
  trace(iteration, 'provider_continuation', {
42
44
  signal,
43
45
  stop_reason: stopReason,
@@ -25,7 +25,7 @@ export function nudgeEmptyTurn(response, { stopReason, isIncompleteStop }, { sta
25
25
  messages.push({
26
26
  role: 'user',
27
27
  content: isIncompleteStop
28
- ? `[mixdog-runtime] Empty truncated continuation (stopReason=${stopReason}). Return the remaining final handoff; use tools only for required evidence still missing.`
28
+ ? `[mixdog-runtime] Empty incomplete turn (stopReason=${stopReason}). Return the remaining final handoff; use tools only for required evidence still missing.`
29
29
  : `[mixdog-runtime] Empty response (${state.emptyNudgeStreak}/${EMPTY_NUDGE_MAX}). Return final text, or use tools only for required evidence still missing.`,
30
30
  });
31
31
  return { action: 'continue', response };
@@ -1,13 +1,22 @@
1
- // Max-output ladder.
1
+ // Max-output recovery — the single rule for every turn the provider cut at its
2
+ // output ceiling, with or without visible text.
2
3
  //
3
- // A provider max-output stop is not a completed assistant turn, even when it
4
- // contains useful text. Preserve each partial in the provider transcript and
5
- // grant at most MAX_OUTPUT_RECOVERY_LIMIT direct continuations before
6
- // surfacing a hard truncation.
4
+ // A max-output stop is not a completed assistant turn. A turn that spent its
5
+ // whole budget (typically on thinking) would repeat identically if re-sent
6
+ // unchanged, so recovery changes the request each time:
7
+ // 1. Escalation, once per progress window: re-send the same transcript with
8
+ // a higher max_tokens when the provider has one to offer (no message
9
+ // added; the cut partial is discarded and regenerated).
10
+ // 2. Up to MAX_OUTPUT_RECOVERY_LIMIT resume prompts at the normal
11
+ // max_tokens, preserving any partial text in the transcript.
12
+ // 3. Then the turn ends with a visible notice that the output limit was hit.
13
+ // Real progress (a tool-call turn, a continued text turn) resets both steps.
14
+ import { isOutputLimitStopReason } from '../termination.mjs';
15
+
7
16
  const MAX_OUTPUT_RECOVERY_LIMIT = 3;
8
- const MAX_OUTPUT_EXHAUSTED_NOTICE = `[mixdog-runtime] Output remained truncated after ${MAX_OUTPUT_RECOVERY_LIMIT} continuation attempts.`;
17
+ const MAX_OUTPUT_EXHAUSTED_NOTICE = `[mixdog-runtime] The model hit its output limit and the response stayed truncated after ${MAX_OUTPUT_RECOVERY_LIMIT} recovery attempts.`;
9
18
  const MAX_OUTPUT_RESUME_PROMPT =
10
- 'Output token limit hit. Resume directly — no apology, no recap. Pick up exactly where the previous text stopped.';
19
+ '[mixdog-runtime] Output token limit hit. Resume directly — no apology, no recap. Pick up exactly where you left off, and break the remaining work into smaller pieces.';
11
20
  // A response whose stream was cut mid-text (and could not be withdrawn from
12
21
  // the screen) resumes through the same ladder with its own reason.
13
22
  const STREAM_CUT_RESUME_PROMPT =
@@ -19,25 +28,65 @@ function resumePrompt(response, attempt) {
19
28
  : { role: 'user', content: MAX_OUTPUT_RESUME_PROMPT, meta: { source: 'max-output-recovery', attempt } };
20
29
  }
21
30
 
22
- export function resolveOutputLimit(response, { state, segments, messages }) {
23
- segments.record(response.content);
31
+ // Real progress ends the recovery window: the next cut starts a fresh ladder.
32
+ export function resetOutputLimitRecovery(state) {
33
+ state.maxOutputRecoveryCount = 0;
34
+ state.outputEscalationUsed = false;
35
+ }
36
+
37
+ // The escalation discards the cut response, so whatever it already put on the
38
+ // live stream must be withdrawn through the owner's retraction channel — the
39
+ // same one transport replays use. Text the owner cannot withdraw would be
40
+ // shown twice, so that case does not escalate.
41
+ async function withdrawStreamedText(opts, chars) {
42
+ if (!(chars > 0)) return true;
43
+ if (typeof opts?.onTextReset !== 'function') return false;
44
+ try {
45
+ return (await opts.onTextReset({ chars, reasoning: false, reason: 'output-limit-escalation' })) === true;
46
+ } catch {
47
+ return false;
48
+ }
49
+ }
50
+
51
+ export async function resolveOutputLimit(
52
+ response,
53
+ { state, segments, messages, opts, escalateMaxTokens, streamedChars }
54
+ ) {
55
+ const hasContent = typeof response.content === 'string' && response.content.trim().length > 0;
56
+ if (!state.outputEscalationUsed && isOutputLimitStopReason(response.stopReason ?? response.stop_reason)) {
57
+ state.outputEscalationUsed = true;
58
+ // A provider may resolve the higher limit asynchronously (route lookup);
59
+ // failing to find one only skips the escalation.
60
+ const limit = await Promise.resolve()
61
+ .then(escalateMaxTokens)
62
+ .catch(() => null);
63
+ if (limit > 0 && (await withdrawStreamedText(opts, streamedChars()))) {
64
+ // Taken off the shared options by the very next send (sendProviderRequest).
65
+ opts.maxOutputTokensOverride = limit;
66
+ return { action: 'continue', response };
67
+ }
68
+ }
69
+ if (hasContent) segments.record(response.content);
24
70
  if (state.maxOutputRecoveryCount < MAX_OUTPUT_RECOVERY_LIMIT) {
25
71
  // The partial assistant turn must be visible to the model so it can
26
72
  // resume at the exact cutoff instead of reconstructing or repeating
27
73
  // it. askSession persists this natural recovery chain;
28
74
  // historyContent below prevents the aggregate returned to callers
29
- // from being persisted a second time.
30
- segments.commitIntermediate(response);
75
+ // from being persisted a second time. An empty cut has no output to keep.
76
+ if (hasContent) segments.commitIntermediate(response);
31
77
  state.maxOutputRecoveryCount += 1;
32
78
  messages.push(resumePrompt(response, state.maxOutputRecoveryCount));
33
79
  return { action: 'continue', response };
34
80
  }
35
- const terminalSegment = `${response.content}\n\n${MAX_OUTPUT_EXHAUSTED_NOTICE}`;
81
+ const terminalSegment = hasContent
82
+ ? `${response.content}\n\n${MAX_OUTPUT_EXHAUSTED_NOTICE}`
83
+ : MAX_OUTPUT_EXHAUSTED_NOTICE;
84
+ const earlier = (hasContent ? segments.parts.slice(0, -1) : segments.parts).join('');
36
85
  return {
37
86
  action: 'break',
38
87
  response: {
39
88
  ...response,
40
- content: `${segments.parts.slice(0, -1).join('')}${terminalSegment}`,
89
+ content: `${earlier}${earlier && !hasContent ? '\n\n' : ''}${terminalSegment}`,
41
90
  historyContent: terminalSegment,
42
91
  maxOutputRecoveryAttempts: state.maxOutputRecoveryCount,
43
92
  },
@@ -14,16 +14,27 @@ import { HIDDEN_AGENT_NAMES } from './hidden-agents.mjs';
14
14
  import { INCOMPLETE_STOP_REASONS, isOutputLimitStopReason, providerContinuationSignal } from './termination.mjs';
15
15
  import { appendAgentTrace } from '../../agent-trace.mjs';
16
16
  import { createTurnSegments } from './no-tool-turn/segments.mjs';
17
- import { resolveOutputLimit } from './no-tool-turn/output-limit.mjs';
17
+ import { resetOutputLimitRecovery, resolveOutputLimit } from './no-tool-turn/output-limit.mjs';
18
18
  import { resolveRefusal } from './no-tool-turn/refusal.mjs';
19
19
  import { resumeContinuation } from './no-tool-turn/continuation.mjs';
20
20
  import { nudgeEmptyTurn } from './no-tool-turn/empty-nudge.mjs';
21
21
 
22
- export function createNoToolTurnResolver({ messages, opts, sessionId, sessionAgent, drainSteering }) {
22
+ // `escalateMaxTokens()` asks the live provider for a higher one-shot max_tokens
23
+ // (number) or reports none (null/undefined) — the loop stays provider-agnostic.
24
+ export function createNoToolTurnResolver({
25
+ messages,
26
+ opts,
27
+ sessionId,
28
+ sessionAgent,
29
+ drainSteering,
30
+ escalateMaxTokens = () => null,
31
+ streamedChars = () => 0,
32
+ }) {
23
33
  const segments = createTurnSegments({ messages, opts });
24
34
  // Per-turn recovery counters shared by the ladders.
25
35
  const state = {
26
36
  maxOutputRecoveryCount: 0,
37
+ outputEscalationUsed: false,
27
38
  refusalRetryUsed: false,
28
39
  emptyNudgeStreak: 0,
29
40
  // Structured provider continuation signals honored this turn. Diagnostic
@@ -42,7 +53,7 @@ export function createNoToolTurnResolver({ messages, opts, sessionId, sessionAge
42
53
  /* best-effort telemetry */
43
54
  }
44
55
  };
45
- const ladder = { state, segments, messages, sessionId, trace };
56
+ const ladder = { state, segments, messages, opts, sessionId, trace, escalateMaxTokens, streamedChars };
46
57
 
47
58
  // A no-tool message ends the turn. Unresolved tool failures remain visible
48
59
  // in history and can be reported directly without a synthetic continuation
@@ -66,11 +77,13 @@ export function createNoToolTurnResolver({ messages, opts, sessionId, sessionAge
66
77
  // { action: 'continue' | 'break', response }. 'continue' means the
67
78
  // recovery appended to the transcript and the loop must re-send;
68
79
  // 'break' returns the (possibly aggregated) terminal response.
69
- resolve(response, iteration) {
80
+ async resolve(response, iteration) {
70
81
  const hasContent = typeof response.content === 'string' && response.content.trim().length > 0;
71
82
  const stopReason = response.stopReason ?? response.stop_reason ?? null;
72
83
  const isOutputLimitStop = isOutputLimitStopReason(stopReason);
73
- if (hasContent && (isOutputLimitStop || response.streamCut === true)) return resolveOutputLimit(response, ladder);
84
+ if (isOutputLimitStop || (hasContent && response.streamCut === true)) {
85
+ return resolveOutputLimit(response, ladder);
86
+ }
74
87
  if (stopReason === 'refusal') return resolveRefusal(response, hasContent, ladder);
75
88
  // Output-limit stops keep the bounded max-output ladder above and
76
89
  // refusals (already returned) keep the bounded refusal retry; both
@@ -108,13 +121,16 @@ export function createNoToolTurnResolver({ messages, opts, sessionId, sessionAge
108
121
  })
109
122
  ) {
110
123
  state.emptyNudgeStreak = 0;
124
+ resetOutputLimitRecovery(state);
111
125
  return { action: 'continue', response };
112
126
  }
113
127
  return finishTurn(response);
114
128
  },
115
- // A turn that produced tool calls is never an empty contract violation.
129
+ // A turn that produced tool calls is never an empty contract violation,
130
+ // and it is real progress: a later output-limit cut starts a fresh ladder.
116
131
  noteToolCallTurn() {
117
132
  state.emptyNudgeStreak = 0;
133
+ resetOutputLimitRecovery(state);
118
134
  },
119
135
  noteToolBatchCompleted() {
120
136
  state.continuationsSinceToolBatch = 0;