agent-relay 1.0.7 → 1.0.9
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 +176 -6
- package/dist/bridge/config.d.ts +41 -0
- package/dist/bridge/config.d.ts.map +1 -0
- package/dist/bridge/config.js +143 -0
- package/dist/bridge/config.js.map +1 -0
- package/dist/bridge/index.d.ts +10 -0
- package/dist/bridge/index.d.ts.map +1 -0
- package/dist/bridge/index.js +10 -0
- package/dist/bridge/index.js.map +1 -0
- package/dist/bridge/multi-project-client.d.ts +99 -0
- package/dist/bridge/multi-project-client.d.ts.map +1 -0
- package/dist/bridge/multi-project-client.js +386 -0
- package/dist/bridge/multi-project-client.js.map +1 -0
- package/dist/bridge/spawner.d.ts +46 -0
- package/dist/bridge/spawner.d.ts.map +1 -0
- package/dist/bridge/spawner.js +223 -0
- package/dist/bridge/spawner.js.map +1 -0
- package/dist/bridge/types.d.ts +55 -0
- package/dist/bridge/types.d.ts.map +1 -0
- package/dist/bridge/types.js +6 -0
- package/dist/bridge/types.js.map +1 -0
- package/dist/bridge/utils.d.ts +30 -0
- package/dist/bridge/utils.d.ts.map +1 -0
- package/dist/bridge/utils.js +54 -0
- package/dist/bridge/utils.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +906 -6
- package/dist/cli/index.js.map +1 -1
- package/dist/daemon/agent-registry.d.ts +60 -0
- package/dist/daemon/agent-registry.d.ts.map +1 -0
- package/dist/daemon/agent-registry.js +163 -0
- package/dist/daemon/agent-registry.js.map +1 -0
- package/dist/daemon/connection.d.ts +33 -1
- package/dist/daemon/connection.d.ts.map +1 -1
- package/dist/daemon/connection.js +86 -11
- package/dist/daemon/connection.js.map +1 -1
- package/dist/daemon/index.d.ts +2 -0
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +2 -0
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/registry.d.ts +9 -0
- package/dist/daemon/registry.d.ts.map +1 -0
- package/dist/daemon/registry.js +9 -0
- package/dist/daemon/registry.js.map +1 -0
- package/dist/daemon/router.d.ts +61 -2
- package/dist/daemon/router.d.ts.map +1 -1
- package/dist/daemon/router.js +219 -4
- package/dist/daemon/router.js.map +1 -1
- package/dist/daemon/server.d.ts +9 -0
- package/dist/daemon/server.d.ts.map +1 -1
- package/dist/daemon/server.js +135 -16
- package/dist/daemon/server.js.map +1 -1
- package/dist/dashboard/metrics.d.ts +105 -0
- package/dist/dashboard/metrics.d.ts.map +1 -0
- package/dist/dashboard/metrics.js +192 -0
- package/dist/dashboard/metrics.js.map +1 -0
- package/dist/dashboard/needs-attention.d.ts +24 -0
- package/dist/dashboard/needs-attention.d.ts.map +1 -0
- package/dist/dashboard/needs-attention.js +78 -0
- package/dist/dashboard/needs-attention.js.map +1 -0
- package/dist/dashboard/public/bridge.html +1272 -0
- package/dist/dashboard/public/index.html +2094 -347
- package/dist/dashboard/public/js/app.js +184 -0
- package/dist/dashboard/public/js/app.js.map +7 -0
- package/dist/dashboard/public/metrics.html +999 -0
- package/dist/dashboard/server.d.ts +14 -1
- package/dist/dashboard/server.d.ts.map +1 -1
- package/dist/dashboard/server.js +689 -16
- package/dist/dashboard/server.js.map +1 -1
- package/dist/dashboard/start.js +1 -1
- package/dist/dashboard/start.js.map +1 -1
- package/dist/dashboard-v2/index.d.ts +10 -0
- package/dist/dashboard-v2/index.d.ts.map +1 -0
- package/dist/dashboard-v2/index.js +54 -0
- package/dist/dashboard-v2/index.js.map +1 -0
- package/dist/dashboard-v2/lib/api.d.ts +95 -0
- package/dist/dashboard-v2/lib/api.d.ts.map +1 -0
- package/dist/dashboard-v2/lib/api.js +270 -0
- package/dist/dashboard-v2/lib/api.js.map +1 -0
- package/dist/dashboard-v2/lib/colors.d.ts +61 -0
- package/dist/dashboard-v2/lib/colors.d.ts.map +1 -0
- package/dist/dashboard-v2/lib/colors.js +198 -0
- package/dist/dashboard-v2/lib/colors.js.map +1 -0
- package/dist/dashboard-v2/lib/hierarchy.d.ts +74 -0
- package/dist/dashboard-v2/lib/hierarchy.d.ts.map +1 -0
- package/dist/dashboard-v2/lib/hierarchy.js +196 -0
- package/dist/dashboard-v2/lib/hierarchy.js.map +1 -0
- package/dist/dashboard-v2/types/index.d.ts +154 -0
- package/dist/dashboard-v2/types/index.d.ts.map +1 -0
- package/dist/dashboard-v2/types/index.js +6 -0
- package/dist/dashboard-v2/types/index.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/protocol/types.d.ts +15 -1
- package/dist/protocol/types.d.ts.map +1 -1
- package/dist/storage/adapter.d.ts +74 -1
- package/dist/storage/adapter.d.ts.map +1 -1
- package/dist/storage/adapter.js +39 -0
- package/dist/storage/adapter.js.map +1 -1
- package/dist/storage/sqlite-adapter.d.ts +92 -1
- package/dist/storage/sqlite-adapter.d.ts.map +1 -1
- package/dist/storage/sqlite-adapter.js +615 -47
- package/dist/storage/sqlite-adapter.js.map +1 -1
- package/dist/utils/agent-config.d.ts +45 -0
- package/dist/utils/agent-config.d.ts.map +1 -0
- package/dist/utils/agent-config.js +118 -0
- package/dist/utils/agent-config.js.map +1 -0
- package/dist/utils/project-namespace.d.ts.map +1 -1
- package/dist/utils/project-namespace.js +22 -1
- package/dist/utils/project-namespace.js.map +1 -1
- package/dist/wrapper/client.d.ts +30 -3
- package/dist/wrapper/client.d.ts.map +1 -1
- package/dist/wrapper/client.js +85 -9
- package/dist/wrapper/client.js.map +1 -1
- package/dist/wrapper/parser.d.ts +127 -4
- package/dist/wrapper/parser.d.ts.map +1 -1
- package/dist/wrapper/parser.js +622 -86
- package/dist/wrapper/parser.js.map +1 -1
- package/dist/wrapper/tmux-wrapper.d.ts +136 -10
- package/dist/wrapper/tmux-wrapper.d.ts.map +1 -1
- package/dist/wrapper/tmux-wrapper.js +599 -79
- package/dist/wrapper/tmux-wrapper.js.map +1 -1
- package/docs/AGENTS.md +132 -27
- package/docs/ARCHITECTURE_DECISIONS.md +175 -0
- package/docs/CHANGELOG.md +1 -1
- package/docs/COMPETITIVE_ANALYSIS.md +897 -0
- package/docs/DESIGN_BRIDGE_STAFFING.md +878 -0
- package/docs/DESIGN_V2.md +1079 -0
- package/docs/INTEGRATION-GUIDE.md +926 -0
- package/docs/MONETIZATION.md +1679 -0
- package/docs/PROPOSAL-trajectories.md +1582 -0
- package/docs/PROTOCOL.md +3 -3
- package/docs/SCALING_ANALYSIS.md +280 -0
- package/docs/TMUX_IMPLEMENTATION_NOTES.md +9 -9
- package/docs/TMUX_IMPROVEMENTS.md +968 -0
- package/docs/agent-relay-snippet.md +61 -0
- package/docs/competitive-analysis-mcp-agent-mail.md +389 -0
- package/docs/dashboard-v2-plan.md +179 -0
- package/package.json +10 -3
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MultiProjectClient
|
|
3
|
+
* Connects to multiple project daemons simultaneously for cross-project orchestration.
|
|
4
|
+
*/
|
|
5
|
+
import net from 'node:net';
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import { v4 as uuid } from 'uuid';
|
|
8
|
+
import { PROTOCOL_VERSION, } from '../protocol/types.js';
|
|
9
|
+
import { encodeFrame, FrameParser } from '../protocol/framing.js';
|
|
10
|
+
export class MultiProjectClient {
|
|
11
|
+
projects;
|
|
12
|
+
connections = new Map();
|
|
13
|
+
leads = new Map();
|
|
14
|
+
options;
|
|
15
|
+
shuttingDown = false;
|
|
16
|
+
/** Handler for incoming messages */
|
|
17
|
+
onMessage;
|
|
18
|
+
/** Handler for connection state changes */
|
|
19
|
+
onProjectStateChange;
|
|
20
|
+
constructor(projects, options = {}) {
|
|
21
|
+
this.projects = projects;
|
|
22
|
+
this.options = {
|
|
23
|
+
agentName: options.agentName ?? '__BridgeClient',
|
|
24
|
+
reconnect: options.reconnect ?? true,
|
|
25
|
+
reconnectDelay: options.reconnectDelay ?? 1000,
|
|
26
|
+
maxReconnectDelay: options.maxReconnectDelay ?? 30000,
|
|
27
|
+
maxReconnectAttempts: options.maxReconnectAttempts ?? Infinity,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Connect to all project daemons
|
|
32
|
+
*/
|
|
33
|
+
async connect() {
|
|
34
|
+
const connectPromises = this.projects.map((project) => this.connectToProject(project));
|
|
35
|
+
await Promise.all(connectPromises);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Connect to a single project daemon
|
|
39
|
+
*/
|
|
40
|
+
connectToProject(project) {
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
// Check socket exists
|
|
43
|
+
if (!fs.existsSync(project.socketPath)) {
|
|
44
|
+
console.error(`[bridge] No daemon running for ${project.id} (${project.path})`);
|
|
45
|
+
console.error(`[bridge] Start with: cd ${project.path} && agent-relay up`);
|
|
46
|
+
reject(new Error(`No daemon for ${project.id}`));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const socket = net.createConnection(project.socketPath, () => {
|
|
50
|
+
this.sendHello(conn);
|
|
51
|
+
});
|
|
52
|
+
const conn = {
|
|
53
|
+
config: project,
|
|
54
|
+
socket,
|
|
55
|
+
parser: new FrameParser(),
|
|
56
|
+
ready: false,
|
|
57
|
+
};
|
|
58
|
+
socket.on('data', (data) => this.handleData(conn, data));
|
|
59
|
+
socket.on('close', () => {
|
|
60
|
+
const wasReady = conn.ready;
|
|
61
|
+
conn.ready = false;
|
|
62
|
+
this.onProjectStateChange?.(project.id, false);
|
|
63
|
+
// Attempt reconnection if enabled and not shutting down
|
|
64
|
+
if (wasReady && this.options.reconnect && !this.shuttingDown) {
|
|
65
|
+
this.scheduleReconnect(conn);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
socket.on('error', (err) => {
|
|
69
|
+
console.error(`[bridge] Connection error for ${project.id}:`, err.message);
|
|
70
|
+
if (!conn.ready) {
|
|
71
|
+
reject(err);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
this.connections.set(project.id, conn);
|
|
75
|
+
// Wait for ready
|
|
76
|
+
const checkReady = setInterval(() => {
|
|
77
|
+
if (conn.ready) {
|
|
78
|
+
clearInterval(checkReady);
|
|
79
|
+
clearTimeout(timeout);
|
|
80
|
+
resolve();
|
|
81
|
+
}
|
|
82
|
+
}, 10);
|
|
83
|
+
const timeout = setTimeout(() => {
|
|
84
|
+
if (!conn.ready) {
|
|
85
|
+
clearInterval(checkReady);
|
|
86
|
+
socket.destroy();
|
|
87
|
+
reject(new Error(`Connection timeout for ${project.id}`));
|
|
88
|
+
}
|
|
89
|
+
}, 5000);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Send HELLO to a project daemon
|
|
94
|
+
*/
|
|
95
|
+
sendHello(conn) {
|
|
96
|
+
const hello = {
|
|
97
|
+
v: PROTOCOL_VERSION,
|
|
98
|
+
type: 'HELLO',
|
|
99
|
+
id: uuid(),
|
|
100
|
+
ts: Date.now(),
|
|
101
|
+
payload: {
|
|
102
|
+
agent: this.options.agentName,
|
|
103
|
+
cli: 'bridge',
|
|
104
|
+
capabilities: {
|
|
105
|
+
ack: true,
|
|
106
|
+
resume: false,
|
|
107
|
+
max_inflight: 256,
|
|
108
|
+
supports_topics: true,
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
this.send(conn, hello);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Handle incoming data from a project connection
|
|
116
|
+
*/
|
|
117
|
+
handleData(conn, data) {
|
|
118
|
+
try {
|
|
119
|
+
const frames = conn.parser.push(data);
|
|
120
|
+
for (const frame of frames) {
|
|
121
|
+
this.processFrame(conn, frame);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
console.error(`[bridge] Parse error for ${conn.config.id}:`, err);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Process a frame from a project daemon
|
|
130
|
+
*/
|
|
131
|
+
processFrame(conn, envelope) {
|
|
132
|
+
switch (envelope.type) {
|
|
133
|
+
case 'WELCOME':
|
|
134
|
+
conn.ready = true;
|
|
135
|
+
console.log(`[bridge] Connected to ${conn.config.id}`);
|
|
136
|
+
this.onProjectStateChange?.(conn.config.id, true);
|
|
137
|
+
break;
|
|
138
|
+
case 'DELIVER':
|
|
139
|
+
this.handleDeliver(conn, envelope);
|
|
140
|
+
break;
|
|
141
|
+
case 'PING':
|
|
142
|
+
this.send(conn, {
|
|
143
|
+
v: PROTOCOL_VERSION,
|
|
144
|
+
type: 'PONG',
|
|
145
|
+
id: uuid(),
|
|
146
|
+
ts: Date.now(),
|
|
147
|
+
payload: envelope.payload ?? {},
|
|
148
|
+
});
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Handle delivered message from a project
|
|
154
|
+
*/
|
|
155
|
+
handleDeliver(conn, envelope) {
|
|
156
|
+
// Send ACK
|
|
157
|
+
this.send(conn, {
|
|
158
|
+
v: PROTOCOL_VERSION,
|
|
159
|
+
type: 'ACK',
|
|
160
|
+
id: uuid(),
|
|
161
|
+
ts: Date.now(),
|
|
162
|
+
payload: {
|
|
163
|
+
ack_id: envelope.id,
|
|
164
|
+
seq: envelope.delivery.seq,
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
// Notify handler
|
|
168
|
+
if (this.onMessage && envelope.from) {
|
|
169
|
+
this.onMessage(conn.config.id, envelope.from, envelope.payload, envelope.id);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Send envelope to a project daemon
|
|
174
|
+
*/
|
|
175
|
+
send(conn, envelope) {
|
|
176
|
+
if (!conn.socket)
|
|
177
|
+
return false;
|
|
178
|
+
try {
|
|
179
|
+
const frame = encodeFrame(envelope);
|
|
180
|
+
conn.socket.write(frame);
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
console.error(`[bridge] Send error for ${conn.config.id}:`, err);
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Send message to a project
|
|
190
|
+
* @param projectId - Target project ID
|
|
191
|
+
* @param to - Agent name within the project (or '*' for broadcast, 'lead' for project lead)
|
|
192
|
+
* @param body - Message body
|
|
193
|
+
*/
|
|
194
|
+
sendToProject(projectId, to, body) {
|
|
195
|
+
const conn = this.connections.get(projectId);
|
|
196
|
+
if (!conn?.ready) {
|
|
197
|
+
console.error(`[bridge] Cannot send to ${projectId}: not connected`);
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
// Resolve 'lead' to actual lead name
|
|
201
|
+
let targetAgent = to;
|
|
202
|
+
if (to === 'lead') {
|
|
203
|
+
const lead = this.leads.get(projectId);
|
|
204
|
+
if (lead) {
|
|
205
|
+
targetAgent = lead.name;
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
// Fallback to configured lead name
|
|
209
|
+
targetAgent = conn.config.leadName;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const envelope = {
|
|
213
|
+
v: PROTOCOL_VERSION,
|
|
214
|
+
type: 'SEND',
|
|
215
|
+
id: uuid(),
|
|
216
|
+
ts: Date.now(),
|
|
217
|
+
to: targetAgent,
|
|
218
|
+
payload: {
|
|
219
|
+
kind: 'message',
|
|
220
|
+
body,
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
return this.send(conn, envelope);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Broadcast to all leads
|
|
227
|
+
*/
|
|
228
|
+
broadcastToLeads(body) {
|
|
229
|
+
for (const [projectId] of this.connections) {
|
|
230
|
+
this.sendToProject(projectId, 'lead', body);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Broadcast to all agents in all projects
|
|
235
|
+
*/
|
|
236
|
+
broadcastAll(body) {
|
|
237
|
+
for (const [projectId, conn] of this.connections) {
|
|
238
|
+
if (conn.ready) {
|
|
239
|
+
const envelope = {
|
|
240
|
+
v: PROTOCOL_VERSION,
|
|
241
|
+
type: 'SEND',
|
|
242
|
+
id: uuid(),
|
|
243
|
+
ts: Date.now(),
|
|
244
|
+
to: '*',
|
|
245
|
+
payload: {
|
|
246
|
+
kind: 'message',
|
|
247
|
+
body,
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
this.send(conn, envelope);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Register a lead for a project
|
|
256
|
+
*/
|
|
257
|
+
registerLead(projectId, leadName) {
|
|
258
|
+
this.leads.set(projectId, {
|
|
259
|
+
name: leadName,
|
|
260
|
+
projectId,
|
|
261
|
+
connected: true,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Get all connected projects
|
|
266
|
+
*/
|
|
267
|
+
getConnectedProjects() {
|
|
268
|
+
return Array.from(this.connections.entries())
|
|
269
|
+
.filter(([_, conn]) => conn.ready)
|
|
270
|
+
.map(([id]) => id);
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Get project connection info
|
|
274
|
+
*/
|
|
275
|
+
getProject(projectId) {
|
|
276
|
+
return this.connections.get(projectId)?.config;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Schedule a reconnection attempt with exponential backoff
|
|
280
|
+
*/
|
|
281
|
+
scheduleReconnect(conn) {
|
|
282
|
+
if (conn.reconnecting || this.shuttingDown)
|
|
283
|
+
return;
|
|
284
|
+
const attempts = conn.reconnectAttempts ?? 0;
|
|
285
|
+
if (attempts >= this.options.maxReconnectAttempts) {
|
|
286
|
+
console.error(`[bridge] Max reconnection attempts reached for ${conn.config.id}`);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
conn.reconnecting = true;
|
|
290
|
+
conn.reconnectAttempts = attempts + 1;
|
|
291
|
+
// Calculate delay with exponential backoff
|
|
292
|
+
const delay = Math.min(this.options.reconnectDelay * Math.pow(2, attempts), this.options.maxReconnectDelay);
|
|
293
|
+
console.log(`[bridge] Reconnecting to ${conn.config.id} in ${delay}ms (attempt ${conn.reconnectAttempts})`);
|
|
294
|
+
conn.reconnectTimer = setTimeout(() => {
|
|
295
|
+
this.attemptReconnect(conn);
|
|
296
|
+
}, delay);
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Attempt to reconnect to a project daemon
|
|
300
|
+
*/
|
|
301
|
+
attemptReconnect(conn) {
|
|
302
|
+
if (this.shuttingDown) {
|
|
303
|
+
conn.reconnecting = false;
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
// Check socket exists
|
|
307
|
+
if (!fs.existsSync(conn.config.socketPath)) {
|
|
308
|
+
console.error(`[bridge] No daemon running for ${conn.config.id}, will retry`);
|
|
309
|
+
conn.reconnecting = false;
|
|
310
|
+
this.scheduleReconnect(conn);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const socket = net.createConnection(conn.config.socketPath, () => {
|
|
314
|
+
this.sendHello(conn);
|
|
315
|
+
});
|
|
316
|
+
// Update connection with new socket
|
|
317
|
+
conn.socket = socket;
|
|
318
|
+
conn.parser = new FrameParser();
|
|
319
|
+
socket.on('data', (data) => this.handleData(conn, data));
|
|
320
|
+
socket.on('close', () => {
|
|
321
|
+
const wasReady = conn.ready;
|
|
322
|
+
conn.ready = false;
|
|
323
|
+
this.onProjectStateChange?.(conn.config.id, false);
|
|
324
|
+
if (wasReady && this.options.reconnect && !this.shuttingDown) {
|
|
325
|
+
this.scheduleReconnect(conn);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
socket.on('error', (err) => {
|
|
329
|
+
console.error(`[bridge] Reconnection error for ${conn.config.id}:`, err.message);
|
|
330
|
+
conn.reconnecting = false;
|
|
331
|
+
if (!this.shuttingDown) {
|
|
332
|
+
this.scheduleReconnect(conn);
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
// Reset reconnection state on successful connect
|
|
336
|
+
const originalReady = conn.ready;
|
|
337
|
+
const checkReady = setInterval(() => {
|
|
338
|
+
if (conn.ready && !originalReady) {
|
|
339
|
+
clearInterval(checkReady);
|
|
340
|
+
clearTimeout(timeout);
|
|
341
|
+
conn.reconnecting = false;
|
|
342
|
+
conn.reconnectAttempts = 0;
|
|
343
|
+
console.log(`[bridge] Reconnected to ${conn.config.id}`);
|
|
344
|
+
}
|
|
345
|
+
}, 10);
|
|
346
|
+
const timeout = setTimeout(() => {
|
|
347
|
+
if (!conn.ready) {
|
|
348
|
+
clearInterval(checkReady);
|
|
349
|
+
socket.destroy();
|
|
350
|
+
conn.reconnecting = false;
|
|
351
|
+
console.error(`[bridge] Reconnection timeout for ${conn.config.id}`);
|
|
352
|
+
if (!this.shuttingDown) {
|
|
353
|
+
this.scheduleReconnect(conn);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}, 5000);
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Disconnect from all projects
|
|
360
|
+
*/
|
|
361
|
+
disconnect() {
|
|
362
|
+
this.shuttingDown = true;
|
|
363
|
+
for (const [_, conn] of this.connections) {
|
|
364
|
+
// Clear any pending reconnection timers
|
|
365
|
+
if (conn.reconnectTimer) {
|
|
366
|
+
clearTimeout(conn.reconnectTimer);
|
|
367
|
+
}
|
|
368
|
+
try {
|
|
369
|
+
this.send(conn, {
|
|
370
|
+
v: PROTOCOL_VERSION,
|
|
371
|
+
type: 'BYE',
|
|
372
|
+
id: uuid(),
|
|
373
|
+
ts: Date.now(),
|
|
374
|
+
payload: {},
|
|
375
|
+
});
|
|
376
|
+
conn.socket.end();
|
|
377
|
+
}
|
|
378
|
+
catch {
|
|
379
|
+
// Ignore
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
this.connections.clear();
|
|
383
|
+
this.leads.clear();
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
//# sourceMappingURL=multi-project-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-project-client.js","sourceRoot":"","sources":["../../src/bridge/multi-project-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Spawner
|
|
3
|
+
* Handles spawning and releasing worker agents via tmux.
|
|
4
|
+
*/
|
|
5
|
+
import type { SpawnRequest, SpawnResult, WorkerInfo } from './types.js';
|
|
6
|
+
export declare class AgentSpawner {
|
|
7
|
+
private activeWorkers;
|
|
8
|
+
private tmuxSession;
|
|
9
|
+
private agentsPath;
|
|
10
|
+
constructor(projectRoot: string, tmuxSession?: string);
|
|
11
|
+
private projectRoot;
|
|
12
|
+
/**
|
|
13
|
+
* Ensure the worker tmux session exists
|
|
14
|
+
*/
|
|
15
|
+
ensureSession(): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Spawn a new worker agent
|
|
18
|
+
*/
|
|
19
|
+
spawn(request: SpawnRequest): Promise<SpawnResult>;
|
|
20
|
+
/**
|
|
21
|
+
* Release (terminate) a worker
|
|
22
|
+
*/
|
|
23
|
+
release(name: string): Promise<boolean>;
|
|
24
|
+
/**
|
|
25
|
+
* Release all workers
|
|
26
|
+
*/
|
|
27
|
+
releaseAll(): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Get all active workers
|
|
30
|
+
*/
|
|
31
|
+
getActiveWorkers(): WorkerInfo[];
|
|
32
|
+
/**
|
|
33
|
+
* Check if a worker exists
|
|
34
|
+
*/
|
|
35
|
+
hasWorker(name: string): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Get worker info
|
|
38
|
+
*/
|
|
39
|
+
getWorker(name: string): WorkerInfo | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Wait for an agent to appear in the registry (agents.json)
|
|
42
|
+
*/
|
|
43
|
+
private waitForAgentRegistration;
|
|
44
|
+
private isAgentRegistered;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=spawner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spawner.d.ts","sourceRoot":"","sources":["../../src/bridge/spawner.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExE,qBAAa,YAAY;IACvB,OAAO,CAAC,aAAa,CAAsC;IAC3D,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,UAAU,CAAS;gBAGzB,WAAW,EAAE,MAAM,EACnB,WAAW,CAAC,EAAE,MAAM;IAStB,OAAO,CAAC,WAAW,CAAS;IAE5B;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAYpC;;OAEG;IACG,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC;IAyGxD;;OAEG;IACG,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiC7C;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAOjC;;OAEG;IACH,gBAAgB,IAAI,UAAU,EAAE;IAIhC;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIhC;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAI/C;;OAEG;YACW,wBAAwB;IAkBtC,OAAO,CAAC,iBAAiB;CAkB1B"}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Spawner
|
|
3
|
+
* Handles spawning and releasing worker agents via tmux.
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { execAsync, sleep, escapeForTmux } from './utils.js';
|
|
8
|
+
import { getProjectPaths } from '../utils/project-namespace.js';
|
|
9
|
+
export class AgentSpawner {
|
|
10
|
+
activeWorkers = new Map();
|
|
11
|
+
tmuxSession;
|
|
12
|
+
agentsPath;
|
|
13
|
+
constructor(projectRoot, tmuxSession) {
|
|
14
|
+
const paths = getProjectPaths(projectRoot);
|
|
15
|
+
this.projectRoot = paths.projectRoot;
|
|
16
|
+
this.agentsPath = path.join(paths.teamDir, 'agents.json');
|
|
17
|
+
// Default session name based on project
|
|
18
|
+
this.tmuxSession = tmuxSession || 'relay-workers';
|
|
19
|
+
}
|
|
20
|
+
projectRoot;
|
|
21
|
+
/**
|
|
22
|
+
* Ensure the worker tmux session exists
|
|
23
|
+
*/
|
|
24
|
+
async ensureSession() {
|
|
25
|
+
try {
|
|
26
|
+
await execAsync(`tmux has-session -t ${this.tmuxSession} 2>/dev/null`);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Session doesn't exist, create it
|
|
30
|
+
await execAsync(`tmux new-session -d -s ${this.tmuxSession} -c "${this.projectRoot}"`);
|
|
31
|
+
console.log(`[spawner] Created session ${this.tmuxSession}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Spawn a new worker agent
|
|
36
|
+
*/
|
|
37
|
+
async spawn(request) {
|
|
38
|
+
const { name, cli, task, requestedBy } = request;
|
|
39
|
+
const debug = process.env.DEBUG_SPAWN === '1';
|
|
40
|
+
// Check if worker already exists
|
|
41
|
+
if (this.activeWorkers.has(name)) {
|
|
42
|
+
return {
|
|
43
|
+
success: false,
|
|
44
|
+
name,
|
|
45
|
+
error: `Worker ${name} already exists`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
await this.ensureSession();
|
|
50
|
+
if (debug)
|
|
51
|
+
console.log(`[spawner:debug] Session ${this.tmuxSession} ready`);
|
|
52
|
+
// Create new window for worker
|
|
53
|
+
const windowName = name;
|
|
54
|
+
const newWindowCmd = `tmux new-window -t ${this.tmuxSession} -n ${windowName} -c "${this.projectRoot}"`;
|
|
55
|
+
if (debug)
|
|
56
|
+
console.log(`[spawner:debug] Creating window: ${newWindowCmd}`);
|
|
57
|
+
await execAsync(newWindowCmd);
|
|
58
|
+
// Build the agent-relay command
|
|
59
|
+
// Unset TMUX to allow Claude to run inside tmux (it refuses to nest by default)
|
|
60
|
+
// Use full path to agent-relay to avoid PATH issues with nvm/shell init
|
|
61
|
+
let agentRelayPath;
|
|
62
|
+
try {
|
|
63
|
+
const { stdout } = await execAsync('which agent-relay');
|
|
64
|
+
agentRelayPath = stdout.trim();
|
|
65
|
+
if (debug)
|
|
66
|
+
console.log(`[spawner:debug] Found agent-relay at: ${agentRelayPath}`);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Fallback to npx if which fails
|
|
70
|
+
agentRelayPath = 'npx agent-relay';
|
|
71
|
+
if (debug)
|
|
72
|
+
console.log(`[spawner:debug] Using npx fallback`);
|
|
73
|
+
}
|
|
74
|
+
// Add --dangerously-skip-permissions for Claude agents to avoid permission dialogs
|
|
75
|
+
// Use -- to separate agent-relay options from the wrapped command
|
|
76
|
+
const isClaudeCli = cli.startsWith('claude');
|
|
77
|
+
const cliWithFlags = isClaudeCli ? `${cli} --dangerously-skip-permissions` : cli;
|
|
78
|
+
const cmd = `unset TMUX && ${agentRelayPath} -n ${name} -- ${cliWithFlags}`;
|
|
79
|
+
if (debug)
|
|
80
|
+
console.log(`[spawner:debug] Agent command: ${cmd}`);
|
|
81
|
+
// Send the command
|
|
82
|
+
const sendCmd = `tmux send-keys -t ${this.tmuxSession}:${windowName} '${cmd}' Enter`;
|
|
83
|
+
if (debug)
|
|
84
|
+
console.log(`[spawner:debug] Sending: ${sendCmd}`);
|
|
85
|
+
await execAsync(sendCmd);
|
|
86
|
+
// Wait for the agent to register with the daemon before injecting tasks
|
|
87
|
+
const registered = await this.waitForAgentRegistration(name, 30_000, 500);
|
|
88
|
+
if (!registered) {
|
|
89
|
+
const error = `Worker ${name} failed to register within 30s`;
|
|
90
|
+
console.error(`[spawner] ${error}`);
|
|
91
|
+
// Clean up the tmux window to avoid orphaned workers
|
|
92
|
+
await execAsync(`tmux kill-window -t ${this.tmuxSession}:${windowName}`).catch(() => { });
|
|
93
|
+
return {
|
|
94
|
+
success: false,
|
|
95
|
+
name,
|
|
96
|
+
error,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
// Inject the initial task if provided
|
|
100
|
+
if (task && task.trim()) {
|
|
101
|
+
const escapedTask = escapeForTmux(task);
|
|
102
|
+
if (debug)
|
|
103
|
+
console.log(`[spawner:debug] Injecting task: ${escapedTask.substring(0, 50)}...`);
|
|
104
|
+
await execAsync(`tmux send-keys -t ${this.tmuxSession}:${windowName} -l "${escapedTask}"`);
|
|
105
|
+
await sleep(100);
|
|
106
|
+
await execAsync(`tmux send-keys -t ${this.tmuxSession}:${windowName} Enter`);
|
|
107
|
+
}
|
|
108
|
+
// Track the worker
|
|
109
|
+
const workerInfo = {
|
|
110
|
+
name,
|
|
111
|
+
cli,
|
|
112
|
+
task,
|
|
113
|
+
spawnedBy: requestedBy,
|
|
114
|
+
spawnedAt: Date.now(),
|
|
115
|
+
window: `${this.tmuxSession}:${windowName}`,
|
|
116
|
+
};
|
|
117
|
+
this.activeWorkers.set(name, workerInfo);
|
|
118
|
+
console.log(`[spawner] Spawned ${name} (${cli}) for ${requestedBy}`);
|
|
119
|
+
return {
|
|
120
|
+
success: true,
|
|
121
|
+
name,
|
|
122
|
+
window: workerInfo.window,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
console.error(`[spawner] Failed to spawn ${name}:`, err.message);
|
|
127
|
+
if (debug)
|
|
128
|
+
console.error(`[spawner:debug] Full error:`, err);
|
|
129
|
+
return {
|
|
130
|
+
success: false,
|
|
131
|
+
name,
|
|
132
|
+
error: err.message,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Release (terminate) a worker
|
|
138
|
+
*/
|
|
139
|
+
async release(name) {
|
|
140
|
+
const worker = this.activeWorkers.get(name);
|
|
141
|
+
if (!worker) {
|
|
142
|
+
console.log(`[spawner] Worker ${name} not found`);
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
// Send exit command gracefully
|
|
147
|
+
await execAsync(`tmux send-keys -t ${worker.window} '/exit' Enter`).catch(() => { });
|
|
148
|
+
// Wait a bit for graceful shutdown
|
|
149
|
+
await sleep(2000);
|
|
150
|
+
// Kill the window
|
|
151
|
+
await execAsync(`tmux kill-window -t ${worker.window}`).catch(() => { });
|
|
152
|
+
this.activeWorkers.delete(name);
|
|
153
|
+
console.log(`[spawner] Released ${name}`);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
console.error(`[spawner] Failed to release ${name}:`, err.message);
|
|
158
|
+
// Still remove from tracking
|
|
159
|
+
this.activeWorkers.delete(name);
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Release all workers
|
|
165
|
+
*/
|
|
166
|
+
async releaseAll() {
|
|
167
|
+
const workers = Array.from(this.activeWorkers.keys());
|
|
168
|
+
for (const name of workers) {
|
|
169
|
+
await this.release(name);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Get all active workers
|
|
174
|
+
*/
|
|
175
|
+
getActiveWorkers() {
|
|
176
|
+
return Array.from(this.activeWorkers.values());
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Check if a worker exists
|
|
180
|
+
*/
|
|
181
|
+
hasWorker(name) {
|
|
182
|
+
return this.activeWorkers.has(name);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Get worker info
|
|
186
|
+
*/
|
|
187
|
+
getWorker(name) {
|
|
188
|
+
return this.activeWorkers.get(name);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Wait for an agent to appear in the registry (agents.json)
|
|
192
|
+
*/
|
|
193
|
+
async waitForAgentRegistration(name, timeoutMs = 30_000, pollIntervalMs = 500) {
|
|
194
|
+
const deadline = Date.now() + timeoutMs;
|
|
195
|
+
while (Date.now() < deadline) {
|
|
196
|
+
if (this.isAgentRegistered(name)) {
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
await sleep(pollIntervalMs);
|
|
200
|
+
}
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
isAgentRegistered(name) {
|
|
204
|
+
if (!this.agentsPath)
|
|
205
|
+
return false;
|
|
206
|
+
if (!fs.existsSync(this.agentsPath))
|
|
207
|
+
return false;
|
|
208
|
+
try {
|
|
209
|
+
const raw = JSON.parse(fs.readFileSync(this.agentsPath, 'utf-8'));
|
|
210
|
+
const agents = Array.isArray(raw?.agents)
|
|
211
|
+
? raw.agents
|
|
212
|
+
: raw?.agents && typeof raw.agents === 'object'
|
|
213
|
+
? Object.values(raw.agents)
|
|
214
|
+
: [];
|
|
215
|
+
return agents.some((a) => a?.name === name);
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
console.error('[spawner] Failed to read agents registry:', err.message);
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=spawner.js.map
|