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
@@ -6,6 +6,8 @@
6
6
  export const CONNECTOR_TRIGGER_MAX_CHARS = 100;
7
7
 
8
8
  export const CONNECTOR_TRIGGERS = Object.freeze({
9
+ // Not a catalog connector (Mixdog uses the signed-in GitHub CLI), but a
10
+ // user-added `github` MCP server still gets this line.
9
11
  github: 'GitHub repos, issues, pull requests, Actions and code search; not local Git history or diffs.',
10
12
  notion: 'Notion pages, databases and workspace search; create or update team docs and notes.',
11
13
  linear: 'Linear issues, projects, cycles and comments; create, triage or update tickets.',
@@ -11,9 +11,10 @@
11
11
  * Left untouched on purpose: `desktop`, credentials, onboarding, detection and
12
12
  * install flags, caches, and keys that are only ever hand-edited.
13
13
  */
14
- import { constants, copyFileSync, existsSync, mkdirSync } from 'node:fs';
14
+ import { existsSync, mkdirSync, readFileSync, statSync } from 'node:fs';
15
15
  import { join } from 'node:path';
16
16
  import { isDeepStrictEqual } from 'node:util';
17
+ import { writeFileAtomicSync } from './atomic-file.mjs';
17
18
  import { packDefinitionFromDir } from './shipped-definitions.mjs';
18
19
  import { resolvePluginData } from './plugin-paths.mjs';
19
20
  import { hasOwn, isPlainObject } from './object.mjs';
@@ -231,7 +232,10 @@ export function backupConfigBeforeDefaultsSeparation({ dataDir = resolvePluginDa
231
232
  throw err;
232
233
  }
233
234
  const target = join(dir, 'mixdog-config.json');
234
- copyFileSync(source, target, constants.COPYFILE_EXCL);
235
+ // Written through an fsynced handle (a plain copy can leave this backup NUL
236
+ // exactly when a crash makes it needed); the folder was claimed above, so
237
+ // nothing can exist at `target`.
238
+ writeFileAtomicSync(target, readFileSync(source), { mode: statSync(source).mode & 0o777 });
235
239
  return target;
236
240
  }
237
241
  }
@@ -0,0 +1,74 @@
1
+ // Delegated approvals: a sub-agent's approval request (tui/session/tool-approval.mjs)
2
+ // is also shown in the top-level conversation the user watches — the root bot's
3
+ // home chat, or the coding Lead's session (human-gate/gate.mjs approvalSurface).
4
+ // It stays ONE request: the sub-agent's queue owns it and its waiter; the
5
+ // top-level session's queue holds a copy whose answer is forwarded to that
6
+ // owner, and the copy is withdrawn as soon as the owner settles (answered in
7
+ // either place, timed out, its turn ended). Like held-request-host.mjs this
8
+ // registry is process-wide and dependency-free.
9
+
10
+ const KEY = Symbol.for('mixdog.delegatedApprovalHost.v1');
11
+
12
+ function registry() {
13
+ globalThis[KEY] ??= { queues: new Set(), pending: new Map(), opener: null };
14
+ return globalThis[KEY];
15
+ }
16
+
17
+ /** `queue`: `{ sessionId() → string, disposed() → boolean, offer(delegated),
18
+ * withdraw(id, result) }` — one session runtime's approval queue. */
19
+ export function registerApprovalQueue(queue) {
20
+ registry().queues.add(queue);
21
+ return () => registry().queues.delete(queue);
22
+ }
23
+
24
+ /** `open(sessionId) → Promise`: brings a session's runtime up (the daemon's
25
+ * session service), so a top-level chat that is not loaded can show the copy. */
26
+ export function registerDelegatedApprovalOpener(open) {
27
+ registry().opener = open;
28
+ return () => {
29
+ if (registry().opener === open) registry().opener = null;
30
+ };
31
+ }
32
+
33
+ function liveQueuesOf(sessionId) {
34
+ const { queues } = registry();
35
+ const live = [];
36
+ for (const queue of queues) {
37
+ if (queue.disposed()) queues.delete(queue);
38
+ else if (queue.sessionId() === sessionId) live.push(queue);
39
+ }
40
+ return live;
41
+ }
42
+
43
+ function offerTo(sessionId, delegated) {
44
+ const live = liveQueuesOf(sessionId);
45
+ for (const queue of live) queue.offer(delegated);
46
+ return live.length > 0;
47
+ }
48
+
49
+ /** Show `delegated` (`{ id, sessionId, request, answer(...finishArgs) → boolean }`,
50
+ * `sessionId` being the top-level conversation) there, now or once it loads. */
51
+ export function delegateApproval(delegated) {
52
+ const { pending, opener } = registry();
53
+ pending.set(delegated.id, delegated);
54
+ if (offerTo(delegated.sessionId, delegated) || typeof opener !== 'function') return;
55
+ void Promise.resolve()
56
+ .then(() => opener(delegated.sessionId))
57
+ .then(() => {
58
+ if (pending.get(delegated.id) === delegated) offerTo(delegated.sessionId, delegated);
59
+ })
60
+ .catch(() => {});
61
+ }
62
+
63
+ /** The owner settled `id`: every copy goes away. `result` (`{ approved, device }`)
64
+ * lets the copy say who answered. */
65
+ export function withdrawDelegatedApproval(id, result = null) {
66
+ const { pending, queues } = registry();
67
+ if (!pending.delete(id)) return;
68
+ for (const queue of queues) queue.withdraw(id, result);
69
+ }
70
+
71
+ /** The pending delegated approvals a top-level session should show. */
72
+ export function delegatedApprovalsFor(sessionId) {
73
+ return [...registry().pending.values()].filter((delegated) => delegated.sessionId === sessionId);
74
+ }
@@ -237,7 +237,9 @@ function typedStreamFailureText(error, text) {
237
237
 
238
238
  export function presentErrorText(error, options = {}) {
239
239
  const presentation = describeError(error);
240
- if (['image-size', 'payload-size', 'request'].includes(presentation.kind)) return presentation.summary;
240
+ if (['image-size', 'payload-size', 'request', 'model-unavailable', 'tool-schema'].includes(presentation.kind)) {
241
+ return presentation.summary;
242
+ }
241
243
  const kindLine = hasTypedEvidence(error) ? KIND_LINES[presentation.providerKind] : null;
242
244
  if (kindLine && presentation.kind !== 'unknown') {
243
245
  return typeof kindLine === 'function' ? kindLine(presentation) : kindLine;
@@ -255,6 +257,10 @@ export function presentErrorText(error, options = {}) {
255
257
  if (code === 'TOOL_SURFACE_EXCEEDS_CONTEXT' || /\(TOOL_SURFACE_EXCEEDS_CONTEXT\)/.test(text)) {
256
258
  return stripErrorPrefix(text);
257
259
  }
260
+ // Likewise a latest message that cannot fit names its size and what would
261
+ // help; a wrapper's prefix ("agent compact failed (...): ") is dropped.
262
+ const latestTurn = /The latest message cannot fit this model \(LATEST_TURN_EXCEEDS_CONTEXT\)[\s\S]*$/.exec(text);
263
+ if (latestTurn) return latestTurn[0].replace(' (LATEST_TURN_EXCEEDS_CONTEXT)', '');
258
264
  const jsonError = extractJsonError(text);
259
265
  if (jsonError) text = jsonError;
260
266
  const embeddedError = extractEmbeddedError(text);
@@ -3,10 +3,14 @@ import { transportErrorText } from './transport-error-text.mjs';
3
3
 
4
4
  const DETAILS_LIMIT = 12_000;
5
5
  const SUMMARY_LIMIT = 180;
6
+ // "<wrapper>: The latest message cannot fit this model (CODE): <sizes>. <cause and remedy>"
7
+ const LATEST_TURN_RE =
8
+ /The latest message cannot fit this model \(LATEST_TURN_EXCEEDS_CONTEXT\)(: it needs about [\d,]+ tokens, but the [^.]* window leaves [\d,]+ for it\.)([\s\S]*)$/;
6
9
 
7
10
  /** Diagnostic text must be safe before it reaches a view, copy button or log. */
8
11
  export function safeErrorDetails(value) {
9
12
  let text = String(value ?? '')
13
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: strips ANSI escape sequences (ESC) from diagnostic text
10
14
  .replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, '')
11
15
  .replace(/\r\n?/g, '\n')
12
16
  .replace(/\b((?:https?|ssh):\/\/)[^/\s@]+@/gi, '$1[redacted]@')
@@ -135,7 +139,80 @@ function resetLabel(resetAt) {
135
139
  const reset = new Date(resetAt);
136
140
  const pad = (n) => String(n).padStart(2, '0');
137
141
  const time = `${pad(reset.getHours())}:${pad(reset.getMinutes())}`;
138
- return reset.toDateString() === new Date().toDateString() ? time : `${reset.getMonth() + 1}/${reset.getDate()} ${time}`;
142
+ return reset.toDateString() === new Date().toDateString()
143
+ ? time
144
+ : `${reset.getMonth() + 1}/${reset.getDate()} ${time}`;
145
+ }
146
+
147
+ // A refused request that names its model as unknown, retired or not offered
148
+ // to this account. Read only for request-refusal kinds, so a typed overload,
149
+ // quota or auth failure never reads as a missing model.
150
+ const MODEL_REFUSAL_KINDS = new Set(['invalid_request', 'permission', 'unknown']);
151
+ const MODEL_UNAVAILABLE_CODES = new Set(['model_not_found', 'model_not_supported']);
152
+ // The model must be the subject of the refusal, at the start of a line or
153
+ // sentence ("The model `x` does not exist", "models/x is not found", Ollama's
154
+ // `model "x" not found`), or the refusal names it ("Unknown model", "… is not
155
+ // a valid model ID", Anthropic's bare "model: x"). A model merely mentioned
156
+ // beside some other missing thing ("For model x, tool lookup: route not
157
+ // found") does not count.
158
+ const MODEL_REFUSAL_PREDICATE =
159
+ "(?:(?:is|was|has been)\\s+)?(?:not found|does not exist|doesn'?t exist|not (?:supported|available)|(?:currently\\s+)?unavailable|no longer (?:available|supported)|deprecated|retired|removed)\\b";
160
+ const SENTENCE_START = '(?:^|[.:;!]\\s+)';
161
+ const MODEL_UNAVAILABLE_RE = new RegExp(
162
+ [
163
+ // "The model `x` does not exist", "models/x is not found", Vertex
164
+ // "Publisher Model `…` was not found", Ollama `model "x" not found`.
165
+ `${SENTENCE_START}(?:(?:the|this|that|requested|selected|publisher)\\s+)*models?(?:\\/\\S+|\\s+[\`'"]?[\\w./:@-]+[\`'"]?)?\\s+${MODEL_REFUSAL_PREDICATE}`,
166
+ // OpenAI "The 'gpt-x' model is not supported when using Codex …".
167
+ `${SENTENCE_START}(?:the\\s+)?[\`'"][^\`'"\\n]+[\`'"]\\s+model\\s+${MODEL_REFUSAL_PREDICATE}`,
168
+ `${SENTENCE_START}(?:unknown|invalid|unsupported) model\\b`,
169
+ // OpenRouter: no provider serves this model id.
170
+ `${SENTENCE_START}no endpoints found for [\\w.-]+\\/[\\w.:-]+`,
171
+ '\\bis not a valid model\\b',
172
+ '^model: \\S+$',
173
+ ].join('|'),
174
+ 'im'
175
+ );
176
+
177
+ /**
178
+ * True when `error` (or its text) says the requested model is unknown,
179
+ * retired or not offered to this account: a typed model code, or provider
180
+ * wording with the model as the subject of the refusal.
181
+ */
182
+ export function namesUnavailableModel(error) {
183
+ if (!error) return false;
184
+ if (typeof error === 'string') return MODEL_UNAVAILABLE_RE.test(error);
185
+ const codes = [error.code, error.providerErrorCode, error.error?.code, error.providerError?.code];
186
+ if (codes.some((code) => MODEL_UNAVAILABLE_CODES.has(String(code || '').toLowerCase()))) return true;
187
+ return [error.message, error.error?.message, error.providerError?.message, error.detail]
188
+ .filter((text) => typeof text === 'string' && text)
189
+ .some((text) => MODEL_UNAVAILABLE_RE.test(text));
190
+ }
191
+ const MODEL_UNAVAILABLE_COPY = [
192
+ 'model-unavailable',
193
+ "This model isn't available for this account.",
194
+ 'Choose another model, or check the model and endpoint in Providers.',
195
+ ];
196
+ const NOT_FOUND_SUMMARY = "The provider couldn't find the requested model or endpoint.";
197
+ // A tool definition the provider's schema validator refused (an MCP or custom
198
+ // tool schema), named by the field path or wording each API uses.
199
+ const TOOL_SCHEMA_RE =
200
+ /invalid schema for (?:function|tool)\b|\btools\.\d+\.(?:custom\.)?input_schema\b|\bfunction_declarations\[\d+\]\.parameters\b|\btools\[\d+\]\.function\.parameters\b|\binvalid (?:tool|function) (?:schema|parameters|definition)\b/i;
201
+ const TOOL_SCHEMA_COPY = [
202
+ 'tool-schema',
203
+ 'The provider rejected a tool definition.',
204
+ 'Disable the tool or MCP server named in the details, then try again.',
205
+ ];
206
+
207
+ // The first line of a provider message worth showing as-is; '' for a stack
208
+ // frame, a markup page or a raw JSON body.
209
+ function firstReadableLine(clean) {
210
+ const lines = clean
211
+ .split('\n')
212
+ .map((line) => line.trim())
213
+ .filter(Boolean);
214
+ const first = lines.find((line) => !/^at\s|^\{|^<!doctype|^<html/i.test(line)) || '';
215
+ return first && !/^[{[]|^<|(?:API|HTTP)\s+[45]\d\d.*[<{]/i.test(first) ? first : '';
139
216
  }
140
217
 
141
218
  // Only legacy items (no persisted descriptor) and untyped strings reach this.
@@ -215,7 +292,9 @@ export function describeError(value) {
215
292
  if (
216
293
  value?.reauthRequired === true ||
217
294
  (!typed &&
218
- /invalid_grant|refresh token (?:expired|revoked|not available)|sign in again|sign-in expired|Reauth Required/i.test(raw))
295
+ /invalid_grant|refresh token (?:expired|revoked|not available)|sign in again|sign-in expired|Reauth Required/i.test(
296
+ raw
297
+ ))
219
298
  ) {
220
299
  kind = 'reauth';
221
300
  summary = 'Sign-in expired.';
@@ -223,7 +302,9 @@ export function describeError(value) {
223
302
  } else if (
224
303
  descriptor.kind === 'auth' ||
225
304
  (descriptor.kind === 'unknown' &&
226
- /\b(?:invalid|expired)\s+(?:api[ _-]?key|access token|credentials?)\b|Provider authentication failed/i.test(clean))
305
+ /\b(?:invalid|expired)\s+(?:api[ _-]?key|access token|credentials?)\b|Provider authentication failed/i.test(
306
+ clean
307
+ ))
227
308
  ) {
228
309
  kind = 'authentication';
229
310
  summary = 'Provider authentication failed';
@@ -241,6 +322,32 @@ export function describeError(value) {
241
322
  kind = 'image-size';
242
323
  summary = 'An attached image exceeds the provider limit.';
243
324
  recovery = 'Reduce the image dimensions or number of images, then try again.';
325
+ } else if (
326
+ clean === MODEL_UNAVAILABLE_COPY[1] ||
327
+ (MODEL_REFUSAL_KINDS.has(descriptor.kind) &&
328
+ (MODEL_UNAVAILABLE_CODES.has(code) || MODEL_UNAVAILABLE_RE.test(clean)))
329
+ ) {
330
+ [kind, summary, recovery] = MODEL_UNAVAILABLE_COPY;
331
+ } else if (
332
+ clean === TOOL_SCHEMA_COPY[1] ||
333
+ (['invalid_request', 'unknown'].includes(descriptor.kind) && TOOL_SCHEMA_RE.test(clean))
334
+ ) {
335
+ [kind, summary, recovery] = TOOL_SCHEMA_COPY;
336
+ } else if (
337
+ clean === NOT_FOUND_SUMMARY ||
338
+ (MODEL_REFUSAL_KINDS.has(descriptor.kind) && (status === 404 || status === 410))
339
+ ) {
340
+ // The provider's own words name what is missing; the next step is the same.
341
+ kind = 'not-found';
342
+ summary = firstReadableLine(clean) || NOT_FOUND_SUMMARY;
343
+ recovery = MODEL_UNAVAILABLE_COPY[2];
344
+ } else if (LATEST_TURN_RE.test(clean)) {
345
+ // The latest message cannot fit the window (compact/runner.mjs): the
346
+ // sizes are the headline, the cause and remedy the next step.
347
+ const [, headline, rest] = LATEST_TURN_RE.exec(clean);
348
+ kind = 'latest-turn-size';
349
+ summary = `The latest message cannot fit this model${headline}`;
350
+ recovery = rest.trim();
244
351
  } else if (
245
352
  descriptor.kind === 'unknown' &&
246
353
  /payload too large|request (?:body )?too large|The request exceeds the size limit/i.test(clean)
@@ -272,15 +379,11 @@ export function describeError(value) {
272
379
  summary = 'The provider rejected this request.';
273
380
  recovery = 'Review the request details before trying again.';
274
381
  } else {
275
- const lines = clean
276
- .split('\n')
277
- .map((line) => line.trim())
278
- .filter(Boolean);
279
- const first = lines.find((line) => !/^at\s|^\{|^<!doctype|^<html/i.test(line)) || '';
280
- summary = first && !/^[{[]|^<|(?:API|HTTP)\s+[45]\d\d.*[<{]/i.test(first) ? first : 'Something went wrong.';
382
+ summary = firstReadableLine(clean) || 'Something went wrong.';
281
383
  }
282
384
  summary = summary.replace(/\s+/g, ' ').trim() || 'Something went wrong.';
283
385
  if (summary.length > SUMMARY_LIMIT) summary = `${summary.slice(0, SUMMARY_LIMIT - 1).trimEnd()}…`;
386
+ const fingerprintStatus = kind === 'reauth' ? '' : status || '';
284
387
  return {
285
388
  kind,
286
389
  summary,
@@ -293,6 +396,6 @@ export function describeError(value) {
293
396
  details,
294
397
  // Known structured failures ignore volatile indexes; unknown failures
295
398
  // retain their diagnostic identity rather than merging unrelated work.
296
- fingerprint: kind === 'unknown' ? details : `${kind}:${kind === 'reauth' ? '' : status || ''}`,
399
+ fingerprint: kind === 'unknown' ? details : `${kind}:${fingerprintStatus}`,
297
400
  };
298
401
  }
@@ -0,0 +1,28 @@
1
+ import { closeSync, openSync, readSync } from 'node:fs';
2
+ import { holdsNoData } from './atomic-file.mjs';
3
+
4
+ const PROBE_BYTES = 4096;
5
+
6
+ /**
7
+ * Install check for a downloaded binary or model: false when `path` is absent
8
+ * or its first bytes hold no data (empty, or NUL-filled by a crash that
9
+ * committed the size but not the bytes). Cheaper than hashing a multi-GB file
10
+ * on every use. A file that exists but cannot be opened right now (locked by a
11
+ * scanner or by the process running it) still counts as present.
12
+ */
13
+ export function fileHasData(path) {
14
+ let fd;
15
+ try {
16
+ fd = openSync(path, 'r');
17
+ const head = Buffer.alloc(PROBE_BYTES);
18
+ return !holdsNoData(head.subarray(0, readSync(fd, head, 0, PROBE_BYTES, 0)));
19
+ } catch (err) {
20
+ return err?.code !== 'ENOENT' && err?.code !== 'ENOTDIR';
21
+ } finally {
22
+ if (fd !== undefined) {
23
+ try {
24
+ closeSync(fd);
25
+ } catch {}
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,20 @@
1
+ export interface HandoffFile {
2
+ name: string;
3
+ path: string;
4
+ kind: 'image' | 'file' | 'text';
5
+ mimeType: string;
6
+ size: number;
7
+ /** Attachment-store reference when the file was stored. */
8
+ id?: string;
9
+ }
10
+ export function normalizeHandoffFiles(files: unknown): HandoffFile[];
11
+ export function artifactLines(files: unknown, omitted?: number, options?: { ids?: boolean }): string[];
12
+ export interface HandoffRecord {
13
+ id: string;
14
+ attachmentRef: string;
15
+ kind: 'image' | 'file' | 'text';
16
+ name: string;
17
+ mimeType: string;
18
+ size: number;
19
+ }
20
+ export function artifactRecords(files: unknown): HandoffRecord[];
@@ -0,0 +1,61 @@
1
+ // The files a bot's or agent's turn handed back (session-runtime/
2
+ // handoff-attachments.mjs collectTurnArtifacts) as the rows that carry them
3
+ // show them: one model-facing line per file — the attachment id that forwards
4
+ // it, or its path when it is too large or of a type no prompt carries — and
5
+ // the message attachment records of the stored ones. Only those records
6
+ // authorize forwarding by id; the lines are information for the model.
7
+ import { escapeHarnessTags } from './shell-output-summary.mjs';
8
+ import { normalizeMessageAttachments } from './transcript-metadata.mjs';
9
+
10
+ const sizeLabel = (size) => {
11
+ const bytes = Number(size) || 0;
12
+ if (bytes >= 1_048_576) return `${(bytes / 1_048_576).toFixed(1)} MB`;
13
+ return bytes >= 1024 ? `${Math.round(bytes / 1024)} KB` : `${bytes} B`;
14
+ };
15
+
16
+ const oneLine = (value) => escapeHarnessTags(String(value ?? '').replace(/[\r\n]+/g, ' '));
17
+
18
+ /** `files` ({ name, mimeType, size, path, id? }) normalized: bounded strings,
19
+ * ids only of the attachment-store shape. */
20
+ export function normalizeHandoffFiles(files) {
21
+ return (Array.isArray(files) ? files : [])
22
+ .filter((file) => file && typeof file === 'object' && typeof file.path === 'string' && file.path)
23
+ .slice(0, 8)
24
+ .map((file) => ({
25
+ name: String(file.name || 'file').slice(0, 200),
26
+ path: file.path.slice(0, 1024),
27
+ kind: ['image', 'file', 'text'].includes(file.kind) ? file.kind : 'file',
28
+ mimeType: String(file.mimeType || 'application/octet-stream').slice(0, 100),
29
+ size: Number(file.size) >= 0 ? Number(file.size) : 0,
30
+ ...(/^[a-f0-9]{64}$/.test(String(file.id ?? '')) ? { id: file.id } : {}),
31
+ }));
32
+ }
33
+
34
+ /** The model-facing list of `files`, empty when there are none. `ids`: the
35
+ * row also records the stored files as its attachments (a task report), so
36
+ * their ids can be forwarded; a plain-text handoff (an agent completion)
37
+ * lists paths only — an id named in text is never trusted. */
38
+ export function artifactLines(files, omitted = 0, { ids = true } = {}) {
39
+ const list = normalizeHandoffFiles(files);
40
+ if (!list.length) return [];
41
+ const attachable = (file) => (ids ? ` [attachment:${file.id}]` : ' (attachable: forward it by path)');
42
+ return [
43
+ `Files from this turn (forward one with attachments: [${ids ? 'id or path' : 'path'}]; link a path to show it):`,
44
+ ...list.map(
45
+ (file) =>
46
+ `- ${oneLine(file.name)} (${oneLine(file.mimeType)}, ${sizeLabel(file.size)}) — ${oneLine(file.path)}${
47
+ file.id ? attachable(file) : ' (too large or unsupported to attach; link the path)'
48
+ }`
49
+ ),
50
+ ...(omitted > 0 ? [`- …and ${omitted} more file(s)`] : []),
51
+ ];
52
+ }
53
+
54
+ /** The message attachment records of `files` (the stored ones). */
55
+ export function artifactRecords(files) {
56
+ return normalizeMessageAttachments(
57
+ normalizeHandoffFiles(files)
58
+ .filter((file) => file.id)
59
+ .map((file) => ({ ...file, attachmentRef: file.id }))
60
+ );
61
+ }
@@ -28,7 +28,12 @@ export const ANTIGRAVITY_MODELS = Object.freeze([
28
28
  low: 'gemini-3.1-pro-low',
29
29
  high: 'gemini-3.1-pro-high',
30
30
  }),
31
- fallbackModel('gemini-4-argon', 'Gemini 4 Argon', 'gemini-pro', 1048576, 'gemini-4-argon', ['low', 'medium', 'high']),
31
+ // The gateway serves Argon only through per-tier ids; bare `gemini-4-argon` is a 404.
32
+ fallbackModel('gemini-4-argon', 'Gemini 4 Argon', 'gemini-pro', 1048576, {
33
+ low: 'gemini-4-argon-low',
34
+ medium: 'gemini-4-argon-medium',
35
+ high: 'gemini-4-argon-high',
36
+ }),
32
37
  fallbackModel('claude-opus-4-6-thinking', 'Claude Opus 4.6 (Thinking)', null, 250000, 'claude-opus-4-6-thinking', [
33
38
  'low',
34
39
  'medium',
@@ -63,6 +63,12 @@ export async function accountProviderSend(provider, instance, send, model, opts
63
63
  if (!ledger) return;
64
64
  recorded.add(owner);
65
65
  const usage = result.usage || UNMEASURED_USAGE;
66
+ const durationMs = timed ? Date.now() - startedAt : 0;
67
+ // The one measured request time: the loop's speed figures read it, so
68
+ // the composer and the ledger show the same span.
69
+ if (timed && result && typeof result === 'object') {
70
+ Object.defineProperty(result, 'usageDurationMs', { value: durationMs, configurable: true, writable: true });
71
+ }
66
72
  const row = makeUsageRecord({
67
73
  id: result.responseId ? undefined : id,
68
74
  ts: Date.now(),
@@ -88,7 +94,7 @@ export async function accountProviderSend(provider, instance, send, model, opts
88
94
  // The account this send is bound to, so quota history can tell one
89
95
  // connected subscription's records from another's.
90
96
  account: ACCOUNT_PROVIDERS.includes(provider) ? currentProviderAccountId(provider) : '',
91
- durationMs: timed ? Date.now() - startedAt : 0,
97
+ durationMs,
92
98
  });
93
99
  // Committed by the ledger worker (batched with concurrent sends); the
94
100
  // send still settles only once its row is durable, as before, but the
@@ -114,7 +114,7 @@ export async function importUsageHistory(ledger, dataDir, { now = Date.now(), re
114
114
  // send's diagnostic copy cannot also enter as historical usage.
115
115
  const until = Number(ledger.get('liveSince')) || now;
116
116
  if (Number(ledger.get('importedThrough')) >= until) return { skipped: true };
117
- const result = { inserted: 0, sourceRows: 0, unrecordableRows: 0, files: [], traceRows: 0 };
117
+ const result = { inserted: 0, sourceRows: 0, unrecordableRows: 0, tornTraceLines: 0, files: [], traceRows: 0 };
118
118
  const accept = (rows) => {
119
119
  const normalized = [];
120
120
  for (const raw of rows) {
@@ -140,15 +140,23 @@ export async function importUsageHistory(ledger, dataDir, { now = Date.now(), re
140
140
  const original = await readFile(path, 'utf8');
141
141
  const lines = original.split(/\r?\n/);
142
142
  const rows = [];
143
+ let torn = 0;
143
144
  for (let i = 0; i < lines.length; i++) {
144
145
  if (!lines[i].trim()) continue;
145
146
  let row;
146
147
  try {
147
148
  row = JSON.parse(lines[i]);
148
149
  } catch {
149
- throw new Error(`Incomplete or invalid trace record: ${path}:${i + 1}`);
150
+ // An append a crash tore (cut short, or NUL-filled) is no record: skip
151
+ // it, like the gateway store below, instead of failing every import.
152
+ torn += 1;
153
+ continue;
150
154
  }
151
- if (row.kind === 'usage_raw' || row.kind === 'usage') rows.push(row);
155
+ if (row?.kind === 'usage_raw' || row?.kind === 'usage') rows.push(row);
156
+ }
157
+ if (torn > 0) {
158
+ process.stderr.write(`[usage-ledger] warning: ${path} has ${torn} torn trace line(s); skipped\n`);
159
+ result.tornTraceLines += torn;
152
160
  }
153
161
  accept(rows);
154
162
  result.files.push({ path, sha256: createHash('sha256').update(original).digest('hex'), rows: rows.length });
@@ -160,29 +168,40 @@ export async function importUsageHistory(ledger, dataDir, { now = Date.now(), re
160
168
  // The JSONL store supersedes the legacy JSON file (it is migrated from it).
161
169
  const gatewayJsonl = await optionalFile(join(dataDir, 'gateway-usage.local.jsonl'));
162
170
  const gateway = gatewayJsonl === null ? await optionalFile(join(dataDir, 'gateway-usage.local.json')) : null;
163
- if (gatewayJsonl !== null || gateway)
171
+ // A missing file is absence. Empty, unparsable, or wrong-shaped JSON is not:
172
+ // skip only that legacy import and leave importedThrough unset so a repair
173
+ // is retried instead of short-circuiting forever.
174
+ let legacyReady = true;
175
+ let gatewayEvents = null;
176
+ if (gatewayJsonl !== null) {
177
+ gatewayEvents = gatewayJsonl.split('\n').flatMap((line) => {
178
+ try {
179
+ return line.trim() ? [JSON.parse(line)] : [];
180
+ } catch {
181
+ return []; // torn append
182
+ }
183
+ });
184
+ } else if (gateway !== null) {
185
+ try {
186
+ const events = JSON.parse(gateway)?.events;
187
+ gatewayEvents = Array.isArray(events) ? events : [];
188
+ } catch (error) {
189
+ legacyReady = false;
190
+ process.stderr.write(
191
+ `[usage-ledger] warning: legacy gateway usage file gateway-usage.local.json is empty or corrupt (${error.message}); skipping its import\n`
192
+ );
193
+ }
194
+ }
195
+ if (gatewayEvents)
164
196
  accept(
165
- (gatewayJsonl !== null
166
- ? gatewayJsonl.split('\n').flatMap((line) => {
167
- try {
168
- return line.trim() ? [JSON.parse(line)] : [];
169
- } catch {
170
- return []; // torn append
171
- }
172
- })
173
- : JSON.parse(gateway).events || []
174
- ).map((event) => ({
197
+ gatewayEvents.map((event) => ({
175
198
  ...event,
176
199
  kind: 'usage',
177
200
  session_id: event.sessionId,
178
201
  }))
179
202
  );
180
- // A missing file is absence. Empty, unparsable, or wrong-shaped JSON is not:
181
- // skip only this legacy import and leave importedThrough unset so a repair
182
- // is retried instead of short-circuiting forever.
183
203
  const legacyPath = join(dataDir, 'usage-rollup.local.json');
184
204
  const legacy = await optionalFile(legacyPath);
185
- let legacyReady = true;
186
205
  if (legacy !== null) {
187
206
  let days;
188
207
  try {
@@ -1,10 +1,84 @@
1
- import { providerErrorKind } from '../agent/orchestrator/providers/provider-error-kind.mjs';
1
+ import {
2
+ boundedCauseChain,
3
+ providerErrorKind,
4
+ typedCodeKind,
5
+ } from '../agent/orchestrator/providers/provider-error-kind.mjs';
2
6
  import { readStreamOutcome } from '../agent/orchestrator/providers/lib/stream-outcome.mjs';
7
+ import { namesUnavailableModel } from './error-presentation.mjs';
3
8
 
4
- const ELIGIBLE = new Set(['rate_limited', 'quota_exhausted', 'overloaded', 'transient']);
9
+ // When a route's model fails, does the next model in its fallback chain get
10
+ // the request? Only for a refusal scoped to THIS provider, account or model —
11
+ // another provider may serve the identical request:
12
+ // quota_exhausted / entitlement / billing — the plan or account is spent;
13
+ // rate_limited / overloaded / service_tier_unavailable — this provider is
14
+ // saturated once its own retries are spent;
15
+ // auth / permission — the provider's own sign-in refresh already failed, or
16
+ // the account, region or tier is refused;
17
+ // transient — only when the server answered (a typed status or code): its
18
+ // retry ladder is spent. A bare network failure would fail on every
19
+ // provider, so it stays with the transport recovery;
20
+ // invalid_request on 404/410, a typed model code, or provider wording with
21
+ // the model as the refused subject (namesUnavailableModel) — the model is
22
+ // unknown, retired or not offered to this account.
23
+ // A request that fails the same way on any model never hops: request too
24
+ // large, a refused tool schema or other invalid request, a policy refusal,
25
+ // a cancel, a stall and any untyped failure. A context overflow hops only to
26
+ // a model with a larger window (`contextGrows`); compaction runs first.
27
+ // Whatever the kind, nothing may have been produced: no visible or buffered
28
+ // output, no tool call started or dispatched.
29
+ const PROVIDER_SCOPED_KINDS = new Set([
30
+ 'quota_exhausted',
31
+ 'entitlement',
32
+ 'billing',
33
+ 'rate_limited',
34
+ 'overloaded',
35
+ 'service_tier_unavailable',
36
+ 'auth',
37
+ 'permission',
38
+ ]);
39
+ const MODEL_UNAVAILABLE_CODES = new Set(['model_not_found', 'model_not_supported']);
5
40
 
6
- export function canFallbackRoute(error, { signal, outcome = readStreamOutcome(error), committed = false } = {}) {
7
- if (signal?.aborted || committed || outcome.userAbort || outcome.stallObserved) return false;
41
+ /** 'hop', 'context' (a context overflow: hop only to a larger window) or 'stay'. */
42
+ // A recovery layer may wrap the provider failure (a context overflow it could
43
+ // not compact away): what the original request exposed lives on the cause.
44
+ // Every node of the chain counts, so a wrapper never launders exposure.
45
+ function chainOutcome(error) {
46
+ return readStreamOutcome(...(error && typeof error === 'object' ? boundedCauseChain(error) : [error]));
47
+ }
48
+
49
+ export function routeFallbackVerdict(error, { signal, outcome = chainOutcome(error), committed = false } = {}) {
50
+ if (signal?.aborted || committed || outcome.userAbort || outcome.stallObserved) return 'stay';
51
+ if (!outcome.replaySafe || outcome.observedOutput || outcome.toolCallsStarted || outcome.pendingToolInput) {
52
+ return 'stay';
53
+ }
54
+ const { kind, status, code } = providerErrorKind(error);
55
+ if (PROVIDER_SCOPED_KINDS.has(kind)) return 'hop';
56
+ if (kind === 'transient') return status >= 408 || typedCodeKind(error) === 'transient' ? 'hop' : 'stay';
57
+ if (kind === 'invalid_request') {
58
+ return status === 404 || status === 410 || MODEL_UNAVAILABLE_CODES.has(code) || namesUnavailableModel(error)
59
+ ? 'hop'
60
+ : 'stay';
61
+ }
62
+ return kind === 'context_overflow' ? 'context' : 'stay';
63
+ }
64
+
65
+ export function canFallbackRoute(error, { contextGrows = false, ...options } = {}) {
66
+ const verdict = routeFallbackVerdict(error, options);
67
+ return verdict === 'hop' || (verdict === 'context' && contextGrows === true);
68
+ }
69
+
70
+ const CAPACITY_KINDS = new Set(['rate_limited', 'quota_exhausted', 'overloaded', 'transient']);
71
+
72
+ /**
73
+ * The narrower rule a stateless tool route (native web search) keeps: only a
74
+ * capacity refusal the server stated (429 / 503, or an account-pool quota
75
+ * refusal) moves to the next provider; a denied or failed credential
76
+ * surfaces so the user fixes that route.
77
+ */
78
+ // It also keeps its stricter stream guards: any started or terminal stream
79
+ // stays on its route, since a tool call cannot prove nothing was generated.
80
+ export function canFallbackCapacityRoute(error, { signal, outcome = readStreamOutcome(error) } = {}) {
81
+ if (signal?.aborted || outcome.userAbort || outcome.stallObserved) return false;
8
82
  if (
9
83
  !outcome.replaySafe ||
10
84
  outcome.observedOutput ||
@@ -15,8 +89,6 @@ export function canFallbackRoute(error, { signal, outcome = readStreamOutcome(er
15
89
  )
16
90
  return false;
17
91
  const { kind, status } = providerErrorKind(error);
18
- if (!ELIGIBLE.has(kind)) return false;
19
- // A rejected HTTP request or a provider account-pool refusal proves no
20
- // generation started. A transport disconnect alone does not.
92
+ if (!CAPACITY_KINDS.has(kind)) return false;
21
93
  return status === 429 || status === 503 || (kind === 'quota_exhausted' && error?.quotaExhausted != null);
22
94
  }