agent-relay 3.1.3 → 3.1.5
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/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/README.md +1 -1
- package/packages/openclaw/dist/__tests__/gateway-control.test.js +1 -1
- package/packages/openclaw/dist/__tests__/gateway-control.test.js.map +1 -1
- package/packages/openclaw/dist/__tests__/gateway-threads.test.js +1 -18
- package/packages/openclaw/dist/__tests__/gateway-threads.test.js.map +1 -1
- package/packages/openclaw/dist/__tests__/ws-client.test.js +1 -1
- package/packages/openclaw/dist/__tests__/ws-client.test.js.map +1 -1
- package/packages/openclaw/dist/cli.js +9 -0
- package/packages/openclaw/dist/cli.js.map +1 -1
- package/packages/openclaw/dist/config.d.ts +2 -0
- package/packages/openclaw/dist/config.d.ts.map +1 -1
- package/packages/openclaw/dist/config.js +17 -6
- package/packages/openclaw/dist/config.js.map +1 -1
- package/packages/openclaw/dist/gateway.d.ts +6 -1
- package/packages/openclaw/dist/gateway.d.ts.map +1 -1
- package/packages/openclaw/dist/gateway.js +44 -17
- package/packages/openclaw/dist/gateway.js.map +1 -1
- package/packages/openclaw/dist/inject.d.ts +1 -1
- package/packages/openclaw/dist/inject.d.ts.map +1 -1
- package/packages/openclaw/dist/inject.js +2 -1
- package/packages/openclaw/dist/inject.js.map +1 -1
- package/packages/openclaw/dist/setup.d.ts.map +1 -1
- package/packages/openclaw/dist/setup.js +166 -73
- package/packages/openclaw/dist/setup.js.map +1 -1
- package/packages/openclaw/dist/spawn/docker.d.ts.map +1 -1
- package/packages/openclaw/dist/spawn/docker.js +2 -1
- package/packages/openclaw/dist/spawn/docker.js.map +1 -1
- package/packages/openclaw/dist/types.d.ts +2 -0
- package/packages/openclaw/dist/types.d.ts.map +1 -1
- package/packages/openclaw/dist/types.js +2 -1
- package/packages/openclaw/dist/types.js.map +1 -1
- package/packages/openclaw/package.json +2 -2
- package/packages/openclaw/skill/SKILL.md +99 -61
- package/packages/openclaw/src/__tests__/SPEC-ws-client-testing.md +2 -2
- package/packages/openclaw/src/__tests__/gateway-control.test.ts +1 -1
- package/packages/openclaw/src/__tests__/gateway-threads.test.ts +1 -22
- package/packages/openclaw/src/__tests__/ws-client.test.ts +1 -1
- package/packages/openclaw/src/cli.ts +10 -0
- package/packages/openclaw/src/config.ts +19 -6
- package/packages/openclaw/src/gateway.ts +54 -18
- package/packages/openclaw/src/inject.ts +2 -2
- package/packages/openclaw/src/setup.ts +176 -78
- package/packages/openclaw/src/spawn/docker.ts +2 -1
- package/packages/openclaw/src/types.ts +3 -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
|
@@ -14,7 +14,7 @@ import type {
|
|
|
14
14
|
} from '@relaycast/sdk';
|
|
15
15
|
import WebSocket from 'ws';
|
|
16
16
|
|
|
17
|
-
import type
|
|
17
|
+
import { DEFAULT_OPENCLAW_GATEWAY_PORT, type GatewayConfig, type InboundMessage, type DeliveryResult } from './types.js';
|
|
18
18
|
import { SpawnManager } from './spawn/manager.js';
|
|
19
19
|
import type { SpawnOptions } from './spawn/types.js';
|
|
20
20
|
|
|
@@ -129,9 +129,14 @@ export class OpenClawGatewayClient {
|
|
|
129
129
|
private connectResolve: (() => void) | null = null;
|
|
130
130
|
private connectReject: ((error: Error) => void) | null = null;
|
|
131
131
|
private connectTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
132
|
+
private pairingRejected = false;
|
|
133
|
+
private consecutiveFailures = 0;
|
|
132
134
|
|
|
133
135
|
/** Default timeout for initial connection (30 seconds). */
|
|
134
136
|
private static readonly CONNECT_TIMEOUT_MS = 30_000;
|
|
137
|
+
private static readonly MAX_CONSECUTIVE_FAILURES = 5;
|
|
138
|
+
private static readonly BASE_RECONNECT_MS = 3_000;
|
|
139
|
+
private static readonly MAX_RECONNECT_MS = 30_000;
|
|
135
140
|
|
|
136
141
|
constructor(token: string, port: number) {
|
|
137
142
|
this.token = token;
|
|
@@ -143,6 +148,10 @@ export class OpenClawGatewayClient {
|
|
|
143
148
|
async connect(): Promise<void> {
|
|
144
149
|
if (this.authenticated && this.ws?.readyState === WebSocket.OPEN) return;
|
|
145
150
|
|
|
151
|
+
// Explicit connect() clears pairing rejection so users can retry after fixing their token
|
|
152
|
+
this.pairingRejected = false;
|
|
153
|
+
this.stopped = false;
|
|
154
|
+
|
|
146
155
|
// Cancel any pending reconnect timer to prevent orphaned WebSocket connections
|
|
147
156
|
if (this.reconnectTimer) {
|
|
148
157
|
clearTimeout(this.reconnectTimer);
|
|
@@ -196,9 +205,18 @@ export class OpenClawGatewayClient {
|
|
|
196
205
|
});
|
|
197
206
|
|
|
198
207
|
this.ws.on('close', (code, reason) => {
|
|
199
|
-
|
|
208
|
+
const reasonStr = reason.toString();
|
|
209
|
+
console.warn(`[openclaw-ws] Disconnected: ${code} ${reasonStr}`);
|
|
200
210
|
const wasAuthenticated = this.authenticated;
|
|
201
211
|
this.authenticated = false;
|
|
212
|
+
|
|
213
|
+
// Detect pairing rejection via close code 1008 (Policy Violation)
|
|
214
|
+
if (code === 1008 || /pairing|not.paired/i.test(reasonStr)) {
|
|
215
|
+
console.error('[openclaw-ws] Connection closed due to pairing policy. Device is not paired.');
|
|
216
|
+
console.error('[openclaw-ws] Ensure OPENCLAW_GATEWAY_TOKEN matches ~/.openclaw/openclaw.json gateway.auth.token');
|
|
217
|
+
this.pairingRejected = true;
|
|
218
|
+
}
|
|
219
|
+
|
|
202
220
|
// Reject all pending RPCs
|
|
203
221
|
for (const [id, pending] of this.pendingRpcs) {
|
|
204
222
|
clearTimeout(pending.timer);
|
|
@@ -213,7 +231,7 @@ export class OpenClawGatewayClient {
|
|
|
213
231
|
this.connectReject = null;
|
|
214
232
|
this.connectResolve = null;
|
|
215
233
|
}
|
|
216
|
-
if (!this.stopped) {
|
|
234
|
+
if (!this.stopped && !this.pairingRejected) {
|
|
217
235
|
this.scheduleReconnect();
|
|
218
236
|
}
|
|
219
237
|
});
|
|
@@ -307,14 +325,23 @@ export class OpenClawGatewayClient {
|
|
|
307
325
|
if (msg.ok) {
|
|
308
326
|
console.log('[openclaw-ws] Authenticated successfully');
|
|
309
327
|
this.authenticated = true;
|
|
328
|
+
this.consecutiveFailures = 0;
|
|
310
329
|
this.connectResolve?.();
|
|
311
330
|
this.connectResolve = null;
|
|
312
331
|
this.connectReject = null;
|
|
313
332
|
} else {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
333
|
+
const errStr = msg.error ? JSON.stringify(msg.error) : 'Authentication rejected';
|
|
334
|
+
const isPairing = /pairing.required|not.paired/i.test(errStr);
|
|
335
|
+
|
|
336
|
+
if (isPairing) {
|
|
337
|
+
console.error('[openclaw-ws] Pairing rejected — device is not paired with the OpenClaw gateway.');
|
|
338
|
+
console.error('[openclaw-ws] Ensure OPENCLAW_GATEWAY_TOKEN matches ~/.openclaw/openclaw.json gateway.auth.token');
|
|
339
|
+
this.pairingRejected = true;
|
|
340
|
+
} else {
|
|
341
|
+
console.warn(`[openclaw-ws] Auth rejected: ${errStr}`);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
this.connectReject?.(new Error(`OpenClaw gateway auth failed: ${errStr}`));
|
|
318
345
|
this.connectReject = null;
|
|
319
346
|
this.connectResolve = null;
|
|
320
347
|
}
|
|
@@ -388,12 +415,25 @@ export class OpenClawGatewayClient {
|
|
|
388
415
|
}
|
|
389
416
|
|
|
390
417
|
private scheduleReconnect(): void {
|
|
391
|
-
if (this.stopped || this.reconnectTimer) return;
|
|
392
|
-
|
|
418
|
+
if (this.stopped || this.pairingRejected || this.reconnectTimer) return;
|
|
419
|
+
|
|
420
|
+
this.consecutiveFailures++;
|
|
421
|
+
|
|
422
|
+
if (this.consecutiveFailures >= OpenClawGatewayClient.MAX_CONSECUTIVE_FAILURES) {
|
|
423
|
+
console.warn(`[openclaw-ws] ${this.consecutiveFailures} consecutive connection failures — stopping reconnect.`);
|
|
424
|
+
console.warn('[openclaw-ws] Check that the OpenClaw gateway is running and OPENCLAW_GATEWAY_TOKEN is correct.');
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const delay = Math.min(
|
|
429
|
+
OpenClawGatewayClient.BASE_RECONNECT_MS * Math.pow(2, this.consecutiveFailures - 1),
|
|
430
|
+
OpenClawGatewayClient.MAX_RECONNECT_MS,
|
|
431
|
+
);
|
|
432
|
+
console.log(`[openclaw-ws] Reconnecting in ${delay / 1000}s (attempt ${this.consecutiveFailures})...`);
|
|
393
433
|
this.reconnectTimer = setTimeout(() => {
|
|
394
434
|
this.reconnectTimer = null;
|
|
395
435
|
this.doConnect();
|
|
396
|
-
},
|
|
436
|
+
}, delay);
|
|
397
437
|
}
|
|
398
438
|
|
|
399
439
|
async disconnect(): Promise<void> {
|
|
@@ -475,7 +515,7 @@ export class InboundGateway {
|
|
|
475
515
|
|
|
476
516
|
// Connect to the local OpenClaw gateway WebSocket (persistent connection)
|
|
477
517
|
const token = this.config.openclawGatewayToken ?? process.env.OPENCLAW_GATEWAY_TOKEN;
|
|
478
|
-
const port = this.config.openclawGatewayPort ??
|
|
518
|
+
const port = this.config.openclawGatewayPort ?? DEFAULT_OPENCLAW_GATEWAY_PORT;
|
|
479
519
|
|
|
480
520
|
if (token) {
|
|
481
521
|
this.openclawClient = new OpenClawGatewayClient(token, port);
|
|
@@ -489,11 +529,8 @@ export class InboundGateway {
|
|
|
489
529
|
console.warn('[gateway] No OPENCLAW_GATEWAY_TOKEN — local delivery disabled');
|
|
490
530
|
}
|
|
491
531
|
|
|
492
|
-
// Register with a viewer- prefixed name so we don't collide with the
|
|
493
|
-
// container broker's agent registration (which uses the bare clawName).
|
|
494
|
-
const viewerName = `viewer-${this.config.clawName}`;
|
|
495
532
|
const registered = await this.relaycast.agents.registerOrGet({
|
|
496
|
-
name:
|
|
533
|
+
name: this.config.clawName,
|
|
497
534
|
type: 'agent',
|
|
498
535
|
persona: 'Relaycast inbound gateway for OpenClaw',
|
|
499
536
|
});
|
|
@@ -785,9 +822,8 @@ export class InboundGateway {
|
|
|
785
822
|
if (!this.running) return;
|
|
786
823
|
if (this.processingMessageIds.has(message.id) || this.isSeen(message.id)) return;
|
|
787
824
|
|
|
788
|
-
// Avoid echo loops — skip messages from this claw
|
|
789
|
-
|
|
790
|
-
if (message.from === this.config.clawName || message.from === viewerName) {
|
|
825
|
+
// Avoid echo loops — skip messages from this claw.
|
|
826
|
+
if (message.from === this.config.clawName) {
|
|
791
827
|
// Only update cursor for real channels with real (non-synthetic) message IDs.
|
|
792
828
|
this.markSeen(message.id);
|
|
793
829
|
return;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AgentRelayClient, SendMessageInput } from '@agent-relay/sdk';
|
|
2
2
|
|
|
3
|
-
import type
|
|
3
|
+
import { DEFAULT_OPENCLAW_GATEWAY_PORT, type InboundMessage, type DeliveryResult } from './types.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Deliver a message to the local claw using the best available method.
|
|
@@ -44,7 +44,7 @@ export async function deliverMessage(
|
|
|
44
44
|
|
|
45
45
|
// Fallback: OpenClaw OpenResponses API (POST /v1/responses on local gateway)
|
|
46
46
|
try {
|
|
47
|
-
const gatewayPort = process.env.OPENCLAW_GATEWAY_PORT ?? process.env.GATEWAY_PORT ??
|
|
47
|
+
const gatewayPort = process.env.OPENCLAW_GATEWAY_PORT ?? process.env.GATEWAY_PORT ?? String(DEFAULT_OPENCLAW_GATEWAY_PORT);
|
|
48
48
|
const token = process.env.OPENCLAW_GATEWAY_TOKEN;
|
|
49
49
|
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
|
50
50
|
if (token) {
|
|
@@ -1,12 +1,67 @@
|
|
|
1
1
|
import { mkdir, writeFile, readFile, copyFile } from 'node:fs/promises';
|
|
2
|
+
import { createConnection } from 'node:net';
|
|
2
3
|
import { join, dirname } from 'node:path';
|
|
3
4
|
import { existsSync } from 'node:fs';
|
|
4
5
|
import { hostname } from 'node:os';
|
|
5
6
|
import { fileURLToPath } from 'node:url';
|
|
6
7
|
import { spawn as spawnProcess, execFileSync } from 'node:child_process';
|
|
7
8
|
|
|
9
|
+
import { RelayCast } from '@relaycast/sdk';
|
|
10
|
+
|
|
8
11
|
import { detectOpenClaw, saveGatewayConfig } from './config.js';
|
|
9
|
-
import
|
|
12
|
+
import { InboundGateway } from './gateway.js';
|
|
13
|
+
import { DEFAULT_OPENCLAW_GATEWAY_PORT, type GatewayConfig } from './types.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Safely traverse a nested object by dot-separated path.
|
|
17
|
+
* Returns undefined if any segment is missing.
|
|
18
|
+
*/
|
|
19
|
+
function extractNestedValue(obj: unknown, path: string): unknown {
|
|
20
|
+
let current: unknown = obj;
|
|
21
|
+
for (const key of path.split('.')) {
|
|
22
|
+
if (current == null || typeof current !== 'object') return undefined;
|
|
23
|
+
current = (current as Record<string, unknown>)[key];
|
|
24
|
+
}
|
|
25
|
+
return current;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Resolve how to invoke mcporter. Prefers a global binary, falls back to npx.
|
|
30
|
+
*/
|
|
31
|
+
function resolveMcporter(): { cmd: string; prefix: string[] } {
|
|
32
|
+
try {
|
|
33
|
+
execFileSync('mcporter', ['--version'], { stdio: 'pipe' });
|
|
34
|
+
return { cmd: 'mcporter', prefix: [] };
|
|
35
|
+
} catch {
|
|
36
|
+
// Global binary not found — try npx (no timeout; cold-cache downloads can be slow)
|
|
37
|
+
try {
|
|
38
|
+
execFileSync('npx', ['-y', 'mcporter', '--version'], { stdio: 'pipe' });
|
|
39
|
+
return { cmd: 'npx', prefix: ['-y', 'mcporter'] };
|
|
40
|
+
} catch {
|
|
41
|
+
throw new Error('mcporter not found (tried global binary and npx)');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Check if a port is already in use by attempting a TCP connection. */
|
|
47
|
+
function isPortInUse(port: number): Promise<boolean> {
|
|
48
|
+
return new Promise((resolve) => {
|
|
49
|
+
const socket = createConnection({ port, host: '127.0.0.1' });
|
|
50
|
+
socket.setTimeout(2000);
|
|
51
|
+
socket.once('connect', () => {
|
|
52
|
+
socket.destroy();
|
|
53
|
+
resolve(true);
|
|
54
|
+
});
|
|
55
|
+
socket.once('timeout', () => {
|
|
56
|
+
socket.destroy();
|
|
57
|
+
resolve(false);
|
|
58
|
+
});
|
|
59
|
+
socket.once('error', () => {
|
|
60
|
+
socket.destroy();
|
|
61
|
+
resolve(false);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
10
65
|
|
|
11
66
|
export interface SetupOptions {
|
|
12
67
|
/** If provided, join this workspace. Otherwise create a new one. */
|
|
@@ -165,16 +220,33 @@ export async function setup(options: SetupOptions): Promise<SetupResult> {
|
|
|
165
220
|
);
|
|
166
221
|
}
|
|
167
222
|
|
|
223
|
+
// Extract gateway auth from openclaw.json (if available)
|
|
224
|
+
const openclawGatewayToken =
|
|
225
|
+
process.env.OPENCLAW_GATEWAY_TOKEN ??
|
|
226
|
+
(extractNestedValue(detection.config, 'gateway.auth.token') as string | undefined);
|
|
227
|
+
const openclawGatewayPortRaw =
|
|
228
|
+
process.env.OPENCLAW_GATEWAY_PORT ??
|
|
229
|
+
(extractNestedValue(detection.config, 'gateway.port') as number | string | undefined);
|
|
230
|
+
const openclawGatewayPort = openclawGatewayPortRaw ? Number(openclawGatewayPortRaw) : undefined;
|
|
231
|
+
|
|
232
|
+
if (!openclawGatewayToken) {
|
|
233
|
+
console.warn('[setup] No gateway token found in openclaw.json or OPENCLAW_GATEWAY_TOKEN env.');
|
|
234
|
+
console.warn('[setup] Inbound gateway may fail to pair. Set it manually:');
|
|
235
|
+
console.warn('[setup] export OPENCLAW_GATEWAY_TOKEN=$(cat ~/.openclaw/openclaw.json | jq -r .gateway.auth.token)');
|
|
236
|
+
}
|
|
237
|
+
|
|
168
238
|
// Save gateway config (.env)
|
|
169
239
|
const gatewayConfig: GatewayConfig = {
|
|
170
240
|
apiKey,
|
|
171
241
|
clawName,
|
|
172
242
|
baseUrl,
|
|
173
243
|
channels,
|
|
244
|
+
openclawGatewayToken,
|
|
245
|
+
openclawGatewayPort: Number.isFinite(openclawGatewayPort) ? openclawGatewayPort : undefined,
|
|
174
246
|
};
|
|
175
247
|
await saveGatewayConfig(gatewayConfig);
|
|
176
248
|
|
|
177
|
-
// Register MCP servers via mcporter
|
|
249
|
+
// Register MCP servers via mcporter (global binary or npx fallback)
|
|
178
250
|
let mcpConfigured = false;
|
|
179
251
|
{
|
|
180
252
|
const envArgs = [
|
|
@@ -184,92 +256,118 @@ export async function setup(options: SetupOptions): Promise<SetupResult> {
|
|
|
184
256
|
: []),
|
|
185
257
|
];
|
|
186
258
|
|
|
259
|
+
let mcp: { cmd: string; prefix: string[] } | null = null;
|
|
187
260
|
try {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
// Register openclaw-spawner MCP server
|
|
199
|
-
execFileSync('mcporter', [
|
|
200
|
-
'config', 'add', 'openclaw-spawner',
|
|
201
|
-
'--command', 'npx',
|
|
202
|
-
'--arg', '@agent-relay/openclaw',
|
|
203
|
-
'--arg', 'mcp-server',
|
|
204
|
-
...envArgs,
|
|
205
|
-
'--scope', 'home',
|
|
206
|
-
'--description', 'OpenClaw spawner MCP server',
|
|
207
|
-
], { stdio: 'pipe' });
|
|
208
|
-
|
|
209
|
-
mcpConfigured = true;
|
|
210
|
-
|
|
211
|
-
// Register this claw as an agent via mcporter and persist the agent token
|
|
261
|
+
mcp = resolveMcporter();
|
|
262
|
+
} catch {
|
|
263
|
+
console.warn('mcporter not found (tried global binary and npx). MCP tools will not be available.');
|
|
264
|
+
console.warn('Install mcporter and re-run setup to enable MCP tools:');
|
|
265
|
+
console.warn(' npm install -g mcporter');
|
|
266
|
+
console.warn(` npx -y @agent-relay/openclaw@latest setup ${apiKey} --name ${clawName}`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (mcp) {
|
|
212
270
|
try {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
'
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
271
|
+
// Register relaycast messaging MCP server
|
|
272
|
+
execFileSync(mcp.cmd, [
|
|
273
|
+
...mcp.prefix,
|
|
274
|
+
'config', 'add', 'relaycast',
|
|
275
|
+
'--command', 'npx',
|
|
276
|
+
'--arg', '@relaycast/mcp',
|
|
277
|
+
...envArgs,
|
|
278
|
+
'--scope', 'home',
|
|
279
|
+
'--description', 'Relaycast messaging MCP server',
|
|
280
|
+
], { stdio: 'pipe' });
|
|
281
|
+
|
|
282
|
+
// Register openclaw-spawner MCP server
|
|
283
|
+
execFileSync(mcp.cmd, [
|
|
284
|
+
...mcp.prefix,
|
|
285
|
+
'config', 'add', 'openclaw-spawner',
|
|
286
|
+
'--command', 'npx',
|
|
287
|
+
'--arg', '@agent-relay/openclaw',
|
|
288
|
+
'--arg', 'mcp-server',
|
|
289
|
+
...envArgs,
|
|
290
|
+
'--scope', 'home',
|
|
291
|
+
'--description', 'OpenClaw spawner MCP server',
|
|
292
|
+
], { stdio: 'pipe' });
|
|
293
|
+
|
|
294
|
+
mcpConfigured = true;
|
|
295
|
+
|
|
296
|
+
// Register this claw via the Relaycast SDK. registerOrRotate handles
|
|
297
|
+
// the 409 "already exists" case by rotating the token automatically.
|
|
221
298
|
try {
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
299
|
+
const relaycast = new RelayCast({ apiKey, baseUrl });
|
|
300
|
+
const registered = await relaycast.agents.registerOrRotate({
|
|
301
|
+
name: clawName,
|
|
302
|
+
type: 'agent',
|
|
303
|
+
});
|
|
304
|
+
const agentToken = registered.token;
|
|
305
|
+
|
|
306
|
+
if (agentToken) {
|
|
307
|
+
// Reconfigure mcporter with the agent token so subsequent calls are authenticated
|
|
308
|
+
try {
|
|
309
|
+
execFileSync(mcp.cmd, [...mcp.prefix, 'config', 'remove', 'relaycast'], { stdio: 'pipe' });
|
|
310
|
+
} catch { /* may not exist */ }
|
|
311
|
+
|
|
312
|
+
execFileSync(mcp.cmd, [
|
|
313
|
+
...mcp.prefix,
|
|
314
|
+
'config', 'add', 'relaycast',
|
|
315
|
+
'--command', 'npx',
|
|
316
|
+
'--arg', '@relaycast/mcp',
|
|
317
|
+
...envArgs,
|
|
318
|
+
'--env', `RELAY_AGENT_TOKEN=${agentToken}`,
|
|
319
|
+
'--scope', 'home',
|
|
320
|
+
'--description', 'Relaycast messaging MCP server',
|
|
321
|
+
], { stdio: 'pipe' });
|
|
322
|
+
|
|
323
|
+
console.log(`Agent "${clawName}" registered with token.`);
|
|
324
|
+
} else {
|
|
325
|
+
console.warn('Agent registered but no token found in response.');
|
|
326
|
+
}
|
|
327
|
+
} catch (regErr) {
|
|
328
|
+
console.warn(`Agent registration failed (non-fatal): ${regErr instanceof Error ? regErr.message : String(regErr)}`);
|
|
249
329
|
}
|
|
250
|
-
} catch (
|
|
251
|
-
console.warn(`
|
|
330
|
+
} catch (err) {
|
|
331
|
+
console.warn(`mcporter configuration failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
252
332
|
}
|
|
253
|
-
} catch (err) {
|
|
254
|
-
// mcporter not installed — non-fatal, print manual instructions
|
|
255
|
-
console.warn('mcporter not found. Install MCP servers manually:');
|
|
256
|
-
console.warn(` mcporter config add relaycast --command npx --arg @relaycast/mcp --env RELAY_API_KEY=${apiKey} --scope home`);
|
|
257
|
-
console.warn(` mcporter config add openclaw-spawner --command npx --arg @agent-relay/openclaw --arg mcp-server --env RELAY_API_KEY=${apiKey} --scope home`);
|
|
258
333
|
}
|
|
259
334
|
}
|
|
260
335
|
|
|
261
|
-
// Auto-start the inbound gateway in the background
|
|
336
|
+
// Auto-start the inbound gateway in the background, but only if one isn't
|
|
337
|
+
// already running. Re-running setup without this check spawns duplicates
|
|
338
|
+
// that fight over the control port.
|
|
262
339
|
let gatewayStarted = false;
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
child.unref();
|
|
340
|
+
// Check the inbound gateway's control port (18790), NOT the OpenClaw
|
|
341
|
+
// gateway WS port (18789) — they are different processes.
|
|
342
|
+
const controlPort = Number(process.env.RELAYCAST_CONTROL_PORT) || InboundGateway.DEFAULT_CONTROL_PORT;
|
|
343
|
+
const gatewayAlreadyRunning = await isPortInUse(controlPort);
|
|
344
|
+
if (gatewayAlreadyRunning) {
|
|
345
|
+
console.log('[setup] Inbound gateway already running — skipping spawn.');
|
|
270
346
|
gatewayStarted = true;
|
|
271
|
-
}
|
|
272
|
-
|
|
347
|
+
} else {
|
|
348
|
+
try {
|
|
349
|
+
const gatewayEnv: Record<string, string> = {
|
|
350
|
+
...process.env as Record<string, string>,
|
|
351
|
+
RELAY_API_KEY: apiKey,
|
|
352
|
+
RELAY_CLAW_NAME: clawName,
|
|
353
|
+
RELAY_BASE_URL: baseUrl,
|
|
354
|
+
};
|
|
355
|
+
if (openclawGatewayToken) {
|
|
356
|
+
gatewayEnv.OPENCLAW_GATEWAY_TOKEN = openclawGatewayToken;
|
|
357
|
+
}
|
|
358
|
+
if (openclawGatewayPort && Number.isFinite(openclawGatewayPort)) {
|
|
359
|
+
gatewayEnv.OPENCLAW_GATEWAY_PORT = String(openclawGatewayPort);
|
|
360
|
+
}
|
|
361
|
+
const child = spawnProcess('npx', ['@agent-relay/openclaw', 'gateway'], {
|
|
362
|
+
stdio: 'ignore',
|
|
363
|
+
detached: true,
|
|
364
|
+
env: gatewayEnv,
|
|
365
|
+
});
|
|
366
|
+
child.unref();
|
|
367
|
+
gatewayStarted = true;
|
|
368
|
+
} catch {
|
|
369
|
+
// Non-fatal — user can start manually
|
|
370
|
+
}
|
|
273
371
|
}
|
|
274
372
|
|
|
275
373
|
const parts = [
|
|
@@ -8,6 +8,7 @@ import { normalizeModelRef } from '../identity/model.js';
|
|
|
8
8
|
import { buildIdentityTask } from '../identity/contract.js';
|
|
9
9
|
import { buildAgentName } from '../identity/naming.js';
|
|
10
10
|
import { convertCodexAuth } from '../auth/converter.js';
|
|
11
|
+
import { DEFAULT_OPENCLAW_GATEWAY_PORT } from '../types.js';
|
|
11
12
|
|
|
12
13
|
async function pathExists(targetPath: string): Promise<boolean> {
|
|
13
14
|
try {
|
|
@@ -152,7 +153,7 @@ export class DockerSpawnProvider implements SpawnProvider {
|
|
|
152
153
|
const modelRef = normalizeModelRef(options.model, preferredProvider);
|
|
153
154
|
const workspaceId = options.workspaceId ?? `local-${Date.now().toString(36)}`;
|
|
154
155
|
const agentName = buildAgentName(workspaceId, options.name);
|
|
155
|
-
const gatewayPort =
|
|
156
|
+
const gatewayPort = DEFAULT_OPENCLAW_GATEWAY_PORT; // Internal to container — each container is isolated
|
|
156
157
|
const identityTask = buildIdentityTask(agentName, workspaceId, modelRef);
|
|
157
158
|
const channels = options.channels?.length ? options.channels : ['general'];
|
|
158
159
|
const gatewayToken = randomUUID().replace(/-/g, '').slice(0, 32);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-relay/policy",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.5",
|
|
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.5"
|
|
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.5",
|
|
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.5",
|
|
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.5",
|
|
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.5"
|
|
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.5",
|
|
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.5"
|
|
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.5",
|
|
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.5",
|
|
116
116
|
"compare-versions": "^6.1.1"
|
|
117
117
|
},
|
|
118
118
|
"publishConfig": {
|