clawdentity 0.0.24 → 0.0.26
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/README.md +1 -1
- package/dist/bin.js +2066 -239
- package/dist/index.js +2066 -239
- package/package.json +1 -1
- package/skill-bundle/openclaw-skill/skill/SKILL.md +12 -3
- package/skill-bundle/openclaw-skill/skill/references/clawdentity-protocol.md +10 -1
package/dist/index.js
CHANGED
|
@@ -14361,6 +14361,9 @@ var INVITES_REDEEM_PATH = "/v1/invites/redeem";
|
|
|
14361
14361
|
var ME_API_KEYS_PATH = "/v1/me/api-keys";
|
|
14362
14362
|
var REGISTRY_METADATA_PATH = "/v1/metadata";
|
|
14363
14363
|
var RELAY_CONNECT_PATH = "/v1/relay/connect";
|
|
14364
|
+
var RELAY_DELIVERY_RECEIPTS_PATH = "/v1/relay/delivery-receipts";
|
|
14365
|
+
var RELAY_CONVERSATION_ID_HEADER = "x-claw-conversation-id";
|
|
14366
|
+
var RELAY_DELIVERY_RECEIPT_URL_HEADER = "x-claw-delivery-receipt-url";
|
|
14364
14367
|
var RELAY_RECIPIENT_AGENT_DID_HEADER = "x-claw-recipient-agent-did";
|
|
14365
14368
|
|
|
14366
14369
|
// ../../packages/protocol/src/http-signing.ts
|
|
@@ -14376,6 +14379,24 @@ function canonicalizeRequest(input) {
|
|
|
14376
14379
|
].join("\n");
|
|
14377
14380
|
}
|
|
14378
14381
|
|
|
14382
|
+
// ../../packages/sdk/src/datetime.ts
|
|
14383
|
+
function toDate(value) {
|
|
14384
|
+
const parsed = value instanceof Date ? value : new Date(value);
|
|
14385
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
14386
|
+
throw new TypeError(`Invalid datetime value: ${String(value)}`);
|
|
14387
|
+
}
|
|
14388
|
+
return parsed;
|
|
14389
|
+
}
|
|
14390
|
+
function nowUtcMs() {
|
|
14391
|
+
return Date.now();
|
|
14392
|
+
}
|
|
14393
|
+
function toIso(value) {
|
|
14394
|
+
return toDate(value).toISOString();
|
|
14395
|
+
}
|
|
14396
|
+
function nowIso() {
|
|
14397
|
+
return toIso(nowUtcMs());
|
|
14398
|
+
}
|
|
14399
|
+
|
|
14379
14400
|
// ../../node_modules/.pnpm/hono@4.11.9/node_modules/hono/dist/request/constants.js
|
|
14380
14401
|
var GET_MATCH_RESULT = /* @__PURE__ */ Symbol();
|
|
14381
14402
|
|
|
@@ -15573,7 +15594,7 @@ async function refreshAgentAuthWithClawProof(input) {
|
|
|
15573
15594
|
const refreshBody = JSON.stringify({
|
|
15574
15595
|
refreshToken: input.refreshToken
|
|
15575
15596
|
});
|
|
15576
|
-
const nowMs = input.nowMs?.() ??
|
|
15597
|
+
const nowMs = input.nowMs?.() ?? nowUtcMs();
|
|
15577
15598
|
const timestamp = String(Math.floor(nowMs / 1e3));
|
|
15578
15599
|
const nonce = encodeBase64url(crypto.getRandomValues(new Uint8Array(16)));
|
|
15579
15600
|
const signed = await signHttpRequest({
|
|
@@ -15780,11 +15801,6 @@ function parseRegistryConfig(env, options = {}) {
|
|
|
15780
15801
|
var DEFAULT_CRL_REFRESH_INTERVAL_MS = 5 * 60 * 1e3;
|
|
15781
15802
|
var DEFAULT_CRL_MAX_AGE_MS = 15 * 60 * 1e3;
|
|
15782
15803
|
|
|
15783
|
-
// ../../packages/sdk/src/datetime.ts
|
|
15784
|
-
function nowIso() {
|
|
15785
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
15786
|
-
}
|
|
15787
|
-
|
|
15788
15804
|
// ../../node_modules/.pnpm/jose@6.1.3/node_modules/jose/dist/webapi/lib/buffer_utils.js
|
|
15789
15805
|
var encoder = new TextEncoder();
|
|
15790
15806
|
var decoder = new TextDecoder();
|
|
@@ -17840,7 +17856,7 @@ var parseAgentIdFromDid = (agentName, did) => {
|
|
|
17840
17856
|
}
|
|
17841
17857
|
};
|
|
17842
17858
|
var formatExpiresAt = (expires) => {
|
|
17843
|
-
return
|
|
17859
|
+
return toIso(expires * 1e3);
|
|
17844
17860
|
};
|
|
17845
17861
|
var resolveFramework = (framework) => {
|
|
17846
17862
|
if (framework === void 0) {
|
|
@@ -17991,7 +18007,7 @@ var writeSecureFile2 = async (path, content) => {
|
|
|
17991
18007
|
await chmod2(path, FILE_MODE2);
|
|
17992
18008
|
};
|
|
17993
18009
|
var writeSecureFileAtomically = async (path, content) => {
|
|
17994
|
-
const tempPath = `${path}.tmp-${
|
|
18010
|
+
const tempPath = `${path}.tmp-${nowUtcMs()}-${Math.random().toString(16).slice(2)}`;
|
|
17995
18011
|
await writeFile2(tempPath, content, "utf-8");
|
|
17996
18012
|
await chmod2(tempPath, FILE_MODE2);
|
|
17997
18013
|
try {
|
|
@@ -18936,7 +18952,9 @@ var DEFAULT_OPENCLAW_DELIVER_MAX_ATTEMPTS = 4;
|
|
|
18936
18952
|
var DEFAULT_OPENCLAW_DELIVER_RETRY_INITIAL_DELAY_MS = 300;
|
|
18937
18953
|
var DEFAULT_OPENCLAW_DELIVER_RETRY_MAX_DELAY_MS = 2e3;
|
|
18938
18954
|
var DEFAULT_OPENCLAW_DELIVER_RETRY_BACKOFF_FACTOR = 2;
|
|
18955
|
+
var DEFAULT_CONNECT_TIMEOUT_MS = 15e3;
|
|
18939
18956
|
var DEFAULT_HEARTBEAT_INTERVAL_MS = 3e4;
|
|
18957
|
+
var DEFAULT_HEARTBEAT_ACK_TIMEOUT_MS = 6e4;
|
|
18940
18958
|
var DEFAULT_RECONNECT_MIN_DELAY_MS = 1e3;
|
|
18941
18959
|
var DEFAULT_RECONNECT_MAX_DELAY_MS = 3e4;
|
|
18942
18960
|
var DEFAULT_RECONNECT_BACKOFF_FACTOR = 2;
|
|
@@ -18953,6 +18971,15 @@ var DEFAULT_CONNECTOR_INBOUND_REPLAY_BATCH_SIZE = 25;
|
|
|
18953
18971
|
var DEFAULT_CONNECTOR_INBOUND_RETRY_INITIAL_DELAY_MS = 1e3;
|
|
18954
18972
|
var DEFAULT_CONNECTOR_INBOUND_RETRY_MAX_DELAY_MS = 6e4;
|
|
18955
18973
|
var DEFAULT_CONNECTOR_INBOUND_RETRY_BACKOFF_FACTOR = 2;
|
|
18974
|
+
var DEFAULT_CONNECTOR_INBOUND_EVENTS_MAX_BYTES = 10 * 1024 * 1024;
|
|
18975
|
+
var DEFAULT_CONNECTOR_INBOUND_EVENTS_MAX_FILES = 5;
|
|
18976
|
+
var DEFAULT_CONNECTOR_INBOUND_DEAD_LETTER_NON_RETRYABLE_MAX_ATTEMPTS = 5;
|
|
18977
|
+
var DEFAULT_CONNECTOR_OPENCLAW_PROBE_INTERVAL_MS = 1e4;
|
|
18978
|
+
var DEFAULT_CONNECTOR_OPENCLAW_PROBE_TIMEOUT_MS = 3e3;
|
|
18979
|
+
var DEFAULT_CONNECTOR_RUNTIME_REPLAY_DELIVER_MAX_ATTEMPTS = 3;
|
|
18980
|
+
var DEFAULT_CONNECTOR_RUNTIME_REPLAY_DELIVER_RETRY_INITIAL_DELAY_MS = 2e3;
|
|
18981
|
+
var DEFAULT_CONNECTOR_RUNTIME_REPLAY_DELIVER_RETRY_MAX_DELAY_MS = 8e3;
|
|
18982
|
+
var DEFAULT_CONNECTOR_RUNTIME_REPLAY_DELIVER_RETRY_BACKOFF_FACTOR = 2;
|
|
18956
18983
|
var AGENT_ACCESS_HEADER = "x-claw-agent-access";
|
|
18957
18984
|
var WS_READY_STATE_OPEN = 1;
|
|
18958
18985
|
|
|
@@ -19114,6 +19141,7 @@ function serializeFrame(frame) {
|
|
|
19114
19141
|
}
|
|
19115
19142
|
|
|
19116
19143
|
// ../../packages/connector/src/client.ts
|
|
19144
|
+
var WS_READY_STATE_CONNECTING = 0;
|
|
19117
19145
|
function isAbortError(error48) {
|
|
19118
19146
|
return error48 instanceof Error && error48.name === "AbortError";
|
|
19119
19147
|
}
|
|
@@ -19178,6 +19206,33 @@ function readCloseEvent(event) {
|
|
|
19178
19206
|
wasClean: typeof event.wasClean === "boolean" ? event.wasClean : false
|
|
19179
19207
|
};
|
|
19180
19208
|
}
|
|
19209
|
+
function readUnexpectedResponseStatus(event) {
|
|
19210
|
+
if (!isObject3(event)) {
|
|
19211
|
+
return void 0;
|
|
19212
|
+
}
|
|
19213
|
+
if (typeof event.status === "number") {
|
|
19214
|
+
return event.status;
|
|
19215
|
+
}
|
|
19216
|
+
if (typeof event.statusCode === "number") {
|
|
19217
|
+
return event.statusCode;
|
|
19218
|
+
}
|
|
19219
|
+
const response = event.response;
|
|
19220
|
+
if (isObject3(response)) {
|
|
19221
|
+
if (typeof response.status === "number") {
|
|
19222
|
+
return response.status;
|
|
19223
|
+
}
|
|
19224
|
+
if (typeof response.statusCode === "number") {
|
|
19225
|
+
return response.statusCode;
|
|
19226
|
+
}
|
|
19227
|
+
}
|
|
19228
|
+
return void 0;
|
|
19229
|
+
}
|
|
19230
|
+
function readErrorEventReason(event) {
|
|
19231
|
+
if (!isObject3(event) || !("error" in event)) {
|
|
19232
|
+
return "WebSocket error";
|
|
19233
|
+
}
|
|
19234
|
+
return sanitizeErrorReason(event.error);
|
|
19235
|
+
}
|
|
19181
19236
|
function normalizeConnectionHeaders(headers) {
|
|
19182
19237
|
if (headers === void 0) {
|
|
19183
19238
|
return {};
|
|
@@ -19199,7 +19254,9 @@ var ConnectorClient = class {
|
|
|
19199
19254
|
connectionHeadersProvider;
|
|
19200
19255
|
openclawHookUrl;
|
|
19201
19256
|
openclawHookToken;
|
|
19257
|
+
connectTimeoutMs;
|
|
19202
19258
|
heartbeatIntervalMs;
|
|
19259
|
+
heartbeatAckTimeoutMs;
|
|
19203
19260
|
reconnectMinDelayMs;
|
|
19204
19261
|
reconnectMaxDelayMs;
|
|
19205
19262
|
reconnectBackoffFactor;
|
|
@@ -19214,14 +19271,36 @@ var ConnectorClient = class {
|
|
|
19214
19271
|
fetchImpl;
|
|
19215
19272
|
logger;
|
|
19216
19273
|
hooks;
|
|
19274
|
+
outboundQueuePersistence;
|
|
19217
19275
|
inboundDeliverHandler;
|
|
19218
19276
|
now;
|
|
19219
19277
|
random;
|
|
19220
19278
|
ulidFactory;
|
|
19221
19279
|
socket;
|
|
19222
19280
|
reconnectTimeout;
|
|
19281
|
+
connectTimeout;
|
|
19223
19282
|
heartbeatInterval;
|
|
19283
|
+
heartbeatAckTimeout;
|
|
19284
|
+
pendingHeartbeatAcks = /* @__PURE__ */ new Map();
|
|
19224
19285
|
reconnectAttempt = 0;
|
|
19286
|
+
reconnectCount = 0;
|
|
19287
|
+
connectAttempts = 0;
|
|
19288
|
+
connectedSinceMs;
|
|
19289
|
+
accumulatedConnectedMs = 0;
|
|
19290
|
+
lastConnectedAtIso;
|
|
19291
|
+
heartbeatRttSampleCount = 0;
|
|
19292
|
+
heartbeatRttTotalMs = 0;
|
|
19293
|
+
heartbeatRttMaxMs = 0;
|
|
19294
|
+
heartbeatRttLastMs;
|
|
19295
|
+
inboundAckLatencySampleCount = 0;
|
|
19296
|
+
inboundAckLatencyTotalMs = 0;
|
|
19297
|
+
inboundAckLatencyMaxMs = 0;
|
|
19298
|
+
inboundAckLatencyLastMs;
|
|
19299
|
+
maxObservedOutboundQueueDepth = 0;
|
|
19300
|
+
outboundQueueLoaded = false;
|
|
19301
|
+
outboundQueueLoadPromise;
|
|
19302
|
+
outboundQueueSaveChain = Promise.resolve();
|
|
19303
|
+
authUpgradeImmediateRetryUsed = false;
|
|
19225
19304
|
started = false;
|
|
19226
19305
|
outboundQueue = [];
|
|
19227
19306
|
constructor(options) {
|
|
@@ -19231,7 +19310,17 @@ var ConnectorClient = class {
|
|
|
19231
19310
|
);
|
|
19232
19311
|
this.connectionHeadersProvider = options.connectionHeadersProvider;
|
|
19233
19312
|
this.openclawHookToken = options.openclawHookToken;
|
|
19313
|
+
this.connectTimeoutMs = Math.max(
|
|
19314
|
+
0,
|
|
19315
|
+
Math.floor(options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS)
|
|
19316
|
+
);
|
|
19234
19317
|
this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
|
|
19318
|
+
this.heartbeatAckTimeoutMs = Math.max(
|
|
19319
|
+
0,
|
|
19320
|
+
Math.floor(
|
|
19321
|
+
options.heartbeatAckTimeoutMs ?? DEFAULT_HEARTBEAT_ACK_TIMEOUT_MS
|
|
19322
|
+
)
|
|
19323
|
+
);
|
|
19235
19324
|
this.reconnectMinDelayMs = options.reconnectMinDelayMs ?? DEFAULT_RECONNECT_MIN_DELAY_MS;
|
|
19236
19325
|
this.reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
19237
19326
|
this.reconnectBackoffFactor = options.reconnectBackoffFactor ?? DEFAULT_RECONNECT_BACKOFF_FACTOR;
|
|
@@ -19269,6 +19358,7 @@ var ConnectorClient = class {
|
|
|
19269
19358
|
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
19270
19359
|
this.logger = options.logger ?? createLogger({ service: "connector", module: "client" });
|
|
19271
19360
|
this.hooks = options.hooks ?? {};
|
|
19361
|
+
this.outboundQueuePersistence = options.outboundQueuePersistence;
|
|
19272
19362
|
this.inboundDeliverHandler = options.inboundDeliverHandler;
|
|
19273
19363
|
this.now = options.now ?? Date.now;
|
|
19274
19364
|
this.random = options.random ?? Math.random;
|
|
@@ -19283,16 +19373,19 @@ var ConnectorClient = class {
|
|
|
19283
19373
|
return;
|
|
19284
19374
|
}
|
|
19285
19375
|
this.started = true;
|
|
19376
|
+
if (this.outboundQueuePersistence !== void 0) {
|
|
19377
|
+
void this.ensureOutboundQueueLoaded();
|
|
19378
|
+
}
|
|
19286
19379
|
void this.connectSocket();
|
|
19287
19380
|
}
|
|
19288
19381
|
disconnect() {
|
|
19289
19382
|
this.started = false;
|
|
19290
19383
|
this.clearReconnectTimeout();
|
|
19291
|
-
this.
|
|
19384
|
+
this.clearSocketState();
|
|
19292
19385
|
if (this.socket !== void 0) {
|
|
19293
19386
|
const socket = this.socket;
|
|
19294
19387
|
this.socket = void 0;
|
|
19295
|
-
|
|
19388
|
+
this.closeSocketQuietly(socket, 1e3, "client disconnect");
|
|
19296
19389
|
}
|
|
19297
19390
|
}
|
|
19298
19391
|
isConnected() {
|
|
@@ -19301,6 +19394,42 @@ var ConnectorClient = class {
|
|
|
19301
19394
|
getQueuedOutboundCount() {
|
|
19302
19395
|
return this.outboundQueue.length;
|
|
19303
19396
|
}
|
|
19397
|
+
getMetricsSnapshot() {
|
|
19398
|
+
const nowMs = this.now();
|
|
19399
|
+
const uptimeMs = this.accumulatedConnectedMs + (this.connectedSinceMs === void 0 ? 0 : nowMs - this.connectedSinceMs);
|
|
19400
|
+
return {
|
|
19401
|
+
connection: {
|
|
19402
|
+
connectAttempts: this.connectAttempts,
|
|
19403
|
+
connected: this.isConnected(),
|
|
19404
|
+
reconnectCount: this.reconnectCount,
|
|
19405
|
+
uptimeMs: Math.max(0, uptimeMs),
|
|
19406
|
+
lastConnectedAt: this.lastConnectedAtIso
|
|
19407
|
+
},
|
|
19408
|
+
heartbeat: {
|
|
19409
|
+
pendingAckCount: this.pendingHeartbeatAcks.size,
|
|
19410
|
+
sampleCount: this.heartbeatRttSampleCount,
|
|
19411
|
+
lastRttMs: this.heartbeatRttLastMs,
|
|
19412
|
+
maxRttMs: this.heartbeatRttSampleCount > 0 ? this.heartbeatRttMaxMs : void 0,
|
|
19413
|
+
avgRttMs: this.heartbeatRttSampleCount > 0 ? Math.floor(
|
|
19414
|
+
this.heartbeatRttTotalMs / this.heartbeatRttSampleCount
|
|
19415
|
+
) : void 0
|
|
19416
|
+
},
|
|
19417
|
+
inboundDelivery: {
|
|
19418
|
+
sampleCount: this.inboundAckLatencySampleCount,
|
|
19419
|
+
lastAckLatencyMs: this.inboundAckLatencyLastMs,
|
|
19420
|
+
maxAckLatencyMs: this.inboundAckLatencySampleCount > 0 ? this.inboundAckLatencyMaxMs : void 0,
|
|
19421
|
+
avgAckLatencyMs: this.inboundAckLatencySampleCount > 0 ? Math.floor(
|
|
19422
|
+
this.inboundAckLatencyTotalMs / this.inboundAckLatencySampleCount
|
|
19423
|
+
) : void 0
|
|
19424
|
+
},
|
|
19425
|
+
outboundQueue: {
|
|
19426
|
+
currentDepth: this.outboundQueue.length,
|
|
19427
|
+
maxDepth: this.maxObservedOutboundQueueDepth,
|
|
19428
|
+
loadedFromPersistence: this.outboundQueueLoaded,
|
|
19429
|
+
persistenceEnabled: this.outboundQueuePersistence !== void 0
|
|
19430
|
+
}
|
|
19431
|
+
};
|
|
19432
|
+
}
|
|
19304
19433
|
enqueueOutbound(input) {
|
|
19305
19434
|
const frame = enqueueFrameSchema.parse({
|
|
19306
19435
|
v: CONNECTOR_FRAME_VERSION,
|
|
@@ -19313,11 +19442,17 @@ var ConnectorClient = class {
|
|
|
19313
19442
|
replyTo: input.replyTo
|
|
19314
19443
|
});
|
|
19315
19444
|
this.outboundQueue.push(frame);
|
|
19445
|
+
this.recordOutboundQueueDepth();
|
|
19446
|
+
this.persistOutboundQueue();
|
|
19316
19447
|
this.flushOutboundQueue();
|
|
19317
19448
|
return frame;
|
|
19318
19449
|
}
|
|
19319
19450
|
async connectSocket() {
|
|
19320
19451
|
this.clearReconnectTimeout();
|
|
19452
|
+
this.connectAttempts += 1;
|
|
19453
|
+
if (this.outboundQueuePersistence !== void 0) {
|
|
19454
|
+
await this.ensureOutboundQueueLoaded();
|
|
19455
|
+
}
|
|
19321
19456
|
let connectionHeaders = this.connectionHeaders;
|
|
19322
19457
|
if (this.connectionHeadersProvider) {
|
|
19323
19458
|
try {
|
|
@@ -19344,8 +19479,18 @@ var ConnectorClient = class {
|
|
|
19344
19479
|
this.scheduleReconnect();
|
|
19345
19480
|
return;
|
|
19346
19481
|
}
|
|
19347
|
-
this.socket
|
|
19482
|
+
const socket = this.socket;
|
|
19483
|
+
this.startConnectTimeout(socket);
|
|
19484
|
+
socket.addEventListener("open", () => {
|
|
19485
|
+
if (this.socket !== socket) {
|
|
19486
|
+
return;
|
|
19487
|
+
}
|
|
19488
|
+
this.clearConnectTimeout();
|
|
19489
|
+
this.clearHeartbeatTracking();
|
|
19348
19490
|
this.reconnectAttempt = 0;
|
|
19491
|
+
this.authUpgradeImmediateRetryUsed = false;
|
|
19492
|
+
this.connectedSinceMs = this.now();
|
|
19493
|
+
this.lastConnectedAtIso = this.makeTimestamp();
|
|
19349
19494
|
this.logger.info("connector.websocket.connected", {
|
|
19350
19495
|
url: this.connectorUrl
|
|
19351
19496
|
});
|
|
@@ -19353,12 +19498,16 @@ var ConnectorClient = class {
|
|
|
19353
19498
|
this.flushOutboundQueue();
|
|
19354
19499
|
this.hooks.onConnected?.();
|
|
19355
19500
|
});
|
|
19356
|
-
|
|
19501
|
+
socket.addEventListener("message", (event) => {
|
|
19502
|
+
if (this.socket !== socket) {
|
|
19503
|
+
return;
|
|
19504
|
+
}
|
|
19357
19505
|
void this.handleIncomingMessage(readMessageEventData(event));
|
|
19358
19506
|
});
|
|
19359
|
-
|
|
19360
|
-
this.
|
|
19361
|
-
|
|
19507
|
+
socket.addEventListener("close", (event) => {
|
|
19508
|
+
if (!this.detachSocket(socket)) {
|
|
19509
|
+
return;
|
|
19510
|
+
}
|
|
19362
19511
|
const closeEvent = readCloseEvent(event);
|
|
19363
19512
|
this.logger.warn("connector.websocket.closed", {
|
|
19364
19513
|
closeCode: closeEvent.code,
|
|
@@ -19374,22 +19523,61 @@ var ConnectorClient = class {
|
|
|
19374
19523
|
this.scheduleReconnect();
|
|
19375
19524
|
}
|
|
19376
19525
|
});
|
|
19377
|
-
|
|
19526
|
+
socket.addEventListener("error", (event) => {
|
|
19527
|
+
if (this.socket !== socket) {
|
|
19528
|
+
return;
|
|
19529
|
+
}
|
|
19530
|
+
const readyState = socket.readyState;
|
|
19531
|
+
const shouldForceReconnect = readyState !== WS_READY_STATE_OPEN && readyState !== WS_READY_STATE_CONNECTING;
|
|
19532
|
+
if (!shouldForceReconnect) {
|
|
19533
|
+
this.logger.warn("connector.websocket.error", {
|
|
19534
|
+
url: this.connectorUrl,
|
|
19535
|
+
reason: readErrorEventReason(event),
|
|
19536
|
+
readyState
|
|
19537
|
+
});
|
|
19538
|
+
return;
|
|
19539
|
+
}
|
|
19540
|
+
if (!this.detachSocket(socket)) {
|
|
19541
|
+
return;
|
|
19542
|
+
}
|
|
19543
|
+
const reason = readErrorEventReason(event);
|
|
19378
19544
|
this.logger.warn("connector.websocket.error", {
|
|
19379
|
-
url: this.connectorUrl
|
|
19545
|
+
url: this.connectorUrl,
|
|
19546
|
+
reason
|
|
19380
19547
|
});
|
|
19548
|
+
this.closeSocketQuietly(socket, 1011, "websocket error");
|
|
19549
|
+
this.hooks.onDisconnected?.({
|
|
19550
|
+
code: 1006,
|
|
19551
|
+
reason,
|
|
19552
|
+
wasClean: false
|
|
19553
|
+
});
|
|
19554
|
+
if (this.started) {
|
|
19555
|
+
this.scheduleReconnect();
|
|
19556
|
+
}
|
|
19557
|
+
});
|
|
19558
|
+
socket.addEventListener("unexpected-response", (event) => {
|
|
19559
|
+
void this.handleUnexpectedResponse(socket, event);
|
|
19381
19560
|
});
|
|
19382
19561
|
}
|
|
19383
|
-
scheduleReconnect() {
|
|
19562
|
+
scheduleReconnect(options) {
|
|
19384
19563
|
if (!this.started) {
|
|
19385
19564
|
return;
|
|
19386
19565
|
}
|
|
19387
|
-
|
|
19388
|
-
|
|
19389
|
-
|
|
19390
|
-
|
|
19391
|
-
|
|
19392
|
-
|
|
19566
|
+
this.clearReconnectTimeout();
|
|
19567
|
+
let delayMs;
|
|
19568
|
+
if (options?.delayMs !== void 0) {
|
|
19569
|
+
delayMs = Math.max(0, Math.floor(options.delayMs));
|
|
19570
|
+
} else {
|
|
19571
|
+
const exponentialDelay = this.reconnectMinDelayMs * this.reconnectBackoffFactor ** this.reconnectAttempt;
|
|
19572
|
+
const boundedDelay = Math.min(exponentialDelay, this.reconnectMaxDelayMs);
|
|
19573
|
+
const jitterRange = boundedDelay * this.reconnectJitterRatio;
|
|
19574
|
+
const jitterOffset = jitterRange === 0 ? 0 : (this.random() * 2 - 1) * jitterRange;
|
|
19575
|
+
delayMs = Math.max(0, Math.floor(boundedDelay + jitterOffset));
|
|
19576
|
+
}
|
|
19577
|
+
if (options?.incrementAttempt ?? true) {
|
|
19578
|
+
this.reconnectAttempt += 1;
|
|
19579
|
+
}
|
|
19580
|
+
this.reconnectCount += 1;
|
|
19393
19581
|
this.reconnectTimeout = setTimeout(() => {
|
|
19394
19582
|
void this.connectSocket();
|
|
19395
19583
|
}, delayMs);
|
|
@@ -19400,8 +19588,124 @@ var ConnectorClient = class {
|
|
|
19400
19588
|
this.reconnectTimeout = void 0;
|
|
19401
19589
|
}
|
|
19402
19590
|
}
|
|
19591
|
+
startConnectTimeout(socket) {
|
|
19592
|
+
this.clearConnectTimeout();
|
|
19593
|
+
if (this.connectTimeoutMs <= 0) {
|
|
19594
|
+
return;
|
|
19595
|
+
}
|
|
19596
|
+
this.connectTimeout = setTimeout(() => {
|
|
19597
|
+
if (!this.detachSocket(socket)) {
|
|
19598
|
+
return;
|
|
19599
|
+
}
|
|
19600
|
+
this.logger.warn("connector.websocket.connect_timeout", {
|
|
19601
|
+
timeoutMs: this.connectTimeoutMs,
|
|
19602
|
+
url: this.connectorUrl
|
|
19603
|
+
});
|
|
19604
|
+
this.closeSocketQuietly(socket, 1e3, "connect timeout");
|
|
19605
|
+
this.hooks.onDisconnected?.({
|
|
19606
|
+
code: 1006,
|
|
19607
|
+
reason: "WebSocket connect timed out",
|
|
19608
|
+
wasClean: false
|
|
19609
|
+
});
|
|
19610
|
+
if (this.started) {
|
|
19611
|
+
this.scheduleReconnect();
|
|
19612
|
+
}
|
|
19613
|
+
}, this.connectTimeoutMs);
|
|
19614
|
+
}
|
|
19615
|
+
clearConnectTimeout() {
|
|
19616
|
+
if (this.connectTimeout !== void 0) {
|
|
19617
|
+
clearTimeout(this.connectTimeout);
|
|
19618
|
+
this.connectTimeout = void 0;
|
|
19619
|
+
}
|
|
19620
|
+
}
|
|
19621
|
+
clearSocketState() {
|
|
19622
|
+
this.clearConnectTimeout();
|
|
19623
|
+
this.clearHeartbeatTracking();
|
|
19624
|
+
}
|
|
19625
|
+
clearHeartbeatTracking() {
|
|
19626
|
+
if (this.heartbeatInterval !== void 0) {
|
|
19627
|
+
clearInterval(this.heartbeatInterval);
|
|
19628
|
+
this.heartbeatInterval = void 0;
|
|
19629
|
+
}
|
|
19630
|
+
if (this.heartbeatAckTimeout !== void 0) {
|
|
19631
|
+
clearTimeout(this.heartbeatAckTimeout);
|
|
19632
|
+
this.heartbeatAckTimeout = void 0;
|
|
19633
|
+
}
|
|
19634
|
+
this.pendingHeartbeatAcks.clear();
|
|
19635
|
+
}
|
|
19636
|
+
detachSocket(socket) {
|
|
19637
|
+
if (this.socket !== socket) {
|
|
19638
|
+
return false;
|
|
19639
|
+
}
|
|
19640
|
+
this.socket = void 0;
|
|
19641
|
+
if (this.connectedSinceMs !== void 0) {
|
|
19642
|
+
this.accumulatedConnectedMs += Math.max(
|
|
19643
|
+
0,
|
|
19644
|
+
this.now() - this.connectedSinceMs
|
|
19645
|
+
);
|
|
19646
|
+
this.connectedSinceMs = void 0;
|
|
19647
|
+
}
|
|
19648
|
+
this.clearSocketState();
|
|
19649
|
+
return true;
|
|
19650
|
+
}
|
|
19651
|
+
closeSocketQuietly(socket, code, reason) {
|
|
19652
|
+
try {
|
|
19653
|
+
socket.close(code, reason);
|
|
19654
|
+
} catch (error48) {
|
|
19655
|
+
this.logger.warn("connector.websocket.close_failed", {
|
|
19656
|
+
reason: sanitizeErrorReason(error48)
|
|
19657
|
+
});
|
|
19658
|
+
}
|
|
19659
|
+
}
|
|
19660
|
+
async handleUnexpectedResponse(socket, event) {
|
|
19661
|
+
if (!this.detachSocket(socket)) {
|
|
19662
|
+
return;
|
|
19663
|
+
}
|
|
19664
|
+
const statusCode = readUnexpectedResponseStatus(event);
|
|
19665
|
+
const isAuthRejected = statusCode === 401;
|
|
19666
|
+
const immediateRetry = isAuthRejected && !this.authUpgradeImmediateRetryUsed;
|
|
19667
|
+
if (isAuthRejected) {
|
|
19668
|
+
this.authUpgradeImmediateRetryUsed = true;
|
|
19669
|
+
await this.invokeAuthUpgradeRejectedHook({
|
|
19670
|
+
status: 401,
|
|
19671
|
+
immediateRetry
|
|
19672
|
+
});
|
|
19673
|
+
}
|
|
19674
|
+
const reason = statusCode === void 0 ? "WebSocket upgrade rejected" : `WebSocket upgrade rejected with status ${statusCode}`;
|
|
19675
|
+
this.logger.warn("connector.websocket.unexpected_response", {
|
|
19676
|
+
statusCode,
|
|
19677
|
+
immediateRetry,
|
|
19678
|
+
url: this.connectorUrl
|
|
19679
|
+
});
|
|
19680
|
+
this.closeSocketQuietly(socket, 1e3, reason);
|
|
19681
|
+
this.hooks.onDisconnected?.({
|
|
19682
|
+
code: 1006,
|
|
19683
|
+
reason,
|
|
19684
|
+
wasClean: false
|
|
19685
|
+
});
|
|
19686
|
+
if (this.started) {
|
|
19687
|
+
this.scheduleReconnect(
|
|
19688
|
+
immediateRetry ? { delayMs: 0, incrementAttempt: false } : void 0
|
|
19689
|
+
);
|
|
19690
|
+
}
|
|
19691
|
+
}
|
|
19692
|
+
async invokeAuthUpgradeRejectedHook(input) {
|
|
19693
|
+
if (this.hooks.onAuthUpgradeRejected === void 0) {
|
|
19694
|
+
return;
|
|
19695
|
+
}
|
|
19696
|
+
try {
|
|
19697
|
+
await this.hooks.onAuthUpgradeRejected(input);
|
|
19698
|
+
} catch (error48) {
|
|
19699
|
+
this.logger.warn(
|
|
19700
|
+
"connector.websocket.auth_upgrade_rejected_hook_failed",
|
|
19701
|
+
{
|
|
19702
|
+
reason: sanitizeErrorReason(error48)
|
|
19703
|
+
}
|
|
19704
|
+
);
|
|
19705
|
+
}
|
|
19706
|
+
}
|
|
19403
19707
|
startHeartbeatInterval() {
|
|
19404
|
-
this.
|
|
19708
|
+
this.clearHeartbeatTracking();
|
|
19405
19709
|
if (this.heartbeatIntervalMs <= 0) {
|
|
19406
19710
|
return;
|
|
19407
19711
|
}
|
|
@@ -19412,13 +19716,82 @@ var ConnectorClient = class {
|
|
|
19412
19716
|
id: this.makeFrameId(),
|
|
19413
19717
|
ts: this.makeTimestamp()
|
|
19414
19718
|
};
|
|
19415
|
-
this.sendFrame(frame)
|
|
19719
|
+
if (this.sendFrame(frame)) {
|
|
19720
|
+
this.trackHeartbeatAck(frame.id);
|
|
19721
|
+
}
|
|
19416
19722
|
}, this.heartbeatIntervalMs);
|
|
19417
19723
|
}
|
|
19418
|
-
|
|
19419
|
-
if (this.
|
|
19420
|
-
|
|
19421
|
-
|
|
19724
|
+
trackHeartbeatAck(ackId) {
|
|
19725
|
+
if (this.heartbeatAckTimeoutMs <= 0) {
|
|
19726
|
+
return;
|
|
19727
|
+
}
|
|
19728
|
+
this.pendingHeartbeatAcks.set(ackId, this.now());
|
|
19729
|
+
this.scheduleHeartbeatAckTimeoutCheck();
|
|
19730
|
+
}
|
|
19731
|
+
handleHeartbeatAckFrame(frame) {
|
|
19732
|
+
const sentAtMs = this.pendingHeartbeatAcks.get(frame.ackId);
|
|
19733
|
+
if (sentAtMs === void 0) {
|
|
19734
|
+
return;
|
|
19735
|
+
}
|
|
19736
|
+
this.pendingHeartbeatAcks.delete(frame.ackId);
|
|
19737
|
+
const rttMs = Math.max(0, this.now() - sentAtMs);
|
|
19738
|
+
this.heartbeatRttSampleCount += 1;
|
|
19739
|
+
this.heartbeatRttTotalMs += rttMs;
|
|
19740
|
+
this.heartbeatRttMaxMs = Math.max(this.heartbeatRttMaxMs, rttMs);
|
|
19741
|
+
this.heartbeatRttLastMs = rttMs;
|
|
19742
|
+
this.scheduleHeartbeatAckTimeoutCheck();
|
|
19743
|
+
}
|
|
19744
|
+
scheduleHeartbeatAckTimeoutCheck() {
|
|
19745
|
+
if (this.heartbeatAckTimeout !== void 0) {
|
|
19746
|
+
clearTimeout(this.heartbeatAckTimeout);
|
|
19747
|
+
this.heartbeatAckTimeout = void 0;
|
|
19748
|
+
}
|
|
19749
|
+
if (this.pendingHeartbeatAcks.size === 0 || this.heartbeatAckTimeoutMs <= 0) {
|
|
19750
|
+
return;
|
|
19751
|
+
}
|
|
19752
|
+
let oldestSentAt = Number.POSITIVE_INFINITY;
|
|
19753
|
+
for (const sentAt of this.pendingHeartbeatAcks.values()) {
|
|
19754
|
+
oldestSentAt = Math.min(oldestSentAt, sentAt);
|
|
19755
|
+
}
|
|
19756
|
+
const elapsedMs = this.now() - oldestSentAt;
|
|
19757
|
+
const delayMs = Math.max(0, this.heartbeatAckTimeoutMs - elapsedMs);
|
|
19758
|
+
this.heartbeatAckTimeout = setTimeout(() => {
|
|
19759
|
+
this.heartbeatAckTimeout = void 0;
|
|
19760
|
+
this.handleHeartbeatAckTimeout();
|
|
19761
|
+
}, delayMs);
|
|
19762
|
+
}
|
|
19763
|
+
handleHeartbeatAckTimeout() {
|
|
19764
|
+
const pendingCount = this.pendingHeartbeatAcks.size;
|
|
19765
|
+
if (pendingCount === 0) {
|
|
19766
|
+
return;
|
|
19767
|
+
}
|
|
19768
|
+
let oldestSentAt = Number.POSITIVE_INFINITY;
|
|
19769
|
+
for (const sentAt of this.pendingHeartbeatAcks.values()) {
|
|
19770
|
+
oldestSentAt = Math.min(oldestSentAt, sentAt);
|
|
19771
|
+
}
|
|
19772
|
+
const nowMs = this.now();
|
|
19773
|
+
const oldestPendingAgeMs = nowMs - oldestSentAt;
|
|
19774
|
+
if (oldestPendingAgeMs < this.heartbeatAckTimeoutMs) {
|
|
19775
|
+
this.scheduleHeartbeatAckTimeoutCheck();
|
|
19776
|
+
return;
|
|
19777
|
+
}
|
|
19778
|
+
const socket = this.socket;
|
|
19779
|
+
if (socket === void 0 || !this.detachSocket(socket)) {
|
|
19780
|
+
return;
|
|
19781
|
+
}
|
|
19782
|
+
this.logger.warn("connector.websocket.heartbeat_ack_timeout", {
|
|
19783
|
+
pendingCount,
|
|
19784
|
+
oldestPendingAgeMs,
|
|
19785
|
+
timeoutMs: this.heartbeatAckTimeoutMs
|
|
19786
|
+
});
|
|
19787
|
+
this.closeSocketQuietly(socket, 1e3, "heartbeat ack timeout");
|
|
19788
|
+
this.hooks.onDisconnected?.({
|
|
19789
|
+
code: 1006,
|
|
19790
|
+
reason: "Heartbeat acknowledgement timed out",
|
|
19791
|
+
wasClean: false
|
|
19792
|
+
});
|
|
19793
|
+
if (this.started) {
|
|
19794
|
+
this.scheduleReconnect();
|
|
19422
19795
|
}
|
|
19423
19796
|
}
|
|
19424
19797
|
flushOutboundQueue() {
|
|
@@ -19432,7 +19805,74 @@ var ConnectorClient = class {
|
|
|
19432
19805
|
return;
|
|
19433
19806
|
}
|
|
19434
19807
|
this.outboundQueue.shift();
|
|
19808
|
+
this.persistOutboundQueue();
|
|
19809
|
+
}
|
|
19810
|
+
}
|
|
19811
|
+
recordOutboundQueueDepth() {
|
|
19812
|
+
this.maxObservedOutboundQueueDepth = Math.max(
|
|
19813
|
+
this.maxObservedOutboundQueueDepth,
|
|
19814
|
+
this.outboundQueue.length
|
|
19815
|
+
);
|
|
19816
|
+
}
|
|
19817
|
+
persistOutboundQueue() {
|
|
19818
|
+
if (this.outboundQueuePersistence === void 0) {
|
|
19819
|
+
return;
|
|
19820
|
+
}
|
|
19821
|
+
this.outboundQueueSaveChain = this.outboundQueueSaveChain.then(async () => {
|
|
19822
|
+
await this.ensureOutboundQueueLoaded();
|
|
19823
|
+
await this.outboundQueuePersistence?.save([...this.outboundQueue]);
|
|
19824
|
+
}).catch((error48) => {
|
|
19825
|
+
this.logger.warn("connector.outbound.persistence_save_failed", {
|
|
19826
|
+
reason: sanitizeErrorReason(error48)
|
|
19827
|
+
});
|
|
19828
|
+
});
|
|
19829
|
+
}
|
|
19830
|
+
async ensureOutboundQueueLoaded() {
|
|
19831
|
+
if (this.outboundQueueLoaded) {
|
|
19832
|
+
return;
|
|
19833
|
+
}
|
|
19834
|
+
if (this.outboundQueuePersistence === void 0) {
|
|
19835
|
+
this.outboundQueueLoaded = true;
|
|
19836
|
+
return;
|
|
19435
19837
|
}
|
|
19838
|
+
if (this.outboundQueueLoadPromise !== void 0) {
|
|
19839
|
+
await this.outboundQueueLoadPromise;
|
|
19840
|
+
return;
|
|
19841
|
+
}
|
|
19842
|
+
this.outboundQueueLoadPromise = (async () => {
|
|
19843
|
+
try {
|
|
19844
|
+
const loadedFrames = await this.outboundQueuePersistence?.load();
|
|
19845
|
+
if (!loadedFrames || loadedFrames.length === 0) {
|
|
19846
|
+
return;
|
|
19847
|
+
}
|
|
19848
|
+
const existingIds = new Set(this.outboundQueue.map((item) => item.id));
|
|
19849
|
+
const validLoadedFrames = [];
|
|
19850
|
+
for (const candidate of loadedFrames) {
|
|
19851
|
+
const parsed = enqueueFrameSchema.safeParse(candidate);
|
|
19852
|
+
if (!parsed.success) {
|
|
19853
|
+
continue;
|
|
19854
|
+
}
|
|
19855
|
+
if (existingIds.has(parsed.data.id)) {
|
|
19856
|
+
continue;
|
|
19857
|
+
}
|
|
19858
|
+
validLoadedFrames.push(parsed.data);
|
|
19859
|
+
existingIds.add(parsed.data.id);
|
|
19860
|
+
}
|
|
19861
|
+
if (validLoadedFrames.length === 0) {
|
|
19862
|
+
return;
|
|
19863
|
+
}
|
|
19864
|
+
this.outboundQueue.unshift(...validLoadedFrames);
|
|
19865
|
+
this.recordOutboundQueueDepth();
|
|
19866
|
+
} catch (error48) {
|
|
19867
|
+
this.logger.warn("connector.outbound.persistence_load_failed", {
|
|
19868
|
+
reason: sanitizeErrorReason(error48)
|
|
19869
|
+
});
|
|
19870
|
+
} finally {
|
|
19871
|
+
this.outboundQueueLoaded = true;
|
|
19872
|
+
}
|
|
19873
|
+
})();
|
|
19874
|
+
await this.outboundQueueLoadPromise;
|
|
19875
|
+
this.flushOutboundQueue();
|
|
19436
19876
|
}
|
|
19437
19877
|
sendFrame(frame) {
|
|
19438
19878
|
const socket = this.socket;
|
|
@@ -19466,6 +19906,10 @@ var ConnectorClient = class {
|
|
|
19466
19906
|
this.handleHeartbeatFrame(frame);
|
|
19467
19907
|
return;
|
|
19468
19908
|
}
|
|
19909
|
+
if (frame.type === "heartbeat_ack") {
|
|
19910
|
+
this.handleHeartbeatAckFrame(frame);
|
|
19911
|
+
return;
|
|
19912
|
+
}
|
|
19469
19913
|
if (frame.type === "deliver") {
|
|
19470
19914
|
await this.handleDeliverFrame(frame);
|
|
19471
19915
|
return;
|
|
@@ -19482,6 +19926,7 @@ var ConnectorClient = class {
|
|
|
19482
19926
|
this.sendFrame(ackFrame);
|
|
19483
19927
|
}
|
|
19484
19928
|
async handleDeliverFrame(frame) {
|
|
19929
|
+
const startedAtMs = this.now();
|
|
19485
19930
|
if (this.inboundDeliverHandler !== void 0) {
|
|
19486
19931
|
try {
|
|
19487
19932
|
const result = await this.inboundDeliverHandler(frame);
|
|
@@ -19505,6 +19950,7 @@ var ConnectorClient = class {
|
|
|
19505
19950
|
)
|
|
19506
19951
|
);
|
|
19507
19952
|
}
|
|
19953
|
+
this.recordInboundDeliveryAckLatency(this.now() - startedAtMs);
|
|
19508
19954
|
} catch (error48) {
|
|
19509
19955
|
const ackFrame = {
|
|
19510
19956
|
v: CONNECTOR_FRAME_VERSION,
|
|
@@ -19517,6 +19963,7 @@ var ConnectorClient = class {
|
|
|
19517
19963
|
};
|
|
19518
19964
|
this.sendFrame(ackFrame);
|
|
19519
19965
|
this.hooks.onDeliverFailed?.(frame, error48);
|
|
19966
|
+
this.recordInboundDeliveryAckLatency(this.now() - startedAtMs);
|
|
19520
19967
|
}
|
|
19521
19968
|
return;
|
|
19522
19969
|
}
|
|
@@ -19532,6 +19979,7 @@ var ConnectorClient = class {
|
|
|
19532
19979
|
};
|
|
19533
19980
|
this.sendFrame(ackFrame);
|
|
19534
19981
|
this.hooks.onDeliverSucceeded?.(frame);
|
|
19982
|
+
this.recordInboundDeliveryAckLatency(this.now() - startedAtMs);
|
|
19535
19983
|
} catch (error48) {
|
|
19536
19984
|
const ackFrame = {
|
|
19537
19985
|
v: CONNECTOR_FRAME_VERSION,
|
|
@@ -19544,8 +19992,19 @@ var ConnectorClient = class {
|
|
|
19544
19992
|
};
|
|
19545
19993
|
this.sendFrame(ackFrame);
|
|
19546
19994
|
this.hooks.onDeliverFailed?.(frame, error48);
|
|
19995
|
+
this.recordInboundDeliveryAckLatency(this.now() - startedAtMs);
|
|
19547
19996
|
}
|
|
19548
19997
|
}
|
|
19998
|
+
recordInboundDeliveryAckLatency(durationMs) {
|
|
19999
|
+
const latencyMs = Math.max(0, Math.floor(durationMs));
|
|
20000
|
+
this.inboundAckLatencySampleCount += 1;
|
|
20001
|
+
this.inboundAckLatencyTotalMs += latencyMs;
|
|
20002
|
+
this.inboundAckLatencyMaxMs = Math.max(
|
|
20003
|
+
this.inboundAckLatencyMaxMs,
|
|
20004
|
+
latencyMs
|
|
20005
|
+
);
|
|
20006
|
+
this.inboundAckLatencyLastMs = latencyMs;
|
|
20007
|
+
}
|
|
19549
20008
|
async deliverToLocalOpenclaw(frame) {
|
|
19550
20009
|
const controller = new AbortController();
|
|
19551
20010
|
const timeout = setTimeout(() => {
|
|
@@ -19553,6 +20012,9 @@ var ConnectorClient = class {
|
|
|
19553
20012
|
}, this.openclawDeliverTimeoutMs);
|
|
19554
20013
|
const headers = {
|
|
19555
20014
|
"content-type": "application/json",
|
|
20015
|
+
"x-clawdentity-agent-did": frame.fromAgentDid,
|
|
20016
|
+
"x-clawdentity-to-agent-did": frame.toAgentDid,
|
|
20017
|
+
"x-clawdentity-verified": "true",
|
|
19556
20018
|
"x-request-id": frame.id
|
|
19557
20019
|
};
|
|
19558
20020
|
if (this.openclawHookToken !== void 0) {
|
|
@@ -19568,7 +20030,7 @@ var ConnectorClient = class {
|
|
|
19568
20030
|
if (!response.ok) {
|
|
19569
20031
|
throw new LocalOpenclawDeliveryError({
|
|
19570
20032
|
message: `Local OpenClaw hook rejected payload with status ${response.status}`,
|
|
19571
|
-
retryable: response.status >= 500 || response.status === 404 || response.status === 429
|
|
20033
|
+
retryable: response.status === 401 || response.status === 403 || response.status >= 500 || response.status === 404 || response.status === 429
|
|
19572
20034
|
});
|
|
19573
20035
|
}
|
|
19574
20036
|
} catch (error48) {
|
|
@@ -19631,7 +20093,7 @@ var ConnectorClient = class {
|
|
|
19631
20093
|
return this.ulidFactory(this.now());
|
|
19632
20094
|
}
|
|
19633
20095
|
makeTimestamp() {
|
|
19634
|
-
return
|
|
20096
|
+
return toIso(this.now());
|
|
19635
20097
|
}
|
|
19636
20098
|
};
|
|
19637
20099
|
|
|
@@ -19641,36 +20103,44 @@ import {
|
|
|
19641
20103
|
mkdir as mkdir3,
|
|
19642
20104
|
readFile as readFile3,
|
|
19643
20105
|
rename as rename2,
|
|
20106
|
+
stat as stat2,
|
|
20107
|
+
unlink as unlink2,
|
|
19644
20108
|
writeFile as writeFile3
|
|
19645
20109
|
} from "fs/promises";
|
|
19646
20110
|
import { dirname as dirname2, join as join4 } from "path";
|
|
19647
20111
|
var INBOUND_INBOX_DIR_NAME = "inbound-inbox";
|
|
19648
20112
|
var INBOUND_INBOX_INDEX_FILE_NAME = "index.json";
|
|
20113
|
+
var INBOUND_INBOX_INDEX_LOCK_FILE_NAME = "index.lock";
|
|
19649
20114
|
var INBOUND_INBOX_EVENTS_FILE_NAME = "events.jsonl";
|
|
19650
|
-
var INBOUND_INBOX_SCHEMA_VERSION =
|
|
19651
|
-
|
|
19652
|
-
|
|
19653
|
-
|
|
20115
|
+
var INBOUND_INBOX_SCHEMA_VERSION = 2;
|
|
20116
|
+
var DEFAULT_INDEX_LOCK_TIMEOUT_MS = 5e3;
|
|
20117
|
+
var DEFAULT_INDEX_LOCK_STALE_MS = 3e4;
|
|
20118
|
+
var DEFAULT_INDEX_LOCK_RETRY_MS = 50;
|
|
19654
20119
|
function isRecord5(value) {
|
|
19655
20120
|
return typeof value === "object" && value !== null;
|
|
19656
20121
|
}
|
|
20122
|
+
function parseOptionalNonEmptyString(value) {
|
|
20123
|
+
if (typeof value !== "string") {
|
|
20124
|
+
return void 0;
|
|
20125
|
+
}
|
|
20126
|
+
const trimmed = value.trim();
|
|
20127
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
20128
|
+
}
|
|
19657
20129
|
function parsePendingItem(value) {
|
|
19658
20130
|
if (!isRecord5(value)) {
|
|
19659
20131
|
return void 0;
|
|
19660
20132
|
}
|
|
19661
|
-
const id =
|
|
19662
|
-
const requestId =
|
|
19663
|
-
const fromAgentDid =
|
|
19664
|
-
const toAgentDid =
|
|
19665
|
-
const receivedAt =
|
|
19666
|
-
const nextAttemptAt =
|
|
20133
|
+
const id = parseOptionalNonEmptyString(value.id) ?? "";
|
|
20134
|
+
const requestId = parseOptionalNonEmptyString(value.requestId) ?? "";
|
|
20135
|
+
const fromAgentDid = parseOptionalNonEmptyString(value.fromAgentDid) ?? "";
|
|
20136
|
+
const toAgentDid = parseOptionalNonEmptyString(value.toAgentDid) ?? "";
|
|
20137
|
+
const receivedAt = parseOptionalNonEmptyString(value.receivedAt) ?? "";
|
|
20138
|
+
const nextAttemptAt = parseOptionalNonEmptyString(value.nextAttemptAt) ?? "";
|
|
19667
20139
|
const attemptCount = typeof value.attemptCount === "number" && Number.isInteger(value.attemptCount) ? value.attemptCount : NaN;
|
|
19668
20140
|
const payloadBytes = typeof value.payloadBytes === "number" && Number.isInteger(value.payloadBytes) ? value.payloadBytes : NaN;
|
|
19669
20141
|
if (id.length === 0 || requestId.length === 0 || fromAgentDid.length === 0 || toAgentDid.length === 0 || receivedAt.length === 0 || nextAttemptAt.length === 0 || !Number.isFinite(attemptCount) || attemptCount < 0 || !Number.isFinite(payloadBytes) || payloadBytes < 0) {
|
|
19670
20142
|
return void 0;
|
|
19671
20143
|
}
|
|
19672
|
-
const lastError = typeof value.lastError === "string" ? value.lastError : void 0;
|
|
19673
|
-
const lastAttemptAt = typeof value.lastAttemptAt === "string" ? value.lastAttemptAt : void 0;
|
|
19674
20144
|
return {
|
|
19675
20145
|
id,
|
|
19676
20146
|
requestId,
|
|
@@ -19681,41 +20151,89 @@ function parsePendingItem(value) {
|
|
|
19681
20151
|
receivedAt,
|
|
19682
20152
|
nextAttemptAt,
|
|
19683
20153
|
attemptCount,
|
|
19684
|
-
lastError,
|
|
19685
|
-
lastAttemptAt
|
|
20154
|
+
lastError: parseOptionalNonEmptyString(value.lastError),
|
|
20155
|
+
lastAttemptAt: parseOptionalNonEmptyString(value.lastAttemptAt),
|
|
20156
|
+
conversationId: parseOptionalNonEmptyString(value.conversationId),
|
|
20157
|
+
replyTo: parseOptionalNonEmptyString(value.replyTo)
|
|
20158
|
+
};
|
|
20159
|
+
}
|
|
20160
|
+
function parseDeadLetterItem(value) {
|
|
20161
|
+
const pending = parsePendingItem(value);
|
|
20162
|
+
if (!pending) {
|
|
20163
|
+
return void 0;
|
|
20164
|
+
}
|
|
20165
|
+
if (!isRecord5(value)) {
|
|
20166
|
+
return void 0;
|
|
20167
|
+
}
|
|
20168
|
+
const deadLetteredAt = parseOptionalNonEmptyString(value.deadLetteredAt) ?? "";
|
|
20169
|
+
const deadLetterReason = parseOptionalNonEmptyString(value.deadLetterReason) ?? "";
|
|
20170
|
+
if (deadLetteredAt.length === 0 || deadLetterReason.length === 0) {
|
|
20171
|
+
return void 0;
|
|
20172
|
+
}
|
|
20173
|
+
return {
|
|
20174
|
+
...pending,
|
|
20175
|
+
deadLetteredAt,
|
|
20176
|
+
deadLetterReason
|
|
19686
20177
|
};
|
|
19687
20178
|
}
|
|
19688
20179
|
function toDefaultIndexFile() {
|
|
19689
20180
|
return {
|
|
19690
20181
|
version: INBOUND_INBOX_SCHEMA_VERSION,
|
|
19691
20182
|
pendingBytes: 0,
|
|
20183
|
+
deadLetterBytes: 0,
|
|
19692
20184
|
pendingByRequestId: {},
|
|
19693
|
-
|
|
20185
|
+
deadLetterByRequestId: {},
|
|
20186
|
+
updatedAt: nowIso()
|
|
19694
20187
|
};
|
|
19695
20188
|
}
|
|
19696
20189
|
function normalizeIndexFile(raw) {
|
|
19697
20190
|
if (!isRecord5(raw)) {
|
|
19698
20191
|
throw new Error("Inbound inbox index root must be an object");
|
|
19699
20192
|
}
|
|
20193
|
+
if (raw.version !== INBOUND_INBOX_SCHEMA_VERSION) {
|
|
20194
|
+
throw new Error(
|
|
20195
|
+
`Inbound inbox index schema version ${String(raw.version)} is unsupported`
|
|
20196
|
+
);
|
|
20197
|
+
}
|
|
19700
20198
|
const pendingByRequestIdRaw = raw.pendingByRequestId;
|
|
20199
|
+
const deadLetterByRequestIdRaw = raw.deadLetterByRequestId;
|
|
19701
20200
|
if (!isRecord5(pendingByRequestIdRaw)) {
|
|
19702
20201
|
throw new Error("Inbound inbox index pendingByRequestId must be an object");
|
|
19703
20202
|
}
|
|
20203
|
+
if (!isRecord5(deadLetterByRequestIdRaw)) {
|
|
20204
|
+
throw new Error(
|
|
20205
|
+
"Inbound inbox index deadLetterByRequestId must be an object"
|
|
20206
|
+
);
|
|
20207
|
+
}
|
|
19704
20208
|
const pendingByRequestId = {};
|
|
19705
20209
|
let pendingBytes = 0;
|
|
19706
20210
|
for (const [requestId, candidate] of Object.entries(pendingByRequestIdRaw)) {
|
|
19707
20211
|
const entry = parsePendingItem(candidate);
|
|
19708
|
-
if (entry
|
|
20212
|
+
if (!entry || entry.requestId !== requestId) {
|
|
19709
20213
|
continue;
|
|
19710
20214
|
}
|
|
19711
20215
|
pendingByRequestId[requestId] = entry;
|
|
19712
20216
|
pendingBytes += entry.payloadBytes;
|
|
19713
20217
|
}
|
|
20218
|
+
const deadLetterByRequestId = {};
|
|
20219
|
+
let deadLetterBytes = 0;
|
|
20220
|
+
for (const [requestId, candidate] of Object.entries(
|
|
20221
|
+
deadLetterByRequestIdRaw
|
|
20222
|
+
)) {
|
|
20223
|
+
const entry = parseDeadLetterItem(candidate);
|
|
20224
|
+
if (!entry || entry.requestId !== requestId) {
|
|
20225
|
+
continue;
|
|
20226
|
+
}
|
|
20227
|
+
deadLetterByRequestId[requestId] = entry;
|
|
20228
|
+
deadLetterBytes += entry.payloadBytes;
|
|
20229
|
+
}
|
|
19714
20230
|
return {
|
|
19715
|
-
version:
|
|
19716
|
-
pendingBytes,
|
|
20231
|
+
version: INBOUND_INBOX_SCHEMA_VERSION,
|
|
19717
20232
|
pendingByRequestId,
|
|
19718
|
-
|
|
20233
|
+
deadLetterByRequestId,
|
|
20234
|
+
pendingBytes,
|
|
20235
|
+
deadLetterBytes,
|
|
20236
|
+
updatedAt: parseOptionalNonEmptyString(raw.updatedAt) ?? nowIso()
|
|
19719
20237
|
};
|
|
19720
20238
|
}
|
|
19721
20239
|
function toComparableTimeMs(value) {
|
|
@@ -19727,11 +20245,14 @@ function toComparableTimeMs(value) {
|
|
|
19727
20245
|
}
|
|
19728
20246
|
var ConnectorInboundInbox = class {
|
|
19729
20247
|
agentName;
|
|
20248
|
+
eventsMaxBytes;
|
|
20249
|
+
eventsMaxFiles;
|
|
19730
20250
|
eventsPath;
|
|
20251
|
+
inboxDir;
|
|
19731
20252
|
indexPath;
|
|
20253
|
+
indexLockPath;
|
|
19732
20254
|
maxPendingBytes;
|
|
19733
20255
|
maxPendingMessages;
|
|
19734
|
-
inboxDir;
|
|
19735
20256
|
writeChain = Promise.resolve();
|
|
19736
20257
|
constructor(options) {
|
|
19737
20258
|
this.agentName = options.agentName;
|
|
@@ -19742,15 +20263,20 @@ var ConnectorInboundInbox = class {
|
|
|
19742
20263
|
INBOUND_INBOX_DIR_NAME
|
|
19743
20264
|
);
|
|
19744
20265
|
this.indexPath = join4(this.inboxDir, INBOUND_INBOX_INDEX_FILE_NAME);
|
|
20266
|
+
this.indexLockPath = join4(
|
|
20267
|
+
this.inboxDir,
|
|
20268
|
+
INBOUND_INBOX_INDEX_LOCK_FILE_NAME
|
|
20269
|
+
);
|
|
19745
20270
|
this.eventsPath = join4(this.inboxDir, INBOUND_INBOX_EVENTS_FILE_NAME);
|
|
19746
20271
|
this.maxPendingBytes = options.maxPendingBytes;
|
|
19747
20272
|
this.maxPendingMessages = options.maxPendingMessages;
|
|
20273
|
+
this.eventsMaxBytes = Math.max(0, options.eventsMaxBytes);
|
|
20274
|
+
this.eventsMaxFiles = Math.max(0, options.eventsMaxFiles);
|
|
19748
20275
|
}
|
|
19749
20276
|
async enqueue(frame) {
|
|
19750
20277
|
return await this.withWriteLock(async () => {
|
|
19751
20278
|
const index = await this.loadIndex();
|
|
19752
|
-
|
|
19753
|
-
if (existing !== void 0) {
|
|
20279
|
+
if (index.pendingByRequestId[frame.id] !== void 0 || index.deadLetterByRequestId[frame.id] !== void 0) {
|
|
19754
20280
|
await this.appendEvent({
|
|
19755
20281
|
type: "inbound_duplicate",
|
|
19756
20282
|
requestId: frame.id
|
|
@@ -19789,13 +20315,15 @@ var ConnectorInboundInbox = class {
|
|
|
19789
20315
|
toAgentDid: frame.toAgentDid,
|
|
19790
20316
|
payload: frame.payload,
|
|
19791
20317
|
payloadBytes,
|
|
19792
|
-
receivedAt:
|
|
19793
|
-
nextAttemptAt:
|
|
19794
|
-
attemptCount: 0
|
|
20318
|
+
receivedAt: nowIso(),
|
|
20319
|
+
nextAttemptAt: nowIso(),
|
|
20320
|
+
attemptCount: 0,
|
|
20321
|
+
conversationId: parseOptionalNonEmptyString(frame.conversationId),
|
|
20322
|
+
replyTo: parseOptionalNonEmptyString(frame.replyTo)
|
|
19795
20323
|
};
|
|
19796
20324
|
index.pendingByRequestId[pendingItem.requestId] = pendingItem;
|
|
19797
20325
|
index.pendingBytes += pendingItem.payloadBytes;
|
|
19798
|
-
index.updatedAt =
|
|
20326
|
+
index.updatedAt = nowIso();
|
|
19799
20327
|
await this.saveIndex(index);
|
|
19800
20328
|
await this.appendEvent({
|
|
19801
20329
|
type: "inbound_persisted",
|
|
@@ -19803,7 +20331,9 @@ var ConnectorInboundInbox = class {
|
|
|
19803
20331
|
details: {
|
|
19804
20332
|
payloadBytes,
|
|
19805
20333
|
fromAgentDid: pendingItem.fromAgentDid,
|
|
19806
|
-
toAgentDid: pendingItem.toAgentDid
|
|
20334
|
+
toAgentDid: pendingItem.toAgentDid,
|
|
20335
|
+
conversationId: pendingItem.conversationId,
|
|
20336
|
+
replyTo: pendingItem.replyTo
|
|
19807
20337
|
}
|
|
19808
20338
|
});
|
|
19809
20339
|
return {
|
|
@@ -19834,7 +20364,7 @@ var ConnectorInboundInbox = class {
|
|
|
19834
20364
|
}
|
|
19835
20365
|
delete index.pendingByRequestId[requestId];
|
|
19836
20366
|
index.pendingBytes = Math.max(0, index.pendingBytes - entry.payloadBytes);
|
|
19837
|
-
index.updatedAt =
|
|
20367
|
+
index.updatedAt = nowIso();
|
|
19838
20368
|
await this.saveIndex(index);
|
|
19839
20369
|
await this.appendEvent({
|
|
19840
20370
|
type: "replay_succeeded",
|
|
@@ -19843,17 +20373,44 @@ var ConnectorInboundInbox = class {
|
|
|
19843
20373
|
});
|
|
19844
20374
|
}
|
|
19845
20375
|
async markReplayFailure(input) {
|
|
19846
|
-
await this.withWriteLock(async () => {
|
|
20376
|
+
return await this.withWriteLock(async () => {
|
|
19847
20377
|
const index = await this.loadIndex();
|
|
19848
20378
|
const entry = index.pendingByRequestId[input.requestId];
|
|
19849
20379
|
if (entry === void 0) {
|
|
19850
|
-
return;
|
|
20380
|
+
return { movedToDeadLetter: false };
|
|
19851
20381
|
}
|
|
19852
20382
|
entry.attemptCount += 1;
|
|
19853
20383
|
entry.lastError = input.errorMessage;
|
|
19854
|
-
entry.lastAttemptAt =
|
|
20384
|
+
entry.lastAttemptAt = nowIso();
|
|
20385
|
+
const shouldMoveToDeadLetter = !input.retryable && entry.attemptCount >= Math.max(1, input.maxNonRetryableAttempts);
|
|
20386
|
+
if (shouldMoveToDeadLetter) {
|
|
20387
|
+
const deadLetterEntry = {
|
|
20388
|
+
...entry,
|
|
20389
|
+
deadLetteredAt: nowIso(),
|
|
20390
|
+
deadLetterReason: input.errorMessage
|
|
20391
|
+
};
|
|
20392
|
+
delete index.pendingByRequestId[input.requestId];
|
|
20393
|
+
index.pendingBytes = Math.max(
|
|
20394
|
+
0,
|
|
20395
|
+
index.pendingBytes - entry.payloadBytes
|
|
20396
|
+
);
|
|
20397
|
+
index.deadLetterByRequestId[input.requestId] = deadLetterEntry;
|
|
20398
|
+
index.deadLetterBytes += deadLetterEntry.payloadBytes;
|
|
20399
|
+
index.updatedAt = nowIso();
|
|
20400
|
+
await this.saveIndex(index);
|
|
20401
|
+
await this.appendEvent({
|
|
20402
|
+
type: "dead_letter_moved",
|
|
20403
|
+
requestId: input.requestId,
|
|
20404
|
+
details: {
|
|
20405
|
+
attemptCount: deadLetterEntry.attemptCount,
|
|
20406
|
+
retryable: input.retryable,
|
|
20407
|
+
errorMessage: input.errorMessage
|
|
20408
|
+
}
|
|
20409
|
+
});
|
|
20410
|
+
return { movedToDeadLetter: true };
|
|
20411
|
+
}
|
|
19855
20412
|
entry.nextAttemptAt = input.nextAttemptAt;
|
|
19856
|
-
index.updatedAt =
|
|
20413
|
+
index.updatedAt = nowIso();
|
|
19857
20414
|
await this.saveIndex(index);
|
|
19858
20415
|
await this.appendEvent({
|
|
19859
20416
|
type: "replay_failed",
|
|
@@ -19861,62 +20418,184 @@ var ConnectorInboundInbox = class {
|
|
|
19861
20418
|
details: {
|
|
19862
20419
|
attemptCount: entry.attemptCount,
|
|
19863
20420
|
nextAttemptAt: input.nextAttemptAt,
|
|
20421
|
+
retryable: input.retryable,
|
|
19864
20422
|
errorMessage: input.errorMessage
|
|
19865
20423
|
}
|
|
19866
20424
|
});
|
|
20425
|
+
return { movedToDeadLetter: false };
|
|
19867
20426
|
});
|
|
19868
20427
|
}
|
|
19869
|
-
async
|
|
19870
|
-
await this.
|
|
19871
|
-
|
|
19872
|
-
|
|
19873
|
-
|
|
19874
|
-
|
|
20428
|
+
async listDeadLetter(input) {
|
|
20429
|
+
const index = await this.loadIndex();
|
|
20430
|
+
const entries = Object.values(index.deadLetterByRequestId).sort(
|
|
20431
|
+
(left, right) => {
|
|
20432
|
+
const leftDeadAt = toComparableTimeMs(left.deadLetteredAt);
|
|
20433
|
+
const rightDeadAt = toComparableTimeMs(right.deadLetteredAt);
|
|
20434
|
+
if (leftDeadAt !== rightDeadAt) {
|
|
20435
|
+
return leftDeadAt - rightDeadAt;
|
|
20436
|
+
}
|
|
20437
|
+
return toComparableTimeMs(left.receivedAt) - toComparableTimeMs(right.receivedAt);
|
|
19875
20438
|
}
|
|
19876
|
-
|
|
19877
|
-
|
|
19878
|
-
|
|
19879
|
-
|
|
19880
|
-
|
|
19881
|
-
|
|
20439
|
+
);
|
|
20440
|
+
const limit = Math.max(1, input?.limit ?? (entries.length || 1));
|
|
20441
|
+
return entries.slice(0, limit);
|
|
20442
|
+
}
|
|
20443
|
+
async replayDeadLetter(input) {
|
|
20444
|
+
return await this.withWriteLock(async () => {
|
|
20445
|
+
const index = await this.loadIndex();
|
|
20446
|
+
const requestIds = input?.requestIds !== void 0 ? Array.from(
|
|
20447
|
+
new Set(
|
|
20448
|
+
input.requestIds.map((item) => item.trim()).filter((item) => item.length > 0)
|
|
20449
|
+
)
|
|
20450
|
+
) : Object.keys(index.deadLetterByRequestId);
|
|
20451
|
+
let replayedCount = 0;
|
|
20452
|
+
for (const requestId of requestIds) {
|
|
20453
|
+
if (requestId.length === 0) {
|
|
19882
20454
|
continue;
|
|
19883
20455
|
}
|
|
19884
|
-
|
|
19885
|
-
|
|
19886
|
-
|
|
19887
|
-
index.pendingByRequestId = after;
|
|
19888
|
-
index.pendingBytes = pendingBytes;
|
|
19889
|
-
index.updatedAt = nowIso2();
|
|
19890
|
-
await this.saveIndex(index);
|
|
19891
|
-
await this.appendEvent({
|
|
19892
|
-
type: "inbox_pruned",
|
|
19893
|
-
details: {
|
|
19894
|
-
beforeCount,
|
|
19895
|
-
afterCount: Object.keys(after).length
|
|
20456
|
+
const dead = index.deadLetterByRequestId[requestId];
|
|
20457
|
+
if (!dead) {
|
|
20458
|
+
continue;
|
|
19896
20459
|
}
|
|
19897
|
-
|
|
19898
|
-
|
|
20460
|
+
delete index.deadLetterByRequestId[requestId];
|
|
20461
|
+
index.deadLetterBytes = Math.max(
|
|
20462
|
+
0,
|
|
20463
|
+
index.deadLetterBytes - dead.payloadBytes
|
|
20464
|
+
);
|
|
20465
|
+
index.pendingByRequestId[requestId] = {
|
|
20466
|
+
...dead,
|
|
20467
|
+
nextAttemptAt: nowIso(),
|
|
20468
|
+
lastError: dead.deadLetterReason
|
|
20469
|
+
};
|
|
20470
|
+
index.pendingBytes += dead.payloadBytes;
|
|
20471
|
+
replayedCount += 1;
|
|
20472
|
+
await this.appendEvent({
|
|
20473
|
+
type: "dead_letter_replayed",
|
|
20474
|
+
requestId,
|
|
20475
|
+
details: {
|
|
20476
|
+
deadLetteredAt: dead.deadLetteredAt,
|
|
20477
|
+
deadLetterReason: dead.deadLetterReason
|
|
20478
|
+
}
|
|
20479
|
+
});
|
|
20480
|
+
}
|
|
20481
|
+
if (replayedCount > 0) {
|
|
20482
|
+
index.updatedAt = nowIso();
|
|
20483
|
+
await this.saveIndex(index);
|
|
20484
|
+
}
|
|
20485
|
+
return { replayedCount };
|
|
20486
|
+
});
|
|
20487
|
+
}
|
|
20488
|
+
async purgeDeadLetter(input) {
|
|
20489
|
+
return await this.withWriteLock(async () => {
|
|
20490
|
+
const index = await this.loadIndex();
|
|
20491
|
+
const requestIds = input?.requestIds !== void 0 ? Array.from(
|
|
20492
|
+
new Set(
|
|
20493
|
+
input.requestIds.map((item) => item.trim()).filter((item) => item.length > 0)
|
|
20494
|
+
)
|
|
20495
|
+
) : Object.keys(index.deadLetterByRequestId);
|
|
20496
|
+
let purgedCount = 0;
|
|
20497
|
+
for (const requestId of requestIds) {
|
|
20498
|
+
if (requestId.length === 0) {
|
|
20499
|
+
continue;
|
|
20500
|
+
}
|
|
20501
|
+
const dead = index.deadLetterByRequestId[requestId];
|
|
20502
|
+
if (!dead) {
|
|
20503
|
+
continue;
|
|
20504
|
+
}
|
|
20505
|
+
delete index.deadLetterByRequestId[requestId];
|
|
20506
|
+
index.deadLetterBytes = Math.max(
|
|
20507
|
+
0,
|
|
20508
|
+
index.deadLetterBytes - dead.payloadBytes
|
|
20509
|
+
);
|
|
20510
|
+
purgedCount += 1;
|
|
20511
|
+
await this.appendEvent({
|
|
20512
|
+
type: "dead_letter_purged",
|
|
20513
|
+
requestId,
|
|
20514
|
+
details: {
|
|
20515
|
+
deadLetteredAt: dead.deadLetteredAt,
|
|
20516
|
+
deadLetterReason: dead.deadLetterReason
|
|
20517
|
+
}
|
|
20518
|
+
});
|
|
20519
|
+
}
|
|
20520
|
+
if (purgedCount > 0) {
|
|
20521
|
+
index.updatedAt = nowIso();
|
|
20522
|
+
await this.saveIndex(index);
|
|
20523
|
+
}
|
|
20524
|
+
return { purgedCount };
|
|
20525
|
+
});
|
|
20526
|
+
}
|
|
20527
|
+
async pruneDelivered() {
|
|
20528
|
+
await this.withWriteLock(async () => {
|
|
20529
|
+
const index = await this.loadIndex();
|
|
20530
|
+
const beforePendingCount = Object.keys(index.pendingByRequestId).length;
|
|
20531
|
+
const beforeDeadLetterCount = Object.keys(
|
|
20532
|
+
index.deadLetterByRequestId
|
|
20533
|
+
).length;
|
|
20534
|
+
if (beforePendingCount === 0 && beforeDeadLetterCount === 0) {
|
|
20535
|
+
return;
|
|
20536
|
+
}
|
|
20537
|
+
const nextPending = {};
|
|
20538
|
+
let pendingBytes = 0;
|
|
20539
|
+
for (const [requestId, entry] of Object.entries(
|
|
20540
|
+
index.pendingByRequestId
|
|
20541
|
+
)) {
|
|
20542
|
+
if (entry.attemptCount < 0) {
|
|
20543
|
+
continue;
|
|
20544
|
+
}
|
|
20545
|
+
nextPending[requestId] = entry;
|
|
20546
|
+
pendingBytes += entry.payloadBytes;
|
|
20547
|
+
}
|
|
20548
|
+
const nextDead = {};
|
|
20549
|
+
let deadLetterBytes = 0;
|
|
20550
|
+
for (const [requestId, entry] of Object.entries(
|
|
20551
|
+
index.deadLetterByRequestId
|
|
20552
|
+
)) {
|
|
20553
|
+
if (entry.attemptCount < 0) {
|
|
20554
|
+
continue;
|
|
20555
|
+
}
|
|
20556
|
+
nextDead[requestId] = entry;
|
|
20557
|
+
deadLetterBytes += entry.payloadBytes;
|
|
20558
|
+
}
|
|
20559
|
+
index.pendingByRequestId = nextPending;
|
|
20560
|
+
index.pendingBytes = pendingBytes;
|
|
20561
|
+
index.deadLetterByRequestId = nextDead;
|
|
20562
|
+
index.deadLetterBytes = deadLetterBytes;
|
|
20563
|
+
index.updatedAt = nowIso();
|
|
20564
|
+
await this.saveIndex(index);
|
|
20565
|
+
await this.appendEvent({
|
|
20566
|
+
type: "inbox_pruned",
|
|
20567
|
+
details: {
|
|
20568
|
+
beforePendingCount,
|
|
20569
|
+
afterPendingCount: Object.keys(nextPending).length,
|
|
20570
|
+
beforeDeadLetterCount,
|
|
20571
|
+
afterDeadLetterCount: Object.keys(nextDead).length
|
|
20572
|
+
}
|
|
20573
|
+
});
|
|
20574
|
+
});
|
|
19899
20575
|
}
|
|
19900
20576
|
async getSnapshot() {
|
|
19901
20577
|
const index = await this.loadIndex();
|
|
19902
|
-
const
|
|
19903
|
-
|
|
19904
|
-
|
|
19905
|
-
|
|
19906
|
-
|
|
19907
|
-
|
|
19908
|
-
|
|
19909
|
-
entries.sort((left, right) => {
|
|
19910
|
-
return toComparableTimeMs(left.receivedAt) - toComparableTimeMs(right.receivedAt);
|
|
19911
|
-
});
|
|
19912
|
-
const nextAttemptAt = entries.map((entry) => entry.nextAttemptAt).sort(
|
|
20578
|
+
const pendingEntries = Object.values(index.pendingByRequestId).sort(
|
|
20579
|
+
(left, right) => toComparableTimeMs(left.receivedAt) - toComparableTimeMs(right.receivedAt)
|
|
20580
|
+
);
|
|
20581
|
+
const deadEntries = Object.values(index.deadLetterByRequestId).sort(
|
|
20582
|
+
(left, right) => toComparableTimeMs(left.deadLetteredAt) - toComparableTimeMs(right.deadLetteredAt)
|
|
20583
|
+
);
|
|
20584
|
+
const nextAttemptAt = pendingEntries.map((entry) => entry.nextAttemptAt).sort(
|
|
19913
20585
|
(left, right) => toComparableTimeMs(left) - toComparableTimeMs(right)
|
|
19914
20586
|
)[0];
|
|
19915
20587
|
return {
|
|
19916
|
-
|
|
19917
|
-
|
|
19918
|
-
|
|
19919
|
-
|
|
20588
|
+
pending: {
|
|
20589
|
+
pendingCount: pendingEntries.length,
|
|
20590
|
+
pendingBytes: index.pendingBytes,
|
|
20591
|
+
oldestPendingAt: pendingEntries[0]?.receivedAt,
|
|
20592
|
+
nextAttemptAt
|
|
20593
|
+
},
|
|
20594
|
+
deadLetter: {
|
|
20595
|
+
deadLetterCount: deadEntries.length,
|
|
20596
|
+
deadLetterBytes: index.deadLetterBytes,
|
|
20597
|
+
oldestDeadLetterAt: deadEntries[0]?.deadLetteredAt
|
|
20598
|
+
}
|
|
19920
20599
|
};
|
|
19921
20600
|
}
|
|
19922
20601
|
async withWriteLock(fn) {
|
|
@@ -19926,12 +20605,67 @@ var ConnectorInboundInbox = class {
|
|
|
19926
20605
|
release = resolve2;
|
|
19927
20606
|
});
|
|
19928
20607
|
await previous;
|
|
20608
|
+
const releaseFileLock = await this.acquireIndexFileLock();
|
|
19929
20609
|
try {
|
|
19930
20610
|
return await fn();
|
|
19931
20611
|
} finally {
|
|
20612
|
+
await releaseFileLock();
|
|
19932
20613
|
release?.();
|
|
19933
20614
|
}
|
|
19934
20615
|
}
|
|
20616
|
+
async acquireIndexFileLock() {
|
|
20617
|
+
const startedAt = nowUtcMs();
|
|
20618
|
+
await mkdir3(this.inboxDir, { recursive: true });
|
|
20619
|
+
while (true) {
|
|
20620
|
+
try {
|
|
20621
|
+
await writeFile3(
|
|
20622
|
+
this.indexLockPath,
|
|
20623
|
+
`${JSON.stringify({ pid: process.pid, createdAt: nowIso() })}
|
|
20624
|
+
`,
|
|
20625
|
+
{
|
|
20626
|
+
encoding: "utf8",
|
|
20627
|
+
flag: "wx"
|
|
20628
|
+
}
|
|
20629
|
+
);
|
|
20630
|
+
let released = false;
|
|
20631
|
+
return async () => {
|
|
20632
|
+
if (released) {
|
|
20633
|
+
return;
|
|
20634
|
+
}
|
|
20635
|
+
released = true;
|
|
20636
|
+
try {
|
|
20637
|
+
await unlink2(this.indexLockPath);
|
|
20638
|
+
} catch {
|
|
20639
|
+
}
|
|
20640
|
+
};
|
|
20641
|
+
} catch (error48) {
|
|
20642
|
+
const code = error48 && typeof error48 === "object" && "code" in error48 ? error48.code : void 0;
|
|
20643
|
+
if (code !== "EEXIST") {
|
|
20644
|
+
throw error48;
|
|
20645
|
+
}
|
|
20646
|
+
const lockStats = await this.readLockStats();
|
|
20647
|
+
if (lockStats !== void 0 && nowUtcMs() - lockStats.mtimeMs > DEFAULT_INDEX_LOCK_STALE_MS) {
|
|
20648
|
+
try {
|
|
20649
|
+
await unlink2(this.indexLockPath);
|
|
20650
|
+
} catch {
|
|
20651
|
+
}
|
|
20652
|
+
continue;
|
|
20653
|
+
}
|
|
20654
|
+
if (nowUtcMs() - startedAt >= DEFAULT_INDEX_LOCK_TIMEOUT_MS) {
|
|
20655
|
+
throw new Error("Timed out waiting for inbound inbox index lock");
|
|
20656
|
+
}
|
|
20657
|
+
await this.sleep(DEFAULT_INDEX_LOCK_RETRY_MS);
|
|
20658
|
+
}
|
|
20659
|
+
}
|
|
20660
|
+
}
|
|
20661
|
+
async readLockStats() {
|
|
20662
|
+
try {
|
|
20663
|
+
const lockStat = await stat2(this.indexLockPath);
|
|
20664
|
+
return { mtimeMs: lockStat.mtimeMs };
|
|
20665
|
+
} catch {
|
|
20666
|
+
return void 0;
|
|
20667
|
+
}
|
|
20668
|
+
}
|
|
19935
20669
|
async loadIndex() {
|
|
19936
20670
|
await mkdir3(this.inboxDir, { recursive: true });
|
|
19937
20671
|
let raw;
|
|
@@ -19954,9 +20688,9 @@ var ConnectorInboundInbox = class {
|
|
|
19954
20688
|
const payload = {
|
|
19955
20689
|
...index,
|
|
19956
20690
|
version: INBOUND_INBOX_SCHEMA_VERSION,
|
|
19957
|
-
updatedAt:
|
|
20691
|
+
updatedAt: nowIso()
|
|
19958
20692
|
};
|
|
19959
|
-
const tmpPath = `${this.indexPath}.tmp-${
|
|
20693
|
+
const tmpPath = `${this.indexPath}.tmp-${nowUtcMs()}`;
|
|
19960
20694
|
await writeFile3(tmpPath, `${JSON.stringify(payload, null, 2)}
|
|
19961
20695
|
`, "utf8");
|
|
19962
20696
|
await rename2(tmpPath, this.indexPath);
|
|
@@ -19965,10 +20699,53 @@ var ConnectorInboundInbox = class {
|
|
|
19965
20699
|
await mkdir3(dirname2(this.eventsPath), { recursive: true });
|
|
19966
20700
|
await appendFile(
|
|
19967
20701
|
this.eventsPath,
|
|
19968
|
-
`${JSON.stringify({ ...event, at:
|
|
20702
|
+
`${JSON.stringify({ ...event, at: nowIso() })}
|
|
19969
20703
|
`,
|
|
19970
20704
|
"utf8"
|
|
19971
20705
|
);
|
|
20706
|
+
await this.rotateEventsIfNeeded();
|
|
20707
|
+
}
|
|
20708
|
+
async rotateEventsIfNeeded() {
|
|
20709
|
+
if (this.eventsMaxBytes <= 0 || this.eventsMaxFiles <= 0) {
|
|
20710
|
+
return;
|
|
20711
|
+
}
|
|
20712
|
+
let currentSize;
|
|
20713
|
+
try {
|
|
20714
|
+
const current = await stat2(this.eventsPath);
|
|
20715
|
+
currentSize = current.size;
|
|
20716
|
+
} catch {
|
|
20717
|
+
return;
|
|
20718
|
+
}
|
|
20719
|
+
if (currentSize <= this.eventsMaxBytes) {
|
|
20720
|
+
return;
|
|
20721
|
+
}
|
|
20722
|
+
for (let index = this.eventsMaxFiles; index >= 1; index -= 1) {
|
|
20723
|
+
const fromPath = index === 1 ? this.eventsPath : `${this.eventsPath}.${index - 1}`;
|
|
20724
|
+
const toPath = `${this.eventsPath}.${index}`;
|
|
20725
|
+
const fromExists = await this.pathExists(fromPath);
|
|
20726
|
+
if (!fromExists) {
|
|
20727
|
+
continue;
|
|
20728
|
+
}
|
|
20729
|
+
const toExists = await this.pathExists(toPath);
|
|
20730
|
+
if (toExists) {
|
|
20731
|
+
await unlink2(toPath);
|
|
20732
|
+
}
|
|
20733
|
+
await rename2(fromPath, toPath);
|
|
20734
|
+
}
|
|
20735
|
+
await writeFile3(this.eventsPath, "", "utf8");
|
|
20736
|
+
}
|
|
20737
|
+
async pathExists(pathValue) {
|
|
20738
|
+
try {
|
|
20739
|
+
await stat2(pathValue);
|
|
20740
|
+
return true;
|
|
20741
|
+
} catch {
|
|
20742
|
+
return false;
|
|
20743
|
+
}
|
|
20744
|
+
}
|
|
20745
|
+
async sleep(durationMs) {
|
|
20746
|
+
await new Promise((resolve2) => {
|
|
20747
|
+
setTimeout(resolve2, durationMs);
|
|
20748
|
+
});
|
|
19972
20749
|
}
|
|
19973
20750
|
};
|
|
19974
20751
|
function createConnectorInboundInbox(options) {
|
|
@@ -19981,10 +20758,13 @@ import { mkdir as mkdir4, readFile as readFile4, rename as rename3, writeFile as
|
|
|
19981
20758
|
import {
|
|
19982
20759
|
createServer
|
|
19983
20760
|
} from "http";
|
|
19984
|
-
import { dirname as dirname3, join as join5 } from "path";
|
|
20761
|
+
import { dirname as dirname3, isAbsolute, join as join5 } from "path";
|
|
19985
20762
|
import { WebSocket as NodeWebSocket } from "ws";
|
|
19986
20763
|
var REGISTRY_AUTH_FILENAME = "registry-auth.json";
|
|
20764
|
+
var OPENCLAW_RELAY_RUNTIME_FILE_NAME = "openclaw-relay.json";
|
|
19987
20765
|
var AGENTS_DIR_NAME2 = "agents";
|
|
20766
|
+
var OUTBOUND_QUEUE_DIR_NAME = "outbound-queue";
|
|
20767
|
+
var OUTBOUND_QUEUE_FILENAME = "queue.json";
|
|
19988
20768
|
var REFRESH_SINGLE_FLIGHT_PREFIX = "connector-runtime";
|
|
19989
20769
|
var NONCE_SIZE = 16;
|
|
19990
20770
|
var MAX_OUTBOUND_BODY_BYTES = 1024 * 1024;
|
|
@@ -20001,6 +20781,23 @@ function parseRequiredString(value, field) {
|
|
|
20001
20781
|
}
|
|
20002
20782
|
return value.trim();
|
|
20003
20783
|
}
|
|
20784
|
+
function parseOptionalString(value) {
|
|
20785
|
+
if (typeof value !== "string") {
|
|
20786
|
+
return void 0;
|
|
20787
|
+
}
|
|
20788
|
+
const trimmed = value.trim();
|
|
20789
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
20790
|
+
}
|
|
20791
|
+
function parseOptionalProxyOrigin(value) {
|
|
20792
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
20793
|
+
return void 0;
|
|
20794
|
+
}
|
|
20795
|
+
try {
|
|
20796
|
+
return new URL(value.trim()).origin;
|
|
20797
|
+
} catch {
|
|
20798
|
+
return void 0;
|
|
20799
|
+
}
|
|
20800
|
+
}
|
|
20004
20801
|
function normalizeOutboundBaseUrl(baseUrlInput) {
|
|
20005
20802
|
const raw = baseUrlInput?.trim() || DEFAULT_CONNECTOR_BASE_URL;
|
|
20006
20803
|
let parsed;
|
|
@@ -20062,6 +20859,15 @@ function toOpenclawHookUrl2(baseUrl, hookPath) {
|
|
|
20062
20859
|
const normalizedHookPath = hookPath.startsWith("/") ? hookPath.slice(1) : hookPath;
|
|
20063
20860
|
return new URL(normalizedHookPath, normalizedBase).toString();
|
|
20064
20861
|
}
|
|
20862
|
+
function toHttpOriginFromWebSocketUrl(value) {
|
|
20863
|
+
const normalized = new URL(value.toString());
|
|
20864
|
+
if (normalized.protocol === "wss:") {
|
|
20865
|
+
normalized.protocol = "https:";
|
|
20866
|
+
} else if (normalized.protocol === "ws:") {
|
|
20867
|
+
normalized.protocol = "http:";
|
|
20868
|
+
}
|
|
20869
|
+
return normalized.origin;
|
|
20870
|
+
}
|
|
20065
20871
|
function parsePositiveIntEnv(key, fallback, minimum = 1) {
|
|
20066
20872
|
const raw = process.env[key]?.trim();
|
|
20067
20873
|
if (!raw) {
|
|
@@ -20080,10 +20886,12 @@ function sanitizeErrorReason2(error48) {
|
|
|
20080
20886
|
return error48.message.trim().slice(0, 240) || "Unknown error";
|
|
20081
20887
|
}
|
|
20082
20888
|
var LocalOpenclawDeliveryError2 = class extends Error {
|
|
20889
|
+
code;
|
|
20083
20890
|
retryable;
|
|
20084
20891
|
constructor(input) {
|
|
20085
20892
|
super(input.message);
|
|
20086
20893
|
this.name = "LocalOpenclawDeliveryError";
|
|
20894
|
+
this.code = input.code;
|
|
20087
20895
|
this.retryable = input.retryable;
|
|
20088
20896
|
}
|
|
20089
20897
|
};
|
|
@@ -20091,7 +20899,22 @@ function loadInboundReplayPolicy() {
|
|
|
20091
20899
|
const retryBackoffFactor = Number.parseFloat(
|
|
20092
20900
|
process.env.CONNECTOR_INBOUND_RETRY_BACKOFF_FACTOR ?? ""
|
|
20093
20901
|
);
|
|
20902
|
+
const runtimeReplayRetryBackoffFactor = Number.parseFloat(
|
|
20903
|
+
process.env.CONNECTOR_RUNTIME_REPLAY_RETRY_BACKOFF_FACTOR ?? ""
|
|
20904
|
+
);
|
|
20094
20905
|
return {
|
|
20906
|
+
deadLetterNonRetryableMaxAttempts: parsePositiveIntEnv(
|
|
20907
|
+
"CONNECTOR_INBOUND_DEAD_LETTER_NON_RETRYABLE_MAX_ATTEMPTS",
|
|
20908
|
+
DEFAULT_CONNECTOR_INBOUND_DEAD_LETTER_NON_RETRYABLE_MAX_ATTEMPTS
|
|
20909
|
+
),
|
|
20910
|
+
eventsMaxBytes: parsePositiveIntEnv(
|
|
20911
|
+
"CONNECTOR_INBOUND_EVENTS_MAX_BYTES",
|
|
20912
|
+
DEFAULT_CONNECTOR_INBOUND_EVENTS_MAX_BYTES
|
|
20913
|
+
),
|
|
20914
|
+
eventsMaxFiles: parsePositiveIntEnv(
|
|
20915
|
+
"CONNECTOR_INBOUND_EVENTS_MAX_FILES",
|
|
20916
|
+
DEFAULT_CONNECTOR_INBOUND_EVENTS_MAX_FILES
|
|
20917
|
+
),
|
|
20095
20918
|
inboxMaxMessages: parsePositiveIntEnv(
|
|
20096
20919
|
"CONNECTOR_INBOUND_INBOX_MAX_MESSAGES",
|
|
20097
20920
|
DEFAULT_CONNECTOR_INBOUND_INBOX_MAX_MESSAGES
|
|
@@ -20116,7 +20939,32 @@ function loadInboundReplayPolicy() {
|
|
|
20116
20939
|
"CONNECTOR_INBOUND_RETRY_MAX_DELAY_MS",
|
|
20117
20940
|
DEFAULT_CONNECTOR_INBOUND_RETRY_MAX_DELAY_MS
|
|
20118
20941
|
),
|
|
20119
|
-
retryBackoffFactor: Number.isFinite(retryBackoffFactor) && retryBackoffFactor >= 1 ? retryBackoffFactor : DEFAULT_CONNECTOR_INBOUND_RETRY_BACKOFF_FACTOR
|
|
20942
|
+
retryBackoffFactor: Number.isFinite(retryBackoffFactor) && retryBackoffFactor >= 1 ? retryBackoffFactor : DEFAULT_CONNECTOR_INBOUND_RETRY_BACKOFF_FACTOR,
|
|
20943
|
+
runtimeReplayMaxAttempts: parsePositiveIntEnv(
|
|
20944
|
+
"CONNECTOR_RUNTIME_REPLAY_MAX_ATTEMPTS",
|
|
20945
|
+
DEFAULT_CONNECTOR_RUNTIME_REPLAY_DELIVER_MAX_ATTEMPTS
|
|
20946
|
+
),
|
|
20947
|
+
runtimeReplayRetryInitialDelayMs: parsePositiveIntEnv(
|
|
20948
|
+
"CONNECTOR_RUNTIME_REPLAY_RETRY_INITIAL_DELAY_MS",
|
|
20949
|
+
DEFAULT_CONNECTOR_RUNTIME_REPLAY_DELIVER_RETRY_INITIAL_DELAY_MS
|
|
20950
|
+
),
|
|
20951
|
+
runtimeReplayRetryMaxDelayMs: parsePositiveIntEnv(
|
|
20952
|
+
"CONNECTOR_RUNTIME_REPLAY_RETRY_MAX_DELAY_MS",
|
|
20953
|
+
DEFAULT_CONNECTOR_RUNTIME_REPLAY_DELIVER_RETRY_MAX_DELAY_MS
|
|
20954
|
+
),
|
|
20955
|
+
runtimeReplayRetryBackoffFactor: Number.isFinite(runtimeReplayRetryBackoffFactor) && runtimeReplayRetryBackoffFactor >= 1 ? runtimeReplayRetryBackoffFactor : DEFAULT_CONNECTOR_RUNTIME_REPLAY_DELIVER_RETRY_BACKOFF_FACTOR
|
|
20956
|
+
};
|
|
20957
|
+
}
|
|
20958
|
+
function loadOpenclawProbePolicy() {
|
|
20959
|
+
return {
|
|
20960
|
+
intervalMs: parsePositiveIntEnv(
|
|
20961
|
+
"CONNECTOR_OPENCLAW_PROBE_INTERVAL_MS",
|
|
20962
|
+
DEFAULT_CONNECTOR_OPENCLAW_PROBE_INTERVAL_MS
|
|
20963
|
+
),
|
|
20964
|
+
timeoutMs: parsePositiveIntEnv(
|
|
20965
|
+
"CONNECTOR_OPENCLAW_PROBE_TIMEOUT_MS",
|
|
20966
|
+
DEFAULT_CONNECTOR_OPENCLAW_PROBE_TIMEOUT_MS
|
|
20967
|
+
)
|
|
20120
20968
|
};
|
|
20121
20969
|
}
|
|
20122
20970
|
function computeReplayDelayMs(input) {
|
|
@@ -20129,13 +20977,90 @@ function computeReplayDelayMs(input) {
|
|
|
20129
20977
|
);
|
|
20130
20978
|
return Math.max(1, delay);
|
|
20131
20979
|
}
|
|
20980
|
+
function computeRuntimeReplayRetryDelayMs(input) {
|
|
20981
|
+
const exponent = Math.max(0, input.attemptCount - 1);
|
|
20982
|
+
const delay = Math.min(
|
|
20983
|
+
input.policy.runtimeReplayRetryMaxDelayMs,
|
|
20984
|
+
Math.floor(
|
|
20985
|
+
input.policy.runtimeReplayRetryInitialDelayMs * input.policy.runtimeReplayRetryBackoffFactor ** exponent
|
|
20986
|
+
)
|
|
20987
|
+
);
|
|
20988
|
+
return Math.max(1, delay);
|
|
20989
|
+
}
|
|
20990
|
+
async function waitWithAbort(input) {
|
|
20991
|
+
if (input.signal.aborted) {
|
|
20992
|
+
throw new LocalOpenclawDeliveryError2({
|
|
20993
|
+
code: "RUNTIME_STOPPING",
|
|
20994
|
+
message: "Connector runtime is stopping",
|
|
20995
|
+
retryable: false
|
|
20996
|
+
});
|
|
20997
|
+
}
|
|
20998
|
+
await new Promise((resolve2, reject) => {
|
|
20999
|
+
const timeoutHandle = setTimeout(() => {
|
|
21000
|
+
input.signal.removeEventListener("abort", onAbort);
|
|
21001
|
+
resolve2();
|
|
21002
|
+
}, input.delayMs);
|
|
21003
|
+
const onAbort = () => {
|
|
21004
|
+
clearTimeout(timeoutHandle);
|
|
21005
|
+
input.signal.removeEventListener("abort", onAbort);
|
|
21006
|
+
reject(
|
|
21007
|
+
new LocalOpenclawDeliveryError2({
|
|
21008
|
+
code: "RUNTIME_STOPPING",
|
|
21009
|
+
message: "Connector runtime is stopping",
|
|
21010
|
+
retryable: false
|
|
21011
|
+
})
|
|
21012
|
+
);
|
|
21013
|
+
};
|
|
21014
|
+
input.signal.addEventListener("abort", onAbort, { once: true });
|
|
21015
|
+
});
|
|
21016
|
+
}
|
|
21017
|
+
async function readOpenclawHookTokenFromRelayRuntimeConfig(input) {
|
|
21018
|
+
const runtimeConfigPath = join5(
|
|
21019
|
+
input.configDir,
|
|
21020
|
+
OPENCLAW_RELAY_RUNTIME_FILE_NAME
|
|
21021
|
+
);
|
|
21022
|
+
let raw;
|
|
21023
|
+
try {
|
|
21024
|
+
raw = await readFile4(runtimeConfigPath, "utf8");
|
|
21025
|
+
} catch (error48) {
|
|
21026
|
+
if (error48 && typeof error48 === "object" && "code" in error48 && error48.code === "ENOENT") {
|
|
21027
|
+
return void 0;
|
|
21028
|
+
}
|
|
21029
|
+
input.logger.warn("connector.runtime.openclaw_relay_config_read_failed", {
|
|
21030
|
+
runtimeConfigPath,
|
|
21031
|
+
reason: sanitizeErrorReason2(error48)
|
|
21032
|
+
});
|
|
21033
|
+
return void 0;
|
|
21034
|
+
}
|
|
21035
|
+
let parsed;
|
|
21036
|
+
try {
|
|
21037
|
+
parsed = JSON.parse(raw);
|
|
21038
|
+
} catch {
|
|
21039
|
+
input.logger.warn("connector.runtime.openclaw_relay_config_invalid_json", {
|
|
21040
|
+
runtimeConfigPath
|
|
21041
|
+
});
|
|
21042
|
+
return void 0;
|
|
21043
|
+
}
|
|
21044
|
+
if (!isRecord6(parsed)) {
|
|
21045
|
+
return void 0;
|
|
21046
|
+
}
|
|
21047
|
+
const tokenValue = parsed.openclawHookToken;
|
|
21048
|
+
if (typeof tokenValue !== "string") {
|
|
21049
|
+
return void 0;
|
|
21050
|
+
}
|
|
21051
|
+
const trimmed = tokenValue.trim();
|
|
21052
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
21053
|
+
}
|
|
20132
21054
|
async function deliverToOpenclawHook(input) {
|
|
20133
|
-
const
|
|
20134
|
-
|
|
20135
|
-
|
|
20136
|
-
|
|
21055
|
+
const timeoutSignal = AbortSignal.timeout(
|
|
21056
|
+
DEFAULT_OPENCLAW_DELIVER_TIMEOUT_MS
|
|
21057
|
+
);
|
|
21058
|
+
const signal = AbortSignal.any([input.shutdownSignal, timeoutSignal]);
|
|
20137
21059
|
const headers = {
|
|
20138
21060
|
"content-type": "application/json",
|
|
21061
|
+
"x-clawdentity-agent-did": input.fromAgentDid,
|
|
21062
|
+
"x-clawdentity-to-agent-did": input.toAgentDid,
|
|
21063
|
+
"x-clawdentity-verified": "true",
|
|
20139
21064
|
"x-request-id": input.requestId
|
|
20140
21065
|
};
|
|
20141
21066
|
if (input.openclawHookToken !== void 0) {
|
|
@@ -20146,16 +21071,24 @@ async function deliverToOpenclawHook(input) {
|
|
|
20146
21071
|
method: "POST",
|
|
20147
21072
|
headers,
|
|
20148
21073
|
body: JSON.stringify(input.payload),
|
|
20149
|
-
signal
|
|
21074
|
+
signal
|
|
20150
21075
|
});
|
|
20151
21076
|
if (!response.ok) {
|
|
20152
21077
|
throw new LocalOpenclawDeliveryError2({
|
|
20153
21078
|
message: `Local OpenClaw hook rejected payload with status ${response.status}`,
|
|
20154
|
-
retryable: response.status >= 500 || response.status === 404 || response.status === 429
|
|
21079
|
+
retryable: response.status === 401 || response.status === 403 || response.status >= 500 || response.status === 404 || response.status === 429,
|
|
21080
|
+
code: response.status === 401 || response.status === 403 ? "HOOK_AUTH_REJECTED" : void 0
|
|
20155
21081
|
});
|
|
20156
21082
|
}
|
|
20157
21083
|
} catch (error48) {
|
|
20158
21084
|
if (error48 instanceof Error && error48.name === "AbortError") {
|
|
21085
|
+
if (input.shutdownSignal.aborted) {
|
|
21086
|
+
throw new LocalOpenclawDeliveryError2({
|
|
21087
|
+
code: "RUNTIME_STOPPING",
|
|
21088
|
+
message: "Connector runtime is stopping",
|
|
21089
|
+
retryable: false
|
|
21090
|
+
});
|
|
21091
|
+
}
|
|
20159
21092
|
throw new LocalOpenclawDeliveryError2({
|
|
20160
21093
|
message: "Local OpenClaw hook request timed out",
|
|
20161
21094
|
retryable: true
|
|
@@ -20168,8 +21101,6 @@ async function deliverToOpenclawHook(input) {
|
|
|
20168
21101
|
message: sanitizeErrorReason2(error48),
|
|
20169
21102
|
retryable: true
|
|
20170
21103
|
});
|
|
20171
|
-
} finally {
|
|
20172
|
-
clearTimeout(timeoutHandle);
|
|
20173
21104
|
}
|
|
20174
21105
|
}
|
|
20175
21106
|
function toInitialAuthBundle(credentials) {
|
|
@@ -20207,11 +21138,26 @@ function parseOutboundRelayRequest(payload) {
|
|
|
20207
21138
|
expose: true
|
|
20208
21139
|
});
|
|
20209
21140
|
}
|
|
21141
|
+
const replyTo = parseOptionalString(payload.replyTo);
|
|
21142
|
+
if (replyTo !== void 0) {
|
|
21143
|
+
try {
|
|
21144
|
+
new URL(replyTo);
|
|
21145
|
+
} catch {
|
|
21146
|
+
throw new AppError({
|
|
21147
|
+
code: "CONNECTOR_OUTBOUND_INVALID_REQUEST",
|
|
21148
|
+
message: "Outbound relay replyTo must be a valid URL",
|
|
21149
|
+
status: 400,
|
|
21150
|
+
expose: true
|
|
21151
|
+
});
|
|
21152
|
+
}
|
|
21153
|
+
}
|
|
20210
21154
|
return {
|
|
20211
21155
|
peer: parseRequiredString(payload.peer, "peer"),
|
|
20212
21156
|
peerDid: parseRequiredString(payload.peerDid, "peerDid"),
|
|
20213
21157
|
peerProxyUrl: parseRequiredString(payload.peerProxyUrl, "peerProxyUrl"),
|
|
20214
|
-
payload: payload.payload
|
|
21158
|
+
payload: payload.payload,
|
|
21159
|
+
conversationId: parseOptionalString(payload.conversationId),
|
|
21160
|
+
replyTo
|
|
20215
21161
|
};
|
|
20216
21162
|
}
|
|
20217
21163
|
function createWebSocketFactory() {
|
|
@@ -20251,6 +21197,14 @@ function createWebSocketFactory() {
|
|
|
20251
21197
|
});
|
|
20252
21198
|
return;
|
|
20253
21199
|
}
|
|
21200
|
+
if (type === "unexpected-response") {
|
|
21201
|
+
socket.on("unexpected-response", (_request, response) => {
|
|
21202
|
+
listener({
|
|
21203
|
+
status: response.statusCode
|
|
21204
|
+
});
|
|
21205
|
+
});
|
|
21206
|
+
return;
|
|
21207
|
+
}
|
|
20254
21208
|
socket.on("error", (error48) => listener({ error: error48 }));
|
|
20255
21209
|
}
|
|
20256
21210
|
};
|
|
@@ -20263,7 +21217,7 @@ async function writeRegistryAuthAtomic(input) {
|
|
|
20263
21217
|
input.agentName,
|
|
20264
21218
|
REGISTRY_AUTH_FILENAME
|
|
20265
21219
|
);
|
|
20266
|
-
const tmpPath = `${targetPath}.tmp-${
|
|
21220
|
+
const tmpPath = `${targetPath}.tmp-${nowUtcMs()}-${Math.random().toString(16).slice(2)}`;
|
|
20267
21221
|
await mkdir4(dirname3(targetPath), { recursive: true });
|
|
20268
21222
|
await writeFile4(tmpPath, `${JSON.stringify(input.auth, null, 2)}
|
|
20269
21223
|
`, "utf8");
|
|
@@ -20326,6 +21280,68 @@ async function readRegistryAuthFromDisk(input) {
|
|
|
20326
21280
|
}
|
|
20327
21281
|
return auth;
|
|
20328
21282
|
}
|
|
21283
|
+
function resolveOutboundQueuePath(input) {
|
|
21284
|
+
return join5(
|
|
21285
|
+
input.configDir,
|
|
21286
|
+
AGENTS_DIR_NAME2,
|
|
21287
|
+
input.agentName,
|
|
21288
|
+
OUTBOUND_QUEUE_DIR_NAME,
|
|
21289
|
+
OUTBOUND_QUEUE_FILENAME
|
|
21290
|
+
);
|
|
21291
|
+
}
|
|
21292
|
+
function createOutboundQueuePersistence(input) {
|
|
21293
|
+
const queuePath = resolveOutboundQueuePath({
|
|
21294
|
+
configDir: input.configDir,
|
|
21295
|
+
agentName: input.agentName
|
|
21296
|
+
});
|
|
21297
|
+
const load = async () => {
|
|
21298
|
+
let raw;
|
|
21299
|
+
try {
|
|
21300
|
+
raw = await readFile4(queuePath, "utf8");
|
|
21301
|
+
} catch (error48) {
|
|
21302
|
+
if (error48 && typeof error48 === "object" && "code" in error48 && error48.code === "ENOENT") {
|
|
21303
|
+
return [];
|
|
21304
|
+
}
|
|
21305
|
+
input.logger.warn("connector.outbound.persistence_read_failed", {
|
|
21306
|
+
queuePath,
|
|
21307
|
+
reason: sanitizeErrorReason2(error48)
|
|
21308
|
+
});
|
|
21309
|
+
return [];
|
|
21310
|
+
}
|
|
21311
|
+
if (raw.trim().length === 0) {
|
|
21312
|
+
return [];
|
|
21313
|
+
}
|
|
21314
|
+
let parsed;
|
|
21315
|
+
try {
|
|
21316
|
+
parsed = JSON.parse(raw);
|
|
21317
|
+
} catch (error48) {
|
|
21318
|
+
input.logger.warn("connector.outbound.persistence_invalid_json", {
|
|
21319
|
+
queuePath,
|
|
21320
|
+
reason: sanitizeErrorReason2(error48)
|
|
21321
|
+
});
|
|
21322
|
+
return [];
|
|
21323
|
+
}
|
|
21324
|
+
if (!Array.isArray(parsed)) {
|
|
21325
|
+
return [];
|
|
21326
|
+
}
|
|
21327
|
+
const frames = [];
|
|
21328
|
+
for (const candidate of parsed) {
|
|
21329
|
+
const parsedFrame = enqueueFrameSchema.safeParse(candidate);
|
|
21330
|
+
if (parsedFrame.success) {
|
|
21331
|
+
frames.push(parsedFrame.data);
|
|
21332
|
+
}
|
|
21333
|
+
}
|
|
21334
|
+
return frames;
|
|
21335
|
+
};
|
|
21336
|
+
const save = async (frames) => {
|
|
21337
|
+
await mkdir4(dirname3(queuePath), { recursive: true });
|
|
21338
|
+
const tmpPath = `${queuePath}.tmp-${nowUtcMs()}-${Math.random().toString(16).slice(2)}`;
|
|
21339
|
+
await writeFile4(tmpPath, `${JSON.stringify(frames, null, 2)}
|
|
21340
|
+
`, "utf8");
|
|
21341
|
+
await rename3(tmpPath, queuePath);
|
|
21342
|
+
};
|
|
21343
|
+
return { load, save };
|
|
21344
|
+
}
|
|
20329
21345
|
async function readRequestJson(req) {
|
|
20330
21346
|
const chunks = [];
|
|
20331
21347
|
let totalBytes = 0;
|
|
@@ -20357,6 +21373,19 @@ async function readRequestJson(req) {
|
|
|
20357
21373
|
});
|
|
20358
21374
|
}
|
|
20359
21375
|
}
|
|
21376
|
+
function parseRequestIds(value) {
|
|
21377
|
+
if (value === void 0) {
|
|
21378
|
+
return void 0;
|
|
21379
|
+
}
|
|
21380
|
+
if (!Array.isArray(value)) {
|
|
21381
|
+
return [];
|
|
21382
|
+
}
|
|
21383
|
+
return Array.from(
|
|
21384
|
+
new Set(
|
|
21385
|
+
value.map((item) => typeof item === "string" ? item.trim() : "").filter((item) => item.length > 0)
|
|
21386
|
+
)
|
|
21387
|
+
);
|
|
21388
|
+
}
|
|
20360
21389
|
function writeJson(res, status, payload) {
|
|
20361
21390
|
res.statusCode = status;
|
|
20362
21391
|
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
@@ -20367,7 +21396,7 @@ function isRetryableRelayAuthError(error48) {
|
|
|
20367
21396
|
return error48 instanceof AppError && error48.code === "OPENCLAW_RELAY_AGENT_AUTH_REJECTED" && error48.status === 401;
|
|
20368
21397
|
}
|
|
20369
21398
|
async function buildUpgradeHeaders(input) {
|
|
20370
|
-
const timestamp = Math.floor(
|
|
21399
|
+
const timestamp = Math.floor(nowUtcMs() / 1e3).toString();
|
|
20371
21400
|
const nonce = encodeBase64url(randomBytes2(NONCE_SIZE));
|
|
20372
21401
|
const signed = await signHttpRequest({
|
|
20373
21402
|
method: "GET",
|
|
@@ -20382,6 +21411,93 @@ async function buildUpgradeHeaders(input) {
|
|
|
20382
21411
|
...signed.headers
|
|
20383
21412
|
};
|
|
20384
21413
|
}
|
|
21414
|
+
async function loadTrustedReceiptTargets(input) {
|
|
21415
|
+
const trustedReceiptTargets = {
|
|
21416
|
+
origins: /* @__PURE__ */ new Set(),
|
|
21417
|
+
byAgentDid: /* @__PURE__ */ new Map()
|
|
21418
|
+
};
|
|
21419
|
+
const relayRuntimeConfigPath = join5(
|
|
21420
|
+
input.configDir,
|
|
21421
|
+
OPENCLAW_RELAY_RUNTIME_FILE_NAME
|
|
21422
|
+
);
|
|
21423
|
+
let relayRuntimeRaw;
|
|
21424
|
+
try {
|
|
21425
|
+
relayRuntimeRaw = await readFile4(relayRuntimeConfigPath, "utf8");
|
|
21426
|
+
} catch (error48) {
|
|
21427
|
+
if (error48 && typeof error48 === "object" && "code" in error48 && error48.code === "ENOENT") {
|
|
21428
|
+
return trustedReceiptTargets;
|
|
21429
|
+
}
|
|
21430
|
+
input.logger.warn("connector.delivery_receipt.runtime_config_read_failed", {
|
|
21431
|
+
relayRuntimeConfigPath,
|
|
21432
|
+
reason: sanitizeErrorReason2(error48)
|
|
21433
|
+
});
|
|
21434
|
+
return trustedReceiptTargets;
|
|
21435
|
+
}
|
|
21436
|
+
let relayRuntimeParsed;
|
|
21437
|
+
try {
|
|
21438
|
+
relayRuntimeParsed = JSON.parse(relayRuntimeRaw);
|
|
21439
|
+
} catch (error48) {
|
|
21440
|
+
input.logger.warn(
|
|
21441
|
+
"connector.delivery_receipt.runtime_config_invalid_json",
|
|
21442
|
+
{
|
|
21443
|
+
relayRuntimeConfigPath,
|
|
21444
|
+
reason: sanitizeErrorReason2(error48)
|
|
21445
|
+
}
|
|
21446
|
+
);
|
|
21447
|
+
return trustedReceiptTargets;
|
|
21448
|
+
}
|
|
21449
|
+
if (!isRecord6(relayRuntimeParsed)) {
|
|
21450
|
+
return trustedReceiptTargets;
|
|
21451
|
+
}
|
|
21452
|
+
const relayTransformPeersPathRaw = typeof relayRuntimeParsed.relayTransformPeersPath === "string" && relayRuntimeParsed.relayTransformPeersPath.trim().length > 0 ? relayRuntimeParsed.relayTransformPeersPath.trim() : void 0;
|
|
21453
|
+
if (!relayTransformPeersPathRaw) {
|
|
21454
|
+
return trustedReceiptTargets;
|
|
21455
|
+
}
|
|
21456
|
+
const relayTransformPeersPath = isAbsolute(relayTransformPeersPathRaw) ? relayTransformPeersPathRaw : join5(input.configDir, relayTransformPeersPathRaw);
|
|
21457
|
+
let relayTransformPeersRaw;
|
|
21458
|
+
try {
|
|
21459
|
+
relayTransformPeersRaw = await readFile4(relayTransformPeersPath, "utf8");
|
|
21460
|
+
} catch (error48) {
|
|
21461
|
+
input.logger.warn("connector.delivery_receipt.peers_snapshot_read_failed", {
|
|
21462
|
+
relayTransformPeersPath,
|
|
21463
|
+
reason: sanitizeErrorReason2(error48)
|
|
21464
|
+
});
|
|
21465
|
+
return trustedReceiptTargets;
|
|
21466
|
+
}
|
|
21467
|
+
let relayTransformPeersParsed;
|
|
21468
|
+
try {
|
|
21469
|
+
relayTransformPeersParsed = JSON.parse(relayTransformPeersRaw);
|
|
21470
|
+
} catch (error48) {
|
|
21471
|
+
input.logger.warn(
|
|
21472
|
+
"connector.delivery_receipt.peers_snapshot_invalid_json",
|
|
21473
|
+
{
|
|
21474
|
+
relayTransformPeersPath,
|
|
21475
|
+
reason: sanitizeErrorReason2(error48)
|
|
21476
|
+
}
|
|
21477
|
+
);
|
|
21478
|
+
return trustedReceiptTargets;
|
|
21479
|
+
}
|
|
21480
|
+
if (!isRecord6(relayTransformPeersParsed)) {
|
|
21481
|
+
return trustedReceiptTargets;
|
|
21482
|
+
}
|
|
21483
|
+
const peersValue = relayTransformPeersParsed.peers;
|
|
21484
|
+
if (!isRecord6(peersValue)) {
|
|
21485
|
+
return trustedReceiptTargets;
|
|
21486
|
+
}
|
|
21487
|
+
for (const peerValue of Object.values(peersValue)) {
|
|
21488
|
+
if (!isRecord6(peerValue)) {
|
|
21489
|
+
continue;
|
|
21490
|
+
}
|
|
21491
|
+
const agentDid = typeof peerValue.did === "string" && peerValue.did.trim().length > 0 ? peerValue.did.trim() : void 0;
|
|
21492
|
+
const origin = parseOptionalProxyOrigin(peerValue.proxyUrl);
|
|
21493
|
+
if (!agentDid || !origin) {
|
|
21494
|
+
continue;
|
|
21495
|
+
}
|
|
21496
|
+
trustedReceiptTargets.origins.add(origin);
|
|
21497
|
+
trustedReceiptTargets.byAgentDid.set(agentDid, origin);
|
|
21498
|
+
}
|
|
21499
|
+
return trustedReceiptTargets;
|
|
21500
|
+
}
|
|
20385
21501
|
async function startConnectorRuntime(input) {
|
|
20386
21502
|
const logger11 = input.logger ?? createLogger({ service: "connector", module: "runtime" });
|
|
20387
21503
|
const fetchImpl = input.fetchImpl ?? fetch;
|
|
@@ -20408,9 +21524,12 @@ async function startConnectorRuntime(input) {
|
|
|
20408
21524
|
};
|
|
20409
21525
|
const refreshCurrentAuthIfNeeded = async () => {
|
|
20410
21526
|
await syncAuthFromDisk();
|
|
20411
|
-
if (!shouldRefreshAccessToken(currentAuth,
|
|
21527
|
+
if (!shouldRefreshAccessToken(currentAuth, nowUtcMs())) {
|
|
20412
21528
|
return;
|
|
20413
21529
|
}
|
|
21530
|
+
await refreshCurrentAuth();
|
|
21531
|
+
};
|
|
21532
|
+
const refreshCurrentAuth = async () => {
|
|
20414
21533
|
currentAuth = await refreshAgentAuthWithClawProof({
|
|
20415
21534
|
registryUrl: input.registryUrl,
|
|
20416
21535
|
ait: input.credentials.ait,
|
|
@@ -20427,23 +21546,47 @@ async function startConnectorRuntime(input) {
|
|
|
20427
21546
|
await refreshCurrentAuthIfNeeded();
|
|
20428
21547
|
const wsUrl = normalizeWebSocketUrl(input.proxyWebsocketUrl);
|
|
20429
21548
|
const wsParsed = new URL(wsUrl);
|
|
21549
|
+
const defaultReceiptCallbackUrl = new URL(
|
|
21550
|
+
RELAY_DELIVERY_RECEIPTS_PATH.slice(1),
|
|
21551
|
+
`${toHttpOriginFromWebSocketUrl(wsParsed)}/`
|
|
21552
|
+
).toString();
|
|
21553
|
+
const defaultReceiptCallbackOrigin = new URL(defaultReceiptCallbackUrl).origin;
|
|
20430
21554
|
const openclawBaseUrl = resolveOpenclawBaseUrl(input.openclawBaseUrl);
|
|
21555
|
+
const openclawProbeUrl = openclawBaseUrl;
|
|
20431
21556
|
const openclawHookPath = resolveOpenclawHookPath(input.openclawHookPath);
|
|
20432
|
-
const
|
|
21557
|
+
const explicitOpenclawHookToken = resolveOpenclawHookToken(
|
|
21558
|
+
input.openclawHookToken
|
|
21559
|
+
);
|
|
21560
|
+
const hasExplicitOpenclawHookToken = explicitOpenclawHookToken !== void 0;
|
|
21561
|
+
let currentOpenclawHookToken = explicitOpenclawHookToken;
|
|
20433
21562
|
const openclawHookUrl = toOpenclawHookUrl2(openclawBaseUrl, openclawHookPath);
|
|
20434
21563
|
const inboundReplayPolicy = loadInboundReplayPolicy();
|
|
21564
|
+
const openclawProbePolicy = loadOpenclawProbePolicy();
|
|
21565
|
+
const trustedReceiptTargets = await loadTrustedReceiptTargets({
|
|
21566
|
+
configDir: input.configDir,
|
|
21567
|
+
logger: logger11
|
|
21568
|
+
});
|
|
21569
|
+
trustedReceiptTargets.origins.add(defaultReceiptCallbackOrigin);
|
|
20435
21570
|
const inboundInbox = createConnectorInboundInbox({
|
|
20436
21571
|
configDir: input.configDir,
|
|
20437
21572
|
agentName: input.agentName,
|
|
21573
|
+
eventsMaxBytes: inboundReplayPolicy.eventsMaxBytes,
|
|
21574
|
+
eventsMaxFiles: inboundReplayPolicy.eventsMaxFiles,
|
|
20438
21575
|
maxPendingMessages: inboundReplayPolicy.inboxMaxMessages,
|
|
20439
21576
|
maxPendingBytes: inboundReplayPolicy.inboxMaxBytes
|
|
20440
21577
|
});
|
|
20441
21578
|
const inboundReplayStatus = {
|
|
20442
21579
|
replayerActive: false
|
|
20443
21580
|
};
|
|
21581
|
+
const openclawGatewayProbeStatus = {
|
|
21582
|
+
reachable: true
|
|
21583
|
+
};
|
|
21584
|
+
let openclawProbeInFlight = false;
|
|
20444
21585
|
let runtimeStopping = false;
|
|
20445
21586
|
let replayInFlight = false;
|
|
20446
21587
|
let replayIntervalHandle;
|
|
21588
|
+
let openclawProbeIntervalHandle;
|
|
21589
|
+
const runtimeShutdownController = new AbortController();
|
|
20447
21590
|
const resolveUpgradeHeaders = async () => {
|
|
20448
21591
|
await refreshCurrentAuthIfNeeded();
|
|
20449
21592
|
return buildUpgradeHeaders({
|
|
@@ -20453,6 +21596,116 @@ async function startConnectorRuntime(input) {
|
|
|
20453
21596
|
secretKey
|
|
20454
21597
|
});
|
|
20455
21598
|
};
|
|
21599
|
+
const syncOpenclawHookToken = async (reason) => {
|
|
21600
|
+
if (hasExplicitOpenclawHookToken) {
|
|
21601
|
+
return;
|
|
21602
|
+
}
|
|
21603
|
+
const diskToken = await readOpenclawHookTokenFromRelayRuntimeConfig({
|
|
21604
|
+
configDir: input.configDir,
|
|
21605
|
+
logger: logger11
|
|
21606
|
+
});
|
|
21607
|
+
const nextToken = diskToken;
|
|
21608
|
+
if (nextToken === currentOpenclawHookToken) {
|
|
21609
|
+
return;
|
|
21610
|
+
}
|
|
21611
|
+
currentOpenclawHookToken = nextToken;
|
|
21612
|
+
logger11.info("connector.runtime.openclaw_hook_token_synced", {
|
|
21613
|
+
reason,
|
|
21614
|
+
source: diskToken !== void 0 ? "openclaw-relay.json" : "unset",
|
|
21615
|
+
hasToken: currentOpenclawHookToken !== void 0
|
|
21616
|
+
});
|
|
21617
|
+
};
|
|
21618
|
+
const probeOpenclawGateway = async () => {
|
|
21619
|
+
if (runtimeStopping || openclawProbeInFlight) {
|
|
21620
|
+
return;
|
|
21621
|
+
}
|
|
21622
|
+
openclawProbeInFlight = true;
|
|
21623
|
+
const checkedAt = nowIso();
|
|
21624
|
+
try {
|
|
21625
|
+
const timeoutSignal = AbortSignal.timeout(openclawProbePolicy.timeoutMs);
|
|
21626
|
+
const signal = AbortSignal.any([
|
|
21627
|
+
runtimeShutdownController.signal,
|
|
21628
|
+
timeoutSignal
|
|
21629
|
+
]);
|
|
21630
|
+
await fetchImpl(openclawProbeUrl, {
|
|
21631
|
+
method: "GET",
|
|
21632
|
+
signal
|
|
21633
|
+
});
|
|
21634
|
+
openclawGatewayProbeStatus.reachable = true;
|
|
21635
|
+
openclawGatewayProbeStatus.lastCheckedAt = checkedAt;
|
|
21636
|
+
openclawGatewayProbeStatus.lastSuccessAt = checkedAt;
|
|
21637
|
+
openclawGatewayProbeStatus.lastFailureReason = void 0;
|
|
21638
|
+
} catch (error48) {
|
|
21639
|
+
if (runtimeShutdownController.signal.aborted) {
|
|
21640
|
+
return;
|
|
21641
|
+
}
|
|
21642
|
+
openclawGatewayProbeStatus.reachable = false;
|
|
21643
|
+
openclawGatewayProbeStatus.lastCheckedAt = checkedAt;
|
|
21644
|
+
openclawGatewayProbeStatus.lastFailureReason = sanitizeErrorReason2(error48);
|
|
21645
|
+
} finally {
|
|
21646
|
+
openclawProbeInFlight = false;
|
|
21647
|
+
}
|
|
21648
|
+
};
|
|
21649
|
+
const deliverToOpenclawHookWithRetry = async (inputReplay) => {
|
|
21650
|
+
let attempt = 1;
|
|
21651
|
+
while (true) {
|
|
21652
|
+
try {
|
|
21653
|
+
await deliverToOpenclawHook({
|
|
21654
|
+
fetchImpl,
|
|
21655
|
+
fromAgentDid: inputReplay.fromAgentDid,
|
|
21656
|
+
openclawHookUrl,
|
|
21657
|
+
openclawHookToken: currentOpenclawHookToken,
|
|
21658
|
+
payload: inputReplay.payload,
|
|
21659
|
+
requestId: inputReplay.requestId,
|
|
21660
|
+
shutdownSignal: runtimeShutdownController.signal,
|
|
21661
|
+
toAgentDid: inputReplay.toAgentDid
|
|
21662
|
+
});
|
|
21663
|
+
return;
|
|
21664
|
+
} catch (error48) {
|
|
21665
|
+
if (error48 instanceof LocalOpenclawDeliveryError2 && error48.code === "RUNTIME_STOPPING") {
|
|
21666
|
+
throw error48;
|
|
21667
|
+
}
|
|
21668
|
+
const retryable = error48 instanceof LocalOpenclawDeliveryError2 ? error48.retryable : true;
|
|
21669
|
+
const authRejected = error48 instanceof LocalOpenclawDeliveryError2 && error48.code === "HOOK_AUTH_REJECTED";
|
|
21670
|
+
if (authRejected) {
|
|
21671
|
+
const previousToken = currentOpenclawHookToken;
|
|
21672
|
+
await syncOpenclawHookToken("auth_rejected");
|
|
21673
|
+
const tokenChanged = currentOpenclawHookToken !== previousToken;
|
|
21674
|
+
const attemptsRemaining2 = attempt < inboundReplayPolicy.runtimeReplayMaxAttempts;
|
|
21675
|
+
if (tokenChanged && !runtimeStopping && attemptsRemaining2) {
|
|
21676
|
+
logger11.warn(
|
|
21677
|
+
"connector.inbound.replay_hook_auth_rejected_retrying",
|
|
21678
|
+
{
|
|
21679
|
+
requestId: inputReplay.requestId,
|
|
21680
|
+
attempt
|
|
21681
|
+
}
|
|
21682
|
+
);
|
|
21683
|
+
attempt += 1;
|
|
21684
|
+
continue;
|
|
21685
|
+
}
|
|
21686
|
+
}
|
|
21687
|
+
const attemptsRemaining = attempt < inboundReplayPolicy.runtimeReplayMaxAttempts;
|
|
21688
|
+
if (!retryable || !attemptsRemaining || runtimeStopping) {
|
|
21689
|
+
throw error48;
|
|
21690
|
+
}
|
|
21691
|
+
const retryDelayMs = computeRuntimeReplayRetryDelayMs({
|
|
21692
|
+
attemptCount: attempt,
|
|
21693
|
+
policy: inboundReplayPolicy
|
|
21694
|
+
});
|
|
21695
|
+
logger11.warn("connector.inbound.replay_retry_scheduled", {
|
|
21696
|
+
requestId: inputReplay.requestId,
|
|
21697
|
+
attempt,
|
|
21698
|
+
retryDelayMs,
|
|
21699
|
+
reason: sanitizeErrorReason2(error48)
|
|
21700
|
+
});
|
|
21701
|
+
await waitWithAbort({
|
|
21702
|
+
delayMs: retryDelayMs,
|
|
21703
|
+
signal: runtimeShutdownController.signal
|
|
21704
|
+
});
|
|
21705
|
+
attempt += 1;
|
|
21706
|
+
}
|
|
21707
|
+
}
|
|
21708
|
+
};
|
|
20456
21709
|
const replayPendingInboundMessages = async () => {
|
|
20457
21710
|
if (runtimeStopping || replayInFlight) {
|
|
20458
21711
|
return;
|
|
@@ -20461,64 +21714,141 @@ async function startConnectorRuntime(input) {
|
|
|
20461
21714
|
inboundReplayStatus.replayerActive = true;
|
|
20462
21715
|
try {
|
|
20463
21716
|
const dueItems = await inboundInbox.listDuePending({
|
|
20464
|
-
nowMs:
|
|
21717
|
+
nowMs: nowUtcMs(),
|
|
20465
21718
|
limit: inboundReplayPolicy.batchSize
|
|
20466
21719
|
});
|
|
21720
|
+
if (dueItems.length === 0) {
|
|
21721
|
+
return;
|
|
21722
|
+
}
|
|
21723
|
+
await syncOpenclawHookToken("batch");
|
|
21724
|
+
if (!openclawGatewayProbeStatus.reachable) {
|
|
21725
|
+
logger11.info("connector.inbound.replay_skipped_gateway_unreachable", {
|
|
21726
|
+
pendingCount: dueItems.length,
|
|
21727
|
+
openclawBaseUrl: openclawProbeUrl,
|
|
21728
|
+
lastFailureReason: openclawGatewayProbeStatus.lastFailureReason
|
|
21729
|
+
});
|
|
21730
|
+
return;
|
|
21731
|
+
}
|
|
21732
|
+
const laneByKey = /* @__PURE__ */ new Map();
|
|
20467
21733
|
for (const pending of dueItems) {
|
|
20468
|
-
|
|
20469
|
-
|
|
20470
|
-
|
|
20471
|
-
|
|
20472
|
-
|
|
20473
|
-
|
|
20474
|
-
requestId: pending.requestId,
|
|
20475
|
-
payload: pending.payload
|
|
20476
|
-
});
|
|
20477
|
-
await inboundInbox.markDelivered(pending.requestId);
|
|
20478
|
-
inboundReplayStatus.lastReplayAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
20479
|
-
inboundReplayStatus.lastReplayError = void 0;
|
|
20480
|
-
inboundReplayStatus.lastAttemptStatus = "ok";
|
|
20481
|
-
logger11.info("connector.inbound.replay_succeeded", {
|
|
20482
|
-
requestId: pending.requestId,
|
|
20483
|
-
attemptCount: pending.attemptCount + 1
|
|
20484
|
-
});
|
|
20485
|
-
} catch (error48) {
|
|
20486
|
-
const reason = sanitizeErrorReason2(error48);
|
|
20487
|
-
const retryable = error48 instanceof LocalOpenclawDeliveryError2 ? error48.retryable : true;
|
|
20488
|
-
const nextAttemptAt = new Date(
|
|
20489
|
-
Date.now() + computeReplayDelayMs({
|
|
20490
|
-
attemptCount: pending.attemptCount + 1,
|
|
20491
|
-
policy: inboundReplayPolicy
|
|
20492
|
-
}) * (retryable ? 1 : 10)
|
|
20493
|
-
).toISOString();
|
|
20494
|
-
await inboundInbox.markReplayFailure({
|
|
20495
|
-
requestId: pending.requestId,
|
|
20496
|
-
errorMessage: reason,
|
|
20497
|
-
nextAttemptAt
|
|
20498
|
-
});
|
|
20499
|
-
inboundReplayStatus.lastReplayError = reason;
|
|
20500
|
-
inboundReplayStatus.lastAttemptStatus = "failed";
|
|
20501
|
-
logger11.warn("connector.inbound.replay_failed", {
|
|
20502
|
-
requestId: pending.requestId,
|
|
20503
|
-
attemptCount: pending.attemptCount + 1,
|
|
20504
|
-
retryable,
|
|
20505
|
-
nextAttemptAt,
|
|
20506
|
-
reason
|
|
20507
|
-
});
|
|
21734
|
+
const laneKey = pending.conversationId !== void 0 ? `conversation:${pending.conversationId}` : "legacy-best-effort";
|
|
21735
|
+
const lane = laneByKey.get(laneKey);
|
|
21736
|
+
if (lane) {
|
|
21737
|
+
lane.push(pending);
|
|
21738
|
+
} else {
|
|
21739
|
+
laneByKey.set(laneKey, [pending]);
|
|
20508
21740
|
}
|
|
20509
21741
|
}
|
|
21742
|
+
await Promise.all(
|
|
21743
|
+
Array.from(laneByKey.values()).map(async (laneItems) => {
|
|
21744
|
+
for (const pending of laneItems) {
|
|
21745
|
+
inboundReplayStatus.lastAttemptAt = nowIso();
|
|
21746
|
+
try {
|
|
21747
|
+
await deliverToOpenclawHookWithRetry({
|
|
21748
|
+
fromAgentDid: pending.fromAgentDid,
|
|
21749
|
+
requestId: pending.requestId,
|
|
21750
|
+
payload: pending.payload,
|
|
21751
|
+
toAgentDid: pending.toAgentDid
|
|
21752
|
+
});
|
|
21753
|
+
await inboundInbox.markDelivered(pending.requestId);
|
|
21754
|
+
inboundReplayStatus.lastReplayAt = nowIso();
|
|
21755
|
+
inboundReplayStatus.lastReplayError = void 0;
|
|
21756
|
+
inboundReplayStatus.lastAttemptStatus = "ok";
|
|
21757
|
+
logger11.info("connector.inbound.replay_succeeded", {
|
|
21758
|
+
requestId: pending.requestId,
|
|
21759
|
+
attemptCount: pending.attemptCount + 1,
|
|
21760
|
+
conversationId: pending.conversationId
|
|
21761
|
+
});
|
|
21762
|
+
if (pending.replyTo) {
|
|
21763
|
+
try {
|
|
21764
|
+
await postDeliveryReceipt({
|
|
21765
|
+
requestId: pending.requestId,
|
|
21766
|
+
senderAgentDid: pending.fromAgentDid,
|
|
21767
|
+
recipientAgentDid: pending.toAgentDid,
|
|
21768
|
+
replyTo: pending.replyTo,
|
|
21769
|
+
status: "processed_by_openclaw"
|
|
21770
|
+
});
|
|
21771
|
+
} catch (error48) {
|
|
21772
|
+
logger11.warn("connector.inbound.delivery_receipt_failed", {
|
|
21773
|
+
requestId: pending.requestId,
|
|
21774
|
+
reason: sanitizeErrorReason2(error48),
|
|
21775
|
+
status: "processed_by_openclaw"
|
|
21776
|
+
});
|
|
21777
|
+
}
|
|
21778
|
+
}
|
|
21779
|
+
} catch (error48) {
|
|
21780
|
+
if (error48 instanceof LocalOpenclawDeliveryError2 && error48.code === "RUNTIME_STOPPING") {
|
|
21781
|
+
logger11.info("connector.inbound.replay_stopped", {
|
|
21782
|
+
requestId: pending.requestId
|
|
21783
|
+
});
|
|
21784
|
+
return;
|
|
21785
|
+
}
|
|
21786
|
+
const reason = sanitizeErrorReason2(error48);
|
|
21787
|
+
const retryable = error48 instanceof LocalOpenclawDeliveryError2 ? error48.retryable : true;
|
|
21788
|
+
const nextAttemptAt = toIso(
|
|
21789
|
+
nowUtcMs() + computeReplayDelayMs({
|
|
21790
|
+
attemptCount: pending.attemptCount + 1,
|
|
21791
|
+
policy: inboundReplayPolicy
|
|
21792
|
+
}) * (retryable ? 1 : 10)
|
|
21793
|
+
);
|
|
21794
|
+
const markResult = await inboundInbox.markReplayFailure({
|
|
21795
|
+
requestId: pending.requestId,
|
|
21796
|
+
errorMessage: reason,
|
|
21797
|
+
nextAttemptAt,
|
|
21798
|
+
retryable,
|
|
21799
|
+
maxNonRetryableAttempts: inboundReplayPolicy.deadLetterNonRetryableMaxAttempts
|
|
21800
|
+
});
|
|
21801
|
+
inboundReplayStatus.lastReplayError = reason;
|
|
21802
|
+
inboundReplayStatus.lastAttemptStatus = "failed";
|
|
21803
|
+
logger11.warn("connector.inbound.replay_failed", {
|
|
21804
|
+
requestId: pending.requestId,
|
|
21805
|
+
attemptCount: pending.attemptCount + 1,
|
|
21806
|
+
retryable,
|
|
21807
|
+
nextAttemptAt,
|
|
21808
|
+
movedToDeadLetter: markResult.movedToDeadLetter,
|
|
21809
|
+
reason
|
|
21810
|
+
});
|
|
21811
|
+
if (markResult.movedToDeadLetter && pending.replyTo) {
|
|
21812
|
+
try {
|
|
21813
|
+
await postDeliveryReceipt({
|
|
21814
|
+
requestId: pending.requestId,
|
|
21815
|
+
senderAgentDid: pending.fromAgentDid,
|
|
21816
|
+
recipientAgentDid: pending.toAgentDid,
|
|
21817
|
+
replyTo: pending.replyTo,
|
|
21818
|
+
status: "dead_lettered",
|
|
21819
|
+
reason
|
|
21820
|
+
});
|
|
21821
|
+
} catch (receiptError) {
|
|
21822
|
+
logger11.warn("connector.inbound.delivery_receipt_failed", {
|
|
21823
|
+
requestId: pending.requestId,
|
|
21824
|
+
reason: sanitizeErrorReason2(receiptError),
|
|
21825
|
+
status: "dead_lettered"
|
|
21826
|
+
});
|
|
21827
|
+
}
|
|
21828
|
+
}
|
|
21829
|
+
}
|
|
21830
|
+
}
|
|
21831
|
+
})
|
|
21832
|
+
);
|
|
20510
21833
|
} finally {
|
|
20511
21834
|
replayInFlight = false;
|
|
20512
21835
|
inboundReplayStatus.replayerActive = false;
|
|
20513
21836
|
}
|
|
20514
21837
|
};
|
|
20515
21838
|
const readInboundReplayView = async () => {
|
|
20516
|
-
const
|
|
21839
|
+
const snapshot = await inboundInbox.getSnapshot();
|
|
20517
21840
|
return {
|
|
20518
|
-
|
|
21841
|
+
snapshot,
|
|
20519
21842
|
replayerActive: inboundReplayStatus.replayerActive || replayInFlight,
|
|
20520
21843
|
lastReplayAt: inboundReplayStatus.lastReplayAt,
|
|
20521
21844
|
lastReplayError: inboundReplayStatus.lastReplayError,
|
|
21845
|
+
openclawGateway: {
|
|
21846
|
+
url: openclawProbeUrl,
|
|
21847
|
+
reachable: openclawGatewayProbeStatus.reachable,
|
|
21848
|
+
lastCheckedAt: openclawGatewayProbeStatus.lastCheckedAt,
|
|
21849
|
+
lastSuccessAt: openclawGatewayProbeStatus.lastSuccessAt,
|
|
21850
|
+
lastFailureReason: openclawGatewayProbeStatus.lastFailureReason
|
|
21851
|
+
},
|
|
20522
21852
|
openclawHook: {
|
|
20523
21853
|
url: openclawHookUrl,
|
|
20524
21854
|
lastAttemptAt: inboundReplayStatus.lastAttemptAt,
|
|
@@ -20526,14 +21856,39 @@ async function startConnectorRuntime(input) {
|
|
|
20526
21856
|
}
|
|
20527
21857
|
};
|
|
20528
21858
|
};
|
|
21859
|
+
const outboundQueuePersistence = createOutboundQueuePersistence({
|
|
21860
|
+
configDir: input.configDir,
|
|
21861
|
+
agentName: input.agentName,
|
|
21862
|
+
logger: logger11
|
|
21863
|
+
});
|
|
20529
21864
|
const connectorClient = new ConnectorClient({
|
|
20530
21865
|
connectorUrl: wsParsed.toString(),
|
|
20531
21866
|
connectionHeadersProvider: resolveUpgradeHeaders,
|
|
20532
21867
|
openclawBaseUrl,
|
|
20533
21868
|
openclawHookPath,
|
|
20534
|
-
openclawHookToken,
|
|
21869
|
+
openclawHookToken: currentOpenclawHookToken,
|
|
20535
21870
|
fetchImpl,
|
|
20536
21871
|
logger: logger11,
|
|
21872
|
+
hooks: {
|
|
21873
|
+
onAuthUpgradeRejected: async ({ status, immediateRetry }) => {
|
|
21874
|
+
logger11.warn("connector.websocket.auth_upgrade_rejected", {
|
|
21875
|
+
status,
|
|
21876
|
+
immediateRetry
|
|
21877
|
+
});
|
|
21878
|
+
await syncAuthFromDisk();
|
|
21879
|
+
try {
|
|
21880
|
+
await refreshCurrentAuth();
|
|
21881
|
+
} catch (error48) {
|
|
21882
|
+
logger11.warn(
|
|
21883
|
+
"connector.runtime.registry_auth_refresh_on_ws_upgrade_reject_failed",
|
|
21884
|
+
{
|
|
21885
|
+
reason: sanitizeErrorReason2(error48)
|
|
21886
|
+
}
|
|
21887
|
+
);
|
|
21888
|
+
}
|
|
21889
|
+
}
|
|
21890
|
+
},
|
|
21891
|
+
outboundQueuePersistence,
|
|
20537
21892
|
inboundDeliverHandler: async (frame) => {
|
|
20538
21893
|
const persisted = await inboundInbox.enqueue(frame);
|
|
20539
21894
|
if (!persisted.accepted) {
|
|
@@ -20560,30 +21915,136 @@ async function startConnectorRuntime(input) {
|
|
|
20560
21915
|
const outboundBaseUrl = normalizeOutboundBaseUrl(input.outboundBaseUrl);
|
|
20561
21916
|
const outboundPath = normalizeOutboundPath(input.outboundPath);
|
|
20562
21917
|
const statusPath = DEFAULT_CONNECTOR_STATUS_PATH;
|
|
21918
|
+
const deadLetterPath = "/v1/inbound/dead-letter";
|
|
21919
|
+
const deadLetterReplayPath = "/v1/inbound/dead-letter/replay";
|
|
21920
|
+
const deadLetterPurgePath = "/v1/inbound/dead-letter/purge";
|
|
20563
21921
|
const outboundUrl = new URL(outboundPath, outboundBaseUrl).toString();
|
|
20564
21922
|
const relayToPeer = async (request) => {
|
|
20565
21923
|
await syncAuthFromDisk();
|
|
20566
|
-
const peerUrl = new URL(request.peerProxyUrl);
|
|
20567
|
-
|
|
20568
|
-
|
|
20569
|
-
const
|
|
20570
|
-
|
|
21924
|
+
const peerUrl = new URL(request.peerProxyUrl);
|
|
21925
|
+
trustedReceiptTargets.origins.add(peerUrl.origin);
|
|
21926
|
+
trustedReceiptTargets.byAgentDid.set(request.peerDid, peerUrl.origin);
|
|
21927
|
+
const body = JSON.stringify(request.payload ?? {});
|
|
21928
|
+
const refreshKey = `${REFRESH_SINGLE_FLIGHT_PREFIX}:${input.configDir}:${input.agentName}`;
|
|
21929
|
+
const performRelay = async (auth) => {
|
|
21930
|
+
const replyTo = request.replyTo ?? defaultReceiptCallbackUrl;
|
|
21931
|
+
const unixSeconds = Math.floor(nowUtcMs() / 1e3).toString();
|
|
21932
|
+
const nonce = encodeBase64url(randomBytes2(NONCE_SIZE));
|
|
21933
|
+
const signed = await signHttpRequest({
|
|
21934
|
+
method: "POST",
|
|
21935
|
+
pathWithQuery: toPathWithQuery2(peerUrl),
|
|
21936
|
+
timestamp: unixSeconds,
|
|
21937
|
+
nonce,
|
|
21938
|
+
body: new TextEncoder().encode(body),
|
|
21939
|
+
secretKey
|
|
21940
|
+
});
|
|
21941
|
+
const response = await fetchImpl(peerUrl.toString(), {
|
|
21942
|
+
method: "POST",
|
|
21943
|
+
headers: {
|
|
21944
|
+
Authorization: `Claw ${input.credentials.ait}`,
|
|
21945
|
+
"Content-Type": "application/json",
|
|
21946
|
+
[AGENT_ACCESS_HEADER]: auth.accessToken,
|
|
21947
|
+
[RELAY_RECIPIENT_AGENT_DID_HEADER]: request.peerDid,
|
|
21948
|
+
...request.conversationId ? { [RELAY_CONVERSATION_ID_HEADER]: request.conversationId } : {},
|
|
21949
|
+
[RELAY_DELIVERY_RECEIPT_URL_HEADER]: replyTo,
|
|
21950
|
+
...signed.headers
|
|
21951
|
+
},
|
|
21952
|
+
body
|
|
21953
|
+
});
|
|
21954
|
+
if (!response.ok) {
|
|
21955
|
+
if (response.status === 401) {
|
|
21956
|
+
throw new AppError({
|
|
21957
|
+
code: "OPENCLAW_RELAY_AGENT_AUTH_REJECTED",
|
|
21958
|
+
message: "Peer relay rejected agent auth credentials",
|
|
21959
|
+
status: 401,
|
|
21960
|
+
expose: true
|
|
21961
|
+
});
|
|
21962
|
+
}
|
|
21963
|
+
throw new AppError({
|
|
21964
|
+
code: "CONNECTOR_OUTBOUND_DELIVERY_FAILED",
|
|
21965
|
+
message: "Peer relay request failed",
|
|
21966
|
+
status: 502
|
|
21967
|
+
});
|
|
21968
|
+
}
|
|
21969
|
+
};
|
|
21970
|
+
await executeWithAgentAuthRefreshRetry({
|
|
21971
|
+
key: refreshKey,
|
|
21972
|
+
shouldRetry: isRetryableRelayAuthError,
|
|
21973
|
+
getAuth: async () => {
|
|
21974
|
+
await syncAuthFromDisk();
|
|
21975
|
+
return currentAuth;
|
|
21976
|
+
},
|
|
21977
|
+
persistAuth: async (nextAuth) => {
|
|
21978
|
+
currentAuth = nextAuth;
|
|
21979
|
+
await writeRegistryAuthAtomic({
|
|
21980
|
+
configDir: input.configDir,
|
|
21981
|
+
agentName: input.agentName,
|
|
21982
|
+
auth: nextAuth
|
|
21983
|
+
});
|
|
21984
|
+
},
|
|
21985
|
+
refreshAuth: async (auth) => refreshAgentAuthWithClawProof({
|
|
21986
|
+
registryUrl: input.registryUrl,
|
|
21987
|
+
ait: input.credentials.ait,
|
|
21988
|
+
secretKey,
|
|
21989
|
+
refreshToken: auth.refreshToken,
|
|
21990
|
+
fetchImpl
|
|
21991
|
+
}),
|
|
21992
|
+
perform: performRelay
|
|
21993
|
+
});
|
|
21994
|
+
};
|
|
21995
|
+
const postDeliveryReceipt = async (inputReceipt) => {
|
|
21996
|
+
await syncAuthFromDisk();
|
|
21997
|
+
const receiptUrl = new URL(inputReceipt.replyTo);
|
|
21998
|
+
if (receiptUrl.pathname !== RELAY_DELIVERY_RECEIPTS_PATH) {
|
|
21999
|
+
throw new AppError({
|
|
22000
|
+
code: "CONNECTOR_DELIVERY_RECEIPT_INVALID_TARGET",
|
|
22001
|
+
message: "Delivery receipt callback target is invalid",
|
|
22002
|
+
status: 400
|
|
22003
|
+
});
|
|
22004
|
+
}
|
|
22005
|
+
const expectedSenderOrigin = trustedReceiptTargets.byAgentDid.get(
|
|
22006
|
+
inputReceipt.senderAgentDid
|
|
22007
|
+
);
|
|
22008
|
+
if (expectedSenderOrigin !== void 0 && receiptUrl.origin !== expectedSenderOrigin) {
|
|
22009
|
+
throw new AppError({
|
|
22010
|
+
code: "CONNECTOR_DELIVERY_RECEIPT_UNTRUSTED_TARGET",
|
|
22011
|
+
message: "Delivery receipt callback target is untrusted",
|
|
22012
|
+
status: 400
|
|
22013
|
+
});
|
|
22014
|
+
}
|
|
22015
|
+
if (expectedSenderOrigin === void 0 && !trustedReceiptTargets.origins.has(receiptUrl.origin)) {
|
|
22016
|
+
throw new AppError({
|
|
22017
|
+
code: "CONNECTOR_DELIVERY_RECEIPT_UNTRUSTED_TARGET",
|
|
22018
|
+
message: "Delivery receipt callback target is untrusted",
|
|
22019
|
+
status: 400
|
|
22020
|
+
});
|
|
22021
|
+
}
|
|
22022
|
+
const body = JSON.stringify({
|
|
22023
|
+
requestId: inputReceipt.requestId,
|
|
22024
|
+
senderAgentDid: inputReceipt.senderAgentDid,
|
|
22025
|
+
recipientAgentDid: inputReceipt.recipientAgentDid,
|
|
22026
|
+
status: inputReceipt.status,
|
|
22027
|
+
reason: inputReceipt.reason,
|
|
22028
|
+
processedAt: nowIso()
|
|
22029
|
+
});
|
|
22030
|
+
const refreshKey = `${REFRESH_SINGLE_FLIGHT_PREFIX}:${input.configDir}:${input.agentName}:delivery-receipt`;
|
|
22031
|
+
const performReceipt = async (auth) => {
|
|
22032
|
+
const unixSeconds = Math.floor(nowUtcMs() / 1e3).toString();
|
|
20571
22033
|
const nonce = encodeBase64url(randomBytes2(NONCE_SIZE));
|
|
20572
22034
|
const signed = await signHttpRequest({
|
|
20573
22035
|
method: "POST",
|
|
20574
|
-
pathWithQuery: toPathWithQuery2(
|
|
22036
|
+
pathWithQuery: toPathWithQuery2(receiptUrl),
|
|
20575
22037
|
timestamp: unixSeconds,
|
|
20576
22038
|
nonce,
|
|
20577
22039
|
body: new TextEncoder().encode(body),
|
|
20578
22040
|
secretKey
|
|
20579
22041
|
});
|
|
20580
|
-
const response = await fetchImpl(
|
|
22042
|
+
const response = await fetchImpl(receiptUrl.toString(), {
|
|
20581
22043
|
method: "POST",
|
|
20582
22044
|
headers: {
|
|
20583
22045
|
Authorization: `Claw ${input.credentials.ait}`,
|
|
20584
22046
|
"Content-Type": "application/json",
|
|
20585
22047
|
[AGENT_ACCESS_HEADER]: auth.accessToken,
|
|
20586
|
-
[RELAY_RECIPIENT_AGENT_DID_HEADER]: request.peerDid,
|
|
20587
22048
|
...signed.headers
|
|
20588
22049
|
},
|
|
20589
22050
|
body
|
|
@@ -20592,14 +22053,14 @@ async function startConnectorRuntime(input) {
|
|
|
20592
22053
|
if (response.status === 401) {
|
|
20593
22054
|
throw new AppError({
|
|
20594
22055
|
code: "OPENCLAW_RELAY_AGENT_AUTH_REJECTED",
|
|
20595
|
-
message: "
|
|
22056
|
+
message: "Delivery receipt callback rejected agent auth credentials",
|
|
20596
22057
|
status: 401,
|
|
20597
22058
|
expose: true
|
|
20598
22059
|
});
|
|
20599
22060
|
}
|
|
20600
22061
|
throw new AppError({
|
|
20601
|
-
code: "
|
|
20602
|
-
message: "
|
|
22062
|
+
code: "CONNECTOR_DELIVERY_RECEIPT_FAILED",
|
|
22063
|
+
message: "Delivery receipt callback request failed",
|
|
20603
22064
|
status: 502
|
|
20604
22065
|
});
|
|
20605
22066
|
}
|
|
@@ -20626,7 +22087,7 @@ async function startConnectorRuntime(input) {
|
|
|
20626
22087
|
refreshToken: auth.refreshToken,
|
|
20627
22088
|
fetchImpl
|
|
20628
22089
|
}),
|
|
20629
|
-
perform:
|
|
22090
|
+
perform: performReceipt
|
|
20630
22091
|
});
|
|
20631
22092
|
};
|
|
20632
22093
|
const server = createServer(async (req, res) => {
|
|
@@ -20653,25 +22114,89 @@ async function startConnectorRuntime(input) {
|
|
|
20653
22114
|
},
|
|
20654
22115
|
outboundUrl,
|
|
20655
22116
|
websocketUrl: wsUrl,
|
|
20656
|
-
|
|
22117
|
+
websocket: {
|
|
22118
|
+
connected: connectorClient.isConnected()
|
|
22119
|
+
}
|
|
20657
22120
|
});
|
|
20658
22121
|
return;
|
|
20659
22122
|
}
|
|
22123
|
+
const clientMetrics = connectorClient.getMetricsSnapshot();
|
|
20660
22124
|
writeJson(res, 200, {
|
|
20661
22125
|
status: "ok",
|
|
20662
22126
|
outboundUrl,
|
|
20663
22127
|
websocketUrl: wsUrl,
|
|
20664
|
-
|
|
20665
|
-
|
|
20666
|
-
pendingCount: inboundReplayView.pending.pendingCount,
|
|
20667
|
-
pendingBytes: inboundReplayView.pending.pendingBytes,
|
|
20668
|
-
oldestPendingAt: inboundReplayView.pending.oldestPendingAt,
|
|
20669
|
-
nextAttemptAt: inboundReplayView.pending.nextAttemptAt,
|
|
20670
|
-
replayerActive: inboundReplayView.replayerActive,
|
|
20671
|
-
lastReplayAt: inboundReplayView.lastReplayAt,
|
|
20672
|
-
lastReplayError: inboundReplayView.lastReplayError
|
|
22128
|
+
websocket: {
|
|
22129
|
+
...clientMetrics.connection
|
|
20673
22130
|
},
|
|
20674
|
-
|
|
22131
|
+
inbound: {
|
|
22132
|
+
pending: inboundReplayView.snapshot.pending,
|
|
22133
|
+
deadLetter: inboundReplayView.snapshot.deadLetter,
|
|
22134
|
+
replay: {
|
|
22135
|
+
replayerActive: inboundReplayView.replayerActive,
|
|
22136
|
+
lastReplayAt: inboundReplayView.lastReplayAt,
|
|
22137
|
+
lastReplayError: inboundReplayView.lastReplayError
|
|
22138
|
+
},
|
|
22139
|
+
openclawGateway: inboundReplayView.openclawGateway,
|
|
22140
|
+
openclawHook: inboundReplayView.openclawHook
|
|
22141
|
+
},
|
|
22142
|
+
outbound: {
|
|
22143
|
+
queue: {
|
|
22144
|
+
pendingCount: connectorClient.getQueuedOutboundCount()
|
|
22145
|
+
}
|
|
22146
|
+
},
|
|
22147
|
+
metrics: {
|
|
22148
|
+
heartbeat: clientMetrics.heartbeat,
|
|
22149
|
+
inboundDelivery: clientMetrics.inboundDelivery,
|
|
22150
|
+
outboundQueue: clientMetrics.outboundQueue
|
|
22151
|
+
}
|
|
22152
|
+
});
|
|
22153
|
+
return;
|
|
22154
|
+
}
|
|
22155
|
+
if (requestPath === deadLetterPath) {
|
|
22156
|
+
if (req.method !== "GET") {
|
|
22157
|
+
res.statusCode = 405;
|
|
22158
|
+
res.setHeader("allow", "GET");
|
|
22159
|
+
writeJson(res, 405, { error: "Method Not Allowed" });
|
|
22160
|
+
return;
|
|
22161
|
+
}
|
|
22162
|
+
const deadLetterItems = await inboundInbox.listDeadLetter();
|
|
22163
|
+
writeJson(res, 200, {
|
|
22164
|
+
status: "ok",
|
|
22165
|
+
count: deadLetterItems.length,
|
|
22166
|
+
items: deadLetterItems
|
|
22167
|
+
});
|
|
22168
|
+
return;
|
|
22169
|
+
}
|
|
22170
|
+
if (requestPath === deadLetterReplayPath) {
|
|
22171
|
+
if (req.method !== "POST") {
|
|
22172
|
+
res.statusCode = 405;
|
|
22173
|
+
res.setHeader("allow", "POST");
|
|
22174
|
+
writeJson(res, 405, { error: "Method Not Allowed" });
|
|
22175
|
+
return;
|
|
22176
|
+
}
|
|
22177
|
+
const body = await readRequestJson(req);
|
|
22178
|
+
const requestIds = isRecord6(body) ? parseRequestIds(body.requestIds) : void 0;
|
|
22179
|
+
const replayResult = await inboundInbox.replayDeadLetter({ requestIds });
|
|
22180
|
+
void replayPendingInboundMessages();
|
|
22181
|
+
writeJson(res, 200, {
|
|
22182
|
+
status: "ok",
|
|
22183
|
+
replayedCount: replayResult.replayedCount
|
|
22184
|
+
});
|
|
22185
|
+
return;
|
|
22186
|
+
}
|
|
22187
|
+
if (requestPath === deadLetterPurgePath) {
|
|
22188
|
+
if (req.method !== "POST") {
|
|
22189
|
+
res.statusCode = 405;
|
|
22190
|
+
res.setHeader("allow", "POST");
|
|
22191
|
+
writeJson(res, 405, { error: "Method Not Allowed" });
|
|
22192
|
+
return;
|
|
22193
|
+
}
|
|
22194
|
+
const body = await readRequestJson(req);
|
|
22195
|
+
const requestIds = isRecord6(body) ? parseRequestIds(body.requestIds) : void 0;
|
|
22196
|
+
const purgeResult = await inboundInbox.purgeDeadLetter({ requestIds });
|
|
22197
|
+
writeJson(res, 200, {
|
|
22198
|
+
status: "ok",
|
|
22199
|
+
purgedCount: purgeResult.purgedCount
|
|
20675
22200
|
});
|
|
20676
22201
|
return;
|
|
20677
22202
|
}
|
|
@@ -20722,10 +22247,15 @@ async function startConnectorRuntime(input) {
|
|
|
20722
22247
|
});
|
|
20723
22248
|
const stop = async () => {
|
|
20724
22249
|
runtimeStopping = true;
|
|
22250
|
+
runtimeShutdownController.abort();
|
|
20725
22251
|
if (replayIntervalHandle !== void 0) {
|
|
20726
22252
|
clearInterval(replayIntervalHandle);
|
|
20727
22253
|
replayIntervalHandle = void 0;
|
|
20728
22254
|
}
|
|
22255
|
+
if (openclawProbeIntervalHandle !== void 0) {
|
|
22256
|
+
clearInterval(openclawProbeIntervalHandle);
|
|
22257
|
+
openclawProbeIntervalHandle = void 0;
|
|
22258
|
+
}
|
|
20729
22259
|
connectorClient.disconnect();
|
|
20730
22260
|
await new Promise((resolve2, reject) => {
|
|
20731
22261
|
server.close((error48) => {
|
|
@@ -20749,12 +22279,17 @@ async function startConnectorRuntime(input) {
|
|
|
20749
22279
|
}
|
|
20750
22280
|
);
|
|
20751
22281
|
});
|
|
22282
|
+
await syncOpenclawHookToken("batch");
|
|
22283
|
+
await probeOpenclawGateway();
|
|
20752
22284
|
connectorClient.connect();
|
|
20753
22285
|
await inboundInbox.pruneDelivered();
|
|
20754
22286
|
void replayPendingInboundMessages();
|
|
20755
22287
|
replayIntervalHandle = setInterval(() => {
|
|
20756
22288
|
void replayPendingInboundMessages();
|
|
20757
22289
|
}, inboundReplayPolicy.replayIntervalMs);
|
|
22290
|
+
openclawProbeIntervalHandle = setInterval(() => {
|
|
22291
|
+
void probeOpenclawGateway();
|
|
22292
|
+
}, openclawProbePolicy.intervalMs);
|
|
20758
22293
|
logger11.info("connector.runtime.started", {
|
|
20759
22294
|
outboundUrl,
|
|
20760
22295
|
websocketUrl: wsUrl,
|
|
@@ -20777,7 +22312,7 @@ var IDENTITY_FILE_NAME2 = "identity.json";
|
|
|
20777
22312
|
var AIT_FILE_NAME2 = "ait.jwt";
|
|
20778
22313
|
var SECRET_KEY_FILE_NAME = "secret.key";
|
|
20779
22314
|
var REGISTRY_AUTH_FILE_NAME2 = "registry-auth.json";
|
|
20780
|
-
var
|
|
22315
|
+
var OPENCLAW_RELAY_RUNTIME_FILE_NAME2 = "openclaw-relay.json";
|
|
20781
22316
|
var OPENCLAW_CONNECTORS_FILE_NAME = "openclaw-connectors.json";
|
|
20782
22317
|
var SERVICE_LOG_DIR_NAME = "logs";
|
|
20783
22318
|
var DEFAULT_CONNECTOR_BASE_URL2 = "http://127.0.0.1:19400";
|
|
@@ -20989,7 +22524,7 @@ async function readRequiredTrimmedFile(filePath, label, readFileImpl) {
|
|
|
20989
22524
|
return trimmed;
|
|
20990
22525
|
}
|
|
20991
22526
|
async function readRelayRuntimeConfig(configDir, readFileImpl) {
|
|
20992
|
-
const filePath = join6(configDir,
|
|
22527
|
+
const filePath = join6(configDir, OPENCLAW_RELAY_RUNTIME_FILE_NAME2);
|
|
20993
22528
|
let raw;
|
|
20994
22529
|
try {
|
|
20995
22530
|
raw = await readFileImpl(filePath, "utf8");
|
|
@@ -21995,7 +23530,7 @@ var LEGACY_OPENCLAW_CONFIG_FILE_NAMES = [
|
|
|
21995
23530
|
"moltbot.json"
|
|
21996
23531
|
];
|
|
21997
23532
|
var OPENCLAW_AGENT_FILE_NAME = "openclaw-agent-name";
|
|
21998
|
-
var
|
|
23533
|
+
var OPENCLAW_RELAY_RUNTIME_FILE_NAME3 = "openclaw-relay.json";
|
|
21999
23534
|
var OPENCLAW_CONNECTORS_FILE_NAME2 = "openclaw-connectors.json";
|
|
22000
23535
|
var SKILL_DIR_NAME = "clawdentity-openclaw-relay";
|
|
22001
23536
|
var RELAY_MODULE_FILE_NAME = "relay-to-peer.mjs";
|
|
@@ -22235,7 +23770,7 @@ function resolveOpenclawAgentNamePath(homeDir) {
|
|
|
22235
23770
|
return join7(getConfigDir({ homeDir }), OPENCLAW_AGENT_FILE_NAME);
|
|
22236
23771
|
}
|
|
22237
23772
|
function resolveRelayRuntimeConfigPath(homeDir) {
|
|
22238
|
-
return join7(getConfigDir({ homeDir }),
|
|
23773
|
+
return join7(getConfigDir({ homeDir }), OPENCLAW_RELAY_RUNTIME_FILE_NAME3);
|
|
22239
23774
|
}
|
|
22240
23775
|
function resolveConnectorAssignmentsPath(homeDir) {
|
|
22241
23776
|
return join7(getConfigDir({ homeDir }), OPENCLAW_CONNECTORS_FILE_NAME2);
|
|
@@ -22608,27 +24143,35 @@ function resolveConnectorStatusUrl(connectorBaseUrl) {
|
|
|
22608
24143
|
).toString();
|
|
22609
24144
|
}
|
|
22610
24145
|
function parseConnectorStatusPayload(payload) {
|
|
22611
|
-
if (!isRecord9(payload) || typeof payload.
|
|
24146
|
+
if (!isRecord9(payload) || !isRecord9(payload.websocket) || typeof payload.websocket.connected !== "boolean") {
|
|
22612
24147
|
throw createCliError6(
|
|
22613
24148
|
"CLI_OPENCLAW_SETUP_CONNECTOR_STATUS_INVALID",
|
|
22614
24149
|
"Connector status response is invalid"
|
|
22615
24150
|
);
|
|
22616
24151
|
}
|
|
24152
|
+
const inboundRoot = isRecord9(payload.inbound) ? payload.inbound : void 0;
|
|
24153
|
+
const pending = inboundRoot && isRecord9(inboundRoot.pending) ? inboundRoot.pending : void 0;
|
|
24154
|
+
const deadLetter = inboundRoot && isRecord9(inboundRoot.deadLetter) ? inboundRoot.deadLetter : void 0;
|
|
24155
|
+
const replay = inboundRoot && isRecord9(inboundRoot.replay) ? inboundRoot.replay : void 0;
|
|
24156
|
+
const hook = inboundRoot && isRecord9(inboundRoot.openclawHook) ? inboundRoot.openclawHook : void 0;
|
|
22617
24157
|
return {
|
|
22618
|
-
websocketConnected: payload.
|
|
22619
|
-
inboundInbox:
|
|
22620
|
-
pendingCount: typeof
|
|
22621
|
-
pendingBytes: typeof
|
|
22622
|
-
oldestPendingAt: typeof
|
|
22623
|
-
nextAttemptAt: typeof
|
|
22624
|
-
lastReplayAt: typeof
|
|
22625
|
-
lastReplayError: typeof
|
|
22626
|
-
replayerActive: typeof
|
|
24158
|
+
websocketConnected: payload.websocket.connected,
|
|
24159
|
+
inboundInbox: pending || deadLetter || replay ? {
|
|
24160
|
+
pendingCount: pending && typeof pending.pendingCount === "number" ? pending.pendingCount : void 0,
|
|
24161
|
+
pendingBytes: pending && typeof pending.pendingBytes === "number" ? pending.pendingBytes : void 0,
|
|
24162
|
+
oldestPendingAt: pending && typeof pending.oldestPendingAt === "string" ? pending.oldestPendingAt : void 0,
|
|
24163
|
+
nextAttemptAt: pending && typeof pending.nextAttemptAt === "string" ? pending.nextAttemptAt : void 0,
|
|
24164
|
+
lastReplayAt: replay && typeof replay.lastReplayAt === "string" ? replay.lastReplayAt : void 0,
|
|
24165
|
+
lastReplayError: replay && typeof replay.lastReplayError === "string" ? replay.lastReplayError : void 0,
|
|
24166
|
+
replayerActive: replay && typeof replay.replayerActive === "boolean" ? replay.replayerActive : void 0,
|
|
24167
|
+
deadLetterCount: deadLetter && typeof deadLetter.deadLetterCount === "number" ? deadLetter.deadLetterCount : void 0,
|
|
24168
|
+
deadLetterBytes: deadLetter && typeof deadLetter.deadLetterBytes === "number" ? deadLetter.deadLetterBytes : void 0,
|
|
24169
|
+
oldestDeadLetterAt: deadLetter && typeof deadLetter.oldestDeadLetterAt === "string" ? deadLetter.oldestDeadLetterAt : void 0
|
|
22627
24170
|
} : void 0,
|
|
22628
|
-
openclawHook:
|
|
22629
|
-
url: typeof
|
|
22630
|
-
lastAttemptAt: typeof
|
|
22631
|
-
lastAttemptStatus:
|
|
24171
|
+
openclawHook: hook ? {
|
|
24172
|
+
url: typeof hook.url === "string" ? hook.url : void 0,
|
|
24173
|
+
lastAttemptAt: typeof hook.lastAttemptAt === "string" ? hook.lastAttemptAt : void 0,
|
|
24174
|
+
lastAttemptStatus: hook.lastAttemptStatus === "ok" || hook.lastAttemptStatus === "failed" ? hook.lastAttemptStatus : void 0
|
|
22632
24175
|
} : void 0
|
|
22633
24176
|
};
|
|
22634
24177
|
}
|
|
@@ -22679,12 +24222,12 @@ async function fetchConnectorHealthStatus(input) {
|
|
|
22679
24222
|
}
|
|
22680
24223
|
}
|
|
22681
24224
|
async function waitForConnectorConnected(input) {
|
|
22682
|
-
const deadline =
|
|
24225
|
+
const deadline = nowUtcMs() + input.waitTimeoutSeconds * 1e3;
|
|
22683
24226
|
let latest = await fetchConnectorHealthStatus({
|
|
22684
24227
|
connectorBaseUrl: input.connectorBaseUrl,
|
|
22685
24228
|
fetchImpl: input.fetchImpl
|
|
22686
24229
|
});
|
|
22687
|
-
while (!latest.connected &&
|
|
24230
|
+
while (!latest.connected && nowUtcMs() < deadline) {
|
|
22688
24231
|
await new Promise((resolve2) => {
|
|
22689
24232
|
setTimeout(resolve2, 1e3);
|
|
22690
24233
|
});
|
|
@@ -22718,7 +24261,7 @@ async function monitorConnectorStabilityWindow(input) {
|
|
|
22718
24261
|
fetchImpl: input.fetchImpl
|
|
22719
24262
|
});
|
|
22720
24263
|
}
|
|
22721
|
-
const deadline =
|
|
24264
|
+
const deadline = nowUtcMs() + input.durationSeconds * 1e3;
|
|
22722
24265
|
let latest = await fetchConnectorHealthStatus({
|
|
22723
24266
|
connectorBaseUrl: input.connectorBaseUrl,
|
|
22724
24267
|
fetchImpl: input.fetchImpl
|
|
@@ -22726,7 +24269,7 @@ async function monitorConnectorStabilityWindow(input) {
|
|
|
22726
24269
|
if (!latest.connected) {
|
|
22727
24270
|
return latest;
|
|
22728
24271
|
}
|
|
22729
|
-
while (
|
|
24272
|
+
while (nowUtcMs() < deadline) {
|
|
22730
24273
|
await sleepMilliseconds(input.pollIntervalMs);
|
|
22731
24274
|
latest = await fetchConnectorHealthStatus({
|
|
22732
24275
|
connectorBaseUrl: input.connectorBaseUrl,
|
|
@@ -23230,10 +24773,80 @@ function printRelayTestResult(result) {
|
|
|
23230
24773
|
writeStdoutLine(`Fix: ${result.remediationHint}`);
|
|
23231
24774
|
}
|
|
23232
24775
|
}
|
|
24776
|
+
function printRelayWebsocketTestResult(result) {
|
|
24777
|
+
writeStdoutLine(`Relay websocket test status: ${result.status}`);
|
|
24778
|
+
writeStdoutLine(`Peer alias: ${result.peerAlias}`);
|
|
24779
|
+
if (typeof result.connectorBaseUrl === "string") {
|
|
24780
|
+
writeStdoutLine(`Connector base URL: ${result.connectorBaseUrl}`);
|
|
24781
|
+
}
|
|
24782
|
+
if (typeof result.connectorStatusUrl === "string") {
|
|
24783
|
+
writeStdoutLine(`Connector status URL: ${result.connectorStatusUrl}`);
|
|
24784
|
+
}
|
|
24785
|
+
writeStdoutLine(`Message: ${result.message}`);
|
|
24786
|
+
if (result.remediationHint) {
|
|
24787
|
+
writeStdoutLine(`Fix: ${result.remediationHint}`);
|
|
24788
|
+
}
|
|
24789
|
+
}
|
|
23233
24790
|
function toSendToPeerEndpoint(openclawBaseUrl) {
|
|
23234
24791
|
const normalizedBase = openclawBaseUrl.endsWith("/") ? openclawBaseUrl : `${openclawBaseUrl}/`;
|
|
23235
24792
|
return new URL(OPENCLAW_SEND_TO_PEER_HOOK_PATH, normalizedBase).toString();
|
|
23236
24793
|
}
|
|
24794
|
+
async function resolveSelectedAgentName(input) {
|
|
24795
|
+
const selectedAgentPath = resolveOpenclawAgentNamePath(input.homeDir);
|
|
24796
|
+
let selectedAgentRaw;
|
|
24797
|
+
try {
|
|
24798
|
+
selectedAgentRaw = await readFile6(selectedAgentPath, "utf8");
|
|
24799
|
+
} catch (error48) {
|
|
24800
|
+
if (getErrorCode2(error48) === "ENOENT") {
|
|
24801
|
+
throw createCliError6(
|
|
24802
|
+
"CLI_OPENCLAW_SELECTED_AGENT_MISSING",
|
|
24803
|
+
"Selected agent marker is missing",
|
|
24804
|
+
{ selectedAgentPath }
|
|
24805
|
+
);
|
|
24806
|
+
}
|
|
24807
|
+
throw createCliError6(
|
|
24808
|
+
"CLI_OPENCLAW_SELECTED_AGENT_INVALID",
|
|
24809
|
+
"Selected agent marker is invalid",
|
|
24810
|
+
{ selectedAgentPath }
|
|
24811
|
+
);
|
|
24812
|
+
}
|
|
24813
|
+
try {
|
|
24814
|
+
return {
|
|
24815
|
+
agentName: assertValidAgentName(selectedAgentRaw.trim()),
|
|
24816
|
+
selectedAgentPath
|
|
24817
|
+
};
|
|
24818
|
+
} catch {
|
|
24819
|
+
throw createCliError6(
|
|
24820
|
+
"CLI_OPENCLAW_SELECTED_AGENT_INVALID",
|
|
24821
|
+
"Selected agent marker is invalid",
|
|
24822
|
+
{ selectedAgentPath }
|
|
24823
|
+
);
|
|
24824
|
+
}
|
|
24825
|
+
}
|
|
24826
|
+
async function resolveConnectorAssignment(input) {
|
|
24827
|
+
const connectorAssignmentsPath = resolveConnectorAssignmentsPath(
|
|
24828
|
+
input.homeDir
|
|
24829
|
+
);
|
|
24830
|
+
const connectorAssignments = await loadConnectorAssignments(
|
|
24831
|
+
connectorAssignmentsPath
|
|
24832
|
+
);
|
|
24833
|
+
const assignment = connectorAssignments.agents[input.agentName];
|
|
24834
|
+
if (assignment === void 0) {
|
|
24835
|
+
throw createCliError6(
|
|
24836
|
+
"CLI_OPENCLAW_CONNECTOR_ASSIGNMENT_MISSING",
|
|
24837
|
+
"Connector assignment is missing for selected agent",
|
|
24838
|
+
{
|
|
24839
|
+
connectorAssignmentsPath,
|
|
24840
|
+
agentName: input.agentName
|
|
24841
|
+
}
|
|
24842
|
+
);
|
|
24843
|
+
}
|
|
24844
|
+
return {
|
|
24845
|
+
connectorAssignmentsPath,
|
|
24846
|
+
connectorBaseUrl: assignment.connectorBaseUrl,
|
|
24847
|
+
connectorStatusUrl: resolveConnectorStatusUrl(assignment.connectorBaseUrl)
|
|
24848
|
+
};
|
|
24849
|
+
}
|
|
23237
24850
|
async function runOpenclawDoctor(options = {}) {
|
|
23238
24851
|
const homeDir = resolveHomeDir2(options.homeDir);
|
|
23239
24852
|
const openclawDir = resolveOpenclawDir(options.openclawDir, homeDir);
|
|
@@ -24245,6 +25858,111 @@ async function runOpenclawRelayTest(options) {
|
|
|
24245
25858
|
preflight
|
|
24246
25859
|
};
|
|
24247
25860
|
}
|
|
25861
|
+
async function runOpenclawRelayWebsocketTest(options) {
|
|
25862
|
+
const homeDir = resolveHomeDir2(options.homeDir);
|
|
25863
|
+
const openclawDir = resolveOpenclawDir(options.openclawDir, homeDir);
|
|
25864
|
+
const checkedAt = nowIso();
|
|
25865
|
+
let peerAlias;
|
|
25866
|
+
try {
|
|
25867
|
+
peerAlias = await resolveRelayProbePeerAlias({
|
|
25868
|
+
homeDir,
|
|
25869
|
+
peerAliasOption: options.peer
|
|
25870
|
+
});
|
|
25871
|
+
} catch (error48) {
|
|
25872
|
+
const appError = error48 instanceof AppError ? error48 : void 0;
|
|
25873
|
+
return {
|
|
25874
|
+
status: "failure",
|
|
25875
|
+
checkedAt,
|
|
25876
|
+
peerAlias: "unresolved",
|
|
25877
|
+
message: appError?.message ?? "Unable to resolve relay peer alias",
|
|
25878
|
+
remediationHint: OPENCLAW_PAIRING_COMMAND_HINT,
|
|
25879
|
+
details: appError?.details
|
|
25880
|
+
};
|
|
25881
|
+
}
|
|
25882
|
+
const preflight = await runOpenclawDoctor({
|
|
25883
|
+
homeDir,
|
|
25884
|
+
openclawDir,
|
|
25885
|
+
peerAlias,
|
|
25886
|
+
resolveConfigImpl: options.resolveConfigImpl,
|
|
25887
|
+
includeConnectorRuntimeCheck: false
|
|
25888
|
+
});
|
|
25889
|
+
if (preflight.status === "unhealthy") {
|
|
25890
|
+
const firstFailure = preflight.checks.find(
|
|
25891
|
+
(check2) => check2.status === "fail"
|
|
25892
|
+
);
|
|
25893
|
+
return {
|
|
25894
|
+
status: "failure",
|
|
25895
|
+
checkedAt,
|
|
25896
|
+
peerAlias,
|
|
25897
|
+
message: firstFailure === void 0 ? "Preflight checks failed" : `Preflight failed: ${firstFailure.label}`,
|
|
25898
|
+
remediationHint: firstFailure?.remediationHint,
|
|
25899
|
+
preflight
|
|
25900
|
+
};
|
|
25901
|
+
}
|
|
25902
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
25903
|
+
if (typeof fetchImpl !== "function") {
|
|
25904
|
+
return {
|
|
25905
|
+
status: "failure",
|
|
25906
|
+
checkedAt,
|
|
25907
|
+
peerAlias,
|
|
25908
|
+
message: "fetch implementation is unavailable",
|
|
25909
|
+
remediationHint: "Run relay websocket test in a Node runtime with fetch support",
|
|
25910
|
+
preflight
|
|
25911
|
+
};
|
|
25912
|
+
}
|
|
25913
|
+
let connectorBaseUrl;
|
|
25914
|
+
let connectorStatusUrl;
|
|
25915
|
+
try {
|
|
25916
|
+
const selectedAgent = await resolveSelectedAgentName({ homeDir });
|
|
25917
|
+
const connectorAssignment = await resolveConnectorAssignment({
|
|
25918
|
+
homeDir,
|
|
25919
|
+
agentName: selectedAgent.agentName
|
|
25920
|
+
});
|
|
25921
|
+
connectorBaseUrl = connectorAssignment.connectorBaseUrl;
|
|
25922
|
+
connectorStatusUrl = connectorAssignment.connectorStatusUrl;
|
|
25923
|
+
} catch (error48) {
|
|
25924
|
+
const appError = error48 instanceof AppError ? error48 : void 0;
|
|
25925
|
+
return {
|
|
25926
|
+
status: "failure",
|
|
25927
|
+
checkedAt,
|
|
25928
|
+
peerAlias,
|
|
25929
|
+
connectorBaseUrl,
|
|
25930
|
+
connectorStatusUrl,
|
|
25931
|
+
message: appError?.message ?? "Unable to resolve connector assignment for websocket test",
|
|
25932
|
+
remediationHint: OPENCLAW_SETUP_COMMAND_HINT,
|
|
25933
|
+
details: appError?.details,
|
|
25934
|
+
preflight
|
|
25935
|
+
};
|
|
25936
|
+
}
|
|
25937
|
+
const connectorStatus = await fetchConnectorHealthStatus({
|
|
25938
|
+
connectorBaseUrl,
|
|
25939
|
+
fetchImpl
|
|
25940
|
+
});
|
|
25941
|
+
if (!connectorStatus.connected) {
|
|
25942
|
+
return {
|
|
25943
|
+
status: "failure",
|
|
25944
|
+
checkedAt,
|
|
25945
|
+
peerAlias,
|
|
25946
|
+
connectorBaseUrl,
|
|
25947
|
+
connectorStatusUrl: connectorStatus.statusUrl,
|
|
25948
|
+
message: "Connector websocket is not connected",
|
|
25949
|
+
remediationHint: OPENCLAW_SETUP_COMMAND_HINT,
|
|
25950
|
+
details: connectorStatus.reason === void 0 ? void 0 : {
|
|
25951
|
+
reason: connectorStatus.reason
|
|
25952
|
+
},
|
|
25953
|
+
preflight
|
|
25954
|
+
};
|
|
25955
|
+
}
|
|
25956
|
+
return {
|
|
25957
|
+
status: "success",
|
|
25958
|
+
checkedAt,
|
|
25959
|
+
peerAlias,
|
|
25960
|
+
connectorBaseUrl,
|
|
25961
|
+
connectorStatusUrl: connectorStatus.statusUrl,
|
|
25962
|
+
message: "Connector websocket is connected for paired relay",
|
|
25963
|
+
preflight
|
|
25964
|
+
};
|
|
25965
|
+
}
|
|
24248
25966
|
async function setupOpenclawRelay(agentName, options) {
|
|
24249
25967
|
const normalizedAgentName = assertValidAgentName(agentName);
|
|
24250
25968
|
const homeDir = resolveHomeDir2(options.homeDir);
|
|
@@ -24629,6 +26347,38 @@ var createOpenclawCommand = () => {
|
|
|
24629
26347
|
}
|
|
24630
26348
|
)
|
|
24631
26349
|
);
|
|
26350
|
+
relayCommand.command("ws-test").description(
|
|
26351
|
+
"Validate connector websocket connectivity for a paired relay peer"
|
|
26352
|
+
).option("--peer <alias>", "Peer alias in local peers map").option(
|
|
26353
|
+
"--openclaw-dir <path>",
|
|
26354
|
+
"OpenClaw state directory (default ~/.openclaw)"
|
|
26355
|
+
).option("--json", "Print machine-readable JSON output").action(
|
|
26356
|
+
withErrorHandling(
|
|
26357
|
+
"openclaw relay ws-test",
|
|
26358
|
+
async (options) => {
|
|
26359
|
+
const result = await runOpenclawRelayWebsocketTest(options);
|
|
26360
|
+
if (options.json) {
|
|
26361
|
+
writeStdoutLine(JSON.stringify(result, null, 2));
|
|
26362
|
+
} else {
|
|
26363
|
+
printRelayWebsocketTestResult(result);
|
|
26364
|
+
if (result.preflight !== void 0 && result.preflight.status === "unhealthy") {
|
|
26365
|
+
writeStdoutLine("Preflight details:");
|
|
26366
|
+
for (const check2 of result.preflight.checks) {
|
|
26367
|
+
if (check2.status === "fail") {
|
|
26368
|
+
writeStdoutLine(formatDoctorCheckLine(check2));
|
|
26369
|
+
if (check2.remediationHint) {
|
|
26370
|
+
writeStdoutLine(`Fix: ${check2.remediationHint}`);
|
|
26371
|
+
}
|
|
26372
|
+
}
|
|
26373
|
+
}
|
|
26374
|
+
}
|
|
26375
|
+
}
|
|
26376
|
+
if (result.status === "failure") {
|
|
26377
|
+
process.exitCode = 1;
|
|
26378
|
+
}
|
|
26379
|
+
}
|
|
26380
|
+
)
|
|
26381
|
+
);
|
|
24632
26382
|
return openclawCommand;
|
|
24633
26383
|
};
|
|
24634
26384
|
|
|
@@ -24639,7 +26389,7 @@ import {
|
|
|
24639
26389
|
mkdir as mkdir7,
|
|
24640
26390
|
readdir as readdir2,
|
|
24641
26391
|
readFile as readFile7,
|
|
24642
|
-
unlink as
|
|
26392
|
+
unlink as unlink3,
|
|
24643
26393
|
writeFile as writeFile7
|
|
24644
26394
|
} from "fs/promises";
|
|
24645
26395
|
import { dirname as dirname6, join as join8, resolve } from "path";
|
|
@@ -24653,7 +26403,7 @@ var AIT_FILE_NAME4 = "ait.jwt";
|
|
|
24653
26403
|
var SECRET_KEY_FILE_NAME3 = "secret.key";
|
|
24654
26404
|
var PAIRING_QR_DIR_NAME = "pairing";
|
|
24655
26405
|
var PEERS_FILE_NAME2 = "peers.json";
|
|
24656
|
-
var
|
|
26406
|
+
var OPENCLAW_RELAY_RUNTIME_FILE_NAME4 = "openclaw-relay.json";
|
|
24657
26407
|
var PAIR_START_PATH = "/pair/start";
|
|
24658
26408
|
var PAIR_CONFIRM_PATH = "/pair/confirm";
|
|
24659
26409
|
var PAIR_STATUS_PATH = "/pair/status";
|
|
@@ -24669,6 +26419,7 @@ var MAX_PROFILE_NAME_LENGTH = 64;
|
|
|
24669
26419
|
var isRecord10 = (value) => {
|
|
24670
26420
|
return typeof value === "object" && value !== null;
|
|
24671
26421
|
};
|
|
26422
|
+
var nowUnixSeconds = () => Math.floor(nowUtcMs() / 1e3);
|
|
24672
26423
|
function createCliError7(code, message2) {
|
|
24673
26424
|
return new AppError({
|
|
24674
26425
|
code,
|
|
@@ -24720,10 +26471,24 @@ function parsePeerProfile(payload) {
|
|
|
24720
26471
|
"Pair profile must be an object"
|
|
24721
26472
|
);
|
|
24722
26473
|
}
|
|
24723
|
-
|
|
26474
|
+
const profile = {
|
|
24724
26475
|
agentName: parseProfileName(payload.agentName, "agentName"),
|
|
24725
26476
|
humanName: parseProfileName(payload.humanName, "humanName")
|
|
24726
26477
|
};
|
|
26478
|
+
const proxyOrigin = parseNonEmptyString10(payload.proxyOrigin);
|
|
26479
|
+
if (proxyOrigin.length > 0) {
|
|
26480
|
+
let parsedProxyOrigin;
|
|
26481
|
+
try {
|
|
26482
|
+
parsedProxyOrigin = new URL(parseProxyUrl2(proxyOrigin)).origin;
|
|
26483
|
+
} catch {
|
|
26484
|
+
throw createCliError7(
|
|
26485
|
+
"CLI_PAIR_PROFILE_INVALID",
|
|
26486
|
+
"proxyOrigin is invalid for pairing"
|
|
26487
|
+
);
|
|
26488
|
+
}
|
|
26489
|
+
profile.proxyOrigin = parsedProxyOrigin;
|
|
26490
|
+
}
|
|
26491
|
+
return profile;
|
|
24727
26492
|
}
|
|
24728
26493
|
function parsePairingTicket(value) {
|
|
24729
26494
|
let ticket = parseNonEmptyString10(value);
|
|
@@ -24992,7 +26757,7 @@ async function savePeersConfig2(input) {
|
|
|
24992
26757
|
await input.chmodImpl(peersPath, FILE_MODE4);
|
|
24993
26758
|
}
|
|
24994
26759
|
function resolveRelayRuntimeConfigPath2(getConfigDirImpl) {
|
|
24995
|
-
return join8(getConfigDirImpl(),
|
|
26760
|
+
return join8(getConfigDirImpl(), OPENCLAW_RELAY_RUNTIME_FILE_NAME4);
|
|
24996
26761
|
}
|
|
24997
26762
|
async function loadRelayTransformPeersPath(input) {
|
|
24998
26763
|
const relayRuntimeConfigPath = resolveRelayRuntimeConfigPath2(
|
|
@@ -25107,10 +26872,59 @@ function resolveLocalPairProfile(input) {
|
|
|
25107
26872
|
"Human name is missing. Run `clawdentity invite redeem <clw_inv_...> --display-name <name>` or `clawdentity config set humanName <name>`."
|
|
25108
26873
|
);
|
|
25109
26874
|
}
|
|
25110
|
-
|
|
26875
|
+
const profile = {
|
|
25111
26876
|
agentName: parseProfileName(input.agentName, "agentName"),
|
|
25112
26877
|
humanName: parseProfileName(humanName, "humanName")
|
|
25113
26878
|
};
|
|
26879
|
+
const proxyUrl = parseNonEmptyString10(input.proxyUrl);
|
|
26880
|
+
if (proxyUrl.length > 0) {
|
|
26881
|
+
profile.proxyOrigin = new URL(parseProxyUrl2(proxyUrl)).origin;
|
|
26882
|
+
}
|
|
26883
|
+
return profile;
|
|
26884
|
+
}
|
|
26885
|
+
function normalizeProxyOrigin(candidate) {
|
|
26886
|
+
return new URL(parseProxyUrl2(candidate)).origin;
|
|
26887
|
+
}
|
|
26888
|
+
function resolvePeerProxyUrl(input) {
|
|
26889
|
+
const configuredPeerOrigin = parseNonEmptyString10(input.peerProxyOrigin);
|
|
26890
|
+
const profilePeerOrigin = parseNonEmptyString10(input.peerProfile.proxyOrigin);
|
|
26891
|
+
const fallbackPeerOrigin = parsePairingTicketIssuerOrigin(input.ticket);
|
|
26892
|
+
const peerOrigin = configuredPeerOrigin.length > 0 ? configuredPeerOrigin : profilePeerOrigin.length > 0 ? profilePeerOrigin : fallbackPeerOrigin;
|
|
26893
|
+
return new URL(
|
|
26894
|
+
"/hooks/agent",
|
|
26895
|
+
`${normalizeProxyOrigin(peerOrigin)}/`
|
|
26896
|
+
).toString();
|
|
26897
|
+
}
|
|
26898
|
+
function toIssuerProxyUrl(ticket) {
|
|
26899
|
+
return parseProxyUrl2(parsePairingTicketIssuerOrigin(ticket));
|
|
26900
|
+
}
|
|
26901
|
+
function toIssuerProxyRequestUrl(ticket, path) {
|
|
26902
|
+
return toProxyRequestUrl(toIssuerProxyUrl(ticket), path);
|
|
26903
|
+
}
|
|
26904
|
+
function toPeerProxyOriginFromStatus(input) {
|
|
26905
|
+
if (input.callerAgentDid === input.initiatorAgentDid) {
|
|
26906
|
+
return input.responderProfile?.proxyOrigin;
|
|
26907
|
+
}
|
|
26908
|
+
if (input.callerAgentDid === input.responderAgentDid) {
|
|
26909
|
+
return input.initiatorProfile.proxyOrigin;
|
|
26910
|
+
}
|
|
26911
|
+
return void 0;
|
|
26912
|
+
}
|
|
26913
|
+
function toPeerProxyOriginFromConfirm(input) {
|
|
26914
|
+
const initiatorOrigin = parseNonEmptyString10(
|
|
26915
|
+
input.initiatorProfile.proxyOrigin
|
|
26916
|
+
);
|
|
26917
|
+
if (initiatorOrigin.length > 0) {
|
|
26918
|
+
return initiatorOrigin;
|
|
26919
|
+
}
|
|
26920
|
+
return parsePairingTicketIssuerOrigin(input.ticket);
|
|
26921
|
+
}
|
|
26922
|
+
function toResponderProfile(input) {
|
|
26923
|
+
return resolveLocalPairProfile({
|
|
26924
|
+
config: input.config,
|
|
26925
|
+
agentName: input.agentName,
|
|
26926
|
+
proxyUrl: input.localProxyUrl
|
|
26927
|
+
});
|
|
25114
26928
|
}
|
|
25115
26929
|
function parseProxyUrl2(candidate) {
|
|
25116
26930
|
try {
|
|
@@ -25507,7 +27321,7 @@ function decodeTicketFromPng(imageBytes) {
|
|
|
25507
27321
|
async function persistPairingQr(input) {
|
|
25508
27322
|
const mkdirImpl = input.dependencies.mkdirImpl ?? mkdir7;
|
|
25509
27323
|
const readdirImpl = input.dependencies.readdirImpl ?? readdir2;
|
|
25510
|
-
const unlinkImpl = input.dependencies.unlinkImpl ??
|
|
27324
|
+
const unlinkImpl = input.dependencies.unlinkImpl ?? unlink3;
|
|
25511
27325
|
const writeFileImpl = input.dependencies.writeFileImpl ?? writeFile7;
|
|
25512
27326
|
const getConfigDirImpl = input.dependencies.getConfigDirImpl ?? getConfigDir;
|
|
25513
27327
|
const qrEncodeImpl = input.dependencies.qrEncodeImpl ?? encodeTicketQrPng;
|
|
@@ -25585,8 +27399,11 @@ async function persistPairedPeer(input) {
|
|
|
25585
27399
|
const mkdirImpl = input.dependencies.mkdirImpl ?? mkdir7;
|
|
25586
27400
|
const writeFileImpl = input.dependencies.writeFileImpl ?? writeFile7;
|
|
25587
27401
|
const chmodImpl = input.dependencies.chmodImpl ?? chmod4;
|
|
25588
|
-
const
|
|
25589
|
-
|
|
27402
|
+
const peerProxyUrl = resolvePeerProxyUrl({
|
|
27403
|
+
ticket: input.ticket,
|
|
27404
|
+
peerProfile: input.peerProfile,
|
|
27405
|
+
peerProxyOrigin: input.peerProxyOrigin
|
|
27406
|
+
});
|
|
25590
27407
|
const peersConfig = await loadPeersConfig2({
|
|
25591
27408
|
getConfigDirImpl,
|
|
25592
27409
|
readFileImpl
|
|
@@ -25621,7 +27438,7 @@ async function persistPairedPeer(input) {
|
|
|
25621
27438
|
async function startPairing(agentName, options, dependencies = {}) {
|
|
25622
27439
|
const fetchImpl = dependencies.fetchImpl ?? fetch;
|
|
25623
27440
|
const resolveConfigImpl = dependencies.resolveConfigImpl ?? resolveConfig;
|
|
25624
|
-
const nowSecondsImpl = dependencies.nowSecondsImpl ??
|
|
27441
|
+
const nowSecondsImpl = dependencies.nowSecondsImpl ?? nowUnixSeconds;
|
|
25625
27442
|
const nonceFactoryImpl = dependencies.nonceFactoryImpl ?? (() => randomBytes4(NONCE_SIZE2).toString("base64url"));
|
|
25626
27443
|
const ttlSeconds = parseTtlSeconds(options.ttlSeconds);
|
|
25627
27444
|
const config2 = await resolveConfigImpl();
|
|
@@ -25632,7 +27449,8 @@ async function startPairing(agentName, options, dependencies = {}) {
|
|
|
25632
27449
|
const normalizedAgentName = assertValidAgentName(agentName);
|
|
25633
27450
|
const initiatorProfile = resolveLocalPairProfile({
|
|
25634
27451
|
config: config2,
|
|
25635
|
-
agentName: normalizedAgentName
|
|
27452
|
+
agentName: normalizedAgentName,
|
|
27453
|
+
proxyUrl
|
|
25636
27454
|
});
|
|
25637
27455
|
const { ait, secretKey } = await readAgentProofMaterial(
|
|
25638
27456
|
normalizedAgentName,
|
|
@@ -25693,21 +27511,22 @@ async function startPairing(agentName, options, dependencies = {}) {
|
|
|
25693
27511
|
async function confirmPairing(agentName, options, dependencies = {}) {
|
|
25694
27512
|
const fetchImpl = dependencies.fetchImpl ?? fetch;
|
|
25695
27513
|
const resolveConfigImpl = dependencies.resolveConfigImpl ?? resolveConfig;
|
|
25696
|
-
const nowSecondsImpl = dependencies.nowSecondsImpl ??
|
|
27514
|
+
const nowSecondsImpl = dependencies.nowSecondsImpl ?? nowUnixSeconds;
|
|
25697
27515
|
const nonceFactoryImpl = dependencies.nonceFactoryImpl ?? (() => randomBytes4(NONCE_SIZE2).toString("base64url"));
|
|
25698
27516
|
const readFileImpl = dependencies.readFileImpl ?? readFile7;
|
|
25699
27517
|
const qrDecodeImpl = dependencies.qrDecodeImpl ?? decodeTicketFromPng;
|
|
25700
27518
|
const config2 = await resolveConfigImpl();
|
|
25701
27519
|
const normalizedAgentName = assertValidAgentName(agentName);
|
|
25702
|
-
const
|
|
27520
|
+
const localProxyUrl = await resolveProxyUrl({
|
|
25703
27521
|
config: config2,
|
|
25704
|
-
|
|
27522
|
+
fetchImpl
|
|
25705
27523
|
});
|
|
25706
|
-
const
|
|
25707
|
-
const proxyUrl = await resolveProxyUrl({
|
|
27524
|
+
const responderProfile = toResponderProfile({
|
|
25708
27525
|
config: config2,
|
|
25709
|
-
|
|
27526
|
+
agentName: normalizedAgentName,
|
|
27527
|
+
localProxyUrl
|
|
25710
27528
|
});
|
|
27529
|
+
const ticketSource = resolveConfirmTicketSource(options);
|
|
25711
27530
|
let ticket = ticketSource.ticket;
|
|
25712
27531
|
if (ticketSource.source === "qr-file") {
|
|
25713
27532
|
if (!ticketSource.qrFilePath) {
|
|
@@ -25732,16 +27551,12 @@ async function confirmPairing(agentName, options, dependencies = {}) {
|
|
|
25732
27551
|
ticket = parsePairingTicket(qrDecodeImpl(new Uint8Array(imageBytes)));
|
|
25733
27552
|
}
|
|
25734
27553
|
ticket = parsePairingTicket(ticket);
|
|
25735
|
-
|
|
25736
|
-
ticket,
|
|
25737
|
-
proxyUrl,
|
|
25738
|
-
context: "confirm"
|
|
25739
|
-
});
|
|
27554
|
+
const proxyUrl = toIssuerProxyUrl(ticket);
|
|
25740
27555
|
const { ait, secretKey } = await readAgentProofMaterial(
|
|
25741
27556
|
normalizedAgentName,
|
|
25742
27557
|
dependencies
|
|
25743
27558
|
);
|
|
25744
|
-
const requestUrl =
|
|
27559
|
+
const requestUrl = toIssuerProxyRequestUrl(ticket, PAIR_CONFIRM_PATH);
|
|
25745
27560
|
const requestBody = JSON.stringify({
|
|
25746
27561
|
ticket,
|
|
25747
27562
|
responderProfile
|
|
@@ -25778,14 +27593,19 @@ async function confirmPairing(agentName, options, dependencies = {}) {
|
|
|
25778
27593
|
);
|
|
25779
27594
|
}
|
|
25780
27595
|
const parsed = parsePairConfirmResponse(responseBody);
|
|
27596
|
+
const peerProxyOrigin = toPeerProxyOriginFromConfirm({
|
|
27597
|
+
ticket,
|
|
27598
|
+
initiatorProfile: parsed.initiatorProfile
|
|
27599
|
+
});
|
|
25781
27600
|
const peerAlias = await persistPairedPeer({
|
|
25782
27601
|
ticket,
|
|
25783
27602
|
peerDid: parsed.initiatorAgentDid,
|
|
25784
27603
|
peerProfile: parsed.initiatorProfile,
|
|
27604
|
+
peerProxyOrigin,
|
|
25785
27605
|
dependencies
|
|
25786
27606
|
});
|
|
25787
27607
|
if (ticketSource.source === "qr-file" && ticketSource.qrFilePath) {
|
|
25788
|
-
const unlinkImpl = dependencies.unlinkImpl ??
|
|
27608
|
+
const unlinkImpl = dependencies.unlinkImpl ?? unlink3;
|
|
25789
27609
|
await unlinkImpl(ticketSource.qrFilePath).catch((error48) => {
|
|
25790
27610
|
const nodeError = error48;
|
|
25791
27611
|
if (nodeError.code === "ENOENT") {
|
|
@@ -25806,7 +27626,7 @@ async function confirmPairing(agentName, options, dependencies = {}) {
|
|
|
25806
27626
|
async function getPairingStatusOnce(agentName, options, dependencies = {}) {
|
|
25807
27627
|
const fetchImpl = dependencies.fetchImpl ?? fetch;
|
|
25808
27628
|
const resolveConfigImpl = dependencies.resolveConfigImpl ?? resolveConfig;
|
|
25809
|
-
const nowSecondsImpl = dependencies.nowSecondsImpl ??
|
|
27629
|
+
const nowSecondsImpl = dependencies.nowSecondsImpl ?? nowUnixSeconds;
|
|
25810
27630
|
const nonceFactoryImpl = dependencies.nonceFactoryImpl ?? (() => randomBytes4(NONCE_SIZE2).toString("base64url"));
|
|
25811
27631
|
const config2 = await resolveConfigImpl();
|
|
25812
27632
|
const proxyUrl = await resolveProxyUrl({
|
|
@@ -25885,6 +27705,13 @@ async function getPairingStatusOnce(agentName, options, dependencies = {}) {
|
|
|
25885
27705
|
ticket,
|
|
25886
27706
|
peerDid,
|
|
25887
27707
|
peerProfile,
|
|
27708
|
+
peerProxyOrigin: toPeerProxyOriginFromStatus({
|
|
27709
|
+
callerAgentDid,
|
|
27710
|
+
initiatorAgentDid: parsed.initiatorAgentDid,
|
|
27711
|
+
responderAgentDid,
|
|
27712
|
+
initiatorProfile: parsed.initiatorProfile,
|
|
27713
|
+
responderProfile: parsed.responderProfile
|
|
27714
|
+
}),
|
|
25888
27715
|
dependencies
|
|
25889
27716
|
});
|
|
25890
27717
|
}
|
|
@@ -25895,7 +27722,7 @@ async function getPairingStatusOnce(agentName, options, dependencies = {}) {
|
|
|
25895
27722
|
};
|
|
25896
27723
|
}
|
|
25897
27724
|
async function waitForPairingStatus(input) {
|
|
25898
|
-
const nowSecondsImpl = input.dependencies.nowSecondsImpl ??
|
|
27725
|
+
const nowSecondsImpl = input.dependencies.nowSecondsImpl ?? nowUnixSeconds;
|
|
25899
27726
|
const sleepImpl = input.dependencies.sleepImpl ?? (async (ms) => {
|
|
25900
27727
|
await new Promise((resolve2) => {
|
|
25901
27728
|
setTimeout(resolve2, ms);
|
|
@@ -26640,7 +28467,7 @@ var fetchRegistryKeys = async (registryUrl) => {
|
|
|
26640
28467
|
return parseSigningKeys(await parseResponseJson(response));
|
|
26641
28468
|
};
|
|
26642
28469
|
var loadRegistryKeys = async (registryUrl) => {
|
|
26643
|
-
const now =
|
|
28470
|
+
const now = nowUtcMs();
|
|
26644
28471
|
const rawCache = await readCacheFile(REGISTRY_KEYS_CACHE_FILE);
|
|
26645
28472
|
const cache2 = typeof rawCache === "string" ? parseRegistryKeysCache(rawCache) : void 0;
|
|
26646
28473
|
const isFresh = isFreshCache({
|
|
@@ -26699,7 +28526,7 @@ var fetchCrlClaims = async (input) => {
|
|
|
26699
28526
|
}
|
|
26700
28527
|
};
|
|
26701
28528
|
var loadCrlClaims = async (input) => {
|
|
26702
|
-
const now =
|
|
28529
|
+
const now = nowUtcMs();
|
|
26703
28530
|
const rawCache = await readCacheFile(CRL_CLAIMS_CACHE_FILE);
|
|
26704
28531
|
const cache2 = typeof rawCache === "string" ? parseCrlCache(rawCache) : void 0;
|
|
26705
28532
|
const isFresh = isFreshCache({
|