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
@@ -25,6 +25,8 @@ import { stripRuntimeUserContext, withRuntimeUserContext } from '../runtime-user
25
25
  import { carryMessageId, messageIdOf } from '../message-ids.mjs';
26
26
  import { normalizeIngestRole, shouldExcludeIngestMessage } from '../../../../memory/lib/session-ingest.mjs';
27
27
  import { codexWireSendOpts } from '../manager/session-id.mjs';
28
+ import { messageHasReferencableMedia, referenceMessageMedia } from '../../../../attachments/attachment-reference.mjs';
29
+ import { messageAttachmentTokens } from '../attachment-preflight.mjs';
28
30
  import { rebaseCompactedEffortConfiguration } from '../../providers/effort-configuration.mjs';
29
31
  import {
30
32
  COMPACTION_SYSTEM_PROMPT,
@@ -73,8 +75,13 @@ function splitFreshSource(messages) {
73
75
  // Budgeting, AI input and rule-only retention use this same projection.
74
76
  // Keep current-turn reminders; remove only proven runtime additions from
75
77
  // older requests. Unmarked legacy text remains part of the summary budget.
78
+ // Older requests also carry their attachments by reference from here on:
79
+ // the file stays on disk under the path the reference names, so the
80
+ // compacted context keeps the pointer instead of the whole document.
76
81
  live: conversation
77
- .map((message, index) => (index === latestUserIndex ? message : stripRuntimeUserContext(message)))
82
+ .map((message, index) =>
83
+ index === latestUserIndex ? message : referenceMessageMedia(stripRuntimeUserContext(message))
84
+ )
78
85
  .filter((message) => !isSummaryMessage(message)),
79
86
  sanitized,
80
87
  };
@@ -135,6 +142,9 @@ function summarySendOpts(opts) {
135
142
  // Fast mode is opt-in (it needs separate account credits); the summary
136
143
  // follows the route/session choice and never enables it on its own.
137
144
  fast: opts.fast ?? opts.sendOpts?.fast ?? false,
145
+ // Usage-ledger source for a foreground session's summary call (a
146
+ // background session's own sourceType still wins there).
147
+ requestKind: 'compaction',
138
148
  maxOutputTokens: opts.maxOutputTokens || SUMMARY_OUTPUT_TOKENS,
139
149
  providerState: undefined,
140
150
  onToolCall: undefined,
@@ -548,15 +558,43 @@ function freshContextLayout(source, budgetTokens, opts) {
548
558
  return { handoffText, summaryMessage, tail, stableHead, mandatory, mandatoryCost: estimateMessagesTokens(mandatory) };
549
559
  }
550
560
 
551
- function assertMandatoryFits(budget, mandatoryCost, summaryMessage, contextWindow) {
552
- if (mandatoryCost > budget || (summaryMessage && mandatoryCost === budget)) {
553
- const window = Number(contextWindow) > 0 ? ` (currently ${contextWindow} tokens)` : '';
554
- throw new Error(
555
- `freshContextCompactMessages: mandatory session context/latest instruction exceeds compact budget=${budget} ` +
556
- `(mandatory=${mandatoryCost}); the model context window${window} is too small for the tool definitions ` +
557
- 'and session context. Increase the model context size.'
558
- );
559
- }
561
+ function mandatoryOverflows(budget, mandatoryCost, summaryMessage) {
562
+ return mandatoryCost > budget || (summaryMessage && mandatoryCost === budget);
563
+ }
564
+
565
+ export const LATEST_TURN_EXCEEDS_CONTEXT = 'LATEST_TURN_EXCEEDS_CONTEXT';
566
+
567
+ // Says what does not fit and what would make it fit. A larger window is only
568
+ // offered when one would actually hold the required size.
569
+ function assertMandatoryFits(budget, mandatoryCost, summaryMessage, contextWindow, latestUser) {
570
+ if (!mandatoryOverflows(budget, mandatoryCost, summaryMessage)) return;
571
+ // Fixed sentences with the sizes inline: the desktop re-speaks each one
572
+ // through its catalog (error-notice-copy / transcript-failure-text).
573
+ const count = (value) => Number(value).toLocaleString('en-US');
574
+ const window = Number(contextWindow) > 0 ? `${count(contextWindow)}-token` : 'current';
575
+ const attachments = messageAttachmentTokens(latestUser);
576
+ const cause =
577
+ attachments > 0
578
+ ? `Its attachments alone are about ${count(attachments)} tokens.`
579
+ : 'The latest message and protected session context (system rules, tool definitions) are that large.';
580
+ const error = new Error(
581
+ `The latest message cannot fit this model (${LATEST_TURN_EXCEEDS_CONTEXT}): it needs about ` +
582
+ `${count(mandatoryCost)} tokens, but the ${window} window leaves ${count(budget)} for it. ${cause} ` +
583
+ 'Shorten the message or move large pasted text into a file the model can read. ' +
584
+ `A larger context window helps only if it holds ${count(mandatoryCost)} tokens plus room for the answer.`
585
+ );
586
+ error.code = LATEST_TURN_EXCEEDS_CONTEXT;
587
+ throw error;
588
+ }
589
+
590
+ // The source with the latest request's attachments sent by reference. Same
591
+ // source when it carries none.
592
+ function withLatestAttachmentsReferenced(source) {
593
+ const latest = latestActualUserInstructionIndex(source.live);
594
+ if (latest < 0 || !messageHasReferencableMedia(source.live[latest])) return source;
595
+ const live = source.live.slice();
596
+ live[latest] = referenceMessageMedia(live[latest]);
597
+ return { ...source, live };
560
598
  }
561
599
 
562
600
  function assertHandoffFits(summaryMessage, room) {
@@ -584,14 +622,26 @@ export function freshContextCompactMessages(messages, budgetTokens, opts = {}) {
584
622
  });
585
623
  }
586
624
 
587
- const source = splitFreshSource(baseSanitized);
588
- const { handoffText, summaryMessage, tail, stableHead, mandatory, mandatoryCost } = freshContextLayout(
589
- source,
590
- budgetTokens,
591
- opts
592
- );
593
- const budget = freshContextBudget({ targetBudget, budgetTokens, mandatoryCost, summaryMessage, opts });
594
- assertMandatoryFits(budget, mandatoryCost, summaryMessage, opts.contextWindow);
625
+ let source = splitFreshSource(baseSanitized);
626
+ let layout = freshContextLayout(source, budgetTokens, opts);
627
+ let budget = freshContextBudget({ targetBudget, budgetTokens, ...layout, opts });
628
+ // The latest request is mandatory and kept verbatim — except its
629
+ // attachments: when they are what does not fit (or the provider already
630
+ // refused this request as too long), they travel by reference instead of
631
+ // failing the turn, and every later turn, forever.
632
+ if (
633
+ opts.referenceLatestAttachments === true ||
634
+ mandatoryOverflows(budget, layout.mandatoryCost, layout.summaryMessage)
635
+ ) {
636
+ const referenced = withLatestAttachmentsReferenced(source);
637
+ if (referenced !== source) {
638
+ source = referenced;
639
+ layout = freshContextLayout(source, budgetTokens, opts);
640
+ budget = freshContextBudget({ targetBudget, budgetTokens, ...layout, opts });
641
+ }
642
+ }
643
+ const { handoffText, summaryMessage, tail, stableHead, mandatory, mandatoryCost } = layout;
644
+ assertMandatoryFits(budget, mandatoryCost, summaryMessage, opts.contextWindow, tail.latestUser);
595
645
  const handoff = handoffRoomFor(budget, mandatoryCost, opts);
596
646
  const summaryContent = String(summaryMessage?.content || '');
597
647
  assertHandoffFits(summaryMessage, handoff.room);
@@ -0,0 +1,16 @@
1
+ // compaction.summaryMode — which model writes a compaction summary
2
+ // (loop/fresh-context.mjs compactionSummaryPlan):
3
+ // smart warm prompt cache: fork the conversation's own cached request;
4
+ // cold: the Maintainer, else the subscription provider's lighter
5
+ // model, else the conversation's own model (isolated)
6
+ // maintainer the Maintainer (isolated), else the conversation's own model
7
+ // self the conversation's own model: fork when warm, isolated when cold
8
+ export const COMPACTION_SUMMARY_MODES = Object.freeze(['smart', 'maintainer', 'self']);
9
+
10
+ /** The saved mode; the previous release's boolean `useMaintainer:false`
11
+ * reads as 'self', true or absent as the 'smart' default. */
12
+ export function normalizeCompactionSummaryMode(compaction) {
13
+ const mode = compaction?.summaryMode;
14
+ if (COMPACTION_SUMMARY_MODES.includes(mode)) return mode;
15
+ return compaction?.useMaintainer === false ? 'self' : 'smart';
16
+ }
@@ -0,0 +1,87 @@
1
+ // What the compaction summary route choice reads: whether the session's
2
+ // prompt cache is still warm (a fork of its last request would read it), and
3
+ // the lighter model of a subscription provider.
4
+ import { cacheCapabilityForProvider, providerTailCacheLifetimeMs } from '../../agent-runtime/cache-strategy.mjs';
5
+ import { getModelMetadataSync } from '../../../../shared/llm/model-catalog.mjs';
6
+ import {
7
+ _compareVersion as compareAnthropicVersion,
8
+ _loadModelCache as loadAnthropicCatalogCache,
9
+ } from '../../providers/anthropic-model-resolve.mjs';
10
+ import { gptVersion } from '../../providers/openai-model-capabilities.mjs';
11
+ import { loadSavedCodexCatalog } from '../../providers/openai-oauth-catalog-store.mjs';
12
+ import { compactionForkRequest } from '../side-question-store.mjs';
13
+
14
+ // A provider outside the Anthropic/OpenAI lifetimes whose catalog marks prompt
15
+ // caching: the shortest common cache lifetime (Gemini's default TTL).
16
+ const CATALOG_PROMPT_CACHE_LIFETIME_MS = 5 * 60_000;
17
+ // The cache is read when the fork is sent, after the route choice: a request
18
+ // this close to its lifetime counts as cold.
19
+ const WARM_MARGIN_MIN_MS = 30_000;
20
+ const WARM_MARGIN_RATIO = 0.1;
21
+
22
+ /** How long the cache a saved request wrote or read stays readable, in ms:
23
+ * Infinity when it does not expire (openai-oauth), 0 when it is not cached.
24
+ * Anthropic: the request's message-tail tier (5m / 1h); OpenAI: the 30m tail
25
+ * that holds the conversation (the Lead's 12h breakpoint covers only the
26
+ * stable tools/instructions prefix). */
27
+ function requestCacheLifetimeMs(request) {
28
+ const name = request?.provider?.name;
29
+ if (name === 'openai-oauth') return Infinity;
30
+ if (name === 'openai' || cacheCapabilityForProvider(name) === 'explicit-breakpoint') {
31
+ return providerTailCacheLifetimeMs(name, request.opts?.cacheStrategy) ?? 0;
32
+ }
33
+ const info = request.provider.getCachedModelInfo?.(request.model) || getModelMetadataSync(request.model, name);
34
+ return info?.supportsPromptCaching ? CATALOG_PROMPT_CACHE_LIFETIME_MS : 0;
35
+ }
36
+
37
+ /** Whether the session's last settled request is still inside the cache
38
+ * lifetime it used, with a safety margin. No forkable request = cold. */
39
+ export function compactionCacheWarm(sessionId, now = Date.now()) {
40
+ const request = compactionForkRequest(sessionId);
41
+ if (!request) return false;
42
+ const lifetime = requestCacheLifetimeMs(request);
43
+ if (lifetime === Infinity) return true;
44
+ const margin = Math.max(WARM_MARGIN_MIN_MS, lifetime * WARM_MARGIN_RATIO);
45
+ return now - Number(request.sentAt) < lifetime - margin;
46
+ }
47
+
48
+ function compareGptVersion(a, b) {
49
+ const [aMajor, aMinor] = gptVersion(a);
50
+ const [bMajor, bMinor] = gptVersion(b);
51
+ // Same version: the undated alias over a dated snapshot.
52
+ return aMajor - bMajor || aMinor - bMinor || b.length - a.length;
53
+ }
54
+
55
+ // The lighter family per subscription provider: its newest catalog model.
56
+ // API-key providers bill per token and get no lighter step. `catalog` reads
57
+ // the provider's saved catalog only (disk cache, no auth or network on the
58
+ // compaction path): Anthropic's within its 24h TTL, Codex's stale or not.
59
+ const LIGHTER_FAMILY = Object.freeze({
60
+ 'anthropic-oauth': {
61
+ catalog: loadAnthropicCatalogCache,
62
+ matches: (row) => row.family === 'haiku',
63
+ compare: compareAnthropicVersion,
64
+ },
65
+ 'openai-oauth': {
66
+ catalog: loadSavedCodexCatalog,
67
+ matches: (row, id) => id.includes('-luna') && gptVersion(id) !== null,
68
+ compare: compareGptVersion,
69
+ },
70
+ });
71
+
72
+ /** The newest lighter-family model the saved catalog of `providerName` lists
73
+ * for this account, or null (not a subscription provider, no saved catalog,
74
+ * none listed). */
75
+ export async function lighterSubscriptionModel(providerName) {
76
+ const family = LIGHTER_FAMILY[providerName];
77
+ if (!family) return null;
78
+ const catalog = await family.catalog();
79
+ if (!Array.isArray(catalog)) return null;
80
+ let best = null;
81
+ for (const row of catalog) {
82
+ const id = String(row?.id || '');
83
+ if (!id || !family.matches(row, id)) continue;
84
+ if (!best || family.compare(id, best) > 0) best = id;
85
+ }
86
+ return best;
87
+ }
@@ -59,6 +59,8 @@ Rules:
59
59
  - Use terse bullets, not prose paragraphs.
60
60
  - Preserve exact file paths, commands, error strings, and identifiers when known.
61
61
  - Use the same language as the active user thread when it is clear.
62
+ - Record work as Done or In Progress only when a tool call and its result in the history show it (an agent spawn/send result, an edit, a command outcome). An assistant's stated intent with no matching tool call is not started: list it under Next Steps as "announced, not started".
63
+ - Attribute every decision, answer, and version/choice to its source: the user (message or ask_user answer), a named agent's report, or the assistant. Never credit an agent or the assistant with a user's answer.
62
64
  - Do not mention the summary process or that context was compacted.`;
63
65
 
64
66
  // The final user turn of a summary forked from the session's own cached
@@ -1,6 +1,10 @@
1
1
  // Text/token/message helpers and secret redaction for compaction.
2
2
  import { createHash } from 'node:crypto';
3
- import { attachmentTextForPart, isAttachmentReference, isPermanentAttachmentError } from '../../../../attachments/store.mjs';
3
+ import {
4
+ attachmentTextForPart,
5
+ isAttachmentReference,
6
+ isPermanentAttachmentError,
7
+ } from '../../../../attachments/store.mjs';
4
8
  import { estimateMessagesTokens } from '../context-utils.mjs';
5
9
  import { inlineFileKind } from '../../../../shared/inline-file-kind.mjs';
6
10
 
@@ -5,6 +5,7 @@
5
5
  import { SUMMARY_PREFIX_ANCHOR } from './summary-anchor.mjs';
6
6
  import { isImportedMessage } from '../imported-message.mjs';
7
7
  import {
8
+ CONTROL_MARKER_RE,
8
9
  isInternalRuntimeNotificationText,
9
10
  isModelVisibleToolCompletionWrapper,
10
11
  } from '../../../../shared/tool-execution-contract.mjs';
@@ -132,7 +133,6 @@ export function isActualUserInstructionMessage(message) {
132
133
  // isActualUserInstructionMessage deliberately keeps treating "[truncated]" as
133
134
  // an instruction (compaction tail selection depends on it), so the real-turn
134
135
  // predicate below recognizes it on its own.
135
- const CONTROL_MARKER_RE = /^\[(?:truncated|request interrupted(?: by user(?: for tool use)?| by process restart)?)\]$/i;
136
136
 
137
137
  /** Whether a message stands for real human conversation: a compact summary
138
138
  * (it replaced earlier human turns), imported prose, or the user's own
@@ -226,7 +226,7 @@ const recordExists = (dir, sessionId) => existsSync(join(dir, `${sessionId}.json
226
226
  * since `epoch` was captured wins. Any outcome but a rename removes the temp
227
227
  * file. */
228
228
  async function publishArchive({ dir, path, sessionId, text, epoch }) {
229
- const tmp = join(dir, `.${basename(path)}.${randomBytes(8).toString('hex')}.tmp`);
229
+ const tmp = join(dir, `${basename(path)}.mixdog-tmp-${randomBytes(4).toString('hex')}`);
230
230
  const removeTmp = () => {
231
231
  try {
232
232
  unlinkSync(tmp);
@@ -260,24 +260,55 @@ function messageImageAllowance(m) {
260
260
  if (!m || typeof m !== 'object') return 0;
261
261
  return messageImageDescriptors(m).reduce((sum, descriptor) => sum + imageDescriptorAllowance(descriptor), 0);
262
262
  }
263
- // Inline document (PDF) allowance: Anthropic bills ~1,500-3,000 tokens per
264
- // page and a typical PDF page is ~50-100KB, so ~bytes/16 with a floor and a
265
- // cap. The base64 payload itself is excluded from text estimates (see
263
+ // Inline document (PDF) allowance. A native PDF reader turns every page into
264
+ // one image AND sends the text it extracts from that page (the provider PDF
265
+ // docs: each page is billed as an image plus its text, typically
266
+ // 1,500-3,000 text tokens for a dense page). The page image is bounded like
267
+ // any image — the reader downscales it to the vision ceiling — so a page costs
268
+ // at most IMAGE_TOKEN_ALLOWANCE for its picture whatever its file size. A
269
+ // scanned page has no text layer and costs only its picture.
270
+ //
271
+ // The base64 payload itself is excluded from text estimates (see
266
272
  // jsonFallbackFromPart), so this allowance is the document's entire cost.
267
273
  const FILE_TOKEN_ALLOWANCE_FLOOR = 1_500;
268
274
  const FILE_MAX_TOKEN_ALLOWANCE = 300_000;
269
- // A PDF that records its page count (intake and the read tool do) is priced per
270
- // page; the size-based figure stays as the floor, so a dense scan still costs
271
- // at least its bytes. Inline and stored parts carry the same count.
272
- const FILE_TOKENS_PER_PAGE = 2_500;
275
+ // Extracted PDF text is priced at two characters per token: the low end of
276
+ // the documented per-page range for Latin text, and close to the measured
277
+ // density of Korean/CJK text, so neither is read low.
278
+ const PDF_TEXT_CHARS_PER_TOKEN = 2;
279
+ // A PDF that records its page count but not its text layer (older parts, an
280
+ // inline part from a path that did not measure it) keeps the documented
281
+ // mid-range per-page figure.
282
+ const FILE_TOKENS_PER_PAGE_UNMEASURED = 2_500;
273
283
  // A PDF that lowers to text costs at most its extraction budget (~4 bytes/token).
274
284
  const PDF_TEXT_MAX_TOKENS = Math.ceil(PDF_TEXT_MAX_BYTES / 4);
285
+ // Without a page count only the file size is known: ~bytes/16 (a typical page
286
+ // is 50-100KB) with a floor and a cap. Never used once pages are known — a
287
+ // scan's bytes are its image data, which the reader downscales, so bytes/16
288
+ // priced a 5 MB, 37-page scan at ~330k tokens instead of ~58k.
275
289
  function fileDescriptorAllowance(descriptor, nativePdf = true) {
276
- const bySize = Math.ceil((descriptor.sizeBytes || 0) / 16);
277
- const byPage = (descriptor.pageCount || 0) * FILE_TOKENS_PER_PAGE;
278
- const native = Math.min(FILE_MAX_TOKEN_ALLOWANCE, Math.max(FILE_TOKEN_ALLOWANCE_FLOOR, bySize, byPage));
290
+ const pages = descriptor.pageCount || 0;
291
+ let native;
292
+ if (pages > 0 && Number.isFinite(descriptor.textChars)) {
293
+ native = pages * IMAGE_TOKEN_ALLOWANCE + Math.ceil(descriptor.textChars / PDF_TEXT_CHARS_PER_TOKEN);
294
+ } else if (pages > 0) {
295
+ native = pages * FILE_TOKENS_PER_PAGE_UNMEASURED;
296
+ } else {
297
+ native = Math.max(FILE_TOKEN_ALLOWANCE_FLOOR, Math.ceil((descriptor.sizeBytes || 0) / 16));
298
+ }
299
+ native = Math.min(FILE_MAX_TOKEN_ALLOWANCE, native);
279
300
  return nativePdf ? native : Math.min(native, PDF_TEXT_MAX_TOKENS);
280
301
  }
302
+
303
+ /** The tokens one PDF part costs inline (native reader unless `nativePdf` is false). */
304
+ export function pdfPartAllowance(part, { nativePdf = true } = {}) {
305
+ return contentFileDescriptors([part]).reduce((sum, d) => sum + fileDescriptorAllowance(d, nativePdf), 0);
306
+ }
307
+
308
+ /** The tokens one image part costs inline. */
309
+ export function imagePartAllowance(part) {
310
+ return contentImageDescriptors([part]).reduce((sum, d) => sum + imageDescriptorAllowance(d), 0);
311
+ }
281
312
  /**
282
313
  * How many tokens the native-PDF meter overstates `messages` for a provider
283
314
  * that takes PDFs as text (nativePdf === false); subtract it from the
@@ -0,0 +1,230 @@
1
+ // session/human-gate/delegation.mjs
2
+ // Who a delegated session works for, recorded when it is created and re-bound
3
+ // only when another session sends it work (rebindDelegation): a sub-agent (any
4
+ // depth) and a task session a bot created carry `delegation` on their session
5
+ // record —
6
+ // { rootSessionId, rootAgentId, via, run?, path }
7
+ // `rootSessionId` is the top-level conversation (a bot's home chat, a room
8
+ // member session, a coding Lead's session); `rootAgentId` the bot or Mixy it
9
+ // acts for ('' under a coding Lead); `via` how that root was acting when the
10
+ // work started: 'home' (the bot's own attended chat), 'room', 'run' (a
11
+ // schedule/webhook turn; `run` = { kind, name }) or 'coding'; `path` the steps
12
+ // from the root down ({ kind: 'task' } | { kind: 'agent', agent }).
13
+ // It is copied from the parent's record (or bound from the parent itself when
14
+ // the parent is top-level) by the host at creation — never from tool input —
15
+ // so no parent walk happens at gate time. It follows the session that drives
16
+ // the work: when a session sends an existing worker (or task session) a new
17
+ // request, the record is re-bound to what that sender is doing now, so work an
18
+ // unattended run started asks when the attended chat later reuses it, and the
19
+ // reverse. A background wake of the worker is no new request and keeps it. A
20
+ // record from before this field existed is
21
+ // backfilled once, from its parent by the same rules (backfillOf), and kept on
22
+ // the in-memory record, which its next save persists. A session that has a
23
+ // parent link but no valid or derivable root is UNRESOLVED: the gate fails
24
+ // closed on it. `via: 'unknown'` — a legacy task session that names its bot
25
+ // but not where it was created: acts as that bot, never remembers.
26
+ import { agentHomeSessionId, agentIdOfHomeSession } from '../agent-home.mjs';
27
+ import { roomMemberOfSession } from '../agent-room.mjs';
28
+ import { loadSession } from '../store/load-session.mjs';
29
+ import { _liveSessions } from '../store/live-state.mjs';
30
+ import { saveSessionAsync } from '../store/save-worker.mjs';
31
+ import { isTurnInFlight, runtimeState } from '../manager/runtime-state.mjs';
32
+ import { unattendedApprovalPort } from './unattended-port.mjs';
33
+
34
+ const VIA = new Set(['home', 'room', 'run', 'coding', 'unknown']);
35
+ const AGENT_ID = /^[A-Za-z0-9_-]+$/;
36
+ const MAX_PATH = 32;
37
+ const clean = (value) => (typeof value === 'string' ? value.trim() : '');
38
+ const isRecord = (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value);
39
+
40
+ const agentOwned = (record) =>
41
+ clean(record?.owner).toLowerCase() === 'agent' || clean(record?.visibility).toLowerCase() === 'agent-only';
42
+
43
+ /** The session that started `record` (its spawn parent, or the chat that
44
+ * created a task session), or '' for a top-level session. */
45
+ function parentOf(record) {
46
+ if (agentOwned(record)) return clean(record.parentSessionId) || clean(record.ownerSessionId);
47
+ return clean(record?.taskOrigin?.sessionId);
48
+ }
49
+
50
+ /** The step a new session adds to its parent's path. */
51
+ function stepOf(record) {
52
+ return agentOwned(record) ? { kind: 'agent', agent: clean(record.agent) } : { kind: 'task' };
53
+ }
54
+
55
+ function validDelegation(value) {
56
+ if (!isRecord(value) || !clean(value.rootSessionId) || !VIA.has(value.via)) return null;
57
+ if (!Array.isArray(value.path) || value.path.length === 0 || value.path.length > MAX_PATH) return null;
58
+ if (value.via === 'run' && !isRecord(value.run)) return null;
59
+ return value;
60
+ }
61
+
62
+ /** A failed rebind leaves `{ unresolved: true }` instead of a delegation: an
63
+ * explicit "no root", so the legacy backfill cannot derive one again from a
64
+ * parent that still holds a run. */
65
+ const UNRESOLVED = Object.freeze({ unresolved: true });
66
+ const isUnresolvedMark = (value) => isRecord(value) && value.unresolved === true;
67
+
68
+ /** The recorded delegation of `record`, else its legacy backfill; null when
69
+ * it has none or was explicitly left unresolved. */
70
+ function delegationOfRecord(id, record, depth) {
71
+ if (isUnresolvedMark(record.delegation)) return null;
72
+ return validDelegation(record.delegation) || backfillOf(id, record, depth);
73
+ }
74
+
75
+ function loadRecord(sessionId) {
76
+ try {
77
+ return loadSession(sessionId);
78
+ } catch {
79
+ return null;
80
+ }
81
+ }
82
+
83
+ /** How a top-level session `sessionId` is acting right now (no record needed
84
+ * for a home or room member id, which name their agent themselves). */
85
+ function bindingOf(sessionId, record, { legacy = false } = {}) {
86
+ // A legacy record is classified from what was recorded, never from what the
87
+ // parent is doing now (a run going or ended since says nothing about it).
88
+ const run = legacy ? null : unattendedApprovalPort()?.runOf?.(sessionId);
89
+ if (run && run.kind !== 'room') {
90
+ return { rootAgentId: clean(run.agentId), via: 'run', run: { kind: run.kind, name: String(run.name || '') } };
91
+ }
92
+ const member = roomMemberOfSession(sessionId);
93
+ if (member) return { rootAgentId: member.agentId, via: 'room' };
94
+ const home = agentIdOfHomeSession(sessionId);
95
+ if (home) return { rootAgentId: home, via: 'home' };
96
+ if (!record || agentOwned(record)) return null;
97
+ // A Lead with host-set bot provenance (session-options taskOriginSuffix) is
98
+ // a bot's task session, never coding work: its root is that bot's chat.
99
+ const source = sourceTaskOf(record);
100
+ if (source === false) return null;
101
+ if (source) {
102
+ const task = { rootSessionId: agentHomeSessionId(source.bot), rootAgentId: source.bot, task: true };
103
+ if (source.from === 'home') return { ...task, via: 'home' };
104
+ if (source.from === 'run') return { ...task, via: 'run', run: { kind: 'schedule', name: '' } };
105
+ if (source.from === undefined) return { ...task, via: 'unknown' };
106
+ return null; // a room (which one is not recorded) or anything else: ambiguous
107
+ }
108
+ return { rootAgentId: '', via: 'coding' };
109
+ }
110
+
111
+ /** A Lead's host-set bot provenance `agent:<bot>[@from]`: `{ bot, from }`,
112
+ * null when it has none, false when it is malformed. */
113
+ function sourceTaskOf(record) {
114
+ const source = clean(record?.sourceName);
115
+ if (agentOwned(record) || clean(record?.sourceType) !== 'lead' || !source.startsWith('agent:')) return null;
116
+ const [bot, from, ...rest] = source.slice(6).split('@');
117
+ return AGENT_ID.test(bot) && !rest.length ? { bot, from } : false;
118
+ }
119
+
120
+ /** The root of a top-level `parent` for a child taking `step`. */
121
+ function rootFrom(parent, record, step, options) {
122
+ const binding = bindingOf(parent, record, options);
123
+ if (!binding) return null;
124
+ const { task, ...root } = binding;
125
+ return { rootSessionId: parent, ...root, path: task ? [{ kind: 'task' }, step] : [step] };
126
+ }
127
+
128
+ /** The delegation a legacy `record` (id `id`, a parent link, none recorded)
129
+ * is given, derived now from what was recorded — its own host-set `@run`
130
+ * provenance, else its parent's record, which must exist — and kept on the
131
+ * live record; null when it cannot be derived. Never `via: 'home'`: whether
132
+ * an old record was started by the attended chat (or by a run that has
133
+ * ended since) is not recorded, so home ancestry becomes 'unknown' — that
134
+ * bot, never remembered. Only a session created with its `delegation` can be
135
+ * 'home'. */
136
+ function backfillOf(id, record, depth = 0) {
137
+ const parent = parentOf(record);
138
+ if (!parent || parent === id || depth >= MAX_PATH) return null;
139
+ const own = sourceTaskOf(record);
140
+ let delegation = null;
141
+ if (own && own.from === 'run') {
142
+ // A run's task session stays the run's, whatever its origin chat is doing now.
143
+ delegation = {
144
+ rootSessionId: agentHomeSessionId(own.bot),
145
+ rootAgentId: own.bot,
146
+ via: 'run',
147
+ run: { kind: 'schedule', name: '' },
148
+ path: [{ kind: 'task' }],
149
+ };
150
+ } else {
151
+ const parentRecord = loadRecord(parent);
152
+ if (!parentRecord) return null;
153
+ const step = stepOf(record);
154
+ if (parentOf(parentRecord)) {
155
+ const inherited = delegationOfRecord(parent, parentRecord, depth + 1);
156
+ if (!inherited || inherited.path.length >= MAX_PATH) return null;
157
+ delegation = { ...inherited, path: [...inherited.path, step] };
158
+ } else {
159
+ delegation = rootFrom(parent, parentRecord, step, { legacy: true });
160
+ }
161
+ if (delegation?.via === 'home') delegation = { ...delegation, via: 'unknown' };
162
+ }
163
+ if (delegation && _liveSessions.get(id) === record) record.delegation = delegation;
164
+ return delegation;
165
+ }
166
+
167
+ /**
168
+ * The `delegation` to record on a session being created with `opts` (its
169
+ * owner/visibility, parentSessionId/ownerSessionId, taskOrigin and agent), or
170
+ * null: none for a top-level session, none for one whose parent's root cannot
171
+ * be established (the record then stays unresolved and the gate fails closed).
172
+ */
173
+ export function delegationForNewSession(opts = {}) {
174
+ const parent = parentOf(opts);
175
+ if (!parent || parent === clean(opts.id)) return null;
176
+ return delegationUnder(parent, stepOf(opts));
177
+ }
178
+
179
+ /** The delegation of a session taking `step` under the session `parent`. */
180
+ function delegationUnder(parent, step) {
181
+ const record = loadRecord(parent);
182
+ if (record && parentOf(record)) {
183
+ // The parent is delegated itself: its recorded root, one step deeper.
184
+ const inherited = delegationOfRecord(parent, record);
185
+ if (!inherited || inherited.path.length >= MAX_PATH) return null;
186
+ return { ...inherited, path: [...inherited.path, step] };
187
+ }
188
+ return rootFrom(parent, record, step);
189
+ }
190
+
191
+ /**
192
+ * Session `callerSessionId` sends the delegated session `sessionId` new work
193
+ * (`agent send`, a spawn of its tag, a task-session send): from now on it
194
+ * acts as that caller does — the delegation it would have been created with
195
+ * under the caller — and the record is saved, so a restart keeps it. A worker
196
+ * a run started is attended when the chat sends it work, and the other way
197
+ * round; only the requests of the session that sends it count, never the
198
+ * session that created it. A turn already running keeps its attended status
199
+ * against a run (the run's request queues behind it and asks): it is never
200
+ * loosened mid-turn. Resolves true when the record changed; false when
201
+ * nothing applies (no record, not delegated, no or the same caller, the same
202
+ * delegation) or the running turn kept its status. A caller whose root cannot
203
+ * be established leaves the record without one, which the gate fails closed on.
204
+ */
205
+ export async function rebindDelegation(sessionId, callerSessionId) {
206
+ const id = clean(sessionId);
207
+ const caller = clean(callerSessionId);
208
+ const record = id ? loadRecord(id) : null;
209
+ if (!record || record.closed === true || !caller || caller === id || !parentOf(record)) return false;
210
+ const current = validDelegation(record.delegation);
211
+ const next = delegationUnder(caller, stepOf(record));
212
+ if (next ? JSON.stringify(current) === JSON.stringify(next) : isUnresolvedMark(record.delegation)) return false;
213
+ if (next?.via === 'run' && current?.via !== 'run' && isTurnInFlight(runtimeState.get(id))) return false;
214
+ record.delegation = next || { ...UNRESOLVED };
215
+ await saveSessionAsync(record, { expectedGeneration: record.generation });
216
+ return true;
217
+ }
218
+
219
+ /** `{ state: 'top' }` for a top-level session (or one with no record),
220
+ * `{ state: 'resolved', ...delegation }` for a delegated one, `{ state:
221
+ * 'unresolved' }` for one with a parent link and no valid recorded root. */
222
+ export function delegationOf(sessionId) {
223
+ const id = clean(sessionId);
224
+ const record = id ? loadRecord(id) : null;
225
+ if (!record) return { state: 'top' };
226
+ const parent = parentOf(record);
227
+ if (!parent) return { state: 'top' };
228
+ const delegation = parent === id ? null : delegationOfRecord(id, record);
229
+ return delegation ? { state: 'resolved', ...delegation } : { state: 'unresolved' };
230
+ }