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.
- package/README.md +85 -236
- package/dist/index.cjs +75 -12
- package/package.json +18 -18
- package/packages/api-types/package.json +1 -1
- package/packages/benchmark/package.json +4 -4
- package/packages/bridge/package.json +8 -8
- package/packages/cli-tester/package.json +1 -1
- package/packages/config/package.json +2 -2
- package/packages/continuity/package.json +2 -2
- package/packages/daemon/dist/server.d.ts +5 -0
- package/packages/daemon/dist/server.d.ts.map +1 -1
- package/packages/daemon/dist/server.js +31 -0
- package/packages/daemon/dist/server.js.map +1 -1
- package/packages/daemon/package.json +12 -12
- package/packages/daemon/src/server.ts +37 -0
- package/packages/hooks/package.json +4 -4
- package/packages/mcp/dist/cloud.d.ts +7 -114
- package/packages/mcp/dist/cloud.d.ts.map +1 -1
- package/packages/mcp/dist/cloud.js +21 -431
- package/packages/mcp/dist/cloud.js.map +1 -1
- package/packages/mcp/dist/errors.d.ts +4 -22
- package/packages/mcp/dist/errors.d.ts.map +1 -1
- package/packages/mcp/dist/errors.js +4 -43
- package/packages/mcp/dist/errors.js.map +1 -1
- package/packages/mcp/dist/hybrid-client.d.ts.map +1 -1
- package/packages/mcp/dist/hybrid-client.js +7 -1
- package/packages/mcp/dist/hybrid-client.js.map +1 -1
- package/packages/mcp/package.json +4 -3
- package/packages/mcp/src/cloud.ts +29 -511
- package/packages/mcp/src/errors.ts +12 -49
- package/packages/mcp/src/hybrid-client.ts +8 -1
- package/packages/mcp/tests/discover.test.ts +72 -11
- package/packages/memory/package.json +2 -2
- package/packages/policy/package.json +2 -2
- package/packages/protocol/dist/types.d.ts +17 -1
- package/packages/protocol/dist/types.d.ts.map +1 -1
- package/packages/protocol/package.json +1 -1
- package/packages/protocol/src/types.ts +23 -0
- package/packages/resiliency/package.json +1 -1
- package/packages/sdk/dist/browser-client.d.ts +212 -0
- package/packages/sdk/dist/browser-client.d.ts.map +1 -0
- package/packages/sdk/dist/browser-client.js +750 -0
- package/packages/sdk/dist/browser-client.js.map +1 -0
- package/packages/sdk/dist/browser-framing.d.ts +46 -0
- package/packages/sdk/dist/browser-framing.d.ts.map +1 -0
- package/packages/sdk/dist/browser-framing.js +122 -0
- package/packages/sdk/dist/browser-framing.js.map +1 -0
- package/packages/sdk/dist/client.d.ts +129 -2
- package/packages/sdk/dist/client.d.ts.map +1 -1
- package/packages/sdk/dist/client.js +312 -2
- package/packages/sdk/dist/client.js.map +1 -1
- package/packages/sdk/dist/discovery.d.ts +10 -0
- package/packages/sdk/dist/discovery.d.ts.map +1 -0
- package/packages/sdk/dist/discovery.js +22 -0
- package/packages/sdk/dist/discovery.js.map +1 -0
- package/packages/sdk/dist/errors.d.ts +9 -0
- package/packages/sdk/dist/errors.d.ts.map +1 -0
- package/packages/sdk/dist/errors.js +9 -0
- package/packages/sdk/dist/errors.js.map +1 -0
- package/packages/sdk/dist/index.d.ts +18 -2
- package/packages/sdk/dist/index.d.ts.map +1 -1
- package/packages/sdk/dist/index.js +27 -1
- package/packages/sdk/dist/index.js.map +1 -1
- package/packages/sdk/dist/transports/index.d.ts +92 -0
- package/packages/sdk/dist/transports/index.d.ts.map +1 -0
- package/packages/sdk/dist/transports/index.js +129 -0
- package/packages/sdk/dist/transports/index.js.map +1 -0
- package/packages/sdk/dist/transports/socket-transport.d.ts +30 -0
- package/packages/sdk/dist/transports/socket-transport.d.ts.map +1 -0
- package/packages/sdk/dist/transports/socket-transport.js +94 -0
- package/packages/sdk/dist/transports/socket-transport.js.map +1 -0
- package/packages/sdk/dist/transports/types.d.ts +69 -0
- package/packages/sdk/dist/transports/types.d.ts.map +1 -0
- package/packages/sdk/dist/transports/types.js +10 -0
- package/packages/sdk/dist/transports/types.js.map +1 -0
- package/packages/sdk/dist/transports/websocket-transport.d.ts +55 -0
- package/packages/sdk/dist/transports/websocket-transport.d.ts.map +1 -0
- package/packages/sdk/dist/transports/websocket-transport.js +180 -0
- package/packages/sdk/dist/transports/websocket-transport.js.map +1 -0
- package/packages/sdk/package.json +28 -4
- package/packages/sdk/src/browser-client.ts +985 -0
- package/packages/sdk/src/browser-framing.test.ts +115 -0
- package/packages/sdk/src/browser-framing.ts +150 -0
- package/packages/sdk/src/client.test.ts +425 -0
- package/packages/sdk/src/client.ts +397 -3
- package/packages/sdk/src/discovery.ts +38 -0
- package/packages/sdk/src/errors.ts +17 -0
- package/packages/sdk/src/index.ts +82 -1
- package/packages/sdk/src/transports/index.ts +197 -0
- package/packages/sdk/src/transports/socket-transport.ts +115 -0
- package/packages/sdk/src/transports/types.ts +77 -0
- package/packages/sdk/src/transports/websocket-transport.ts +245 -0
- package/packages/sdk/tsconfig.json +1 -1
- package/packages/spawner/package.json +1 -1
- package/packages/state/package.json +1 -1
- package/packages/storage/package.json +2 -2
- package/packages/storage/src/jsonl-adapter.test.ts +8 -3
- 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/dist/cjs/discovery.js +328 -0
- package/packages/utils/dist/cjs/errors.js +81 -0
- package/packages/utils/dist/discovery.d.ts +123 -0
- package/packages/utils/dist/discovery.d.ts.map +1 -0
- package/packages/utils/dist/discovery.js +439 -0
- package/packages/utils/dist/discovery.js.map +1 -0
- package/packages/utils/dist/errors.d.ts +29 -0
- package/packages/utils/dist/errors.d.ts.map +1 -0
- package/packages/utils/dist/errors.js +50 -0
- package/packages/utils/dist/errors.js.map +1 -0
- package/packages/utils/package.json +15 -2
- package/packages/utils/src/consolidation.test.ts +125 -0
- package/packages/utils/src/discovery.test.ts +196 -0
- package/packages/utils/src/discovery.ts +524 -0
- package/packages/utils/src/errors.test.ts +83 -0
- package/packages/utils/src/errors.ts +56 -0
- 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
|
-
*
|
|
5
|
-
*
|
|
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
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
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
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|
-
//
|
|
500
|
-
|
|
501
|
-
|
|
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
|
-
|
|
510
|
-
|
|
511
|
-
return agents[0];
|
|
512
|
-
}
|
|
33
|
+
// Connection factory
|
|
34
|
+
getConnectionInfo,
|
|
513
35
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
} catch {
|
|
517
|
-
// Ignore read errors
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
}
|
|
36
|
+
// Debug helpers
|
|
37
|
+
getCloudEnvironmentSummary,
|
|
521
38
|
|
|
522
|
-
|
|
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
|
-
*
|
|
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
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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;
|