agent-relay 2.0.4 → 2.0.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/dist/dashboard/out/404.html +1 -1
- package/dist/dashboard/out/app/onboarding.html +1 -1
- package/dist/dashboard/out/app/onboarding.txt +1 -1
- package/dist/dashboard/out/app.html +1 -1
- package/dist/dashboard/out/app.txt +1 -1
- package/dist/dashboard/out/cloud/link.html +1 -1
- package/dist/dashboard/out/cloud/link.txt +1 -1
- package/dist/dashboard/out/connect-repos.html +1 -1
- package/dist/dashboard/out/connect-repos.txt +1 -1
- package/dist/dashboard/out/history.html +1 -1
- package/dist/dashboard/out/history.txt +1 -1
- package/dist/dashboard/out/index.html +1 -1
- package/dist/dashboard/out/index.txt +1 -1
- package/dist/dashboard/out/login.html +1 -1
- package/dist/dashboard/out/login.txt +1 -1
- package/dist/dashboard/out/metrics.html +1 -1
- package/dist/dashboard/out/metrics.txt +1 -1
- package/dist/dashboard/out/pricing.html +1 -1
- package/dist/dashboard/out/pricing.txt +1 -1
- package/dist/dashboard/out/providers/setup/claude.html +1 -1
- package/dist/dashboard/out/providers/setup/claude.txt +1 -1
- package/dist/dashboard/out/providers/setup/codex.html +1 -1
- package/dist/dashboard/out/providers/setup/codex.txt +1 -1
- package/dist/dashboard/out/providers/setup/cursor.html +1 -1
- package/dist/dashboard/out/providers/setup/cursor.txt +1 -1
- package/dist/dashboard/out/providers.html +1 -1
- package/dist/dashboard/out/providers.txt +1 -1
- package/dist/dashboard/out/signup.html +1 -1
- package/dist/dashboard/out/signup.txt +1 -1
- package/dist/src/cli/index.js +61 -3
- package/package.json +14 -12
- package/packages/api-types/package.json +1 -1
- package/packages/bridge/dist/spawner.d.ts +2 -0
- package/packages/bridge/dist/spawner.js +24 -6
- package/packages/bridge/dist/types.d.ts +2 -0
- package/packages/bridge/package.json +7 -7
- package/packages/cloud/package.json +6 -6
- package/packages/config/package.json +2 -2
- package/packages/continuity/package.json +1 -1
- package/packages/daemon/dist/router.d.ts +2 -1
- package/packages/daemon/dist/router.js +142 -52
- package/packages/daemon/dist/server.d.ts +3 -0
- package/packages/daemon/dist/server.js +31 -1
- package/packages/daemon/dist/spawn-manager.d.ts +5 -0
- package/packages/daemon/dist/spawn-manager.js +44 -1
- package/packages/daemon/package.json +12 -11
- package/packages/dashboard/dist/server.js +2 -0
- package/packages/dashboard/package.json +12 -12
- package/packages/dashboard/ui-dist/404.html +1 -1
- package/packages/dashboard/ui-dist/app/onboarding.html +1 -1
- package/packages/dashboard/ui-dist/app/onboarding.txt +1 -1
- package/packages/dashboard/ui-dist/app.html +1 -1
- package/packages/dashboard/ui-dist/app.txt +1 -1
- package/packages/dashboard/ui-dist/cloud/link.html +1 -1
- package/packages/dashboard/ui-dist/cloud/link.txt +1 -1
- package/packages/dashboard/ui-dist/connect-repos.html +1 -1
- package/packages/dashboard/ui-dist/connect-repos.txt +1 -1
- package/packages/dashboard/ui-dist/history.html +1 -1
- package/packages/dashboard/ui-dist/history.txt +1 -1
- package/packages/dashboard/ui-dist/index.html +1 -1
- package/packages/dashboard/ui-dist/index.txt +1 -1
- package/packages/dashboard/ui-dist/login.html +1 -1
- package/packages/dashboard/ui-dist/login.txt +1 -1
- package/packages/dashboard/ui-dist/metrics.html +1 -1
- package/packages/dashboard/ui-dist/metrics.txt +1 -1
- package/packages/dashboard/ui-dist/pricing.html +1 -1
- package/packages/dashboard/ui-dist/pricing.txt +1 -1
- package/packages/dashboard/ui-dist/providers/setup/claude.html +1 -1
- package/packages/dashboard/ui-dist/providers/setup/claude.txt +1 -1
- package/packages/dashboard/ui-dist/providers/setup/codex.html +1 -1
- package/packages/dashboard/ui-dist/providers/setup/codex.txt +1 -1
- package/packages/dashboard/ui-dist/providers/setup/cursor.html +1 -1
- package/packages/dashboard/ui-dist/providers/setup/cursor.txt +1 -1
- package/packages/dashboard/ui-dist/providers.html +1 -1
- package/packages/dashboard/ui-dist/providers.txt +1 -1
- package/packages/dashboard/ui-dist/signup.html +1 -1
- package/packages/dashboard/ui-dist/signup.txt +1 -1
- package/packages/dashboard-server/dist/server.js +2 -0
- package/packages/dashboard-server/dist/user-bridge.d.ts +7 -3
- package/packages/dashboard-server/dist/user-bridge.js +48 -30
- package/packages/dashboard-server/package.json +12 -12
- package/packages/hooks/package.json +4 -4
- package/packages/mcp/README.md +19 -135
- package/packages/mcp/dist/client.js +67 -27
- package/packages/mcp/dist/cloud.js +1 -18
- package/packages/mcp/dist/prompts/protocol.d.ts +1 -1
- package/packages/mcp/dist/prompts/protocol.js +6 -14
- package/packages/mcp/package.json +2 -1
- package/packages/memory/package.json +2 -2
- package/packages/policy/package.json +2 -2
- package/packages/protocol/dist/types.d.ts +2 -0
- package/packages/protocol/package.json +1 -1
- package/packages/resiliency/package.json +1 -1
- package/packages/sdk/README.md +43 -160
- package/packages/sdk/dist/client.d.ts +3 -99
- package/packages/sdk/dist/client.js +6 -113
- package/packages/sdk/dist/index.d.ts +0 -1
- package/packages/sdk/dist/index.js +0 -2
- package/packages/sdk/dist/standalone.js +0 -1
- package/packages/sdk/package.json +2 -2
- package/packages/spawner/package.json +1 -1
- package/packages/state/package.json +1 -1
- package/packages/storage/package.json +2 -2
- package/packages/telemetry/dist/client.d.ts +19 -0
- package/packages/telemetry/dist/client.js +126 -0
- package/packages/telemetry/dist/config.d.ts +29 -0
- package/packages/telemetry/dist/config.js +88 -0
- package/packages/telemetry/dist/events.d.ts +106 -0
- package/packages/telemetry/dist/events.js +10 -0
- package/packages/telemetry/dist/index.d.ts +8 -0
- package/packages/telemetry/dist/index.js +7 -0
- package/packages/telemetry/dist/machine-id.d.ts +12 -0
- package/packages/telemetry/dist/machine-id.js +58 -0
- package/packages/telemetry/dist/posthog-config.d.ts +16 -0
- package/packages/telemetry/dist/posthog-config.js +33 -0
- package/packages/telemetry/package.json +41 -0
- package/packages/trajectory/package.json +2 -2
- package/packages/user-directory/package.json +2 -2
- package/packages/utils/package.json +1 -1
- package/packages/wrapper/dist/relay-pty-orchestrator.js +38 -29
- package/packages/wrapper/package.json +6 -6
- package/packages/sdk/dist/discovery.d.ts +0 -29
- package/packages/sdk/dist/discovery.js +0 -126
- /package/dist/dashboard/out/_next/static/{72btMIJ64BCAB4UgVkpaq → lIJs7zSKBaI58kpqegulQ}/_buildManifest.js +0 -0
- /package/dist/dashboard/out/_next/static/{72btMIJ64BCAB4UgVkpaq → lIJs7zSKBaI58kpqegulQ}/_ssgManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{0AsOfRemPXJmtynCKT-rx → KIxE0Ds_zdGuDJDQu7_sb}/_buildManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{0AsOfRemPXJmtynCKT-rx → KIxE0Ds_zdGuDJDQu7_sb}/_ssgManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{72btMIJ64BCAB4UgVkpaq → SoK46dEi3IsNBVWXD9x0L}/_buildManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{72btMIJ64BCAB4UgVkpaq → SoK46dEi3IsNBVWXD9x0L}/_ssgManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{clUN2n0bz9HCjKI0qlOxU → lIJs7zSKBaI58kpqegulQ}/_buildManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{clUN2n0bz9HCjKI0qlOxU → lIJs7zSKBaI58kpqegulQ}/_ssgManifest.js +0 -0
|
@@ -4051,6 +4051,7 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
4051
4051
|
shadowTriggers,
|
|
4052
4052
|
shadowSpeakOn,
|
|
4053
4053
|
userId: typeof userId === 'string' ? userId : undefined,
|
|
4054
|
+
includeWorkflowConventions: true, // Cloud opts into ACK/DONE workflow conventions
|
|
4054
4055
|
};
|
|
4055
4056
|
const result = await spawner.spawn(request);
|
|
4056
4057
|
if (result.success) {
|
|
@@ -4172,6 +4173,7 @@ Start by greeting the project leads and asking for status updates.`;
|
|
|
4172
4173
|
name: 'Architect',
|
|
4173
4174
|
cli,
|
|
4174
4175
|
task: architectPrompt,
|
|
4176
|
+
includeWorkflowConventions: true, // Cloud opts into ACK/DONE workflow conventions
|
|
4175
4177
|
});
|
|
4176
4178
|
if (result.success) {
|
|
4177
4179
|
broadcastData().catch(() => { });
|
|
@@ -88,6 +88,11 @@ export declare class UserBridge {
|
|
|
88
88
|
avatarUrl?: string;
|
|
89
89
|
displayName?: string;
|
|
90
90
|
}): Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* Remove a specific WebSocket connection from a user's session.
|
|
93
|
+
* If this was the last connection, unregister the user entirely.
|
|
94
|
+
*/
|
|
95
|
+
private removeWebSocket;
|
|
91
96
|
/**
|
|
92
97
|
* Unregister a user and disconnect their relay client.
|
|
93
98
|
*/
|
|
@@ -97,9 +102,8 @@ export declare class UserBridge {
|
|
|
97
102
|
*/
|
|
98
103
|
isUserRegistered(username: string): boolean;
|
|
99
104
|
/**
|
|
100
|
-
*
|
|
101
|
-
* This is needed when a user
|
|
102
|
-
* so messages are forwarded to the active WebSocket.
|
|
105
|
+
* Add a new WebSocket connection for an existing user session.
|
|
106
|
+
* This is needed when a user opens a new tab.
|
|
103
107
|
*/
|
|
104
108
|
updateWebSocket(username: string, newWebSocket: WebSocket): boolean;
|
|
105
109
|
/**
|
|
@@ -36,9 +36,11 @@ export class UserBridge {
|
|
|
36
36
|
* Creates a relay client connection for the user.
|
|
37
37
|
*/
|
|
38
38
|
async registerUser(username, webSocket, options) {
|
|
39
|
-
// If user already registered,
|
|
39
|
+
// If user already registered, just update the WebSocket (multi-tab support)
|
|
40
40
|
if (this.users.has(username)) {
|
|
41
|
-
|
|
41
|
+
console.log(`[user-bridge] User ${username} already registered, updating WebSocket`);
|
|
42
|
+
this.updateWebSocket(username, webSocket);
|
|
43
|
+
return;
|
|
42
44
|
}
|
|
43
45
|
// Create relay client for this user
|
|
44
46
|
const relayClient = await this.createRelayClient({
|
|
@@ -61,16 +63,16 @@ export class UserBridge {
|
|
|
61
63
|
relayClient.onChannelMessage = (from, channel, body, envelope) => {
|
|
62
64
|
this.handleIncomingChannelMessage(username, from, channel, body, envelope);
|
|
63
65
|
};
|
|
64
|
-
// Create session
|
|
66
|
+
// Create session with WebSocket set for multi-tab support
|
|
65
67
|
const session = {
|
|
66
68
|
username,
|
|
67
69
|
relayClient,
|
|
68
|
-
webSocket,
|
|
70
|
+
webSockets: new Set([webSocket]),
|
|
69
71
|
channels: new Set(),
|
|
70
72
|
avatarUrl: options?.avatarUrl,
|
|
71
73
|
};
|
|
72
74
|
this.users.set(username, session);
|
|
73
|
-
console.log(`[user-bridge] User registered: ${username} (total: ${this.users.size})`);
|
|
75
|
+
console.log(`[user-bridge] User registered: ${username} (total users: ${this.users.size}, connections: 1)`);
|
|
74
76
|
// Auto-join user to #general channel
|
|
75
77
|
// Note: The daemon auto-joins on connect, but we need to track locally too
|
|
76
78
|
session.channels.add('#general');
|
|
@@ -90,12 +92,27 @@ export class UserBridge {
|
|
|
90
92
|
console.error(`[user-bridge] Failed to restore persisted channels for ${username}:`, err);
|
|
91
93
|
}
|
|
92
94
|
}
|
|
93
|
-
// Set up WebSocket close handler
|
|
95
|
+
// Set up WebSocket close handler to remove this specific connection
|
|
94
96
|
webSocket.on('close', () => {
|
|
95
|
-
this.
|
|
97
|
+
this.removeWebSocket(username, webSocket);
|
|
96
98
|
});
|
|
97
99
|
console.log(`[user-bridge] User ${username} registered with relay daemon`);
|
|
98
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Remove a specific WebSocket connection from a user's session.
|
|
103
|
+
* If this was the last connection, unregister the user entirely.
|
|
104
|
+
*/
|
|
105
|
+
removeWebSocket(username, webSocket) {
|
|
106
|
+
const session = this.users.get(username);
|
|
107
|
+
if (!session)
|
|
108
|
+
return;
|
|
109
|
+
session.webSockets.delete(webSocket);
|
|
110
|
+
console.log(`[user-bridge] WebSocket closed for ${username} (${session.webSockets.size} connections remaining)`);
|
|
111
|
+
// Only unregister if ALL connections are closed
|
|
112
|
+
if (session.webSockets.size === 0) {
|
|
113
|
+
this.unregisterUser(username);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
99
116
|
/**
|
|
100
117
|
* Unregister a user and disconnect their relay client.
|
|
101
118
|
*/
|
|
@@ -114,26 +131,22 @@ export class UserBridge {
|
|
|
114
131
|
return this.users.has(username);
|
|
115
132
|
}
|
|
116
133
|
/**
|
|
117
|
-
*
|
|
118
|
-
* This is needed when a user
|
|
119
|
-
* so messages are forwarded to the active WebSocket.
|
|
134
|
+
* Add a new WebSocket connection for an existing user session.
|
|
135
|
+
* This is needed when a user opens a new tab.
|
|
120
136
|
*/
|
|
121
137
|
updateWebSocket(username, newWebSocket) {
|
|
122
138
|
const session = this.users.get(username);
|
|
123
139
|
if (!session) {
|
|
124
|
-
console.log(`[user-bridge] Cannot
|
|
140
|
+
console.log(`[user-bridge] Cannot add WebSocket - user ${username} not registered`);
|
|
125
141
|
return false;
|
|
126
142
|
}
|
|
127
|
-
//
|
|
128
|
-
session.
|
|
129
|
-
//
|
|
130
|
-
session.webSocket = newWebSocket;
|
|
131
|
-
// Set up close handler on new WebSocket
|
|
143
|
+
// Add the new WebSocket to the set
|
|
144
|
+
session.webSockets.add(newWebSocket);
|
|
145
|
+
// Set up close handler to remove this specific connection
|
|
132
146
|
newWebSocket.on('close', () => {
|
|
133
|
-
|
|
134
|
-
// unregisterUser when all connections are closed
|
|
147
|
+
this.removeWebSocket(username, newWebSocket);
|
|
135
148
|
});
|
|
136
|
-
console.log(`[user-bridge]
|
|
149
|
+
console.log(`[user-bridge] Added WebSocket for user ${username} (${session.webSockets.size} connections)`);
|
|
137
150
|
return true;
|
|
138
151
|
}
|
|
139
152
|
/**
|
|
@@ -224,23 +237,25 @@ export class UserBridge {
|
|
|
224
237
|
const session = this.users.get(username);
|
|
225
238
|
if (!session)
|
|
226
239
|
return;
|
|
227
|
-
const ws = session.webSocket;
|
|
228
|
-
if (ws.readyState !== 1)
|
|
229
|
-
return; // Not OPEN
|
|
230
240
|
// Look up sender's avatar if lookup function is available
|
|
231
241
|
const senderInfo = this.lookupUserInfo?.(from);
|
|
232
242
|
const fromAvatarUrl = senderInfo?.avatarUrl;
|
|
233
243
|
// Determine entity type: user if they have info, agent otherwise
|
|
234
244
|
const fromEntityType = senderInfo ? 'user' : 'agent';
|
|
235
|
-
|
|
236
|
-
ws.send(JSON.stringify({
|
|
245
|
+
const message = JSON.stringify({
|
|
237
246
|
type: 'direct_message',
|
|
238
247
|
from,
|
|
239
248
|
fromAvatarUrl,
|
|
240
249
|
fromEntityType,
|
|
241
250
|
body: payloadObj?.body || body,
|
|
242
251
|
timestamp: new Date().toISOString(),
|
|
243
|
-
})
|
|
252
|
+
});
|
|
253
|
+
// Send to ALL open WebSocket connections (multi-tab support)
|
|
254
|
+
for (const ws of session.webSockets) {
|
|
255
|
+
if (ws.readyState === 1) { // OPEN
|
|
256
|
+
ws.send(message);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
244
259
|
}
|
|
245
260
|
/**
|
|
246
261
|
* Handle incoming channel message from relay daemon.
|
|
@@ -249,9 +264,6 @@ export class UserBridge {
|
|
|
249
264
|
const session = this.users.get(username);
|
|
250
265
|
if (!session)
|
|
251
266
|
return;
|
|
252
|
-
const ws = session.webSocket;
|
|
253
|
-
if (ws.readyState !== 1)
|
|
254
|
-
return; // Not OPEN
|
|
255
267
|
// Look up sender's avatar if lookup function is available
|
|
256
268
|
const senderInfo = this.lookupUserInfo?.(from);
|
|
257
269
|
const fromAvatarUrl = senderInfo?.avatarUrl;
|
|
@@ -259,7 +271,7 @@ export class UserBridge {
|
|
|
259
271
|
const fromEntityType = senderInfo ? 'user' : 'agent';
|
|
260
272
|
// Channel message
|
|
261
273
|
const env = envelope;
|
|
262
|
-
|
|
274
|
+
const message = JSON.stringify({
|
|
263
275
|
type: 'channel_message',
|
|
264
276
|
channel,
|
|
265
277
|
from,
|
|
@@ -269,7 +281,13 @@ export class UserBridge {
|
|
|
269
281
|
thread: env?.payload?.thread,
|
|
270
282
|
mentions: env?.payload?.mentions,
|
|
271
283
|
timestamp: new Date().toISOString(),
|
|
272
|
-
})
|
|
284
|
+
});
|
|
285
|
+
// Send to ALL open WebSocket connections (multi-tab support)
|
|
286
|
+
for (const ws of session.webSockets) {
|
|
287
|
+
if (ws.readyState === 1) { // OPEN
|
|
288
|
+
ws.send(message);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
273
291
|
}
|
|
274
292
|
/**
|
|
275
293
|
* Admin: Add a member to a channel (does not require member to be connected).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-relay/dashboard-server",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.6",
|
|
4
4
|
"description": "Relay dashboard server - HTTP/WebSocket server for agent coordination",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -22,17 +22,17 @@
|
|
|
22
22
|
"test:watch": "vitest"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@agent-relay/protocol": "2.0.
|
|
26
|
-
"@agent-relay/config": "2.0.
|
|
27
|
-
"@agent-relay/storage": "2.0.
|
|
28
|
-
"@agent-relay/bridge": "2.0.
|
|
29
|
-
"@agent-relay/utils": "2.0.
|
|
30
|
-
"@agent-relay/resiliency": "2.0.
|
|
31
|
-
"@agent-relay/trajectory": "2.0.
|
|
32
|
-
"@agent-relay/cloud": "2.0.
|
|
33
|
-
"@agent-relay/daemon": "2.0.
|
|
34
|
-
"@agent-relay/user-directory": "2.0.
|
|
35
|
-
"@agent-relay/sdk": "2.0.
|
|
25
|
+
"@agent-relay/protocol": "2.0.6",
|
|
26
|
+
"@agent-relay/config": "2.0.6",
|
|
27
|
+
"@agent-relay/storage": "2.0.6",
|
|
28
|
+
"@agent-relay/bridge": "2.0.6",
|
|
29
|
+
"@agent-relay/utils": "2.0.6",
|
|
30
|
+
"@agent-relay/resiliency": "2.0.6",
|
|
31
|
+
"@agent-relay/trajectory": "2.0.6",
|
|
32
|
+
"@agent-relay/cloud": "2.0.6",
|
|
33
|
+
"@agent-relay/daemon": "2.0.6",
|
|
34
|
+
"@agent-relay/user-directory": "2.0.6",
|
|
35
|
+
"@agent-relay/sdk": "2.0.6",
|
|
36
36
|
"array-flatten": "^1.1.1",
|
|
37
37
|
"express": "^4.21.2",
|
|
38
38
|
"ws": "^8.18.3"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-relay/hooks",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.6",
|
|
4
4
|
"description": "Hook emitter, registry, and trajectory hooks for Agent Relay",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"test:watch": "vitest"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@agent-relay/protocol": "2.0.
|
|
41
|
-
"@agent-relay/config": "2.0.
|
|
42
|
-
"@agent-relay/trajectory": "2.0.
|
|
40
|
+
"@agent-relay/protocol": "2.0.6",
|
|
41
|
+
"@agent-relay/config": "2.0.6",
|
|
42
|
+
"@agent-relay/trajectory": "2.0.6"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/node": "^22.19.3",
|
package/packages/mcp/README.md
CHANGED
|
@@ -160,166 +160,50 @@ The MCP server provides:
|
|
|
160
160
|
|
|
161
161
|
In cloud environments (with `WORKSPACE_ID` set), MCP is pre-configured and uses workspace-specific sockets automatically.
|
|
162
162
|
|
|
163
|
-
## Programmatic Usage
|
|
163
|
+
## Programmatic Usage
|
|
164
164
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
### Simple Tools API (Recommended)
|
|
165
|
+
Use relay tools directly in your code (no MCP protocol needed):
|
|
168
166
|
|
|
169
167
|
```typescript
|
|
170
168
|
import { createTools } from '@agent-relay/mcp';
|
|
171
169
|
|
|
172
|
-
|
|
173
|
-
const tools = createTools('Conductor');
|
|
174
|
-
|
|
175
|
-
// Send messages to agents
|
|
176
|
-
await tools.send('Worker1', 'Run the test suite');
|
|
177
|
-
await tools.send('#team', 'Starting task coordination'); // Channel
|
|
178
|
-
await tools.send('*', 'System announcement'); // Broadcast
|
|
170
|
+
const tools = createTools('MyAgent');
|
|
179
171
|
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
task: 'Run all tests and report failures',
|
|
185
|
-
cwd: '/path/to/project', // Optional working directory
|
|
186
|
-
});
|
|
172
|
+
// Send messages
|
|
173
|
+
await tools.send('OtherAgent', 'Hello!');
|
|
174
|
+
await tools.send('#general', 'Channel message');
|
|
175
|
+
await tools.send('*', 'Broadcast');
|
|
187
176
|
|
|
188
|
-
|
|
189
|
-
console.error('Spawn failed:', result.error);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Check your inbox
|
|
177
|
+
// Check inbox
|
|
193
178
|
const messages = await tools.inbox();
|
|
194
179
|
for (const msg of messages) {
|
|
195
|
-
console.log(
|
|
196
|
-
if (msg.channel) console.log(` (in ${msg.channel})`);
|
|
180
|
+
console.log(`${msg.from}: ${msg.content}`);
|
|
197
181
|
}
|
|
198
182
|
|
|
199
183
|
// List online agents
|
|
200
184
|
const agents = await tools.who();
|
|
201
|
-
console.log('Online agents:', agents.map(a => a.name));
|
|
202
|
-
|
|
203
|
-
// Release workers when done
|
|
204
|
-
await tools.release('TestRunner', 'Tests complete');
|
|
205
|
-
|
|
206
|
-
// Get connection status
|
|
207
|
-
const status = await tools.status();
|
|
208
|
-
console.log(`Connected: ${status.connected}, Project: ${status.project}`);
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
### Full Integration Example
|
|
212
|
-
|
|
213
|
-
```typescript
|
|
214
|
-
import { createTools, type RelayTools, type Message, type Agent } from '@agent-relay/mcp';
|
|
215
|
-
|
|
216
|
-
class AgentOrchestrator {
|
|
217
|
-
private tools: RelayTools;
|
|
218
|
-
private workers: Map<string, { task: string; status: string }> = new Map();
|
|
219
|
-
|
|
220
|
-
constructor(name: string) {
|
|
221
|
-
this.tools = createTools(name);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
async spawnWorker(name: string, task: string) {
|
|
225
|
-
const result = await this.tools.spawn({
|
|
226
|
-
name,
|
|
227
|
-
cli: 'claude',
|
|
228
|
-
task,
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
if (result.success) {
|
|
232
|
-
this.workers.set(name, { task, status: 'running' });
|
|
233
|
-
}
|
|
234
|
-
return result;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
async sendTask(workerName: string, task: string) {
|
|
238
|
-
await this.tools.send(workerName, task);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
async waitForCompletion(workerName: string, timeoutMs = 60000) {
|
|
242
|
-
// Use sendAndWait for synchronous request-response
|
|
243
|
-
const response = await this.tools.sendAndWait(
|
|
244
|
-
workerName,
|
|
245
|
-
'Report your status',
|
|
246
|
-
{ timeoutMs }
|
|
247
|
-
);
|
|
248
|
-
return response;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
async getMessages(): Promise<Message[]> {
|
|
252
|
-
return this.tools.inbox();
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
async getOnlineAgents(): Promise<Agent[]> {
|
|
256
|
-
return this.tools.who();
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
async releaseWorker(name: string) {
|
|
260
|
-
await this.tools.release(name);
|
|
261
|
-
this.workers.delete(name);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
async releaseAll() {
|
|
265
|
-
for (const name of this.workers.keys()) {
|
|
266
|
-
await this.releaseWorker(name);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// Usage
|
|
272
|
-
const conductor = new AgentOrchestrator('Conductor');
|
|
273
185
|
|
|
274
|
-
|
|
275
|
-
await
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
// Check for responses
|
|
282
|
-
const messages = await conductor.getMessages();
|
|
186
|
+
// Spawn workers
|
|
187
|
+
await tools.spawn({
|
|
188
|
+
name: 'Worker1',
|
|
189
|
+
cli: 'claude',
|
|
190
|
+
task: 'Run tests',
|
|
191
|
+
});
|
|
283
192
|
|
|
284
|
-
//
|
|
285
|
-
await
|
|
193
|
+
// Release workers
|
|
194
|
+
await tools.release('Worker1');
|
|
286
195
|
```
|
|
287
196
|
|
|
288
|
-
### One-liners
|
|
197
|
+
### One-liners
|
|
289
198
|
|
|
290
199
|
```typescript
|
|
291
|
-
import { send, inbox, who } from '@agent-relay/mcp';
|
|
200
|
+
import { send, inbox, who } from '@agent-relay/mcp/simple';
|
|
292
201
|
|
|
293
|
-
// Send a message
|
|
294
202
|
await send('MyAgent', 'Bob', 'Hello!');
|
|
295
|
-
|
|
296
|
-
// Check inbox
|
|
297
203
|
const messages = await inbox('MyAgent');
|
|
298
|
-
|
|
299
|
-
// List agents
|
|
300
204
|
const agents = await who();
|
|
301
205
|
```
|
|
302
206
|
|
|
303
|
-
### Socket Discovery
|
|
304
|
-
|
|
305
|
-
The MCP package auto-discovers the daemon socket:
|
|
306
|
-
|
|
307
|
-
```typescript
|
|
308
|
-
import { discoverSocket, getConnectionInfo } from '@agent-relay/mcp';
|
|
309
|
-
|
|
310
|
-
// Get socket path and metadata
|
|
311
|
-
const discovery = discoverSocket();
|
|
312
|
-
if (discovery) {
|
|
313
|
-
console.log(`Socket: ${discovery.socketPath}`);
|
|
314
|
-
console.log(`Project: ${discovery.project}`);
|
|
315
|
-
console.log(`Source: ${discovery.source}`); // 'env' | 'cloud' | 'cwd' | 'scan'
|
|
316
|
-
console.log(`Cloud: ${discovery.isCloud}`);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// Or get full connection info
|
|
320
|
-
const info = getConnectionInfo();
|
|
321
|
-
```
|
|
322
|
-
|
|
323
207
|
## Requirements
|
|
324
208
|
|
|
325
209
|
- Node.js 18+
|
|
@@ -2,55 +2,95 @@
|
|
|
2
2
|
* RelayClient - Client for connecting to the Agent Relay daemon
|
|
3
3
|
*/
|
|
4
4
|
import { createConnection } from 'node:net';
|
|
5
|
-
import { randomUUID } from 'node:crypto';
|
|
6
5
|
import { discoverSocket } from './cloud.js';
|
|
7
6
|
import { DaemonNotRunningError } from './errors.js';
|
|
7
|
+
// Protocol version
|
|
8
|
+
const PROTOCOL_VERSION = 1;
|
|
9
|
+
/**
|
|
10
|
+
* Encode a message envelope into a length-prefixed frame (legacy format).
|
|
11
|
+
* Format: 4-byte big-endian length + JSON payload
|
|
12
|
+
*/
|
|
13
|
+
function encodeFrame(envelope) {
|
|
14
|
+
const json = JSON.stringify(envelope);
|
|
15
|
+
const data = Buffer.from(json, 'utf-8');
|
|
16
|
+
const header = Buffer.alloc(4);
|
|
17
|
+
header.writeUInt32BE(data.length, 0);
|
|
18
|
+
return Buffer.concat([header, data]);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Frame parser for length-prefixed messages.
|
|
22
|
+
*/
|
|
23
|
+
class FrameParser {
|
|
24
|
+
buffer = Buffer.alloc(0);
|
|
25
|
+
push(data) {
|
|
26
|
+
this.buffer = Buffer.concat([this.buffer, data]);
|
|
27
|
+
const frames = [];
|
|
28
|
+
while (this.buffer.length >= 4) {
|
|
29
|
+
const frameLength = this.buffer.readUInt32BE(0);
|
|
30
|
+
const totalLength = 4 + frameLength;
|
|
31
|
+
if (this.buffer.length < totalLength)
|
|
32
|
+
break;
|
|
33
|
+
const payload = this.buffer.subarray(4, totalLength);
|
|
34
|
+
this.buffer = this.buffer.subarray(totalLength);
|
|
35
|
+
try {
|
|
36
|
+
frames.push(JSON.parse(payload.toString('utf-8')));
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Skip malformed frames
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return frames;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
8
45
|
export function createRelayClient(options) {
|
|
9
46
|
const { agentName, project = 'default', timeout = 5000 } = options;
|
|
10
47
|
const discovery = discoverSocket({ socketPath: options.socketPath });
|
|
11
48
|
const socketPath = discovery?.socketPath || options.socketPath || '/tmp/agent-relay.sock';
|
|
12
|
-
//
|
|
13
|
-
|
|
49
|
+
// Generate unique IDs
|
|
50
|
+
let idCounter = 0;
|
|
51
|
+
const generateId = () => `mcp-${Date.now().toString(36)}-${(++idCounter).toString(36)}`;
|
|
14
52
|
async function request(type, payload) {
|
|
15
53
|
return new Promise((resolve, reject) => {
|
|
16
54
|
const id = generateId();
|
|
17
|
-
|
|
55
|
+
// Build a proper protocol envelope
|
|
56
|
+
const envelope = {
|
|
57
|
+
v: PROTOCOL_VERSION,
|
|
58
|
+
type,
|
|
59
|
+
id,
|
|
60
|
+
ts: Date.now(),
|
|
61
|
+
payload,
|
|
62
|
+
};
|
|
18
63
|
let timedOut = false;
|
|
19
|
-
|
|
64
|
+
const parser = new FrameParser();
|
|
20
65
|
const socket = createConnection(socketPath);
|
|
21
66
|
const timeoutId = setTimeout(() => {
|
|
22
67
|
timedOut = true;
|
|
23
68
|
socket.destroy();
|
|
24
69
|
reject(new Error(`Request timeout after ${timeout}ms`));
|
|
25
70
|
}, timeout);
|
|
26
|
-
socket.on('connect', () => socket.write(
|
|
71
|
+
socket.on('connect', () => socket.write(encodeFrame(envelope)));
|
|
27
72
|
socket.on('data', (data) => {
|
|
28
73
|
// Ignore data if we've already timed out
|
|
29
74
|
if (timedOut)
|
|
30
75
|
return;
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
socket.end();
|
|
42
|
-
if (response.error)
|
|
43
|
-
reject(new Error(response.error));
|
|
44
|
-
else
|
|
45
|
-
resolve(response.payload);
|
|
46
|
-
return;
|
|
76
|
+
const frames = parser.push(data);
|
|
77
|
+
for (const response of frames) {
|
|
78
|
+
// Check if this is a response to our request
|
|
79
|
+
if (response.id === id || response.payload?.replyTo === id) {
|
|
80
|
+
clearTimeout(timeoutId);
|
|
81
|
+
socket.end();
|
|
82
|
+
// Handle error responses
|
|
83
|
+
if (response.type === 'ERROR') {
|
|
84
|
+
const errorPayload = response.payload;
|
|
85
|
+
reject(new Error(errorPayload?.message || errorPayload?.code || 'Unknown error'));
|
|
47
86
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
87
|
+
else if (response.payload?.error) {
|
|
88
|
+
reject(new Error(response.payload.error));
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
resolve(response.payload);
|
|
53
92
|
}
|
|
93
|
+
return;
|
|
54
94
|
}
|
|
55
95
|
}
|
|
56
96
|
});
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
8
8
|
import { join } from 'node:path';
|
|
9
9
|
import { homedir } from 'node:os';
|
|
10
|
+
import { findProjectRoot } from '@agent-relay/config';
|
|
10
11
|
// ============================================================================
|
|
11
12
|
// Cloud Workspace Detection
|
|
12
13
|
// ============================================================================
|
|
@@ -61,24 +62,6 @@ export function getCloudSocketPath(workspaceId) {
|
|
|
61
62
|
export function getCloudOutboxPath(workspaceId, agentName) {
|
|
62
63
|
return `/tmp/relay/${workspaceId}/outbox/${agentName}`;
|
|
63
64
|
}
|
|
64
|
-
/**
|
|
65
|
-
* Find project root by looking for common markers.
|
|
66
|
-
* Scans up from startDir until it finds a marker or hits the filesystem root.
|
|
67
|
-
*/
|
|
68
|
-
function findProjectRoot(startDir = process.cwd()) {
|
|
69
|
-
let current = startDir;
|
|
70
|
-
const root = current.split('/')[0] || '/';
|
|
71
|
-
const markers = ['.git', 'package.json', 'Cargo.toml', 'go.mod', 'pyproject.toml', '.agent-relay'];
|
|
72
|
-
while (current !== root && current !== '/') {
|
|
73
|
-
for (const marker of markers) {
|
|
74
|
-
if (existsSync(join(current, marker))) {
|
|
75
|
-
return current;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
current = join(current, '..');
|
|
79
|
-
}
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
65
|
/**
|
|
83
66
|
* Get platform-specific data directory.
|
|
84
67
|
*/
|
|
@@ -6,6 +6,6 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { Prompt } from '@modelcontextprotocol/sdk/types.js';
|
|
8
8
|
export declare const protocolPrompt: Prompt;
|
|
9
|
-
export declare const PROTOCOL_DOCUMENTATION = "\n# Agent Relay Protocol\n\nYou are connected to Agent Relay, a real-time messaging system for AI agent coordination.\n\n## Communication Patterns\n\n### Direct Messages\nSend a message to a specific agent by name:\n```\nrelay_send(to=\"Alice\", message=\"Can you review this PR?\")\n```\n\n### Channel Messages\nSend to a channel (prefix with #):\n```\nrelay_send(to=\"#engineering\", message=\"Build complete\")\n```\nChannel messages are visible to all agents subscribed to that channel.\n\n### Broadcast\nSend to all online agents:\n```\nrelay_send(to=\"*\", message=\"System maintenance in 5 minutes\")\n```\nUse sparingly - broadcasts interrupt all agents.\n\n### Threaded Conversations\nFor multi-turn conversations, use thread IDs:\n```\nrelay_send(to=\"Bob\", message=\"Starting task\", thread=\"task-123\")\nrelay_send(to=\"Bob\", message=\"Task update\", thread=\"task-123\")\n```\n\n### Await Response\nBlock and wait for a reply:\n```\nrelay_send(to=\"Worker\", message=\"Process this file\", await_response=true, timeout_ms=60000)\n```\n\n## Spawning Workers\n\nCreate worker agents to parallelize work:\n\n```\nrelay_spawn(\n name=\"TestRunner\",\n cli=\"claude\",\n task=\"Run the test suite in src/tests/ and report any failures\"\n)\n```\n\nWorkers:\n- Run in separate processes\n- Have their own CLI instance\n- Can use relay to communicate back\n- Should be released when done\n\n### Worker Lifecycle\n1. Spawn worker with task\n2. Worker sends ACK when ready\n3. Worker sends progress updates\n4. Worker sends DONE when complete\n5. Lead releases worker\n\n### Release Workers\n```\nrelay_release(name=\"TestRunner\", reason=\"Tests completed\")\n```\n\n## Message Protocol\n\nWhen you receive messages, they follow this format:\n```\nRelay message from Alice [msg-id-123]: Content here\n```\n\nChannel messages include the channel:\n```\nRelay message from Alice [msg-id-456] [#general]: Hello team!\n```\n\n### ACK/DONE
|
|
9
|
+
export declare const PROTOCOL_DOCUMENTATION = "\n# Agent Relay Protocol\n\nYou are connected to Agent Relay, a real-time messaging system for AI agent coordination.\n\n## Communication Patterns\n\n### Direct Messages\nSend a message to a specific agent by name:\n```\nrelay_send(to=\"Alice\", message=\"Can you review this PR?\")\n```\n\n### Channel Messages\nSend to a channel (prefix with #):\n```\nrelay_send(to=\"#engineering\", message=\"Build complete\")\n```\nChannel messages are visible to all agents subscribed to that channel.\n\n### Broadcast\nSend to all online agents:\n```\nrelay_send(to=\"*\", message=\"System maintenance in 5 minutes\")\n```\nUse sparingly - broadcasts interrupt all agents.\n\n### Threaded Conversations\nFor multi-turn conversations, use thread IDs:\n```\nrelay_send(to=\"Bob\", message=\"Starting task\", thread=\"task-123\")\nrelay_send(to=\"Bob\", message=\"Task update\", thread=\"task-123\")\n```\n\n### Await Response\nBlock and wait for a reply:\n```\nrelay_send(to=\"Worker\", message=\"Process this file\", await_response=true, timeout_ms=60000)\n```\n\n## Spawning Workers\n\nCreate worker agents to parallelize work:\n\n```\nrelay_spawn(\n name=\"TestRunner\",\n cli=\"claude\",\n task=\"Run the test suite in src/tests/ and report any failures\"\n)\n```\n\nWorkers:\n- Run in separate processes\n- Have their own CLI instance\n- Can use relay to communicate back\n- Should be released when done\n\n### Worker Lifecycle\n1. Spawn worker with task\n2. Worker sends ACK when ready\n3. Worker sends progress updates\n4. Worker sends DONE when complete\n5. Lead releases worker\n\n### Release Workers\n```\nrelay_release(name=\"TestRunner\", reason=\"Tests completed\")\n```\n\n## Message Protocol\n\nWhen you receive messages, they follow this format:\n```\nRelay message from Alice [msg-id-123]: Content here\n```\n\nChannel messages include the channel:\n```\nRelay message from Alice [msg-id-456] [#general]: Hello team!\n```\n\n### Optional: ACK/DONE Convention\nSome applications use ACK/DONE conventions for task tracking. If your application uses this pattern:\n1. Send ACK when starting: \"ACK: Starting work on X\"\n2. Send progress updates as needed\n3. Send DONE when complete: \"DONE: Completed X with result Y\"\n\nNote: This is an application-level convention, not a protocol requirement. Check your application's documentation for expected message formats.\n\n## Best Practices\n\n### For Lead Agents\n- Spawn workers for parallelizable tasks\n- Keep track of spawned workers\n- Release workers when done\n- Use channels for team announcements\n\n### For Worker Agents\n- Respond promptly when receiving tasks\n- Send progress updates for long tasks\n- Report results when complete\n- Ask clarifying questions if needed\n\n### Message Etiquette\n- Keep messages concise\n- Include relevant context\n- Use threads for related messages\n- Don't spam broadcasts\n\n## Checking Messages\n\nProactively check your inbox:\n```\nrelay_inbox()\nrelay_inbox(from=\"Lead\")\nrelay_inbox(channel=\"#urgent\")\n```\n\n## Seeing Who's Online\n\n```\nrelay_who()\n```\n\n## Error Handling\n\nIf relay returns an error:\n- \"Daemon not running\" - The relay daemon needs to be started\n- \"Agent not found\" - Target agent is offline\n- \"Channel not found\" - Channel doesn't exist\n- \"Timeout\" - No response within timeout period\n\n## Multi-Project Communication\n\nIn multi-project setups, specify project:\n```\nrelay_send(to=\"frontend:Designer\", message=\"Need UI mockup\")\n```\n\nSpecial targets:\n- `project:lead` - Lead agent of that project\n- `project:*` - Broadcast to project\n- `*:*` - Broadcast to all projects\n";
|
|
10
10
|
export declare function getProtocolPrompt(): string;
|
|
11
11
|
//# sourceMappingURL=protocol.d.ts.map
|