agent-relay 3.1.8 → 3.1.10
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/bin/agent-relay-broker-darwin-arm64 +0 -0
- package/bin/agent-relay-broker-darwin-x64 +0 -0
- package/bin/agent-relay-broker-linux-arm64 +0 -0
- package/bin/agent-relay-broker-linux-x64 +0 -0
- package/package.json +8 -8
- package/packages/acp-bridge/package.json +2 -2
- package/packages/config/package.json +1 -1
- package/packages/hooks/package.json +4 -4
- package/packages/memory/package.json +2 -2
- package/packages/openclaw/dist/__tests__/ws-client.test.js +69 -0
- package/packages/openclaw/dist/__tests__/ws-client.test.js.map +1 -1
- package/packages/openclaw/dist/gateway.d.ts +7 -0
- package/packages/openclaw/dist/gateway.d.ts.map +1 -1
- package/packages/openclaw/dist/gateway.js +192 -35
- package/packages/openclaw/dist/gateway.js.map +1 -1
- package/packages/openclaw/dist/setup.d.ts.map +1 -1
- package/packages/openclaw/dist/setup.js +6 -0
- package/packages/openclaw/dist/setup.js.map +1 -1
- package/packages/openclaw/package.json +2 -2
- package/packages/openclaw/skill/SKILL.md +14 -0
- package/packages/openclaw/src/__tests__/ws-client.test.ts +72 -0
- package/packages/openclaw/src/gateway.ts +215 -37
- package/packages/openclaw/src/setup.ts +7 -0
- package/packages/policy/package.json +2 -2
- package/packages/sdk/package.json +2 -2
- package/packages/sdk-py/pyproject.toml +1 -1
- package/packages/telemetry/package.json +1 -1
- package/packages/trajectory/package.json +2 -2
- package/packages/user-directory/package.json +2 -2
- package/packages/utils/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createHash, createPrivateKey, createPublicKey, generateKeyPairSync, sign, type KeyObject } from 'node:crypto';
|
|
1
|
+
import { createHash, createPrivateKey, createPublicKey, generateKeyPairSync, sign, verify, type KeyObject } from 'node:crypto';
|
|
2
2
|
import { chmod, readFile, rename, writeFile, mkdir } from 'node:fs/promises';
|
|
3
3
|
import { createServer, type Server as HttpServer, type IncomingMessage, type ServerResponse } from 'node:http';
|
|
4
4
|
import { join } from 'node:path';
|
|
@@ -16,7 +16,7 @@ import type {
|
|
|
16
16
|
} from '@relaycast/sdk';
|
|
17
17
|
import WebSocket from 'ws';
|
|
18
18
|
|
|
19
|
-
import { openclawHome } from './config.js';
|
|
19
|
+
import { openclawHome, detectOpenClaw } from './config.js';
|
|
20
20
|
import { DEFAULT_OPENCLAW_GATEWAY_PORT, type GatewayConfig, type InboundMessage, type DeliveryResult } from './types.js';
|
|
21
21
|
import { SpawnManager } from './spawn/manager.js';
|
|
22
22
|
import type { SpawnOptions } from './spawn/types.js';
|
|
@@ -71,9 +71,12 @@ const AUTH_PROFILES: Record<string, AuthProfile> = {
|
|
|
71
71
|
signatureEncoding: 'base64url',
|
|
72
72
|
},
|
|
73
73
|
'clawdbot-v1': {
|
|
74
|
+
// Server (openclaw/openclaw device-identity.ts) accepts both PEM and raw-base64url
|
|
75
|
+
// public keys, and decodes signatures in both base64url and base64. Use base64url
|
|
76
|
+
// for consistency — matches the server's own signDevicePayload() output.
|
|
74
77
|
name: 'clawdbot-v1',
|
|
75
|
-
publicKeyFormat: '
|
|
76
|
-
signatureEncoding: '
|
|
78
|
+
publicKeyFormat: 'raw-base64url',
|
|
79
|
+
signatureEncoding: 'base64url',
|
|
77
80
|
},
|
|
78
81
|
};
|
|
79
82
|
|
|
@@ -93,9 +96,12 @@ function resolveAuthProfile(): AuthProfile {
|
|
|
93
96
|
return AUTH_PROFILES[envVal];
|
|
94
97
|
}
|
|
95
98
|
|
|
96
|
-
// 2. Variant detection via
|
|
97
|
-
|
|
98
|
-
|
|
99
|
+
// 2. Variant detection via filesystem probing — delegates to openclawHome()
|
|
100
|
+
// which checks valid parseable config files, not just directory existence.
|
|
101
|
+
// Strict suffix check avoids false positives from substring matching.
|
|
102
|
+
const home = openclawHome();
|
|
103
|
+
const homeSuffix = home.replace(/[/\\]+$/, '').split(/[/\\]/).pop() ?? '';
|
|
104
|
+
if (homeSuffix === '.clawdbot' || homeSuffix === 'clawdbot') {
|
|
99
105
|
return AUTH_PROFILES['clawdbot-v1'];
|
|
100
106
|
}
|
|
101
107
|
|
|
@@ -235,6 +241,86 @@ async function loadOrCreateDeviceIdentity(): Promise<DeviceIdentity> {
|
|
|
235
241
|
return identity;
|
|
236
242
|
}
|
|
237
243
|
|
|
244
|
+
/** Hash helper for diagnostics (no secrets leaked — just truncated SHA-256). */
|
|
245
|
+
function shortHash(data: string | Buffer): string {
|
|
246
|
+
const buf = typeof data === 'string' ? Buffer.from(data, 'utf-8') : data;
|
|
247
|
+
return createHash('sha256').update(buf).digest('hex').slice(0, 16);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Canonicalization variants to try for debugging. Each produces a different
|
|
252
|
+
* pipe-delimited payload string. The server should match exactly one.
|
|
253
|
+
*/
|
|
254
|
+
function buildCanonicalVariants(
|
|
255
|
+
device: DeviceIdentity,
|
|
256
|
+
params: {
|
|
257
|
+
clientId: string;
|
|
258
|
+
clientMode: string;
|
|
259
|
+
platform: string;
|
|
260
|
+
deviceFamily: string;
|
|
261
|
+
role: string;
|
|
262
|
+
scopes: string[];
|
|
263
|
+
signedAt: number;
|
|
264
|
+
token: string;
|
|
265
|
+
nonce: string;
|
|
266
|
+
},
|
|
267
|
+
): Array<{ name: string; payload: string }> {
|
|
268
|
+
const signedAtMs = String(params.signedAt);
|
|
269
|
+
const signedAtSec = String(Math.floor(params.signedAt / 1000));
|
|
270
|
+
const scopesCsv = params.scopes.join(',');
|
|
271
|
+
|
|
272
|
+
return [
|
|
273
|
+
// V0: current default order (v3|deviceId|clientId|clientMode|role|scopes|signedAtMs|token|nonce|platform|deviceFamily)
|
|
274
|
+
{
|
|
275
|
+
name: 'v3-default-ms',
|
|
276
|
+
payload: ['v3', device.deviceId, params.clientId, params.clientMode, params.role, scopesCsv, signedAtMs, params.token || '', params.nonce, params.platform, params.deviceFamily].join('|'),
|
|
277
|
+
},
|
|
278
|
+
// V1: signedAt in seconds instead of milliseconds
|
|
279
|
+
{
|
|
280
|
+
name: 'v3-default-sec',
|
|
281
|
+
payload: ['v3', device.deviceId, params.clientId, params.clientMode, params.role, scopesCsv, signedAtSec, params.token || '', params.nonce, params.platform, params.deviceFamily].join('|'),
|
|
282
|
+
},
|
|
283
|
+
// V2: no token in payload (token omitted entirely)
|
|
284
|
+
{
|
|
285
|
+
name: 'v3-no-token-ms',
|
|
286
|
+
payload: ['v3', device.deviceId, params.clientId, params.clientMode, params.role, scopesCsv, signedAtMs, params.nonce, params.platform, params.deviceFamily].join('|'),
|
|
287
|
+
},
|
|
288
|
+
// V3: nonce before token (swapped positions)
|
|
289
|
+
{
|
|
290
|
+
name: 'v3-nonce-first-ms',
|
|
291
|
+
payload: ['v3', device.deviceId, params.clientId, params.clientMode, params.role, scopesCsv, signedAtMs, params.nonce, params.token || '', params.platform, params.deviceFamily].join('|'),
|
|
292
|
+
},
|
|
293
|
+
// V4: fewer fields — just core identity + nonce + signedAt (minimal)
|
|
294
|
+
{
|
|
295
|
+
name: 'v3-minimal',
|
|
296
|
+
payload: ['v3', device.deviceId, signedAtMs, params.nonce].join('|'),
|
|
297
|
+
},
|
|
298
|
+
// V5: signedAt seconds + no token
|
|
299
|
+
{
|
|
300
|
+
name: 'v3-no-token-sec',
|
|
301
|
+
payload: ['v3', device.deviceId, params.clientId, params.clientMode, params.role, scopesCsv, signedAtSec, params.nonce, params.platform, params.deviceFamily].join('|'),
|
|
302
|
+
},
|
|
303
|
+
// V6: v2 format (no platform/deviceFamily) — used by older gateway versions
|
|
304
|
+
{
|
|
305
|
+
name: 'v2-default-ms',
|
|
306
|
+
payload: ['v2', device.deviceId, params.clientId, params.clientMode, params.role, scopesCsv, signedAtMs, params.token || '', params.nonce].join('|'),
|
|
307
|
+
},
|
|
308
|
+
// V7: v2 with signedAt in seconds
|
|
309
|
+
{
|
|
310
|
+
name: 'v2-default-sec',
|
|
311
|
+
payload: ['v2', device.deviceId, params.clientId, params.clientMode, params.role, scopesCsv, signedAtSec, params.token || '', params.nonce].join('|'),
|
|
312
|
+
},
|
|
313
|
+
// V8: v2 without token
|
|
314
|
+
{
|
|
315
|
+
name: 'v2-no-token-ms',
|
|
316
|
+
payload: ['v2', device.deviceId, params.clientId, params.clientMode, params.role, scopesCsv, signedAtMs, params.nonce].join('|'),
|
|
317
|
+
},
|
|
318
|
+
];
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** Payload version override for v3↔v2 fallback. */
|
|
322
|
+
type PayloadVersionOverride = 'v2' | 'v3' | null;
|
|
323
|
+
|
|
238
324
|
function signConnectPayload(
|
|
239
325
|
device: DeviceIdentity,
|
|
240
326
|
params: {
|
|
@@ -248,34 +334,75 @@ function signConnectPayload(
|
|
|
248
334
|
token: string;
|
|
249
335
|
nonce: string;
|
|
250
336
|
},
|
|
337
|
+
versionOverride?: PayloadVersionOverride,
|
|
251
338
|
): string {
|
|
252
339
|
const profile = resolveAuthProfile();
|
|
253
340
|
|
|
254
|
-
//
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
341
|
+
// Build canonicalization variants for diagnostics
|
|
342
|
+
const variants = buildCanonicalVariants(device, params);
|
|
343
|
+
|
|
344
|
+
// Select primary payload version:
|
|
345
|
+
// 1. If versionOverride is set (from fallback), use that directly
|
|
346
|
+
// 2. clawdbot-v1 defaults to v2 (older gateway compat)
|
|
347
|
+
// 3. default profile uses v3
|
|
348
|
+
let primaryName: string;
|
|
349
|
+
if (versionOverride === 'v2') {
|
|
350
|
+
primaryName = 'v2-default-ms';
|
|
351
|
+
} else if (versionOverride === 'v3') {
|
|
352
|
+
primaryName = 'v3-default-ms';
|
|
353
|
+
} else {
|
|
354
|
+
primaryName = profile.name === 'clawdbot-v1' ? 'v2-default-ms' : 'v3-default-ms';
|
|
355
|
+
}
|
|
356
|
+
const primary = variants.find(v => v.name === primaryName) ?? variants[0];
|
|
357
|
+
|
|
358
|
+
const payloadBytes = Buffer.from(primary.payload, 'utf-8');
|
|
359
|
+
|
|
360
|
+
const isDebug = process.env.RELAY_LOG_LEVEL === 'DEBUG' || process.env.OPENCLAW_WS_DEBUG === '1';
|
|
361
|
+
|
|
362
|
+
// Concise production log — one line with essential info
|
|
363
|
+
console.log(`[ws-auth] profile=${profile.name} payload=${primary.name} device=${device.deviceId.slice(0, 12)}...${versionOverride ? ` override=${versionOverride}` : ''}`);
|
|
364
|
+
|
|
365
|
+
// Verbose debug logging — field hashes and canonicalization matrix
|
|
366
|
+
if (isDebug) {
|
|
367
|
+
console.log(`[ws-auth-debug] signedAt=${params.signedAt}ms nonce=${shortHash(params.nonce)} keyFormat=${profile.publicKeyFormat} sigEncoding=${profile.signatureEncoding}`);
|
|
368
|
+
console.log(`[ws-auth-debug] field hashes: deviceId=${shortHash(device.deviceId)} clientId=${shortHash(params.clientId)} role=${shortHash(params.role)} scopes=${shortHash(params.scopes.join(','))} token=${shortHash(params.token || '')} nonce=${shortHash(params.nonce)}`);
|
|
369
|
+
console.log('[ws-auth-debug] canonicalization matrix:');
|
|
370
|
+
for (const v of variants) {
|
|
371
|
+
console.log(` ${v.name}: hash=${shortHash(v.payload)}`);
|
|
372
|
+
}
|
|
373
|
+
console.log(`[ws-auth-debug] payloadHash=${shortHash(primary.payload)}`);
|
|
374
|
+
}
|
|
274
375
|
|
|
275
376
|
// Ed25519 sign — no hash algorithm needed (null), it's built into Ed25519
|
|
276
377
|
const signature = sign(null, payloadBytes, device.privateKeyObj);
|
|
378
|
+
const encoded = Buffer.from(signature).toString(profile.signatureEncoding);
|
|
277
379
|
|
|
278
|
-
|
|
380
|
+
// Self-verification (debug only): confirm our signature is valid locally.
|
|
381
|
+
if (isDebug) {
|
|
382
|
+
try {
|
|
383
|
+
// Derive public key from private key (same as server would use from our publicKey field)
|
|
384
|
+
const pubKey = createPublicKey(device.privateKeyObj);
|
|
385
|
+
const selfVerifyRaw = verify(null, payloadBytes, pubKey, signature);
|
|
386
|
+
|
|
387
|
+
// Also verify the round-trip: decode our encoded signature like the server would
|
|
388
|
+
const decodedSig = Buffer.from(encoded, profile.signatureEncoding === 'base64url' ? 'base64url' : 'base64');
|
|
389
|
+
const selfVerifyEncoded = verify(null, payloadBytes, pubKey, decodedSig);
|
|
390
|
+
|
|
391
|
+
// Verify deviceId matches public key
|
|
392
|
+
const rawPubBytes = pubKey.export({ type: 'spki', format: 'der' }).subarray(12);
|
|
393
|
+
const derivedDeviceId = createHash('sha256').update(rawPubBytes).digest('hex');
|
|
394
|
+
const deviceIdMatch = derivedDeviceId === device.deviceId;
|
|
395
|
+
|
|
396
|
+
console.log(`[ws-auth-debug] self-verify: raw=${selfVerifyRaw} encoded=${selfVerifyEncoded} deviceIdMatch=${deviceIdMatch} derivedId=${derivedDeviceId.slice(0, 16)}...`);
|
|
397
|
+
if (!deviceIdMatch) {
|
|
398
|
+
console.error(`[ws-auth-debug] DEVICE ID MISMATCH: derived=${derivedDeviceId} sent=${device.deviceId}`);
|
|
399
|
+
}
|
|
400
|
+
} catch (err) {
|
|
401
|
+
console.error(`[ws-auth-debug] self-verify error: ${err instanceof Error ? err.message : String(err)}`);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return encoded;
|
|
279
406
|
}
|
|
280
407
|
|
|
281
408
|
|
|
@@ -305,6 +432,13 @@ export class OpenClawGatewayClient {
|
|
|
305
432
|
private connectTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
306
433
|
private pairingRejected = false;
|
|
307
434
|
private consecutiveFailures = 0;
|
|
435
|
+
/** Payload version override for v3↔v2 fallback (null = use profile default). */
|
|
436
|
+
private payloadVersionOverride: PayloadVersionOverride = null;
|
|
437
|
+
/** Whether a fallback attempt has already been tried this connection cycle. */
|
|
438
|
+
private fallbackAttempted = false;
|
|
439
|
+
/** Auth rejection counters for observability. */
|
|
440
|
+
private authRejectCount = 0;
|
|
441
|
+
private authFallbackCount = 0;
|
|
308
442
|
|
|
309
443
|
/** Default timeout for initial connection (30 seconds). */
|
|
310
444
|
private static readonly CONNECT_TIMEOUT_MS = 30_000;
|
|
@@ -337,6 +471,9 @@ export class OpenClawGatewayClient {
|
|
|
337
471
|
// Explicit connect() clears pairing rejection so users can retry after fixing their token
|
|
338
472
|
this.pairingRejected = false;
|
|
339
473
|
this.stopped = false;
|
|
474
|
+
// Reset fallback state for fresh connection attempts
|
|
475
|
+
this.payloadVersionOverride = null;
|
|
476
|
+
this.fallbackAttempted = false;
|
|
340
477
|
|
|
341
478
|
// Cancel any pending reconnect timer to prevent orphaned WebSocket connections
|
|
342
479
|
if (this.reconnectTimer) {
|
|
@@ -374,23 +511,29 @@ export class OpenClawGatewayClient {
|
|
|
374
511
|
private doConnect(): void {
|
|
375
512
|
if (this.stopped) return;
|
|
376
513
|
|
|
514
|
+
let ws: WebSocket;
|
|
377
515
|
try {
|
|
378
|
-
|
|
516
|
+
ws = new WebSocket(`ws://127.0.0.1:${this.port}`);
|
|
379
517
|
} catch (err) {
|
|
380
518
|
console.warn(`[openclaw-ws] Connection failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
381
519
|
this.scheduleReconnect();
|
|
382
520
|
return;
|
|
383
521
|
}
|
|
522
|
+
this.ws = ws;
|
|
384
523
|
|
|
385
|
-
|
|
524
|
+
ws.on('open', () => {
|
|
386
525
|
console.log('[openclaw-ws] Connected to OpenClaw gateway');
|
|
387
526
|
});
|
|
388
527
|
|
|
389
|
-
|
|
528
|
+
ws.on('message', (data) => {
|
|
390
529
|
this.handleMessage(data.toString());
|
|
391
530
|
});
|
|
392
531
|
|
|
393
|
-
|
|
532
|
+
ws.on('close', (code, reason) => {
|
|
533
|
+
// Guard: ignore close events from superseded WebSocket instances.
|
|
534
|
+
// During v3↔v2 fallback, the old WS is replaced before its close fires.
|
|
535
|
+
if (this.ws !== ws) return;
|
|
536
|
+
|
|
394
537
|
const reasonStr = reason.toString();
|
|
395
538
|
console.warn(`[openclaw-ws] Disconnected: ${code} ${reasonStr}`);
|
|
396
539
|
const wasAuthenticated = this.authenticated;
|
|
@@ -423,7 +566,10 @@ export class OpenClawGatewayClient {
|
|
|
423
566
|
}
|
|
424
567
|
});
|
|
425
568
|
|
|
426
|
-
|
|
569
|
+
ws.on('error', (err) => {
|
|
570
|
+
// Guard: ignore error events from superseded WebSocket instances.
|
|
571
|
+
if (this.ws !== ws) return;
|
|
572
|
+
|
|
427
573
|
console.warn(`[openclaw-ws] Error: ${err.message}`);
|
|
428
574
|
// If we weren't authenticated yet, reject the connect promise
|
|
429
575
|
if (!this.authenticated && this.connectReject) {
|
|
@@ -447,6 +593,10 @@ export class OpenClawGatewayClient {
|
|
|
447
593
|
if (msg.type === 'event' && msg.event === 'connect.challenge') {
|
|
448
594
|
const payload = msg.payload as { nonce: string; ts: number };
|
|
449
595
|
console.log('[openclaw-ws] Received connect.challenge, signing...');
|
|
596
|
+
// Log raw challenge payload for debugging canonicalization issues
|
|
597
|
+
if (process.env.RELAY_LOG_LEVEL === 'DEBUG' || process.env.OPENCLAW_WS_DEBUG === '1') {
|
|
598
|
+
console.log(`[ws-auth-debug] challenge payload: ${JSON.stringify(payload)}`);
|
|
599
|
+
}
|
|
450
600
|
|
|
451
601
|
const signedAt = Date.now();
|
|
452
602
|
const clientId = 'cli';
|
|
@@ -466,7 +616,7 @@ export class OpenClawGatewayClient {
|
|
|
466
616
|
signedAt,
|
|
467
617
|
token: this.token,
|
|
468
618
|
nonce: payload.nonce,
|
|
469
|
-
});
|
|
619
|
+
}, this.payloadVersionOverride);
|
|
470
620
|
|
|
471
621
|
// Select public key format based on resolved auth profile.
|
|
472
622
|
const profile = resolveAuthProfile();
|
|
@@ -514,9 +664,11 @@ export class OpenClawGatewayClient {
|
|
|
514
664
|
|
|
515
665
|
// Handle connect response
|
|
516
666
|
if (msg.type === 'res' && msg.id === 'connect-1') {
|
|
517
|
-
this.clearConnectTimeout();
|
|
518
667
|
if (msg.ok) {
|
|
519
|
-
|
|
668
|
+
this.clearConnectTimeout();
|
|
669
|
+
const versionUsed = this.payloadVersionOverride
|
|
670
|
+
?? (resolveAuthProfile().name === 'clawdbot-v1' ? 'v2' : 'v3');
|
|
671
|
+
console.log(`[openclaw-ws] Authenticated successfully (payload=${versionUsed}${this.fallbackAttempted ? ', via fallback' : ''})`);
|
|
520
672
|
this.authenticated = true;
|
|
521
673
|
this.consecutiveFailures = 0;
|
|
522
674
|
this.connectResolve?.();
|
|
@@ -525,8 +677,10 @@ export class OpenClawGatewayClient {
|
|
|
525
677
|
} else {
|
|
526
678
|
const errStr = msg.error ? JSON.stringify(msg.error) : 'Authentication rejected';
|
|
527
679
|
const isPairing = /pairing.required|not.paired/i.test(errStr);
|
|
680
|
+
const isSignatureInvalid = /signature.invalid|device.signature|invalid.signature/i.test(errStr);
|
|
528
681
|
|
|
529
682
|
if (isPairing) {
|
|
683
|
+
this.clearConnectTimeout();
|
|
530
684
|
const errObj = msg.error as Record<string, unknown> | undefined;
|
|
531
685
|
const requestId = errObj?.requestId ?? errObj?.request_id ?? '';
|
|
532
686
|
console.error('[openclaw-ws] Pairing rejected — device is not paired with the OpenClaw gateway.');
|
|
@@ -539,8 +693,31 @@ export class OpenClawGatewayClient {
|
|
|
539
693
|
: '~/.openclaw/openclaw.json';
|
|
540
694
|
console.error(`[openclaw-ws] Ensure OPENCLAW_GATEWAY_TOKEN matches ${configHint} gateway.auth.token`);
|
|
541
695
|
this.pairingRejected = true;
|
|
696
|
+
} else if (isSignatureInvalid && !this.fallbackAttempted) {
|
|
697
|
+
// Signature rejected — try the alternate payload version once.
|
|
698
|
+
// Do NOT clear connect timeout — it protects the fallback attempt too.
|
|
699
|
+
this.authRejectCount++;
|
|
700
|
+
this.authFallbackCount++;
|
|
701
|
+
const profile = resolveAuthProfile();
|
|
702
|
+
const currentVersion = this.payloadVersionOverride
|
|
703
|
+
?? (profile.name === 'clawdbot-v1' ? 'v2' : 'v3');
|
|
704
|
+
const fallbackVersion: PayloadVersionOverride = currentVersion === 'v2' ? 'v3' : 'v2';
|
|
705
|
+
|
|
706
|
+
console.warn(`[ws-auth] Signature rejected with ${currentVersion} payload — retrying with ${fallbackVersion} fallback (rejects=${this.authRejectCount} fallbacks=${this.authFallbackCount})`);
|
|
707
|
+
this.payloadVersionOverride = fallbackVersion;
|
|
708
|
+
this.fallbackAttempted = true;
|
|
709
|
+
|
|
710
|
+
// Close current WS and reconnect with the alternate payload.
|
|
711
|
+
// Setting this.ws = null ensures the old WS's close/error handlers
|
|
712
|
+
// no-op via the `this.ws !== ws` guard in doConnect().
|
|
713
|
+
try { this.ws?.close(); } catch {}
|
|
714
|
+
this.ws = null;
|
|
715
|
+
setTimeout(() => this.doConnect(), 0);
|
|
716
|
+
return; // Don't reject the connect promise yet — fallback attempt in progress
|
|
542
717
|
} else {
|
|
543
|
-
|
|
718
|
+
this.clearConnectTimeout();
|
|
719
|
+
this.authRejectCount++;
|
|
720
|
+
console.warn(`[openclaw-ws] Auth rejected (rejects=${this.authRejectCount}): ${errStr}`);
|
|
544
721
|
}
|
|
545
722
|
|
|
546
723
|
this.connectReject?.(new Error(`OpenClaw gateway auth failed: ${errStr}`));
|
|
@@ -576,6 +753,7 @@ export class OpenClawGatewayClient {
|
|
|
576
753
|
|
|
577
754
|
/** Send a chat.send RPC. Returns true if accepted. */
|
|
578
755
|
async sendChatMessage(text: string, idempotencyKey?: string): Promise<boolean> {
|
|
756
|
+
if (this.stopped) return false;
|
|
579
757
|
if (!this.authenticated || !this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
580
758
|
// Try to reconnect
|
|
581
759
|
try {
|
|
@@ -30,8 +30,15 @@ function extractNestedValue(obj: unknown, path: string): unknown {
|
|
|
30
30
|
* Set a deeply nested value in an object by dot-separated path, creating
|
|
31
31
|
* intermediate objects as needed.
|
|
32
32
|
*/
|
|
33
|
+
const DANGEROUS_KEYS = new Set(['__proto__', 'prototype', 'constructor']);
|
|
34
|
+
|
|
33
35
|
function setNestedValue(obj: Record<string, unknown>, path: string, value: unknown): void {
|
|
34
36
|
const keys = path.split('.');
|
|
37
|
+
for (const key of keys) {
|
|
38
|
+
if (DANGEROUS_KEYS.has(key)) {
|
|
39
|
+
throw new Error(`Refusing to set dangerous key "${key}" in path "${path}"`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
35
42
|
let current: Record<string, unknown> = obj;
|
|
36
43
|
for (let i = 0; i < keys.length - 1; i++) {
|
|
37
44
|
const key = keys[i];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-relay/policy",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.10",
|
|
4
4
|
"description": "Agent policy management with multi-level fallback (repo, local PRPM, cloud workspace)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"test:watch": "vitest"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@agent-relay/config": "3.1.
|
|
25
|
+
"@agent-relay/config": "3.1.10"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^22.19.3",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-relay/sdk",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"typescript": "^5.7.3"
|
|
82
82
|
},
|
|
83
83
|
"dependencies": {
|
|
84
|
-
"@agent-relay/config": "3.1.
|
|
84
|
+
"@agent-relay/config": "3.1.10",
|
|
85
85
|
"@relaycast/sdk": "^0.4.0",
|
|
86
86
|
"yaml": "^2.7.0"
|
|
87
87
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-relay/trajectory",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.10",
|
|
4
4
|
"description": "Trajectory integration utilities (trail/PDERO) for Relay",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"test:watch": "vitest"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@agent-relay/config": "3.1.
|
|
25
|
+
"@agent-relay/config": "3.1.10"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^22.19.3",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-relay/user-directory",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.10",
|
|
4
4
|
"description": "User directory service for agent-relay (per-user credential storage)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"test:watch": "vitest"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@agent-relay/utils": "3.1.
|
|
25
|
+
"@agent-relay/utils": "3.1.10"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^22.19.3",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-relay/utils",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.10",
|
|
4
4
|
"description": "Shared utilities for agent-relay: logging, name generation, command resolution, update checking",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/cjs/index.js",
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
"vitest": "^3.2.4"
|
|
113
113
|
},
|
|
114
114
|
"dependencies": {
|
|
115
|
-
"@agent-relay/config": "3.1.
|
|
115
|
+
"@agent-relay/config": "3.1.10",
|
|
116
116
|
"compare-versions": "^6.1.1"
|
|
117
117
|
},
|
|
118
118
|
"publishConfig": {
|