agent-relay 1.0.8 → 1.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/README.md +129 -8
  2. package/bin/.gitkeep +0 -0
  3. package/bin/tmux +0 -0
  4. package/dist/bridge/config.d.ts +41 -0
  5. package/dist/bridge/config.d.ts.map +1 -0
  6. package/dist/bridge/config.js +143 -0
  7. package/dist/bridge/config.js.map +1 -0
  8. package/dist/bridge/index.d.ts +10 -0
  9. package/dist/bridge/index.d.ts.map +1 -0
  10. package/dist/bridge/index.js +10 -0
  11. package/dist/bridge/index.js.map +1 -0
  12. package/dist/bridge/multi-project-client.d.ts +99 -0
  13. package/dist/bridge/multi-project-client.d.ts.map +1 -0
  14. package/dist/bridge/multi-project-client.js +386 -0
  15. package/dist/bridge/multi-project-client.js.map +1 -0
  16. package/dist/bridge/spawner.d.ts +47 -0
  17. package/dist/bridge/spawner.d.ts.map +1 -0
  18. package/dist/bridge/spawner.js +227 -0
  19. package/dist/bridge/spawner.js.map +1 -0
  20. package/dist/bridge/types.d.ts +55 -0
  21. package/dist/bridge/types.d.ts.map +1 -0
  22. package/dist/bridge/types.js +6 -0
  23. package/dist/bridge/types.js.map +1 -0
  24. package/dist/bridge/utils.d.ts +30 -0
  25. package/dist/bridge/utils.d.ts.map +1 -0
  26. package/dist/bridge/utils.js +54 -0
  27. package/dist/bridge/utils.js.map +1 -0
  28. package/dist/cli/index.js +599 -8
  29. package/dist/cli/index.js.map +1 -1
  30. package/dist/daemon/agent-registry.d.ts.map +1 -1
  31. package/dist/daemon/agent-registry.js +6 -1
  32. package/dist/daemon/agent-registry.js.map +1 -1
  33. package/dist/daemon/connection.d.ts +22 -0
  34. package/dist/daemon/connection.d.ts.map +1 -1
  35. package/dist/daemon/connection.js +59 -13
  36. package/dist/daemon/connection.js.map +1 -1
  37. package/dist/daemon/router.d.ts +27 -0
  38. package/dist/daemon/router.d.ts.map +1 -1
  39. package/dist/daemon/router.js +108 -3
  40. package/dist/daemon/router.js.map +1 -1
  41. package/dist/daemon/server.d.ts +8 -0
  42. package/dist/daemon/server.d.ts.map +1 -1
  43. package/dist/daemon/server.js +95 -23
  44. package/dist/daemon/server.js.map +1 -1
  45. package/dist/dashboard/metrics.d.ts +105 -0
  46. package/dist/dashboard/metrics.d.ts.map +1 -0
  47. package/dist/dashboard/metrics.js +192 -0
  48. package/dist/dashboard/metrics.js.map +1 -0
  49. package/dist/dashboard/needs-attention.d.ts +24 -0
  50. package/dist/dashboard/needs-attention.d.ts.map +1 -0
  51. package/dist/dashboard/needs-attention.js +78 -0
  52. package/dist/dashboard/needs-attention.js.map +1 -0
  53. package/dist/dashboard/public/bridge.html +1272 -0
  54. package/dist/dashboard/public/index.html +2017 -879
  55. package/dist/dashboard/public/js/app.js +184 -0
  56. package/dist/dashboard/public/js/app.js.map +7 -0
  57. package/dist/dashboard/public/metrics.html +999 -0
  58. package/dist/dashboard/server.d.ts +13 -0
  59. package/dist/dashboard/server.d.ts.map +1 -1
  60. package/dist/dashboard/server.js +594 -13
  61. package/dist/dashboard/server.js.map +1 -1
  62. package/dist/dashboard/start.js +1 -1
  63. package/dist/dashboard/start.js.map +1 -1
  64. package/dist/dashboard-v2/index.d.ts +10 -0
  65. package/dist/dashboard-v2/index.d.ts.map +1 -0
  66. package/dist/dashboard-v2/index.js +54 -0
  67. package/dist/dashboard-v2/index.js.map +1 -0
  68. package/dist/dashboard-v2/lib/api.d.ts +95 -0
  69. package/dist/dashboard-v2/lib/api.d.ts.map +1 -0
  70. package/dist/dashboard-v2/lib/api.js +270 -0
  71. package/dist/dashboard-v2/lib/api.js.map +1 -0
  72. package/dist/dashboard-v2/lib/colors.d.ts +61 -0
  73. package/dist/dashboard-v2/lib/colors.d.ts.map +1 -0
  74. package/dist/dashboard-v2/lib/colors.js +198 -0
  75. package/dist/dashboard-v2/lib/colors.js.map +1 -0
  76. package/dist/dashboard-v2/lib/hierarchy.d.ts +74 -0
  77. package/dist/dashboard-v2/lib/hierarchy.d.ts.map +1 -0
  78. package/dist/dashboard-v2/lib/hierarchy.js +196 -0
  79. package/dist/dashboard-v2/lib/hierarchy.js.map +1 -0
  80. package/dist/dashboard-v2/types/index.d.ts +154 -0
  81. package/dist/dashboard-v2/types/index.d.ts.map +1 -0
  82. package/dist/dashboard-v2/types/index.js +6 -0
  83. package/dist/dashboard-v2/types/index.js.map +1 -0
  84. package/dist/storage/adapter.d.ts +21 -1
  85. package/dist/storage/adapter.d.ts.map +1 -1
  86. package/dist/storage/adapter.js +36 -0
  87. package/dist/storage/adapter.js.map +1 -1
  88. package/dist/storage/sqlite-adapter.d.ts +34 -0
  89. package/dist/storage/sqlite-adapter.d.ts.map +1 -1
  90. package/dist/storage/sqlite-adapter.js +253 -12
  91. package/dist/storage/sqlite-adapter.js.map +1 -1
  92. package/dist/utils/agent-config.d.ts +45 -0
  93. package/dist/utils/agent-config.d.ts.map +1 -0
  94. package/dist/utils/agent-config.js +118 -0
  95. package/dist/utils/agent-config.js.map +1 -0
  96. package/dist/utils/tmux-resolver.d.ts +55 -0
  97. package/dist/utils/tmux-resolver.d.ts.map +1 -0
  98. package/dist/utils/tmux-resolver.js +175 -0
  99. package/dist/utils/tmux-resolver.js.map +1 -0
  100. package/dist/wrapper/client.d.ts +8 -0
  101. package/dist/wrapper/client.d.ts.map +1 -1
  102. package/dist/wrapper/client.js +26 -0
  103. package/dist/wrapper/client.js.map +1 -1
  104. package/dist/wrapper/parser.d.ts +17 -0
  105. package/dist/wrapper/parser.d.ts.map +1 -1
  106. package/dist/wrapper/parser.js +334 -10
  107. package/dist/wrapper/parser.js.map +1 -1
  108. package/dist/wrapper/tmux-wrapper.d.ts +38 -2
  109. package/dist/wrapper/tmux-wrapper.d.ts.map +1 -1
  110. package/dist/wrapper/tmux-wrapper.js +196 -32
  111. package/dist/wrapper/tmux-wrapper.js.map +1 -1
  112. package/docs/AGENTS.md +105 -0
  113. package/docs/ARCHITECTURE_DECISIONS.md +175 -0
  114. package/docs/COMPETITIVE_ANALYSIS.md +897 -0
  115. package/docs/DESIGN_BRIDGE_STAFFING.md +878 -0
  116. package/docs/MONETIZATION.md +1679 -0
  117. package/docs/agent-relay-snippet.md +61 -0
  118. package/docs/dashboard-v2-plan.md +179 -0
  119. package/package.json +8 -3
  120. package/scripts/dev/PUBLIC_RELEASE_PLAN.md +88 -0
  121. package/scripts/dev/dev-team-setup.sh +431 -0
  122. package/scripts/e2e-test.sh +119 -0
  123. package/scripts/games/game-protocol.md +79 -0
  124. package/scripts/games/hearts-setup.sh +264 -0
  125. package/scripts/postinstall.js +225 -0
  126. package/scripts/tictactoe-setup.sh +181 -0
@@ -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,47 @@
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
+ private projectRoot;
11
+ private tmuxPath;
12
+ constructor(projectRoot: string, tmuxSession?: string);
13
+ /**
14
+ * Ensure the worker tmux session exists
15
+ */
16
+ ensureSession(): Promise<void>;
17
+ /**
18
+ * Spawn a new worker agent
19
+ */
20
+ spawn(request: SpawnRequest): Promise<SpawnResult>;
21
+ /**
22
+ * Release (terminate) a worker
23
+ */
24
+ release(name: string): Promise<boolean>;
25
+ /**
26
+ * Release all workers
27
+ */
28
+ releaseAll(): Promise<void>;
29
+ /**
30
+ * Get all active workers
31
+ */
32
+ getActiveWorkers(): WorkerInfo[];
33
+ /**
34
+ * Check if a worker exists
35
+ */
36
+ hasWorker(name: string): boolean;
37
+ /**
38
+ * Get worker info
39
+ */
40
+ getWorker(name: string): WorkerInfo | undefined;
41
+ /**
42
+ * Wait for an agent to appear in the registry (agents.json)
43
+ */
44
+ private waitForAgentRegistration;
45
+ private isAgentRegistered;
46
+ }
47
+ //# 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;AAOH,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;IAC3B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAS;gBAGvB,WAAW,EAAE,MAAM,EACnB,WAAW,CAAC,EAAE,MAAM;IAatB;;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,227 @@
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
+ import { getTmuxPath } from '../utils/tmux-resolver.js';
10
+ export class AgentSpawner {
11
+ activeWorkers = new Map();
12
+ tmuxSession;
13
+ agentsPath;
14
+ projectRoot;
15
+ tmuxPath; // Resolved path to tmux binary
16
+ constructor(projectRoot, tmuxSession) {
17
+ const paths = getProjectPaths(projectRoot);
18
+ this.projectRoot = paths.projectRoot;
19
+ this.agentsPath = path.join(paths.teamDir, 'agents.json');
20
+ // Resolve tmux path (will throw TmuxNotFoundError if tmux unavailable)
21
+ this.tmuxPath = getTmuxPath();
22
+ // Default session name based on project
23
+ this.tmuxSession = tmuxSession || 'relay-workers';
24
+ }
25
+ /**
26
+ * Ensure the worker tmux session exists
27
+ */
28
+ async ensureSession() {
29
+ try {
30
+ await execAsync(`"${this.tmuxPath}" has-session -t ${this.tmuxSession} 2>/dev/null`);
31
+ }
32
+ catch {
33
+ // Session doesn't exist, create it
34
+ await execAsync(`"${this.tmuxPath}" new-session -d -s ${this.tmuxSession} -c "${this.projectRoot}"`);
35
+ console.log(`[spawner] Created session ${this.tmuxSession}`);
36
+ }
37
+ }
38
+ /**
39
+ * Spawn a new worker agent
40
+ */
41
+ async spawn(request) {
42
+ const { name, cli, task, requestedBy } = request;
43
+ const debug = process.env.DEBUG_SPAWN === '1';
44
+ // Check if worker already exists
45
+ if (this.activeWorkers.has(name)) {
46
+ return {
47
+ success: false,
48
+ name,
49
+ error: `Worker ${name} already exists`,
50
+ };
51
+ }
52
+ try {
53
+ await this.ensureSession();
54
+ if (debug)
55
+ console.log(`[spawner:debug] Session ${this.tmuxSession} ready`);
56
+ // Create new window for worker
57
+ const windowName = name;
58
+ const newWindowCmd = `"${this.tmuxPath}" new-window -t ${this.tmuxSession} -n ${windowName} -c "${this.projectRoot}"`;
59
+ if (debug)
60
+ console.log(`[spawner:debug] Creating window: ${newWindowCmd}`);
61
+ await execAsync(newWindowCmd);
62
+ // Build the agent-relay command
63
+ // Unset TMUX to allow Claude to run inside tmux (it refuses to nest by default)
64
+ // Use full path to agent-relay to avoid PATH issues with nvm/shell init
65
+ let agentRelayPath;
66
+ try {
67
+ const { stdout } = await execAsync('which agent-relay');
68
+ agentRelayPath = stdout.trim();
69
+ if (debug)
70
+ console.log(`[spawner:debug] Found agent-relay at: ${agentRelayPath}`);
71
+ }
72
+ catch {
73
+ // Fallback to npx if which fails
74
+ agentRelayPath = 'npx agent-relay';
75
+ if (debug)
76
+ console.log(`[spawner:debug] Using npx fallback`);
77
+ }
78
+ // Add --dangerously-skip-permissions for Claude agents to avoid permission dialogs
79
+ // Use -- to separate agent-relay options from the wrapped command
80
+ const isClaudeCli = cli.startsWith('claude');
81
+ const cliWithFlags = isClaudeCli ? `${cli} --dangerously-skip-permissions` : cli;
82
+ const cmd = `unset TMUX && ${agentRelayPath} -n ${name} -- ${cliWithFlags}`;
83
+ if (debug)
84
+ console.log(`[spawner:debug] Agent command: ${cmd}`);
85
+ // Send the command
86
+ const sendCmd = `"${this.tmuxPath}" send-keys -t ${this.tmuxSession}:${windowName} '${cmd}' Enter`;
87
+ if (debug)
88
+ console.log(`[spawner:debug] Sending: ${sendCmd}`);
89
+ await execAsync(sendCmd);
90
+ // Wait for the agent to register with the daemon before injecting tasks
91
+ const registered = await this.waitForAgentRegistration(name, 30_000, 500);
92
+ if (!registered) {
93
+ const error = `Worker ${name} failed to register within 30s`;
94
+ console.error(`[spawner] ${error}`);
95
+ // Clean up the tmux window to avoid orphaned workers
96
+ await execAsync(`"${this.tmuxPath}" kill-window -t ${this.tmuxSession}:${windowName}`).catch(() => { });
97
+ return {
98
+ success: false,
99
+ name,
100
+ error,
101
+ };
102
+ }
103
+ // Inject the initial task if provided
104
+ if (task && task.trim()) {
105
+ const escapedTask = escapeForTmux(task);
106
+ if (debug)
107
+ console.log(`[spawner:debug] Injecting task: ${escapedTask.substring(0, 50)}...`);
108
+ await execAsync(`"${this.tmuxPath}" send-keys -t ${this.tmuxSession}:${windowName} -l "${escapedTask}"`);
109
+ await sleep(100);
110
+ await execAsync(`"${this.tmuxPath}" send-keys -t ${this.tmuxSession}:${windowName} Enter`);
111
+ }
112
+ // Track the worker
113
+ const workerInfo = {
114
+ name,
115
+ cli,
116
+ task,
117
+ spawnedBy: requestedBy,
118
+ spawnedAt: Date.now(),
119
+ window: `${this.tmuxSession}:${windowName}`,
120
+ };
121
+ this.activeWorkers.set(name, workerInfo);
122
+ console.log(`[spawner] Spawned ${name} (${cli}) for ${requestedBy}`);
123
+ return {
124
+ success: true,
125
+ name,
126
+ window: workerInfo.window,
127
+ };
128
+ }
129
+ catch (err) {
130
+ console.error(`[spawner] Failed to spawn ${name}:`, err.message);
131
+ if (debug)
132
+ console.error(`[spawner:debug] Full error:`, err);
133
+ return {
134
+ success: false,
135
+ name,
136
+ error: err.message,
137
+ };
138
+ }
139
+ }
140
+ /**
141
+ * Release (terminate) a worker
142
+ */
143
+ async release(name) {
144
+ const worker = this.activeWorkers.get(name);
145
+ if (!worker) {
146
+ console.log(`[spawner] Worker ${name} not found`);
147
+ return false;
148
+ }
149
+ try {
150
+ // Send exit command gracefully
151
+ await execAsync(`"${this.tmuxPath}" send-keys -t ${worker.window} '/exit' Enter`).catch(() => { });
152
+ // Wait a bit for graceful shutdown
153
+ await sleep(2000);
154
+ // Kill the window
155
+ await execAsync(`"${this.tmuxPath}" kill-window -t ${worker.window}`).catch(() => { });
156
+ this.activeWorkers.delete(name);
157
+ console.log(`[spawner] Released ${name}`);
158
+ return true;
159
+ }
160
+ catch (err) {
161
+ console.error(`[spawner] Failed to release ${name}:`, err.message);
162
+ // Still remove from tracking
163
+ this.activeWorkers.delete(name);
164
+ return false;
165
+ }
166
+ }
167
+ /**
168
+ * Release all workers
169
+ */
170
+ async releaseAll() {
171
+ const workers = Array.from(this.activeWorkers.keys());
172
+ for (const name of workers) {
173
+ await this.release(name);
174
+ }
175
+ }
176
+ /**
177
+ * Get all active workers
178
+ */
179
+ getActiveWorkers() {
180
+ return Array.from(this.activeWorkers.values());
181
+ }
182
+ /**
183
+ * Check if a worker exists
184
+ */
185
+ hasWorker(name) {
186
+ return this.activeWorkers.has(name);
187
+ }
188
+ /**
189
+ * Get worker info
190
+ */
191
+ getWorker(name) {
192
+ return this.activeWorkers.get(name);
193
+ }
194
+ /**
195
+ * Wait for an agent to appear in the registry (agents.json)
196
+ */
197
+ async waitForAgentRegistration(name, timeoutMs = 30_000, pollIntervalMs = 500) {
198
+ const deadline = Date.now() + timeoutMs;
199
+ while (Date.now() < deadline) {
200
+ if (this.isAgentRegistered(name)) {
201
+ return true;
202
+ }
203
+ await sleep(pollIntervalMs);
204
+ }
205
+ return false;
206
+ }
207
+ isAgentRegistered(name) {
208
+ if (!this.agentsPath)
209
+ return false;
210
+ if (!fs.existsSync(this.agentsPath))
211
+ return false;
212
+ try {
213
+ const raw = JSON.parse(fs.readFileSync(this.agentsPath, 'utf-8'));
214
+ const agents = Array.isArray(raw?.agents)
215
+ ? raw.agents
216
+ : raw?.agents && typeof raw.agents === 'object'
217
+ ? Object.values(raw.agents)
218
+ : [];
219
+ return agents.some((a) => a?.name === name);
220
+ }
221
+ catch (err) {
222
+ console.error('[spawner] Failed to read agents registry:', err.message);
223
+ return false;
224
+ }
225
+ }
226
+ }
227
+ //# sourceMappingURL=spawner.js.map