@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.
- package/CHANGELOG.md +54 -0
- package/dist/acp/bindings-store.js +209 -0
- package/dist/acp/control-plane/runtime-cache.js +54 -0
- package/dist/acp/control-plane/runtime-options.js +215 -0
- package/dist/acp/control-plane/session-actor-queue.js +36 -0
- package/dist/acp/runtime/errors.js +47 -0
- package/dist/acp/runtime/registry.js +86 -0
- package/dist/acp/runtime/types.js +1 -0
- package/dist/acp/translator.js +97 -0
- package/dist/agents/failover-error.js +145 -47
- package/dist/browser/browser-profile-manager.js +319 -0
- package/dist/browser/cdp-proxy-bypass.js +129 -0
- package/dist/browser/cdp-timeouts.js +41 -0
- package/dist/browser/chrome-extension-validator.js +406 -0
- package/dist/browser/chrome-mcp-snapshot.js +222 -0
- package/dist/browser/chrome-mcp.js +421 -0
- package/dist/browser/chrome-mcp.snapshot.js +133 -0
- package/dist/browser/errors.js +67 -0
- package/dist/browser/form-fields.js +22 -0
- package/dist/browser/output-atomic.js +44 -0
- package/dist/browser/profile-capabilities.js +47 -0
- package/dist/browser/safe-filename.js +25 -0
- package/dist/browser/snapshot-roles.js +60 -0
- package/dist/build-info.json +3 -3
- package/dist/commands/security-owner-only.js +86 -0
- package/dist/control-ui/assets/{index-Dvkl4Xlx.js → index-D7shnQwQ.js} +404 -388
- package/dist/control-ui/assets/index-D7shnQwQ.js.map +1 -0
- package/dist/control-ui/index.html +1 -1
- package/dist/cron/cron-filters.js +150 -0
- package/dist/gateway/device-pairing-security.js +197 -0
- package/dist/gateway/event-deduplication.js +167 -0
- package/dist/gateway/run-tracker.js +253 -0
- package/dist/gateway/server-methods/nodes.js +14 -0
- package/dist/gateway/websocket-preauth-security.js +188 -0
- package/dist/infra/errors.js +53 -13
- package/dist/infra/exec-approvals-security.js +217 -0
- package/dist/infra/security/command-analyzer.js +257 -0
- package/dist/plugins/loader.js +16 -8
- package/dist/security/external-content.js +51 -1
- package/dist/sessions/session-costs.js +228 -0
- package/dist/shared/param-key.js +16 -0
- package/dist/shared/poll-params.js +58 -0
- package/dist/shared/polls.js +55 -0
- package/docs/DASHBOARD-GAP-ANALYSIS-AND-PLAN.md +430 -0
- package/docs/FEATURES.md +523 -0
- package/docs/FINAL-IMPLEMENTATION-REVIEW.md +274 -0
- package/docs/FINAL-IMPLEMENTATION-SUMMARY.md +356 -0
- package/docs/FINAL-PROFESSIONAL-EVALUATION.md +312 -0
- package/docs/IMPLEMENTATION-PRIORITY-EVALUATION.md +298 -0
- package/docs/IMPLEMENTATION-PROGRESS.md +237 -0
- package/docs/IMPLEMENTATION-REVIEW-PHASE1-2.md +381 -0
- package/docs/IMPLEMENTATION-REVIEW-PHASE4.md +389 -0
- package/docs/IMPLEMENTATION-REVIEW-PHASE5.md +420 -0
- package/docs/IMPLEMENTATION-REVIEW-PHASE6.md +422 -0
- package/docs/IMPLEMENTATION-REVIEW-PHASE7-FINAL.md +184 -0
- package/docs/MIKRODASH-ANALYSIS.md +412 -0
- package/docs/OPENCLAW-GAP-ANALYSIS-FINAL.md +431 -0
- package/docs/OPENCLAW-VS-POOLBOT-ANALYSIS.md +351 -0
- package/docs/PHASE-7-SUMMARY.md +144 -0
- package/docs/POOLBOT-OFFICE-PLAN.md +697 -0
- package/docs/PROJECT-FINAL-STATUS.md +237 -0
- package/docs/README.md +116 -0
- package/docs/REAL-IMPROVEMENTS-EVALUATION.md +477 -0
- package/docs/SECURITY-HARDENING-IMPLEMENTATION.md +161 -0
- package/docs/channels/googlechat.md +235 -206
- package/docs/channels/irc.md +332 -0
- package/docs/channels/nostr.md +255 -168
- package/docs/components/command-palette.md +166 -0
- package/docs/components/login-gate.md +219 -0
- package/docs/getting-started/installation.md +191 -0
- package/docs/getting-started/introduction.md +120 -0
- package/docs/improvements/USAGE-GUIDE.md +359 -0
- package/docs/plans/2026-03-15-openclaw-features-implementation.md +1632 -0
- package/docs/reference/deadcode-detection.md +72 -0
- package/extensions/acpx/node_modules/.bin/acpx +21 -0
- package/extensions/agency-agents/node_modules/.bin/vite +4 -4
- package/extensions/agency-agents/node_modules/.bin/vitest +2 -2
- package/extensions/googlechat/node_modules/.bin/tsc +21 -0
- package/extensions/googlechat/node_modules/.bin/tsserver +21 -0
- package/extensions/googlechat/node_modules/.bin/vitest +21 -0
- package/extensions/googlechat/package.json +11 -28
- package/extensions/googlechat/src/googlechat-channel.test.ts +60 -0
- package/extensions/googlechat/src/googlechat-channel.ts +120 -0
- package/extensions/googlechat/src/index.ts +14 -0
- package/extensions/irc/node_modules/.bin/tsc +21 -0
- package/extensions/irc/node_modules/.bin/tsserver +21 -0
- package/extensions/irc/node_modules/.bin/vitest +21 -0
- package/extensions/irc/package.json +16 -8
- package/extensions/irc/src/index.ts +14 -0
- package/extensions/irc/src/irc-channel.test.ts +43 -0
- package/extensions/irc/src/irc-channel.ts +191 -0
- package/extensions/keyed-async-queue/node_modules/.bin/tsc +21 -0
- package/extensions/keyed-async-queue/node_modules/.bin/tsserver +21 -0
- package/extensions/keyed-async-queue/node_modules/.bin/vitest +21 -0
- package/extensions/keyed-async-queue/package.json +20 -0
- package/extensions/keyed-async-queue/src/index.ts +14 -0
- package/extensions/keyed-async-queue/src/queue.test.ts +135 -0
- package/extensions/keyed-async-queue/src/queue.ts +200 -0
- package/extensions/memory-core/node_modules/.bin/tsc +21 -0
- package/extensions/memory-core/node_modules/.bin/tsserver +21 -0
- package/extensions/memory-core/node_modules/.bin/vitest +21 -0
- package/extensions/memory-core/package.json +11 -8
- package/extensions/memory-core/src/index.ts +14 -0
- package/extensions/memory-core/src/memory-manager.test.ts +124 -0
- package/extensions/memory-core/src/memory-manager.ts +186 -0
- package/extensions/nostr/node_modules/.bin/tsc +2 -2
- package/extensions/nostr/node_modules/.bin/tsserver +2 -2
- package/extensions/nostr/node_modules/.bin/vitest +21 -0
- package/extensions/nostr/package.json +15 -24
- package/extensions/nostr/src/index.ts +14 -0
- package/extensions/nostr/src/nostr-channel.test.ts +55 -0
- package/extensions/nostr/src/nostr-channel.ts +228 -0
- package/extensions/page-agent/node_modules/.bin/vitest +2 -2
- package/extensions/test-utils/node_modules/.bin/jiti +21 -0
- package/extensions/test-utils/node_modules/.bin/playwright +21 -0
- package/extensions/test-utils/node_modules/.bin/tsx +21 -0
- package/extensions/test-utils/node_modules/.bin/vite +21 -0
- package/extensions/test-utils/node_modules/.bin/vitest +21 -0
- package/extensions/test-utils/node_modules/.bin/yaml +21 -0
- package/extensions/xyops/node_modules/.bin/vitest +2 -2
- package/package.json +2 -1
- package/dist/control-ui/assets/index-Dvkl4Xlx.js.map +0 -1
- package/extensions/googlechat/node_modules/.bin/poolbot +0 -21
- 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 {};
|
package/dist/acp/translator.js
CHANGED
|
@@ -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 {
|
|
2
|
-
|
|
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
|
|
44
|
-
|
|
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
|
|
56
|
-
|
|
57
|
-
return "";
|
|
58
|
-
return "name" in err ? String(err.name) : "";
|
|
79
|
+
function getStatusCode(err) {
|
|
80
|
+
return findErrorProperty(err, readDirectStatusCode);
|
|
59
81
|
}
|
|
60
|
-
function
|
|
61
|
-
if (!err || typeof err !== "object")
|
|
82
|
+
function readDirectErrorCode(err) {
|
|
83
|
+
if (!err || typeof err !== "object") {
|
|
62
84
|
return undefined;
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
94
|
+
}
|
|
95
|
+
const trimmed = status.trim();
|
|
67
96
|
return trimmed ? trimmed : undefined;
|
|
68
97
|
}
|
|
69
|
-
function
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
158
|
+
}
|
|
159
|
+
if (readErrorName(err) === "TimeoutError") {
|
|
90
160
|
return true;
|
|
161
|
+
}
|
|
91
162
|
const message = getErrorMessage(err);
|
|
92
|
-
return Boolean(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
|
-
|
|
168
|
+
}
|
|
169
|
+
if (!err || typeof err !== "object") {
|
|
98
170
|
return false;
|
|
99
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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 ([
|
|
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
|
-
|
|
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
|
-
|
|
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);
|