@poolzin/pool-bot 2026.3.22 → 2026.3.23

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 (124) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/acp/bindings-store.js +209 -0
  3. package/dist/acp/control-plane/runtime-cache.js +54 -0
  4. package/dist/acp/control-plane/runtime-options.js +215 -0
  5. package/dist/acp/control-plane/session-actor-queue.js +36 -0
  6. package/dist/acp/runtime/errors.js +47 -0
  7. package/dist/acp/runtime/registry.js +86 -0
  8. package/dist/acp/runtime/types.js +1 -0
  9. package/dist/acp/translator.js +97 -0
  10. package/dist/agents/failover-error.js +145 -47
  11. package/dist/browser/browser-profile-manager.js +319 -0
  12. package/dist/browser/cdp-proxy-bypass.js +129 -0
  13. package/dist/browser/cdp-timeouts.js +41 -0
  14. package/dist/browser/chrome-extension-validator.js +406 -0
  15. package/dist/browser/chrome-mcp-snapshot.js +222 -0
  16. package/dist/browser/chrome-mcp.js +421 -0
  17. package/dist/browser/chrome-mcp.snapshot.js +133 -0
  18. package/dist/browser/errors.js +67 -0
  19. package/dist/browser/form-fields.js +22 -0
  20. package/dist/browser/output-atomic.js +44 -0
  21. package/dist/browser/profile-capabilities.js +47 -0
  22. package/dist/browser/safe-filename.js +25 -0
  23. package/dist/browser/snapshot-roles.js +60 -0
  24. package/dist/build-info.json +3 -3
  25. package/dist/commands/security-owner-only.js +86 -0
  26. package/dist/control-ui/assets/{index-Dvkl4Xlx.js → index-D7shnQwQ.js} +404 -388
  27. package/dist/control-ui/assets/index-D7shnQwQ.js.map +1 -0
  28. package/dist/control-ui/index.html +1 -1
  29. package/dist/cron/cron-filters.js +150 -0
  30. package/dist/gateway/device-pairing-security.js +197 -0
  31. package/dist/gateway/event-deduplication.js +167 -0
  32. package/dist/gateway/run-tracker.js +253 -0
  33. package/dist/gateway/server-methods/nodes.js +14 -0
  34. package/dist/gateway/websocket-preauth-security.js +188 -0
  35. package/dist/infra/errors.js +53 -13
  36. package/dist/infra/exec-approvals-security.js +217 -0
  37. package/dist/infra/security/command-analyzer.js +257 -0
  38. package/dist/plugins/loader.js +16 -8
  39. package/dist/security/external-content.js +51 -1
  40. package/dist/sessions/session-costs.js +228 -0
  41. package/dist/shared/param-key.js +16 -0
  42. package/dist/shared/poll-params.js +58 -0
  43. package/dist/shared/polls.js +55 -0
  44. package/docs/DASHBOARD-GAP-ANALYSIS-AND-PLAN.md +430 -0
  45. package/docs/FEATURES.md +523 -0
  46. package/docs/FINAL-IMPLEMENTATION-REVIEW.md +274 -0
  47. package/docs/FINAL-IMPLEMENTATION-SUMMARY.md +356 -0
  48. package/docs/FINAL-PROFESSIONAL-EVALUATION.md +312 -0
  49. package/docs/IMPLEMENTATION-PRIORITY-EVALUATION.md +298 -0
  50. package/docs/IMPLEMENTATION-PROGRESS.md +237 -0
  51. package/docs/IMPLEMENTATION-REVIEW-PHASE1-2.md +381 -0
  52. package/docs/IMPLEMENTATION-REVIEW-PHASE4.md +389 -0
  53. package/docs/IMPLEMENTATION-REVIEW-PHASE5.md +420 -0
  54. package/docs/IMPLEMENTATION-REVIEW-PHASE6.md +422 -0
  55. package/docs/IMPLEMENTATION-REVIEW-PHASE7-FINAL.md +184 -0
  56. package/docs/MIKRODASH-ANALYSIS.md +412 -0
  57. package/docs/OPENCLAW-GAP-ANALYSIS-FINAL.md +431 -0
  58. package/docs/OPENCLAW-VS-POOLBOT-ANALYSIS.md +351 -0
  59. package/docs/PHASE-7-SUMMARY.md +144 -0
  60. package/docs/POOLBOT-OFFICE-PLAN.md +697 -0
  61. package/docs/PROJECT-FINAL-STATUS.md +237 -0
  62. package/docs/README.md +116 -0
  63. package/docs/REAL-IMPROVEMENTS-EVALUATION.md +477 -0
  64. package/docs/SECURITY-HARDENING-IMPLEMENTATION.md +161 -0
  65. package/docs/channels/googlechat.md +235 -206
  66. package/docs/channels/irc.md +332 -0
  67. package/docs/channels/nostr.md +255 -168
  68. package/docs/components/command-palette.md +166 -0
  69. package/docs/components/login-gate.md +219 -0
  70. package/docs/getting-started/installation.md +191 -0
  71. package/docs/getting-started/introduction.md +120 -0
  72. package/docs/improvements/USAGE-GUIDE.md +359 -0
  73. package/docs/plans/2026-03-15-openclaw-features-implementation.md +1632 -0
  74. package/docs/reference/deadcode-detection.md +72 -0
  75. package/extensions/acpx/node_modules/.bin/acpx +21 -0
  76. package/extensions/agency-agents/node_modules/.bin/vite +4 -4
  77. package/extensions/agency-agents/node_modules/.bin/vitest +2 -2
  78. package/extensions/googlechat/node_modules/.bin/tsc +21 -0
  79. package/extensions/googlechat/node_modules/.bin/tsserver +21 -0
  80. package/extensions/googlechat/node_modules/.bin/vitest +21 -0
  81. package/extensions/googlechat/package.json +11 -28
  82. package/extensions/googlechat/src/googlechat-channel.test.ts +60 -0
  83. package/extensions/googlechat/src/googlechat-channel.ts +120 -0
  84. package/extensions/googlechat/src/index.ts +14 -0
  85. package/extensions/irc/node_modules/.bin/tsc +21 -0
  86. package/extensions/irc/node_modules/.bin/tsserver +21 -0
  87. package/extensions/irc/node_modules/.bin/vitest +21 -0
  88. package/extensions/irc/package.json +16 -8
  89. package/extensions/irc/src/index.ts +14 -0
  90. package/extensions/irc/src/irc-channel.test.ts +43 -0
  91. package/extensions/irc/src/irc-channel.ts +191 -0
  92. package/extensions/keyed-async-queue/node_modules/.bin/tsc +21 -0
  93. package/extensions/keyed-async-queue/node_modules/.bin/tsserver +21 -0
  94. package/extensions/keyed-async-queue/node_modules/.bin/vitest +21 -0
  95. package/extensions/keyed-async-queue/package.json +20 -0
  96. package/extensions/keyed-async-queue/src/index.ts +14 -0
  97. package/extensions/keyed-async-queue/src/queue.test.ts +135 -0
  98. package/extensions/keyed-async-queue/src/queue.ts +200 -0
  99. package/extensions/memory-core/node_modules/.bin/tsc +21 -0
  100. package/extensions/memory-core/node_modules/.bin/tsserver +21 -0
  101. package/extensions/memory-core/node_modules/.bin/vitest +21 -0
  102. package/extensions/memory-core/package.json +11 -8
  103. package/extensions/memory-core/src/index.ts +14 -0
  104. package/extensions/memory-core/src/memory-manager.test.ts +124 -0
  105. package/extensions/memory-core/src/memory-manager.ts +186 -0
  106. package/extensions/nostr/node_modules/.bin/tsc +2 -2
  107. package/extensions/nostr/node_modules/.bin/tsserver +2 -2
  108. package/extensions/nostr/node_modules/.bin/vitest +21 -0
  109. package/extensions/nostr/package.json +15 -24
  110. package/extensions/nostr/src/index.ts +14 -0
  111. package/extensions/nostr/src/nostr-channel.test.ts +55 -0
  112. package/extensions/nostr/src/nostr-channel.ts +228 -0
  113. package/extensions/page-agent/node_modules/.bin/vitest +2 -2
  114. package/extensions/test-utils/node_modules/.bin/jiti +21 -0
  115. package/extensions/test-utils/node_modules/.bin/playwright +21 -0
  116. package/extensions/test-utils/node_modules/.bin/tsx +21 -0
  117. package/extensions/test-utils/node_modules/.bin/vite +21 -0
  118. package/extensions/test-utils/node_modules/.bin/vitest +21 -0
  119. package/extensions/test-utils/node_modules/.bin/yaml +21 -0
  120. package/extensions/xyops/node_modules/.bin/vitest +2 -2
  121. package/package.json +2 -1
  122. package/dist/control-ui/assets/index-Dvkl4Xlx.js.map +0 -1
  123. package/extensions/googlechat/node_modules/.bin/poolbot +0 -21
  124. package/extensions/memory-core/node_modules/.bin/poolbot +0 -21
@@ -0,0 +1,86 @@
1
+ import { AcpRuntimeError } from "./errors.js";
2
+ const ACP_RUNTIME_REGISTRY_STATE_KEY = Symbol.for("poolbot.acpRuntimeRegistryState");
3
+ function createAcpRuntimeRegistryGlobalState() {
4
+ return {
5
+ backendsById: new Map(),
6
+ };
7
+ }
8
+ function resolveAcpRuntimeRegistryGlobalState() {
9
+ const runtimeGlobal = globalThis;
10
+ if (!runtimeGlobal[ACP_RUNTIME_REGISTRY_STATE_KEY]) {
11
+ runtimeGlobal[ACP_RUNTIME_REGISTRY_STATE_KEY] = createAcpRuntimeRegistryGlobalState();
12
+ }
13
+ return runtimeGlobal[ACP_RUNTIME_REGISTRY_STATE_KEY];
14
+ }
15
+ const ACP_BACKENDS_BY_ID = resolveAcpRuntimeRegistryGlobalState().backendsById;
16
+ function normalizeBackendId(id) {
17
+ return id?.trim().toLowerCase() || "";
18
+ }
19
+ function isBackendHealthy(backend) {
20
+ if (!backend.healthy) {
21
+ return true;
22
+ }
23
+ try {
24
+ return backend.healthy();
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ }
30
+ export function registerAcpRuntimeBackend(backend) {
31
+ const id = normalizeBackendId(backend.id);
32
+ if (!id) {
33
+ throw new Error("ACP runtime backend id is required");
34
+ }
35
+ if (!backend.runtime) {
36
+ throw new Error(`ACP runtime backend "${id}" is missing runtime implementation`);
37
+ }
38
+ ACP_BACKENDS_BY_ID.set(id, {
39
+ ...backend,
40
+ id,
41
+ });
42
+ }
43
+ export function unregisterAcpRuntimeBackend(id) {
44
+ const normalized = normalizeBackendId(id);
45
+ if (!normalized) {
46
+ return;
47
+ }
48
+ ACP_BACKENDS_BY_ID.delete(normalized);
49
+ }
50
+ export function getAcpRuntimeBackend(id) {
51
+ const normalized = normalizeBackendId(id);
52
+ if (normalized) {
53
+ return ACP_BACKENDS_BY_ID.get(normalized) ?? null;
54
+ }
55
+ if (ACP_BACKENDS_BY_ID.size === 0) {
56
+ return null;
57
+ }
58
+ for (const backend of ACP_BACKENDS_BY_ID.values()) {
59
+ if (isBackendHealthy(backend)) {
60
+ return backend;
61
+ }
62
+ }
63
+ return ACP_BACKENDS_BY_ID.values().next().value ?? null;
64
+ }
65
+ export function requireAcpRuntimeBackend(id) {
66
+ const normalized = normalizeBackendId(id);
67
+ const backend = getAcpRuntimeBackend(normalized || undefined);
68
+ if (!backend) {
69
+ throw new AcpRuntimeError("ACP_BACKEND_MISSING", "ACP runtime backend is not configured. Install and enable the acpx runtime plugin.");
70
+ }
71
+ if (!isBackendHealthy(backend)) {
72
+ throw new AcpRuntimeError("ACP_BACKEND_UNAVAILABLE", "ACP runtime backend is currently unavailable. Try again in a moment.");
73
+ }
74
+ if (normalized && backend.id !== normalized) {
75
+ throw new AcpRuntimeError("ACP_BACKEND_MISSING", `ACP runtime backend "${normalized}" is not registered.`);
76
+ }
77
+ return backend;
78
+ }
79
+ export const __testing = {
80
+ resetAcpRuntimeBackendsForTests() {
81
+ ACP_BACKENDS_BY_ID.clear();
82
+ },
83
+ getAcpRuntimeRegistryGlobalStateForTests() {
84
+ return resolveAcpRuntimeRegistryGlobalState();
85
+ },
86
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -342,3 +342,100 @@ export class AcpGatewayAgent {
342
342
  });
343
343
  }
344
344
  }
345
+ /**
346
+ * Apply prompt prefix to a prompt request
347
+ */
348
+ export function applyPromptPrefix(request, config) {
349
+ if (!config.enabled || !config.prefix) {
350
+ return request;
351
+ }
352
+ // Check session filter
353
+ if (config.sessionIds && !config.sessionIds.includes(request.sessionId)) {
354
+ return request;
355
+ }
356
+ // Apply prefix to prompt text
357
+ const modified = { ...request };
358
+ if (modified.prompt) {
359
+ const promptText = typeof modified.prompt === "string" ? modified.prompt : JSON.stringify(modified.prompt);
360
+ modified.prompt = (config.prefix + "\n\n" + promptText);
361
+ }
362
+ return modified;
363
+ }
364
+ /**
365
+ * Cancel scoping manager to prevent cascade cancels
366
+ */
367
+ export class CancelScopingManager {
368
+ cancelHistory = [];
369
+ config;
370
+ constructor(config = {}) {
371
+ this.config = {
372
+ enabled: config.enabled ?? true,
373
+ timeWindowMs: config.timeWindowMs ?? 5000, // 5 seconds default
374
+ maxCancelsInWindow: config.maxCancelsInWindow ?? 3,
375
+ sessionIds: config.sessionIds,
376
+ };
377
+ }
378
+ /**
379
+ * Check if a cancel request should be allowed
380
+ */
381
+ shouldAllowCancel(sessionId, reason) {
382
+ if (!this.config.enabled) {
383
+ return { allowed: true };
384
+ }
385
+ // Check session filter
386
+ if (this.config.sessionIds && !this.config.sessionIds.includes(sessionId)) {
387
+ return { allowed: true };
388
+ }
389
+ const now = Date.now();
390
+ const windowStart = now - (this.config.timeWindowMs ?? 5000);
391
+ // Clean old records
392
+ this.cancelHistory = this.cancelHistory.filter((r) => r.timestamp > windowStart);
393
+ // Count recent cancels for this session
394
+ const recentCancels = this.cancelHistory.filter((r) => r.sessionId === sessionId && r.timestamp > windowStart).length;
395
+ const maxAllowed = this.config.maxCancelsInWindow ?? 3;
396
+ if (recentCancels >= maxAllowed) {
397
+ return {
398
+ allowed: false,
399
+ reason: `Cancel rate limit exceeded: ${recentCancels}/${maxAllowed} cancels in the last ${this.config.timeWindowMs}ms`,
400
+ };
401
+ }
402
+ // Record this cancel
403
+ this.cancelHistory.push({
404
+ sessionId,
405
+ timestamp: now,
406
+ reason,
407
+ });
408
+ return { allowed: true };
409
+ }
410
+ /**
411
+ * Clear cancel history
412
+ */
413
+ clear() {
414
+ this.cancelHistory = [];
415
+ }
416
+ /**
417
+ * Get statistics about cancel activity
418
+ */
419
+ getStats() {
420
+ const now = Date.now();
421
+ const windowStart = now - (this.config.timeWindowMs ?? 5000);
422
+ const recentHistory = this.cancelHistory.filter((r) => r.timestamp > windowStart);
423
+ const cancelsBySession = recentHistory.reduce((acc, r) => {
424
+ acc[r.sessionId] = (acc[r.sessionId] || 0) + 1;
425
+ return acc;
426
+ }, {});
427
+ return {
428
+ totalCancels: recentHistory.length,
429
+ cancelsBySession,
430
+ oldestCancelInWindow: recentHistory.length > 0 ? Math.min(...recentHistory.map((r) => r.timestamp)) : undefined,
431
+ };
432
+ }
433
+ }
434
+ /**
435
+ * Process a cancel notification with scoping
436
+ */
437
+ export function processCancelWithScoping(notification, manager) {
438
+ const sessionId = notification.sessionId || "unknown";
439
+ const reason = notification.reason || "user_requested";
440
+ return manager.shouldAllowCancel(sessionId, reason);
441
+ }
@@ -1,5 +1,5 @@
1
- import { classifyFailoverReason } from "./pi-embedded-helpers.js";
2
- const TIMEOUT_HINT_RE = /timeout|timed out|deadline exceeded|context deadline exceeded/i;
1
+ import { readErrorName } from "../infra/errors.js";
2
+ import { classifyFailoverReason, isTimeoutErrorMessage, } from "./pi-embedded-helpers.js";
3
3
  const ABORT_TIMEOUT_RE = /request was aborted|request aborted/i;
4
4
  export class FailoverError extends Error {
5
5
  reason;
@@ -28,104 +28,200 @@ export function resolveFailoverStatus(reason) {
28
28
  return 402;
29
29
  case "rate_limit":
30
30
  return 429;
31
+ case "overloaded":
32
+ return 503;
31
33
  case "auth":
32
34
  return 401;
35
+ case "auth_permanent":
36
+ return 403;
33
37
  case "timeout":
34
38
  return 408;
35
39
  case "format":
36
40
  return 400;
37
41
  case "model_not_found":
38
42
  return 404;
43
+ case "session_expired":
44
+ return 410; // Gone - session no longer exists
39
45
  default:
40
46
  return undefined;
41
47
  }
42
48
  }
43
- function getStatusCode(err) {
44
- if (!err || typeof err !== "object")
49
+ function findErrorProperty(err, reader, seen = new Set()) {
50
+ const direct = reader(err);
51
+ if (direct !== undefined) {
52
+ return direct;
53
+ }
54
+ if (!err || typeof err !== "object") {
55
+ return undefined;
56
+ }
57
+ if (seen.has(err)) {
58
+ return undefined;
59
+ }
60
+ seen.add(err);
61
+ const candidate = err;
62
+ return (findErrorProperty(candidate.error, reader, seen) ??
63
+ findErrorProperty(candidate.cause, reader, seen));
64
+ }
65
+ function readDirectStatusCode(err) {
66
+ if (!err || typeof err !== "object") {
45
67
  return undefined;
68
+ }
46
69
  const candidate = err.status ??
47
70
  err.statusCode;
48
- if (typeof candidate === "number")
71
+ if (typeof candidate === "number") {
49
72
  return candidate;
73
+ }
50
74
  if (typeof candidate === "string" && /^\d+$/.test(candidate)) {
51
75
  return Number(candidate);
52
76
  }
53
77
  return undefined;
54
78
  }
55
- function getErrorName(err) {
56
- if (!err || typeof err !== "object")
57
- return "";
58
- return "name" in err ? String(err.name) : "";
79
+ function getStatusCode(err) {
80
+ return findErrorProperty(err, readDirectStatusCode);
59
81
  }
60
- function getErrorCode(err) {
61
- if (!err || typeof err !== "object")
82
+ function readDirectErrorCode(err) {
83
+ if (!err || typeof err !== "object") {
62
84
  return undefined;
63
- const candidate = err.code;
64
- if (typeof candidate !== "string")
85
+ }
86
+ const directCode = err.code;
87
+ if (typeof directCode === "string") {
88
+ const trimmed = directCode.trim();
89
+ return trimmed ? trimmed : undefined;
90
+ }
91
+ const status = err.status;
92
+ if (typeof status !== "string" || /^\d+$/.test(status)) {
65
93
  return undefined;
66
- const trimmed = candidate.trim();
94
+ }
95
+ const trimmed = status.trim();
67
96
  return trimmed ? trimmed : undefined;
68
97
  }
69
- function getErrorMessage(err) {
70
- if (err instanceof Error)
71
- return err.message;
72
- if (typeof err === "string")
73
- return err;
98
+ function getErrorCode(err) {
99
+ return findErrorProperty(err, readDirectErrorCode);
100
+ }
101
+ function readDirectErrorMessage(err) {
102
+ if (err instanceof Error) {
103
+ return err.message || undefined;
104
+ }
105
+ if (typeof err === "string") {
106
+ return err || undefined;
107
+ }
74
108
  if (typeof err === "number" || typeof err === "boolean" || typeof err === "bigint") {
75
109
  return String(err);
76
110
  }
77
- if (typeof err === "symbol")
78
- return err.description ?? "";
111
+ if (typeof err === "symbol") {
112
+ return err.description ?? undefined;
113
+ }
79
114
  if (err && typeof err === "object") {
80
115
  const message = err.message;
81
- if (typeof message === "string")
82
- return message;
116
+ if (typeof message === "string") {
117
+ return message || undefined;
118
+ }
119
+ }
120
+ return undefined;
121
+ }
122
+ function getErrorMessage(err) {
123
+ return findErrorProperty(err, readDirectErrorMessage) ?? "";
124
+ }
125
+ function getErrorCause(err) {
126
+ if (!err || typeof err !== "object" || !("cause" in err)) {
127
+ return undefined;
128
+ }
129
+ return err.cause;
130
+ }
131
+ /** Classify rate-limit / overloaded from symbolic error codes like RESOURCE_EXHAUSTED. */
132
+ function classifyFailoverReasonFromSymbolicCode(raw) {
133
+ const normalized = raw?.trim().toUpperCase();
134
+ if (!normalized) {
135
+ return null;
136
+ }
137
+ switch (normalized) {
138
+ case "RESOURCE_EXHAUSTED":
139
+ case "RATE_LIMIT":
140
+ case "RATE_LIMITED":
141
+ case "RATE_LIMIT_EXCEEDED":
142
+ case "TOO_MANY_REQUESTS":
143
+ case "THROTTLED":
144
+ case "THROTTLING":
145
+ case "THROTTLINGEXCEPTION":
146
+ case "THROTTLING_EXCEPTION":
147
+ return "rate_limit";
148
+ case "OVERLOADED":
149
+ case "OVERLOADED_ERROR":
150
+ return "overloaded";
151
+ default:
152
+ return null;
83
153
  }
84
- return "";
85
154
  }
86
155
  function hasTimeoutHint(err) {
87
- if (!err)
156
+ if (!err) {
88
157
  return false;
89
- if (getErrorName(err) === "TimeoutError")
158
+ }
159
+ if (readErrorName(err) === "TimeoutError") {
90
160
  return true;
161
+ }
91
162
  const message = getErrorMessage(err);
92
- return Boolean(message && TIMEOUT_HINT_RE.test(message));
163
+ return Boolean(message && isTimeoutErrorMessage(message));
93
164
  }
94
165
  export function isTimeoutError(err) {
95
- if (hasTimeoutHint(err))
166
+ if (hasTimeoutHint(err)) {
96
167
  return true;
97
- if (!err || typeof err !== "object")
168
+ }
169
+ if (!err || typeof err !== "object") {
98
170
  return false;
99
- if (getErrorName(err) !== "AbortError")
171
+ }
172
+ if (readErrorName(err) !== "AbortError") {
100
173
  return false;
174
+ }
101
175
  const message = getErrorMessage(err);
102
- if (message && ABORT_TIMEOUT_RE.test(message))
176
+ if (message && ABORT_TIMEOUT_RE.test(message)) {
103
177
  return true;
178
+ }
104
179
  const cause = "cause" in err ? err.cause : undefined;
105
180
  const reason = "reason" in err ? err.reason : undefined;
106
181
  return hasTimeoutHint(cause) || hasTimeoutHint(reason);
107
182
  }
108
183
  export function resolveFailoverReasonFromError(err) {
109
- if (isFailoverError(err))
184
+ if (isFailoverError(err)) {
110
185
  return err.reason;
111
- const status = getStatusCode(err);
112
- if (status === 402)
113
- return "billing";
114
- if (status === 429)
115
- return "rate_limit";
116
- if (status === 401 || status === 403)
117
- return "auth";
118
- if (status === 408)
119
- return "timeout";
186
+ }
187
+ const message = getErrorMessage(err);
188
+ // Check symbolic error codes (e.g. RESOURCE_EXHAUSTED from Google APIs)
189
+ const symbolicCodeReason = classifyFailoverReasonFromSymbolicCode(getErrorCode(err));
190
+ if (symbolicCodeReason) {
191
+ return symbolicCodeReason;
192
+ }
120
193
  const code = (getErrorCode(err) ?? "").toUpperCase();
121
- if (["ETIMEDOUT", "ESOCKETTIMEDOUT", "ECONNRESET", "ECONNABORTED"].includes(code)) {
194
+ if ([
195
+ "ETIMEDOUT",
196
+ "ESOCKETTIMEDOUT",
197
+ "ECONNRESET",
198
+ "ECONNABORTED",
199
+ "ECONNREFUSED",
200
+ "ENETUNREACH",
201
+ "EHOSTUNREACH",
202
+ "EHOSTDOWN",
203
+ "ENETRESET",
204
+ "EPIPE",
205
+ "EAI_AGAIN",
206
+ ].includes(code)) {
122
207
  return "timeout";
123
208
  }
124
- if (isTimeoutError(err))
209
+ // Walk into error cause chain *before* timeout heuristics so that a specific
210
+ // cause (e.g. RESOURCE_EXHAUSTED wrapped in AbortError) overrides a parent
211
+ // message-based "timeout" guess from isTimeoutError.
212
+ const cause = getErrorCause(err);
213
+ if (cause && cause !== err) {
214
+ const causeReason = resolveFailoverReasonFromError(cause);
215
+ if (causeReason) {
216
+ return causeReason;
217
+ }
218
+ }
219
+ if (isTimeoutError(err)) {
125
220
  return "timeout";
126
- const message = getErrorMessage(err);
127
- if (!message)
221
+ }
222
+ if (!message) {
128
223
  return null;
224
+ }
129
225
  return classifyFailoverReason(message);
130
226
  }
131
227
  export function describeFailoverError(err) {
@@ -146,11 +242,13 @@ export function describeFailoverError(err) {
146
242
  };
147
243
  }
148
244
  export function coerceToFailoverError(err, context) {
149
- if (isFailoverError(err))
245
+ if (isFailoverError(err)) {
150
246
  return err;
247
+ }
151
248
  const reason = resolveFailoverReasonFromError(err);
152
- if (!reason)
249
+ if (!reason) {
153
250
  return null;
251
+ }
154
252
  const message = getErrorMessage(err) || String(err);
155
253
  const status = getStatusCode(err) ?? resolveFailoverStatus(reason);
156
254
  const code = getErrorCode(err);