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.
Files changed (165) hide show
  1. package/bin/relay-pty-darwin-arm64 +0 -0
  2. package/bin/relay-pty-darwin-x64 +0 -0
  3. package/bin/relay-pty-linux-x64 +0 -0
  4. package/dist/dashboard/out/404.html +1 -1
  5. package/dist/dashboard/out/_next/static/chunks/320-a6304232cd0ee2ce.js +1 -0
  6. package/dist/dashboard/out/_next/static/chunks/631-16b905e5920f9b59.js +1 -0
  7. package/dist/dashboard/out/_next/static/chunks/app/providers/page-ecb16ffd3b36262b.js +1 -0
  8. package/dist/dashboard/out/_next/static/css/{45361ce86b2847c4.css → 6892f8422896ef7a.css} +1 -1
  9. package/dist/dashboard/out/app/onboarding.html +1 -1
  10. package/dist/dashboard/out/app/onboarding.txt +1 -1
  11. package/dist/dashboard/out/app.html +1 -1
  12. package/dist/dashboard/out/app.txt +2 -2
  13. package/dist/dashboard/out/cloud/link.html +1 -1
  14. package/dist/dashboard/out/cloud/link.txt +1 -1
  15. package/dist/dashboard/out/complete-profile.html +2 -2
  16. package/dist/dashboard/out/complete-profile.txt +1 -1
  17. package/dist/dashboard/out/connect-repos.html +1 -1
  18. package/dist/dashboard/out/connect-repos.txt +1 -1
  19. package/dist/dashboard/out/history.html +1 -1
  20. package/dist/dashboard/out/history.txt +1 -1
  21. package/dist/dashboard/out/index.html +1 -1
  22. package/dist/dashboard/out/index.txt +2 -2
  23. package/dist/dashboard/out/login.html +2 -2
  24. package/dist/dashboard/out/login.txt +1 -1
  25. package/dist/dashboard/out/metrics.html +1 -1
  26. package/dist/dashboard/out/metrics.txt +1 -1
  27. package/dist/dashboard/out/pricing.html +2 -2
  28. package/dist/dashboard/out/pricing.txt +1 -1
  29. package/dist/dashboard/out/providers/setup/claude.html +1 -1
  30. package/dist/dashboard/out/providers/setup/claude.txt +1 -1
  31. package/dist/dashboard/out/providers/setup/codex.html +1 -1
  32. package/dist/dashboard/out/providers/setup/codex.txt +1 -1
  33. package/dist/dashboard/out/providers/setup/cursor.html +1 -1
  34. package/dist/dashboard/out/providers/setup/cursor.txt +1 -1
  35. package/dist/dashboard/out/providers.html +1 -1
  36. package/dist/dashboard/out/providers.txt +2 -2
  37. package/dist/dashboard/out/signup.html +2 -2
  38. package/dist/dashboard/out/signup.txt +1 -1
  39. package/package.json +23 -17
  40. package/packages/api-types/package.json +2 -2
  41. package/packages/bridge/dist/spawner.d.ts +2 -0
  42. package/packages/bridge/dist/spawner.js +75 -23
  43. package/packages/bridge/package.json +8 -8
  44. package/packages/cli-tester/README.md +277 -0
  45. package/packages/cli-tester/dist/index.d.ts +21 -0
  46. package/packages/cli-tester/dist/index.js +21 -0
  47. package/packages/cli-tester/dist/utils/credential-check.d.ts +56 -0
  48. package/packages/cli-tester/dist/utils/credential-check.js +230 -0
  49. package/packages/cli-tester/dist/utils/socket-client.d.ts +76 -0
  50. package/packages/cli-tester/dist/utils/socket-client.js +153 -0
  51. package/packages/cli-tester/docker/entrypoint.sh +58 -0
  52. package/packages/cli-tester/package.json +32 -0
  53. package/packages/cli-tester/scripts/clear-auth.sh +101 -0
  54. package/packages/cli-tester/scripts/inject-message.sh +42 -0
  55. package/packages/cli-tester/scripts/start.sh +71 -0
  56. package/packages/cli-tester/scripts/test-cli.sh +56 -0
  57. package/packages/cli-tester/scripts/test-full-spawn.sh +238 -0
  58. package/packages/cli-tester/scripts/test-registration.sh +182 -0
  59. package/packages/cli-tester/scripts/test-setup-flow.sh +202 -0
  60. package/packages/cli-tester/scripts/test-spawn.sh +140 -0
  61. package/packages/cli-tester/scripts/test-with-daemon.sh +247 -0
  62. package/packages/cli-tester/scripts/verify-auth.sh +112 -0
  63. package/packages/cloud/dist/api/cli-pty-runner.js +9 -6
  64. package/packages/cloud/package.json +6 -6
  65. package/packages/config/dist/cli-auth-config.js +75 -0
  66. package/packages/config/package.json +2 -2
  67. package/packages/continuity/package.json +1 -1
  68. package/packages/daemon/dist/cli-auth.js +5 -1
  69. package/packages/daemon/dist/router.js +4 -4
  70. package/packages/daemon/dist/server.js +38 -19
  71. package/packages/daemon/dist/spawn-manager.d.ts +4 -0
  72. package/packages/daemon/dist/spawn-manager.js +2 -0
  73. package/packages/daemon/package.json +12 -12
  74. package/packages/dashboard/dist/server.js +4 -0
  75. package/packages/dashboard/package.json +14 -14
  76. package/packages/dashboard/ui/app/providers/page.tsx +2 -2
  77. package/packages/dashboard/ui/react-components/ProviderConnectionList.tsx +23 -8
  78. package/packages/dashboard/ui/react-components/SpawnModal.tsx +16 -6
  79. package/packages/dashboard/ui/react-components/settings/WorkspaceSettingsPanel.tsx +22 -6
  80. package/packages/dashboard/ui-dist/404.html +1 -1
  81. package/packages/dashboard/ui-dist/_next/static/chunks/320-a6304232cd0ee2ce.js +1 -0
  82. package/packages/dashboard/ui-dist/_next/static/chunks/631-16b905e5920f9b59.js +1 -0
  83. package/packages/dashboard/ui-dist/_next/static/chunks/app/providers/page-ecb16ffd3b36262b.js +1 -0
  84. package/packages/dashboard/ui-dist/_next/static/css/{45361ce86b2847c4.css → 6892f8422896ef7a.css} +1 -1
  85. package/packages/dashboard/ui-dist/app/onboarding.html +1 -1
  86. package/packages/dashboard/ui-dist/app/onboarding.txt +1 -1
  87. package/packages/dashboard/ui-dist/app.html +1 -1
  88. package/packages/dashboard/ui-dist/app.txt +2 -2
  89. package/packages/dashboard/ui-dist/cloud/link.html +1 -1
  90. package/packages/dashboard/ui-dist/cloud/link.txt +1 -1
  91. package/packages/dashboard/ui-dist/complete-profile.html +2 -2
  92. package/packages/dashboard/ui-dist/complete-profile.txt +1 -1
  93. package/packages/dashboard/ui-dist/connect-repos.html +1 -1
  94. package/packages/dashboard/ui-dist/connect-repos.txt +1 -1
  95. package/packages/dashboard/ui-dist/history.html +1 -1
  96. package/packages/dashboard/ui-dist/history.txt +1 -1
  97. package/packages/dashboard/ui-dist/index.html +1 -1
  98. package/packages/dashboard/ui-dist/index.txt +2 -2
  99. package/packages/dashboard/ui-dist/login.html +2 -2
  100. package/packages/dashboard/ui-dist/login.txt +1 -1
  101. package/packages/dashboard/ui-dist/metrics.html +1 -1
  102. package/packages/dashboard/ui-dist/metrics.txt +1 -1
  103. package/packages/dashboard/ui-dist/pricing.html +2 -2
  104. package/packages/dashboard/ui-dist/pricing.txt +1 -1
  105. package/packages/dashboard/ui-dist/providers/setup/claude.html +1 -1
  106. package/packages/dashboard/ui-dist/providers/setup/claude.txt +1 -1
  107. package/packages/dashboard/ui-dist/providers/setup/codex.html +1 -1
  108. package/packages/dashboard/ui-dist/providers/setup/codex.txt +1 -1
  109. package/packages/dashboard/ui-dist/providers/setup/cursor.html +1 -1
  110. package/packages/dashboard/ui-dist/providers/setup/cursor.txt +1 -1
  111. package/packages/dashboard/ui-dist/providers.html +1 -1
  112. package/packages/dashboard/ui-dist/providers.txt +2 -2
  113. package/packages/dashboard/ui-dist/signup.html +2 -2
  114. package/packages/dashboard/ui-dist/signup.txt +1 -1
  115. package/packages/dashboard-server/dist/server.js +4 -0
  116. package/packages/dashboard-server/package.json +12 -12
  117. package/packages/hooks/package.json +4 -4
  118. package/packages/mcp/package.json +2 -2
  119. package/packages/memory/package.json +2 -2
  120. package/packages/policy/package.json +2 -2
  121. package/packages/protocol/package.json +1 -1
  122. package/packages/resiliency/package.json +1 -1
  123. package/packages/sdk/README.md +512 -58
  124. package/packages/sdk/dist/client.d.ts +135 -1
  125. package/packages/sdk/dist/client.js +338 -0
  126. package/packages/sdk/dist/index.d.ts +2 -1
  127. package/packages/sdk/dist/index.js +2 -0
  128. package/packages/sdk/dist/logs.d.ts +61 -0
  129. package/packages/sdk/dist/logs.js +95 -0
  130. package/packages/sdk/dist/protocol/index.d.ts +1 -1
  131. package/packages/sdk/dist/protocol/types.d.ts +186 -1
  132. package/packages/sdk/package.json +3 -3
  133. package/packages/spawner/package.json +2 -2
  134. package/packages/state/package.json +1 -1
  135. package/packages/storage/dist/sqlite-adapter.js +2 -0
  136. package/packages/storage/package.json +2 -2
  137. package/packages/telemetry/package.json +1 -1
  138. package/packages/trajectory/package.json +2 -2
  139. package/packages/user-directory/package.json +2 -2
  140. package/packages/utils/package.json +1 -1
  141. package/packages/wrapper/dist/base-wrapper.js +27 -10
  142. package/packages/wrapper/dist/idle-detector.d.ts +4 -0
  143. package/packages/wrapper/dist/idle-detector.js +21 -8
  144. package/packages/wrapper/dist/relay-pty-orchestrator.d.ts +5 -0
  145. package/packages/wrapper/dist/relay-pty-orchestrator.js +60 -5
  146. package/packages/wrapper/dist/tmux-wrapper.js +16 -0
  147. package/packages/wrapper/package.json +7 -7
  148. package/scripts/hooks/install.sh +16 -0
  149. package/scripts/hooks/pre-commit +60 -0
  150. package/scripts/postinstall.js +41 -1
  151. package/specs/PRIMITIVES_ROADMAP.md +2154 -0
  152. package/dist/dashboard/out/_next/static/chunks/320-402ffc8646b31da1.js +0 -1
  153. package/dist/dashboard/out/_next/static/chunks/631-af51bad94027527a.js +0 -1
  154. package/dist/dashboard/out/_next/static/chunks/app/providers/page-bcf46064ac4474ce.js +0 -1
  155. package/packages/dashboard/ui-dist/_next/static/chunks/320-402ffc8646b31da1.js +0 -1
  156. package/packages/dashboard/ui-dist/_next/static/chunks/631-af51bad94027527a.js +0 -1
  157. package/packages/dashboard/ui-dist/_next/static/chunks/app/providers/page-bcf46064ac4474ce.js +0 -1
  158. /package/dist/dashboard/out/_next/static/{JIjqkuDKNeoSg7KaMMuhx → PwtT8u1tFMW_S1HUv0i5S}/_buildManifest.js +0 -0
  159. /package/dist/dashboard/out/_next/static/{JIjqkuDKNeoSg7KaMMuhx → PwtT8u1tFMW_S1HUv0i5S}/_ssgManifest.js +0 -0
  160. /package/packages/dashboard/ui-dist/_next/static/{JIjqkuDKNeoSg7KaMMuhx → 52xh7eSCZzG97BVf5zzLY}/_buildManifest.js +0 -0
  161. /package/packages/dashboard/ui-dist/_next/static/{JIjqkuDKNeoSg7KaMMuhx → 52xh7eSCZzG97BVf5zzLY}/_ssgManifest.js +0 -0
  162. /package/packages/dashboard/ui-dist/_next/static/{nmkOi7bqeDmLMoWBih8lz → NN1eZ4W4r5XU6mkmJWV2-}/_buildManifest.js +0 -0
  163. /package/packages/dashboard/ui-dist/_next/static/{nmkOi7bqeDmLMoWBih8lz → NN1eZ4W4r5XU6mkmJWV2-}/_ssgManifest.js +0 -0
  164. /package/packages/dashboard/ui-dist/_next/static/{wk_gKRNSPpWE-ZhGL6UMl → PwtT8u1tFMW_S1HUv0i5S}/_buildManifest.js +0 -0
  165. /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
- const match = cleanText.match(config.urlPattern);
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
- // Initialize SpawnManager if enabled
70
- if (this.config.spawnManager) {
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: '2.0.13',
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: agentName,
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.18",
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.18",
26
- "@agent-relay/config": "2.0.18",
27
- "@agent-relay/storage": "2.0.18",
28
- "@agent-relay/bridge": "2.0.18",
29
- "@agent-relay/utils": "2.0.18",
30
- "@agent-relay/policy": "2.0.18",
31
- "@agent-relay/memory": "2.0.18",
32
- "@agent-relay/resiliency": "2.0.18",
33
- "@agent-relay/user-directory": "2.0.18",
34
- "@agent-relay/wrapper": "2.0.18",
35
- "@agent-relay/telemetry": "2.0.18",
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.18",
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.18",
29
- "@agent-relay/config": "2.0.18",
30
- "@agent-relay/storage": "2.0.18",
31
- "@agent-relay/bridge": "2.0.18",
32
- "@agent-relay/utils": "2.0.18",
33
- "@agent-relay/resiliency": "2.0.18",
34
- "@agent-relay/trajectory": "2.0.18",
35
- "@agent-relay/cloud": "2.0.18",
36
- "@agent-relay/daemon": "2.0.18",
37
- "@agent-relay/user-directory": "2.0.18",
38
- "@agent-relay/wrapper": "2.0.18",
39
- "@agent-relay/sdk": "2.0.18",
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": "MIT"
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
- connected
295
- ? 'border-green-500/50 cursor-default'
296
- : 'border-border-subtle hover:border-accent-cyan/50'
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="w-10 h-10 rounded-lg flex items-center justify-center text-white font-bold flex-shrink-0 relative"
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">{provider.displayName}</p>
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
- {connected ? (
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 cursor-pointer font-sans transition-all duration-150
398
- ${selectedTemplate.id === template.id
399
- ? 'bg-accent/10 border-accent'
400
- : 'bg-bg-hover border-transparent hover:bg-bg-active'
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
- <span className="text-2xl">{template.icon}</span>
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="p-5 bg-bg-tertiary rounded-xl border border-border-subtle hover:border-border-medium transition-all duration-200"
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="w-12 h-12 rounded-xl flex items-center justify-center text-white font-bold text-lg shadow-lg"
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
- {providerStatus[provider.id] ? (
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/45361ce86b2847c4.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,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;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/45361ce86b2847c4.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\":\"JIjqkuDKNeoSg7KaMMuhx\",\"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/45361ce86b2847c4.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>
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,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;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>