mixdog 1.3.4 → 1.4.0

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 (472) hide show
  1. package/LICENSES/oh-my-pi-MIT.txt +23 -0
  2. package/NOTICE.md +17 -2
  3. package/README.md +11 -0
  4. package/package.json +3 -11
  5. package/scripts/daemon-stub.mjs +1 -1
  6. package/scripts/embed-google-oauth-client.mjs +49 -0
  7. package/scripts/gate-local.mjs +4 -2
  8. package/scripts/lib/test-spawn-ledger.mjs +46 -1
  9. package/scripts/native-release-status.mjs +2 -2
  10. package/scripts/recall-history-cases.json +1 -1
  11. package/scripts/suite-health.mjs +10 -1
  12. package/scripts/test.mjs +12 -11
  13. package/src/agents/reviewer/AGENT.md +7 -11
  14. package/src/agents/reviewer/agent.json +1 -1
  15. package/src/defaults/shipped-history.json +25 -1
  16. package/src/defaults/skills/browser-use/SKILL.md +77 -17
  17. package/src/defaults/skills/code-tidy/SKILL.md +142 -270
  18. package/src/defaults/skills/code-tidy/references/agent-cleanup.md +4 -4
  19. package/src/defaults/skills/code-tidy/references/cleanup-report.md +31 -76
  20. package/src/defaults/skills/code-tidy/references/dead-code.md +6 -12
  21. package/src/defaults/skills/computer-use/SKILL.md +33 -9
  22. package/src/defaults/skills/setup/SKILL.md +29 -2
  23. package/src/defaults/skills/setup/references/actions.md +117 -28
  24. package/src/defaults/skills/setup/references/surfaces.md +77 -35
  25. package/src/defaults/skills/setup-guide/SKILL.md +3 -3
  26. package/src/defaults/skills/visualize/SKILL.md +2 -1
  27. package/src/lib/keychain-cjs.cjs +359 -29
  28. package/src/lib/rules-builder.cjs +10 -8
  29. package/src/rules/agent/AGENT.md +6 -6
  30. package/src/rules/agent-mode/AGENT_MODE.md +10 -2
  31. package/src/rules/agent-mode/MIXY.md +12 -0
  32. package/src/rules/lead/LEAD.md +18 -20
  33. package/src/rules/shared/05-tool-calls.md +5 -6
  34. package/src/rules/shared/10-tool-workflow.md +2 -0
  35. package/src/rules/shared/60-verification.md +7 -5
  36. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -3
  37. package/src/runtime/agent/orchestrator/agent-runtime/heartbeat-completion.mjs +193 -0
  38. package/src/runtime/agent/orchestrator/agent-runtime/title-completion.mjs +3 -1
  39. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +3 -1
  40. package/src/runtime/agent/orchestrator/config.mjs +34 -0
  41. package/src/runtime/agent/orchestrator/context/collect.mjs +3 -0
  42. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +72 -67
  43. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +54 -1
  44. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +3 -3
  45. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +177 -73
  46. package/src/runtime/agent/orchestrator/mcp/client.mjs +25 -2
  47. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +31 -2
  48. package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +16 -3
  49. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +42 -7
  50. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +21 -2
  51. package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +23 -0
  52. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +11 -1
  53. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +230 -137
  54. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +64 -7
  55. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +18 -5
  56. package/src/runtime/agent/orchestrator/providers/anthropic-cache-warm.mjs +165 -0
  57. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  58. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +4 -1
  59. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +14 -14
  60. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +160 -69
  61. package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +25 -3
  62. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +64 -33
  63. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +135 -87
  64. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +52 -8
  65. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +10 -1
  66. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +22 -6
  67. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +15 -4
  68. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +17 -2
  69. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +142 -104
  70. package/src/runtime/agent/orchestrator/providers/cache-warm-admission.mjs +90 -0
  71. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  72. package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +10 -4
  73. package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +10 -217
  74. package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +25 -9
  75. package/src/runtime/agent/orchestrator/providers/cursor.mjs +22 -6
  76. package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +409 -0
  77. package/src/runtime/agent/orchestrator/providers/devin-wire-families.mjs +187 -0
  78. package/src/runtime/agent/orchestrator/providers/devin-wire-identity.mjs +342 -0
  79. package/src/runtime/agent/orchestrator/providers/devin-wire-models.mjs +541 -0
  80. package/src/runtime/agent/orchestrator/providers/devin-wire-protobuf.mjs +305 -0
  81. package/src/runtime/agent/orchestrator/providers/devin-wire-request.mjs +558 -0
  82. package/src/runtime/agent/orchestrator/providers/devin-wire-schema.mjs +880 -0
  83. package/src/runtime/agent/orchestrator/providers/devin-wire-session.mjs +190 -0
  84. package/src/runtime/agent/orchestrator/providers/devin-wire-tool-args.mjs +351 -0
  85. package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +560 -0
  86. package/src/runtime/agent/orchestrator/providers/devin.mjs +491 -0
  87. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +3 -0
  88. package/src/runtime/agent/orchestrator/providers/gemini-output-limit.mjs +46 -0
  89. package/src/runtime/agent/orchestrator/providers/gemini-reasoning-relay.mjs +30 -0
  90. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +7 -11
  91. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +61 -22
  92. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +1 -0
  93. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +71 -4
  94. package/src/runtime/agent/orchestrator/providers/gemini.mjs +25 -2
  95. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  96. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +96 -5
  97. package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +17 -3
  98. package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +23 -5
  99. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +52 -10
  100. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +8 -1
  101. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
  102. package/src/runtime/agent/orchestrator/providers/npm-cli-version.mjs +11 -3
  103. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +19 -1
  104. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +39 -3
  105. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +2 -0
  107. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +6 -1
  108. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -9
  109. package/src/runtime/agent/orchestrator/providers/openai-ws-send/send-context.mjs +22 -3
  110. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +43 -5
  111. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/handshake-failure.mjs +3 -1
  112. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/stream-failure.mjs +6 -3
  113. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +1 -0
  114. package/src/runtime/agent/orchestrator/providers/provider-availability.mjs +19 -0
  115. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +65 -1
  116. package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +38 -12
  117. package/src/runtime/agent/orchestrator/providers/registry.mjs +43 -3
  118. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +39 -7
  119. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +6 -0
  120. package/src/runtime/agent/orchestrator/providers/wire-protobuf-codec.mjs +368 -0
  121. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +12 -7
  122. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
  123. package/src/runtime/agent/orchestrator/runtime-core/compaction-config-keys.mjs +62 -0
  124. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +37 -16
  125. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-delta.mjs +100 -23
  126. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +1 -1
  127. package/src/runtime/agent/orchestrator/runtime-core/reaction-tool-defs.mjs +133 -0
  128. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -0
  129. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +19 -4
  130. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  131. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +125 -30
  132. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +23 -5
  133. package/src/runtime/agent/orchestrator/runtime-core/tool-profile.mjs +35 -10
  134. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +57 -10
  135. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +9 -6
  136. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -1
  137. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +18 -0
  138. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +0 -3
  139. package/src/runtime/agent/orchestrator/session/agent-room.mjs +10 -4
  140. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +792 -0
  141. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +148 -25
  142. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +475 -58
  143. package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +42 -17
  144. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +8 -0
  145. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +47 -9
  146. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +6 -2
  147. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -4
  148. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +14 -2
  149. package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +8 -0
  150. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +18 -22
  151. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +32 -3
  152. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +11 -2
  153. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +4 -1
  154. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +51 -3
  155. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +17 -3
  156. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -2
  157. package/src/runtime/agent/orchestrator/session/loop/tool-exec/after-hook.mjs +2 -1
  158. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +14 -4
  159. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +16 -2
  160. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +16 -1
  161. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +10 -0
  162. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -10
  163. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +23 -5
  164. package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +60 -0
  165. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -2
  166. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +19 -1
  167. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +2 -0
  168. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +133 -90
  169. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +14 -0
  170. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -1
  171. package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +31 -4
  172. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -18
  173. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +23 -13
  174. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +11 -2
  175. package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +13 -0
  176. package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +129 -0
  177. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +20 -2
  178. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +2 -0
  179. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +8 -0
  180. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +1 -1
  181. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +13 -0
  182. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +7 -2
  183. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +102 -15
  184. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +18 -5
  185. package/src/runtime/agent/orchestrator/session/message-ids.mjs +5 -3
  186. package/src/runtime/agent/orchestrator/session/message-reactions.mjs +89 -0
  187. package/src/runtime/agent/orchestrator/session/message-reactions.test-support.mjs +75 -0
  188. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +19 -8
  189. package/src/runtime/agent/orchestrator/session/prompt-cache-idle-gaps.mjs +113 -0
  190. package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +127 -0
  191. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +0 -2
  192. package/src/runtime/agent/orchestrator/session/routine-result.mjs +47 -11
  193. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +20 -2
  194. package/src/runtime/agent/orchestrator/session/sensitive-projection.mjs +100 -0
  195. package/src/runtime/agent/orchestrator/session/sensitive-values.mjs +257 -0
  196. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +20 -7
  197. package/src/runtime/agent/orchestrator/session/side-question.mjs +18 -0
  198. package/src/runtime/agent/orchestrator/session/skill-update-notice.mjs +60 -0
  199. package/src/runtime/agent/orchestrator/session/store/live-state.mjs +6 -3
  200. package/src/runtime/agent/orchestrator/session/store/save-delta.mjs +9 -5
  201. package/src/runtime/agent/orchestrator/session/store/serialize.mjs +9 -8
  202. package/src/runtime/agent/orchestrator/session/store/session-persistence-events.mjs +29 -0
  203. package/src/runtime/agent/orchestrator/session/store-agent-worker-header-cache.mjs +85 -0
  204. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +133 -18
  205. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +12 -2
  206. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -14
  207. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +84 -3
  208. package/src/runtime/agent/orchestrator/session/stored-attachment-history.mjs +32 -0
  209. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +11 -5
  210. package/src/runtime/agent/orchestrator/session/thinking-binding.mjs +108 -0
  211. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +2 -2
  212. package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +2 -1
  213. package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +4 -0
  214. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +5 -0
  215. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +25 -2
  216. package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-status.mjs +9 -7
  217. package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-text.mjs +2 -2
  218. package/src/runtime/agent/orchestrator/session/turn-failure.mjs +5 -1
  219. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +5 -2
  220. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +12 -1
  221. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/misuse-guard.mjs +240 -0
  222. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +1 -1
  223. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/shell-cwd.mjs +38 -0
  224. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +25 -5
  225. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +65 -7
  226. package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +7 -1
  227. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +36 -13
  228. package/src/runtime/browser-bridge/action-schema.mjs +13 -2
  229. package/src/runtime/browser-bridge/client.mjs +73 -8
  230. package/src/runtime/browser-bridge/input-fields.mjs +8 -9
  231. package/src/runtime/browser-bridge/mail-links.mjs +107 -0
  232. package/src/runtime/browser-bridge/tool-defs.mjs +1 -1
  233. package/src/runtime/channels/lib/config.mjs +6 -61
  234. package/src/runtime/channels/lib/crash-log.mjs +1 -1
  235. package/src/runtime/channels/lib/event-pipeline.mjs +4 -19
  236. package/src/runtime/channels/lib/event-queue.mjs +3 -18
  237. package/src/runtime/channels/lib/owned-runtime/automation-services.mjs +2 -2
  238. package/src/runtime/channels/lib/owned-runtime/automation-start.mjs +3 -4
  239. package/src/runtime/channels/lib/owned-runtime/config-reload.mjs +4 -59
  240. package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +33 -115
  241. package/src/runtime/channels/lib/owned-runtime/owned-stop.mjs +5 -22
  242. package/src/runtime/channels/lib/owned-runtime/state.mjs +2 -8
  243. package/src/runtime/channels/lib/owned-runtime.mjs +6 -23
  244. package/src/runtime/channels/lib/parent-bridge.mjs +2 -2
  245. package/src/runtime/channels/lib/runtime-paths.mjs +4 -43
  246. package/src/runtime/channels/lib/schedule-errors.mjs +51 -0
  247. package/src/runtime/channels/lib/scheduler.mjs +135 -154
  248. package/src/runtime/channels/lib/schedules-db.mjs +83 -11
  249. package/src/runtime/channels/lib/status-snapshot.mjs +1 -1
  250. package/src/runtime/channels/lib/tool-dispatch.mjs +14 -2
  251. package/src/runtime/channels/lib/webhooks-db.mjs +5 -9
  252. package/src/runtime/channels/lib/worker-bootstrap.mjs +0 -1
  253. package/src/runtime/channels/lib/worker-ipc.mjs +0 -1
  254. package/src/runtime/channels/lib/worker-main.mjs +19 -34
  255. package/src/runtime/computer-bridge/action-schema.mjs +42 -1
  256. package/src/runtime/computer-bridge/actions.mjs +9 -1
  257. package/src/runtime/computer-bridge/client.mjs +180 -8
  258. package/src/runtime/computer-bridge/core-actions.mjs +2 -2
  259. package/src/runtime/computer-bridge/error-recovery.mjs +27 -2
  260. package/src/runtime/computer-bridge/linux-readiness.mjs +65 -23
  261. package/src/runtime/computer-bridge/pending-continuation.mjs +1 -1
  262. package/src/runtime/connectors/connector-accounts-mcp.mjs +230 -0
  263. package/src/runtime/connectors/gmail-app-password.mjs +172 -39
  264. package/src/runtime/connectors/google-mcp.mjs +10 -12
  265. package/src/runtime/memory/lib/pg/adapter.mjs +77 -70
  266. package/src/runtime/memory/lib/pg/supervisor.mjs +24 -12
  267. package/src/runtime/memory/lib/runtime-fetcher.mjs +23 -5
  268. package/src/runtime/office/com/com-adapter.mjs +17 -1
  269. package/src/runtime/office/com/office-com-host.ps1 +51 -1
  270. package/src/runtime/office/pdf/cfb-fixture.mjs +328 -0
  271. package/src/runtime/office/pdf/cfb-protection.mjs +311 -0
  272. package/src/runtime/office/pdf/document-preview.mjs +326 -8
  273. package/src/runtime/office/pdf/ooxml-crypto.mjs +469 -0
  274. package/src/runtime/office/pdf/ooxml-key-worker.mjs +14 -0
  275. package/src/runtime/office/portable/portable-soffice.mjs +45 -5
  276. package/src/runtime/session-search/session-search-db.mjs +73 -9
  277. package/src/runtime/session-search/session-search-ingest.mjs +110 -20
  278. package/src/runtime/shared/abort-race.mjs +89 -0
  279. package/src/runtime/shared/agent-avatar.d.mts +0 -1
  280. package/src/runtime/shared/agent-kind.mjs +4 -3
  281. package/src/runtime/shared/bot-hops.mjs +62 -2
  282. package/src/runtime/shared/bounded-download.mjs +8 -3
  283. package/src/runtime/shared/config.mjs +51 -35
  284. package/src/runtime/shared/connector-accounts.mjs +20 -10
  285. package/src/runtime/shared/connector-catalog.d.mts +33 -2
  286. package/src/runtime/shared/connector-catalog.mjs +166 -8
  287. package/src/runtime/shared/connector-triggers.mjs +22 -19
  288. package/src/runtime/shared/defaults-separation.mjs +2 -14
  289. package/src/runtime/shared/delegated-approval-host.mjs +103 -11
  290. package/src/runtime/shared/developer-options.mjs +32 -1
  291. package/src/runtime/shared/err-text.mjs +24 -15
  292. package/src/runtime/shared/file-permissions.d.mts +0 -8
  293. package/src/runtime/shared/file-permissions.mjs +0 -11
  294. package/src/runtime/shared/git-direct.test-support.mjs +19 -0
  295. package/src/runtime/shared/google-oauth-client.generated.json +4 -0
  296. package/src/runtime/shared/google-oauth-client.mjs +34 -9
  297. package/src/runtime/shared/heartbeat-interval.d.mts +4 -0
  298. package/src/runtime/shared/heartbeat-interval.mjs +19 -0
  299. package/src/runtime/shared/idle-auto-clear-gate.mjs +104 -0
  300. package/src/runtime/shared/llm/anthropic-betas.mjs +7 -0
  301. package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +12 -0
  302. package/src/runtime/shared/llm/provider-catalog-registry.mjs +1 -0
  303. package/src/runtime/shared/llm/usage-rollup.mjs +1 -1
  304. package/src/runtime/shared/llm/well-formed.mjs +66 -0
  305. package/src/runtime/shared/mixy-presence.mjs +22 -0
  306. package/src/runtime/shared/orchestration.d.mts +2 -0
  307. package/src/runtime/shared/pg-schema-db.mjs +27 -16
  308. package/src/runtime/shared/profile-config.cjs +1 -1
  309. package/src/runtime/shared/prompt-origin.d.mts +4 -0
  310. package/src/runtime/shared/prompt-origin.mjs +21 -0
  311. package/src/runtime/shared/provider-accounts.mjs +231 -59
  312. package/src/runtime/shared/provider-api-key.mjs +19 -8
  313. package/src/runtime/shared/provider-display-name.mjs +29 -0
  314. package/src/runtime/shared/provider-timeout-phase.mjs +2 -0
  315. package/src/runtime/shared/routine-trigger.d.mts +2 -0
  316. package/src/runtime/shared/routine-trigger.mjs +52 -0
  317. package/src/runtime/shared/sensitive-path-rules.d.mts +2 -10
  318. package/src/runtime/shared/sensitive-path-rules.mjs +19 -41
  319. package/src/runtime/shared/session-activity-signal.mjs +34 -1
  320. package/src/runtime/shared/session-sidebar-host.d.mts +1 -1
  321. package/src/runtime/shared/session-sidebar-host.mjs +7 -3
  322. package/src/runtime/shared/session-turn-prelude.mjs +18 -0
  323. package/src/runtime/shared/tool-execution-contract.mjs +2 -0
  324. package/src/runtime/shared/tool-result-summary.mjs +1 -14
  325. package/src/runtime/shared/transcript-metadata.mjs +38 -0
  326. package/src/runtime/shared/user-data-guard.mjs +30 -2
  327. package/src/runtime/shared/worktrees/activity-gate.mjs +12 -5
  328. package/src/runtime/shared/worktrees/test-support.mjs +9 -4
  329. package/src/runtime/terminal-bridge/client.mjs +10 -1
  330. package/src/runtime/visualize-bridge/client.mjs +27 -8
  331. package/src/runtime/visualize-bridge/tool-defs.mjs +22 -8
  332. package/src/runtime/visualize-bridge/tool-meta.d.mts +5 -0
  333. package/src/runtime/visualize-bridge/tool-meta.mjs +22 -0
  334. package/src/runtime/web-search/lib/fetch-pipeline.mjs +2 -2
  335. package/src/session-runtime/agent-home-delivery.mjs +486 -179
  336. package/src/session-runtime/agent-room-delivery.mjs +28 -1
  337. package/src/session-runtime/agent-task-sessions.mjs +561 -199
  338. package/src/session-runtime/boot/apis.mjs +9 -0
  339. package/src/session-runtime/boot/begin.mjs +3 -2
  340. package/src/session-runtime/boot/infrastructure.mjs +4 -0
  341. package/src/session-runtime/boot/providers.mjs +3 -0
  342. package/src/session-runtime/boot/tools.mjs +8 -1
  343. package/src/session-runtime/bot-chain.mjs +32 -0
  344. package/src/session-runtime/bot-status.mjs +7 -4
  345. package/src/session-runtime/channel-config-api.mjs +63 -17
  346. package/src/session-runtime/config-lifecycle.mjs +6 -0
  347. package/src/session-runtime/connector-merge-migration.mjs +305 -0
  348. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +132 -28
  349. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +2 -0
  350. package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +45 -0
  351. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +72 -5
  352. package/src/session-runtime/internal-tool-executor.mjs +4 -0
  353. package/src/session-runtime/lifecycle/inheritance.mjs +16 -3
  354. package/src/session-runtime/lifecycle/session-catalog.mjs +3 -1
  355. package/src/session-runtime/lifecycle/teardown/runtime-stops.mjs +50 -4
  356. package/src/session-runtime/lifecycle/teardown.mjs +23 -5
  357. package/src/session-runtime/mcp-glue/server-catalog.mjs +61 -3
  358. package/src/session-runtime/mcp-oauth-login.mjs +5 -3
  359. package/src/session-runtime/mixy-heartbeat.mjs +927 -0
  360. package/src/session-runtime/model-route/route-tuning.mjs +10 -6
  361. package/src/session-runtime/notification-bus.mjs +31 -117
  362. package/src/session-runtime/notification-completion-queue.mjs +54 -0
  363. package/src/session-runtime/notification-listeners.mjs +76 -0
  364. package/src/session-runtime/owned-automation-run.mjs +35 -13
  365. package/src/session-runtime/prewarm/channel-start.mjs +3 -7
  366. package/src/session-runtime/provider-auth/login.mjs +7 -0
  367. package/src/session-runtime/provider-models/row-hydration.mjs +13 -2
  368. package/src/session-runtime/resource-mcp-api.mjs +143 -224
  369. package/src/session-runtime/resource-mcp-connector-accounts.mjs +163 -0
  370. package/src/session-runtime/resource-mcp-oauth-api.mjs +217 -0
  371. package/src/session-runtime/resource-mcp-secrets.mjs +82 -0
  372. package/src/session-runtime/resource-mcp-servers.mjs +78 -0
  373. package/src/session-runtime/resource-mcp-store.mjs +49 -0
  374. package/src/session-runtime/routine-home-turn.mjs +33 -6
  375. package/src/session-runtime/routine-owner.mjs +40 -9
  376. package/src/session-runtime/schedule-session-run.mjs +7 -3
  377. package/src/session-runtime/services/agent-tool/close-flow.mjs +3 -0
  378. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +8 -12
  379. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +7 -2
  380. package/src/session-runtime/services/agent-tool/tag-registry.mjs +1 -0
  381. package/src/session-runtime/services/agent-tool/worker-index/awaiting-user-sync.mjs +51 -0
  382. package/src/session-runtime/services/agent-tool/worker-index/disposal.mjs +55 -0
  383. package/src/session-runtime/services/agent-tool/worker-index/released-store.mjs +123 -0
  384. package/src/session-runtime/services/agent-tool/worker-index/restart-settlement.mjs +112 -0
  385. package/src/session-runtime/services/agent-tool/worker-index/row-reads.mjs +36 -0
  386. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -0
  387. package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +17 -4
  388. package/src/session-runtime/services/agent-tool/worker-index/row-writes.mjs +58 -0
  389. package/src/session-runtime/services/agent-tool/worker-index.mjs +41 -241
  390. package/src/session-runtime/services/agent-tool/worker-rows.mjs +3 -0
  391. package/src/session-runtime/services/channel-admin.mjs +73 -31
  392. package/src/session-runtime/services/channel-session-router.mjs +4 -2
  393. package/src/session-runtime/services/channel-worker.mjs +1 -1
  394. package/src/session-runtime/services/provider-admin.mjs +128 -25
  395. package/src/session-runtime/services/schedule-access.mjs +14 -0
  396. package/src/session-runtime/services/seeds.mjs +0 -28
  397. package/src/session-runtime/services/usage-dashboard-model.mjs +1 -0
  398. package/src/session-runtime/session-compaction-sync.mjs +27 -0
  399. package/src/session-runtime/session-create/session-options.mjs +8 -4
  400. package/src/session-runtime/settings-builtin-tools-api.mjs +11 -0
  401. package/src/session-runtime/settings-compaction-api.mjs +2 -40
  402. package/src/session-runtime/settings-system-api.mjs +28 -1
  403. package/src/session-runtime/setup-tool/agent-settings-actions.mjs +65 -0
  404. package/src/session-runtime/setup-tool/executor.mjs +187 -29
  405. package/src/session-runtime/setup-tool/extended-actions.mjs +33 -2
  406. package/src/session-runtime/setup-tool/session-route-actions.mjs +152 -0
  407. package/src/session-runtime/setup-tool/settings-contract.mjs +68 -6
  408. package/src/session-runtime/setup-tool/tool-defs.mjs +92 -6
  409. package/src/session-runtime/task-session-route.mjs +99 -0
  410. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +17 -13
  411. package/src/session-runtime/tool-policy/policy-stage.mjs +21 -4
  412. package/src/session-runtime/tool-policy/surface-helpers.mjs +2 -3
  413. package/src/session-runtime/tool-surface.mjs +4 -0
  414. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
  415. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +98 -21
  416. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +4 -0
  417. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -1
  418. package/src/session-runtime/workspace/transitions.mjs +10 -2
  419. package/src/standalone/channel-transport/client-registry/client-records.mjs +65 -0
  420. package/src/standalone/channel-transport/client-registry/client-registration.mjs +118 -0
  421. package/src/standalone/channel-transport/client-registry.mjs +24 -163
  422. package/src/standalone/channel-transport.mjs +1 -1
  423. package/src/standalone/daemon-channels-loader.mjs +10 -8
  424. package/src/standalone/daemon-session-control/list-page.mjs +72 -0
  425. package/src/standalone/daemon-session-control/request.mjs +51 -0
  426. package/src/standalone/daemon-session-control/room-host.mjs +57 -0
  427. package/src/standalone/daemon-session-control/route-control.mjs +48 -0
  428. package/src/standalone/daemon-session-control/send-turn.mjs +60 -0
  429. package/src/standalone/daemon-session-control/worktree-control.mjs +40 -0
  430. package/src/standalone/daemon-session-control.mjs +161 -157
  431. package/src/standalone/daemon.mjs +33 -2
  432. package/src/standalone/session-client.mjs +7 -3
  433. package/src/standalone/session-protocol.mjs +10 -0
  434. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +6 -2
  435. package/src/standalone/session-service/agent-tree/agent-child.mjs +1 -0
  436. package/src/standalone/session-service/quota-resume.mjs +3 -0
  437. package/src/standalone/session-service/retention/busy.mjs +65 -1
  438. package/src/standalone/session-service/session-calls/session-action.mjs +23 -2
  439. package/src/standalone/session-service/session-calls/session-turns.mjs +6 -0
  440. package/src/standalone/session-service/session-calls/session-views.mjs +8 -0
  441. package/src/standalone/session-service.mjs +13 -0
  442. package/src/standalone/session-tool-output.mjs +21 -10
  443. package/src/tui/app/model-options.mjs +4 -27
  444. package/src/tui/dist/index.mjs +81 -65
  445. package/src/tui/session/agent-job-feed/model-notification.mjs +3 -0
  446. package/src/tui/session/initial-state.mjs +3 -0
  447. package/src/tui/session/notice-surface.mjs +23 -1
  448. package/src/tui/session/queue-helpers.mjs +3 -42
  449. package/src/tui/session/session-api/integrations.mjs +1 -0
  450. package/src/tui/session/session-api/lifecycle.mjs +8 -0
  451. package/src/tui/session/session-api/routes-agent-definitions.mjs +14 -2
  452. package/src/tui/session/session-api/routes-write.mjs +5 -1
  453. package/src/tui/session/session-api/routes.mjs +1 -0
  454. package/src/tui/session/session-api/settings.mjs +30 -2
  455. package/src/tui/session/session-flow/auto-clear.mjs +4 -33
  456. package/src/tui/session/session-flow/drain-closed-queue.mjs +38 -0
  457. package/src/tui/session/session-flow/drain-enqueue.mjs +86 -0
  458. package/src/tui/session/session-flow/drain-retry.mjs +35 -0
  459. package/src/tui/session/session-flow/drain.mjs +118 -149
  460. package/src/tui/session/session-flow/queue/entry-shape.mjs +5 -0
  461. package/src/tui/session/session-flow/queue/take-entries.mjs +5 -2
  462. package/src/tui/session/tool-approval-delegation.mjs +64 -0
  463. package/src/tui/session/tool-approval-request.mjs +39 -0
  464. package/src/tui/session/tool-approval-settle.mjs +62 -0
  465. package/src/tui/session/tool-approval.mjs +69 -115
  466. package/src/tui/session/tui-steering-persist.mjs +8 -1
  467. package/src/tui/session/turn-stream.mjs +36 -3
  468. package/src/tui/session/turn.mjs +23 -2
  469. package/src/vendor/statusline/bin/statusline-route.mjs +1 -219
  470. package/src/vendor/statusline/src/gateway/route-meta.mjs +35 -13
  471. package/scripts/code-graph-description-contract.mjs +0 -188
  472. package/src/runtime/channels/lib/owned-runtime/ownership-refresh.mjs +0 -36
@@ -1,6 +1,6 @@
1
1
  import { normalizeMcpTransportUrl } from './client-config.mjs';
2
2
  import { withAccountParams } from './oauth-account.mjs';
3
- import { loginFailed, reauthenticationRequired } from './oauth-shared.mjs';
3
+ import { loginFailed, missingScopes, reauthenticationRequired, scopeNotGranted } from './oauth-shared.mjs';
4
4
 
5
5
  /**
6
6
  * The SDK's OAuth client provider over one connection's credential `cache`
@@ -60,7 +60,17 @@ export function createOAuthProvider({ cache, flow, lifetimeSignal, client = null
60
60
  return creds.tokens;
61
61
  },
62
62
  saveTokens: async (value) => {
63
- lifetimeSignal.throwIfAborted();
63
+ // The login's own deadline/cancel signal (oauth-login.mjs) when one runs.
64
+ const signal = flow.signal ?? lifetimeSignal;
65
+ signal.throwIfAborted();
66
+ // A browser sign-in of a client with its own scopes must be granted all
67
+ // of them (a granular consent screen lets the user uncheck some): nothing
68
+ // is stored, so the next connect asks again. A refresh is not checked.
69
+ if (flow.browserOpened && client?.scopes) {
70
+ const missing = missingScopes(client.scopes, value.scope);
71
+ if (missing.length) throw scopeNotGranted(missing);
72
+ }
73
+ const { tokens: priorTokens, identity: priorIdentity } = creds;
64
74
  await save(() => {
65
75
  creds.tokens = value;
66
76
  // A browser sign-in may be another account: its identity replaces the
@@ -69,6 +79,15 @@ export function createOAuthProvider({ cache, flow, lifetimeSignal, client = null
69
79
  if (flow.browserOpened) creds.identity = flow.identity ?? null;
70
80
  else if (flow.identity) creds.identity = flow.identity;
71
81
  });
82
+ // The login was reported failed while this write was outstanding: roll
83
+ // it back (same save path) so no tokens outlive the reported failure.
84
+ if (signal.aborted) {
85
+ await save(() => {
86
+ creds.tokens = priorTokens;
87
+ creds.identity = priorIdentity;
88
+ });
89
+ throw loginFailed();
90
+ }
72
91
  },
73
92
  saveCodeVerifier: (value) => {
74
93
  flow.verifier = value;
@@ -5,5 +5,28 @@ export const reauthenticationRequired = () =>
5
5
  });
6
6
  export const loginFailed = () => new Error('MCP OAuth login failed, cancelled, or timed out');
7
7
 
8
+ // A sign-in whose token response lacks a requested scope (a provider's
9
+ // granular consent let the user leave a box unchecked). The message names only
10
+ // the scopes, never token data; callers match on the marker, which survives
11
+ // the wrapping transports and connect paths apply to the message.
12
+ export const SCOPE_NOT_GRANTED_MARKER = 'MCP OAuth scopeNotGranted';
13
+ export const scopeNotGranted = (missing) =>
14
+ Object.assign(new Error(`${SCOPE_NOT_GRANTED_MARKER}: access not granted for ${missing.join(' ')}`), {
15
+ code: 'scopeNotGranted',
16
+ missingScopes: [...missing],
17
+ });
18
+
19
+ // The basic identity scopes are never checked: Google answers a requested
20
+ // `email` with its userinfo.email URL (likewise `profile`).
21
+ const BASIC_SCOPES = new Set(['openid', 'email', 'profile']);
22
+
23
+ /** The requested non-basic scopes a token response's `scope` leaves out. A
24
+ * response without a `scope` field grants what was requested (RFC 6749). */
25
+ export function missingScopes(requested, granted) {
26
+ if (typeof granted !== 'string') return [];
27
+ const have = new Set(granted.split(/\s+/).filter(Boolean));
28
+ return (requested || []).filter((scope) => !BASIC_SCOPES.has(scope) && !have.has(scope));
29
+ }
30
+
8
31
  // `signal`, also aborted by the caller's optional `other` signal.
9
32
  export const anySignal = (signal, other) => AbortSignal.any(other ? [signal, other] : [signal]);
@@ -21,7 +21,7 @@ import {
21
21
  import { createLoginFlow } from './oauth-login.mjs';
22
22
  import { createOAuthProvider } from './oauth-provider.mjs';
23
23
  import { createAuthorizedFetch } from './oauth-request.mjs';
24
- import { REAUTHENTICATION_REQUIRED_MESSAGE, anySignal } from './oauth-shared.mjs';
24
+ import { REAUTHENTICATION_REQUIRED_MESSAGE, SCOPE_NOT_GRANTED_MARKER, anySignal } from './oauth-shared.mjs';
25
25
 
26
26
  export { MCP_OAUTH_CALLBACK_MAX_BYTES, MCP_OAUTH_CALLBACK_NOT_PENDING, parsePastedMcpOAuthCallback };
27
27
 
@@ -40,6 +40,16 @@ export function isMcpOAuthReauthenticationRequired(error) {
40
40
  return false;
41
41
  }
42
42
 
43
+ /** Whether a sign-in failed because the provider did not grant a requested
44
+ * scope (a real failure with its own message, not a plain "sign in"). */
45
+ export function isMcpOAuthScopeNotGranted(error) {
46
+ for (let current = error, depth = 0; current && depth < 5; current = current.cause, depth++) {
47
+ if (current.code === 'scopeNotGranted') return true;
48
+ if (String(current.message ?? current).includes(SCOPE_NOT_GRANTED_MARKER)) return true;
49
+ }
50
+ return false;
51
+ }
52
+
43
53
  // One keychain entry per exact server URL. Windows stores each account as a
44
54
  // file name, so the account is a hash of the URL and nothing else.
45
55
  export function mcpOAuthAccount(url) {
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  ACCOUNT_PROVIDERS,
3
+ MAX_ACCOUNTS,
3
4
  accountScopedSessionId,
4
5
  readProviderAccountPool,
5
6
  chooseProviderAccount,
@@ -9,11 +10,14 @@ import {
9
10
  providerAccountExhaustedForModelOnly,
10
11
  providerAccountPolicy,
11
12
  providerAccountsResetAt,
13
+ providerAccountWithBlocks,
14
+ providerAccountBlockedUntil,
15
+ staleQuotaWindows,
12
16
  } from '../../../shared/provider-accounts.mjs';
13
17
  import { withProviderAccount, hasExplicitProviderAuthBinding } from '../../../shared/provider-auth-binding.mjs';
14
18
  import { isExplicitUserAbortError, markProviderRecoveryExhausted } from './retry-classifier.mjs';
15
19
  import { sleepWithAbort } from './retry-backoff.mjs';
16
- import { QUOTA_KINDS, providerErrorKind } from './provider-error-kind.mjs';
20
+ import { QUOTA_KINDS, boundedCauseChain, providerErrorKind } from './provider-error-kind.mjs';
17
21
  import { fetchOAuthUsageSnapshot, readCachedOAuthUsageSnapshot } from './oauth-usage.mjs';
18
22
 
19
23
  // Only an explicit quota / plan / billing refusal advances the roster as
@@ -124,15 +128,18 @@ function accountsExhaustedError(useCredits) {
124
128
  // evidence): a rate limit, advising the earliest cooldown end as the wait.
125
129
  // The pool already spent its cooldown wait, so this is a terminal verdict for
126
130
  // the loop, not an unclassified failure.
127
- function earliestCooldownMs(rows, now) {
128
- const ends = rows.map((row) => Number(row.blockedUntil)).filter((until) => Number.isFinite(until) && until > now);
131
+ // A row's cooldown for `model` ends when every block holding that model lifts.
132
+ const coolingUntil = (row, model) => providerAccountBlockedUntil(row, model) ?? 0;
133
+
134
+ function earliestCooldownMs(rows, now, model) {
135
+ const ends = rows.map((row) => coolingUntil(row, model)).filter((until) => until > now);
129
136
  return ends.length ? Math.min(...ends) - now : null;
130
137
  }
131
138
 
132
- function accountsCoolingDownError(rows, now) {
139
+ function accountsCoolingDownError(rows, now, model) {
133
140
  const error = new Error('Every connected account is cooling down after a rate limit. Try again shortly.');
134
141
  error.code = 'rate_limited';
135
- const wait = earliestCooldownMs(rows, now);
142
+ const wait = earliestCooldownMs(rows, now, model);
136
143
  if (wait != null) error.retryAfterMs = wait;
137
144
  return markProviderRecoveryExhausted(error, { owner: 'account-pool', terminal: true });
138
145
  }
@@ -258,6 +265,36 @@ function recordServingAccount(providerName, result, id, bindingId) {
258
265
  };
259
266
  }
260
267
 
268
+ // A block covers the quota bucket of the model that failed, which only a usage
269
+ // reading names. An account without a recorded reading is measured once before
270
+ // it is blocked — once per account for this process, whatever the outcome,
271
+ // through the ordinary cache (a fresh failed reading is not repeated) — and
272
+ // again only after a reading has recorded windows and they were dropped.
273
+ // Without a reading the block stays account-wide.
274
+ const measureAttempted = new Set();
275
+
276
+ async function measureOnce(providerName, row, account) {
277
+ const key = `${providerName}:${row.id}`;
278
+ if (row.usage?.windows?.length) {
279
+ measureAttempted.delete(key);
280
+ return;
281
+ }
282
+ if (measureAttempted.has(key)) return;
283
+ measureAttempted.add(key);
284
+ await fetchOAuthUsageSnapshot({ provider: providerName, accountId: row.id }, account(row.id), () => {}).catch(
285
+ () => null
286
+ );
287
+ }
288
+
289
+ // The server's id of the limit a refusal names (a Codex model-specific limit),
290
+ // stamped on the error by the provider boundary.
291
+ function quotaLimitOf(error) {
292
+ for (const item of boundedCauseChain(error)) {
293
+ if (typeof item?.quotaLimit === 'string' && item.quotaLimit) return item.quotaLimit;
294
+ }
295
+ return '';
296
+ }
297
+
261
298
  // Whether the failed account's quota window is exhausted, plus the server's
262
299
  // retry delay. Anthropic and other subscriptions may use the same 429 code
263
300
  // for burst throttling and exhausted subscription windows, so the native
@@ -272,7 +309,7 @@ async function accountQuotaExhausted(providerName, row, model, error, account) {
272
309
  const policy = cooldownPolicyOf(account(row.id));
273
310
  const floorMs = policy ? finiteMs(policy.floorMs, 0) : 0;
274
311
  const blockUntil = (withReset, reason = 'unknown') => {
275
- if (withReset && resetAt > now) return { until: resetAt, source: 'retry-after' };
312
+ if (withReset && resetAt > now) return { until: resetAt, source: 'retry-after', reset: true };
276
313
  if (delay > 0) return { until: now + Math.max(delay, floorMs), source: 'retry-after' };
277
314
  if (policy) {
278
315
  const ms = policyCooldownMs(policy, `${providerName}:${row.id}`, reason);
@@ -298,10 +335,14 @@ async function accountQuotaExhausted(providerName, row, model, error, account) {
298
335
  };
299
336
  }
300
337
  const typed = isAccountQuotaError(error);
301
- if (typed) return { exhausted: true, delay, evidence: true, ...blockUntil(true, 'quota') };
338
+ if (typed) {
339
+ await measureOnce(providerName, row, account);
340
+ return { exhausted: true, delay, evidence: true, ...blockUntil(true, 'quota') };
341
+ }
302
342
  // A policy with rotateOnRateLimit rotates on a plain rate limit too (the
303
343
  // advice as its cooldown, else rateLimitedMs); never quota evidence.
304
344
  if (policy?.rotateOnRateLimit === true && kind === 'rate_limited' && status === 429) {
345
+ await measureOnce(providerName, row, account);
305
346
  return { exhausted: true, delay, evidence: false, ...blockUntil(false, 'rate_limited') };
306
347
  }
307
348
  let exhausted = status === 429 && delay > QUOTA_WINDOW_RETRY_AFTER_MS;
@@ -315,111 +356,185 @@ async function accountQuotaExhausted(providerName, row, model, error, account) {
315
356
  const pool = readProviderAccountPool(providerName);
316
357
  const entry = pool.accounts.find((item) => item.id === row.id);
317
358
  const options = { useCredits: pool.useCredits === true };
318
- evidence = providerAccountExhausted({ ...entry, blockedUntil: 0 }, Date.now(), model, options);
359
+ evidence = providerAccountExhausted(providerAccountWithBlocks(entry), Date.now(), model, options);
319
360
  exhausted = exhausted || evidence || providerAccountExhausted(entry, Date.now(), model, options);
320
361
  }
321
- return { exhausted, delay, evidence, ...(exhausted ? blockUntil(evidence, evidence ? 'quota' : 'unknown') : {}) };
362
+ const reason = evidence ? 'quota' : 'unknown';
363
+ return { exhausted, delay, evidence, ...(exhausted ? blockUntil(evidence, reason) : {}) };
364
+ }
365
+
366
+ // Record an exhausted verdict on the roster. An account-level block holds
367
+ // every model, so it carries no model or limit scope.
368
+ function blockRefusedAccount(providerName, accountId, model, error, verdict) {
369
+ const { evidence, until, source, reset, accountBlocked } = verdict;
370
+ return blockProviderAccount(providerName, accountId, until, accountBlocked ? '' : model, source, {
371
+ quota: evidence === true,
372
+ reset: reset === true,
373
+ limit: accountBlocked ? '' : quotaLimitOf(error),
374
+ });
375
+ }
376
+
377
+ /**
378
+ * A refusal `error` that account `accountId` gave outside the pool's send (a
379
+ * cache warm, which never rotates): judged and recorded on the roster exactly
380
+ * as the send would, with `instance` as that account's provider. Resolves
381
+ * whether the account was blocked.
382
+ */
383
+ export async function recordAccountRefusal(providerName, accountId, model, error, instance) {
384
+ const row = readProviderAccountPool(providerName).accounts.find((entry) => entry.id === accountId);
385
+ if (!row) return false;
386
+ const verdict = await accountQuotaExhausted(providerName, row, model, error, () => instance);
387
+ if (!verdict.exhausted) return false;
388
+ await blockRefusedAccount(providerName, row.id, model, error, verdict);
389
+ return true;
390
+ }
391
+
392
+ // Whether every account in `ids` (default: the roster) is exhausted with quota
393
+ // evidence: observed in this send (`evidenced`), or a full window.
394
+ function rosterExhaustion(providerName, model, evidenced, ids) {
395
+ const pool = readProviderAccountPool(providerName);
396
+ // Manual policy: the eligible set is the selected account alone.
397
+ const scope = ids || (providerAccountPolicy(pool) === 'manual' ? [pool.selectedId] : null);
398
+ const rows = scope ? pool.accounts.filter((entry) => scope.includes(entry.id)) : pool.accounts;
399
+ const opts = { useCredits: pool.useCredits === true };
400
+ const now = Date.now();
401
+ const all =
402
+ rows.length > 0 &&
403
+ rows.every((entry) =>
404
+ providerAccountExhausted(
405
+ evidenced.has(entry.id) ? entry : providerAccountWithBlocks(entry, (block) => block.blockedQuota === true),
406
+ now,
407
+ model,
408
+ opts
409
+ )
410
+ );
411
+ return { rows, opts, now, all };
412
+ }
413
+
414
+ // Mark `error` only when every account in `ids` is exhausted with quota
415
+ // evidence (`rosterExhaustion`). Without an error of its own, the send reports
416
+ // quota exhaustion only on that evidence, and a rate-limit cooldown otherwise.
417
+ function exhaustedSendError(providerName, model, evidenced, error, ids) {
418
+ const { rows, opts, now, all } = rosterExhaustion(providerName, model, evidenced, ids);
419
+ if (all) {
420
+ return quotaExhausted(error || accountsExhaustedError(opts.useCredits), providerName, model, rows, opts);
421
+ }
422
+ return error || accountsCoolingDownError(rows, now, model);
423
+ }
424
+
425
+ // The provider's bound on the time one send spends waiting for cooldowns.
426
+ function cooldownWaitBoundMs(providerName, account, ids) {
427
+ const id = ids?.[0] || readProviderAccountPool(providerName).selectedId || 'default';
428
+ return finiteMs(cooldownPolicyOf(account(id))?.waitForCooldownMs, 0);
429
+ }
430
+
431
+ // A cooldown-only eligible set whose earliest cooldown ends inside the
432
+ // remaining wait budget is waited out; otherwise the send fails as before.
433
+ // `ledger` is the send's own record of the roster (see `poolSend`).
434
+ async function waitOutCooldownOrThrow(request, ledger, error, ids) {
435
+ const { providerName, model, account, options } = request;
436
+ const { rows, now, all } = rosterExhaustion(providerName, model, ledger.evidenced, ids);
437
+ const bound = cooldownWaitBoundMs(providerName, account, ids);
438
+ // Persisting a short cooldown can outlast its deadline. Release only
439
+ // attempts cooled by this send, and only under an enabled wait policy.
440
+ const expired = bound > 0 && rows.some((row) => ledger.cooled.has(row.id) && !(coolingUntil(row, model) > now));
441
+ let wait = null;
442
+ if (!all) wait = expired ? 0 : earliestCooldownMs(rows, now, model);
443
+ if (wait == null || ledger.waitedMs + wait > bound) {
444
+ throw exhaustedSendError(providerName, model, ledger.evidenced, error, ids);
445
+ }
446
+ options.onStageChange?.('reconnecting', {
447
+ message: `Rate limited — every account is cooling down; retrying in ${Math.ceil(wait / 1000)}s`,
448
+ reason: 'rate_limited',
449
+ waitMs: wait,
450
+ retryAt: now + wait,
451
+ });
452
+ // A zero-duration sleep does not check cancellation, including on the
453
+ // final allowed send when no next loop iteration will check it either.
454
+ options.signal?.throwIfAborted();
455
+ await sleepWithAbort(wait, options.signal);
456
+ ledger.waitedMs += wait;
457
+ const lifted = Date.now();
458
+ for (const row of readProviderAccountPool(providerName).accounts) {
459
+ if (ledger.cooled.has(row.id) && !(coolingUntil(row, model) > lifted)) {
460
+ ledger.cooled.delete(row.id);
461
+ ledger.attempted.delete(row.id);
462
+ }
463
+ }
464
+ }
465
+
466
+ // No roster: with credit use off, a stored reading showing the plan's window
467
+ // spent refuses before the implicit account sends.
468
+ function throwIfPlanQuotaSpent(providerName, pool, model) {
469
+ if (pool.useCredits !== true) {
470
+ const stored = readCachedOAuthUsageSnapshot({ provider: providerName, model: '' });
471
+ // A snapshot cached before per-model gating is stale evidence.
472
+ if (stored?.quotaWindows?.length && !staleQuotaWindows(providerName, stored.quotaWindows)) {
473
+ const row = { usage: { windows: stored.quotaWindows, checkedAt: stored.cachedAt } };
474
+ if (providerAccountExhausted(row, Date.now(), model)) {
475
+ throw quotaExhausted(accountsExhaustedError(false), providerName, model, [row]);
476
+ }
477
+ }
478
+ }
479
+ }
480
+
481
+ // Headroom policy: a session's first pick, or its move off its account on a
482
+ // limit, becomes the selection; continuing on its account does not, so
483
+ // concurrent sessions never flip the selection back and forth. A move forced
484
+ // by a model-scoped limit serves this request only (`scoped`). Stamps the
485
+ // serving account on `result` and resolves whether the selection may commit.
486
+ function bindServedAccount(providerName, result, rowId, model, binding) {
487
+ const { policy, boundId, sessionKey, switched, refusalScoped } = binding;
488
+ const leftBound = Boolean(boundId) && rowId !== boundId;
489
+ const scoped = switched
490
+ ? refusalScoped
491
+ : policy === 'headroom' && leftBound && accountExhaustedForModelOnly(providerName, boundId, model);
492
+ recordServingAccount(providerName, result, rowId, scoped && boundId ? boundId : rowId);
493
+ if (sessionKey && policy === 'headroom' && !scoped) rememberSessionAccount(sessionKey, rowId);
494
+ return { scoped, commit: policy !== 'headroom' || !boundId || leftBound };
495
+ }
496
+
497
+ // A failed attempt is never re-issued once output reached the caller or the
498
+ // attempt was unsafe to retry (`unsafe`, judged by the caller and read only
499
+ // when nothing was emitted).
500
+ function attemptUnretryable(bound, error, unsafe) {
501
+ return Boolean(bound.emitted() || unsafe() || error.liveTextEmitted || error.emittedToolCall);
502
+ }
503
+
504
+ function rotationReason(evidence, accountBlocked) {
505
+ if (evidence) return 'Quota exhausted';
506
+ if (accountBlocked) return 'Account blocked';
507
+ return 'Rate limited (cooldown)';
322
508
  }
323
509
 
324
510
  // The pool's send: walks the eligible accounts until one answers, blocking
325
511
  // the ones whose quota window is exhausted along the way.
326
512
  function poolSend(providerName, account) {
327
513
  return async function send(messages, model, tools, options = {}) {
328
- const attempted = new Set();
514
+ const request = { providerName, model, account, options };
515
+ // This send's record of the roster: the accounts it tried, those it found
516
+ // exhausted with quota evidence, those it blocked on a rate-limit
517
+ // cooldown, and the time it spent waiting for cooldowns.
518
+ const ledger = { attempted: new Set(), evidenced: new Set(), cooled: new Set(), waitedMs: 0 };
329
519
  let lastError;
330
520
  let probed = false;
331
- const evidenced = new Set();
521
+ let sends = 0;
332
522
  const sessionKey = options.sessionId ? `${providerName}:${options.sessionId}` : null;
333
523
  const boundId = sessionAccountId(providerName, sessionKey, messages, options);
334
524
  // Whether this send already left an account, and whether the latest quota
335
525
  // refusal held only for this model's family.
336
526
  let switched = false;
337
527
  let refusalScoped = false;
338
- // Mark `error` only when every account in `ids` (default: the roster) is
339
- // exhausted with quota evidence: observed in this send, or a full window.
340
- // Without an error of its own, the send reports quota exhaustion only on
341
- // that evidence, and a rate-limit cooldown otherwise.
342
- const exhaustion = (ids) => {
343
- const pool = readProviderAccountPool(providerName);
344
- // Manual policy: the eligible set is the selected account alone.
345
- const scope = ids || (providerAccountPolicy(pool) === 'manual' ? [pool.selectedId] : null);
346
- const rows = scope ? pool.accounts.filter((entry) => scope.includes(entry.id)) : pool.accounts;
347
- const opts = { useCredits: pool.useCredits === true };
348
- const now = Date.now();
349
- const all =
350
- rows.length > 0 &&
351
- rows.every((entry) =>
352
- providerAccountExhausted(
353
- evidenced.has(entry.id) || entry.blockedQuota === true ? entry : { ...entry, blockedUntil: 0 },
354
- now,
355
- model,
356
- opts
357
- )
358
- );
359
- return { rows, opts, now, all };
360
- };
361
- const markExhausted = (error, ids) => {
362
- const { rows, opts, now, all } = exhaustion(ids);
363
- if (all) {
364
- return quotaExhausted(error || accountsExhaustedError(opts.useCredits), providerName, model, rows, opts);
365
- }
366
- return error || accountsCoolingDownError(rows, now);
367
- };
368
- // Accounts this send blocked on a rate-limit cooldown, the time it spent
369
- // waiting for cooldowns, and the provider's wait bound.
370
- const cooled = new Set();
371
- let waitedMs = 0;
372
- let sends = 0;
373
- const waitBoundMs = (ids) => {
374
- const id = ids?.[0] || readProviderAccountPool(providerName).selectedId || 'default';
375
- return finiteMs(cooldownPolicyOf(account(id))?.waitForCooldownMs, 0);
376
- };
377
- // A cooldown-only eligible set whose earliest cooldown ends inside the
378
- // remaining wait budget is waited out; otherwise the send fails as before.
379
- const waitOrThrow = async (error, ids) => {
380
- const { rows, now, all } = exhaustion(ids);
381
- const bound = waitBoundMs(ids);
382
- // Persisting a short cooldown can outlast its deadline. Release only
383
- // attempts cooled by this send, and only under an enabled wait policy.
384
- const expired = bound > 0 && rows.some((row) => cooled.has(row.id) && !(Number(row.blockedUntil) > now));
385
- const wait = all ? null : expired ? 0 : earliestCooldownMs(rows, now);
386
- if (wait == null || waitedMs + wait > bound) throw markExhausted(error, ids);
387
- options.onStageChange?.('reconnecting', {
388
- message: `Rate limited — every account is cooling down; retrying in ${Math.ceil(wait / 1000)}s`,
389
- reason: 'rate_limited',
390
- waitMs: wait,
391
- retryAt: now + wait,
392
- });
393
- // A zero-duration sleep does not check cancellation, including on the
394
- // final allowed send when no next loop iteration will check it either.
395
- options.signal?.throwIfAborted();
396
- await sleepWithAbort(wait, options.signal);
397
- waitedMs += wait;
398
- const lifted = Date.now();
399
- for (const row of readProviderAccountPool(providerName).accounts) {
400
- if (cooled.has(row.id) && !(Number(row.blockedUntil) > lifted)) {
401
- cooled.delete(row.id);
402
- attempted.delete(row.id);
403
- }
404
- }
405
- };
406
- while (attempted.size < 20 && sends < 20) {
528
+ // One try per connectable account at most (provider-accounts.mjs MAX_ACCOUNTS).
529
+ while (ledger.attempted.size < MAX_ACCOUNTS && sends < MAX_ACCOUNTS) {
407
530
  options.signal?.throwIfAborted();
408
531
  const pool = readProviderAccountPool(providerName);
409
532
  if (!pool.accounts.length) {
410
- if (pool.useCredits !== true) {
411
- const stored = readCachedOAuthUsageSnapshot({ provider: providerName, model: '' });
412
- if (stored?.quotaWindows?.length) {
413
- const row = { usage: { windows: stored.quotaWindows, checkedAt: stored.cachedAt } };
414
- if (providerAccountExhausted(row, Date.now(), model)) {
415
- throw quotaExhausted(accountsExhaustedError(false), providerName, model, [row]);
416
- }
417
- }
418
- }
533
+ throwIfPlanQuotaSpent(providerName, pool, model);
419
534
  return account('default').send(messages, model, tools, options);
420
535
  }
421
536
  const policy = providerAccountPolicy(pool);
422
- const row = chooseProviderAccount(pool, attempted, Date.now(), model, { sessionAccountId: boundId });
537
+ const row = chooseProviderAccount(pool, ledger.attempted, Date.now(), model, { sessionAccountId: boundId });
423
538
  if (!row) {
424
539
  // Once per send: a refusal built on a stale meter must not outlive the
425
540
  // meter. Re-measuring can only widen the roster, so it is tried before
@@ -428,26 +543,22 @@ function poolSend(providerName, account) {
428
543
  probed = true;
429
544
  if (await probeRefusedAccounts(providerName, pool, model, account)) continue;
430
545
  }
431
- await waitOrThrow(lastError);
546
+ await waitOutCooldownOrThrow(request, ledger, lastError);
432
547
  continue;
433
548
  }
434
- attempted.add(row.id);
549
+ ledger.attempted.add(row.id);
435
550
  sends += 1;
436
551
  const bound = boundAccountSend(providerName, row, messages, options);
437
552
  try {
438
553
  const result = await account(row.id).send(bound.history, model, tools, bound.opts);
439
554
  quotaStrikes.delete(`${providerName}:${row.id}`);
440
- // Headroom policy: a session's first pick, or its move off its account
441
- // on a limit, becomes the selection; continuing on its account does
442
- // not, so concurrent sessions never flip the selection back and forth.
443
- // A move forced by a model-scoped limit serves this request only.
444
- const leftBound = Boolean(boundId) && row.id !== boundId;
445
- const scoped = switched
446
- ? refusalScoped
447
- : policy === 'headroom' && leftBound && accountExhaustedForModelOnly(providerName, boundId, model);
448
- recordServingAccount(providerName, result, row.id, scoped && boundId ? boundId : row.id);
449
- if (sessionKey && policy === 'headroom' && !scoped) rememberSessionAccount(sessionKey, row.id);
450
- const commit = policy !== 'headroom' || !boundId || leftBound;
555
+ const { scoped, commit } = bindServedAccount(providerName, result, row.id, model, {
556
+ policy,
557
+ boundId,
558
+ sessionKey,
559
+ switched,
560
+ refusalScoped,
561
+ });
451
562
  if (commit && (await commitFallbackAccount(providerName, pool, row, model, scoped, policy))) {
452
563
  // Publish only a committed selection, never a speculative retry or
453
564
  // one superseded by a manual switch. The existing progress channel
@@ -466,59 +577,41 @@ function poolSend(providerName, account) {
466
577
  } catch (error) {
467
578
  if (options.signal?.aborted) throw error;
468
579
  if (error?.reauthRequired === true) {
469
- if (
470
- policy === 'manual' ||
471
- bound.emitted() ||
472
- error.unsafeToRetry ||
473
- error.liveTextEmitted ||
474
- error.emittedToolCall
475
- )
476
- throw error;
580
+ if (policy === 'manual' || attemptUnretryable(bound, error, () => error.unsafeToRetry)) throw error;
477
581
  lastError = error;
478
582
  switched = true;
479
583
  refusalScoped = false;
480
584
  options.onStageChange?.('reconnecting', { message: 'Sign-in expired — switching to the next account' });
481
585
  continue;
482
586
  }
483
- const { exhausted, evidence, until, source, accountBlocked } = await accountQuotaExhausted(
484
- providerName,
485
- row,
486
- model,
487
- error,
488
- account
489
- );
587
+ const verdict = await accountQuotaExhausted(providerName, row, model, error, account);
588
+ const { exhausted, evidence, accountBlocked } = verdict;
490
589
  if (options.signal?.aborted || !exhausted) throw error;
491
- if (evidence) evidenced.add(row.id);
492
- else cooled.add(row.id);
493
- await blockProviderAccount(providerName, row.id, until, model, source, { quota: evidence === true });
590
+ if (evidence) ledger.evidenced.add(row.id);
591
+ else ledger.cooled.add(row.id);
592
+ await blockRefusedAccount(providerName, row.id, model, error, verdict);
494
593
  // An account block is unsafe to re-issue on THAT account only; with no
495
594
  // output emitted, the next account may serve the same request.
496
- if (
497
- bound.emitted() ||
498
- (error.unsafeToRetry && accountBlocked !== true) ||
499
- error.liveTextEmitted ||
500
- error.emittedToolCall
501
- )
502
- throw error;
595
+ if (attemptUnretryable(bound, error, () => error.unsafeToRetry && accountBlocked !== true)) throw error;
503
596
  // Manual policy: the eligible set is the selected account alone.
504
597
  if (policy === 'manual') {
505
- await waitOrThrow(error, [row.id]);
598
+ await waitOutCooldownOrThrow(request, ledger, error, [row.id]);
506
599
  lastError = error;
507
600
  continue;
508
601
  }
509
602
  lastError = error;
510
603
  switched = true;
511
604
  refusalScoped = accountExhaustedForModelOnly(providerName, row.id, model);
512
- options.onStageChange?.('reconnecting', {
513
- message: evidence
514
- ? 'Quota exhausted — switching to the next account'
515
- : accountBlocked
516
- ? 'Account blocked — switching to the next account'
517
- : 'Rate limited (cooldown) — switching to the next account',
518
- });
605
+ const reason = rotationReason(evidence, accountBlocked);
606
+ options.onStageChange?.('reconnecting', { message: `${reason} — switching to the next account` });
519
607
  }
520
608
  }
521
- throw markExhausted(lastError || new Error('Provider account attempts exhausted.'));
609
+ throw exhaustedSendError(
610
+ providerName,
611
+ model,
612
+ ledger.evidenced,
613
+ lastError || new Error('Provider account attempts exhausted.')
614
+ );
522
615
  };
523
616
  }
524
617