mixdog 1.3.0 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (423) hide show
  1. package/NOTICE.md +52 -9
  2. package/README.md +8 -4
  3. package/package.json +2 -2
  4. package/scripts/native-release-status.mjs +238 -0
  5. package/src/defaults/shipped-history.json +8 -1
  6. package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
  7. package/src/defaults/skills/setup/SKILL.md +23 -1
  8. package/src/defaults/skills/setup/references/actions.md +54 -4
  9. package/src/defaults/skills/setup/references/surfaces.md +23 -3
  10. package/src/defaults/skills/setup-guide/SKILL.md +17 -10
  11. package/src/defaults/skills/visualize/SKILL.md +9 -6
  12. package/src/defaults/skills/visualize/references/craft.md +8 -1
  13. package/src/help.mjs +1 -1
  14. package/src/lib/keychain-cjs.cjs +4 -1
  15. package/src/lib/rules-builder.cjs +14 -7
  16. package/src/output-styles/common.md +13 -8
  17. package/src/rules/agent-mode/AGENT_MODE.md +9 -6
  18. package/src/rules/agent-mode/MIXY.md +11 -6
  19. package/src/rules/lead/LEAD.md +2 -2
  20. package/src/rules/shared/10-tool-workflow.md +4 -0
  21. package/src/rules/shared/20-research.md +2 -0
  22. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
  23. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
  24. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
  25. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
  26. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
  27. package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
  28. package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
  29. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
  30. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  31. package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
  32. package/src/runtime/agent/orchestrator/config.mjs +11 -0
  33. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +81 -16
  34. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -4
  35. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
  36. package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
  37. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +80 -31
  38. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
  39. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  40. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +67 -38
  41. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
  42. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +7 -2
  43. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
  44. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +34 -16
  45. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
  46. package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
  47. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
  48. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
  49. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +19 -1
  50. package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
  51. package/src/runtime/agent/orchestrator/providers/lib/tool-call-wire-ids.mjs +65 -0
  52. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -1
  53. package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +3 -1
  54. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +1 -0
  55. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +5 -5
  56. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +9 -5
  57. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +9 -1
  58. package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +8 -2
  59. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +10 -2
  60. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +30 -3
  61. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +1 -1
  62. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +3 -1
  63. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +3 -1
  64. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +61 -8
  65. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +14 -2
  66. package/src/runtime/agent/orchestrator/providers/registry.mjs +9 -0
  67. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +17 -0
  68. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
  69. package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
  70. package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
  71. package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
  72. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
  73. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +5 -5
  74. package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
  75. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
  76. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +12 -1
  77. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +17 -6
  78. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +41 -10
  79. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +6 -4
  80. package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
  81. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +2 -0
  82. package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
  83. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
  84. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
  85. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +21 -2
  86. package/src/runtime/agent/orchestrator/session/agent-room.mjs +149 -45
  87. package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +80 -0
  88. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  89. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +14 -11
  90. package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
  91. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +68 -18
  92. package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
  93. package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
  94. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
  95. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +5 -1
  96. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +1 -1
  97. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
  98. package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
  99. package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +230 -0
  100. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +156 -25
  101. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
  102. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +21 -9
  103. package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
  104. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
  105. package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
  106. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
  107. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +189 -105
  108. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +9 -0
  109. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
  110. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +111 -9
  111. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +11 -14
  112. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
  113. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +38 -5
  114. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +21 -0
  115. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
  116. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
  117. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
  118. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +46 -4
  119. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
  120. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +18 -8
  121. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +4 -0
  122. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  123. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  124. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
  125. package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +99 -0
  126. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
  127. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
  128. package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
  129. package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
  130. package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +50 -0
  131. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +40 -1
  132. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
  133. package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +20 -0
  134. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +62 -47
  135. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +6 -8
  136. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +12 -1
  137. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
  138. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +7 -1
  139. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
  140. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +1 -1
  141. package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
  142. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +5 -2
  143. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +42 -10
  144. package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
  145. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +35 -4
  146. package/src/runtime/agent/orchestrator/session/routine-result.mjs +179 -16
  147. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +5 -1
  148. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
  149. package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
  150. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
  151. package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
  152. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
  153. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +8 -3
  154. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +36 -1
  155. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -1
  156. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
  157. package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
  158. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
  159. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
  160. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
  161. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +49 -14
  162. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +134 -17
  163. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +35 -13
  164. package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
  165. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +4 -3
  166. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
  167. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
  168. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
  169. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
  170. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +6 -14
  171. package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
  172. package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
  173. package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
  174. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
  175. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
  176. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +34 -21
  177. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
  178. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
  179. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
  180. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
  181. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
  182. package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
  183. package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
  184. package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
  185. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
  186. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
  187. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
  188. package/src/runtime/attachments/attachment-reference.mjs +155 -0
  189. package/src/runtime/attachments/limits.mjs +5 -1
  190. package/src/runtime/attachments/office-extract.mjs +3 -6
  191. package/src/runtime/attachments/pdf-extract.mjs +84 -10
  192. package/src/runtime/attachments/store.mjs +55 -30
  193. package/src/runtime/browser-bridge/client.mjs +73 -20
  194. package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
  195. package/src/runtime/channels/lib/scheduler.mjs +14 -7
  196. package/src/runtime/channels/lib/state-file.mjs +15 -15
  197. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
  198. package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
  199. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
  200. package/src/runtime/channels/lib/worker-main.mjs +3 -2
  201. package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
  202. package/src/runtime/effort-judge/judge-client.mjs +6 -3
  203. package/src/runtime/effort-judge/model-install.mjs +16 -8
  204. package/src/runtime/effort-judge/model-manifest.json +7 -7
  205. package/src/runtime/local-provider/asset-installer.mjs +10 -4
  206. package/src/runtime/local-provider/catalog.mjs +5 -2
  207. package/src/runtime/local-provider/registered-models.mjs +64 -27
  208. package/src/runtime/local-provider/server.mjs +2 -3
  209. package/src/runtime/media/adapters/image-api.mjs +89 -0
  210. package/src/runtime/media/auth.mjs +64 -1
  211. package/src/runtime/media/catalog.mjs +66 -4
  212. package/src/runtime/media/jobs.mjs +4 -0
  213. package/src/runtime/media/lanes.mjs +46 -5
  214. package/src/runtime/media/media-usage.mjs +17 -0
  215. package/src/runtime/media/renditions.mjs +15 -15
  216. package/src/runtime/media/store.mjs +36 -35
  217. package/src/runtime/media/tool.mjs +8 -1
  218. package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +8 -2
  219. package/src/runtime/memory/lib/embedding-model-files.mjs +30 -7
  220. package/src/runtime/memory/lib/ingest/transcript-tail.mjs +16 -7
  221. package/src/runtime/memory/lib/pg/process.mjs +8 -4
  222. package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
  223. package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
  224. package/src/runtime/office/core/journal.mjs +7 -14
  225. package/src/runtime/office/design/library/design-library-core.mjs +4 -16
  226. package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
  227. package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
  228. package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
  229. package/src/runtime/office/pdf/pdf-render.mjs +30 -18
  230. package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
  231. package/src/runtime/office/portable/portable-soffice.mjs +4 -0
  232. package/src/runtime/shared/agent-kind-migration.mjs +200 -0
  233. package/src/runtime/shared/agent-kind.mjs +152 -0
  234. package/src/runtime/shared/agent-message-envelope.d.mts +6 -0
  235. package/src/runtime/shared/agent-message-envelope.mjs +44 -0
  236. package/src/runtime/shared/agent-reap-state.d.mts +4 -0
  237. package/src/runtime/shared/agent-route-config.d.mts +1 -0
  238. package/src/runtime/shared/agent-route-config.mjs +22 -8
  239. package/src/runtime/shared/agent-store.mjs +149 -0
  240. package/src/runtime/shared/agent-task-starts.mjs +27 -0
  241. package/src/runtime/shared/atomic-file.d.mts +50 -0
  242. package/src/runtime/shared/atomic-file.mjs +394 -132
  243. package/src/runtime/shared/background-tasks.mjs +7 -4
  244. package/src/runtime/shared/bot-hops.mjs +30 -0
  245. package/src/runtime/shared/bot-split-migration.mjs +457 -0
  246. package/src/runtime/shared/bounded-download.mjs +4 -2
  247. package/src/runtime/shared/config-patch.mjs +20 -1
  248. package/src/runtime/shared/config.mjs +39 -9
  249. package/src/runtime/shared/connector-catalog.d.mts +1 -2
  250. package/src/runtime/shared/connector-catalog.mjs +2 -4
  251. package/src/runtime/shared/connector-triggers.mjs +2 -0
  252. package/src/runtime/shared/defaults-separation.mjs +6 -2
  253. package/src/runtime/shared/delegated-approval-host.mjs +74 -0
  254. package/src/runtime/shared/err-text.mjs +7 -1
  255. package/src/runtime/shared/error-presentation.mjs +113 -10
  256. package/src/runtime/shared/file-has-data.mjs +28 -0
  257. package/src/runtime/shared/handoff-artifacts.d.mts +20 -0
  258. package/src/runtime/shared/handoff-artifacts.mjs +61 -0
  259. package/src/runtime/shared/llm/provider-model-identities.mjs +6 -1
  260. package/src/runtime/shared/llm/usage-accounting.mjs +7 -1
  261. package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
  262. package/src/runtime/shared/model-route-fallback.mjs +79 -7
  263. package/src/runtime/shared/pending-agent-work.mjs +54 -0
  264. package/src/runtime/shared/plain-preview.mjs +22 -17
  265. package/src/runtime/shared/process-lifecycle.mjs +8 -12
  266. package/src/runtime/shared/projects.mjs +50 -41
  267. package/src/runtime/shared/provider-accounts.mjs +17 -12
  268. package/src/runtime/shared/routine-trigger.d.mts +6 -1
  269. package/src/runtime/shared/routine-trigger.mjs +14 -6
  270. package/src/runtime/shared/run-child.d.mts +3 -0
  271. package/src/runtime/shared/run-child.mjs +24 -3
  272. package/src/runtime/shared/sensitive-path-rules.d.mts +15 -0
  273. package/src/runtime/shared/sensitive-path-rules.mjs +256 -0
  274. package/src/runtime/shared/session-baseline-store.mjs +8 -2
  275. package/src/runtime/shared/singleton-owner.mjs +7 -6
  276. package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
  277. package/src/runtime/shared/staged-update.mjs +4 -2
  278. package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
  279. package/src/runtime/shared/tool-execution-contract.mjs +6 -0
  280. package/src/runtime/shared/transcript-metadata.mjs +28 -3
  281. package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
  282. package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
  283. package/src/runtime/shared/turn-snapshot.mjs +44 -2
  284. package/src/runtime/shared/usage-readout-totals.mjs +15 -13
  285. package/src/runtime/shared/user-cwd.mjs +9 -4
  286. package/src/runtime/shared/user-data-guard.mjs +46 -44
  287. package/src/runtime/shared/utf8-cut.mjs +10 -0
  288. package/src/runtime/shared/worktrees/activity-gate.mjs +269 -61
  289. package/src/runtime/shared/worktrees/registry.mjs +26 -26
  290. package/src/runtime/tidy/core-install.mjs +26 -4
  291. package/src/runtime/tidy/install.mjs +91 -23
  292. package/src/runtime/web-search/lib/config.mjs +4 -16
  293. package/src/session-runtime/agent-home-delivery.mjs +859 -136
  294. package/src/session-runtime/agent-room-delivery.mjs +167 -19
  295. package/src/session-runtime/agent-task-sessions.mjs +831 -69
  296. package/src/session-runtime/agent-tool-routing.mjs +1 -0
  297. package/src/session-runtime/boot/apis.mjs +58 -24
  298. package/src/session-runtime/boot/config.mjs +1 -0
  299. package/src/session-runtime/boot/facade.mjs +11 -1
  300. package/src/session-runtime/boot/infrastructure.mjs +14 -2
  301. package/src/session-runtime/boot/providers.mjs +16 -6
  302. package/src/session-runtime/boot/shared.mjs +12 -6
  303. package/src/session-runtime/boot/tools.mjs +2 -2
  304. package/src/session-runtime/bot-status.mjs +432 -0
  305. package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
  306. package/src/session-runtime/config-lifecycle.mjs +78 -32
  307. package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
  308. package/src/session-runtime/handoff-attachments.mjs +593 -0
  309. package/src/session-runtime/held-requests.mjs +36 -33
  310. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
  311. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
  312. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +45 -6
  313. package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
  314. package/src/session-runtime/lifecycle/teardown.mjs +11 -3
  315. package/src/session-runtime/mcp-glue/connect.mjs +100 -186
  316. package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
  317. package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
  318. package/src/session-runtime/media-api.mjs +5 -2
  319. package/src/session-runtime/model-route/route-persist.mjs +2 -2
  320. package/src/session-runtime/model-route/route-tuning.mjs +11 -2
  321. package/src/session-runtime/model-route/set-route.mjs +82 -2
  322. package/src/session-runtime/native-web-search.mjs +2 -2
  323. package/src/session-runtime/new-session-config.mjs +6 -1
  324. package/src/session-runtime/owned-automation-run.mjs +8 -1
  325. package/src/session-runtime/room-member-gate.mjs +16 -3
  326. package/src/session-runtime/route-resolution.mjs +21 -19
  327. package/src/session-runtime/route-state.mjs +1 -59
  328. package/src/session-runtime/routine-home-turn.mjs +5 -3
  329. package/src/session-runtime/runtime-bootstrap.mjs +5 -3
  330. package/src/session-runtime/runtime-paths.mjs +11 -5
  331. package/src/session-runtime/runtime-tunables.mjs +6 -0
  332. package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
  333. package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
  334. package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
  335. package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
  336. package/src/session-runtime/services/agent-tool/helpers.mjs +24 -5
  337. package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
  338. package/src/session-runtime/services/agent-tool/send-flow.mjs +36 -24
  339. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +4 -0
  340. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
  341. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +12 -6
  342. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +5 -8
  343. package/src/session-runtime/services/agent-tool/tool-def.mjs +7 -0
  344. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
  345. package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
  346. package/src/session-runtime/services/agent-tool.mjs +6 -0
  347. package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
  348. package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
  349. package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
  350. package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
  351. package/src/session-runtime/services/plugin-admin.mjs +26 -26
  352. package/src/session-runtime/services/provider-admin.mjs +14 -4
  353. package/src/session-runtime/services/schedule-access.mjs +33 -4
  354. package/src/session-runtime/services/seeds.mjs +4 -3
  355. package/src/session-runtime/services/usage-dashboard.mjs +1 -1
  356. package/src/session-runtime/services/usage-stats-model.mjs +2 -1
  357. package/src/session-runtime/session-create/materialize.mjs +2 -3
  358. package/src/session-runtime/session-create/session-options.mjs +15 -2
  359. package/src/session-runtime/settings-builtin-tools-api.mjs +63 -1
  360. package/src/session-runtime/settings-compaction-api.mjs +86 -39
  361. package/src/session-runtime/setup-tool/executor.mjs +20 -3
  362. package/src/session-runtime/setup-tool/extended-actions.mjs +19 -7
  363. package/src/session-runtime/setup-tool/settings-contract.mjs +37 -1
  364. package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
  365. package/src/session-runtime/skills-api.mjs +12 -5
  366. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +265 -0
  367. package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
  368. package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
  369. package/src/session-runtime/tool-policy/policy-stage.mjs +165 -0
  370. package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
  371. package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
  372. package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
  373. package/src/session-runtime/tool-policy-refresh.mjs +47 -358
  374. package/src/session-runtime/tool-surface.mjs +5 -3
  375. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +99 -81
  376. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  377. package/src/session-runtime/turn/turn-run.mjs +2 -1
  378. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
  379. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +578 -301
  380. package/src/session-runtime/workflow-agents-api/agent-route.mjs +113 -53
  381. package/src/session-runtime/workflow-agents-api/onboarding.mjs +11 -4
  382. package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
  383. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
  384. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
  385. package/src/session-runtime/workspace/workspace-state.mjs +7 -6
  386. package/src/standalone/daemon-agent-control.mjs +4 -1
  387. package/src/standalone/daemon-session-control.mjs +60 -11
  388. package/src/standalone/daemon.mjs +34 -4
  389. package/src/standalone/local-session-runtime.mjs +22 -14
  390. package/src/standalone/session-client.mjs +7 -2
  391. package/src/standalone/session-protocol.mjs +7 -0
  392. package/src/standalone/session-service/agent-tree/agent-turns.mjs +46 -3
  393. package/src/standalone/session-service/agent-tree.mjs +4 -3
  394. package/src/standalone/session-service/session-calls/session-turns.mjs +19 -0
  395. package/src/standalone/session-service.mjs +11 -0
  396. package/src/standalone/session-state-projection.mjs +19 -9
  397. package/src/standalone/session-tool-output.mjs +35 -9
  398. package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
  399. package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
  400. package/src/tui/app/slash-commands.mjs +1 -1
  401. package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
  402. package/src/tui/dist/index.mjs +599 -342
  403. package/src/tui/prompt-history-store.mjs +16 -21
  404. package/src/tui/session/context-state.mjs +4 -0
  405. package/src/tui/session/notice-surface.mjs +3 -4
  406. package/src/tui/session/session-api/intake/abort.mjs +19 -1
  407. package/src/tui/session/session-api/media.mjs +2 -1
  408. package/src/tui/session/session-api/routes-agent-definitions.mjs +75 -0
  409. package/src/tui/session/session-api/routes-write.mjs +203 -0
  410. package/src/tui/session/session-api/routes.mjs +23 -236
  411. package/src/tui/session/session-api/settings.mjs +14 -1
  412. package/src/tui/session/session-flow/drain.mjs +28 -3
  413. package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
  414. package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
  415. package/src/tui/session/session-flow/steering.mjs +8 -3
  416. package/src/tui/session/tool-approval.mjs +185 -20
  417. package/src/tui/session/transcript-intake.mjs +4 -0
  418. package/src/tui/session/turn-live-call.mjs +11 -3
  419. package/src/tui/session/turn.mjs +36 -17
  420. package/src/tui/transcript-tool-failures.mjs +8 -2
  421. package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -8
  422. package/src/vendor/statusline/src/gateway/session-routes.mjs +6 -7
  423. package/src/rules/agent-mode/ROUTINE.md +0 -6
@@ -0,0 +1,76 @@
1
+ // Diagnostic per-block prefix fingerprint of an Anthropic request: the tools,
2
+ // each system block (BP1/BP2/BP3/env, the routing prefix included) and the
3
+ // cache_control marker positions, as short hashes only — never any text, so
4
+ // it is safe in shipped builds. The last two requests per session are kept in
5
+ // memory so a usage-detected cache miss (session/loop/send-phase.mjs) can say
6
+ // which block or marker moved.
7
+ import { createHash } from 'node:crypto';
8
+
9
+ const HASH_CHARS = 12;
10
+ const MAX_SESSIONS = 256;
11
+ const bySession = new Map();
12
+
13
+ const hash = (value) =>
14
+ createHash('sha256')
15
+ .update(JSON.stringify(value ?? null))
16
+ .digest('hex')
17
+ .slice(0, HASH_CHARS);
18
+
19
+ const withoutMarker = (block) => {
20
+ if (!block || typeof block !== 'object') return block;
21
+ const { cache_control: _marker, ...rest } = block;
22
+ return rest;
23
+ };
24
+
25
+ const markerTtl = (marker) => (marker?.ttl ? String(marker.ttl) : '5m');
26
+
27
+ /** `{ tools, system: [hash…], markers: ['s1:1h', 'm12:5m', …] }` of a request body. */
28
+ export function anthropicPrefixFingerprint(body) {
29
+ const system = Array.isArray(body?.system) ? body.system : [];
30
+ const messages = Array.isArray(body?.messages) ? body.messages : [];
31
+ const markers = [];
32
+ system.forEach((block, index) => {
33
+ if (block?.cache_control) markers.push(`s${index}:${markerTtl(block.cache_control)}`);
34
+ });
35
+ messages.forEach((message, index) => {
36
+ const content = Array.isArray(message?.content) ? message.content : [];
37
+ const marked = content.find((block) => block?.cache_control);
38
+ if (marked) markers.push(`m${index}:${markerTtl(marked.cache_control)}`);
39
+ });
40
+ return { tools: hash(body?.tools || []), system: system.map((block) => hash(withoutMarker(block))), markers };
41
+ }
42
+
43
+ /** Keep `fingerprint` as the session's latest request (the one before it stays as previous). */
44
+ export function recordAnthropicPrefix(sessionId, fingerprint) {
45
+ if (!sessionId || !fingerprint) return;
46
+ const key = String(sessionId);
47
+ const previous = bySession.get(key)?.current || null;
48
+ bySession.delete(key);
49
+ bySession.set(key, { previous, current: fingerprint });
50
+ if (bySession.size > MAX_SESSIONS) bySession.delete(bySession.keys().next().value);
51
+ }
52
+
53
+ /** The session's latest fingerprint, or null. */
54
+ export function latestAnthropicPrefix(sessionId) {
55
+ return bySession.get(String(sessionId || ''))?.current || null;
56
+ }
57
+
58
+ /** `{ changedBlocks: 'tools,s3' | 'none', markersChanged }` between the session's
59
+ * last two requests, or null before it has two. */
60
+ export function anthropicPrefixChanges(sessionId) {
61
+ const entry = bySession.get(String(sessionId || ''));
62
+ if (!entry?.previous) return null;
63
+ const { previous, current } = entry;
64
+ const changed = [];
65
+ if (previous.tools !== current.tools) changed.push('tools');
66
+ const blocks = Math.max(previous.system.length, current.system.length);
67
+ for (let index = 0; index < blocks; index += 1) {
68
+ if (previous.system[index] !== current.system[index]) changed.push(`s${index}`);
69
+ }
70
+ // Message markers move every turn; only the system markers are a layout change.
71
+ const systemMarkers = (print) => print.markers.filter((marker) => marker.startsWith('s')).join(',');
72
+ return {
73
+ changedBlocks: changed.length ? changed.join(',') : 'none',
74
+ markersChanged: systemMarkers(previous) !== systemMarkers(current),
75
+ };
76
+ }
@@ -10,6 +10,7 @@ import { mergeFlatSchemaProperty } from './flat-schema-merge.mjs';
10
10
  import { reportedCostUsd } from './usage-primitives.mjs';
11
11
  import { reportInvalidTool, wireInputSchema } from './claude-bridge-schema.mjs';
12
12
  import { normalizeServerToolUse } from './anthropic-server-tool-usage.mjs';
13
+ import { ANTHROPIC_TOOL_CALL_IDS, withWireToolCallIds } from './tool-call-wire-ids.mjs';
13
14
  export const ANTHROPIC_CACHE_TTL_STABLE = { type: 'ephemeral', ttl: '1h' };
14
15
  const ANTHROPIC_CACHE_TTL_VOLATILE = { type: 'ephemeral' };
15
16
 
@@ -81,6 +82,22 @@ function appendAnthropicCacheControl(content, ttl = ANTHROPIC_CACHE_TTL_VOLATILE
81
82
  return next;
82
83
  }
83
84
 
85
+ // The builders lift the tail reminder off (splitTailReminder) before lowering and
86
+ // cache marking, then append it here as the final text block, so the message
87
+ // breakpoint stays on the block before it and the cached prefix is unchanged.
88
+ export function appendAnthropicTailReminder(anthropicMessages, reminder) {
89
+ if (!reminder) return anthropicMessages;
90
+ const block = { type: 'text', text: reminder };
91
+ const last = anthropicMessages[anthropicMessages.length - 1];
92
+ if (last?.role === 'user') {
93
+ const content = typeof last.content === 'string' ? [{ type: 'text', text: last.content }] : last.content || [];
94
+ last.content = [...content, block];
95
+ } else {
96
+ anthropicMessages.push({ role: 'user', content: [block] });
97
+ }
98
+ return anthropicMessages;
99
+ }
100
+
84
101
  export function resolveAnthropicCacheTtls(opts) {
85
102
  const strategy = opts?.cacheStrategy || {};
86
103
  const pick = (layer, fallback) => {
@@ -232,7 +249,8 @@ const ANTHROPIC_NATIVE_PROVIDERS = new Set(['anthropic', 'anthropic-oauth']);
232
249
 
233
250
  // `provider`: the engine's provider name. A gateway on a native Messages
234
251
  // route (wire-route.mjs) also replays the references it tagged itself.
235
- export function toAnthropicMessages(messages, availableTools, provider = '') {
252
+ export function toAnthropicMessages(transcript, availableTools, provider = '') {
253
+ const messages = withWireToolCallIds(transcript, ANTHROPIC_TOOL_CALL_IDS);
236
254
  const ownProvider = String(provider || '').toLowerCase();
237
255
  const availableNames = anthropicToolNameSet(availableTools);
238
256
  const result = [];
@@ -0,0 +1,13 @@
1
+ // The tail reminder (session/tail-reminder-rules.mjs) rides the outgoing request
2
+ // as its last message, marked meta.source 'tail-reminder'. It is ephemeral: a
3
+ // provider builder that keeps per-message state (cache breakpoints, message
4
+ // counts, continuation indexes, tool-loop closure) lifts it off first with
5
+ // splitTailReminder, builds from the real history, and appends the reminder as
6
+ // the final wire element, so the history-derived prefix never contains it.
7
+ export const TAIL_REMINDER_META = 'tail-reminder';
8
+
9
+ export function splitTailReminder(messages) {
10
+ const last = Array.isArray(messages) ? messages[messages.length - 1] : null;
11
+ if (last?.role !== 'user' || last.meta?.source !== TAIL_REMINDER_META) return { messages, reminder: '' };
12
+ return { messages: messages.slice(0, -1), reminder: String(last.content || '') };
13
+ }
@@ -0,0 +1,65 @@
1
+ // Tool-call ids at the wire. A transcript keeps each tool call's id as the
2
+ // provider that produced it minted it; after a model switch another provider
3
+ // may refuse it (Anthropic accepts only [A-Za-z0-9_-]; OpenAI chat caps the
4
+ // length). Each request builder projects the ids onto its own rule just
5
+ // before lowering: a valid id passes unchanged (so native replay items and
6
+ // the provider's own ids stay byte-exact), an invalid one becomes a
7
+ // deterministic, collision-free stand-in, and a call and its result always
8
+ // map to the same value. Persisted history is never rewritten.
9
+ import { createHash } from 'node:crypto';
10
+
11
+ export const ANTHROPIC_TOOL_CALL_IDS = Object.freeze({ allowed: /^[A-Za-z0-9_-]+$/, maxLength: 64 });
12
+ export const OPENAI_RESPONSES_TOOL_CALL_IDS = Object.freeze({ allowed: /^[A-Za-z0-9_.:-]+$/, maxLength: 64 });
13
+ export const OPENAI_CHAT_TOOL_CALL_IDS = Object.freeze({ allowed: /^[A-Za-z0-9_.:-]+$/, maxLength: 40 });
14
+
15
+ const HASH_CHARS = 10;
16
+
17
+ function standIn(id, rule, salt) {
18
+ const hash = createHash('sha256').update(`${salt}\u0000${id}`).digest('hex').slice(0, HASH_CHARS);
19
+ const stem = String(id)
20
+ .replace(/[^A-Za-z0-9_-]+/g, '_')
21
+ .replace(/^_+|_+$/g, '')
22
+ .slice(0, Math.max(0, rule.maxLength - HASH_CHARS - 1));
23
+ return stem ? `${stem}_${hash}` : `call_${hash}`.slice(0, rule.maxLength);
24
+ }
25
+
26
+ const isValid = (id, rule) => id.length > 0 && id.length <= rule.maxLength && rule.allowed.test(id);
27
+
28
+ /** id → wire id for every tool call and result in `messages` under `rule`. */
29
+ export function toolCallWireIdMap(messages, rule) {
30
+ const ids = new Set();
31
+ for (const message of Array.isArray(messages) ? messages : []) {
32
+ if (message?.role === 'assistant')
33
+ for (const call of message.toolCalls || []) if (call?.id) ids.add(String(call.id));
34
+ if (message?.role === 'tool' && message.toolCallId) ids.add(String(message.toolCallId));
35
+ }
36
+ const map = new Map();
37
+ const taken = new Set([...ids].filter((id) => isValid(id, rule)));
38
+ for (const id of ids) {
39
+ if (isValid(id, rule)) continue;
40
+ let salt = 0;
41
+ let wire = standIn(id, rule, salt);
42
+ while (taken.has(wire)) wire = standIn(id, rule, ++salt);
43
+ taken.add(wire);
44
+ map.set(id, wire);
45
+ }
46
+ return map;
47
+ }
48
+
49
+ /** `messages` with tool-call ids projected onto `rule`; the same array when none needs it. */
50
+ export function withWireToolCallIds(messages, rule) {
51
+ const map = toolCallWireIdMap(messages, rule);
52
+ if (!map.size) return messages;
53
+ return messages.map((message) => {
54
+ if (message?.role === 'tool' && map.has(String(message.toolCallId))) {
55
+ return { ...message, toolCallId: map.get(String(message.toolCallId)) };
56
+ }
57
+ if (message?.role !== 'assistant' || !message.toolCalls?.some((call) => map.has(String(call?.id)))) return message;
58
+ return {
59
+ ...message,
60
+ toolCalls: message.toolCalls.map((call) =>
61
+ map.has(String(call?.id)) ? { ...call, id: map.get(String(call.id)) } : call
62
+ ),
63
+ };
64
+ });
65
+ }
@@ -652,9 +652,17 @@ export function contentImageDescriptors(content) {
652
652
  // one-line description.
653
653
  // The page count (recorded by intake and by the read tool) rides on the part
654
654
  // itself, so an inline part and its stored reference carry the same one.
655
+ // The text-layer character count (recorded with it) prices the text a native
656
+ // reader extracts on top of each page image.
655
657
  function pdfDescriptor(sizeBytes, part) {
656
658
  const pageCount = Math.floor(Number(part?.pageCount));
657
- return { mimeType: 'application/pdf', sizeBytes, ...(pageCount > 0 ? { pageCount } : {}) };
659
+ const textChars = Number(part?.textChars);
660
+ return {
661
+ mimeType: 'application/pdf',
662
+ sizeBytes,
663
+ ...(pageCount > 0 ? { pageCount } : {}),
664
+ ...(pageCount > 0 && Number.isFinite(textChars) && textChars >= 0 ? { textChars: Math.floor(textChars) } : {}),
665
+ };
658
666
  }
659
667
 
660
668
  export function contentFileDescriptors(content) {
@@ -974,6 +982,13 @@ function normalizeAnthropicPart(part) {
974
982
  if (part?.type === 'text' && isAttachmentReference(part)) {
975
983
  return { type: 'text', text: attachmentTextForPart(part) };
976
984
  }
985
+ // A media reference (attachment-reference.mjs) is a text part whose
986
+ // `attachmentReference` metadata stays in saved history. It is not a
987
+ // Messages API text-block field, and the API rejects the whole request for it.
988
+ if (part?.type === 'text' && part.attachmentReference !== undefined) {
989
+ const { attachmentReference: _internal, ...wirePart } = part;
990
+ return wirePart;
991
+ }
977
992
  return part;
978
993
  }
979
994
 
@@ -13,8 +13,10 @@ import { beginLocalInference, localModelState } from '../../../local-provider/mo
13
13
 
14
14
  export class MixdogLocalProvider {
15
15
  static inputExcludesCache = false;
16
- // The managed local runtime is text-only: PDFs lower to text.
16
+ // The managed local runtime is text-only: PDFs lower to text and images to
17
+ // a placeholder (input-capabilities.mjs).
17
18
  nativePdf = false;
19
+ takesImages = false;
18
20
 
19
21
  constructor(config = {}, { ensureServer = ensureLocalProviderServer, runRequest = runLocalProviderRequest } = {}) {
20
22
  this.name = 'mixdog-local';
@@ -111,6 +111,7 @@ export class ModelProxyProvider {
111
111
  // Adapter traits are reported by the PC's real instance.
112
112
  if (typeof this.config.modelProxy?.nativePdf === 'boolean') this.nativePdf = this.config.modelProxy.nativePdf;
113
113
  if (this.config.modelProxy?.consumesReadWindows === true) this.consumesReadWindows = true;
114
+ if (this.config.modelProxy?.takesImages === false) this.takesImages = false;
114
115
  }
115
116
 
116
117
  #call(method, fields = {}, options = {}) {
@@ -3,8 +3,9 @@
3
3
  // handshake-header projection, and the per-turn x-codex-turn-state guard.
4
4
  // openai-oauth-ws.mjs owns the transport flow only.
5
5
  import { createHash, randomUUID } from 'node:crypto';
6
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
6
+ import { existsSync, mkdirSync, readFileSync } from 'node:fs';
7
7
  import { join } from 'node:path';
8
+ import { writeFileAtomicSync } from '../../../shared/atomic-file.mjs';
8
9
  import { cleanString } from '../../../shared/clean.mjs';
9
10
  import { getPluginData } from '../config.mjs';
10
11
  import { activateCodexTurnState } from './openai-turn-state.mjs';
@@ -77,15 +78,14 @@ function _codexInstallationId(sendOpts) {
77
78
  const generated = randomUUID();
78
79
  try {
79
80
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
80
- try {
81
- writeFileSync(file, generated, { encoding: 'utf8', flag: 'wx', mode: 0o644 });
82
- } catch {
81
+ // Atomic create-if-absent; a damaged (empty/NUL) file is replaced whole.
82
+ if (!writeFileAtomicSync(file, generated, { createOnly: true, mode: 0o644 })) {
83
83
  const winner = readFileSync(file, 'utf8').trim().toLowerCase();
84
84
  if (UUID_RE.test(winner)) {
85
85
  _installationId = winner;
86
86
  return _installationId;
87
87
  }
88
- writeFileSync(file, generated, { encoding: 'utf8', mode: 0o644 });
88
+ writeFileAtomicSync(file, generated, { mode: 0o644 });
89
89
  }
90
90
  } catch {}
91
91
  _installationId = generated;
@@ -94,10 +94,14 @@ async function compatModelsError(name, res, apiKey = '') {
94
94
  }
95
95
 
96
96
  // `keyless` omits the Authorization header entirely (keyless endpoints);
97
- // `signal` aborts the request together with the list timeout.
98
- export async function fetchCompatModelItems(provider, { signal = null, keyless = false } = {}) {
97
+ // `signal` aborts the request together with the list timeout; `timeoutMs`
98
+ // replaces the default list timeout for callers that wait on a slow catalog.
99
+ export async function fetchCompatModelItems(
100
+ provider,
101
+ { signal = null, keyless = false, timeoutMs = MODEL_LIST_TIMEOUT_MS } = {}
102
+ ) {
99
103
  const name = provider.name;
100
- const timeout = createTimeoutSignal(signal, MODEL_LIST_TIMEOUT_MS, `${name} model list`);
104
+ const timeout = createTimeoutSignal(signal, timeoutMs, `${name} model list`);
101
105
  try {
102
106
  const apiKey = keyless ? '' : resolveProviderApiKey(name, provider.config || provider);
103
107
  const res = await fetch(`${String(provider.baseURL || '').replace(/\/+$/, '')}/models`, {
@@ -137,9 +141,9 @@ function nativeMode(m) {
137
141
  return null;
138
142
  }
139
143
 
140
- export async function listCompatModels(provider, { throwOnError = false, signal = null } = {}) {
144
+ export async function listCompatModels(provider, { throwOnError = false, signal = null, timeoutMs } = {}) {
141
145
  try {
142
- const list = await provider._fetchModelItems({ signal });
146
+ const list = await provider._fetchModelItems({ signal, timeoutMs });
143
147
  const models = [];
144
148
  for (const m of list) {
145
149
  const contextWindow = Number(
@@ -29,6 +29,7 @@ import { isCustomProviderName, resolveCustomReasoning } from './custom-reasoning
29
29
  import { projectEffortConfiguration, withEffortConfigurationFallback } from './effort-configuration.mjs';
30
30
  import { normalizeCacheKey } from '../agent-runtime/cache-strategy.mjs';
31
31
  import { createProviderReplay } from './lib/provider-replay.mjs';
32
+ import { splitTailReminder } from './lib/tail-reminder-split.mjs';
32
33
  import {
33
34
  applyCompatToolChoice,
34
35
  compatResponsesReplayProvider,
@@ -296,7 +297,13 @@ export function sendCompatResponses(provider, messages, useModel, tools, opts =
296
297
  return withEffortConfigurationFallback(provider.name, useModel, opts, send);
297
298
  }
298
299
 
299
- async function sendCompatResponsesOnce(provider, messages, useModel, tools, opts) {
300
+ async function sendCompatResponsesOnce(provider, requestMessages, useModel, tools, opts) {
301
+ // The continuation state below is indexed by transcript position
302
+ // (seenMessageCount, reasoning messageIndex): the ephemeral tail reminder is
303
+ // not part of the transcript, so it is lifted off here and appended to the
304
+ // wire input last.
305
+ const { messages: transcript, reminder } = splitTailReminder(requestMessages);
306
+ let messages = transcript;
300
307
  const replayProvider = compatResponsesReplayProvider(provider.name);
301
308
  const signal = opts.signal || null;
302
309
  if (signal?.aborted) {
@@ -320,6 +327,7 @@ async function sendCompatResponsesOnce(provider, messages, useModel, tools, opts
320
327
  includeSystem: provider.config?.responsesSystemAsInstructions !== true,
321
328
  });
322
329
  const instructions = provider.config?.responsesSystemAsInstructions === true ? xaiSystemInstructions(messages) : '';
330
+ if (reminder) input.push({ role: 'user', content: [{ type: 'input_text', text: reminder }] });
323
331
  const params = buildCompatResponsesParams({
324
332
  provider,
325
333
  useModel,
@@ -104,10 +104,16 @@ export const COMPAT_RETRY_PROFILES = Object.freeze({
104
104
  }),
105
105
  });
106
106
 
107
- /** withRetry options (attempt budget, schedule, jitter) for a provider; {} for the shared defaults. */
107
+ // A provider without a profile (custom endpoints, gateways, local servers)
108
+ // keeps the shared ladder, but a Retry-After beyond a minute is not waited
109
+ // out: it surfaces with its wait so the account pool can rotate and the
110
+ // notice can name when to try again.
111
+ export const COMPAT_DEFAULT_MAX_RETRY_AFTER_MS = 60_000;
112
+
113
+ /** withRetry options (attempt budget, schedule, jitter) for a provider; only the advice ceiling for the shared defaults. */
108
114
  export function compatRetryOptions(providerName) {
109
115
  const profile = COMPAT_RETRY_PROFILES[providerName];
110
- if (!profile) return {};
116
+ if (!profile) return { maxRetryAfterMs: COMPAT_DEFAULT_MAX_RETRY_AFTER_MS };
111
117
  return {
112
118
  maxAttempts: profile.maxAttempts,
113
119
  backoffMs: profile.backoffMs,
@@ -17,6 +17,11 @@ import {
17
17
  } from './media-normalization.mjs';
18
18
  import { customToolCallFromResponseItem, nativeToolSearchCallFromArguments } from './custom-tool-wire.mjs';
19
19
  import { providerReplayItems } from './lib/provider-replay.mjs';
20
+ import {
21
+ OPENAI_CHAT_TOOL_CALL_IDS,
22
+ OPENAI_RESPONSES_TOOL_CALL_IDS,
23
+ withWireToolCallIds,
24
+ } from './lib/tool-call-wire-ids.mjs';
20
25
  import { acceptedWireTools } from './lib/claude-bridge-schema.mjs';
21
26
  import { ensureResponsesCallOutputs } from './lib/wire-pairing.mjs';
22
27
  import { responsesEffortUpdates } from './effort-configuration.mjs';
@@ -34,7 +39,9 @@ export function resolveCompatMaxOutputTokens(opts = {}) {
34
39
  );
35
40
  }
36
41
 
37
- export function toOpenAIMessages(messages, providerName, options = {}) {
42
+ export function toOpenAIMessages(transcript, providerName, options = {}) {
43
+ const messages =
44
+ options.preserveToolCallIds === true ? transcript : withWireToolCallIds(transcript, OPENAI_CHAT_TOOL_CALL_IDS);
38
45
  // NOTE: chat.completions has no equivalent slot for replaying reasoning
39
46
  // encrypted_content the way the Responses API does (no `type:'reasoning'`
40
47
  // input item). Whatever reasoningItems may be attached to assistant
@@ -421,7 +428,8 @@ export function xaiSystemInstructions(messages) {
421
428
  return instructions || undefined;
422
429
  }
423
430
 
424
- export function toXaiResponsesInput(messages, providerState, options = {}) {
431
+ export function toXaiResponsesInput(transcript, providerState, options = {}) {
432
+ const messages = withWireToolCallIds(transcript, OPENAI_RESPONSES_TOOL_CALL_IDS);
425
433
  const includeSystem = options.includeSystem !== false;
426
434
  // Generic Responses callers (opencode-go gateway) keep their own state
427
435
  // slot and replay tag so a provider switch never replays foreign items.
@@ -41,7 +41,25 @@ function promptCacheForm(model) {
41
41
  return openAiSupportsExtendedRetention(model) ? 'retention' : null;
42
42
  }
43
43
 
44
- export function applyOpenAIDirectCachePolicy(body, model, storeResponses) {
44
+ // The environment developer item buildRequestBody places first in `input`:
45
+ // the end of the session's stable prefix (tools + instructions + environment).
46
+ // Top-level `instructions` cannot carry a breakpoint, so this is the earliest
47
+ // place one fits.
48
+ function stablePrefixBlock(body) {
49
+ const first = Array.isArray(body.input) ? body.input[0] : null;
50
+ const content = first?.type === 'message' && first.role === 'developer' ? first.content : null;
51
+ const block = Array.isArray(content) && content.length === 1 ? content[0] : null;
52
+ return block?.type === 'input_text' && String(block.text || '').startsWith('<environment_context>') ? block : null;
53
+ }
54
+
55
+ /**
56
+ * Public-API cache lifetime fields. GPT-5.6+: request ttl "30m" for the
57
+ * moving tail; `prefixTtl` '12h' (Lead class, cache-strategy.mjs
58
+ * resolveOpenAiPrefixCacheTtl) adds one explicit 12h breakpoint at the end of
59
+ * the stable prefix in the default implicit mode, so the implicit tail
60
+ * breakpoint stays 30m. Earlier models are unchanged.
61
+ */
62
+ export function applyOpenAIDirectCachePolicy(body, model, storeResponses, { prefixTtl = null } = {}) {
45
63
  body.store = storeResponses;
46
64
  delete body.prompt_cache_retention;
47
65
  delete body.prompt_cache_options;
@@ -49,7 +67,16 @@ export function applyOpenAIDirectCachePolicy(body, model, storeResponses) {
49
67
  // response storage is disabled. The provider's default cache still applies.
50
68
  if (!storeResponses) return body;
51
69
  const form = promptCacheForm(model);
52
- if (form === 'options') body.prompt_cache_options = { ttl: '30m' };
53
- else if (form === 'retention') body.prompt_cache_retention = '24h';
70
+ if (form === 'options') {
71
+ body.prompt_cache_options = { ttl: '30m' };
72
+ const block = prefixTtl === '12h' ? stablePrefixBlock(body) : null;
73
+ if (block) {
74
+ const [first, ...rest] = body.input;
75
+ body.input = [
76
+ { ...first, content: [{ ...block, prompt_cache_breakpoint: { mode: 'explicit', ttl: '12h' } }] },
77
+ ...rest,
78
+ ];
79
+ }
80
+ } else if (form === 'retention') body.prompt_cache_retention = '24h';
54
81
  return body;
55
82
  }
@@ -25,7 +25,7 @@ export function openAiCatalogMeta(model) {
25
25
  // gpt-5.4 -> [5, 4]; gpt-6-astra -> [6, 0]; non-GPT ids -> null.
26
26
  // A bare multi-digit major with no minor or family suffix (gpt-60) is not a
27
27
  // generation id.
28
- function gptVersion(id) {
28
+ export function gptVersion(id) {
29
29
  const m = /^gpt-(\d+)(?:\.(\d+))?(?=-|$)/.exec(id);
30
30
  if (!m || (m[1].length > 1 && m[2] === undefined && m[0] === id)) return null;
31
31
  return [Number(m[1]), Number(m[2] || 0)];
@@ -1,6 +1,7 @@
1
1
  // Transcript lowering is independent of request/turn metadata. A retained
2
2
  // message must have the same wire representation in every subsequent request.
3
3
  import { providerReplayItems } from './lib/provider-replay.mjs';
4
+ import { OPENAI_RESPONSES_TOOL_CALL_IDS, withWireToolCallIds } from './lib/tool-call-wire-ids.mjs';
4
5
  import { responsesEffortUpdates } from './effort-configuration.mjs';
5
6
  import { ensureResponsesCallOutputs } from './lib/wire-pairing.mjs';
6
7
  import { normalizeContentForOpenAIResponses, splitToolContentForOpenAIResponses } from './media-normalization.mjs';
@@ -147,7 +148,8 @@ function pushAssistantToolCalls(toolCalls, { out, customToolCallIds, nativeSearc
147
148
  * - `includeSystem`, `nativePdf`, `splitToolContent`, `startIndex`: the
148
149
  * gateway's system/media/continuation handling.
149
150
  */
150
- export function convertMessagesToResponsesInput(messages, opts = {}) {
151
+ export function convertMessagesToResponsesInput(transcript, opts = {}) {
152
+ const messages = withWireToolCallIds(transcript, OPENAI_RESPONSES_TOOL_CALL_IDS);
151
153
  const out = [];
152
154
  const pendingToolMedia = [];
153
155
  const pendingToolLoads = [];
@@ -142,7 +142,9 @@ export class OpenAIDirectProvider {
142
142
  // Keep public response storage and model-specific cache options out of
143
143
  // the shared OAuth payload. Storage opt-out still forces full frames in
144
144
  // _computeDelta; retained output items make those frames self-contained.
145
- applyOpenAIDirectCachePolicy(body, useModel, _envFlag('MIXDOG_OAI_STORE', true));
145
+ applyOpenAIDirectCachePolicy(body, useModel, _envFlag('MIXDOG_OAI_STORE', true), {
146
+ prefixTtl: opts.promptCachePrefixTtl || null,
147
+ });
146
148
  // poolKey MUST be sessionId-only. Falling back to promptCacheKey would
147
149
  // let unrelated raw sessions sharing the same provider-scoped cache
148
150
  // bucket reuse each other's pooled socket and inherit lastResponseId
@@ -4,11 +4,14 @@
4
4
  * The organization's remote config (`/api/v2/config` → providers.opencode-go)
5
5
  * is the authority for the inference base URL, the request headers and the
6
6
  * model catalog (the gateway's own `/models` is not served on this base).
7
+ * When that config lists no models at all (the console serves `models: {}` for
8
+ * an organization without overrides, as the OpenCode CLI sees it), the catalog
9
+ * is the models.dev `opencode-go` provider, the list the CLI itself shows.
7
10
  * Requests run on the OpenCode Go gateway engines (opencode-go.mjs) with the
8
11
  * access token as the bearer; a 401 refreshes the token once and retries.
9
12
  */
10
13
  import { scrubOAuthSecrets } from './lib/oauth-token-utils.mjs';
11
- import { getModelMetadataSync } from './model-catalog.mjs';
14
+ import { getModelMetadataSync, getModelsDevProviderModelsSync, loadModelsDevCatalog } from './model-catalog.mjs';
12
15
  import { makeModelCache } from './model-cache.mjs';
13
16
  import { OpenCodeGoProvider, WIRE_API_BY_NPM, resolveOpenCodeGoWireRoute } from './opencode-go.mjs';
14
17
  import { fetchOpenCodeGoOAuthUsageSnapshot } from './opencode-go-usage.mjs';
@@ -90,11 +93,51 @@ function catalogRow(id, model, providerPackage) {
90
93
  };
91
94
  }
92
95
 
96
+ // A models.dev row in the remote config's model shape, for catalogRow.
97
+ function configModelFromModelsDev(row) {
98
+ const cost = isPlainObject(row.cost) ? row.cost : null;
99
+ const effort = Array.isArray(row.reasoning_options)
100
+ ? row.reasoning_options.find((option) => option?.type === 'effort')?.values
101
+ : null;
102
+ return {
103
+ name: row.name,
104
+ ...(typeof row.npm === 'string' ? { package: row.npm } : {}),
105
+ limit: row.limit,
106
+ ...(cost
107
+ ? { cost: { input: cost.input, output: cost.output, cache: { read: cost.cache_read, write: cost.cache_write } } }
108
+ : {}),
109
+ ...(typeof row.reasoning === 'boolean'
110
+ ? { capabilities: { reasoning: { supported: row.reasoning, ...(effort ? { effort } : {}) } } }
111
+ : {}),
112
+ };
113
+ }
114
+
115
+ // models.dev serves the Go models over the OpenAI-compatible package unless a row names its own.
116
+ const MODELS_DEV_PROVIDER_PACKAGE = '@ai-sdk/openai-compatible';
117
+
118
+ const listsGoModels = (json) => Object.keys(json?.providers?.[CONFIG_PROVIDER_KEY]?.models || {}).length > 0;
119
+
120
+ // The models.dev Go catalog, loaded first because the sync read is null on a cold cache.
121
+ // The loader swallows its own failure, so a missing catalog is a failed discovery
122
+ // (the last published roster stays), never an authoritative empty roster.
123
+ async function loadModelsDevGoModels() {
124
+ await loadModelsDevCatalog();
125
+ const models = getModelsDevProviderModelsSync(CONFIG_PROVIDER_KEY);
126
+ if (!models || Object.keys(models).length === 0) {
127
+ throw openCodeGoError(
128
+ `[${PROVIDER_ID}] the OpenCode organization config lists no models and the models.dev OpenCode Go catalog is unavailable`,
129
+ { kind: 'catalog' }
130
+ );
131
+ }
132
+ return models;
133
+ }
134
+
93
135
  /**
94
136
  * The Go provider of a remote config response: `{ baseURL, headers, models }`
95
- * with one catalog row per enabled model.
137
+ * with one catalog row per enabled model. A config that lists no models takes
138
+ * the models.dev Go models (`modelsDevModels`, id → row) instead.
96
139
  */
97
- export function goConfigFromResponse(json) {
140
+ export function goConfigFromResponse(json, modelsDevModels = null) {
98
141
  const entry = json?.providers?.[CONFIG_PROVIDER_KEY];
99
142
  if (!isPlainObject(entry)) {
100
143
  throw openCodeGoError(`[${PROVIDER_ID}] the OpenCode organization config has no OpenCode Go provider`, {
@@ -102,9 +145,20 @@ export function goConfigFromResponse(json) {
102
145
  });
103
146
  }
104
147
  const providerPackage = typeof entry.package === 'string' ? entry.package : '';
105
- const models = Object.entries(isPlainObject(entry.models) ? entry.models : {})
148
+ const fallback = !listsGoModels(json) && isPlainObject(modelsDevModels);
149
+ const source = fallback
150
+ ? Object.fromEntries(
151
+ Object.entries(modelsDevModels).map(([id, row]) => [
152
+ id,
153
+ isPlainObject(row) ? configModelFromModelsDev(row) : row,
154
+ ])
155
+ )
156
+ : isPlainObject(entry.models)
157
+ ? entry.models
158
+ : {};
159
+ const models = Object.entries(source)
106
160
  .filter(([id, model]) => id && isPlainObject(model) && model.disabled !== true)
107
- .map(([id, model]) => catalogRow(id, model, providerPackage));
161
+ .map(([id, model]) => catalogRow(id, model, fallback ? MODELS_DEV_PROVIDER_PACKAGE : providerPackage));
108
162
  return {
109
163
  baseURL:
110
164
  typeof entry.settings?.baseURL === 'string' && entry.settings.baseURL
@@ -172,9 +226,8 @@ export class OpenCodeGoOAuthProvider {
172
226
  const current = loadTokens();
173
227
  return current?.account_id === tokens.account_id && current?.org_id === tokens.org_id;
174
228
  });
175
- const remote = goConfigFromResponse(
176
- await consoleGetJson(CONFIG_URL, tokens, { fetchFn: this.config.fetchImpl, refresh: true })
177
- );
229
+ const response = await consoleGetJson(CONFIG_URL, tokens, { fetchFn: this.config.fetchImpl, refresh: true });
230
+ const remote = goConfigFromResponse(response, listsGoModels(response) ? null : await loadModelsDevGoModels());
178
231
  publish(remote.models, {
179
232
  baseURL: remote.baseURL,
180
233
  headers: remote.headers,
@@ -335,8 +335,19 @@ const CODE_KINDS = new Map(
335
335
  quota_exhausted: ['quota_exceeded', 'quota_exhausted', 'usage_limit_reached', 'provider_accounts_exhausted'],
336
336
  context_overflow: ['context_length_exceeded', 'context_window_exceeded', 'model_context_window_exceeded'],
337
337
  request_too_large: ['request_too_large', 'payload_too_large', 'request_entity_too_large'],
338
- auth: ['invalid_api_key', 'authentication_error', 'unauthenticated', 'unauthorized', 'invalid_authentication'],
339
- permission: ['permission_error', 'permission_denied', 'forbidden'],
338
+ // Google names a rejected or expired key with an ErrorInfo reason on an
339
+ // INVALID_ARGUMENT 400; the reason outranks the envelope status.
340
+ auth: [
341
+ 'invalid_api_key',
342
+ 'authentication_error',
343
+ 'unauthenticated',
344
+ 'unauthorized',
345
+ 'invalid_authentication',
346
+ 'api_key_invalid',
347
+ 'api_key_expired',
348
+ 'access_token_expired',
349
+ ],
350
+ permission: ['permission_error', 'permission_denied', 'forbidden', 'service_disabled', 'api_key_service_blocked'],
340
351
  overloaded: [
341
352
  'server_is_overloaded',
342
353
  'overloaded_error',
@@ -374,6 +385,7 @@ const CODE_KINDS = new Map(
374
385
  'not_found',
375
386
  'not_found_error',
376
387
  'model_not_found',
388
+ 'model_not_supported',
377
389
  'unsupported_parameter',
378
390
  ],
379
391
  }).flatMap(([kind, codes]) => codes.map((code) => [code, kind]))
@@ -509,6 +509,15 @@ export function providerInputExcludesCache(name) {
509
509
  export function providerNativePdf(name) {
510
510
  return providerTakesNativePdf(providers.get(String(name || '').toLowerCase()));
511
511
  }
512
+ /**
513
+ * Whether the named provider sends image input to its models: false only for
514
+ * an adapter that declares `takesImages = false` (the text-only local
515
+ * runtime, which lowers every image to a placeholder). A pure lookup; an
516
+ * unknown provider is treated as taking images, as attachments are.
517
+ */
518
+ export function providerReadsImages(name) {
519
+ return providers.get(String(name || '').toLowerCase())?.takesImages !== false;
520
+ }
512
521
  /**
513
522
  * Whether the named provider's adapter declares `consumesReadWindows`: it
514
523
  * needs the structured clip report of read results. A pure lookup; a proxy