agent-relay 2.1.5 → 2.1.6

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 (117) hide show
  1. package/README.md +85 -236
  2. package/dist/index.cjs +75 -12
  3. package/package.json +18 -18
  4. package/packages/api-types/package.json +1 -1
  5. package/packages/benchmark/package.json +4 -4
  6. package/packages/bridge/package.json +8 -8
  7. package/packages/cli-tester/package.json +1 -1
  8. package/packages/config/package.json +2 -2
  9. package/packages/continuity/package.json +2 -2
  10. package/packages/daemon/dist/server.d.ts +5 -0
  11. package/packages/daemon/dist/server.d.ts.map +1 -1
  12. package/packages/daemon/dist/server.js +31 -0
  13. package/packages/daemon/dist/server.js.map +1 -1
  14. package/packages/daemon/package.json +12 -12
  15. package/packages/daemon/src/server.ts +37 -0
  16. package/packages/hooks/package.json +4 -4
  17. package/packages/mcp/dist/cloud.d.ts +7 -114
  18. package/packages/mcp/dist/cloud.d.ts.map +1 -1
  19. package/packages/mcp/dist/cloud.js +21 -431
  20. package/packages/mcp/dist/cloud.js.map +1 -1
  21. package/packages/mcp/dist/errors.d.ts +4 -22
  22. package/packages/mcp/dist/errors.d.ts.map +1 -1
  23. package/packages/mcp/dist/errors.js +4 -43
  24. package/packages/mcp/dist/errors.js.map +1 -1
  25. package/packages/mcp/dist/hybrid-client.d.ts.map +1 -1
  26. package/packages/mcp/dist/hybrid-client.js +7 -1
  27. package/packages/mcp/dist/hybrid-client.js.map +1 -1
  28. package/packages/mcp/package.json +4 -3
  29. package/packages/mcp/src/cloud.ts +29 -511
  30. package/packages/mcp/src/errors.ts +12 -49
  31. package/packages/mcp/src/hybrid-client.ts +8 -1
  32. package/packages/mcp/tests/discover.test.ts +72 -11
  33. package/packages/memory/package.json +2 -2
  34. package/packages/policy/package.json +2 -2
  35. package/packages/protocol/dist/types.d.ts +17 -1
  36. package/packages/protocol/dist/types.d.ts.map +1 -1
  37. package/packages/protocol/package.json +1 -1
  38. package/packages/protocol/src/types.ts +23 -0
  39. package/packages/resiliency/package.json +1 -1
  40. package/packages/sdk/dist/browser-client.d.ts +212 -0
  41. package/packages/sdk/dist/browser-client.d.ts.map +1 -0
  42. package/packages/sdk/dist/browser-client.js +750 -0
  43. package/packages/sdk/dist/browser-client.js.map +1 -0
  44. package/packages/sdk/dist/browser-framing.d.ts +46 -0
  45. package/packages/sdk/dist/browser-framing.d.ts.map +1 -0
  46. package/packages/sdk/dist/browser-framing.js +122 -0
  47. package/packages/sdk/dist/browser-framing.js.map +1 -0
  48. package/packages/sdk/dist/client.d.ts +129 -2
  49. package/packages/sdk/dist/client.d.ts.map +1 -1
  50. package/packages/sdk/dist/client.js +312 -2
  51. package/packages/sdk/dist/client.js.map +1 -1
  52. package/packages/sdk/dist/discovery.d.ts +10 -0
  53. package/packages/sdk/dist/discovery.d.ts.map +1 -0
  54. package/packages/sdk/dist/discovery.js +22 -0
  55. package/packages/sdk/dist/discovery.js.map +1 -0
  56. package/packages/sdk/dist/errors.d.ts +9 -0
  57. package/packages/sdk/dist/errors.d.ts.map +1 -0
  58. package/packages/sdk/dist/errors.js +9 -0
  59. package/packages/sdk/dist/errors.js.map +1 -0
  60. package/packages/sdk/dist/index.d.ts +18 -2
  61. package/packages/sdk/dist/index.d.ts.map +1 -1
  62. package/packages/sdk/dist/index.js +27 -1
  63. package/packages/sdk/dist/index.js.map +1 -1
  64. package/packages/sdk/dist/transports/index.d.ts +92 -0
  65. package/packages/sdk/dist/transports/index.d.ts.map +1 -0
  66. package/packages/sdk/dist/transports/index.js +129 -0
  67. package/packages/sdk/dist/transports/index.js.map +1 -0
  68. package/packages/sdk/dist/transports/socket-transport.d.ts +30 -0
  69. package/packages/sdk/dist/transports/socket-transport.d.ts.map +1 -0
  70. package/packages/sdk/dist/transports/socket-transport.js +94 -0
  71. package/packages/sdk/dist/transports/socket-transport.js.map +1 -0
  72. package/packages/sdk/dist/transports/types.d.ts +69 -0
  73. package/packages/sdk/dist/transports/types.d.ts.map +1 -0
  74. package/packages/sdk/dist/transports/types.js +10 -0
  75. package/packages/sdk/dist/transports/types.js.map +1 -0
  76. package/packages/sdk/dist/transports/websocket-transport.d.ts +55 -0
  77. package/packages/sdk/dist/transports/websocket-transport.d.ts.map +1 -0
  78. package/packages/sdk/dist/transports/websocket-transport.js +180 -0
  79. package/packages/sdk/dist/transports/websocket-transport.js.map +1 -0
  80. package/packages/sdk/package.json +28 -4
  81. package/packages/sdk/src/browser-client.ts +985 -0
  82. package/packages/sdk/src/browser-framing.test.ts +115 -0
  83. package/packages/sdk/src/browser-framing.ts +150 -0
  84. package/packages/sdk/src/client.test.ts +425 -0
  85. package/packages/sdk/src/client.ts +397 -3
  86. package/packages/sdk/src/discovery.ts +38 -0
  87. package/packages/sdk/src/errors.ts +17 -0
  88. package/packages/sdk/src/index.ts +82 -1
  89. package/packages/sdk/src/transports/index.ts +197 -0
  90. package/packages/sdk/src/transports/socket-transport.ts +115 -0
  91. package/packages/sdk/src/transports/types.ts +77 -0
  92. package/packages/sdk/src/transports/websocket-transport.ts +245 -0
  93. package/packages/sdk/tsconfig.json +1 -1
  94. package/packages/spawner/package.json +1 -1
  95. package/packages/state/package.json +1 -1
  96. package/packages/storage/package.json +2 -2
  97. package/packages/storage/src/jsonl-adapter.test.ts +8 -3
  98. package/packages/telemetry/package.json +1 -1
  99. package/packages/trajectory/package.json +2 -2
  100. package/packages/user-directory/package.json +2 -2
  101. package/packages/utils/dist/cjs/discovery.js +328 -0
  102. package/packages/utils/dist/cjs/errors.js +81 -0
  103. package/packages/utils/dist/discovery.d.ts +123 -0
  104. package/packages/utils/dist/discovery.d.ts.map +1 -0
  105. package/packages/utils/dist/discovery.js +439 -0
  106. package/packages/utils/dist/discovery.js.map +1 -0
  107. package/packages/utils/dist/errors.d.ts +29 -0
  108. package/packages/utils/dist/errors.d.ts.map +1 -0
  109. package/packages/utils/dist/errors.js +50 -0
  110. package/packages/utils/dist/errors.js.map +1 -0
  111. package/packages/utils/package.json +15 -2
  112. package/packages/utils/src/consolidation.test.ts +125 -0
  113. package/packages/utils/src/discovery.test.ts +196 -0
  114. package/packages/utils/src/discovery.ts +524 -0
  115. package/packages/utils/src/errors.test.ts +83 -0
  116. package/packages/utils/src/errors.ts +56 -0
  117. package/packages/wrapper/package.json +6 -6
@@ -1,523 +1,41 @@
1
1
  /**
2
2
  * Cloud Integration for Agent Relay MCP Server
3
3
  *
4
- * Provides cloud workspace detection, remote daemon connection,
5
- * and workspace-aware socket discovery for cloud deployments.
6
- */
7
-
8
- import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
9
- import { join } from 'node:path';
10
- import { homedir } from 'node:os';
11
- import { findProjectRoot } from '@agent-relay/config';
12
-
13
- // ============================================================================
14
- // Types
15
- // ============================================================================
16
-
17
- export interface CloudWorkspace {
18
- workspaceId: string;
19
- cloudApiUrl: string;
20
- workspaceToken?: string;
21
- ownerUserId?: string;
22
- }
23
-
24
- export interface DiscoveryResult {
25
- socketPath: string;
26
- project: string;
27
- source: 'env' | 'cloud' | 'cwd' | 'scan';
28
- isCloud: boolean;
29
- workspace?: CloudWorkspace;
30
- }
31
-
32
- export interface CloudConnectionOptions {
33
- /** Override socket path (for testing) */
34
- socketPath?: string;
35
- /** Override workspace detection */
36
- workspace?: Partial<CloudWorkspace>;
37
- }
38
-
39
- // ============================================================================
40
- // Cloud Workspace Detection
41
- // ============================================================================
42
-
43
- /**
44
- * Detect if running in a cloud workspace environment.
4
+ * This module re-exports all cloud/discovery functionality from
5
+ * @agent-relay/utils, which is the single source of truth for socket
6
+ * discovery, cloud workspace detection, and agent identity discovery.
45
7
  *
46
- * Cloud workspaces set these environment variables:
47
- * - WORKSPACE_ID: The unique workspace identifier
48
- * - CLOUD_API_URL: The cloud API endpoint
49
- * - WORKSPACE_TOKEN: Bearer token for API auth (optional)
50
- * - WORKSPACE_OWNER_USER_ID: The workspace owner's user ID (optional)
8
+ * Previously this module contained its own implementation (~520 lines).
9
+ * It has been consolidated into @agent-relay/utils to eliminate code
10
+ * duplication between MCP and SDK packages.
51
11
  */
52
- export function detectCloudWorkspace(): CloudWorkspace | null {
53
- const workspaceId = process.env.WORKSPACE_ID;
54
- const cloudApiUrl = process.env.CLOUD_API_URL;
55
-
56
- if (!workspaceId || !cloudApiUrl) {
57
- return null;
58
- }
59
-
60
- return {
61
- workspaceId,
62
- cloudApiUrl,
63
- workspaceToken: process.env.WORKSPACE_TOKEN,
64
- ownerUserId: process.env.WORKSPACE_OWNER_USER_ID,
65
- };
66
- }
67
-
68
- /**
69
- * Check if we're running in a cloud workspace.
70
- */
71
- export function isCloudWorkspace(): boolean {
72
- return detectCloudWorkspace() !== null;
73
- }
74
-
75
- // ============================================================================
76
- // Workspace-Aware Socket Discovery
77
- // ============================================================================
78
-
79
- /**
80
- * Get the workspace-namespaced socket path.
81
- *
82
- * In cloud workspaces, sockets are stored at:
83
- * /tmp/relay/{WORKSPACE_ID}/sockets/daemon.sock
84
- *
85
- * This provides multi-tenant isolation on shared infrastructure.
86
- */
87
- export function getCloudSocketPath(workspaceId: string): string {
88
- return `/tmp/relay/${workspaceId}/sockets/daemon.sock`;
89
- }
90
-
91
- /**
92
- * Get the workspace-namespaced outbox path.
93
- *
94
- * In cloud workspaces, outbox directories are at:
95
- * /tmp/relay/{WORKSPACE_ID}/outbox/{agentName}/
96
- */
97
- export function getCloudOutboxPath(workspaceId: string, agentName: string): string {
98
- return `/tmp/relay/${workspaceId}/outbox/${agentName}`;
99
- }
100
-
101
- /**
102
- * Get platform-specific data directory.
103
- */
104
- function getDataDir(): string {
105
- const platform = process.platform;
106
-
107
- if (platform === 'darwin') {
108
- return join(homedir(), 'Library', 'Application Support', 'agent-relay');
109
- } else if (platform === 'win32') {
110
- return join(process.env.APPDATA || homedir(), 'agent-relay');
111
- } else {
112
- return join(
113
- process.env.XDG_DATA_HOME || join(homedir(), '.local', 'share'),
114
- 'agent-relay'
115
- );
116
- }
117
- }
118
-
119
- /**
120
- * Discover relay daemon socket with cloud-awareness.
121
- *
122
- * Priority order:
123
- * 1. RELAY_SOCKET environment variable (explicit path)
124
- * 2. Cloud workspace socket (if WORKSPACE_ID is set)
125
- * 3. RELAY_PROJECT environment variable (project name → data dir)
126
- * 4. Current working directory .relay/config.json
127
- * 5. Scan data directory for active sockets
128
- *
129
- * @param options - Optional configuration overrides
130
- * @returns Discovery result with socket path, project info, and cloud status
131
- */
132
- export function discoverSocket(options: CloudConnectionOptions = {}): DiscoveryResult | null {
133
- // 0. Use override if provided
134
- if (options.socketPath && existsSync(options.socketPath)) {
135
- const workspace = options.workspace
136
- ? ({
137
- workspaceId: options.workspace.workspaceId || 'override',
138
- cloudApiUrl: options.workspace.cloudApiUrl || '',
139
- } as CloudWorkspace)
140
- : undefined;
141
-
142
- return {
143
- socketPath: options.socketPath,
144
- project: workspace?.workspaceId || 'override',
145
- source: 'env',
146
- isCloud: !!workspace,
147
- workspace,
148
- };
149
- }
150
-
151
- // 1. Explicit socket path from environment
152
- const socketEnv = process.env.RELAY_SOCKET;
153
- if (socketEnv && existsSync(socketEnv)) {
154
- const workspace = detectCloudWorkspace();
155
- return {
156
- socketPath: socketEnv,
157
- project: process.env.RELAY_PROJECT || workspace?.workspaceId || 'unknown',
158
- source: 'env',
159
- isCloud: !!workspace,
160
- workspace: workspace || undefined,
161
- };
162
- }
163
-
164
- // 2. Cloud workspace socket (highest priority for cloud environments)
165
- const workspace = detectCloudWorkspace();
166
- if (workspace) {
167
- const cloudSocket = getCloudSocketPath(workspace.workspaceId);
168
- if (existsSync(cloudSocket)) {
169
- return {
170
- socketPath: cloudSocket,
171
- project: workspace.workspaceId,
172
- source: 'cloud',
173
- isCloud: true,
174
- workspace,
175
- };
176
- }
177
- }
178
-
179
- // 3. Project name → data dir lookup
180
- const projectEnv = process.env.RELAY_PROJECT;
181
- if (projectEnv) {
182
- const dataDir = getDataDir();
183
- const projectSocket = join(dataDir, 'projects', projectEnv, 'daemon.sock');
184
- if (existsSync(projectSocket)) {
185
- return {
186
- socketPath: projectSocket,
187
- project: projectEnv,
188
- source: 'env',
189
- isCloud: false,
190
- };
191
- }
192
- }
193
-
194
- // 4. Project-local socket (created by daemon in project's .agent-relay directory)
195
- // This is the primary path for local development
196
- // First try cwd, then scan up to find project root
197
- const projectRoot = findProjectRoot(process.cwd());
198
- const searchDirs = [process.cwd()];
199
- if (projectRoot && projectRoot !== process.cwd()) {
200
- searchDirs.push(projectRoot);
201
- }
202
-
203
- for (const dir of searchDirs) {
204
- const projectLocalSocket = join(dir, '.agent-relay', 'relay.sock');
205
- if (existsSync(projectLocalSocket)) {
206
- // Read project ID from marker file if available
207
- let projectId = 'local';
208
- const markerPath = join(dir, '.agent-relay', '.project');
209
- if (existsSync(markerPath)) {
210
- try {
211
- const marker = JSON.parse(readFileSync(markerPath, 'utf-8'));
212
- projectId = marker.projectId || 'local';
213
- } catch {
214
- // Ignore marker read errors
215
- }
216
- }
217
- return {
218
- socketPath: projectLocalSocket,
219
- project: projectId,
220
- source: 'cwd',
221
- isCloud: false,
222
- };
223
- }
224
- }
225
-
226
- // 4b. Legacy .relay/config.json support
227
- const cwdConfig = join(process.cwd(), '.relay', 'config.json');
228
- if (existsSync(cwdConfig)) {
229
- try {
230
- const config = JSON.parse(readFileSync(cwdConfig, 'utf-8'));
231
- if (config.socketPath && existsSync(config.socketPath)) {
232
- return {
233
- socketPath: config.socketPath,
234
- project: config.project || 'local',
235
- source: 'cwd',
236
- isCloud: false,
237
- };
238
- }
239
- } catch (err) {
240
- // Invalid config (malformed JSON, permission error, etc.), continue to next method
241
- if (process.env.DEBUG || process.env.RELAY_DEBUG) {
242
- console.debug('[cloud] Failed to read cwd config:', cwdConfig, err);
243
- }
244
- }
245
- }
246
-
247
- // 5. Scan data directory for active sockets
248
- const dataDir = getDataDir();
249
- const projectsDir = join(dataDir, 'projects');
250
-
251
- if (existsSync(projectsDir)) {
252
- try {
253
- const projects = readdirSync(projectsDir, { withFileTypes: true })
254
- .filter((d) => d.isDirectory())
255
- .map((d) => d.name);
256
-
257
- for (const project of projects) {
258
- const socketPath = join(projectsDir, project, 'daemon.sock');
259
- if (existsSync(socketPath)) {
260
- return {
261
- socketPath,
262
- project,
263
- source: 'scan',
264
- isCloud: false,
265
- };
266
- }
267
- }
268
- } catch (err) {
269
- // Directory read failed (permission error, etc.), return null
270
- if (process.env.DEBUG || process.env.RELAY_DEBUG) {
271
- console.debug('[cloud] Failed to scan projects directory:', projectsDir, err);
272
- }
273
- }
274
- }
275
-
276
- return null;
277
- }
278
-
279
- // ============================================================================
280
- // Cloud API Helpers
281
- // ============================================================================
282
-
283
- /**
284
- * Make an authenticated request to the cloud API.
285
- *
286
- * @param workspace - Cloud workspace configuration
287
- * @param path - API path (e.g., '/api/status')
288
- * @param options - Fetch options
289
- * @returns Response from the API
290
- */
291
- export async function cloudApiRequest(
292
- workspace: CloudWorkspace,
293
- path: string,
294
- options: RequestInit = {}
295
- ): Promise<Response> {
296
- const url = `${workspace.cloudApiUrl}${path}`;
297
-
298
- const headers: Record<string, string> = {
299
- 'Content-Type': 'application/json',
300
- ...(options.headers as Record<string, string>),
301
- };
302
-
303
- if (workspace.workspaceToken) {
304
- headers['Authorization'] = `Bearer ${workspace.workspaceToken}`;
305
- }
306
-
307
- return fetch(url, {
308
- ...options,
309
- headers,
310
- });
311
- }
312
-
313
- /**
314
- * Get the workspace status from the cloud API.
315
- */
316
- export async function getWorkspaceStatus(
317
- workspace: CloudWorkspace
318
- ): Promise<{ status: string; agents?: string[] } | null> {
319
- try {
320
- const response = await cloudApiRequest(
321
- workspace,
322
- `/api/workspaces/${workspace.workspaceId}/status`
323
- );
324
-
325
- if (!response.ok) {
326
- return null;
327
- }
328
-
329
- return (await response.json()) as { status: string; agents?: string[] };
330
- } catch {
331
- return null;
332
- }
333
- }
334
-
335
- // ============================================================================
336
- // Cloud Connection Factory
337
- // ============================================================================
338
-
339
- export interface CloudConnectionInfo {
340
- socketPath: string;
341
- project: string;
342
- isCloud: boolean;
343
- workspace?: CloudWorkspace;
344
- daemonUrl?: string;
345
- }
346
-
347
- /**
348
- * Get connection info for the relay daemon.
349
- *
350
- * This function determines the best way to connect to the daemon:
351
- * - In cloud environments: Uses workspace-namespaced socket
352
- * - In local environments: Uses standard socket discovery
353
- *
354
- * @param options - Optional configuration overrides
355
- * @returns Connection info or null if daemon not found
356
- */
357
- export function getConnectionInfo(
358
- options: CloudConnectionOptions = {}
359
- ): CloudConnectionInfo | null {
360
- const discovery = discoverSocket(options);
361
-
362
- if (!discovery) {
363
- return null;
364
- }
365
-
366
- const info: CloudConnectionInfo = {
367
- socketPath: discovery.socketPath,
368
- project: discovery.project,
369
- isCloud: discovery.isCloud,
370
- workspace: discovery.workspace,
371
- };
372
-
373
- // In cloud environments, we may also have a daemon URL for HTTP API access
374
- if (discovery.workspace?.cloudApiUrl) {
375
- info.daemonUrl = discovery.workspace.cloudApiUrl;
376
- }
377
-
378
- return info;
379
- }
380
-
381
- /**
382
- * Environment variable summary for debugging.
383
- */
384
- export function getCloudEnvironmentSummary(): Record<string, string | undefined> {
385
- return {
386
- WORKSPACE_ID: process.env.WORKSPACE_ID,
387
- CLOUD_API_URL: process.env.CLOUD_API_URL,
388
- WORKSPACE_TOKEN: process.env.WORKSPACE_TOKEN ? '[set]' : undefined,
389
- WORKSPACE_OWNER_USER_ID: process.env.WORKSPACE_OWNER_USER_ID,
390
- RELAY_SOCKET: process.env.RELAY_SOCKET,
391
- RELAY_PROJECT: process.env.RELAY_PROJECT,
392
- RELAY_AGENT_NAME: process.env.RELAY_AGENT_NAME,
393
- };
394
- }
395
-
396
- // ============================================================================
397
- // Agent Identity Discovery
398
- // ============================================================================
399
-
400
- /**
401
- * Discover the agent name for the MCP server.
402
- *
403
- * Priority order:
404
- * 1. RELAY_AGENT_NAME environment variable (explicit)
405
- * 2. Identity file in .agent-relay directory (written by wrapper)
406
- * 3. Scan outbox directories to find agent's outbox
407
- *
408
- * @param discovery - Optional discovery result with socket path info
409
- * @returns Agent name or null if not found
410
- */
411
- export function discoverAgentName(discovery?: DiscoveryResult | null): string | null {
412
- // 1. Explicit environment variable
413
- const envName = process.env.RELAY_AGENT_NAME;
414
- if (envName) {
415
- return envName;
416
- }
417
-
418
- // 2. Identity file in .agent-relay directory
419
- // The wrapper creates this file with the agent name
420
- const projectRoot = findProjectRoot(process.cwd());
421
- const searchDirs = [process.cwd()];
422
- if (projectRoot && projectRoot !== process.cwd()) {
423
- searchDirs.push(projectRoot);
424
- }
425
-
426
- for (const dir of searchDirs) {
427
- const relayDir = join(dir, '.agent-relay');
428
- if (!existsSync(relayDir)) continue;
429
-
430
- // First check for per-process identity files
431
- // The orchestrator writes mcp-identity-{orchestrator.pid}
432
- // Try to find one by checking process.ppid and its ancestors
433
- const pidIdentityPath = join(relayDir, `mcp-identity-${process.ppid}`);
434
- if (existsSync(pidIdentityPath)) {
435
- try {
436
- const content = readFileSync(pidIdentityPath, 'utf-8').trim();
437
- if (content) {
438
- return content;
439
- }
440
- } catch {
441
- // Ignore read errors
442
- }
443
- }
444
-
445
- // Scan all mcp-identity-* files and return the most recently modified one
446
- // This handles the case where MCP server's ppid doesn't match the orchestrator
447
- try {
448
- const files = readdirSync(relayDir, { withFileTypes: true })
449
- .filter((d) => d.isFile() && d.name.startsWith('mcp-identity-'))
450
- .map((d) => ({
451
- path: join(relayDir, d.name),
452
- name: d.name,
453
- }));
454
12
 
455
- if (files.length > 0) {
456
- // Sort by mtime (most recent first) to get the latest identity
457
- const sorted = files
458
- .map((f) => {
459
- try {
460
- const stat = statSync(f.path);
461
- return { ...f, mtime: stat.mtimeMs };
462
- } catch {
463
- return { ...f, mtime: 0 };
464
- }
465
- })
466
- .sort((a, b) => b.mtime - a.mtime);
13
+ export {
14
+ // Types
15
+ type CloudWorkspace,
16
+ type DiscoveryResult,
17
+ type CloudConnectionOptions,
18
+ type CloudConnectionInfo,
467
19
 
468
- // Return the most recently modified identity file
469
- const latest = sorted[0];
470
- if (latest) {
471
- try {
472
- const content = readFileSync(latest.path, 'utf-8').trim();
473
- if (content) {
474
- return content;
475
- }
476
- } catch {
477
- // Ignore
478
- }
479
- }
480
- }
481
- } catch {
482
- // Ignore scan errors
483
- }
20
+ // Cloud workspace detection
21
+ detectCloudWorkspace,
22
+ isCloudWorkspace,
484
23
 
485
- // Fallback to simple identity file (for single-agent scenarios)
486
- const identityPath = join(relayDir, 'mcp-identity');
487
- if (existsSync(identityPath)) {
488
- try {
489
- const content = readFileSync(identityPath, 'utf-8').trim();
490
- if (content) {
491
- return content;
492
- }
493
- } catch {
494
- // Ignore read errors
495
- }
496
- }
497
- }
24
+ // Socket discovery
25
+ getCloudSocketPath,
26
+ getCloudOutboxPath,
27
+ discoverSocket,
498
28
 
499
- // 3. Check outbox directories for a match
500
- // If only one agent's outbox exists, assume we're that agent
501
- for (const dir of searchDirs) {
502
- const outboxDir = join(dir, '.agent-relay', 'outbox');
503
- if (existsSync(outboxDir)) {
504
- try {
505
- const agents = readdirSync(outboxDir, { withFileTypes: true })
506
- .filter((d) => d.isDirectory())
507
- .map((d) => d.name);
29
+ // Cloud API helpers
30
+ cloudApiRequest,
31
+ getWorkspaceStatus,
508
32
 
509
- // If there's exactly one outbox, use that agent name
510
- if (agents.length === 1) {
511
- return agents[0];
512
- }
33
+ // Connection factory
34
+ getConnectionInfo,
513
35
 
514
- // If there are multiple, we can't determine which one we are
515
- // The wrapper should have created an identity file
516
- } catch {
517
- // Ignore read errors
518
- }
519
- }
520
- }
36
+ // Debug helpers
37
+ getCloudEnvironmentSummary,
521
38
 
522
- return null;
523
- }
39
+ // Agent identity
40
+ discoverAgentName,
41
+ } from '@agent-relay/utils/discovery';
@@ -1,54 +1,17 @@
1
1
  /**
2
2
  * Error Types for Agent Relay MCP Server
3
3
  *
4
- * Provides typed error classes for better error handling and messaging.
4
+ * Re-exports error classes from @agent-relay/utils, which is the single
5
+ * source of truth for error types. Previously this module contained
6
+ * its own implementation.
5
7
  */
6
8
 
7
- export class RelayError extends Error {
8
- constructor(message: string) {
9
- super(message);
10
- this.name = 'RelayError';
11
- }
12
- }
13
-
14
- export class DaemonNotRunningError extends RelayError {
15
- constructor(message?: string) {
16
- super(message || 'Relay daemon is not running. Start with: agent-relay up');
17
- this.name = 'DaemonNotRunningError';
18
- }
19
- }
20
-
21
- export class AgentNotFoundError extends RelayError {
22
- constructor(agentName: string) {
23
- super(`Agent not found: ${agentName}`);
24
- this.name = 'AgentNotFoundError';
25
- }
26
- }
27
-
28
- export class TimeoutError extends RelayError {
29
- constructor(operation: string, timeoutMs: number) {
30
- super(`Timeout after ${timeoutMs}ms: ${operation}`);
31
- this.name = 'TimeoutError';
32
- }
33
- }
34
-
35
- export class ConnectionError extends RelayError {
36
- constructor(message: string) {
37
- super(`Connection error: ${message}`);
38
- this.name = 'ConnectionError';
39
- }
40
- }
41
-
42
- export class ChannelNotFoundError extends RelayError {
43
- constructor(channel: string) {
44
- super(`Channel not found: ${channel}`);
45
- this.name = 'ChannelNotFoundError';
46
- }
47
- }
48
-
49
- export class SpawnError extends RelayError {
50
- constructor(workerName: string, reason: string) {
51
- super(`Failed to spawn worker "${workerName}": ${reason}`);
52
- this.name = 'SpawnError';
53
- }
54
- }
9
+ export {
10
+ RelayError,
11
+ DaemonNotRunningError,
12
+ AgentNotFoundError,
13
+ TimeoutError,
14
+ ConnectionError,
15
+ ChannelNotFoundError,
16
+ SpawnError,
17
+ } from '@agent-relay/utils/errors';
@@ -46,7 +46,14 @@ export function createHybridClient(options: HybridClientOptions): RelayClient {
46
46
  }
47
47
 
48
48
  // Get socket path for queries
49
- const socketPath = options.socketPath || discoverSocket()?.socketPath || join(relayDir, 'relay.sock');
49
+ // Use discoverSocket() which respects cloud workspace config and env overrides.
50
+ // Only fall back to relayDir/relay.sock for non-cloud local development.
51
+ const discovery = discoverSocket();
52
+ const socketPath = options.socketPath || discovery?.socketPath || join(relayDir, 'relay.sock');
53
+
54
+ if (process.env.DEBUG || process.env.RELAY_DEBUG) {
55
+ console.debug('[hybrid-client] Socket path:', socketPath, 'source:', discovery?.source ?? 'fallback', 'isCloud:', discovery?.isCloud ?? false);
56
+ }
50
57
 
51
58
  // Create socket client for queries only
52
59
  let socketClient: RelayClient | null = null;