agent-relay 2.0.18 → 2.0.20
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/relay-pty-darwin-arm64 +0 -0
- package/bin/relay-pty-darwin-x64 +0 -0
- package/bin/relay-pty-linux-x64 +0 -0
- package/dist/dashboard/out/404.html +1 -1
- package/dist/dashboard/out/_next/static/chunks/320-a6304232cd0ee2ce.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/631-16b905e5920f9b59.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/providers/page-ecb16ffd3b36262b.js +1 -0
- package/dist/dashboard/out/_next/static/css/{45361ce86b2847c4.css → 6892f8422896ef7a.css} +1 -1
- package/dist/dashboard/out/app/onboarding.html +1 -1
- package/dist/dashboard/out/app/onboarding.txt +1 -1
- package/dist/dashboard/out/app.html +1 -1
- package/dist/dashboard/out/app.txt +2 -2
- package/dist/dashboard/out/cloud/link.html +1 -1
- package/dist/dashboard/out/cloud/link.txt +1 -1
- package/dist/dashboard/out/complete-profile.html +2 -2
- package/dist/dashboard/out/complete-profile.txt +1 -1
- package/dist/dashboard/out/connect-repos.html +1 -1
- package/dist/dashboard/out/connect-repos.txt +1 -1
- package/dist/dashboard/out/history.html +1 -1
- package/dist/dashboard/out/history.txt +1 -1
- package/dist/dashboard/out/index.html +1 -1
- package/dist/dashboard/out/index.txt +2 -2
- package/dist/dashboard/out/login.html +2 -2
- package/dist/dashboard/out/login.txt +1 -1
- package/dist/dashboard/out/metrics.html +1 -1
- package/dist/dashboard/out/metrics.txt +1 -1
- package/dist/dashboard/out/pricing.html +2 -2
- package/dist/dashboard/out/pricing.txt +1 -1
- package/dist/dashboard/out/providers/setup/claude.html +1 -1
- package/dist/dashboard/out/providers/setup/claude.txt +1 -1
- package/dist/dashboard/out/providers/setup/codex.html +1 -1
- package/dist/dashboard/out/providers/setup/codex.txt +1 -1
- package/dist/dashboard/out/providers/setup/cursor.html +1 -1
- package/dist/dashboard/out/providers/setup/cursor.txt +1 -1
- package/dist/dashboard/out/providers.html +1 -1
- package/dist/dashboard/out/providers.txt +2 -2
- package/dist/dashboard/out/signup.html +2 -2
- package/dist/dashboard/out/signup.txt +1 -1
- package/package.json +23 -17
- package/packages/api-types/package.json +2 -2
- package/packages/bridge/dist/spawner.d.ts +2 -0
- package/packages/bridge/dist/spawner.js +75 -23
- package/packages/bridge/package.json +8 -8
- package/packages/cli-tester/README.md +277 -0
- package/packages/cli-tester/dist/index.d.ts +21 -0
- package/packages/cli-tester/dist/index.js +21 -0
- package/packages/cli-tester/dist/utils/credential-check.d.ts +56 -0
- package/packages/cli-tester/dist/utils/credential-check.js +230 -0
- package/packages/cli-tester/dist/utils/socket-client.d.ts +76 -0
- package/packages/cli-tester/dist/utils/socket-client.js +153 -0
- package/packages/cli-tester/docker/entrypoint.sh +58 -0
- package/packages/cli-tester/package.json +32 -0
- package/packages/cli-tester/scripts/clear-auth.sh +101 -0
- package/packages/cli-tester/scripts/inject-message.sh +42 -0
- package/packages/cli-tester/scripts/start.sh +71 -0
- package/packages/cli-tester/scripts/test-cli.sh +56 -0
- package/packages/cli-tester/scripts/test-full-spawn.sh +238 -0
- package/packages/cli-tester/scripts/test-registration.sh +182 -0
- package/packages/cli-tester/scripts/test-setup-flow.sh +202 -0
- package/packages/cli-tester/scripts/test-spawn.sh +140 -0
- package/packages/cli-tester/scripts/test-with-daemon.sh +247 -0
- package/packages/cli-tester/scripts/verify-auth.sh +112 -0
- package/packages/cloud/dist/api/cli-pty-runner.js +9 -6
- package/packages/cloud/package.json +6 -6
- package/packages/config/dist/cli-auth-config.js +75 -0
- package/packages/config/package.json +2 -2
- package/packages/continuity/package.json +1 -1
- package/packages/daemon/dist/cli-auth.js +5 -1
- package/packages/daemon/dist/router.js +4 -4
- package/packages/daemon/dist/server.js +38 -19
- package/packages/daemon/dist/spawn-manager.d.ts +4 -0
- package/packages/daemon/dist/spawn-manager.js +2 -0
- package/packages/daemon/package.json +12 -12
- package/packages/dashboard/dist/server.js +4 -0
- package/packages/dashboard/package.json +14 -14
- package/packages/dashboard/ui/app/providers/page.tsx +2 -2
- package/packages/dashboard/ui/react-components/ProviderConnectionList.tsx +23 -8
- package/packages/dashboard/ui/react-components/SpawnModal.tsx +16 -6
- package/packages/dashboard/ui/react-components/settings/WorkspaceSettingsPanel.tsx +22 -6
- package/packages/dashboard/ui-dist/404.html +1 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/320-a6304232cd0ee2ce.js +1 -0
- package/packages/dashboard/ui-dist/_next/static/chunks/631-16b905e5920f9b59.js +1 -0
- package/packages/dashboard/ui-dist/_next/static/chunks/app/providers/page-ecb16ffd3b36262b.js +1 -0
- package/packages/dashboard/ui-dist/_next/static/css/{45361ce86b2847c4.css → 6892f8422896ef7a.css} +1 -1
- package/packages/dashboard/ui-dist/app/onboarding.html +1 -1
- package/packages/dashboard/ui-dist/app/onboarding.txt +1 -1
- package/packages/dashboard/ui-dist/app.html +1 -1
- package/packages/dashboard/ui-dist/app.txt +2 -2
- package/packages/dashboard/ui-dist/cloud/link.html +1 -1
- package/packages/dashboard/ui-dist/cloud/link.txt +1 -1
- package/packages/dashboard/ui-dist/complete-profile.html +2 -2
- package/packages/dashboard/ui-dist/complete-profile.txt +1 -1
- package/packages/dashboard/ui-dist/connect-repos.html +1 -1
- package/packages/dashboard/ui-dist/connect-repos.txt +1 -1
- package/packages/dashboard/ui-dist/history.html +1 -1
- package/packages/dashboard/ui-dist/history.txt +1 -1
- package/packages/dashboard/ui-dist/index.html +1 -1
- package/packages/dashboard/ui-dist/index.txt +2 -2
- package/packages/dashboard/ui-dist/login.html +2 -2
- package/packages/dashboard/ui-dist/login.txt +1 -1
- package/packages/dashboard/ui-dist/metrics.html +1 -1
- package/packages/dashboard/ui-dist/metrics.txt +1 -1
- package/packages/dashboard/ui-dist/pricing.html +2 -2
- package/packages/dashboard/ui-dist/pricing.txt +1 -1
- package/packages/dashboard/ui-dist/providers/setup/claude.html +1 -1
- package/packages/dashboard/ui-dist/providers/setup/claude.txt +1 -1
- package/packages/dashboard/ui-dist/providers/setup/codex.html +1 -1
- package/packages/dashboard/ui-dist/providers/setup/codex.txt +1 -1
- package/packages/dashboard/ui-dist/providers/setup/cursor.html +1 -1
- package/packages/dashboard/ui-dist/providers/setup/cursor.txt +1 -1
- package/packages/dashboard/ui-dist/providers.html +1 -1
- package/packages/dashboard/ui-dist/providers.txt +2 -2
- package/packages/dashboard/ui-dist/signup.html +2 -2
- package/packages/dashboard/ui-dist/signup.txt +1 -1
- package/packages/dashboard-server/dist/server.js +4 -0
- package/packages/dashboard-server/package.json +12 -12
- package/packages/hooks/package.json +4 -4
- package/packages/mcp/package.json +2 -2
- package/packages/memory/package.json +2 -2
- package/packages/policy/package.json +2 -2
- package/packages/protocol/package.json +1 -1
- package/packages/resiliency/package.json +1 -1
- package/packages/sdk/README.md +512 -58
- package/packages/sdk/dist/client.d.ts +135 -1
- package/packages/sdk/dist/client.js +338 -0
- package/packages/sdk/dist/index.d.ts +2 -1
- package/packages/sdk/dist/index.js +2 -0
- package/packages/sdk/dist/logs.d.ts +61 -0
- package/packages/sdk/dist/logs.js +95 -0
- package/packages/sdk/dist/protocol/index.d.ts +1 -1
- package/packages/sdk/dist/protocol/types.d.ts +186 -1
- package/packages/sdk/package.json +3 -3
- package/packages/spawner/package.json +2 -2
- package/packages/state/package.json +1 -1
- package/packages/storage/dist/sqlite-adapter.js +2 -0
- package/packages/storage/package.json +2 -2
- 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 +1 -1
- package/packages/wrapper/dist/base-wrapper.js +27 -10
- package/packages/wrapper/dist/idle-detector.d.ts +4 -0
- package/packages/wrapper/dist/idle-detector.js +21 -8
- package/packages/wrapper/dist/relay-pty-orchestrator.d.ts +5 -0
- package/packages/wrapper/dist/relay-pty-orchestrator.js +60 -5
- package/packages/wrapper/dist/tmux-wrapper.js +16 -0
- package/packages/wrapper/package.json +7 -7
- package/scripts/hooks/install.sh +16 -0
- package/scripts/hooks/pre-commit +60 -0
- package/scripts/postinstall.js +41 -1
- package/specs/PRIMITIVES_ROADMAP.md +2154 -0
- package/dist/dashboard/out/_next/static/chunks/320-402ffc8646b31da1.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/631-af51bad94027527a.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/providers/page-bcf46064ac4474ce.js +0 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/320-402ffc8646b31da1.js +0 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/631-af51bad94027527a.js +0 -1
- package/packages/dashboard/ui-dist/_next/static/chunks/app/providers/page-bcf46064ac4474ce.js +0 -1
- /package/dist/dashboard/out/_next/static/{JIjqkuDKNeoSg7KaMMuhx → PwtT8u1tFMW_S1HUv0i5S}/_buildManifest.js +0 -0
- /package/dist/dashboard/out/_next/static/{JIjqkuDKNeoSg7KaMMuhx → PwtT8u1tFMW_S1HUv0i5S}/_ssgManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{JIjqkuDKNeoSg7KaMMuhx → 52xh7eSCZzG97BVf5zzLY}/_buildManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{JIjqkuDKNeoSg7KaMMuhx → 52xh7eSCZzG97BVf5zzLY}/_ssgManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{nmkOi7bqeDmLMoWBih8lz → NN1eZ4W4r5XU6mkmJWV2-}/_buildManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{nmkOi7bqeDmLMoWBih8lz → NN1eZ4W4r5XU6mkmJWV2-}/_ssgManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{wk_gKRNSPpWE-ZhGL6UMl → PwtT8u1tFMW_S1HUv0i5S}/_buildManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{wk_gKRNSPpWE-ZhGL6UMl → PwtT8u1tFMW_S1HUv0i5S}/_ssgManifest.js +0 -0
|
@@ -222,7 +222,11 @@ export async function startCLIAuth(provider, options = {}) {
|
|
|
222
222
|
}
|
|
223
223
|
}
|
|
224
224
|
// Extract auth URL (only if not in error state and don't have URL yet)
|
|
225
|
-
|
|
225
|
+
// Use accumulated output to handle URLs that wrap across multiple lines
|
|
226
|
+
// Join lines that look like URL continuations (no space/newline within URLs)
|
|
227
|
+
const cleanAccumulated = stripAnsiCodes(session.output)
|
|
228
|
+
.replace(/\n(?=[a-zA-Z0-9\-_.~:/?#\[\]@!$&'()*+,;=%])/g, '');
|
|
229
|
+
const match = cleanAccumulated.match(config.urlPattern);
|
|
226
230
|
if (match && match[1] && !session.authUrl && session.status !== 'error') {
|
|
227
231
|
session.authUrl = match[1];
|
|
228
232
|
session.status = 'waiting_auth';
|
|
@@ -442,7 +442,7 @@ export class Router {
|
|
|
442
442
|
}
|
|
443
443
|
const to = envelope.to;
|
|
444
444
|
const topic = envelope.topic;
|
|
445
|
-
routerLog.debug(`Route ${senderName} -> ${to}`, { preview: envelope.payload.body?.substring(0, 50) });
|
|
445
|
+
routerLog.debug(`Route ${senderName} -> ${to}`, { preview: typeof envelope.payload.body === 'string' ? envelope.payload.body.substring(0, 50) : JSON.stringify(envelope.payload.body)?.substring(0, 50) });
|
|
446
446
|
if (to === '*') {
|
|
447
447
|
// Broadcast to all (except sender)
|
|
448
448
|
this.broadcast(senderName, envelope, topic);
|
|
@@ -564,7 +564,7 @@ export class Router {
|
|
|
564
564
|
anySent = true;
|
|
565
565
|
routerLog.debug(`Delivered ${from} -> ${to} (user connection ${userConn.id})`, {
|
|
566
566
|
success: sent,
|
|
567
|
-
preview: envelope.payload.body?.substring(0, 40),
|
|
567
|
+
preview: typeof envelope.payload.body === 'string' ? envelope.payload.body.substring(0, 40) : JSON.stringify(envelope.payload.body)?.substring(0, 40),
|
|
568
568
|
});
|
|
569
569
|
// Persist only once (for the first connection)
|
|
570
570
|
if (userConn === [...userConnections][0]) {
|
|
@@ -583,7 +583,7 @@ export class Router {
|
|
|
583
583
|
const target = agentTarget;
|
|
584
584
|
const deliver = this.createDeliverEnvelope(from, to, envelope, target);
|
|
585
585
|
const sent = target.send(deliver);
|
|
586
|
-
routerLog.debug(`Delivered ${from} -> ${to}`, { success: sent, preview: envelope.payload.body?.substring(0, 40) });
|
|
586
|
+
routerLog.debug(`Delivered ${from} -> ${to}`, { success: sent, preview: typeof envelope.payload.body === 'string' ? envelope.payload.body.substring(0, 40) : JSON.stringify(envelope.payload.body)?.substring(0, 40) });
|
|
587
587
|
this.persistDeliverEnvelope(deliver);
|
|
588
588
|
if (sent) {
|
|
589
589
|
this.trackDelivery(target, deliver);
|
|
@@ -772,7 +772,7 @@ export class Router {
|
|
|
772
772
|
routerLog.info(`Persisting offline message for "${to}"`, {
|
|
773
773
|
from,
|
|
774
774
|
messageId: envelope.id,
|
|
775
|
-
bodyPreview: envelope.payload.body?.substring(0, 50),
|
|
775
|
+
bodyPreview: typeof envelope.payload.body === 'string' ? envelope.payload.body.substring(0, 50) : JSON.stringify(envelope.payload.body)?.substring(0, 50),
|
|
776
776
|
});
|
|
777
777
|
this.storage.saveMessage({
|
|
778
778
|
id: envelope.id || generateId(),
|
|
@@ -6,6 +6,7 @@ import net from 'node:net';
|
|
|
6
6
|
import fs from 'node:fs';
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import os from 'node:os';
|
|
9
|
+
import { createRequire } from 'node:module';
|
|
9
10
|
import { Connection, DEFAULT_CONFIG } from './connection.js';
|
|
10
11
|
import { Router } from './router.js';
|
|
11
12
|
import { PROTOCOL_VERSION, } from '@agent-relay/protocol/types';
|
|
@@ -21,6 +22,10 @@ import { generateId } from '@agent-relay/wrapper';
|
|
|
21
22
|
import { createConsensusIntegration, } from './consensus-integration.js';
|
|
22
23
|
import { initTelemetry, track, shutdown as shutdownTelemetry, } from '@agent-relay/telemetry';
|
|
23
24
|
import { RelayWatchdog } from './relay-watchdog.js';
|
|
25
|
+
// Get version from package.json
|
|
26
|
+
const require = createRequire(import.meta.url);
|
|
27
|
+
const packageJson = require('../package.json');
|
|
28
|
+
const DAEMON_VERSION = packageJson.version;
|
|
24
29
|
export const DEFAULT_SOCKET_PATH = '/tmp/agent-relay.sock';
|
|
25
30
|
export const DEFAULT_DAEMON_CONFIG = {
|
|
26
31
|
...DEFAULT_CONFIG,
|
|
@@ -66,23 +71,8 @@ export class Daemon {
|
|
|
66
71
|
if (this.config.teamDir) {
|
|
67
72
|
this.registry = new AgentRegistry(this.config.teamDir);
|
|
68
73
|
}
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
const spawnConfig = typeof this.config.spawnManager === 'object'
|
|
72
|
-
? this.config.spawnManager
|
|
73
|
-
: {};
|
|
74
|
-
// Derive projectRoot from teamDir (teamDir is typically {projectRoot}/.agent-relay/)
|
|
75
|
-
const projectRoot = spawnConfig.projectRoot || path.dirname(this.config.teamDir || this.config.socketPath);
|
|
76
|
-
this.spawnManager = new SpawnManager({
|
|
77
|
-
projectRoot,
|
|
78
|
-
socketPath: this.config.socketPath,
|
|
79
|
-
...spawnConfig,
|
|
80
|
-
// Track spawn count for telemetry
|
|
81
|
-
onAgentSpawn: () => {
|
|
82
|
-
this.agentSpawnCount++;
|
|
83
|
-
},
|
|
84
|
-
});
|
|
85
|
-
}
|
|
74
|
+
// SpawnManager is initialized in start() after router is created
|
|
75
|
+
// so we can wire up onMarkSpawning/onClearSpawning callbacks
|
|
86
76
|
// Storage is initialized lazily in start() to support async createStorageAdapter
|
|
87
77
|
this.server = net.createServer(this.handleConnection.bind(this));
|
|
88
78
|
}
|
|
@@ -150,6 +140,12 @@ export class Daemon {
|
|
|
150
140
|
const connectedUsers = this.router.getUsers();
|
|
151
141
|
const targetDir = this.config.teamDir ?? path.dirname(this.config.socketPath);
|
|
152
142
|
const targetPath = path.join(targetDir, 'connected-agents.json');
|
|
143
|
+
// Debug: log what we're writing
|
|
144
|
+
log.info('Writing connected-agents.json', {
|
|
145
|
+
agents: connectedAgents.join(','),
|
|
146
|
+
path: targetPath,
|
|
147
|
+
teamDir: this.config.teamDir,
|
|
148
|
+
});
|
|
153
149
|
// Ensure directory exists (defensive - may have been deleted)
|
|
154
150
|
if (!fs.existsSync(targetDir)) {
|
|
155
151
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
@@ -230,6 +226,27 @@ export class Daemon {
|
|
|
230
226
|
},
|
|
231
227
|
channelMembershipStore,
|
|
232
228
|
});
|
|
229
|
+
// Initialize SpawnManager if enabled (after router, so we can wire callbacks)
|
|
230
|
+
if (this.config.spawnManager) {
|
|
231
|
+
const spawnConfig = typeof this.config.spawnManager === 'object'
|
|
232
|
+
? this.config.spawnManager
|
|
233
|
+
: {};
|
|
234
|
+
// Derive projectRoot from teamDir (teamDir is typically {projectRoot}/.agent-relay/)
|
|
235
|
+
const projectRoot = spawnConfig.projectRoot || path.dirname(this.config.teamDir || this.config.socketPath);
|
|
236
|
+
this.spawnManager = new SpawnManager({
|
|
237
|
+
projectRoot,
|
|
238
|
+
socketPath: this.config.socketPath,
|
|
239
|
+
...spawnConfig,
|
|
240
|
+
// Track spawn count for telemetry
|
|
241
|
+
onAgentSpawn: () => {
|
|
242
|
+
this.agentSpawnCount++;
|
|
243
|
+
},
|
|
244
|
+
// Wire spawn tracking to router so messages are queued during spawn
|
|
245
|
+
onMarkSpawning: (name) => this.router.markSpawning(name),
|
|
246
|
+
onClearSpawning: (name) => this.router.clearSpawning(name),
|
|
247
|
+
});
|
|
248
|
+
log.info('SpawnManager initialized with spawn tracking callbacks');
|
|
249
|
+
}
|
|
233
250
|
// Initialize consensus (enabled by default, can be disabled with consensus: false)
|
|
234
251
|
if (this.config.consensus !== false) {
|
|
235
252
|
const consensusConfig = typeof this.config.consensus === 'object'
|
|
@@ -1084,7 +1101,7 @@ export class Daemon {
|
|
|
1084
1101
|
id: envelope.id,
|
|
1085
1102
|
ts: Date.now(),
|
|
1086
1103
|
payload: {
|
|
1087
|
-
version:
|
|
1104
|
+
version: DAEMON_VERSION,
|
|
1088
1105
|
uptime: uptimeMs,
|
|
1089
1106
|
cloudConnected: this.cloudSync?.isConnected() ?? false,
|
|
1090
1107
|
agentCount: this.router.connectionCount,
|
|
@@ -1102,8 +1119,10 @@ export class Daemon {
|
|
|
1102
1119
|
return [];
|
|
1103
1120
|
}
|
|
1104
1121
|
try {
|
|
1122
|
+
// If channel is specified, get channel messages; otherwise get DMs to agent
|
|
1123
|
+
const toFilter = inboxPayload.channel || agentName;
|
|
1105
1124
|
const messages = await this.storage.getMessages({
|
|
1106
|
-
to:
|
|
1125
|
+
to: toFilter,
|
|
1107
1126
|
from: inboxPayload.from,
|
|
1108
1127
|
limit: inboxPayload.limit || 50,
|
|
1109
1128
|
unreadOnly: inboxPayload.unreadOnly,
|
|
@@ -20,6 +20,10 @@ export interface SpawnManagerConfig {
|
|
|
20
20
|
onAgentDeath?: OnAgentDeathCallback;
|
|
21
21
|
/** Callback when an agent is spawned (for telemetry tracking) */
|
|
22
22
|
onAgentSpawn?: () => void;
|
|
23
|
+
/** Callback to mark an agent as spawning (before HELLO completes) */
|
|
24
|
+
onMarkSpawning?: (agentName: string) => void;
|
|
25
|
+
/** Callback to clear the spawning flag for an agent */
|
|
26
|
+
onClearSpawning?: (agentName: string) => void;
|
|
23
27
|
}
|
|
24
28
|
/**
|
|
25
29
|
* SpawnManager handles agent lifecycle via protocol messages.
|
|
@@ -20,6 +20,8 @@ export class SpawnManager {
|
|
|
20
20
|
this.spawner = new AgentSpawner({
|
|
21
21
|
projectRoot: config.projectRoot,
|
|
22
22
|
socketPath: config.socketPath,
|
|
23
|
+
onMarkSpawning: config.onMarkSpawning,
|
|
24
|
+
onClearSpawning: config.onClearSpawning,
|
|
23
25
|
});
|
|
24
26
|
this.onAgentSpawn = config.onAgentSpawn;
|
|
25
27
|
if (config.cloudPersistence) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-relay/daemon",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.20",
|
|
4
4
|
"description": "Relay daemon server - agent coordination and message routing",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -22,17 +22,17 @@
|
|
|
22
22
|
"test:watch": "vitest"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@agent-relay/protocol": "2.0.
|
|
26
|
-
"@agent-relay/config": "2.0.
|
|
27
|
-
"@agent-relay/storage": "2.0.
|
|
28
|
-
"@agent-relay/bridge": "2.0.
|
|
29
|
-
"@agent-relay/utils": "2.0.
|
|
30
|
-
"@agent-relay/policy": "2.0.
|
|
31
|
-
"@agent-relay/memory": "2.0.
|
|
32
|
-
"@agent-relay/resiliency": "2.0.
|
|
33
|
-
"@agent-relay/user-directory": "2.0.
|
|
34
|
-
"@agent-relay/wrapper": "2.0.
|
|
35
|
-
"@agent-relay/telemetry": "2.0.
|
|
25
|
+
"@agent-relay/protocol": "2.0.20",
|
|
26
|
+
"@agent-relay/config": "2.0.20",
|
|
27
|
+
"@agent-relay/storage": "2.0.20",
|
|
28
|
+
"@agent-relay/bridge": "2.0.20",
|
|
29
|
+
"@agent-relay/utils": "2.0.20",
|
|
30
|
+
"@agent-relay/policy": "2.0.20",
|
|
31
|
+
"@agent-relay/memory": "2.0.20",
|
|
32
|
+
"@agent-relay/resiliency": "2.0.20",
|
|
33
|
+
"@agent-relay/user-directory": "2.0.20",
|
|
34
|
+
"@agent-relay/wrapper": "2.0.20",
|
|
35
|
+
"@agent-relay/telemetry": "2.0.20",
|
|
36
36
|
"ws": "^8.18.3",
|
|
37
37
|
"better-sqlite3": "^12.6.2",
|
|
38
38
|
"pg": "^8.16.3",
|
|
@@ -420,11 +420,15 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
420
420
|
// Use detectWorkspacePath to find the actual repo directory in cloud workspaces
|
|
421
421
|
const workspacePath = detectWorkspacePath(projectRoot || dataDir);
|
|
422
422
|
console.log(`[dashboard] Workspace path: ${workspacePath}`);
|
|
423
|
+
console.log(`[dashboard] Team dir: ${teamDir}`);
|
|
423
424
|
// Pass dashboard port to spawner so spawned agents can call spawn/release APIs for nested spawning
|
|
424
425
|
// Also pass spawn tracking callbacks so messages can be queued before HELLO completes
|
|
426
|
+
// IMPORTANT: Pass teamDir explicitly to ensure spawner checks same files as daemon
|
|
427
|
+
// This prevents path mismatch when detectWorkspacePath returns different path than daemon uses
|
|
425
428
|
const spawner = enableSpawner
|
|
426
429
|
? new AgentSpawner({
|
|
427
430
|
projectRoot: workspacePath,
|
|
431
|
+
teamDir, // Use same teamDir as daemon to avoid registration detection failures
|
|
428
432
|
tmuxSession,
|
|
429
433
|
dashboardPort: port,
|
|
430
434
|
onMarkSpawning,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-relay/dashboard",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.20",
|
|
4
4
|
"description": "Web dashboard for Agent Relay - optional package for visual agent coordination",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -25,18 +25,18 @@
|
|
|
25
25
|
"test:watch": "vitest"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@agent-relay/protocol": "2.0.
|
|
29
|
-
"@agent-relay/config": "2.0.
|
|
30
|
-
"@agent-relay/storage": "2.0.
|
|
31
|
-
"@agent-relay/bridge": "2.0.
|
|
32
|
-
"@agent-relay/utils": "2.0.
|
|
33
|
-
"@agent-relay/resiliency": "2.0.
|
|
34
|
-
"@agent-relay/trajectory": "2.0.
|
|
35
|
-
"@agent-relay/cloud": "2.0.
|
|
36
|
-
"@agent-relay/daemon": "2.0.
|
|
37
|
-
"@agent-relay/user-directory": "2.0.
|
|
38
|
-
"@agent-relay/wrapper": "2.0.
|
|
39
|
-
"@agent-relay/sdk": "2.0.
|
|
28
|
+
"@agent-relay/protocol": "2.0.20",
|
|
29
|
+
"@agent-relay/config": "2.0.20",
|
|
30
|
+
"@agent-relay/storage": "2.0.20",
|
|
31
|
+
"@agent-relay/bridge": "2.0.20",
|
|
32
|
+
"@agent-relay/utils": "2.0.20",
|
|
33
|
+
"@agent-relay/resiliency": "2.0.20",
|
|
34
|
+
"@agent-relay/trajectory": "2.0.20",
|
|
35
|
+
"@agent-relay/cloud": "2.0.20",
|
|
36
|
+
"@agent-relay/daemon": "2.0.20",
|
|
37
|
+
"@agent-relay/user-directory": "2.0.20",
|
|
38
|
+
"@agent-relay/wrapper": "2.0.20",
|
|
39
|
+
"@agent-relay/sdk": "2.0.20",
|
|
40
40
|
"express": "^5.2.1",
|
|
41
41
|
"ws": "^8.18.3"
|
|
42
42
|
},
|
|
@@ -61,5 +61,5 @@
|
|
|
61
61
|
"web-ui",
|
|
62
62
|
"agent-coordination"
|
|
63
63
|
],
|
|
64
|
-
"license": "
|
|
64
|
+
"license": "Apache-2.0"
|
|
65
65
|
}
|
|
@@ -28,8 +28,8 @@ const AI_PROVIDERS: ProviderInfo[] = [
|
|
|
28
28
|
{ id: 'anthropic', name: 'Anthropic', displayName: 'Claude', color: '#D97757', cliCommand: 'claude' },
|
|
29
29
|
{ id: 'codex', name: 'OpenAI', displayName: 'Codex', color: '#10A37F', cliCommand: 'codex login', supportsDeviceFlow: true, requiresUrlCopy: true },
|
|
30
30
|
{ id: 'google', name: 'Google', displayName: 'Gemini', color: '#4285F4', cliCommand: 'gemini' },
|
|
31
|
-
{ id: 'opencode', name: 'OpenCode', displayName: 'OpenCode', color: '#00D4AA', cliCommand: 'opencode' },
|
|
32
|
-
{ id: 'droid', name: 'Factory', displayName: 'Droid', color: '#6366F1', cliCommand: 'droid' },
|
|
31
|
+
{ id: 'opencode', name: 'OpenCode', displayName: 'OpenCode', color: '#00D4AA', cliCommand: 'opencode', comingSoon: true },
|
|
32
|
+
{ id: 'droid', name: 'Factory', displayName: 'Droid', color: '#6366F1', cliCommand: 'droid', comingSoon: true },
|
|
33
33
|
{ id: 'cursor', name: 'Cursor', displayName: 'Cursor', color: '#7C3AED', cliCommand: 'agent' },
|
|
34
34
|
];
|
|
35
35
|
|
|
@@ -21,6 +21,8 @@ export interface ProviderInfo {
|
|
|
21
21
|
requiresUrlCopy?: boolean;
|
|
22
22
|
/** For OAuth providers, whether they support device flow */
|
|
23
23
|
supportsDeviceFlow?: boolean;
|
|
24
|
+
/** Provider is not yet fully tested/available */
|
|
25
|
+
comingSoon?: boolean;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
// Provider auth configuration
|
|
@@ -288,16 +290,20 @@ export function ProviderConnectionList({
|
|
|
288
290
|
return (
|
|
289
291
|
<button
|
|
290
292
|
key={provider.id}
|
|
291
|
-
onClick={() => !connected && handleConnectProvider(provider)}
|
|
292
|
-
disabled={connected}
|
|
293
|
+
onClick={() => !connected && !provider.comingSoon && handleConnectProvider(provider)}
|
|
294
|
+
disabled={connected || provider.comingSoon}
|
|
293
295
|
className={`w-full flex items-center gap-3 p-4 bg-bg-tertiary rounded-xl border transition-colors text-left ${
|
|
294
|
-
|
|
295
|
-
? 'border-
|
|
296
|
-
:
|
|
296
|
+
provider.comingSoon
|
|
297
|
+
? 'border-border-subtle opacity-60 cursor-not-allowed'
|
|
298
|
+
: connected
|
|
299
|
+
? 'border-green-500/50 cursor-default'
|
|
300
|
+
: 'border-border-subtle hover:border-accent-cyan/50'
|
|
297
301
|
}`}
|
|
298
302
|
>
|
|
299
303
|
<div
|
|
300
|
-
className=
|
|
304
|
+
className={`w-10 h-10 rounded-lg flex items-center justify-center text-white font-bold flex-shrink-0 relative ${
|
|
305
|
+
provider.comingSoon ? 'grayscale' : ''
|
|
306
|
+
}`}
|
|
301
307
|
style={{ backgroundColor: provider.color }}
|
|
302
308
|
>
|
|
303
309
|
{provider.displayName[0]}
|
|
@@ -310,10 +316,19 @@ export function ProviderConnectionList({
|
|
|
310
316
|
)}
|
|
311
317
|
</div>
|
|
312
318
|
<div className="flex-1">
|
|
313
|
-
<p className="text-white font-medium">
|
|
319
|
+
<p className="text-white font-medium flex items-center gap-2">
|
|
320
|
+
{provider.displayName}
|
|
321
|
+
{provider.comingSoon && (
|
|
322
|
+
<span className="px-2 py-0.5 bg-amber-400/20 text-amber-400 text-xs font-medium rounded-full">
|
|
323
|
+
Coming Soon
|
|
324
|
+
</span>
|
|
325
|
+
)}
|
|
326
|
+
</p>
|
|
314
327
|
<p className="text-text-muted text-sm">{provider.description || provider.name}</p>
|
|
315
328
|
</div>
|
|
316
|
-
{
|
|
329
|
+
{provider.comingSoon ? (
|
|
330
|
+
<span className="text-text-muted text-sm">Not available yet</span>
|
|
331
|
+
) : connected ? (
|
|
317
332
|
<span className="text-green-400 text-sm font-medium">Connected</span>
|
|
318
333
|
) : (
|
|
319
334
|
<svg className="w-5 h-5 text-text-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
@@ -122,6 +122,7 @@ const AGENT_TEMPLATES = [
|
|
|
122
122
|
description: 'OpenCode AI agent',
|
|
123
123
|
icon: '🔷',
|
|
124
124
|
providerId: 'opencode',
|
|
125
|
+
comingSoon: true, // Not yet fully tested
|
|
125
126
|
},
|
|
126
127
|
{
|
|
127
128
|
id: 'droid',
|
|
@@ -130,6 +131,7 @@ const AGENT_TEMPLATES = [
|
|
|
130
131
|
description: 'Factory Droid agent',
|
|
131
132
|
icon: '🤖',
|
|
132
133
|
providerId: 'droid',
|
|
134
|
+
comingSoon: true, // Not yet fully tested
|
|
133
135
|
},
|
|
134
136
|
{
|
|
135
137
|
id: 'cursor',
|
|
@@ -393,16 +395,24 @@ export function SpawnModal({
|
|
|
393
395
|
<button
|
|
394
396
|
key={template.id}
|
|
395
397
|
type="button"
|
|
398
|
+
disabled={template.comingSoon}
|
|
396
399
|
className={`
|
|
397
|
-
flex flex-col items-center gap-1 py-3 px-2 border-2 rounded-lg
|
|
398
|
-
${
|
|
399
|
-
? 'bg-
|
|
400
|
-
:
|
|
400
|
+
flex flex-col items-center gap-1 py-3 px-2 border-2 rounded-lg font-sans transition-all duration-150 relative
|
|
401
|
+
${template.comingSoon
|
|
402
|
+
? 'opacity-50 cursor-not-allowed bg-bg-hover border-transparent'
|
|
403
|
+
: selectedTemplate.id === template.id
|
|
404
|
+
? 'bg-accent/10 border-accent cursor-pointer'
|
|
405
|
+
: 'bg-bg-hover border-transparent hover:bg-bg-active cursor-pointer'
|
|
401
406
|
}
|
|
402
407
|
`}
|
|
403
|
-
onClick={() => setSelectedTemplate(template)}
|
|
408
|
+
onClick={() => !template.comingSoon && setSelectedTemplate(template)}
|
|
404
409
|
>
|
|
405
|
-
|
|
410
|
+
{template.comingSoon && (
|
|
411
|
+
<span className="absolute top-1 right-1 px-1.5 py-0.5 bg-amber-400/20 text-amber-400 text-[10px] font-medium rounded">
|
|
412
|
+
Soon
|
|
413
|
+
</span>
|
|
414
|
+
)}
|
|
415
|
+
<span className={`text-2xl ${template.comingSoon ? 'grayscale' : ''}`}>{template.icon}</span>
|
|
406
416
|
<span className="text-sm font-semibold text-text-primary">{template.name}</span>
|
|
407
417
|
<span className="text-xs text-text-muted text-center">{template.description}</span>
|
|
408
418
|
</button>
|
|
@@ -67,6 +67,7 @@ interface AIProvider {
|
|
|
67
67
|
supportsDeviceFlow?: boolean; // Provider supports device flow (easier for headless environments)
|
|
68
68
|
preferApiKey?: boolean; // Show API key input by default (simpler for mobile/containers)
|
|
69
69
|
isConnected?: boolean;
|
|
70
|
+
comingSoon?: boolean; // Provider is not yet fully tested/available
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
const AI_PROVIDERS: AIProvider[] = [
|
|
@@ -111,6 +112,7 @@ const AI_PROVIDERS: AIProvider[] = [
|
|
|
111
112
|
color: '#00D4AA',
|
|
112
113
|
cliCommand: 'opencode',
|
|
113
114
|
supportsOAuth: true,
|
|
115
|
+
comingSoon: true, // Not yet fully tested
|
|
114
116
|
},
|
|
115
117
|
{
|
|
116
118
|
id: 'droid',
|
|
@@ -120,6 +122,7 @@ const AI_PROVIDERS: AIProvider[] = [
|
|
|
120
122
|
color: '#6366F1',
|
|
121
123
|
cliCommand: 'droid',
|
|
122
124
|
supportsOAuth: true,
|
|
125
|
+
comingSoon: true, // Not yet fully tested
|
|
123
126
|
},
|
|
124
127
|
{
|
|
125
128
|
id: 'cursor',
|
|
@@ -614,28 +617,41 @@ export function WorkspaceSettingsPanel({
|
|
|
614
617
|
{AI_PROVIDERS.map((provider) => (
|
|
615
618
|
<div
|
|
616
619
|
key={provider.id}
|
|
617
|
-
className=
|
|
620
|
+
className={`p-5 bg-bg-tertiary rounded-xl border border-border-subtle transition-all duration-200 ${
|
|
621
|
+
provider.comingSoon ? 'opacity-60' : 'hover:border-border-medium'
|
|
622
|
+
}`}
|
|
618
623
|
>
|
|
619
624
|
<div className="flex items-center justify-between">
|
|
620
625
|
<div className="flex items-center gap-4">
|
|
621
626
|
<div
|
|
622
|
-
className=
|
|
627
|
+
className={`w-12 h-12 rounded-xl flex items-center justify-center text-white font-bold text-lg shadow-lg ${
|
|
628
|
+
provider.comingSoon ? 'grayscale' : ''
|
|
629
|
+
}`}
|
|
623
630
|
style={{
|
|
624
631
|
backgroundColor: provider.color,
|
|
625
|
-
boxShadow: `0 4px 20px ${provider.color}40`,
|
|
632
|
+
boxShadow: provider.comingSoon ? 'none' : `0 4px 20px ${provider.color}40`,
|
|
626
633
|
}}
|
|
627
634
|
>
|
|
628
635
|
{provider.displayName[0]}
|
|
629
636
|
</div>
|
|
630
637
|
<div>
|
|
631
|
-
<h4 className="text-base font-semibold text-text-primary">
|
|
638
|
+
<h4 className="text-base font-semibold text-text-primary flex items-center gap-2">
|
|
632
639
|
{provider.displayName}
|
|
640
|
+
{provider.comingSoon && (
|
|
641
|
+
<span className="px-2 py-0.5 bg-amber-400/20 text-amber-400 text-xs font-medium rounded-full">
|
|
642
|
+
Coming Soon
|
|
643
|
+
</span>
|
|
644
|
+
)}
|
|
633
645
|
</h4>
|
|
634
646
|
<p className="text-sm text-text-muted">{provider.description}</p>
|
|
635
647
|
</div>
|
|
636
648
|
</div>
|
|
637
649
|
|
|
638
|
-
{
|
|
650
|
+
{provider.comingSoon ? (
|
|
651
|
+
<div className="px-4 py-2 bg-bg-card rounded-full border border-border-subtle">
|
|
652
|
+
<span className="text-sm text-text-muted">Not available yet</span>
|
|
653
|
+
</div>
|
|
654
|
+
) : providerStatus[provider.id] ? (
|
|
639
655
|
<div className="flex items-center gap-3">
|
|
640
656
|
<div className="flex items-center gap-2 px-4 py-2 bg-success/15 rounded-full border border-success/30">
|
|
641
657
|
<div className="w-2 h-2 rounded-full bg-success animate-pulse" />
|
|
@@ -653,7 +669,7 @@ export function WorkspaceSettingsPanel({
|
|
|
653
669
|
) : null}
|
|
654
670
|
</div>
|
|
655
671
|
|
|
656
|
-
{!providerStatus[provider.id] && (
|
|
672
|
+
{!providerStatus[provider.id] && !provider.comingSoon && (
|
|
657
673
|
<div className="mt-5 pt-5 border-t border-border-subtle">
|
|
658
674
|
{connectingProvider === provider.id && !showApiKeyFallback[provider.id] ? (
|
|
659
675
|
useTerminalSetup[provider.id] ? (
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="/_next/static/css/
|
|
1
|
+
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="/_next/static/css/6892f8422896ef7a.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-1cdd8ed57114d5e1.js"/><script src="/_next/static/chunks/fd9d1056-609918ca7b6280bb.js" async=""></script><script src="/_next/static/chunks/117-c8afed19e821a35d.js" async=""></script><script src="/_next/static/chunks/main-app-fdbeb09028f57c9f.js" async=""></script><meta name="robots" content="noindex"/><title>404: This page could not be found.</title><title>Agent Relay Dashboard</title><meta name="description" content="Fleet control dashboard for Agent Relay"/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body><div style="font-family:system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding:0 23px 0 0;font-size:24px;font-weight:500;vertical-align:top;line-height:49px">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:49px;margin:0">This page could not be found.</h2></div></div></div><script src="/_next/static/chunks/webpack-1cdd8ed57114d5e1.js" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0]);self.__next_f.push([2,null])</script><script>self.__next_f.push([1,"1:HL[\"/_next/static/css/6892f8422896ef7a.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"2:I[2846,[],\"\"]\n4:I[4707,[],\"\"]\n5:I[6423,[],\"\"]\nb:I[1060,[],\"\"]\n6:{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"}\n7:{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"}\n8:{\"display\":\"inline-block\"}\n9:{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0}\nc:[]\n"])</script><script>self.__next_f.push([1,"0:[\"$\",\"$L2\",null,{\"buildId\":\"PwtT8u1tFMW_S1HUv0i5S\",\"assetPrefix\":\"\",\"urlParts\":[\"\",\"_not-found\"],\"initialTree\":[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{}]}]},\"$undefined\",\"$undefined\",true],\"initialSeedData\":[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{},[[\"$L3\",[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],null],null],null]},[null,[\"$\",\"$L4\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\",\"/_not-found\",\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L5\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"notFoundStyles\":\"$undefined\"}]],null]},[[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/6892f8422896ef7a.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[\"$\",\"body\",null,{\"children\":[\"$\",\"$L4\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L5\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":\"$6\",\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":\"$7\",\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":\"$8\",\"children\":[\"$\",\"h2\",null,{\"style\":\"$9\",\"children\":\"This page could not be found.\"}]}]]}]}]],\"notFoundStyles\":[]}]}]}]],null],null],\"couldBeIntercepted\":false,\"initialHead\":[[\"$\",\"meta\",null,{\"name\":\"robots\",\"content\":\"noindex\"}],\"$La\"],\"globalErrorComponent\":\"$b\",\"missingSlots\":\"$Wc\"}]\n"])</script><script>self.__next_f.push([1,"a:[[\"$\",\"meta\",\"0\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}],[\"$\",\"meta\",\"1\",{\"charSet\":\"utf-8\"}],[\"$\",\"title\",\"2\",{\"children\":\"Agent Relay Dashboard\"}],[\"$\",\"meta\",\"3\",{\"name\":\"description\",\"content\":\"Fleet control dashboard for Agent Relay\"}]]\n3:null\n"])</script></body></html>
|