mixdog 1.3.0 → 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 (423) hide show
  1. package/NOTICE.md +52 -9
  2. package/README.md +8 -4
  3. package/package.json +2 -2
  4. package/scripts/native-release-status.mjs +238 -0
  5. package/src/defaults/shipped-history.json +8 -1
  6. package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
  7. package/src/defaults/skills/setup/SKILL.md +23 -1
  8. package/src/defaults/skills/setup/references/actions.md +54 -4
  9. package/src/defaults/skills/setup/references/surfaces.md +23 -3
  10. package/src/defaults/skills/setup-guide/SKILL.md +17 -10
  11. package/src/defaults/skills/visualize/SKILL.md +9 -6
  12. package/src/defaults/skills/visualize/references/craft.md +8 -1
  13. package/src/help.mjs +1 -1
  14. package/src/lib/keychain-cjs.cjs +4 -1
  15. package/src/lib/rules-builder.cjs +14 -7
  16. package/src/output-styles/common.md +13 -8
  17. package/src/rules/agent-mode/AGENT_MODE.md +9 -6
  18. package/src/rules/agent-mode/MIXY.md +11 -6
  19. package/src/rules/lead/LEAD.md +2 -2
  20. package/src/rules/shared/10-tool-workflow.md +4 -0
  21. package/src/rules/shared/20-research.md +2 -0
  22. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
  23. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
  24. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
  25. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
  26. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
  27. package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
  28. package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
  29. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
  30. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  31. package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
  32. package/src/runtime/agent/orchestrator/config.mjs +11 -0
  33. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +81 -16
  34. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -4
  35. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
  36. package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
  37. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +80 -31
  38. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
  39. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  40. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +67 -38
  41. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
  42. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +7 -2
  43. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
  44. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +34 -16
  45. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
  46. package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
  47. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
  48. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
  49. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +19 -1
  50. package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
  51. package/src/runtime/agent/orchestrator/providers/lib/tool-call-wire-ids.mjs +65 -0
  52. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -1
  53. package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +3 -1
  54. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +1 -0
  55. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +5 -5
  56. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +9 -5
  57. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +9 -1
  58. package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +8 -2
  59. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +10 -2
  60. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +30 -3
  61. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +1 -1
  62. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +3 -1
  63. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +3 -1
  64. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +61 -8
  65. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +14 -2
  66. package/src/runtime/agent/orchestrator/providers/registry.mjs +9 -0
  67. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +17 -0
  68. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
  69. package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
  70. package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
  71. package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
  72. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
  73. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +5 -5
  74. package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
  75. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
  76. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +12 -1
  77. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +17 -6
  78. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +41 -10
  79. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +6 -4
  80. package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
  81. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +2 -0
  82. package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
  83. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
  84. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
  85. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +21 -2
  86. package/src/runtime/agent/orchestrator/session/agent-room.mjs +149 -45
  87. package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +80 -0
  88. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  89. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +14 -11
  90. package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
  91. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +68 -18
  92. package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
  93. package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
  94. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
  95. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +5 -1
  96. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +1 -1
  97. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
  98. package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
  99. package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +230 -0
  100. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +156 -25
  101. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
  102. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +21 -9
  103. package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
  104. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
  105. package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
  106. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
  107. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +189 -105
  108. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +9 -0
  109. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
  110. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +111 -9
  111. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +11 -14
  112. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
  113. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +38 -5
  114. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +21 -0
  115. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
  116. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
  117. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
  118. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +46 -4
  119. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
  120. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +18 -8
  121. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +4 -0
  122. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  123. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  124. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
  125. package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +99 -0
  126. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
  127. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
  128. package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
  129. package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
  130. package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +50 -0
  131. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +40 -1
  132. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
  133. package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +20 -0
  134. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +62 -47
  135. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +6 -8
  136. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +12 -1
  137. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
  138. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +7 -1
  139. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
  140. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +1 -1
  141. package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
  142. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +5 -2
  143. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +42 -10
  144. package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
  145. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +35 -4
  146. package/src/runtime/agent/orchestrator/session/routine-result.mjs +179 -16
  147. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +5 -1
  148. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
  149. package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
  150. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
  151. package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
  152. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
  153. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +8 -3
  154. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +36 -1
  155. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -1
  156. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
  157. package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
  158. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
  159. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
  160. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
  161. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +49 -14
  162. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +134 -17
  163. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +35 -13
  164. package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
  165. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +4 -3
  166. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
  167. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
  168. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
  169. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
  170. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +6 -14
  171. package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
  172. package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
  173. package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
  174. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
  175. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
  176. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +34 -21
  177. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
  178. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
  179. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
  180. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
  181. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
  182. package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
  183. package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
  184. package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
  185. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
  186. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
  187. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
  188. package/src/runtime/attachments/attachment-reference.mjs +155 -0
  189. package/src/runtime/attachments/limits.mjs +5 -1
  190. package/src/runtime/attachments/office-extract.mjs +3 -6
  191. package/src/runtime/attachments/pdf-extract.mjs +84 -10
  192. package/src/runtime/attachments/store.mjs +55 -30
  193. package/src/runtime/browser-bridge/client.mjs +73 -20
  194. package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
  195. package/src/runtime/channels/lib/scheduler.mjs +14 -7
  196. package/src/runtime/channels/lib/state-file.mjs +15 -15
  197. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
  198. package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
  199. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
  200. package/src/runtime/channels/lib/worker-main.mjs +3 -2
  201. package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
  202. package/src/runtime/effort-judge/judge-client.mjs +6 -3
  203. package/src/runtime/effort-judge/model-install.mjs +16 -8
  204. package/src/runtime/effort-judge/model-manifest.json +7 -7
  205. package/src/runtime/local-provider/asset-installer.mjs +10 -4
  206. package/src/runtime/local-provider/catalog.mjs +5 -2
  207. package/src/runtime/local-provider/registered-models.mjs +64 -27
  208. package/src/runtime/local-provider/server.mjs +2 -3
  209. package/src/runtime/media/adapters/image-api.mjs +89 -0
  210. package/src/runtime/media/auth.mjs +64 -1
  211. package/src/runtime/media/catalog.mjs +66 -4
  212. package/src/runtime/media/jobs.mjs +4 -0
  213. package/src/runtime/media/lanes.mjs +46 -5
  214. package/src/runtime/media/media-usage.mjs +17 -0
  215. package/src/runtime/media/renditions.mjs +15 -15
  216. package/src/runtime/media/store.mjs +36 -35
  217. package/src/runtime/media/tool.mjs +8 -1
  218. package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +8 -2
  219. package/src/runtime/memory/lib/embedding-model-files.mjs +30 -7
  220. package/src/runtime/memory/lib/ingest/transcript-tail.mjs +16 -7
  221. package/src/runtime/memory/lib/pg/process.mjs +8 -4
  222. package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
  223. package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
  224. package/src/runtime/office/core/journal.mjs +7 -14
  225. package/src/runtime/office/design/library/design-library-core.mjs +4 -16
  226. package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
  227. package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
  228. package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
  229. package/src/runtime/office/pdf/pdf-render.mjs +30 -18
  230. package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
  231. package/src/runtime/office/portable/portable-soffice.mjs +4 -0
  232. package/src/runtime/shared/agent-kind-migration.mjs +200 -0
  233. package/src/runtime/shared/agent-kind.mjs +152 -0
  234. package/src/runtime/shared/agent-message-envelope.d.mts +6 -0
  235. package/src/runtime/shared/agent-message-envelope.mjs +44 -0
  236. package/src/runtime/shared/agent-reap-state.d.mts +4 -0
  237. package/src/runtime/shared/agent-route-config.d.mts +1 -0
  238. package/src/runtime/shared/agent-route-config.mjs +22 -8
  239. package/src/runtime/shared/agent-store.mjs +149 -0
  240. package/src/runtime/shared/agent-task-starts.mjs +27 -0
  241. package/src/runtime/shared/atomic-file.d.mts +50 -0
  242. package/src/runtime/shared/atomic-file.mjs +394 -132
  243. package/src/runtime/shared/background-tasks.mjs +7 -4
  244. package/src/runtime/shared/bot-hops.mjs +30 -0
  245. package/src/runtime/shared/bot-split-migration.mjs +457 -0
  246. package/src/runtime/shared/bounded-download.mjs +4 -2
  247. package/src/runtime/shared/config-patch.mjs +20 -1
  248. package/src/runtime/shared/config.mjs +39 -9
  249. package/src/runtime/shared/connector-catalog.d.mts +1 -2
  250. package/src/runtime/shared/connector-catalog.mjs +2 -4
  251. package/src/runtime/shared/connector-triggers.mjs +2 -0
  252. package/src/runtime/shared/defaults-separation.mjs +6 -2
  253. package/src/runtime/shared/delegated-approval-host.mjs +74 -0
  254. package/src/runtime/shared/err-text.mjs +7 -1
  255. package/src/runtime/shared/error-presentation.mjs +113 -10
  256. package/src/runtime/shared/file-has-data.mjs +28 -0
  257. package/src/runtime/shared/handoff-artifacts.d.mts +20 -0
  258. package/src/runtime/shared/handoff-artifacts.mjs +61 -0
  259. package/src/runtime/shared/llm/provider-model-identities.mjs +6 -1
  260. package/src/runtime/shared/llm/usage-accounting.mjs +7 -1
  261. package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
  262. package/src/runtime/shared/model-route-fallback.mjs +79 -7
  263. package/src/runtime/shared/pending-agent-work.mjs +54 -0
  264. package/src/runtime/shared/plain-preview.mjs +22 -17
  265. package/src/runtime/shared/process-lifecycle.mjs +8 -12
  266. package/src/runtime/shared/projects.mjs +50 -41
  267. package/src/runtime/shared/provider-accounts.mjs +17 -12
  268. package/src/runtime/shared/routine-trigger.d.mts +6 -1
  269. package/src/runtime/shared/routine-trigger.mjs +14 -6
  270. package/src/runtime/shared/run-child.d.mts +3 -0
  271. package/src/runtime/shared/run-child.mjs +24 -3
  272. package/src/runtime/shared/sensitive-path-rules.d.mts +15 -0
  273. package/src/runtime/shared/sensitive-path-rules.mjs +256 -0
  274. package/src/runtime/shared/session-baseline-store.mjs +8 -2
  275. package/src/runtime/shared/singleton-owner.mjs +7 -6
  276. package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
  277. package/src/runtime/shared/staged-update.mjs +4 -2
  278. package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
  279. package/src/runtime/shared/tool-execution-contract.mjs +6 -0
  280. package/src/runtime/shared/transcript-metadata.mjs +28 -3
  281. package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
  282. package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
  283. package/src/runtime/shared/turn-snapshot.mjs +44 -2
  284. package/src/runtime/shared/usage-readout-totals.mjs +15 -13
  285. package/src/runtime/shared/user-cwd.mjs +9 -4
  286. package/src/runtime/shared/user-data-guard.mjs +46 -44
  287. package/src/runtime/shared/utf8-cut.mjs +10 -0
  288. package/src/runtime/shared/worktrees/activity-gate.mjs +269 -61
  289. package/src/runtime/shared/worktrees/registry.mjs +26 -26
  290. package/src/runtime/tidy/core-install.mjs +26 -4
  291. package/src/runtime/tidy/install.mjs +91 -23
  292. package/src/runtime/web-search/lib/config.mjs +4 -16
  293. package/src/session-runtime/agent-home-delivery.mjs +859 -136
  294. package/src/session-runtime/agent-room-delivery.mjs +167 -19
  295. package/src/session-runtime/agent-task-sessions.mjs +831 -69
  296. package/src/session-runtime/agent-tool-routing.mjs +1 -0
  297. package/src/session-runtime/boot/apis.mjs +58 -24
  298. package/src/session-runtime/boot/config.mjs +1 -0
  299. package/src/session-runtime/boot/facade.mjs +11 -1
  300. package/src/session-runtime/boot/infrastructure.mjs +14 -2
  301. package/src/session-runtime/boot/providers.mjs +16 -6
  302. package/src/session-runtime/boot/shared.mjs +12 -6
  303. package/src/session-runtime/boot/tools.mjs +2 -2
  304. package/src/session-runtime/bot-status.mjs +432 -0
  305. package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
  306. package/src/session-runtime/config-lifecycle.mjs +78 -32
  307. package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
  308. package/src/session-runtime/handoff-attachments.mjs +593 -0
  309. package/src/session-runtime/held-requests.mjs +36 -33
  310. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
  311. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
  312. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +45 -6
  313. package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
  314. package/src/session-runtime/lifecycle/teardown.mjs +11 -3
  315. package/src/session-runtime/mcp-glue/connect.mjs +100 -186
  316. package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
  317. package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
  318. package/src/session-runtime/media-api.mjs +5 -2
  319. package/src/session-runtime/model-route/route-persist.mjs +2 -2
  320. package/src/session-runtime/model-route/route-tuning.mjs +11 -2
  321. package/src/session-runtime/model-route/set-route.mjs +82 -2
  322. package/src/session-runtime/native-web-search.mjs +2 -2
  323. package/src/session-runtime/new-session-config.mjs +6 -1
  324. package/src/session-runtime/owned-automation-run.mjs +8 -1
  325. package/src/session-runtime/room-member-gate.mjs +16 -3
  326. package/src/session-runtime/route-resolution.mjs +21 -19
  327. package/src/session-runtime/route-state.mjs +1 -59
  328. package/src/session-runtime/routine-home-turn.mjs +5 -3
  329. package/src/session-runtime/runtime-bootstrap.mjs +5 -3
  330. package/src/session-runtime/runtime-paths.mjs +11 -5
  331. package/src/session-runtime/runtime-tunables.mjs +6 -0
  332. package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
  333. package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
  334. package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
  335. package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
  336. package/src/session-runtime/services/agent-tool/helpers.mjs +24 -5
  337. package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
  338. package/src/session-runtime/services/agent-tool/send-flow.mjs +36 -24
  339. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +4 -0
  340. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
  341. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +12 -6
  342. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +5 -8
  343. package/src/session-runtime/services/agent-tool/tool-def.mjs +7 -0
  344. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
  345. package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
  346. package/src/session-runtime/services/agent-tool.mjs +6 -0
  347. package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
  348. package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
  349. package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
  350. package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
  351. package/src/session-runtime/services/plugin-admin.mjs +26 -26
  352. package/src/session-runtime/services/provider-admin.mjs +14 -4
  353. package/src/session-runtime/services/schedule-access.mjs +33 -4
  354. package/src/session-runtime/services/seeds.mjs +4 -3
  355. package/src/session-runtime/services/usage-dashboard.mjs +1 -1
  356. package/src/session-runtime/services/usage-stats-model.mjs +2 -1
  357. package/src/session-runtime/session-create/materialize.mjs +2 -3
  358. package/src/session-runtime/session-create/session-options.mjs +15 -2
  359. package/src/session-runtime/settings-builtin-tools-api.mjs +63 -1
  360. package/src/session-runtime/settings-compaction-api.mjs +86 -39
  361. package/src/session-runtime/setup-tool/executor.mjs +20 -3
  362. package/src/session-runtime/setup-tool/extended-actions.mjs +19 -7
  363. package/src/session-runtime/setup-tool/settings-contract.mjs +37 -1
  364. package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
  365. package/src/session-runtime/skills-api.mjs +12 -5
  366. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +265 -0
  367. package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
  368. package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
  369. package/src/session-runtime/tool-policy/policy-stage.mjs +165 -0
  370. package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
  371. package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
  372. package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
  373. package/src/session-runtime/tool-policy-refresh.mjs +47 -358
  374. package/src/session-runtime/tool-surface.mjs +5 -3
  375. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +99 -81
  376. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  377. package/src/session-runtime/turn/turn-run.mjs +2 -1
  378. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
  379. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +578 -301
  380. package/src/session-runtime/workflow-agents-api/agent-route.mjs +113 -53
  381. package/src/session-runtime/workflow-agents-api/onboarding.mjs +11 -4
  382. package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
  383. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
  384. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
  385. package/src/session-runtime/workspace/workspace-state.mjs +7 -6
  386. package/src/standalone/daemon-agent-control.mjs +4 -1
  387. package/src/standalone/daemon-session-control.mjs +60 -11
  388. package/src/standalone/daemon.mjs +34 -4
  389. package/src/standalone/local-session-runtime.mjs +22 -14
  390. package/src/standalone/session-client.mjs +7 -2
  391. package/src/standalone/session-protocol.mjs +7 -0
  392. package/src/standalone/session-service/agent-tree/agent-turns.mjs +46 -3
  393. package/src/standalone/session-service/agent-tree.mjs +4 -3
  394. package/src/standalone/session-service/session-calls/session-turns.mjs +19 -0
  395. package/src/standalone/session-service.mjs +11 -0
  396. package/src/standalone/session-state-projection.mjs +19 -9
  397. package/src/standalone/session-tool-output.mjs +35 -9
  398. package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
  399. package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
  400. package/src/tui/app/slash-commands.mjs +1 -1
  401. package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
  402. package/src/tui/dist/index.mjs +599 -342
  403. package/src/tui/prompt-history-store.mjs +16 -21
  404. package/src/tui/session/context-state.mjs +4 -0
  405. package/src/tui/session/notice-surface.mjs +3 -4
  406. package/src/tui/session/session-api/intake/abort.mjs +19 -1
  407. package/src/tui/session/session-api/media.mjs +2 -1
  408. package/src/tui/session/session-api/routes-agent-definitions.mjs +75 -0
  409. package/src/tui/session/session-api/routes-write.mjs +203 -0
  410. package/src/tui/session/session-api/routes.mjs +23 -236
  411. package/src/tui/session/session-api/settings.mjs +14 -1
  412. package/src/tui/session/session-flow/drain.mjs +28 -3
  413. package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
  414. package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
  415. package/src/tui/session/session-flow/steering.mjs +8 -3
  416. package/src/tui/session/tool-approval.mjs +185 -20
  417. package/src/tui/session/transcript-intake.mjs +4 -0
  418. package/src/tui/session/turn-live-call.mjs +11 -3
  419. package/src/tui/session/turn.mjs +36 -17
  420. package/src/tui/transcript-tool-failures.mjs +8 -2
  421. package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -8
  422. package/src/vendor/statusline/src/gateway/session-routes.mjs +6 -7
  423. package/src/rules/agent-mode/ROUTINE.md +0 -6
@@ -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
+ // and dialogue; tool output, skills and injections excluded), never the
122
+ // 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;
@@ -117,24 +169,24 @@ function sumUsage(a, b) {
117
169
  return Object.fromEntries(Object.keys(a).map((key) => [key, (a[key] || 0) + (Number(b[key]) || 0)]));
118
170
  }
119
171
 
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
- });
172
+ // The summary on one route. While the session's prompt cache is warm, the
173
+ // summary forks the latest cached request on the conversation's own model
174
+ // (compact/forked-summary.mjs): reading the cached context costs a tenth to a
175
+ // twentieth of sending the text-only transcript uncached, and the summary
176
+ // sees the full execution context. A cold cache would be rewritten in full,
177
+ // so the isolated text-only request runs instead; so does any other route
178
+ // (a cache is never shared across models). A fork that cannot run or fails
179
+ // falls back to the isolated request on the same route.
180
+ async function summarizeOnRoute(route, ctx, warm) {
181
+ const shared = warm
182
+ ? await summarizeOnSharedPrefix({
183
+ sessionId: ctx.sessionId,
184
+ route,
185
+ conversationInput: ctx.conversationInput,
186
+ signal: ctx.signal,
187
+ timeoutMs: ctx.compactPolicy.handoffTimeoutMs,
188
+ })
189
+ : { fallback: 'cold_cache' };
138
190
  if (shared.generated) return shared.generated;
139
191
  ctx.signal?.throwIfAborted();
140
192
  const generated = await summarizeConversation(route, ctx);
@@ -145,34 +197,45 @@ async function summarizeOnRoute(route, ctx) {
145
197
  };
146
198
  }
147
199
 
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
- }
200
+ // A Maintainer or lighter model that is out of quota or not available to the
201
+ // account must not take the whole turn down: the next route still can
202
+ // summarize. Every other compaction failure (overflow, malformed summary,
203
+ // transport) keeps its existing handling, and an aborted turn stays aborted.
204
+ function fallsToNextRoute(error) {
205
+ const status = Number(error?.status || error?.httpStatus || error?.response?.status || 0);
206
+ return (
207
+ error?.code === 'provider_accounts_exhausted' ||
208
+ error?.code === 'model_not_found' ||
209
+ status === 429 ||
210
+ status === 404 ||
211
+ isAccountQuotaError(error)
212
+ );
213
+ }
214
+
215
+ // Resolving a route (provider init, catalog lookup) is part of its step: a
216
+ // quota or availability failure there moves to the next route like one from
217
+ // the summary request. The conversation's own model is the last step, so its
218
+ // failure always ends the compaction.
219
+ async function summarizeOnPlan(ctx, plan) {
220
+ const tried = new Set();
221
+ let failure = null;
222
+ for (const step of plan.steps) {
223
+ let route = null;
169
224
  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;
225
+ route = await step();
226
+ ctx.signal?.throwIfAborted();
227
+ const key = route && `${route.providerName}\u0000${route.model}`;
228
+ if (!route || tried.has(key)) continue;
229
+ tried.add(key);
230
+ return { route, generated: await summarizeOnRoute(route, ctx, plan.warm) };
231
+ } catch (error) {
232
+ ctx.signal?.throwIfAborted();
233
+ if (route) error.compactRoute = { provider: route.providerName, model: route.model };
234
+ if (route?.source === 'session' || !fallsToNextRoute(error)) throw error;
235
+ failure = error;
174
236
  }
175
237
  }
238
+ throw failure;
176
239
  }
177
240
 
178
241
  // The window this compaction must fit (calibrated to the provider's token
@@ -200,7 +263,10 @@ function freshContextBudget({ compactPolicy, sessionRef, provider, compactBudget
200
263
  return { contextWindow, hardBudget, conversationInput, conversationTokens, conversationThresholdTokens };
201
264
  }
202
265
 
203
- function freshContextBuildOptions({ compactPolicy = {}, sessionId, goalReminderText, activeTurn }, budget) {
266
+ function freshContextBuildOptions(
267
+ { compactPolicy = {}, sessionId, goalReminderText, activeTurn, referenceLatestAttachments },
268
+ budget
269
+ ) {
204
270
  return {
205
271
  reserveTokens: compactPolicy.reserveTokens,
206
272
  maxBudgetTokens: budget.hardBudget,
@@ -209,6 +275,7 @@ function freshContextBuildOptions({ compactPolicy = {}, sessionId, goalReminderT
209
275
  sessionId,
210
276
  latestUserPrefix: goalReminderText,
211
277
  activeTurn,
278
+ referenceLatestAttachments: referenceLatestAttachments === true,
212
279
  };
213
280
  }
214
281
 
@@ -230,6 +297,27 @@ export function previewFreshContextCompaction(input) {
230
297
  return { beforeTokens, afterTokens, reducesTokens: afterTokens < beforeTokens, summaryTriggered };
231
298
  }
232
299
 
300
+ function buildFreshContextResult(
301
+ { messages, compactBudgetTokens, buildOptions, requireReduction, sessionId },
302
+ handoffText
303
+ ) {
304
+ const result = freshContextCompactMessages(messages, compactBudgetTokens, { ...buildOptions, handoffText });
305
+ // A generated summary can differ from its prediction. Never replace the
306
+ // original with an automatic result that is equal-sized or larger.
307
+ if (requireReduction && estimateMessagesTokens(result.messages) >= estimateMessagesTokens(messages)) {
308
+ return {
309
+ ...result,
310
+ messages,
311
+ freshContext: false,
312
+ skipped: true,
313
+ diagnostics: { ...result.diagnostics, noOp: true, reason: 'no_token_reduction' },
314
+ };
315
+ }
316
+ // The rewritten transcript is no longer what the saved request carried.
317
+ if (result.freshContext) retireCompactionFork(sessionId);
318
+ return result;
319
+ }
320
+
233
321
  export async function runFreshContextCompact({
234
322
  sessionRef,
235
323
  messages,
@@ -246,35 +334,19 @@ export async function runFreshContextCompact({
246
334
  getProviderFn,
247
335
  initProvidersFn,
248
336
  requireReduction = false,
337
+ referenceLatestAttachments = false,
249
338
  } = {}) {
250
339
  const startedAt = Date.now();
251
340
  signal?.throwIfAborted();
252
341
  const { contextWindow, hardBudget, conversationInput, conversationTokens, conversationThresholdTokens } =
253
342
  freshContextBudget({ compactPolicy, sessionRef, provider, compactBudgetTokens, messages });
254
343
  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
- };
344
+ const buildOptions = freshContextBuildOptions(
345
+ { compactPolicy, sessionId, goalReminderText, activeTurn, referenceLatestAttachments },
346
+ { contextWindow, hardBudget }
347
+ );
348
+ const build = (handoffText) =>
349
+ buildFreshContextResult({ messages, compactBudgetTokens, buildOptions, requireReduction, sessionId }, handoffText);
278
350
  const pipeline = {
279
351
  mode: summaryTriggered ? 'conversation-summary' : 'rules',
280
352
  conversationTokens,
@@ -291,7 +363,17 @@ export async function runFreshContextCompact({
291
363
  pipeline: { ...pipeline, totalMs: Date.now() - startedAt },
292
364
  });
293
365
  }
294
- const { route, generated } = await summarizeWithQuotaFallback(
366
+ const plan = compactionSummaryPlan({
367
+ sessionRef,
368
+ sessionId,
369
+ provider,
370
+ model,
371
+ config,
372
+ signal,
373
+ getProviderFn,
374
+ initProvidersFn,
375
+ });
376
+ const { route, generated } = await summarizeOnPlan(
295
377
  {
296
378
  sessionRef,
297
379
  sessionId,
@@ -303,7 +385,7 @@ export async function runFreshContextCompact({
303
385
  contextWindow,
304
386
  conversationInput,
305
387
  },
306
- { sessionRef, provider, model, config, signal, getProviderFn, initProvidersFn }
388
+ plan
307
389
  );
308
390
  signal?.throwIfAborted();
309
391
  const result = build(generated.summary);
@@ -315,6 +397,8 @@ export async function runFreshContextCompact({
315
397
  pipeline: {
316
398
  ...pipeline,
317
399
  handoffSource: 'session-local',
400
+ summaryMode: plan.mode,
401
+ cacheWarm: plan.warm,
318
402
  summaryRoute: route.source,
319
403
  summaryProvider: route.providerName,
320
404
  summaryModel: route.model,
@@ -8,6 +8,7 @@ import { resolveLiveToolCwd } from './tool-exec.mjs';
8
8
  import { createSteeringDrain } from './steering.mjs';
9
9
  import { createNoToolTurnResolver } from './no-tool-turn.mjs';
10
10
  import { classifyTerminationReason } from './termination.mjs';
11
+ import { routeUsageSegments } from './usage.mjs';
11
12
 
12
13
  // Consecutive identical-AND-failing tool calls (same name+args, error result)
13
14
  // tolerated across iterations before the loop refuses to re-execute and steers
@@ -153,5 +154,13 @@ export function finishLoop(state) {
153
154
  providerStateUpdated: state.providerStateUpdated,
154
155
  terminationReason: classifyTerminationReason(response, { sessionAgent: state.sessionAgent }),
155
156
  providerContinuations: state.noToolTurn.providerContinuations,
157
+ ...(state.routeUsageHops?.length
158
+ ? {
159
+ usageSegments: routeUsageSegments(state.routeUsageHops, state.lastUsage, {
160
+ provider: state.sessionRef?.provider,
161
+ model: state.sessionRef?.model,
162
+ }),
163
+ }
164
+ : {}),
156
165
  };
157
166
  }
@@ -16,7 +16,8 @@ import { isOffReadonlySurface } from './deferred-call-through.mjs';
16
16
  import { BRIDGE_CALL_TOOL_NAME, bridgeErrorText, isToolBridgeEnabled } from '../../runtime-core/tool-bridge.mjs';
17
17
  import { sessionAllowlistExcludes, sessionDisallows } from '../../runtime-core/deferred-tool-availability.mjs';
18
18
  import { providerToolExclusionReason } from './provider-tool-exclusions.mjs';
19
- import { agentMcpToolPermission, projectMcpToolDisabled } from '../../mcp/agent-connectors.mjs';
19
+ import { workflowOverlayDenial } from '../manager/workflow-switch-notice.mjs';
20
+ import { agentMcpToolPermission, connectorPolicyAgentOf, projectMcpToolDisabled } from '../../mcp/agent-connectors.mjs';
20
21
 
21
22
  const WORKER_DENIED_TOOLS = new Set(['agent', 'ask_user', 'session']);
22
23
  const BRIDGE_DISCOVERY_TOOL_NAMES = new Set(['load_tool']);
@@ -120,12 +121,15 @@ export function preDispatchDenyForSession(sessionRef, call) {
120
121
  if (!bridgeDiscovery && sessionAllowlistExcludes(sessionRef, schemaName)) {
121
122
  return `Error: tool "${name}" is not available on this session's schema allowlist.`;
122
123
  }
124
+ // A workflow switched mid-conversation keeps the schema; its denials apply here.
125
+ const workflowDenial = workflowOverlayDenial(sessionRef, schemaName);
126
+ if (workflowDenial) return workflowDenial;
123
127
  if (sessionDisallows(sessionRef, schemaName)) {
124
128
  return `Error: tool "${name}" is not available in this session.`;
125
129
  }
126
130
  // An agent's connectors: a tool of a server it does not mount, or one set
127
131
  // to `disabled`, stays refused though the process-wide registry knows it.
128
- if (agentMcpToolPermission(sessionRef?.agent, schemaName) === 'disabled') {
132
+ if (agentMcpToolPermission(connectorPolicyAgentOf(sessionRef), schemaName) === 'disabled') {
129
133
  return `Error: tool "${name}" is not available to this agent.`;
130
134
  }
131
135
  // The project the session runs in may switch a connector off as well.
@@ -1,4 +1,7 @@
1
- import { canFallbackRoute } from '../../../../shared/model-route-fallback.mjs';
1
+ import { routeFallbackVerdict } from '../../../../shared/model-route-fallback.mjs';
2
+ import { providerErrorKind } from '../../providers/provider-error-kind.mjs';
3
+ import { displayModelName } from '../../../../shared/model-display.mjs';
4
+ import { presentErrorText } from '../../../../shared/err-text.mjs';
2
5
  import { getProvider, initProviders } from '../../providers/registry.mjs';
3
6
  import { loadConfig } from '../../config.mjs';
4
7
  import { ensureProviderEnabled } from '../../runtime-core/config-helpers.mjs';
@@ -7,19 +10,105 @@ import { pinSessionWireRoute } from '../../providers/wire-route.mjs';
7
10
  import { buildRequestSendOptions } from '../manager/ask-turn-loop-options.mjs';
8
11
  import { prepareTurnEffortConfiguration } from '../../providers/effort-configuration.mjs';
9
12
  import { resolveSessionContextMeta } from '../manager/context-meta.mjs';
13
+ import { stripProviderReplayMessages } from '../manager/provider-replay.mjs';
14
+
15
+ // The short reason a notice gives for leaving a model, by provider kind.
16
+ const HOP_REASONS = {
17
+ quota_exhausted: 'usage limit reached',
18
+ entitlement: 'not included in the plan',
19
+ billing: 'out of credits',
20
+ rate_limited: 'rate-limited',
21
+ overloaded: 'unavailable',
22
+ transient: 'unavailable',
23
+ service_tier_unavailable: 'unavailable',
24
+ auth: 'sign-in failed',
25
+ permission: 'access denied',
26
+ invalid_request: 'model not available',
27
+ context_overflow: 'context too long',
28
+ };
29
+
30
+ const routeLabel = (route) => displayModelName(route.model, route.provider) || `${route.provider}/${route.model}`;
31
+
32
+ /** The muted transcript line for a hop: `<from> <reason> — switched to <to>.` */
33
+ export function routeFallbackNoticeText(from, to, error) {
34
+ const reason = HOP_REASONS[providerErrorKind(error).kind] || 'unavailable';
35
+ return `${routeLabel(from)} ${reason} — switched to ${routeLabel(to)}.`;
36
+ }
37
+
38
+ function routeFailure(route, error) {
39
+ return {
40
+ provider: route.provider,
41
+ model: route.model,
42
+ kind: providerErrorKind(error).kind,
43
+ error: presentErrorText(error),
44
+ };
45
+ }
46
+
47
+ // The chain ended after at least one hop: the final error keeps its own kind
48
+ // and names every model that was tried and why each failed. `skipped` is the
49
+ // next route when the chain stopped before it (a context overflow whose next
50
+ // window is no larger), so the text says why instead of "every model failed".
51
+ function annotateEndedChain(session, error, skipped = null) {
52
+ const tried = session?.routeFallbackFailures;
53
+ if (!tried?.length || !error || typeof error !== 'object' || error.routeFallbackAttempts) return;
54
+ const attempts = [...tried, routeFailure(session, error)];
55
+ try {
56
+ error.routeFallbackAttempts = attempts;
57
+ const lines = attempts.map((attempt) => `${routeLabel(attempt)}: ${attempt.error}`).join('; ');
58
+ const head = skipped
59
+ ? `Fallback chain stopped before ${routeLabel(skipped)} (its context window is not larger) — tried`
60
+ : 'Every model in the fallback chain failed —';
61
+ error.message = `${error.message}\n${head} ${lines}`;
62
+ if (skipped) error.routeFallbackStoppedBefore = { provider: skipped.provider, model: skipped.model };
63
+ } catch {
64
+ /* frozen errors keep their own message */
65
+ }
66
+ }
67
+
68
+ // A context overflow moves only to a model whose window is larger.
69
+ async function nextContextGrows(state, next) {
70
+ await initProviders(ensureProviderEnabled(loadConfig(), next.provider), { signal: state.signal });
71
+ state.throwIfAborted();
72
+ const provider = getProvider(next.provider);
73
+ if (!provider) return false;
74
+ const current = resolveSessionContextMeta(state.provider, state.sessionRef.model, state.sessionRef).rawContextWindow;
75
+ const window = resolveSessionContextMeta(provider, next.model, next).rawContextWindow;
76
+ return Number(window) > Number(current || state.sessionRef.selectedContextWindow || 0);
77
+ }
10
78
 
11
79
  export async function advanceAgentRoute(state, error) {
12
80
  const session = state.sessionRef;
13
- const index = session?.routeFallbackIndex || 0;
14
- const next = session?.routeFallbacks?.[index];
15
- const committed = state.toolCallsTotal > 0 || state.response != null;
16
- if (!session?.agent || !next || !canFallbackRoute(error, { signal: state.signal, committed })) return false;
81
+ if (!session?.agent || !session.routeFallbacks?.length) return false;
82
+ const index = session.routeFallbackIndex || 0;
83
+ const next = session.routeFallbacks[index];
84
+ // Only THIS request must have produced nothing (its stream outcome says so):
85
+ // earlier requests of the turn may have run tools. Their calls and results
86
+ // are already in the live turn, which the next model continues from, so no
87
+ // completed tool runs again.
88
+ const verdict = routeFallbackVerdict(error, { signal: state.signal });
89
+ const hops = verdict === 'hop' || (verdict === 'context' && next && (await nextContextGrows(state, next)));
90
+ if (!next || !hops) {
91
+ if (verdict !== 'stay') annotateEndedChain(session, error, next || null);
92
+ return false;
93
+ }
17
94
  state.throwIfAborted();
18
- const config = ensureProviderEnabled(loadConfig(), next.provider);
19
- await initProviders(config.providers, { signal: state.signal });
95
+ await initProviders(ensureProviderEnabled(loadConfig(), next.provider), { signal: state.signal });
20
96
  state.throwIfAborted();
21
97
  const provider = getProvider(next.provider);
22
98
  if (!provider) throw new Error(`Provider "${next.provider}" not available`);
99
+ const failed = routeFailure(session, error);
100
+ // The left model's share of the turn ends here: the running total at the
101
+ // switch bounds it, so each model's tokens are booked at its own rates.
102
+ state.routeUsageHops = [
103
+ ...(state.routeUsageHops || []),
104
+ { provider: session.provider, model: session.model, usage: state.lastUsage ? { ...state.lastUsage } : null },
105
+ ];
106
+ const notice = routeFallbackNoticeText(session, next, error);
107
+ // Saved with the new route: a crash before the live turn is checkpointed
108
+ // again recovers that turn without the previous provider's native state.
109
+ if (session.activeTurnCheckpoint && typeof session.activeTurnCheckpoint === 'object') {
110
+ session.activeTurnCheckpoint = { ...session.activeTurnCheckpoint, routeChanged: true };
111
+ }
23
112
  const oldOptions = buildRequestSendOptions({
24
113
  sessionId: state.sessionId,
25
114
  session,
@@ -37,13 +126,18 @@ export async function advanceAgentRoute(state, error) {
37
126
  contextTokens: next.contextTokens,
38
127
  selectedContextWindow: resolveSessionContextMeta(provider, next.model, next).rawContextWindow,
39
128
  },
40
- { crossProvider: true }
129
+ // The live turn object is what changes: the route is applied to it in place, never to
130
+ // another copy of the id (a disk reload after a generation takeover).
131
+ { crossProvider: true, session }
41
132
  );
42
133
  if (!updated) throw new Error('agent session closed during model fallback');
43
134
  updated.routeFallbackIndex = index + 1;
135
+ updated.routeFallbackFailures = [...(session.routeFallbackFailures || []), failed];
44
136
  await pinSessionWireRoute(updated, provider);
45
137
  state.throwIfAborted();
46
- state.messages.splice(0, state.messages.length, ...updated.messages);
138
+ // The live turn (prompt, assistant tool calls, tool results) carries on;
139
+ // only the previous provider's native replay state is dropped from it.
140
+ state.messages.splice(0, state.messages.length, ...stripProviderReplayMessages(state.messages));
47
141
  updated.liveTurnMessages = state.messages;
48
142
  state.sessionRef = updated;
49
143
  state.provider = provider;
@@ -67,6 +161,14 @@ export async function advanceAgentRoute(state, error) {
67
161
  signal: state.signal,
68
162
  })
69
163
  );
164
+ try {
165
+ process.stderr.write(
166
+ `[route-fallback] ${failed.provider}/${failed.model} (${failed.kind}) -> ${updated.provider}/${updated.model}\n`
167
+ );
168
+ state.opts.onStageChange?.('notice', { tone: 'info', message: notice });
169
+ } catch {
170
+ /* display-only */
171
+ }
70
172
  state.opts.onStageChange?.('connecting', {
71
173
  provider: updated.provider,
72
174
  model: updated.model,