@poolzin/pool-bot 2026.2.23 → 2026.2.25

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 (235) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/acp/client.js +207 -18
  3. package/dist/acp/secret-file.js +22 -0
  4. package/dist/agents/agent-scope.js +10 -0
  5. package/dist/agents/bash-process-registry.test-helpers.js +29 -0
  6. package/dist/agents/bash-tools.exec-approval-request.js +20 -0
  7. package/dist/agents/bash-tools.exec-host-gateway.js +230 -0
  8. package/dist/agents/bash-tools.exec-host-node.js +235 -0
  9. package/dist/agents/bash-tools.exec-types.js +1 -0
  10. package/dist/agents/bash-tools.process.js +224 -218
  11. package/dist/agents/content-blocks.js +16 -0
  12. package/dist/agents/model-fallback.js +96 -101
  13. package/dist/agents/models-config.providers.js +299 -182
  14. package/dist/agents/pi-embedded-payloads.js +1 -0
  15. package/dist/agents/pi-embedded-runner/run.overflow-compaction.fixture.js +34 -0
  16. package/dist/agents/skills.test-helpers.js +13 -0
  17. package/dist/agents/stable-stringify.js +12 -0
  18. package/dist/agents/subagent-registry.mocks.shared.js +12 -0
  19. package/dist/agents/test-helpers/assistant-message-fixtures.js +29 -0
  20. package/dist/agents/test-helpers/pi-tools-sandbox-context.js +27 -0
  21. package/dist/agents/tool-policy-shared.js +108 -0
  22. package/dist/agents/tools/browser-tool.js +160 -54
  23. package/dist/agents/tools/cron-tool.test-helpers.js +12 -0
  24. package/dist/agents/tools/discord-actions-moderation-shared.js +27 -0
  25. package/dist/agents/tools/image-tool.js +214 -99
  26. package/dist/agents/tools/sessions-history-tool.js +140 -108
  27. package/dist/agents/workspace.js +222 -46
  28. package/dist/auto-reply/commands-registry.js +15 -18
  29. package/dist/auto-reply/fallback-state.js +114 -0
  30. package/dist/auto-reply/model-runtime.js +68 -0
  31. package/dist/auto-reply/reply/agent-runner-execution.js +36 -4
  32. package/dist/auto-reply/reply/agent-runner.js +165 -39
  33. package/dist/auto-reply/reply/commands-setunset-standard.js +13 -0
  34. package/dist/browser/config.js +26 -0
  35. package/dist/browser/navigation-guard.js +31 -0
  36. package/dist/browser/routes/agent.act.js +431 -424
  37. package/dist/browser/routes/agent.shared.js +47 -3
  38. package/dist/browser/routes/agent.snapshot.js +122 -116
  39. package/dist/browser/routes/agent.storage.js +303 -297
  40. package/dist/browser/routes/tabs.js +154 -100
  41. package/dist/browser/server-lifecycle.js +37 -0
  42. package/dist/build-info.json +3 -3
  43. package/dist/channels/allow-from.js +25 -0
  44. package/dist/channels/plugins/account-action-gate.js +13 -0
  45. package/dist/channels/plugins/message-actions.js +10 -0
  46. package/dist/channels/telegram/api.js +18 -0
  47. package/dist/cli/argv.js +84 -21
  48. package/dist/cli/banner.js +2 -1
  49. package/dist/cli/exec-approvals-cli.js +92 -124
  50. package/dist/cli/memory-cli.js +158 -61
  51. package/dist/cli/nodes-cli/register.push.js +63 -0
  52. package/dist/cli/nodes-media-utils.js +21 -0
  53. package/dist/cli/plugins-cli.js +245 -61
  54. package/dist/cli/program/build-program.js +3 -1
  55. package/dist/cli/program/command-registry.js +223 -136
  56. package/dist/cli/program/help.js +43 -12
  57. package/dist/cli/route.js +1 -1
  58. package/dist/cli/test-runtime-capture.js +24 -0
  59. package/dist/commands/agent.js +163 -87
  60. package/dist/commands/channels.mock-harness.js +23 -0
  61. package/dist/commands/daemon-install-runtime-warning.js +11 -0
  62. package/dist/commands/onboard-helpers.js +4 -4
  63. package/dist/commands/sessions.test-helpers.js +61 -0
  64. package/dist/compat/legacy-names.js +2 -2
  65. package/dist/config/commands.js +3 -0
  66. package/dist/config/config.js +1 -1
  67. package/dist/config/env-substitution.js +62 -34
  68. package/dist/config/env-vars.js +9 -0
  69. package/dist/config/io.js +571 -171
  70. package/dist/config/merge-patch.js +50 -4
  71. package/dist/config/redact-snapshot.js +404 -76
  72. package/dist/config/schema.js +58 -570
  73. package/dist/config/validation.js +140 -85
  74. package/dist/config/zod-schema.hooks.js +40 -11
  75. package/dist/config/zod-schema.installs.js +20 -0
  76. package/dist/config/zod-schema.js +8 -7
  77. package/dist/control-ui/assets/{index-HRr1grwl.js → index-Dvkl4Xlx.js} +2 -1
  78. package/dist/control-ui/assets/{index-HRr1grwl.js.map → index-Dvkl4Xlx.js.map} +1 -1
  79. package/dist/control-ui/index.html +1 -1
  80. package/dist/daemon/cmd-argv.js +21 -0
  81. package/dist/daemon/cmd-set.js +58 -0
  82. package/dist/daemon/service-types.js +1 -0
  83. package/dist/discord/monitor/exec-approvals.js +357 -162
  84. package/dist/gateway/auth.js +38 -3
  85. package/dist/gateway/call.js +149 -68
  86. package/dist/gateway/canvas-capability.js +75 -0
  87. package/dist/gateway/control-plane-audit.js +28 -0
  88. package/dist/gateway/control-plane-rate-limit.js +53 -0
  89. package/dist/gateway/events.js +1 -0
  90. package/dist/gateway/hooks.js +109 -54
  91. package/dist/gateway/http-common.js +22 -0
  92. package/dist/gateway/method-scopes.js +169 -0
  93. package/dist/gateway/net.js +23 -0
  94. package/dist/gateway/openresponses-http.js +120 -110
  95. package/dist/gateway/probe-auth.js +2 -0
  96. package/dist/gateway/protocol/index.js +3 -2
  97. package/dist/gateway/protocol/schema/protocol-schemas.js +2 -0
  98. package/dist/gateway/protocol/schema/push.js +18 -0
  99. package/dist/gateway/protocol/schema.js +1 -0
  100. package/dist/gateway/server-http.js +236 -52
  101. package/dist/gateway/server-methods/agent.js +162 -24
  102. package/dist/gateway/server-methods/chat.js +461 -130
  103. package/dist/gateway/server-methods/config.js +193 -150
  104. package/dist/gateway/server-methods/nodes.helpers.js +12 -0
  105. package/dist/gateway/server-methods/nodes.js +251 -69
  106. package/dist/gateway/server-methods/push.js +53 -0
  107. package/dist/gateway/server-reload-handlers.js +2 -3
  108. package/dist/gateway/server-runtime-config.js +5 -0
  109. package/dist/gateway/server-runtime-state.js +2 -0
  110. package/dist/gateway/server-ws-runtime.js +1 -0
  111. package/dist/gateway/server.impl.js +296 -139
  112. package/dist/gateway/session-preview.test-helpers.js +11 -0
  113. package/dist/gateway/startup-auth.js +126 -0
  114. package/dist/gateway/test-helpers.agent-results.js +15 -0
  115. package/dist/gateway/test-helpers.mocks.js +37 -14
  116. package/dist/gateway/test-helpers.server.js +161 -77
  117. package/dist/hooks/bundled/session-memory/handler.js +165 -34
  118. package/dist/hooks/gmail-watcher-lifecycle.js +23 -0
  119. package/dist/infra/archive-path.js +49 -0
  120. package/dist/infra/device-pairing.js +148 -167
  121. package/dist/infra/exec-approvals-allowlist.js +19 -70
  122. package/dist/infra/exec-approvals-analysis.js +44 -17
  123. package/dist/infra/exec-safe-bin-policy.js +269 -0
  124. package/dist/infra/fixed-window-rate-limit.js +33 -0
  125. package/dist/infra/git-root.js +61 -0
  126. package/dist/infra/heartbeat-active-hours.js +2 -2
  127. package/dist/infra/heartbeat-reason.js +40 -0
  128. package/dist/infra/heartbeat-runner.js +72 -32
  129. package/dist/infra/install-source-utils.js +91 -7
  130. package/dist/infra/node-pairing.js +50 -105
  131. package/dist/infra/npm-integrity.js +45 -0
  132. package/dist/infra/npm-pack-install.js +40 -0
  133. package/dist/infra/outbound/channel-adapters.js +20 -7
  134. package/dist/infra/outbound/message-action-runner.js +107 -327
  135. package/dist/infra/outbound/message.js +59 -36
  136. package/dist/infra/outbound/outbound-policy.js +52 -25
  137. package/dist/infra/outbound/outbound-send-service.js +58 -71
  138. package/dist/infra/pairing-files.js +10 -0
  139. package/dist/infra/plain-object.js +9 -0
  140. package/dist/infra/push-apns.js +365 -0
  141. package/dist/infra/restart-sentinel.js +16 -1
  142. package/dist/infra/restart.js +229 -26
  143. package/dist/infra/scp-host.js +54 -0
  144. package/dist/infra/update-startup.js +86 -9
  145. package/dist/media/inbound-path-policy.js +114 -0
  146. package/dist/media/input-files.js +16 -0
  147. package/dist/memory/test-manager.js +8 -0
  148. package/dist/plugin-sdk/temp-path.js +47 -0
  149. package/dist/plugins/discovery.js +217 -23
  150. package/dist/plugins/hook-runner-global.js +16 -0
  151. package/dist/plugins/loader.js +192 -26
  152. package/dist/plugins/logger.js +8 -0
  153. package/dist/plugins/manifest-registry.js +3 -0
  154. package/dist/plugins/path-safety.js +34 -0
  155. package/dist/plugins/registry.js +5 -2
  156. package/dist/plugins/runtime/index.js +271 -206
  157. package/dist/providers/github-copilot-models.js +4 -1
  158. package/dist/security/audit-channel.js +8 -19
  159. package/dist/security/audit-extra.async.js +354 -182
  160. package/dist/security/audit-extra.js +11 -1
  161. package/dist/security/audit-extra.sync.js +340 -33
  162. package/dist/security/audit-fs.js +31 -13
  163. package/dist/security/audit.js +145 -371
  164. package/dist/security/dm-policy-shared.js +24 -0
  165. package/dist/security/external-content.js +20 -8
  166. package/dist/security/fix.js +49 -85
  167. package/dist/security/scan-paths.js +20 -0
  168. package/dist/security/secret-equal.js +3 -7
  169. package/dist/security/windows-acl.js +30 -15
  170. package/dist/shared/node-list-parse.js +13 -0
  171. package/dist/shared/operator-scope-compat.js +37 -0
  172. package/dist/shared/text-chunking.js +29 -0
  173. package/dist/slack/blocks.test-helpers.js +31 -0
  174. package/dist/slack/monitor/mrkdwn.js +8 -0
  175. package/dist/telegram/bot-message-dispatch.js +366 -164
  176. package/dist/telegram/draft-stream.js +30 -7
  177. package/dist/telegram/reasoning-lane-coordinator.js +128 -0
  178. package/dist/terminal/prompt-select-styled.js +9 -0
  179. package/dist/test-utils/command-runner.js +6 -0
  180. package/dist/test-utils/internal-hook-event-payload.js +10 -0
  181. package/dist/test-utils/model-auth-mock.js +12 -0
  182. package/dist/test-utils/provider-usage-fetch.js +14 -0
  183. package/dist/test-utils/temp-home.js +33 -0
  184. package/dist/tui/components/chat-log.js +9 -0
  185. package/dist/tui/tui-command-handlers.js +36 -27
  186. package/dist/tui/tui-event-handlers.js +122 -32
  187. package/dist/tui/tui.js +181 -45
  188. package/dist/utils/mask-api-key.js +10 -0
  189. package/dist/utils/run-with-concurrency.js +39 -0
  190. package/dist/web/media.js +4 -0
  191. package/docs/tools/slash-commands.md +5 -1
  192. package/extensions/bluebubbles/package.json +1 -1
  193. package/extensions/copilot-proxy/package.json +1 -1
  194. package/extensions/diagnostics-otel/package.json +1 -1
  195. package/extensions/discord/package.json +1 -1
  196. package/extensions/feishu/package.json +1 -1
  197. package/extensions/feishu/src/external-keys.ts +19 -0
  198. package/extensions/google-antigravity-auth/package.json +1 -1
  199. package/extensions/google-gemini-cli-auth/package.json +1 -1
  200. package/extensions/googlechat/package.json +1 -1
  201. package/extensions/imessage/package.json +1 -1
  202. package/extensions/irc/package.json +1 -1
  203. package/extensions/line/package.json +1 -1
  204. package/extensions/llm-task/package.json +1 -1
  205. package/extensions/lobster/package.json +1 -1
  206. package/extensions/lobster/src/windows-spawn.ts +193 -0
  207. package/extensions/matrix/CHANGELOG.md +5 -0
  208. package/extensions/matrix/package.json +1 -1
  209. package/extensions/matrix/src/matrix/actions/limits.ts +6 -0
  210. package/extensions/mattermost/package.json +1 -1
  211. package/extensions/mattermost/src/mattermost/reactions.test-helpers.ts +83 -0
  212. package/extensions/memory-core/package.json +1 -1
  213. package/extensions/memory-lancedb/package.json +1 -1
  214. package/extensions/minimax-portal-auth/package.json +1 -1
  215. package/extensions/msteams/CHANGELOG.md +5 -0
  216. package/extensions/msteams/package.json +1 -1
  217. package/extensions/nextcloud-talk/package.json +1 -1
  218. package/extensions/nostr/CHANGELOG.md +5 -0
  219. package/extensions/nostr/package.json +1 -1
  220. package/extensions/open-prose/package.json +1 -1
  221. package/extensions/openai-codex-auth/package.json +1 -1
  222. package/extensions/signal/package.json +1 -1
  223. package/extensions/slack/package.json +1 -1
  224. package/extensions/telegram/package.json +1 -1
  225. package/extensions/tlon/package.json +1 -1
  226. package/extensions/twitch/CHANGELOG.md +5 -0
  227. package/extensions/twitch/package.json +1 -1
  228. package/extensions/voice-call/CHANGELOG.md +5 -0
  229. package/extensions/voice-call/package.json +1 -1
  230. package/extensions/whatsapp/package.json +1 -1
  231. package/extensions/zalo/CHANGELOG.md +5 -0
  232. package/extensions/zalo/package.json +1 -1
  233. package/extensions/zalouser/CHANGELOG.md +5 -0
  234. package/extensions/zalouser/package.json +1 -1
  235. package/package.json +1 -1
@@ -1,64 +1,75 @@
1
+ import { ensureAuthProfileStore, getSoonestCooldownExpiry, isProfileInCooldown, resolveAuthProfileOrder, } from "./auth-profiles.js";
1
2
  import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "./defaults.js";
2
3
  import { coerceToFailoverError, describeFailoverError, isFailoverError, isTimeoutError, } from "./failover-error.js";
3
- import { buildModelAliasIndex, modelKey, parseModelRef, resolveConfiguredModelRef, resolveModelRefFromString, } from "./model-selection.js";
4
- import { ensureAuthProfileStore, getSoonestCooldownExpiry, isProfileInCooldown, resolveAuthProfileOrder, } from "./auth-profiles.js";
5
- import { isProviderRateLimited, recordFallbackError } from "./provider/integration.js";
6
- function isAbortError(err) {
7
- if (!err || typeof err !== "object")
4
+ import { buildConfiguredAllowlistKeys, buildModelAliasIndex, modelKey, normalizeModelRef, resolveConfiguredModelRef, resolveModelRefFromString, } from "./model-selection.js";
5
+ import { isLikelyContextOverflowError } from "./pi-embedded-helpers.js";
6
+ /**
7
+ * Fallback abort check. Only treats explicit AbortError names as user aborts.
8
+ * Message-based checks (e.g., "aborted") can mask timeouts and skip fallback.
9
+ */
10
+ function isFallbackAbortError(err) {
11
+ if (!err || typeof err !== "object") {
8
12
  return false;
9
- if (isFailoverError(err))
13
+ }
14
+ if (isFailoverError(err)) {
10
15
  return false;
16
+ }
11
17
  const name = "name" in err ? String(err.name) : "";
12
- // Only treat explicit AbortError names as user aborts.
13
- // Message-based checks (e.g., "aborted") can mask timeouts and skip fallback.
14
18
  return name === "AbortError";
15
19
  }
16
20
  function shouldRethrowAbort(err) {
17
- return isAbortError(err) && !isTimeoutError(err);
21
+ return isFallbackAbortError(err) && !isTimeoutError(err);
18
22
  }
19
- function buildAllowedModelKeys(cfg, defaultProvider) {
20
- const rawAllowlist = (() => {
21
- const modelMap = cfg?.agents?.defaults?.models ?? {};
22
- return Object.keys(modelMap);
23
- })();
24
- if (rawAllowlist.length === 0)
25
- return null;
26
- const keys = new Set();
27
- for (const raw of rawAllowlist) {
28
- const parsed = parseModelRef(String(raw ?? ""), defaultProvider);
29
- if (!parsed)
30
- continue;
31
- keys.add(modelKey(parsed.provider, parsed.model));
32
- }
33
- return keys.size > 0 ? keys : null;
34
- }
35
- function resolveImageFallbackCandidates(params) {
36
- const aliasIndex = buildModelAliasIndex({
37
- cfg: params.cfg ?? {},
38
- defaultProvider: params.defaultProvider,
39
- });
40
- const allowlist = buildAllowedModelKeys(params.cfg, params.defaultProvider);
23
+ function createModelCandidateCollector(allowlist) {
41
24
  const seen = new Set();
42
25
  const candidates = [];
43
26
  const addCandidate = (candidate, enforceAllowlist) => {
44
- if (!candidate.provider || !candidate.model)
27
+ if (!candidate.provider || !candidate.model) {
45
28
  return;
29
+ }
46
30
  const key = modelKey(candidate.provider, candidate.model);
47
- if (seen.has(key))
31
+ if (seen.has(key)) {
48
32
  return;
49
- if (enforceAllowlist && allowlist && !allowlist.has(key))
33
+ }
34
+ if (enforceAllowlist && allowlist && !allowlist.has(key)) {
50
35
  return;
36
+ }
51
37
  seen.add(key);
52
38
  candidates.push(candidate);
53
39
  };
40
+ return { candidates, addCandidate };
41
+ }
42
+ function sameModelCandidate(a, b) {
43
+ return a.provider === b.provider && a.model === b.model;
44
+ }
45
+ function throwFallbackFailureSummary(params) {
46
+ if (params.attempts.length <= 1 && params.lastError) {
47
+ throw params.lastError;
48
+ }
49
+ const summary = params.attempts.length > 0 ? params.attempts.map(params.formatAttempt).join(" | ") : "unknown";
50
+ throw new Error(`All ${params.label} failed (${params.attempts.length || params.candidates.length}): ${summary}`, {
51
+ cause: params.lastError instanceof Error ? params.lastError : undefined,
52
+ });
53
+ }
54
+ function resolveImageFallbackCandidates(params) {
55
+ const aliasIndex = buildModelAliasIndex({
56
+ cfg: params.cfg ?? {},
57
+ defaultProvider: params.defaultProvider,
58
+ });
59
+ const allowlist = buildConfiguredAllowlistKeys({
60
+ cfg: params.cfg,
61
+ defaultProvider: params.defaultProvider,
62
+ });
63
+ const { candidates, addCandidate } = createModelCandidateCollector(allowlist);
54
64
  const addRaw = (raw, enforceAllowlist) => {
55
65
  const resolved = resolveModelRefFromString({
56
66
  raw: String(raw ?? ""),
57
67
  defaultProvider: params.defaultProvider,
58
68
  aliasIndex,
59
69
  });
60
- if (!resolved)
70
+ if (!resolved) {
61
71
  return;
72
+ }
62
73
  addCandidate(resolved.ref, enforceAllowlist);
63
74
  };
64
75
  if (params.modelOverride?.trim()) {
@@ -67,8 +78,9 @@ function resolveImageFallbackCandidates(params) {
67
78
  else {
68
79
  const imageModel = params.cfg?.agents?.defaults?.imageModel;
69
80
  const primary = typeof imageModel === "string" ? imageModel.trim() : imageModel?.primary;
70
- if (primary?.trim())
81
+ if (primary?.trim()) {
71
82
  addRaw(primary, false);
83
+ }
72
84
  }
73
85
  const imageFallbacks = (() => {
74
86
  const imageModel = params.cfg?.agents?.defaults?.imageModel;
@@ -92,33 +104,33 @@ function resolveFallbackCandidates(params) {
92
104
  : null;
93
105
  const defaultProvider = primary?.provider ?? DEFAULT_PROVIDER;
94
106
  const defaultModel = primary?.model ?? DEFAULT_MODEL;
95
- const provider = String(params.provider ?? "").trim() || defaultProvider;
96
- const model = String(params.model ?? "").trim() || defaultModel;
107
+ const providerRaw = String(params.provider ?? "").trim() || defaultProvider;
108
+ const modelRaw = String(params.model ?? "").trim() || defaultModel;
109
+ const normalizedPrimary = normalizeModelRef(providerRaw, modelRaw);
110
+ const configuredPrimary = normalizeModelRef(defaultProvider, defaultModel);
97
111
  const aliasIndex = buildModelAliasIndex({
98
112
  cfg: params.cfg ?? {},
99
113
  defaultProvider,
100
114
  });
101
- const allowlist = buildAllowedModelKeys(params.cfg, defaultProvider);
102
- const seen = new Set();
103
- const candidates = [];
104
- const addCandidate = (candidate, enforceAllowlist) => {
105
- if (!candidate.provider || !candidate.model)
106
- return;
107
- const key = modelKey(candidate.provider, candidate.model);
108
- if (seen.has(key))
109
- return;
110
- if (enforceAllowlist && allowlist && !allowlist.has(key))
111
- return;
112
- seen.add(key);
113
- candidates.push(candidate);
114
- };
115
- addCandidate({ provider, model }, false);
115
+ const allowlist = buildConfiguredAllowlistKeys({
116
+ cfg: params.cfg,
117
+ defaultProvider,
118
+ });
119
+ const { candidates, addCandidate } = createModelCandidateCollector(allowlist);
120
+ addCandidate(normalizedPrimary, false);
116
121
  const modelFallbacks = (() => {
117
- if (params.fallbacksOverride !== undefined)
122
+ if (params.fallbacksOverride !== undefined) {
118
123
  return params.fallbacksOverride;
124
+ }
125
+ // Skip configured fallback chain when the user runs a non-default override.
126
+ // In that case, retry should return directly to configured primary.
127
+ if (!sameModelCandidate(normalizedPrimary, configuredPrimary)) {
128
+ return []; // Override model failed → go straight to configured default
129
+ }
119
130
  const model = params.cfg?.agents?.defaults?.model;
120
- if (model && typeof model === "object")
131
+ if (model && typeof model === "object") {
121
132
  return model.fallbacks ?? [];
133
+ }
122
134
  return [];
123
135
  })();
124
136
  for (const raw of modelFallbacks) {
@@ -127,8 +139,9 @@ function resolveFallbackCandidates(params) {
127
139
  defaultProvider,
128
140
  aliasIndex,
129
141
  });
130
- if (!resolved)
142
+ if (!resolved) {
131
143
  continue;
144
+ }
132
145
  addCandidate(resolved.ref, true);
133
146
  }
134
147
  if (params.fallbacksOverride === undefined && primary?.provider && primary.model) {
@@ -177,8 +190,8 @@ export async function runWithModelFallback(params) {
177
190
  ? ensureAuthProfileStore(params.agentDir, { allowKeychainPrompt: false })
178
191
  : null;
179
192
  const attempts = [];
180
- const hasFallbackCandidates = candidates.length > 1;
181
193
  let lastError;
194
+ const hasFallbackCandidates = candidates.length > 1;
182
195
  for (let i = 0; i < candidates.length; i += 1) {
183
196
  const candidate = candidates[i];
184
197
  if (authStore) {
@@ -219,21 +232,6 @@ export async function runWithModelFallback(params) {
219
232
  lastProbeAttempt.set(probeThrottleKey, now);
220
233
  }
221
234
  }
222
- // Provider-level rate limit check (from token pool / rate limit tracking).
223
- // Complements the per-profile cooldown above — skips candidates when all
224
- // API keys in the pool are known to be rate-limited.
225
- const poolLimit = isProviderRateLimited(candidate.provider);
226
- if (poolLimit.isLimited && hasFallbackCandidates && i > 0) {
227
- // Skip non-primary candidates that are pool-rate-limited.
228
- // Primary (i === 0) is never skipped here — it gets a chance to probe.
229
- attempts.push({
230
- provider: candidate.provider,
231
- model: candidate.model,
232
- error: `Provider ${candidate.provider} pool is rate-limited (wait ${poolLimit.waitTimeMs}ms)`,
233
- reason: "rate_limit",
234
- });
235
- continue;
236
- }
237
235
  try {
238
236
  const result = await params.run(candidate.provider, candidate.model);
239
237
  return {
@@ -244,14 +242,24 @@ export async function runWithModelFallback(params) {
244
242
  };
245
243
  }
246
244
  catch (err) {
247
- if (shouldRethrowAbort(err))
245
+ if (shouldRethrowAbort(err)) {
246
+ throw err;
247
+ }
248
+ // Context overflow errors should be handled by the inner runner's
249
+ // compaction/retry logic, not by model fallback. If one escapes as a
250
+ // throw, rethrow it immediately rather than trying a different model
251
+ // that may have a smaller context window and fail worse.
252
+ const errMessage = err instanceof Error ? err.message : String(err);
253
+ if (isLikelyContextOverflowError(errMessage)) {
248
254
  throw err;
255
+ }
249
256
  const normalized = coerceToFailoverError(err, {
250
257
  provider: candidate.provider,
251
258
  model: candidate.model,
252
259
  }) ?? err;
253
- if (!isFailoverError(normalized))
260
+ if (!isFailoverError(normalized)) {
254
261
  throw err;
262
+ }
255
263
  lastError = normalized;
256
264
  const described = describeFailoverError(normalized);
257
265
  attempts.push({
@@ -262,14 +270,6 @@ export async function runWithModelFallback(params) {
262
270
  status: described.status,
263
271
  code: described.code,
264
272
  });
265
- // Feed error into provider infrastructure (rate limits + monitoring)
266
- recordFallbackError({
267
- provider: candidate.provider,
268
- model: candidate.model,
269
- status: described.status,
270
- reason: described.reason,
271
- error: described.message,
272
- });
273
273
  await params.onError?.({
274
274
  provider: candidate.provider,
275
275
  model: candidate.model,
@@ -279,15 +279,12 @@ export async function runWithModelFallback(params) {
279
279
  });
280
280
  }
281
281
  }
282
- if (attempts.length <= 1 && lastError)
283
- throw lastError;
284
- const summary = attempts.length > 0
285
- ? attempts
286
- .map((attempt) => `${attempt.provider}/${attempt.model}: ${attempt.error}${attempt.reason ? ` (${attempt.reason})` : ""}`)
287
- .join(" | ")
288
- : "unknown";
289
- throw new Error(`All models failed (${attempts.length || candidates.length}): ${summary}`, {
290
- cause: lastError instanceof Error ? lastError : undefined,
282
+ throwFallbackFailureSummary({
283
+ attempts,
284
+ candidates,
285
+ lastError,
286
+ label: "models",
287
+ formatAttempt: (attempt) => `${attempt.provider}/${attempt.model}: ${attempt.error}${attempt.reason ? ` (${attempt.reason})` : ""}`,
291
288
  });
292
289
  }
293
290
  export async function runWithImageModelFallback(params) {
@@ -313,8 +310,9 @@ export async function runWithImageModelFallback(params) {
313
310
  };
314
311
  }
315
312
  catch (err) {
316
- if (shouldRethrowAbort(err))
313
+ if (shouldRethrowAbort(err)) {
317
314
  throw err;
315
+ }
318
316
  lastError = err;
319
317
  attempts.push({
320
318
  provider: candidate.provider,
@@ -330,14 +328,11 @@ export async function runWithImageModelFallback(params) {
330
328
  });
331
329
  }
332
330
  }
333
- if (attempts.length <= 1 && lastError)
334
- throw lastError;
335
- const summary = attempts.length > 0
336
- ? attempts
337
- .map((attempt) => `${attempt.provider}/${attempt.model}: ${attempt.error}`)
338
- .join(" | ")
339
- : "unknown";
340
- throw new Error(`All image models failed (${attempts.length || candidates.length}): ${summary}`, {
341
- cause: lastError instanceof Error ? lastError : undefined,
331
+ throwFallbackFailureSummary({
332
+ attempts,
333
+ candidates,
334
+ lastError,
335
+ label: "image models",
336
+ formatAttempt: (attempt) => `${attempt.provider}/${attempt.model}: ${attempt.error}`,
342
337
  });
343
338
  }