mixdog 1.3.1 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (377) hide show
  1. package/README.md +8 -4
  2. package/package.json +2 -2
  3. package/scripts/native-release-status.mjs +238 -0
  4. package/src/defaults/shipped-history.json +5 -1
  5. package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
  6. package/src/defaults/skills/setup/SKILL.md +23 -1
  7. package/src/defaults/skills/setup/references/actions.md +50 -3
  8. package/src/defaults/skills/setup/references/surfaces.md +23 -3
  9. package/src/defaults/skills/setup-guide/SKILL.md +7 -3
  10. package/src/defaults/skills/visualize/SKILL.md +9 -6
  11. package/src/defaults/skills/visualize/references/craft.md +8 -1
  12. package/src/help.mjs +1 -1
  13. package/src/lib/keychain-cjs.cjs +4 -1
  14. package/src/lib/rules-builder.cjs +14 -7
  15. package/src/rules/agent-mode/AGENT_MODE.md +8 -5
  16. package/src/rules/agent-mode/MIXY.md +6 -8
  17. package/src/rules/lead/LEAD.md +2 -2
  18. package/src/rules/shared/10-tool-workflow.md +4 -0
  19. package/src/rules/shared/20-research.md +2 -0
  20. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
  21. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
  22. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
  23. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
  24. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
  25. package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
  26. package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
  27. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
  28. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  29. package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
  30. package/src/runtime/agent/orchestrator/config.mjs +11 -0
  31. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +9 -2
  32. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -5
  33. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
  34. package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
  35. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +80 -31
  36. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
  37. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  38. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +67 -38
  39. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
  40. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
  41. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +27 -8
  42. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
  43. package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
  44. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
  45. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
  46. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +16 -0
  47. package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
  48. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -1
  49. package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +3 -1
  50. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +1 -0
  51. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +5 -5
  52. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +9 -5
  53. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +9 -1
  54. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +2 -1
  55. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +30 -3
  56. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +1 -1
  57. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +3 -1
  58. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +61 -8
  59. package/src/runtime/agent/orchestrator/providers/registry.mjs +9 -0
  60. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
  61. package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
  62. package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
  63. package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
  64. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
  65. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +4 -6
  66. package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
  67. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
  68. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +12 -1
  69. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +6 -5
  70. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +7 -6
  71. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +1 -1
  72. package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
  73. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +2 -0
  74. package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
  75. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
  76. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
  77. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +17 -2
  78. package/src/runtime/agent/orchestrator/session/agent-room.mjs +133 -38
  79. package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +80 -0
  80. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  81. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +14 -11
  82. package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
  83. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +68 -18
  84. package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
  85. package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
  86. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
  87. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +5 -1
  88. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +1 -1
  89. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
  90. package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
  91. package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +230 -0
  92. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +136 -32
  93. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
  94. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +21 -9
  95. package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
  96. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
  97. package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
  98. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
  99. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +189 -105
  100. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
  101. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +5 -5
  102. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +3 -0
  103. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
  104. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +38 -5
  105. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +21 -0
  106. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
  107. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
  108. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
  109. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
  110. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +4 -0
  111. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  112. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  113. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
  114. package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +99 -0
  115. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
  116. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
  117. package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
  118. package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
  119. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +40 -1
  120. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
  121. package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +20 -0
  122. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +55 -27
  123. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +3 -8
  124. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +12 -1
  125. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
  126. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
  127. package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
  128. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +42 -10
  129. package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
  130. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +18 -3
  131. package/src/runtime/agent/orchestrator/session/routine-result.mjs +120 -9
  132. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +5 -1
  133. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
  134. package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
  135. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
  136. package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
  137. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
  138. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +8 -3
  139. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +36 -1
  140. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -1
  141. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
  142. package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
  143. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
  144. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
  145. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
  146. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +35 -5
  147. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +42 -2
  148. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +35 -13
  149. package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
  150. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +4 -3
  151. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
  152. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
  153. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
  154. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
  155. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +6 -14
  156. package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
  157. package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
  158. package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
  159. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
  160. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
  161. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +34 -21
  162. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
  163. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
  164. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
  165. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
  166. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
  167. package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
  168. package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
  169. package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
  170. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
  171. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
  172. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
  173. package/src/runtime/attachments/attachment-reference.mjs +155 -0
  174. package/src/runtime/attachments/limits.mjs +5 -1
  175. package/src/runtime/attachments/office-extract.mjs +3 -6
  176. package/src/runtime/attachments/pdf-extract.mjs +84 -10
  177. package/src/runtime/attachments/store.mjs +49 -30
  178. package/src/runtime/browser-bridge/client.mjs +73 -20
  179. package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
  180. package/src/runtime/channels/lib/scheduler.mjs +14 -7
  181. package/src/runtime/channels/lib/state-file.mjs +15 -15
  182. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
  183. package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
  184. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
  185. package/src/runtime/channels/lib/worker-main.mjs +3 -2
  186. package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
  187. package/src/runtime/effort-judge/judge-client.mjs +5 -2
  188. package/src/runtime/effort-judge/model-install.mjs +16 -8
  189. package/src/runtime/local-provider/asset-installer.mjs +10 -4
  190. package/src/runtime/local-provider/catalog.mjs +5 -2
  191. package/src/runtime/local-provider/registered-models.mjs +64 -27
  192. package/src/runtime/local-provider/server.mjs +2 -3
  193. package/src/runtime/media/adapters/image-api.mjs +89 -0
  194. package/src/runtime/media/auth.mjs +64 -1
  195. package/src/runtime/media/catalog.mjs +66 -4
  196. package/src/runtime/media/jobs.mjs +4 -0
  197. package/src/runtime/media/lanes.mjs +46 -5
  198. package/src/runtime/media/media-usage.mjs +17 -0
  199. package/src/runtime/media/renditions.mjs +15 -15
  200. package/src/runtime/media/store.mjs +36 -35
  201. package/src/runtime/media/tool.mjs +8 -1
  202. package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +8 -2
  203. package/src/runtime/memory/lib/embedding-model-files.mjs +30 -7
  204. package/src/runtime/memory/lib/ingest/transcript-tail.mjs +16 -7
  205. package/src/runtime/memory/lib/pg/process.mjs +8 -4
  206. package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
  207. package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
  208. package/src/runtime/office/core/journal.mjs +7 -14
  209. package/src/runtime/office/design/library/design-library-core.mjs +4 -16
  210. package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
  211. package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
  212. package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
  213. package/src/runtime/office/pdf/pdf-render.mjs +30 -18
  214. package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
  215. package/src/runtime/office/portable/portable-soffice.mjs +4 -0
  216. package/src/runtime/shared/agent-kind-migration.mjs +200 -0
  217. package/src/runtime/shared/agent-message-envelope.d.mts +6 -0
  218. package/src/runtime/shared/agent-message-envelope.mjs +44 -0
  219. package/src/runtime/shared/agent-reap-state.d.mts +4 -0
  220. package/src/runtime/shared/agent-store.mjs +24 -7
  221. package/src/runtime/shared/agent-task-starts.mjs +27 -0
  222. package/src/runtime/shared/atomic-file.d.mts +50 -0
  223. package/src/runtime/shared/atomic-file.mjs +394 -132
  224. package/src/runtime/shared/background-tasks.mjs +7 -4
  225. package/src/runtime/shared/bot-hops.mjs +30 -0
  226. package/src/runtime/shared/bot-split-migration.mjs +1 -1
  227. package/src/runtime/shared/bounded-download.mjs +4 -2
  228. package/src/runtime/shared/config.mjs +39 -9
  229. package/src/runtime/shared/defaults-separation.mjs +6 -2
  230. package/src/runtime/shared/delegated-approval-host.mjs +74 -0
  231. package/src/runtime/shared/err-text.mjs +4 -0
  232. package/src/runtime/shared/error-presentation.mjs +22 -4
  233. package/src/runtime/shared/file-has-data.mjs +28 -0
  234. package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
  235. package/src/runtime/shared/pending-agent-work.mjs +54 -0
  236. package/src/runtime/shared/process-lifecycle.mjs +8 -12
  237. package/src/runtime/shared/projects.mjs +50 -41
  238. package/src/runtime/shared/provider-accounts.mjs +17 -12
  239. package/src/runtime/shared/routine-trigger.d.mts +6 -1
  240. package/src/runtime/shared/routine-trigger.mjs +14 -6
  241. package/src/runtime/shared/run-child.d.mts +3 -0
  242. package/src/runtime/shared/run-child.mjs +24 -3
  243. package/src/runtime/shared/session-baseline-store.mjs +8 -2
  244. package/src/runtime/shared/singleton-owner.mjs +7 -6
  245. package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
  246. package/src/runtime/shared/staged-update.mjs +4 -2
  247. package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
  248. package/src/runtime/shared/tool-execution-contract.mjs +6 -0
  249. package/src/runtime/shared/transcript-metadata.mjs +28 -3
  250. package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
  251. package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
  252. package/src/runtime/shared/turn-snapshot.mjs +44 -2
  253. package/src/runtime/shared/user-cwd.mjs +9 -4
  254. package/src/runtime/shared/user-data-guard.mjs +41 -42
  255. package/src/runtime/shared/utf8-cut.mjs +10 -0
  256. package/src/runtime/shared/worktrees/activity-gate.mjs +269 -61
  257. package/src/runtime/shared/worktrees/registry.mjs +26 -26
  258. package/src/runtime/tidy/core-install.mjs +26 -4
  259. package/src/runtime/tidy/install.mjs +91 -23
  260. package/src/runtime/web-search/lib/config.mjs +4 -16
  261. package/src/session-runtime/agent-home-delivery.mjs +555 -199
  262. package/src/session-runtime/agent-room-delivery.mjs +143 -36
  263. package/src/session-runtime/agent-task-sessions.mjs +667 -165
  264. package/src/session-runtime/agent-tool-routing.mjs +1 -0
  265. package/src/session-runtime/boot/apis.mjs +45 -24
  266. package/src/session-runtime/boot/config.mjs +1 -0
  267. package/src/session-runtime/boot/facade.mjs +11 -1
  268. package/src/session-runtime/boot/infrastructure.mjs +14 -2
  269. package/src/session-runtime/boot/providers.mjs +2 -2
  270. package/src/session-runtime/boot/shared.mjs +12 -6
  271. package/src/session-runtime/boot/tools.mjs +2 -2
  272. package/src/session-runtime/bot-status.mjs +432 -0
  273. package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
  274. package/src/session-runtime/config-lifecycle.mjs +60 -55
  275. package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
  276. package/src/session-runtime/held-requests.mjs +36 -33
  277. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
  278. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
  279. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +19 -4
  280. package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
  281. package/src/session-runtime/lifecycle/teardown.mjs +11 -3
  282. package/src/session-runtime/mcp-glue/connect.mjs +100 -186
  283. package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
  284. package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
  285. package/src/session-runtime/media-api.mjs +5 -2
  286. package/src/session-runtime/model-route/route-tuning.mjs +4 -1
  287. package/src/session-runtime/model-route/set-route.mjs +61 -1
  288. package/src/session-runtime/owned-automation-run.mjs +8 -1
  289. package/src/session-runtime/room-member-gate.mjs +16 -3
  290. package/src/session-runtime/route-resolution.mjs +16 -8
  291. package/src/session-runtime/route-state.mjs +1 -59
  292. package/src/session-runtime/routine-home-turn.mjs +5 -3
  293. package/src/session-runtime/runtime-bootstrap.mjs +5 -3
  294. package/src/session-runtime/runtime-paths.mjs +11 -5
  295. package/src/session-runtime/runtime-tunables.mjs +6 -0
  296. package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
  297. package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
  298. package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
  299. package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
  300. package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
  301. package/src/session-runtime/services/agent-tool/send-flow.mjs +25 -21
  302. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +4 -0
  303. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
  304. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +8 -5
  305. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
  306. package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
  307. package/src/session-runtime/services/agent-tool.mjs +6 -0
  308. package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
  309. package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
  310. package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
  311. package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
  312. package/src/session-runtime/services/plugin-admin.mjs +26 -26
  313. package/src/session-runtime/services/provider-admin.mjs +14 -4
  314. package/src/session-runtime/services/schedule-access.mjs +33 -4
  315. package/src/session-runtime/services/seeds.mjs +4 -3
  316. package/src/session-runtime/services/usage-dashboard.mjs +1 -1
  317. package/src/session-runtime/services/usage-stats-model.mjs +2 -1
  318. package/src/session-runtime/session-create/materialize.mjs +2 -3
  319. package/src/session-runtime/session-create/session-options.mjs +15 -2
  320. package/src/session-runtime/settings-builtin-tools-api.mjs +63 -1
  321. package/src/session-runtime/settings-compaction-api.mjs +86 -39
  322. package/src/session-runtime/setup-tool/executor.mjs +8 -2
  323. package/src/session-runtime/setup-tool/settings-contract.mjs +31 -1
  324. package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
  325. package/src/session-runtime/skills-api.mjs +12 -5
  326. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +265 -0
  327. package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
  328. package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
  329. package/src/session-runtime/tool-policy/policy-stage.mjs +165 -0
  330. package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
  331. package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
  332. package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
  333. package/src/session-runtime/tool-policy-refresh.mjs +47 -369
  334. package/src/session-runtime/tool-surface.mjs +5 -3
  335. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +95 -88
  336. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  337. package/src/session-runtime/turn/turn-run.mjs +2 -1
  338. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
  339. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +519 -333
  340. package/src/session-runtime/workflow-agents-api/agent-route.mjs +46 -46
  341. package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
  342. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
  343. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
  344. package/src/session-runtime/workspace/workspace-state.mjs +7 -6
  345. package/src/standalone/daemon-agent-control.mjs +4 -1
  346. package/src/standalone/daemon-session-control.mjs +51 -10
  347. package/src/standalone/daemon.mjs +28 -10
  348. package/src/standalone/local-session-runtime.mjs +22 -14
  349. package/src/standalone/session-client.mjs +7 -2
  350. package/src/standalone/session-protocol.mjs +7 -0
  351. package/src/standalone/session-service.mjs +11 -0
  352. package/src/standalone/session-state-projection.mjs +19 -9
  353. package/src/standalone/session-tool-output.mjs +35 -9
  354. package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
  355. package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
  356. package/src/tui/app/slash-commands.mjs +1 -1
  357. package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
  358. package/src/tui/dist/index.mjs +529 -333
  359. package/src/tui/prompt-history-store.mjs +16 -21
  360. package/src/tui/session/context-state.mjs +4 -0
  361. package/src/tui/session/session-api/intake/abort.mjs +19 -1
  362. package/src/tui/session/session-api/media.mjs +2 -1
  363. package/src/tui/session/session-api/routes-agent-definitions.mjs +75 -0
  364. package/src/tui/session/session-api/routes-write.mjs +203 -0
  365. package/src/tui/session/session-api/routes.mjs +23 -247
  366. package/src/tui/session/session-api/settings.mjs +14 -1
  367. package/src/tui/session/session-flow/drain.mjs +28 -3
  368. package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
  369. package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
  370. package/src/tui/session/session-flow/steering.mjs +8 -3
  371. package/src/tui/session/tool-approval.mjs +185 -20
  372. package/src/tui/session/transcript-intake.mjs +2 -0
  373. package/src/tui/session/turn.mjs +23 -14
  374. package/src/tui/transcript-tool-failures.mjs +8 -2
  375. package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -8
  376. package/src/vendor/statusline/src/gateway/session-routes.mjs +6 -7
  377. package/src/rules/agent-mode/ROUTINE.md +0 -6
@@ -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,
@@ -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.
@@ -67,8 +67,7 @@ function annotateEndedChain(session, error, skipped = null) {
67
67
 
68
68
  // A context overflow moves only to a model whose window is larger.
69
69
  async function nextContextGrows(state, next) {
70
- const config = ensureProviderEnabled(loadConfig(), next.provider);
71
- await initProviders(config.providers, { signal: state.signal });
70
+ await initProviders(ensureProviderEnabled(loadConfig(), next.provider), { signal: state.signal });
72
71
  state.throwIfAborted();
73
72
  const provider = getProvider(next.provider);
74
73
  if (!provider) return false;
@@ -93,8 +92,7 @@ export async function advanceAgentRoute(state, error) {
93
92
  return false;
94
93
  }
95
94
  state.throwIfAborted();
96
- const config = ensureProviderEnabled(loadConfig(), next.provider);
97
- await initProviders(config.providers, { signal: state.signal });
95
+ await initProviders(ensureProviderEnabled(loadConfig(), next.provider), { signal: state.signal });
98
96
  state.throwIfAborted();
99
97
  const provider = getProvider(next.provider);
100
98
  if (!provider) throw new Error(`Provider "${next.provider}" not available`);
@@ -128,7 +126,9 @@ export async function advanceAgentRoute(state, error) {
128
126
  contextTokens: next.contextTokens,
129
127
  selectedContextWindow: resolveSessionContextMeta(provider, next.model, next).rawContextWindow,
130
128
  },
131
- { 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 }
132
132
  );
133
133
  if (!updated) throw new Error('agent session closed during model fallback');
134
134
  updated.routeFallbackIndex = index + 1;
@@ -4,6 +4,7 @@
4
4
  * consume a retry verdict, and settle a completed send into the loop state.
5
5
  */
6
6
  import { traceCacheBreak } from '../../cache-break-trace.mjs';
7
+ import { anthropicPrefixChanges } from '../../providers/lib/anthropic-prefix-trace.mjs';
7
8
  import { resolveLiveToolCwd } from './tool-exec.mjs';
8
9
  import { notifySkillsApplied, prepareExplicitSkills, recordAppliedSkills } from '../explicit-skills.mjs';
9
10
  import { addUsage, usageDeltaEvent } from './usage.mjs';
@@ -352,6 +353,8 @@ function traceActualCacheMiss(state, sent, response, { previousGuardState, previ
352
353
  previousCachedPrefixTokens: baseline,
353
354
  idleMs,
354
355
  missingPrefixGuardBaseline: !previousGuardState,
356
+ // Which wire block moved ('none': same bytes, so the cache itself dropped it).
357
+ ...anthropicPrefixChanges(sessionId),
355
358
  },
356
359
  state.cacheBreakTraceOptions
357
360
  );
@@ -1,6 +1,7 @@
1
1
  // Steering-message normalization/merge helpers.
2
2
  // Merges queued steering entries into a single content payload + display text.
3
3
  import { stableMessageId } from '../../../../shared/transcript-metadata.mjs';
4
+ import { raiseTurnHop } from '../../../../shared/bot-hops.mjs';
4
5
 
5
6
  function steeringContentText(content) {
6
7
  if (typeof content === 'string') return content;
@@ -140,6 +141,8 @@ function injectSteeringEntry(merged, { messages, opts, stage, onSkillPrompt }) {
140
141
  // gets it back (onSteerMessage) for the row it draws. A steered prompt
141
142
  // keeps the id the surface gave it, else gets one.
142
143
  const messageId = stableMessageId(merged.transcriptMeta?.messageId);
144
+ // A bot's message at a higher hop makes the running turn bot-descended at that hop.
145
+ raiseTurnHop(opts.session, merged.transcriptMeta);
143
146
  const steeringTranscriptMeta =
144
147
  merged.transcriptMeta && typeof merged.transcriptMeta === 'object'
145
148
  ? { at: submittedAt ?? injectedAt, ...merged.transcriptMeta, messageId }
@@ -6,15 +6,20 @@ import { resolvePreToolAskApproval } from '../tool-helpers.mjs';
6
6
  import { runAbortable, throwIfAborted } from '../../../../../shared/abort-race.mjs';
7
7
  import { evaluateExecPolicy } from '../../../tools/bash-policy-scan.mjs';
8
8
  import { beginSessionApprovalWait } from '../../manager/runtime-liveness.mjs';
9
- import { agentMcpToolPermission } from '../../../mcp/agent-connectors.mjs';
10
- import { humanGateDecision, recordHumanDecision } from '../../human-gate/gate.mjs';
9
+ import { agentMcpToolPermission, writeAgentMcpToolPermission } from '../../../mcp/agent-connectors.mjs';
10
+ import {
11
+ actingContactBotId,
12
+ humanGateDecision,
13
+ recordHumanDecision,
14
+ rememberSurfaceOf,
15
+ } from '../../human-gate/gate.mjs';
11
16
  import { applyEditedArgs } from '../../human-gate/requests.mjs';
12
17
 
13
18
  const isArgsObject = (value) => value && typeof value === 'object' && !Array.isArray(value);
14
19
 
15
20
  async function askApproval(
16
21
  call,
17
- { callerSessionId, executeOpts, toolApprovalHook, decision, request = null, gate = null }
22
+ { callerSessionId, executeOpts, toolApprovalHook, decision, request = null, gate = null, onApproved = null }
18
23
  ) {
19
24
  const askReason = String(decision?.reason || 'approval requested by hook').trim();
20
25
  // While the user decides, the session is waiting on them, not working.
@@ -46,26 +51,52 @@ async function askApproval(
46
51
  }
47
52
  // The user approved this very call on the richer outbound card.
48
53
  if (request?.kind === 'outbound') call.outboundApproved = true;
54
+ await onApproved?.(approval);
49
55
  return null;
50
56
  }
51
57
 
58
+ /** The renderer translates this exact text (desktop-toasts.tsx ALLOW_NOT_SAVED_TEXT). */
59
+ export const ALLOW_NOT_SAVED_NOTICE = 'Allowed this once. The setting could not be saved.';
52
60
  export const CONNECTOR_APPROVAL_REASON = 'This connector tool is set to ask before it runs for this agent.';
53
61
 
54
62
  /**
55
63
  * A connector tool the running agent set to `needs_approval` asks through the
56
64
  * ordinary approval hook, whatever the PreToolUse hooks decided. Run after
57
65
  * them, on the final call: a renamed call is judged under its final name.
58
- * A call the outbound card already got approved is not asked a second time.
66
+ * A call the outbound gate owns (its card approved it, or a remembered
67
+ * outbound grant answered it) is not asked a second time.
59
68
  * @returns {Promise<{ denial: string } | null>} the denial, or null to run
60
69
  */
61
70
  export function applyConnectorApproval(call, { agentId, callerSessionId, executeOpts, toolApprovalHook }) {
62
71
  if (call.outboundApproved) return null;
63
72
  if (agentMcpToolPermission(agentId, call.name) !== 'needs_approval') return null;
73
+ // "Always allow" is offered only to the contact bot that is acting, for its
74
+ // own policy entry for this one tool: never another bot, Mixy or the Lead.
75
+ const botId = actingContactBotId(callerSessionId);
76
+ const rememberable = Boolean(botId) && botId === agentId;
64
77
  return askApproval(call, {
65
78
  callerSessionId,
66
79
  executeOpts,
67
80
  toolApprovalHook,
68
81
  decision: { reason: CONNECTOR_APPROVAL_REASON },
82
+ request: rememberable
83
+ ? {
84
+ kind: 'approval',
85
+ agentId: botId,
86
+ remember: ['once', 'agent'],
87
+ rememberAt: rememberSurfaceOf(callerSessionId),
88
+ }
89
+ : null,
90
+ onApproved: async (approval) => {
91
+ if (!rememberable || approval?.remember !== 'agent') return;
92
+ try {
93
+ if (await writeAgentMcpToolPermission(botId, call.name, 'auto')) return;
94
+ } catch {
95
+ // fall through to the notice below
96
+ }
97
+ // Not saved: this call still runs once, and the next one asks again.
98
+ toolApprovalHook?.notice?.(ALLOW_NOT_SAVED_NOTICE, 'warn');
99
+ },
69
100
  });
70
101
  }
71
102
 
@@ -120,7 +151,7 @@ export async function applyBeforeToolHook({
120
151
  // Judged as the call will run: only a rewriting decision changes name/args.
121
152
  const hookAction = String(hookDecision?.action || hookDecision?.decision || '').toLowerCase();
122
153
  const rewritten = hookAction === 'ask' || hookAction === 'modify' || hookAction === 'rewrite';
123
- const { decision, request, gate } = await humanGateDecision(
154
+ const { decision, request, gate, remembered } = await humanGateDecision(
124
155
  {
125
156
  name:
126
157
  rewritten && typeof hookDecision?.name === 'string' && hookDecision.name.trim()
@@ -132,6 +163,8 @@ export async function applyBeforeToolHook({
132
163
  { sessionId: callerSessionId, mcpScopeId }
133
164
  );
134
165
  const action = String(decision?.action || decision?.decision || '').toLowerCase();
166
+ // A remembered outbound grant answered this call: the connector ask is not repeated.
167
+ if (remembered) call.outboundApproved = true;
135
168
  if (action === 'deny' || action === 'block') {
136
169
  const reason = decision?.reason ? `: ${decision.reason}` : '';
137
170
  return { denial: `Error: tool "${name}" denied by hook${reason}` };
@@ -17,6 +17,26 @@ import { isOnDeferredToolSurface } from '../deferred-call-through.mjs';
17
17
  import { _executeCodeGraphToolLazy } from '../../manager/runtime-loaders.mjs';
18
18
  import { requiresReadBody } from '../tool-classify.mjs';
19
19
  import { executeCursorNativeBuiltin } from '../../../tools/builtin/external-tool-adapters.mjs';
20
+ import { providerNativePdf, providerReadsImages } from '../../../providers/registry.mjs';
21
+ import { getModelMetadataSync } from '../../../../../shared/llm/model-catalog.mjs';
22
+
23
+ /**
24
+ * How read returns a PDF to this session's model. Native PDF support decides
25
+ * only whether a short PDF may arrive whole as a document block. A scanned
26
+ * page arrives as a page image whenever the model sees images — the provider
27
+ * sends image input (only the text-only local runtime does not) and the
28
+ * catalog does not say the model lacks it — and as OCR text otherwise.
29
+ */
30
+ export function pdfReadModes(
31
+ sessionRef,
32
+ { nativePdf = providerNativePdf, readsImages = providerReadsImages, catalogRow = getModelMetadataSync } = {}
33
+ ) {
34
+ const provider = sessionRef?.provider;
35
+ const model = sessionRef?.model;
36
+ const seesImages =
37
+ readsImages(provider) && (model ? catalogRow(model, provider)?.supportsVision : undefined) !== false;
38
+ return { pdfPageMode: seesImages ? 'image' : 'ocr', pdfNativeDocument: nativePdf(provider) };
39
+ }
20
40
 
21
41
  function dispatchMcp(name, args, { cwd, callerSessionId, sessionRef, executeOpts }) {
22
42
  const mcpScopeId = sessionRef?.mcpScopeId || null;
@@ -107,6 +127,7 @@ export function dispatchToolCall({ name, args }, ctx) {
107
127
  : {}),
108
128
  // Runtime-granted only; any value inherited from executeOpts is overridden.
109
129
  elevationApproved: ctx.elevationApproved === true,
130
+ ...pdfReadModes(sessionRef),
110
131
  };
111
132
  return executeCursorNativeBuiltin(
112
133
  name,
@@ -6,6 +6,7 @@
6
6
  // tool-exec/after-hook.mjs — PostToolUse result override
7
7
  // tool-exec/read-only-deadline.mjs — bounded read-only I/O with partial-output grace
8
8
  import { isMcpTool } from '../../mcp/client.mjs';
9
+ import { connectorPolicyAgentOf } from '../../mcp/agent-connectors.mjs';
9
10
  import { prepareDeferredToolCallThrough } from './deferred-call-through.mjs';
10
11
  import { refreshDeferredMcpToolCatalog } from '../../runtime-core/tool-catalog.mjs';
11
12
  import { bridgeTargetUnavailable } from '../../runtime-core/tool-bridge.mjs';
@@ -54,7 +55,7 @@ async function executeToolOwned(name, args, cwd, callerSessionId, sessionRef, ex
54
55
  });
55
56
  if (hooked.denial) return hooked.denial;
56
57
  const connectorDenied = await applyConnectorApproval(hooked, {
57
- agentId: sessionRef?.agent,
58
+ agentId: connectorPolicyAgentOf(sessionRef),
58
59
  callerSessionId,
59
60
  executeOpts,
60
61
  toolApprovalHook,