mixdog 1.2.3 → 1.2.5

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 (469) hide show
  1. package/package.json +1 -1
  2. package/scripts/gate-local.mjs +6 -4
  3. package/scripts/lib/gate-scheduler.mjs +29 -11
  4. package/scripts/mobile-release.mjs +5 -4
  5. package/scripts/probe-provider-catalog.mjs +31 -0
  6. package/scripts/provider-pingpong-live.mjs +128 -38
  7. package/scripts/session-sweep.mjs +5 -25
  8. package/src/agents/maintainer/AGENT.md +1 -1
  9. package/src/agents/maintainer/agent.json +1 -1
  10. package/src/agents/reviewer/AGENT.md +2 -4
  11. package/src/agents/security/AGENT.md +1 -2
  12. package/src/agents/writer/AGENT.md +2 -3
  13. package/src/defaults/shipped-history.json +7 -1
  14. package/src/defaults/skills/image/SKILL.md +21 -23
  15. package/src/lib/keychain-cjs.cjs +13 -11
  16. package/src/output-styles/common.md +2 -1
  17. package/src/rules/agent/AGENT.md +7 -5
  18. package/src/rules/lead/LEAD.md +13 -13
  19. package/src/rules/shared/45-destructive.md +4 -0
  20. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -0
  21. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +28 -7
  22. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -11
  23. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +101 -23
  24. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +2 -0
  25. package/src/runtime/agent/orchestrator/providers/anthropic-api-key-fetch.mjs +36 -0
  26. package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +1 -1
  27. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -3
  28. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  29. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +26 -44
  30. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +2 -5
  31. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +4 -0
  32. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +41 -12
  33. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +13 -1
  34. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +81 -10
  35. package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +109 -5
  36. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +2 -1
  37. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +107 -37
  38. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +4 -1
  39. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +3 -1
  40. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  41. package/src/runtime/agent/orchestrator/providers/cursor-native-context.mjs +48 -0
  42. package/src/runtime/agent/orchestrator/providers/cursor-wire-exec.mjs +144 -34
  43. package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +28 -4
  44. package/src/runtime/agent/orchestrator/providers/cursor-wire-request.mjs +8 -4
  45. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +41 -11
  46. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-messages.mjs +67 -5
  47. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +14 -4
  48. package/src/runtime/agent/orchestrator/providers/cursor.mjs +14 -1
  49. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +9 -6
  50. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +7 -2
  51. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +26 -4
  52. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +12 -6
  53. package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +14 -6
  54. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +4 -4
  55. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +1 -0
  56. package/src/runtime/agent/orchestrator/providers/gemini-rest-stream/watchdogs.mjs +16 -5
  57. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +6 -1
  58. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +22 -10
  59. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +4 -2
  60. package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +33 -7
  61. package/src/runtime/agent/orchestrator/providers/gemini.mjs +11 -2
  62. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  63. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +9 -2
  64. package/src/runtime/agent/orchestrator/providers/lib/claude-bridge-schema.mjs +9 -5
  65. package/src/runtime/agent/orchestrator/providers/lib/request-compression.mjs +86 -0
  66. package/src/runtime/agent/orchestrator/providers/lib/wire-pairing.mjs +4 -2
  67. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -5
  68. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +7 -1
  69. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +60 -16
  70. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +11 -13
  71. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +30 -19
  72. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
  73. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +5 -0
  74. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +42 -5
  75. package/src/runtime/agent/orchestrator/providers/openai-compat-reasoning-field.mjs +13 -9
  76. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +1 -3
  77. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-parity.fixtures.mjs +18 -7
  78. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +36 -3
  79. package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +51 -3
  80. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +11 -1
  81. package/src/runtime/agent/orchestrator/providers/openai-http-sse-request.mjs +64 -52
  82. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +3 -1
  83. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +2 -1
  84. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +7 -1
  85. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +1 -4
  86. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/tool-calls.mjs +0 -1
  87. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +7 -6
  88. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +13 -2
  89. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +4 -1
  90. package/src/runtime/agent/orchestrator/providers/openrouter.mjs +1 -2
  91. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +5 -4
  92. package/src/runtime/agent/orchestrator/providers/registry.mjs +9 -1
  93. package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +2 -1
  94. package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +11 -4
  95. package/src/runtime/agent/orchestrator/providers/wire-route.mjs +14 -4
  96. package/src/runtime/agent/orchestrator/providers/xai-tool-search.fixtures.mjs +15 -3
  97. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +6 -2
  98. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +8 -6
  99. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +4 -1
  100. package/src/runtime/agent/orchestrator/runtime-core/goal-store.mjs +30 -2
  101. package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +3 -2
  102. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +8 -3
  103. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +3 -1
  104. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge-contract.mjs +6 -0
  105. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +10 -10
  106. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +3 -1
  107. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +46 -8
  108. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +10 -8
  109. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -2
  110. package/src/runtime/agent/orchestrator/session/agent-pool-progress.mjs +24 -0
  111. package/src/runtime/agent/orchestrator/session/cache/tool-result-reuse.mjs +10 -2
  112. package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +2 -3
  113. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +3 -11
  114. package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +6 -22
  115. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +2 -0
  116. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +16 -2
  117. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +28 -0
  118. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
  119. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +0 -6
  120. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +0 -6
  121. package/src/runtime/agent/orchestrator/session/eager-dispatch/admission.mjs +1 -1
  122. package/src/runtime/agent/orchestrator/session/historical-session-closure.mjs +10 -0
  123. package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +2 -5
  124. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +2 -4
  125. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +3 -3
  126. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +0 -27
  127. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +1 -1
  128. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +4 -10
  129. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +2 -9
  130. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +4 -1
  131. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +4 -1
  132. package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +1 -4
  133. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +76 -0
  134. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +5 -1
  135. package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +8 -9
  136. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +6 -0
  137. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +15 -2
  138. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +1 -4
  139. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +3 -4
  140. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +33 -3
  141. package/src/runtime/agent/orchestrator/session/manager/ask-support.mjs +0 -4
  142. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +21 -1
  143. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +38 -13
  144. package/src/runtime/agent/orchestrator/session/manager/ask-turn-failure.mjs +33 -0
  145. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +5 -0
  146. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +19 -1
  147. package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +57 -13
  148. package/src/runtime/agent/orchestrator/session/manager/failed-turn-rewind.mjs +16 -0
  149. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +6 -120
  150. package/src/runtime/agent/orchestrator/session/manager/pending-persist-queue.mjs +1 -1
  151. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +29 -8
  152. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +42 -49
  153. package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +50 -0
  154. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +32 -14
  155. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +1 -1
  156. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +17 -33
  157. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +29 -13
  158. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +3 -3
  159. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-recovery.mjs +45 -0
  160. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -9
  161. package/src/runtime/agent/orchestrator/session/send-instrumentation.mjs +2 -1
  162. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +5 -2
  163. package/src/runtime/agent/orchestrator/session/side-question.mjs +12 -7
  164. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +5 -12
  165. package/src/runtime/agent/orchestrator/session/store/lifecycle-read.mjs +1 -1
  166. package/src/runtime/agent/orchestrator/session/store/listing.mjs +2 -6
  167. package/src/runtime/agent/orchestrator/session/store/live-cache-eviction.mjs +1 -1
  168. package/src/runtime/agent/orchestrator/session/store/liveness-veto.mjs +46 -37
  169. package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +1 -1
  170. package/src/runtime/agent/orchestrator/session/store/load-session.mjs +1 -1
  171. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +5 -11
  172. package/src/runtime/agent/orchestrator/session/store/session-paths.mjs +59 -0
  173. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +1 -1
  174. package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +1 -1
  175. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +27 -32
  176. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +0 -8
  177. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +6 -18
  178. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +33 -106
  179. package/src/runtime/agent/orchestrator/session/store/write-admission.mjs +1 -1
  180. package/src/runtime/agent/orchestrator/session/store-agent-worker-pool.mjs +5 -8
  181. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +160 -212
  182. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +50 -116
  183. package/src/runtime/agent/orchestrator/session/store-summary-locations.mjs +3 -29
  184. package/src/runtime/agent/orchestrator/session/store-summary-reader-catalog-fixture.mjs +1 -1
  185. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +7 -8
  186. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +9 -20
  187. package/src/runtime/agent/orchestrator/session/store.mjs +3 -16
  188. package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +11 -12
  189. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +37 -13
  190. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +15 -4
  191. package/src/runtime/agent/orchestrator/session/tool-schema-rejection.mjs +4 -2
  192. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +72 -15
  193. package/src/runtime/agent/orchestrator/session/turn-failure.mjs +45 -0
  194. package/src/runtime/agent/orchestrator/stall-policy.mjs +50 -0
  195. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +5 -1
  196. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/inline-hoist.mjs +6 -2
  197. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +204 -10
  198. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +160 -39
  199. package/src/runtime/agent/orchestrator/tools/builtin/git-partial-stage.mjs +7 -5
  200. package/src/runtime/agent/orchestrator/tools/builtin/grep-formatting.mjs +14 -5
  201. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-context-expander.mjs +4 -3
  202. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-output.mjs +2 -1
  203. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-path-fanout.mjs +48 -44
  204. package/src/runtime/agent/orchestrator/tools/builtin/lib/search-input-helpers.mjs +10 -3
  205. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +200 -17
  206. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +5 -4
  207. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +6 -1
  208. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +20 -0
  209. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +101 -2
  210. package/src/runtime/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +1 -1
  211. package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +67 -0
  212. package/src/runtime/agent/orchestrator/tools/lib/process-table-fixture.mjs +50 -0
  213. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +303 -63
  214. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +2 -0
  215. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +108 -18
  216. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +77 -11
  217. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +81 -5
  218. package/src/runtime/attachments/store.d.mts +9 -0
  219. package/src/runtime/attachments/store.mjs +80 -9
  220. package/src/runtime/browser-bridge/browser-action-contract.d.mts +0 -1
  221. package/src/runtime/browser-bridge/browser-action-contract.mjs +0 -2
  222. package/src/runtime/computer-bridge/action-schema.mjs +178 -178
  223. package/src/runtime/computer-bridge/linux-readiness.d.mts +0 -2
  224. package/src/runtime/computer-bridge/linux-readiness.mjs +0 -16
  225. package/src/runtime/effort-judge/judge-client.mjs +21 -4
  226. package/src/runtime/effort-judge/judge-worker.mjs +4 -1
  227. package/src/runtime/github/tool.mjs +118 -17
  228. package/src/runtime/media/adapters/codex-image.mjs +140 -21
  229. package/src/runtime/media/catalog.mjs +4 -4
  230. package/src/runtime/media/chatgpt-usage.mjs +55 -0
  231. package/src/runtime/media/defaults.mjs +37 -0
  232. package/src/runtime/media/jobs.mjs +12 -2
  233. package/src/runtime/media/lanes.mjs +12 -0
  234. package/src/runtime/media/tool-defs.mjs +15 -4
  235. package/src/runtime/media/tool.mjs +41 -12
  236. package/src/runtime/media/upstream-error.mjs +72 -6
  237. package/src/runtime/memory/lib/ingest/transcript-rows.mjs +1 -0
  238. package/src/runtime/memory/lib/pg/process.mjs +5 -7
  239. package/src/runtime/memory/lib/pg/supervisor.mjs +4 -1
  240. package/src/runtime/office/authoring/docx-html-runner.mjs +1 -1
  241. package/src/runtime/office/authoring/html-browser.mjs +18 -8
  242. package/src/runtime/office/authoring/pdf-html-runner.mjs +1 -1
  243. package/src/runtime/office/authoring/pptx-html-measure.mjs +1 -1
  244. package/src/runtime/office/authoring/xlsx-html-runner.mjs +1 -1
  245. package/src/runtime/office/core/office-write-ledger.mjs +1 -5
  246. package/src/runtime/office/index.mjs +2 -3
  247. package/src/runtime/office/pdf/pdf-analysis.mjs +0 -68
  248. package/src/runtime/session-search/session-search-ingest.mjs +1 -1
  249. package/src/runtime/shared/agent-reap-state.mjs +13 -0
  250. package/src/runtime/shared/atomic-file.d.mts +1 -1
  251. package/src/runtime/shared/background-tasks.mjs +6 -1
  252. package/src/runtime/shared/bounded-line-diff.mjs +11 -2
  253. package/src/runtime/shared/browser-launch.mjs +15 -0
  254. package/src/runtime/shared/err-text.mjs +42 -4
  255. package/src/runtime/shared/file-lock.mjs +6 -5
  256. package/src/runtime/shared/llm/anthropic-betas.mjs +1 -2
  257. package/src/runtime/shared/llm/provider-model-identities.mjs +1 -0
  258. package/src/runtime/shared/model-route-chain.d.mts +8 -0
  259. package/src/runtime/shared/model-route-chain.mjs +48 -0
  260. package/src/runtime/shared/model-route-fallback.mjs +22 -0
  261. package/src/runtime/shared/pid-liveness.mjs +12 -0
  262. package/src/runtime/shared/provider-api-key.mjs +6 -0
  263. package/src/runtime/shared/provider-timeout-phase.mjs +17 -0
  264. package/src/runtime/shared/quiet-progress.d.mts +35 -0
  265. package/src/runtime/shared/quiet-progress.mjs +101 -0
  266. package/src/runtime/shared/quota-resume-store.mjs +32 -1
  267. package/src/runtime/shared/read-row-numbers.mjs +44 -0
  268. package/src/runtime/shared/remote-intent.mjs +2 -42
  269. package/src/runtime/shared/slow-span.mjs +8 -1
  270. package/src/runtime/shared/statusline/statusline-segments.mjs +24 -2
  271. package/src/runtime/shared/tool-card-model/card-header.mjs +2 -1
  272. package/src/runtime/shared/tool-output-budget.mjs +2 -0
  273. package/src/runtime/shared/tool-result-summary.mjs +16 -2
  274. package/src/runtime/shared/tool-status.mjs +3 -2
  275. package/src/runtime/shared/tool-surface.mjs +2 -3
  276. package/src/runtime/shared/tool-work-units.mjs +22 -15
  277. package/src/runtime/shared/transcript-metadata.mjs +49 -1
  278. package/src/runtime/shared/transcript-turn-keys.mjs +33 -0
  279. package/src/runtime/shared/transcript-writer/transcript-file.mjs +10 -1
  280. package/src/runtime/shared/transcript-writer.mjs +14 -4
  281. package/src/runtime/shared/turn-changes.mjs +2 -1
  282. package/src/runtime/shared/turn-snapshot.mjs +11 -3
  283. package/src/runtime/shared/worktrees/activity-gate.mjs +175 -0
  284. package/src/runtime/shared/worktrees/branch-management.mjs +179 -0
  285. package/src/runtime/shared/worktrees/carry.mjs +6 -4
  286. package/src/runtime/shared/worktrees/contract.mjs +27 -1
  287. package/src/runtime/shared/worktrees/dependencies.mjs +456 -0
  288. package/src/runtime/shared/worktrees/file-recovery.mjs +29 -13
  289. package/src/runtime/shared/worktrees/git-head.mjs +22 -13
  290. package/src/runtime/shared/worktrees/include.mjs +125 -7
  291. package/src/runtime/shared/worktrees/lock.mjs +1 -1
  292. package/src/runtime/shared/worktrees/maintenance.mjs +20 -2
  293. package/src/runtime/shared/worktrees/manager.mjs +378 -32
  294. package/src/runtime/shared/worktrees/owner.mjs +2 -1
  295. package/src/runtime/shared/worktrees/preparation-report.mjs +79 -0
  296. package/src/runtime/shared/worktrees/setup-commands.mjs +232 -0
  297. package/src/runtime/shared/worktrees/workspace-visibility.mjs +17 -0
  298. package/src/runtime/terminal-bridge/client.mjs +14 -10
  299. package/src/session-runtime/boot/apis.mjs +7 -1
  300. package/src/session-runtime/boot/config.mjs +2 -0
  301. package/src/session-runtime/boot/facade.mjs +3 -0
  302. package/src/session-runtime/boot/session.mjs +3 -2
  303. package/src/session-runtime/boot/tools.mjs +3 -0
  304. package/src/session-runtime/conversation-import/service.mjs +1 -1
  305. package/src/session-runtime/goal-facade-api.mjs +2 -2
  306. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +44 -18
  307. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +4 -1
  308. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +22 -8
  309. package/src/session-runtime/internal-tool-executor.mjs +2 -0
  310. package/src/session-runtime/lifecycle/cold-heir.mjs +0 -1
  311. package/src/session-runtime/lifecycle/session-catalog.mjs +14 -16
  312. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +18 -53
  313. package/src/session-runtime/lifecycle/session-switch.mjs +1 -1
  314. package/src/session-runtime/lifecycle/shared.mjs +5 -6
  315. package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -5
  316. package/src/session-runtime/lifecycle/teardown.mjs +7 -16
  317. package/src/session-runtime/media-api.mjs +8 -0
  318. package/src/session-runtime/model-route/route-ownership.mjs +60 -0
  319. package/src/session-runtime/model-route/route-persist.mjs +3 -2
  320. package/src/session-runtime/model-route/set-route.mjs +114 -34
  321. package/src/session-runtime/model-route/web-search-route.mjs +7 -2
  322. package/src/session-runtime/model-route-api.mjs +4 -2
  323. package/src/session-runtime/native-web-search/routes.mjs +37 -24
  324. package/src/session-runtime/native-web-search.mjs +9 -8
  325. package/src/session-runtime/provider-auth/setup.mjs +4 -0
  326. package/src/session-runtime/provider-models/catalog-load.mjs +9 -0
  327. package/src/session-runtime/provider-models/web-search-catalog.mjs +23 -3
  328. package/src/session-runtime/provider-models.mjs +1 -0
  329. package/src/session-runtime/provider-usage.mjs +4 -0
  330. package/src/session-runtime/route-resolution.mjs +3 -1
  331. package/src/session-runtime/runtime-core.mjs +4 -0
  332. package/src/session-runtime/runtime-facade.mjs +6 -0
  333. package/src/session-runtime/services/agent-task-status.mjs +2 -0
  334. package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +0 -3
  335. package/src/session-runtime/services/agent-tool/close-flow.mjs +6 -7
  336. package/src/session-runtime/services/agent-tool/execute-send.mjs +2 -2
  337. package/src/session-runtime/services/agent-tool/execute-spawn.mjs +36 -3
  338. package/src/session-runtime/services/agent-tool/execute.mjs +9 -1
  339. package/src/session-runtime/services/agent-tool/helpers.mjs +30 -7
  340. package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +7 -2
  341. package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +6 -3
  342. package/src/session-runtime/services/agent-tool/lead-worker-index/lead-rows.mjs +3 -4
  343. package/src/session-runtime/services/agent-tool/lead-worker-index/reap-timers.mjs +9 -3
  344. package/src/session-runtime/services/agent-tool/lead-worker-index/row-settlement.mjs +7 -18
  345. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +9 -6
  346. package/src/session-runtime/services/agent-tool/model-select.mjs +82 -0
  347. package/src/session-runtime/services/agent-tool/send-flow.mjs +24 -6
  348. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +3 -1
  349. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +19 -5
  350. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +5 -4
  351. package/src/session-runtime/services/agent-tool/spawn-flow.mjs +2 -1
  352. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +3 -0
  353. package/src/session-runtime/services/agent-tool/tag-registry/tag-lookup.mjs +1 -3
  354. package/src/session-runtime/services/agent-tool/tag-registry/tag-refresh.mjs +1 -3
  355. package/src/session-runtime/services/agent-tool/tag-registry.mjs +3 -9
  356. package/src/session-runtime/services/agent-tool/terminal-reaper/scan-admission.mjs +8 -41
  357. package/src/session-runtime/services/agent-tool/terminal-reaper/stale-rows.mjs +3 -3
  358. package/src/session-runtime/services/agent-tool/terminal-reaper.mjs +6 -23
  359. package/src/session-runtime/services/agent-tool/terminal-trace.mjs +0 -1
  360. package/src/session-runtime/services/agent-tool/tool-def.mjs +7 -0
  361. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +32 -20
  362. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +5 -2
  363. package/src/session-runtime/services/agent-tool/worker-index.mjs +28 -8
  364. package/src/session-runtime/services/agent-tool.mjs +4 -0
  365. package/src/session-runtime/services/agent-watchdog-registry.mjs +2 -0
  366. package/src/session-runtime/services/provider-admin.mjs +77 -0
  367. package/src/session-runtime/session-create/session-options.mjs +7 -5
  368. package/src/session-runtime/session-create/session-start-hook.mjs +2 -1
  369. package/src/session-runtime/session-transcript.mjs +4 -5
  370. package/src/session-runtime/session-turn-api.mjs +3 -9
  371. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -6
  372. package/src/session-runtime/setup-tool/executor.mjs +23 -5
  373. package/src/session-runtime/setup-tool/extended-actions.mjs +6 -6
  374. package/src/session-runtime/setup-tool/settings-contract.mjs +7 -2
  375. package/src/session-runtime/setup-tool/tool-defs.mjs +13 -1
  376. package/src/session-runtime/tool-policy-refresh.mjs +83 -32
  377. package/src/session-runtime/turn/agent-ops.mjs +49 -2
  378. package/src/session-runtime/turn/side-question-ops.mjs +1 -4
  379. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +2 -0
  380. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +4 -0
  381. package/src/session-runtime/turn/turn-run/turn-settle.mjs +8 -2
  382. package/src/session-runtime/turn/turn-run.mjs +1 -36
  383. package/src/session-runtime/usage-stats-api.mjs +4 -1
  384. package/src/session-runtime/workflow-agents-api/agent-route.mjs +16 -1
  385. package/src/session-runtime/workspace/admission-gate.mjs +44 -15
  386. package/src/session-runtime/workspace/project-branches.mjs +28 -36
  387. package/src/session-runtime/workspace/transition-steps.mjs +96 -95
  388. package/src/session-runtime/workspace/transitions.mjs +248 -68
  389. package/src/standalone/channel-transport.mjs +3 -4
  390. package/src/standalone/daemon-desktop-runtime.mjs +32 -1
  391. package/src/standalone/daemon-pg-stop.mjs +1 -3
  392. package/src/standalone/daemon-session-control.mjs +88 -0
  393. package/src/standalone/daemon-stored-session-views.mjs +23 -3
  394. package/src/standalone/daemon.mjs +2 -0
  395. package/src/standalone/desktop-service-registry.mjs +32 -5
  396. package/src/standalone/local-session-runtime.mjs +11 -12
  397. package/src/standalone/session-protocol.mjs +7 -0
  398. package/src/standalone/session-runtime-inline-host.mjs +2 -0
  399. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +91 -49
  400. package/src/standalone/session-service/agent-tree/agent-child.mjs +19 -1
  401. package/src/standalone/session-service/agent-tree/agent-registry.mjs +25 -0
  402. package/src/standalone/session-service/agent-tree/agent-turns.mjs +9 -1
  403. package/src/standalone/session-service/agent-tree.mjs +28 -2
  404. package/src/standalone/session-service/entries/session-loads.mjs +16 -5
  405. package/src/standalone/session-service/entries.mjs +1 -0
  406. package/src/standalone/session-service/projection/transcript-window.mjs +39 -0
  407. package/src/standalone/session-service/quota-resume.mjs +28 -13
  408. package/src/standalone/session-service/session-calls/session-turns.mjs +17 -6
  409. package/src/standalone/session-service/session-calls/session-views.mjs +6 -0
  410. package/src/standalone/session-tool-output.mjs +82 -0
  411. package/src/tui/app/app-view.jsx +2 -1
  412. package/src/tui/app/provider-setup/provider-items.mjs +8 -0
  413. package/src/tui/app/provider-setup-picker.mjs +15 -3
  414. package/src/tui/app/slash-commands.mjs +6 -0
  415. package/src/tui/app/slash-dispatch/session-commands.mjs +49 -0
  416. package/src/tui/components/Spinner.jsx +4 -0
  417. package/src/tui/components/SpinnerByline.jsx +21 -2
  418. package/src/tui/components/status-line/local-line.mjs +10 -2
  419. package/src/tui/dist/index.mjs +529 -270
  420. package/src/tui/session/active-tool-summary.mjs +1 -1
  421. package/src/tui/session/context-state.mjs +0 -3
  422. package/src/tui/session/goal-continuation/continuation-queue.mjs +2 -3
  423. package/src/tui/session/goal-continuation/deadline-delivery.mjs +4 -2
  424. package/src/tui/session/goal-continuation/session-boundary.mjs +21 -0
  425. package/src/tui/session/quota-resume.mjs +140 -61
  426. package/src/tui/session/session-api/commands.mjs +4 -10
  427. package/src/tui/session/session-api/controls.mjs +12 -0
  428. package/src/tui/session/session-api/lifecycle/dispose.mjs +2 -9
  429. package/src/tui/session/session-api/lifecycle/resume.mjs +2 -7
  430. package/src/tui/session/session-api/lifecycle.mjs +13 -11
  431. package/src/tui/session/session-api/media.mjs +2 -0
  432. package/src/tui/session/session-api/routes.mjs +81 -57
  433. package/src/tui/session/session-api/settings.mjs +3 -3
  434. package/src/tui/session/session-api/worktree.mjs +3 -2
  435. package/src/tui/session/session-flow/auto-clear.mjs +1 -3
  436. package/src/tui/session/session-flow/drain.mjs +6 -4
  437. package/src/tui/session/session-flow/queue/entry-shape.mjs +2 -0
  438. package/src/tui/session/session-flow/queue/take-entries.mjs +2 -1
  439. package/src/tui/session/session-flow/queue.mjs +1 -1
  440. package/src/tui/session/tool-approval.mjs +17 -8
  441. package/src/tui/session/transcript-store/streaming-tail.mjs +1 -1
  442. package/src/tui/session/tui-steering-persist.mjs +3 -5
  443. package/src/tui/session/turn-quiet.mjs +53 -0
  444. package/src/tui/session/turn-stream.mjs +5 -1
  445. package/src/tui/session/turn.mjs +38 -31
  446. package/src/tui/session/worktree-setup-notice.mjs +38 -0
  447. package/src/tui/spinner-meta.mjs +10 -0
  448. package/src/tui/spinner-verbs.mjs +3 -0
  449. package/src/ui/statusline.mjs +13 -3
  450. package/src/ui/streaming-markdown-heal.mjs +70 -18
  451. package/src/runtime/agent/orchestrator/providers/model-pricing-rates.mjs +0 -1
  452. package/src/runtime/agent/orchestrator/session/manager/foreign-pending-messages.mjs +0 -346
  453. package/src/runtime/agent/orchestrator/session/manager/session-owner-liveness.mjs +0 -117
  454. package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +0 -302
  455. package/src/session-runtime/services/agent-tool/terminal-reaper/reap-timers.mjs +0 -142
  456. package/src/session-runtime/turn/share-ops.mjs +0 -77
  457. package/src/tui/session/cross-surface-share.mjs +0 -406
  458. package/src/tui/session/live-share/delta.mjs +0 -153
  459. package/src/tui/session/live-share/mirror.mjs +0 -106
  460. package/src/tui/session/live-share/owner.mjs +0 -254
  461. package/src/tui/session/live-share/retry.mjs +0 -54
  462. package/src/tui/session/live-share/viewer/frame-router.mjs +0 -32
  463. package/src/tui/session/live-share/viewer/pipe-client.mjs +0 -108
  464. package/src/tui/session/live-share/viewer/submit-acks.mjs +0 -65
  465. package/src/tui/session/live-share/viewer/sync-waiters.mjs +0 -53
  466. package/src/tui/session/live-share/viewer.mjs +0 -99
  467. package/src/tui/session/live-share/wire.mjs +0 -44
  468. package/src/tui/session/live-share.mjs +0 -154
  469. package/src/tui/session/shared-dir-watch.mjs +0 -69
@@ -9,20 +9,34 @@
9
9
  import { mediaError } from './lanes.mjs';
10
10
 
11
11
  const CREDIT_HINTS = /credit|spending limit|billing|quota|insufficient|payment/i;
12
+ // Subscription usage caps (ChatGPT plan windows), as opposed to short-lived
13
+ // rate limiting: retrying in a moment does not help until the window resets.
14
+ const USAGE_LIMIT = /usage_?limit/i;
12
15
 
13
- function reason(text) {
16
+ function parseJson(text) {
14
17
  try {
15
- const parsed = JSON.parse(text);
16
- const message = parsed?.error?.message || parsed?.error || parsed?.message;
17
- return typeof message === 'string' ? message : '';
18
+ return JSON.parse(text);
18
19
  } catch {
19
- return '';
20
+ return null;
20
21
  }
21
22
  }
22
23
 
24
+ function reason(text) {
25
+ const parsed = parseJson(text);
26
+ const message = parsed?.error?.message || parsed?.error || parsed?.message;
27
+ return typeof message === 'string' ? message : '';
28
+ }
29
+
30
+ // Raw bodies are only echoed when they are short plain text; HTML error pages
31
+ // and other markup never reach the Studio.
32
+ function rawDetail(text) {
33
+ const raw = String(text || '').trim();
34
+ return raw && !raw.startsWith('<') ? raw.slice(0, 300) : '';
35
+ }
36
+
23
37
  /** Build the error a lane throws for a non-OK upstream response. */
24
38
  export function upstreamError(label, status, text) {
25
- const detail = reason(text) || String(text || '').slice(0, 300);
39
+ const detail = reason(text) || rawDetail(text);
26
40
  if (status === 401 || status === 403) {
27
41
  if (CREDIT_HINTS.test(detail)) {
28
42
  return mediaError(`${label}: account has no usable balance — ${detail}`, 'MEDIA_BILLING_BLOCKED', status);
@@ -41,3 +55,55 @@ export function upstreamError(label, status, text) {
41
55
  }
42
56
  return mediaError(`${label} failed (${status})${detail ? `: ${detail}` : ''}`, 'MEDIA_UPSTREAM_FAILED', status);
43
57
  }
58
+
59
+ const header = (headers, name) => String(headers?.get?.(name) || '').trim();
60
+ const epochSeconds = (value) => (Number.isFinite(Number(value)) && Number(value) > 0 ? Math.trunc(Number(value)) : 0);
61
+
62
+ // Latest reset among the exhausted windows advertised in the rate-limit
63
+ // headers. The family is named after the active limit, with the plain codex
64
+ // family as the fallback the backend also sends.
65
+ function headerResetAt(headers, limitId) {
66
+ const families = new Set([limitId && `x-${limitId.toLowerCase().replace(/_/g, '-')}`, 'x-codex'].filter(Boolean));
67
+ let latest = 0;
68
+ for (const family of families) {
69
+ for (const window of ['primary', 'secondary']) {
70
+ const used = Number(header(headers, `${family}-${window}-used-percent`));
71
+ const resetAt = epochSeconds(header(headers, `${family}-${window}-reset-at`));
72
+ if (used >= 100 && resetAt > latest) latest = resetAt;
73
+ }
74
+ }
75
+ return latest || null;
76
+ }
77
+
78
+ /**
79
+ * Recognize a subscription usage-limit failure. `error` is the upstream error
80
+ * object (`{ type, code, resets_at }`), from an HTTP body or a stream event.
81
+ * Returns `{ limitId, resetsAt }` (epoch seconds or null), or null when the
82
+ * failure is something else.
83
+ */
84
+ export function usageLimitInfo(error, headers) {
85
+ const kinds = [error?.type, error?.code, header(headers, 'x-codex-rate-limit-reached-type')];
86
+ if (!kinds.some((kind) => typeof kind === 'string' && USAGE_LIMIT.test(kind))) return null;
87
+ const limitId = header(headers, 'x-codex-active-limit') || String(error?.limit_id || error?.metered_feature || '');
88
+ return { limitId, resetsAt: epochSeconds(error?.resets_at) || headerResetAt(headers, limitId) };
89
+ }
90
+
91
+ /** usageLimitInfo for a non-OK HTTP response; only 429s are usage limits. */
92
+ export function usageLimitFromResponse(status, text, headers) {
93
+ if (status !== 429) return null;
94
+ const parsed = parseJson(text);
95
+ return usageLimitInfo(parsed?.error && typeof parsed.error === 'object' ? parsed.error : parsed, headers);
96
+ }
97
+
98
+ function formatResetAt(seconds) {
99
+ return `${new Date(seconds * 1000).toISOString().slice(0, 16).replace('T', ' ')} UTC`;
100
+ }
101
+
102
+ /** The error a lane throws when the subscription's usage window is exhausted. */
103
+ export function usageLimitError(label, { limitId = '', resetsAt = null } = {}) {
104
+ const what = /image/i.test(limitId) ? 'image generation usage limit reached' : 'usage limit reached';
105
+ const when = resetsAt ? `resets ${formatResetAt(resetsAt)}` : 'try again after the usage window resets';
106
+ const err = mediaError(`${label}: ${what} — ${when}`, 'MEDIA_USAGE_LIMIT', 429);
107
+ if (resetsAt) err.resetsAt = new Date(resetsAt * 1000).toISOString();
108
+ return err;
109
+ }
@@ -69,6 +69,7 @@ export function cwdFromTranscriptPath(fp) {
69
69
  * prefix envelopes, drops synthetic reference-files/compaction/ack/
70
70
  * internal-notification rows). */
71
71
  export function transcriptRowFromLine(parsed) {
72
+ if (parsed.memoryEligible === false) return null;
72
73
  const role =
73
74
  parsed.message?.role ?? (parsed.type === 'user' || parsed.type === 'assistant' ? parsed.type : undefined);
74
75
  if (role !== 'user' && role !== 'assistant') return null;
@@ -667,13 +667,11 @@ async function listPostmasterRows() {
667
667
  }
668
668
  const stdout = await execFileText('ps', ['-eo', 'pid=,etimes=,args='], { timeout: 15_000 });
669
669
  if (stdout === null) return [];
670
- return stdout
671
- .split(/\r?\n/)
672
- .flatMap((line) => {
673
- const match = line.match(/^\s*(\d+)\s+(\d+)\s+(.*postgres.*)$/);
674
- if (!match) return [];
675
- return [{ pid: Number(match[1]), uptimeSec: Number(match[2]), args: match[3] }];
676
- });
670
+ return stdout.split(/\r?\n/).flatMap((line) => {
671
+ const match = line.match(/^\s*(\d+)\s+(\d+)\s+(.*postgres.*)$/);
672
+ if (!match) return [];
673
+ return [{ pid: Number(match[1]), uptimeSec: Number(match[2]), args: match[3] }];
674
+ });
677
675
  }
678
676
 
679
677
  const PRISTINE_ROOT_PREFIX = 'mixdog-headless-pristine-';
@@ -828,7 +828,10 @@ export function stopPgForShutdownSync() {
828
828
  } catch {}
829
829
  // The postmaster is gone, so drop its advert too (same guarded clear as the
830
830
  // async path: never another live owner's); otherwise pg.json outlives the run.
831
- patchActiveInstance({ pg_port: null, pg_started_at: null, pg_pgdata: null, pg_runtime_dir: null }, { timeoutMs: 1000 });
831
+ patchActiveInstance(
832
+ { pg_port: null, pg_started_at: null, pg_pgdata: null, pg_runtime_dir: null },
833
+ { timeoutMs: 1000 }
834
+ );
832
835
  }
833
836
 
834
837
  // ── Owned-cluster shutdown (a host that starts PG through its stores) ────────
@@ -39,7 +39,7 @@ export async function runDocxHtmlAuthoring(html, output, { target, signal = null
39
39
  const scratch = await mkdtemp(join(tmpdir(), 'mixdog-docx-captures-'));
40
40
  let browser;
41
41
  try {
42
- browser = await launchHtmlBrowser('docx-html-browser');
42
+ browser = await launchHtmlBrowser('docx-html-browser', { signal });
43
43
  } catch (error) {
44
44
  await rm(scratch, { recursive: true, force: true }).catch(() => {});
45
45
  return htmlFailure(error, startedAt);
@@ -2,17 +2,25 @@
2
2
  // deadline and abort wiring around the layout work, the failure result, and the photographs of captured elements.
3
3
  import { join } from 'node:path';
4
4
 
5
- import { buildPuppeteerLaunchArgs, resolveBrowserLaunchOptions } from '../../shared/browser-launch.mjs';
5
+ import {
6
+ buildPuppeteerLaunchArgs,
7
+ launchPuppeteerBrowser,
8
+ resolveBrowserLaunchOptions,
9
+ } from '../../shared/browser-launch.mjs';
6
10
  import { startChildGuardian } from '../../shared/child-guardian.mjs';
7
11
 
8
12
  /** Launches the local browser the HTML is laid out in, under a guardian that reaps it with this process. */
9
- export async function launchHtmlBrowser(label) {
13
+ export async function launchHtmlBrowser(label, { signal = null } = {}) {
10
14
  const puppeteer = (await import('puppeteer-core')).default;
11
- const browser = await puppeteer.launch({
12
- headless: true,
13
- ...resolveBrowserLaunchOptions(),
14
- args: buildPuppeteerLaunchArgs(['--font-render-hinting=none']),
15
- });
15
+ const browser = await launchPuppeteerBrowser(
16
+ puppeteer,
17
+ {
18
+ headless: true,
19
+ ...resolveBrowserLaunchOptions(),
20
+ args: buildPuppeteerLaunchArgs(['--font-render-hinting=none']),
21
+ },
22
+ { signal }
23
+ );
16
24
  try {
17
25
  startChildGuardian({ childPid: browser.process?.()?.pid, label });
18
26
  } catch {}
@@ -22,7 +30,9 @@ export async function launchHtmlBrowser(label) {
22
30
  /** Runs `work` against the browser under a deadline and an abort signal, and closes the browser afterwards. */
23
31
  export async function runInBrowser(browser, { signal, timeoutMs }, work) {
24
32
  const abort = () => browser.close().catch(() => {});
25
- signal?.addEventListener('abort', abort, { once: true });
33
+ // A cancel that landed while the browser was launching closes it at once.
34
+ if (signal?.aborted) abort();
35
+ else signal?.addEventListener('abort', abort, { once: true });
26
36
  let timer = null;
27
37
  const running = work();
28
38
  const timeout = new Promise((_, reject) => {
@@ -262,7 +262,7 @@ export async function printHtmlPdf(
262
262
  await rm(output, { force: true });
263
263
  let browser;
264
264
  try {
265
- browser = await launchHtmlBrowser('pdf-html-browser');
265
+ browser = await launchHtmlBrowser('pdf-html-browser', { signal });
266
266
  } catch (error) {
267
267
  return htmlFailure(error, startedAt);
268
268
  }
@@ -559,7 +559,7 @@ export async function measureHtmlDeck(
559
559
  { sourcePath, shotPath, timeoutMs = 90_000, signal = null, prepare = null }
560
560
  ) {
561
561
  await writeFile(sourcePath, html, 'utf8');
562
- const browser = await launchHtmlBrowser('pptx-html-browser');
562
+ const browser = await launchHtmlBrowser('pptx-html-browser', { signal });
563
563
  const work = async () => {
564
564
  const page = await browser.newPage();
565
565
  await page.setViewport(viewport(HTML_CANVAS, 1));
@@ -66,7 +66,7 @@ export async function runXlsxHtmlAuthoring(html, output, { target, signal = null
66
66
  const scratch = await mkdtemp(join(tmpdir(), 'mixdog-xlsx-captures-'));
67
67
  let browser;
68
68
  try {
69
- browser = await launchHtmlBrowser('xlsx-html-browser');
69
+ browser = await launchHtmlBrowser('xlsx-html-browser', { signal });
70
70
  } catch (error) {
71
71
  await rm(scratch, { recursive: true, force: true }).catch(() => {});
72
72
  return htmlFailure(error, startedAt);
@@ -16,11 +16,7 @@ import { AsyncLocalStorage } from 'node:async_hooks';
16
16
  import { statSync } from 'node:fs';
17
17
  import { tmpdir } from 'node:os';
18
18
  import { isAbsolute, relative, resolve } from 'node:path';
19
- import {
20
- PATCH_EDIT_BUDGET_BYTES,
21
- createSnapshotBudget,
22
- reviewFileContent,
23
- } from '../../shared/bounded-file-read.mjs';
19
+ import { PATCH_EDIT_BUDGET_BYTES, createSnapshotBudget, reviewFileContent } from '../../shared/bounded-file-read.mjs';
24
20
 
25
21
  const ledgers = new AsyncLocalStorage();
26
22
 
@@ -31,9 +31,8 @@ export async function executeOfficeTool(args = {}, context = {}) {
31
31
  const sessionId = String(context.sessionId || '');
32
32
  if (!sessionId) return await tracedOfficeTool(args, context);
33
33
  const cwd = context.cwd || process.cwd();
34
- const { ledger, run } = runWithOfficeWriteLedger(
35
- { cwd, dataDir: context.dataDir || defaultOfficeDataDir() },
36
- () => tracedOfficeTool(args, context)
34
+ const { ledger, run } = runWithOfficeWriteLedger({ cwd, dataDir: context.dataDir || defaultOfficeDataDir() }, () =>
35
+ tracedOfficeTool(args, context)
37
36
  );
38
37
  try {
39
38
  return await run;
@@ -261,74 +261,6 @@ export async function extractPdfTextLayout(
261
261
  // so the adapter, the tool surface, and tests keep one import.
262
262
  export { findPdfText } from './pdf-search.mjs';
263
263
 
264
- // The text run at `index`, joined with the next run on the same line when only
265
- // the joined text names a category (a label the PDF drew as two runs).
266
- function categoryLabelAt(items, index, expected) {
267
- const item = items[index];
268
- const text = String(item?.text || '').trim();
269
- if (!text || expected.includes(text)) return text;
270
- let nextIndex = index + 1;
271
- while (nextIndex < items.length && !String(items[nextIndex]?.text || '').trim()) nextIndex += 1;
272
- const next = items[nextIndex];
273
- if (!next || Math.abs(Number(next.top) - Number(item.top)) > 1) return text;
274
- const joined = `${text}${String(next.text || '').trim()}`;
275
- return expected.includes(joined) ? joined : text;
276
- }
277
-
278
- // Every line that carries category labels, keyed by page and baseline.
279
- function categoryLabelRows(layout, expected) {
280
- const rows = [];
281
- for (const page of layout?.pages || []) {
282
- const items = Array.isArray(page.items) ? page.items : [];
283
- for (let index = 0; index < items.length; index += 1) {
284
- const item = items[index];
285
- const text = categoryLabelAt(items, index, expected);
286
- if (!text || !expected.includes(text)) continue;
287
- let row = rows.find((entry) => entry.page === page.page && Math.abs(entry.top - Number(item.top)) <= 1);
288
- if (!row) {
289
- row = { page: page.page, width: Number(page.width) || 0, top: Number(item.top), labels: [] };
290
- rows.push(row);
291
- }
292
- if (!row.labels.some((entry) => entry.text === text)) {
293
- row.labels.push({ text, x: Number(item.x) || 0 });
294
- }
295
- }
296
- }
297
- return rows;
298
- }
299
-
300
- function categoryRowSpacing(row, expected) {
301
- const labels = expected
302
- .map((text) => row.labels.find((entry) => entry.text === text))
303
- .sort((left, right) => left.x - right.x);
304
- const gaps = labels.slice(1).map((entry, index) => entry.x - labels[index].x);
305
- return {
306
- ...row,
307
- labels,
308
- span: labels.length > 1 ? labels.at(-1).x - labels[0].x : 0,
309
- minimumGap: gaps.length ? Math.min(...gaps) : 0,
310
- };
311
- }
312
-
313
- export function evaluatePowerPointCategorySpacing(layout, categories = []) {
314
- const expected = [...new Set(categories.map((entry) => String(entry || '').trim()).filter(Boolean))];
315
- const complete = categoryLabelRows(layout, expected)
316
- .filter((row) => expected.every((text) => row.labels.some((entry) => entry.text === text)))
317
- .map((row) => categoryRowSpacing(row, expected))
318
- .sort((left, right) => right.span - left.span);
319
- const best = complete[0] || null;
320
- const requiredGap = best ? best.width * 0.1 : 0;
321
- return {
322
- ok: expected.length <= 1 || Boolean(best && best.minimumGap >= requiredGap),
323
- categories: expected,
324
- page: best?.page || 0,
325
- labels: best?.labels || [],
326
- span: Number((best?.span || 0).toFixed(2)),
327
- minimumGap: Number((best?.minimumGap || 0).toFixed(2)),
328
- requiredGap: Number(requiredGap.toFixed(2)),
329
- };
330
- }
331
-
332
264
  // A PDF reports the runs it drew, not cells: a cell whose words were drawn as
333
265
  // separate runs would count as several columns and break its row against the
334
266
  // rows around it. Runs closer than half a line height are the same cell — the
@@ -58,7 +58,7 @@ export async function forgetSessionSearch(sessionId) {
58
58
 
59
59
  async function loadStoreDeps() {
60
60
  const [{ getStoreDir }, { _storedSessionFromFile, STORED_SESSION_UNREADABLE }] = await Promise.all([
61
- import('../agent/orchestrator/session/store/paths-heartbeat.mjs'),
61
+ import('../agent/orchestrator/session/store/session-paths.mjs'),
62
62
  import('../agent/orchestrator/session/store/serialize.mjs'),
63
63
  ]);
64
64
  return {
@@ -3,6 +3,19 @@ import { positiveInt } from './numbers.mjs';
3
3
 
4
4
  export const TAG_TOMBSTONE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
5
5
 
6
+ // How long a finished agent (completed, failed or cancelled child, or a Lead
7
+ // row) stays listed in the Agents pane, measured from its finish stamp. This is
8
+ // pane visibility only: session reuse and cache reaping keep their own windows
9
+ // (resolveAgentTerminalReapMs).
10
+ export const AGENT_PANE_VISIBLE_MS = 60 * 60 * 1000;
11
+
12
+ /** Whether a row finished at `finishedAtMs` is still inside the pane window.
13
+ * Index rows are always stamped; a row with no usable stamp has no provable
14
+ * age and stays listed rather than vanishing on a guess. */
15
+ export function agentPaneVisible(finishedAtMs, now) {
16
+ return !(finishedAtMs > 0) || now - finishedAtMs < AGENT_PANE_VISIBLE_MS;
17
+ }
18
+
6
19
  export function stampMs(value) {
7
20
  if (typeof value === 'number') return Number.isFinite(value) && value > 0 ? value : 0;
8
21
  const text = clean(value);
@@ -23,5 +23,5 @@ export function writeJsonAtomicAsync(
23
23
  export function withFileLock<T>(
24
24
  lockPath: string,
25
25
  task: () => T | Promise<T>,
26
- options?: { timeoutMs?: number; staleMs?: number; secret?: boolean }
26
+ options?: { timeoutMs?: number; staleMs?: number; secret?: boolean; strictOwner?: boolean }
27
27
  ): Promise<T>;
@@ -330,13 +330,18 @@ export function settleCancelledBackgroundTask(taskId, { result } = {}) {
330
330
  export function cancelBackgroundTasks(options = {}) {
331
331
  const reason = clean(options.reason) || 'cancelled';
332
332
  const wanted = clean(options.surface);
333
+ const executionSessionId = clean(options.executionSessionId);
333
334
  let cancelled = 0;
334
335
  for (const task of [...tasks.values()]) {
335
336
  if (wanted && task.surface !== wanted) continue;
336
337
  // Scoped teardown must be exact. Legacy tasks without ownership stay
337
338
  // visible/recoverable, but may not be claimed by whichever session happens
338
339
  // to close first.
339
- if (!taskMatchesScope(task, options, { includeUnattributed: false })) continue;
340
+ if (executionSessionId) {
341
+ // Delegated work executes in its bound session; before binding it still
342
+ // belongs to the registering session. Notification ownership is separate.
343
+ if ((clean(task.meta?.sessionId) || task.ownerSessionId) !== executionSessionId) continue;
344
+ } else if (!taskMatchesScope(task, options, { includeUnattributed: false })) continue;
340
345
  if (TERMINAL_STATUSES.has(task.status)) {
341
346
  if (options.notify === false) silenceCancelledNotification(task);
342
347
  continue;
@@ -162,14 +162,23 @@ export function lineDiffHunks(oldText, newText, budget, sources = [oldText, newT
162
162
  result ||= { hunks: replacementHunks(oldText, newText), exact: false };
163
163
  if (key && repeatable) cacheSet(key, result, oldText.length + newText.length);
164
164
  // Within its own operation it is final: the budget never grows back.
165
- else if (key) (budget.fallbacks ||= new Map()).set(key, result);
165
+ else if (key) {
166
+ budget.fallbacks ||= new Map();
167
+ budget.fallbacks.set(key, result);
168
+ }
166
169
  return result;
167
170
  }
168
171
 
169
172
  /** The `--- `/`+++ ` headers and hunks of `hunks`, as jsdiff formats them,
170
173
  * ending in exactly one newline. */
171
174
  export function unifiedHunksBody(oldHeader, newHeader, hunks) {
172
- const generated = formatPatch({ oldFileName: oldHeader, newFileName: newHeader, oldHeader: '', newHeader: '', hunks });
175
+ const generated = formatPatch({
176
+ oldFileName: oldHeader,
177
+ newFileName: newHeader,
178
+ oldHeader: '',
179
+ newHeader: '',
180
+ hunks,
181
+ });
173
182
  let end = generated.length;
174
183
  // Linear trim: a /\n*$/ regex rescans from every position of a large patch.
175
184
  while (end > 0 && generated.charCodeAt(end - 1) === 10) end -= 1;
@@ -88,3 +88,18 @@ export function buildPuppeteerLaunchArgs(extra = []) {
88
88
  if (puppeteerNoSandboxEnabled()) args.push('--no-sandbox');
89
89
  return args;
90
90
  }
91
+
92
+ // A healthy headless launch answers in a few seconds; the observed CI failures
93
+ // are launches that never print the DevTools endpoint and hit puppeteer's 30 s
94
+ // TimeoutError (Windows runner, Linux runner). Puppeteer closes that child
95
+ // itself, so one fresh attempt is the remedy; a longer wait would only delay
96
+ // the same hang. Any other launch error (missing executable, bad args), or a
97
+ // timeout after `signal` aborted, is not retried.
98
+ export async function launchPuppeteerBrowser(puppeteer, options, { signal = null } = {}) {
99
+ try {
100
+ return await puppeteer.launch(options);
101
+ } catch (error) {
102
+ if (error?.name !== 'TimeoutError' || signal?.aborted) throw error;
103
+ return puppeteer.launch(options);
104
+ }
105
+ }
@@ -1,6 +1,7 @@
1
1
  import { transportErrorText } from './transport-error-text.mjs';
2
2
  import { describeError, safeErrorDetails } from './error-presentation.mjs';
3
3
  import { oneLine } from './clean.mjs';
4
+ import { TIMEOUT_PHASE, isFirstByteTimeout } from './provider-timeout-phase.mjs';
4
5
 
5
6
  // HTTP status carried by a provider/transport error, or 0 when absent.
6
7
  export function errorHttpStatus(err) {
@@ -185,7 +186,9 @@ export function isCancelLikeError(error) {
185
186
  const KIND_LINES = {
186
187
  overloaded: 'Provider is busy at capacity.',
187
188
  rate_limited: ({ retryAfterMs }) =>
188
- retryAfterMs ? `Provider is rate-limited (retry in ${formatDurationMs(retryAfterMs)}).` : 'Provider is rate-limited.',
189
+ retryAfterMs
190
+ ? `Provider is rate-limited (retry in ${formatDurationMs(retryAfterMs)}).`
191
+ : 'Provider is rate-limited.',
189
192
  quota_exhausted: 'Provider usage limit reached.',
190
193
  entitlement: 'Not included in your plan.',
191
194
  billing: 'Out of credits or at spend limit.',
@@ -198,7 +201,38 @@ const KIND_LINES = {
198
201
  // A bare message carries none, so its wording keeps the legacy text path.
199
202
  function hasTypedEvidence(error) {
200
203
  if (!error || typeof error !== 'object') return false;
201
- return Boolean(error.errorKind?.kind) || errorHttpStatus(error) > 0 || Boolean(error.code) || Boolean(error.retryAfterMs);
204
+ return (
205
+ Boolean(error.errorKind?.kind) || errorHttpStatus(error) > 0 || Boolean(error.code) || Boolean(error.retryAfterMs)
206
+ );
207
+ }
208
+
209
+ // The window a provider timeout names (providerTimeoutError's message), or 0.
210
+ const timedOutMs = (text) => Number(/timed out after (\d+)ms/i.exec(text)?.[1]) || 0;
211
+
212
+ // A provider timeout before the model produced anything (isFirstByteTimeout):
213
+ // the model is slow, not the connection.
214
+ function stillThinkingText(error, text) {
215
+ const ms = Number(error.waitedMs) || timedOutMs(text);
216
+ const provider =
217
+ typeof error.providerLabel === 'string' && error.providerLabel ? error.providerLabel : 'the provider';
218
+ const attempts = Number(error.attempts) > 0 ? `; attempts: ${Math.floor(Number(error.attempts))}` : '';
219
+ return `Model still thinking: no output in ${formatDurationMs(ms)}; ${provider} sent nothing yet${attempts}.`;
220
+ }
221
+
222
+ // Stamped provider timeout phases and stream cuts. Null when the error names
223
+ // neither, so the text rules below decide.
224
+ function typedStreamFailureText(error, text) {
225
+ if (!error || typeof error !== 'object') return null;
226
+ if (isFirstByteTimeout(error)) return stillThinkingText(error, text);
227
+ if (error.timeoutPhase === TIMEOUT_PHASE.IDLE) {
228
+ const ms = timedOutMs(text);
229
+ return ms > 0 ? `No progress ${formatDurationMs(ms)}.` : 'No progress.';
230
+ }
231
+ // A stream that ended before its finish marker lost its connection.
232
+ if (error.code === 'TRUNCATED_STREAM' && error.streamCorruption !== true) {
233
+ return 'Connection to the provider was lost (TRUNCATED_STREAM).';
234
+ }
235
+ return null;
202
236
  }
203
237
 
204
238
  export function presentErrorText(error, options = {}) {
@@ -227,6 +261,8 @@ export function presentErrorText(error, options = {}) {
227
261
  if (embeddedError) text = embeddedError;
228
262
  text = oneLine(stripErrorPrefix(text));
229
263
  if (!text) return 'Unknown error';
264
+ const streamFailure = typedStreamFailureText(error, text);
265
+ if (streamFailure) return streamFailure;
230
266
 
231
267
  if (code === 'PROVIDER_PREFIX_MUTATION' || name === 'ProviderPrefixMutationError') {
232
268
  return 'Session state changed unexpectedly.';
@@ -282,9 +318,10 @@ export function presentErrorText(error, options = {}) {
282
318
  return Number.isFinite(ms) && ms > 0 ? `No first response ${formatDurationMs(ms)}.` : 'No first response.';
283
319
  }
284
320
 
321
+ // The agent's own watchdog, worded apart from any provider timeout.
285
322
  const firstTransport = /first (?:transport|semantic response) stale\s*\((\d+)ms\)/i.exec(text);
286
323
  if (firstTransport) {
287
- return `No first response ${formatDurationMs(firstTransport[1])}.`;
324
+ return `Agent watchdog stopped the run: no model response in ${formatDurationMs(firstTransport[1])}.`;
288
325
  }
289
326
 
290
327
  const firstResponse = /(?:agent\s+)?first response stale\s*\((\d+)ms\)/i.exec(text);
@@ -363,7 +400,8 @@ export function backgroundTaskFailureStatusLabel(status, error, options = {}) {
363
400
  /^timeout$/i.test(statusNorm) ||
364
401
  watchdog?.kind === 'timeout' ||
365
402
  /first response stale/i.test(raw) ||
366
- /no first response from/i.test(presented)
403
+ /no first response from/i.test(presented) ||
404
+ /^(?:Model still thinking|Agent watchdog stopped the run):/.test(presented)
367
405
  )
368
406
  head = 'Timeout';
369
407
  else if (
@@ -278,12 +278,13 @@ function* releaseReclaimGuard(reclaim) {
278
278
 
279
279
  // A lock is reclaimable when its recorded owner is dead, or when it names no
280
280
  // owner at all and has outlived the stale window.
281
- function lockReclaimable(owner, stat, staleMs) {
281
+ function lockReclaimable(owner, stat, staleMs, strictOwner = false) {
282
282
  if (owner.pid !== null) return !ownerIsLive(owner);
283
+ if (strictOwner) return false;
283
284
  return Date.now() - (Number(stat.mtimeMs) || 0) >= Math.max(0, staleMs);
284
285
  }
285
286
 
286
- function* tryReclaimStaleLock(lockPath, staleMs) {
287
+ function* tryReclaimStaleLock(lockPath, staleMs, strictOwner = false) {
287
288
  let initial;
288
289
  try {
289
290
  initial = yield ['stat', lockPath];
@@ -291,7 +292,7 @@ function* tryReclaimStaleLock(lockPath, staleMs) {
291
292
  return false;
292
293
  }
293
294
  const owner = yield* readLockOwner(lockPath);
294
- if (!lockReclaimable(owner, initial, staleMs)) return false;
295
+ if (!lockReclaimable(owner, initial, staleMs, strictOwner)) return false;
295
296
  const reclaim = yield* tryAcquireReclaimGuard(lockPath);
296
297
  if (reclaim === null) return false;
297
298
  try {
@@ -303,7 +304,7 @@ function* tryReclaimStaleLock(lockPath, staleMs) {
303
304
  }
304
305
  const currentOwner = yield* readLockOwner(lockPath);
305
306
  if (currentOwner.pid !== owner.pid || currentOwner.token !== owner.token) return false;
306
- if (lockReclaimable(currentOwner, current, staleMs)) {
307
+ if (lockReclaimable(currentOwner, current, staleMs, strictOwner)) {
307
308
  try {
308
309
  yield ['unlink', lockPath];
309
310
  return true;
@@ -429,7 +430,7 @@ function* acquireLock(lockPath, opts, mode, markHeld, timing = null) {
429
430
  // The reclaim runs synchronously in both modes: it is a few metadata
430
431
  // calls, and an await inside it would let a process exit strand its guard.
431
432
  try {
432
- if (runSync(tryReclaimStaleLock(lockPath, staleMs))) continue;
433
+ if (runSync(tryReclaimStaleLock(lockPath, staleMs, opts.strictOwner === true))) continue;
433
434
  } catch {}
434
435
  if (timeoutMs <= 0) throw contentionError(lockPath, error, yield* describeLockHolder(lockPath));
435
436
  if (Date.now() >= deadline) break;
@@ -2,7 +2,6 @@ import { EFFORT_BETA_HEADER } from './anthropic-effort.mjs';
2
2
  import { normalizeClaudeModelId } from './anthropic-thinking-contract.mjs';
3
3
  import { getModelMetadataSync } from './model-catalog.mjs';
4
4
 
5
- const EXTENDED_CACHE_TTL_BETA_HEADER = 'extended-cache-ttl-2025-04-11';
6
5
  const INTERLEAVED_THINKING_BETA_HEADER = 'interleaved-thinking-2025-05-14';
7
6
  const FAST_MODE_BETA_HEADER = 'fast-mode-2026-02-01';
8
7
  const TOOL_SEARCH_BETA_HEADER = 'advanced-tool-use-2025-11-20';
@@ -23,7 +22,7 @@ export function supportsAnthropicFastMode(model) {
23
22
  }
24
23
 
25
24
  export function buildAnthropicBetaHeaders({
26
- base = `${INTERLEAVED_THINKING_BETA_HEADER},${EXTENDED_CACHE_TTL_BETA_HEADER}`,
25
+ base = INTERLEAVED_THINKING_BETA_HEADER,
27
26
  fastMode = false,
28
27
  toolSearch = false,
29
28
  effort = false,
@@ -28,6 +28,7 @@ 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
32
  fallbackModel('claude-opus-4-6-thinking', 'Claude Opus 4.6 (Thinking)', null, 250000, 'claude-opus-4-6-thinking', [
32
33
  'low',
33
34
  'medium',
@@ -0,0 +1,8 @@
1
+ export function canonicalRouteProvider(value: unknown): string;
2
+ export function routeChain(route: Record<string, unknown>): Record<string, unknown>[];
3
+ export function validateRouteChainProviders(routes: Record<string, unknown>[]): void;
4
+ export function normalizeRouteChain(
5
+ route: Record<string, unknown>,
6
+ normalizeLeaf: (route: Record<string, unknown>, fallback: Record<string, unknown>) => Record<string, unknown> | null,
7
+ fallback?: Record<string, unknown>
8
+ ): Record<string, unknown> | null;
@@ -0,0 +1,48 @@
1
+ const PROVIDER_ALIASES = Object.freeze({
2
+ 'openai-api': 'openai',
3
+ 'xai-api': 'xai',
4
+ 'gemini-api': 'gemini',
5
+ 'anthropic-api': 'anthropic',
6
+ });
7
+
8
+ export function canonicalRouteProvider(value) {
9
+ const id = String(value || '').trim();
10
+ return PROVIDER_ALIASES[id] || id;
11
+ }
12
+
13
+ export function routeChain(route) {
14
+ if (!route || typeof route !== 'object' || Array.isArray(route)) return [];
15
+ const { fallbacks = [], ...primary } = route;
16
+ if (!Array.isArray(fallbacks)) throw new Error('route.fallbacks must be an array');
17
+ for (const entry of fallbacks) {
18
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry) || Object.hasOwn(entry, 'fallbacks')) {
19
+ throw new Error('fallback routes must be leaf route objects');
20
+ }
21
+ }
22
+ return [primary, ...fallbacks];
23
+ }
24
+
25
+ export function validateRouteChainProviders(routes) {
26
+ const providers = new Set();
27
+ for (const route of routes) {
28
+ const provider = canonicalRouteProvider(route.provider);
29
+ if (!provider || !String(route.model || '').trim()) throw new Error('route requires provider and model');
30
+ if (providers.has(provider)) throw new Error(`duplicate route provider "${provider}"`);
31
+ if (providers.size && provider === 'default') throw new Error('follow Main is only allowed as the primary route');
32
+ providers.add(provider);
33
+ }
34
+ }
35
+
36
+ export function normalizeRouteChain(route, normalizeLeaf, fallback = {}) {
37
+ const raw = routeChain({ ...fallback, ...route });
38
+ const primary = normalizeLeaf(route, fallback);
39
+ if (!primary) return null;
40
+ const routes = raw.map((entry, index) => {
41
+ const normalized = index === 0 ? primary : normalizeLeaf(entry, {});
42
+ if (!normalized) throw new Error('fallback route requires provider and model');
43
+ return { ...normalized, provider: canonicalRouteProvider(normalized.provider) };
44
+ });
45
+ validateRouteChainProviders(routes);
46
+ const [first, ...rest] = routes;
47
+ return rest.length ? { ...first, fallbacks: rest } : first;
48
+ }