@agent-relay/bridge 0.1.0
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/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/multi-project-client.d.ts +99 -0
- package/dist/multi-project-client.d.ts.map +1 -0
- package/dist/multi-project-client.js +389 -0
- package/dist/multi-project-client.js.map +1 -0
- package/dist/shadow-cli.d.ts +17 -0
- package/dist/shadow-cli.d.ts.map +1 -0
- package/dist/shadow-cli.js +75 -0
- package/dist/shadow-cli.js.map +1 -0
- package/dist/spawner.d.ts +210 -0
- package/dist/spawner.d.ts.map +1 -0
- package/dist/spawner.js +1276 -0
- package/dist/spawner.js.map +1 -0
- package/dist/types.d.ts +131 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +15 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +60 -0
- package/dist/utils.js.map +1 -0
- package/package.json +40 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type { ProjectConfig } from '@agent-relay/config/bridge-config';
|
|
2
|
+
export * from './types.js';
|
|
3
|
+
export * from './multi-project-client.js';
|
|
4
|
+
export * from './utils.js';
|
|
5
|
+
export { escapeForShell, escapeForTmux } from './utils.js';
|
|
6
|
+
export { selectShadowCli, type ShadowCli, type ShadowMode, type ShadowCliSelection, } from './shadow-cli.js';
|
|
7
|
+
export { AgentSpawner, readWorkersMetadata, getWorkerLogsDir, type AgentSpawnerOptions, type CloudPersistenceHandler, type OnAgentDeathCallback, } from './spawner.js';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AACvE,cAAc,YAAY,CAAC;AAC3B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAG3D,OAAO,EACL,eAAe,EACf,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,kBAAkB,GACxB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,KAAK,mBAAmB,EACxB,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,GAC1B,MAAM,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export * from './multi-project-client.js';
|
|
3
|
+
export * from './utils.js';
|
|
4
|
+
export { escapeForShell, escapeForTmux } from './utils.js';
|
|
5
|
+
// Shadow CLI selection
|
|
6
|
+
export { selectShadowCli, } from './shadow-cli.js';
|
|
7
|
+
// Agent spawner
|
|
8
|
+
export { AgentSpawner, readWorkersMetadata, getWorkerLogsDir, } from './spawner.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,cAAc,YAAY,CAAC;AAC3B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3D,uBAAuB;AACvB,OAAO,EACL,eAAe,GAIhB,MAAM,iBAAiB,CAAC;AAEzB,gBAAgB;AAChB,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,GAIjB,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MultiProjectClient
|
|
3
|
+
* Connects to multiple project daemons simultaneously for cross-project orchestration.
|
|
4
|
+
*/
|
|
5
|
+
import { type SendPayload } from '@agent-relay/protocol/types';
|
|
6
|
+
import type { ProjectConfig } from './types.js';
|
|
7
|
+
interface MultiProjectClientOptions {
|
|
8
|
+
/** Agent name to register as (default: '__BridgeClient'). Must be unique per daemon. */
|
|
9
|
+
agentName?: string;
|
|
10
|
+
/** Enable automatic reconnection on disconnect (default: true) */
|
|
11
|
+
reconnect?: boolean;
|
|
12
|
+
/** Initial reconnection delay in ms (default: 1000) */
|
|
13
|
+
reconnectDelay?: number;
|
|
14
|
+
/** Maximum reconnection delay in ms (default: 30000) */
|
|
15
|
+
maxReconnectDelay?: number;
|
|
16
|
+
/** Maximum reconnection attempts before giving up (default: Infinity) */
|
|
17
|
+
maxReconnectAttempts?: number;
|
|
18
|
+
}
|
|
19
|
+
export declare class MultiProjectClient {
|
|
20
|
+
private projects;
|
|
21
|
+
private connections;
|
|
22
|
+
private leads;
|
|
23
|
+
private options;
|
|
24
|
+
private shuttingDown;
|
|
25
|
+
/** Handler for incoming messages */
|
|
26
|
+
onMessage?: (projectId: string, from: string, payload: SendPayload, messageId: string) => void;
|
|
27
|
+
/** Handler for connection state changes */
|
|
28
|
+
onProjectStateChange?: (projectId: string, connected: boolean) => void;
|
|
29
|
+
constructor(projects: ProjectConfig[], options?: MultiProjectClientOptions);
|
|
30
|
+
/**
|
|
31
|
+
* Connect to all project daemons
|
|
32
|
+
*/
|
|
33
|
+
connect(): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Connect to a single project daemon
|
|
36
|
+
*/
|
|
37
|
+
private connectToProject;
|
|
38
|
+
/**
|
|
39
|
+
* Send HELLO to a project daemon
|
|
40
|
+
*/
|
|
41
|
+
private sendHello;
|
|
42
|
+
/**
|
|
43
|
+
* Handle incoming data from a project connection
|
|
44
|
+
*/
|
|
45
|
+
private handleData;
|
|
46
|
+
/**
|
|
47
|
+
* Process a frame from a project daemon
|
|
48
|
+
*/
|
|
49
|
+
private processFrame;
|
|
50
|
+
/**
|
|
51
|
+
* Handle delivered message from a project
|
|
52
|
+
*/
|
|
53
|
+
private handleDeliver;
|
|
54
|
+
/**
|
|
55
|
+
* Send envelope to a project daemon
|
|
56
|
+
*/
|
|
57
|
+
private send;
|
|
58
|
+
/**
|
|
59
|
+
* Send message to a project
|
|
60
|
+
* @param projectId - Target project ID
|
|
61
|
+
* @param to - Agent name within the project (or '*' for broadcast, 'lead' for project lead)
|
|
62
|
+
* @param body - Message body
|
|
63
|
+
*/
|
|
64
|
+
sendToProject(projectId: string, to: string, body: string): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Broadcast to all leads
|
|
67
|
+
*/
|
|
68
|
+
broadcastToLeads(body: string): void;
|
|
69
|
+
/**
|
|
70
|
+
* Broadcast to all agents in all projects
|
|
71
|
+
*/
|
|
72
|
+
broadcastAll(body: string): void;
|
|
73
|
+
/**
|
|
74
|
+
* Register a lead for a project
|
|
75
|
+
*/
|
|
76
|
+
registerLead(projectId: string, leadName: string): void;
|
|
77
|
+
/**
|
|
78
|
+
* Get all connected projects
|
|
79
|
+
*/
|
|
80
|
+
getConnectedProjects(): string[];
|
|
81
|
+
/**
|
|
82
|
+
* Get project connection info
|
|
83
|
+
*/
|
|
84
|
+
getProject(projectId: string): ProjectConfig | undefined;
|
|
85
|
+
/**
|
|
86
|
+
* Schedule a reconnection attempt with exponential backoff
|
|
87
|
+
*/
|
|
88
|
+
private scheduleReconnect;
|
|
89
|
+
/**
|
|
90
|
+
* Attempt to reconnect to a project daemon
|
|
91
|
+
*/
|
|
92
|
+
private attemptReconnect;
|
|
93
|
+
/**
|
|
94
|
+
* Disconnect from all projects
|
|
95
|
+
*/
|
|
96
|
+
disconnect(): void;
|
|
97
|
+
}
|
|
98
|
+
export {};
|
|
99
|
+
//# sourceMappingURL=multi-project-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-project-client.d.ts","sourceRoot":"","sources":["../src/multi-project-client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAGL,KAAK,WAAW,EAGjB,MAAM,6BAA6B,CAAC;AAErC,OAAO,KAAK,EAAE,aAAa,EAAY,MAAM,YAAY,CAAC;AAa1D,UAAU,yBAAyB;IACjC,wFAAwF;IACxF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,uDAAuD;IACvD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,wDAAwD;IACxD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,yEAAyE;IACzE,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,qBAAa,kBAAkB;IAYjB,OAAO,CAAC,QAAQ;IAX5B,OAAO,CAAC,WAAW,CAA6C;IAChE,OAAO,CAAC,KAAK,CAAoC;IACjD,OAAO,CAAC,OAAO,CAAiF;IAChG,OAAO,CAAC,YAAY,CAAS;IAE7B,oCAAoC;IACpC,SAAS,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAE/F,2CAA2C;IAC3C,oBAAoB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;gBAEnD,QAAQ,EAAE,aAAa,EAAE,EAAE,OAAO,GAAE,yBAA8B;IAUtF;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAK9B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAiExB;;OAEG;IACH,OAAO,CAAC,SAAS;IAqBjB;;OAEG;IACH,OAAO,CAAC,UAAU;IAWlB;;OAEG;IACH,OAAO,CAAC,YAAY;IAwBpB;;OAEG;IACH,OAAO,CAAC,aAAa;IAmBrB;;OAEG;IACH,OAAO,CAAC,IAAI;IAaZ;;;;;OAKG;IACH,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO;IAkCnE;;OAEG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAMpC;;OAEG;IACH,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAmBhC;;OAEG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAQvD;;OAEG;IACH,oBAAoB,IAAI,MAAM,EAAE;IAMhC;;OAEG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAIxD;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAyBzB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAoExB;;OAEG;IACH,UAAU,IAAI,IAAI;CAyBnB"}
|
|
@@ -0,0 +1,389 @@
|
|
|
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 { generateId } from './utils.js';
|
|
8
|
+
import { PROTOCOL_VERSION, } from '@agent-relay/protocol/types';
|
|
9
|
+
import { encodeFrameLegacy as encodeFrame, FrameParser } from '@agent-relay/protocol/framing';
|
|
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 parser = new FrameParser();
|
|
53
|
+
parser.setLegacyMode(true); // Use 4-byte header for backwards compatibility
|
|
54
|
+
const conn = {
|
|
55
|
+
config: project,
|
|
56
|
+
socket,
|
|
57
|
+
parser,
|
|
58
|
+
ready: false,
|
|
59
|
+
};
|
|
60
|
+
socket.on('data', (data) => this.handleData(conn, data));
|
|
61
|
+
socket.on('close', () => {
|
|
62
|
+
const wasReady = conn.ready;
|
|
63
|
+
conn.ready = false;
|
|
64
|
+
this.onProjectStateChange?.(project.id, false);
|
|
65
|
+
// Attempt reconnection if enabled and not shutting down
|
|
66
|
+
if (wasReady && this.options.reconnect && !this.shuttingDown) {
|
|
67
|
+
this.scheduleReconnect(conn);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
socket.on('error', (err) => {
|
|
71
|
+
console.error(`[bridge] Connection error for ${project.id}:`, err.message);
|
|
72
|
+
if (!conn.ready) {
|
|
73
|
+
reject(err);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
this.connections.set(project.id, conn);
|
|
77
|
+
// Wait for ready
|
|
78
|
+
const checkReady = setInterval(() => {
|
|
79
|
+
if (conn.ready) {
|
|
80
|
+
clearInterval(checkReady);
|
|
81
|
+
clearTimeout(timeout);
|
|
82
|
+
resolve();
|
|
83
|
+
}
|
|
84
|
+
}, 10);
|
|
85
|
+
const timeout = setTimeout(() => {
|
|
86
|
+
if (!conn.ready) {
|
|
87
|
+
clearInterval(checkReady);
|
|
88
|
+
socket.destroy();
|
|
89
|
+
reject(new Error(`Connection timeout for ${project.id}`));
|
|
90
|
+
}
|
|
91
|
+
}, 5000);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Send HELLO to a project daemon
|
|
96
|
+
*/
|
|
97
|
+
sendHello(conn) {
|
|
98
|
+
const hello = {
|
|
99
|
+
v: PROTOCOL_VERSION,
|
|
100
|
+
type: 'HELLO',
|
|
101
|
+
id: generateId(),
|
|
102
|
+
ts: Date.now(),
|
|
103
|
+
payload: {
|
|
104
|
+
agent: this.options.agentName,
|
|
105
|
+
cli: 'bridge',
|
|
106
|
+
capabilities: {
|
|
107
|
+
ack: true,
|
|
108
|
+
resume: false,
|
|
109
|
+
max_inflight: 256,
|
|
110
|
+
supports_topics: true,
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
this.send(conn, hello);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Handle incoming data from a project connection
|
|
118
|
+
*/
|
|
119
|
+
handleData(conn, data) {
|
|
120
|
+
try {
|
|
121
|
+
const frames = conn.parser.push(data);
|
|
122
|
+
for (const frame of frames) {
|
|
123
|
+
this.processFrame(conn, frame);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
console.error(`[bridge] Parse error for ${conn.config.id}:`, err);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Process a frame from a project daemon
|
|
132
|
+
*/
|
|
133
|
+
processFrame(conn, envelope) {
|
|
134
|
+
switch (envelope.type) {
|
|
135
|
+
case 'WELCOME':
|
|
136
|
+
conn.ready = true;
|
|
137
|
+
console.log(`[bridge] Connected to ${conn.config.id}`);
|
|
138
|
+
this.onProjectStateChange?.(conn.config.id, true);
|
|
139
|
+
break;
|
|
140
|
+
case 'DELIVER':
|
|
141
|
+
this.handleDeliver(conn, envelope);
|
|
142
|
+
break;
|
|
143
|
+
case 'PING':
|
|
144
|
+
this.send(conn, {
|
|
145
|
+
v: PROTOCOL_VERSION,
|
|
146
|
+
type: 'PONG',
|
|
147
|
+
id: generateId(),
|
|
148
|
+
ts: Date.now(),
|
|
149
|
+
payload: envelope.payload ?? {},
|
|
150
|
+
});
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Handle delivered message from a project
|
|
156
|
+
*/
|
|
157
|
+
handleDeliver(conn, envelope) {
|
|
158
|
+
// Send ACK
|
|
159
|
+
this.send(conn, {
|
|
160
|
+
v: PROTOCOL_VERSION,
|
|
161
|
+
type: 'ACK',
|
|
162
|
+
id: generateId(),
|
|
163
|
+
ts: Date.now(),
|
|
164
|
+
payload: {
|
|
165
|
+
ack_id: envelope.id,
|
|
166
|
+
seq: envelope.delivery.seq,
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
// Notify handler
|
|
170
|
+
if (this.onMessage && envelope.from) {
|
|
171
|
+
this.onMessage(conn.config.id, envelope.from, envelope.payload, envelope.id);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Send envelope to a project daemon
|
|
176
|
+
*/
|
|
177
|
+
send(conn, envelope) {
|
|
178
|
+
if (!conn.socket)
|
|
179
|
+
return false;
|
|
180
|
+
try {
|
|
181
|
+
const frame = encodeFrame(envelope);
|
|
182
|
+
conn.socket.write(frame);
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
console.error(`[bridge] Send error for ${conn.config.id}:`, err);
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Send message to a project
|
|
192
|
+
* @param projectId - Target project ID
|
|
193
|
+
* @param to - Agent name within the project (or '*' for broadcast, 'lead' for project lead)
|
|
194
|
+
* @param body - Message body
|
|
195
|
+
*/
|
|
196
|
+
sendToProject(projectId, to, body) {
|
|
197
|
+
const conn = this.connections.get(projectId);
|
|
198
|
+
if (!conn?.ready) {
|
|
199
|
+
console.error(`[bridge] Cannot send to ${projectId}: not connected`);
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
// Resolve 'lead' to actual lead name
|
|
203
|
+
let targetAgent = to;
|
|
204
|
+
if (to === 'lead') {
|
|
205
|
+
const lead = this.leads.get(projectId);
|
|
206
|
+
if (lead) {
|
|
207
|
+
targetAgent = lead.name;
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
// Fallback to configured lead name
|
|
211
|
+
targetAgent = conn.config.leadName;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const envelope = {
|
|
215
|
+
v: PROTOCOL_VERSION,
|
|
216
|
+
type: 'SEND',
|
|
217
|
+
id: generateId(),
|
|
218
|
+
ts: Date.now(),
|
|
219
|
+
to: targetAgent,
|
|
220
|
+
payload: {
|
|
221
|
+
kind: 'message',
|
|
222
|
+
body,
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
return this.send(conn, envelope);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Broadcast to all leads
|
|
229
|
+
*/
|
|
230
|
+
broadcastToLeads(body) {
|
|
231
|
+
for (const [projectId] of this.connections) {
|
|
232
|
+
this.sendToProject(projectId, 'lead', body);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Broadcast to all agents in all projects
|
|
237
|
+
*/
|
|
238
|
+
broadcastAll(body) {
|
|
239
|
+
for (const [_projectId, conn] of this.connections) {
|
|
240
|
+
if (conn.ready) {
|
|
241
|
+
const envelope = {
|
|
242
|
+
v: PROTOCOL_VERSION,
|
|
243
|
+
type: 'SEND',
|
|
244
|
+
id: generateId(),
|
|
245
|
+
ts: Date.now(),
|
|
246
|
+
to: '*',
|
|
247
|
+
payload: {
|
|
248
|
+
kind: 'message',
|
|
249
|
+
body,
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
this.send(conn, envelope);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Register a lead for a project
|
|
258
|
+
*/
|
|
259
|
+
registerLead(projectId, leadName) {
|
|
260
|
+
this.leads.set(projectId, {
|
|
261
|
+
name: leadName,
|
|
262
|
+
projectId,
|
|
263
|
+
connected: true,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Get all connected projects
|
|
268
|
+
*/
|
|
269
|
+
getConnectedProjects() {
|
|
270
|
+
return Array.from(this.connections.entries())
|
|
271
|
+
.filter(([_, conn]) => conn.ready)
|
|
272
|
+
.map(([id]) => id);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Get project connection info
|
|
276
|
+
*/
|
|
277
|
+
getProject(projectId) {
|
|
278
|
+
return this.connections.get(projectId)?.config;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Schedule a reconnection attempt with exponential backoff
|
|
282
|
+
*/
|
|
283
|
+
scheduleReconnect(conn) {
|
|
284
|
+
if (conn.reconnecting || this.shuttingDown)
|
|
285
|
+
return;
|
|
286
|
+
const attempts = conn.reconnectAttempts ?? 0;
|
|
287
|
+
if (attempts >= this.options.maxReconnectAttempts) {
|
|
288
|
+
console.error(`[bridge] Max reconnection attempts reached for ${conn.config.id}`);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
conn.reconnecting = true;
|
|
292
|
+
conn.reconnectAttempts = attempts + 1;
|
|
293
|
+
// Calculate delay with exponential backoff
|
|
294
|
+
const delay = Math.min(this.options.reconnectDelay * Math.pow(2, attempts), this.options.maxReconnectDelay);
|
|
295
|
+
console.log(`[bridge] Reconnecting to ${conn.config.id} in ${delay}ms (attempt ${conn.reconnectAttempts})`);
|
|
296
|
+
conn.reconnectTimer = setTimeout(() => {
|
|
297
|
+
this.attemptReconnect(conn);
|
|
298
|
+
}, delay);
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Attempt to reconnect to a project daemon
|
|
302
|
+
*/
|
|
303
|
+
attemptReconnect(conn) {
|
|
304
|
+
if (this.shuttingDown) {
|
|
305
|
+
conn.reconnecting = false;
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
// Check socket exists
|
|
309
|
+
if (!fs.existsSync(conn.config.socketPath)) {
|
|
310
|
+
console.error(`[bridge] No daemon running for ${conn.config.id}, will retry`);
|
|
311
|
+
conn.reconnecting = false;
|
|
312
|
+
this.scheduleReconnect(conn);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const socket = net.createConnection(conn.config.socketPath, () => {
|
|
316
|
+
this.sendHello(conn);
|
|
317
|
+
});
|
|
318
|
+
// Update connection with new socket and fresh parser
|
|
319
|
+
conn.socket = socket;
|
|
320
|
+
conn.parser = new FrameParser();
|
|
321
|
+
conn.parser.setLegacyMode(true); // Use 4-byte header for backwards compatibility
|
|
322
|
+
socket.on('data', (data) => this.handleData(conn, data));
|
|
323
|
+
socket.on('close', () => {
|
|
324
|
+
const wasReady = conn.ready;
|
|
325
|
+
conn.ready = false;
|
|
326
|
+
this.onProjectStateChange?.(conn.config.id, false);
|
|
327
|
+
if (wasReady && this.options.reconnect && !this.shuttingDown) {
|
|
328
|
+
this.scheduleReconnect(conn);
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
socket.on('error', (err) => {
|
|
332
|
+
console.error(`[bridge] Reconnection error for ${conn.config.id}:`, err.message);
|
|
333
|
+
conn.reconnecting = false;
|
|
334
|
+
if (!this.shuttingDown) {
|
|
335
|
+
this.scheduleReconnect(conn);
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
// Reset reconnection state on successful connect
|
|
339
|
+
const originalReady = conn.ready;
|
|
340
|
+
const checkReady = setInterval(() => {
|
|
341
|
+
if (conn.ready && !originalReady) {
|
|
342
|
+
clearInterval(checkReady);
|
|
343
|
+
clearTimeout(timeout);
|
|
344
|
+
conn.reconnecting = false;
|
|
345
|
+
conn.reconnectAttempts = 0;
|
|
346
|
+
console.log(`[bridge] Reconnected to ${conn.config.id}`);
|
|
347
|
+
}
|
|
348
|
+
}, 10);
|
|
349
|
+
const timeout = setTimeout(() => {
|
|
350
|
+
if (!conn.ready) {
|
|
351
|
+
clearInterval(checkReady);
|
|
352
|
+
socket.destroy();
|
|
353
|
+
conn.reconnecting = false;
|
|
354
|
+
console.error(`[bridge] Reconnection timeout for ${conn.config.id}`);
|
|
355
|
+
if (!this.shuttingDown) {
|
|
356
|
+
this.scheduleReconnect(conn);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}, 5000);
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Disconnect from all projects
|
|
363
|
+
*/
|
|
364
|
+
disconnect() {
|
|
365
|
+
this.shuttingDown = true;
|
|
366
|
+
for (const [_, conn] of this.connections) {
|
|
367
|
+
// Clear any pending reconnection timers
|
|
368
|
+
if (conn.reconnectTimer) {
|
|
369
|
+
clearTimeout(conn.reconnectTimer);
|
|
370
|
+
}
|
|
371
|
+
try {
|
|
372
|
+
this.send(conn, {
|
|
373
|
+
v: PROTOCOL_VERSION,
|
|
374
|
+
type: 'BYE',
|
|
375
|
+
id: generateId(),
|
|
376
|
+
ts: Date.now(),
|
|
377
|
+
payload: {},
|
|
378
|
+
});
|
|
379
|
+
conn.socket.end();
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
// Ignore
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
this.connections.clear();
|
|
386
|
+
this.leads.clear();
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
//# sourceMappingURL=multi-project-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-project-client.js","sourceRoot":"","sources":["../src/multi-project-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type ShadowCli = 'claude' | 'codex';
|
|
2
|
+
export type ShadowMode = 'subagent' | 'process';
|
|
3
|
+
export interface ShadowCliSelection {
|
|
4
|
+
cli: ShadowCli;
|
|
5
|
+
/** Actual command to execute when spawning a process */
|
|
6
|
+
command: string;
|
|
7
|
+
mode: ShadowMode;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Select the shadow CLI and execution mode based on the primary agent's CLI.
|
|
11
|
+
* - Claude/OpenCode primaries run shadows as subagents (Task tool)
|
|
12
|
+
* - Others fall back to spawning a process using an authenticated CLI
|
|
13
|
+
*/
|
|
14
|
+
export declare function selectShadowCli(primaryCli: string, options?: {
|
|
15
|
+
preferredShadowCli?: string;
|
|
16
|
+
}): Promise<ShadowCliSelection>;
|
|
17
|
+
//# sourceMappingURL=shadow-cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shadow-cli.d.ts","sourceRoot":"","sources":["../src/shadow-cli.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;AAC3C,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,SAAS,CAAC;AAEhD,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,SAAS,CAAC;IACf,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,UAAU,CAAC;CAClB;AAsCD;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE;IAAE,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAAE,GACxC,OAAO,CAAC,kBAAkB,CAAC,CAkC7B"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import { commandExists } from '@agent-relay/utils/command-resolver';
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
/** Normalize CLI name to a supported identifier */
|
|
6
|
+
function normalizeCli(cli) {
|
|
7
|
+
if (!cli)
|
|
8
|
+
return null;
|
|
9
|
+
const base = cli.trim().split(' ')[0]; // Strip any args
|
|
10
|
+
const [command] = base.split(':'); // Handle variants like claude:opus
|
|
11
|
+
const lower = command.toLowerCase();
|
|
12
|
+
if (lower.startsWith('claude'))
|
|
13
|
+
return 'claude';
|
|
14
|
+
if (lower === 'codex' || lower === 'opencode')
|
|
15
|
+
return 'codex';
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
async function isCommandAuthenticated(command) {
|
|
19
|
+
if (!commandExists(command))
|
|
20
|
+
return false;
|
|
21
|
+
try {
|
|
22
|
+
await execFileAsync(command, ['--version'], { timeout: 4000 });
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async function detectAuthenticatedCommand(cli) {
|
|
30
|
+
const candidates = cli === 'claude' ? ['claude'] : ['codex', 'opencode'];
|
|
31
|
+
for (const candidate of candidates) {
|
|
32
|
+
if (await isCommandAuthenticated(candidate)) {
|
|
33
|
+
return candidate;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Select the shadow CLI and execution mode based on the primary agent's CLI.
|
|
40
|
+
* - Claude/OpenCode primaries run shadows as subagents (Task tool)
|
|
41
|
+
* - Others fall back to spawning a process using an authenticated CLI
|
|
42
|
+
*/
|
|
43
|
+
export async function selectShadowCli(primaryCli, options) {
|
|
44
|
+
const primary = normalizeCli(primaryCli);
|
|
45
|
+
const preferred = normalizeCli(options?.preferredShadowCli);
|
|
46
|
+
// Native subagent support for Claude/OpenCode primaries
|
|
47
|
+
if (primary) {
|
|
48
|
+
return {
|
|
49
|
+
cli: primary,
|
|
50
|
+
command: primary === 'claude' ? 'claude' : 'codex',
|
|
51
|
+
mode: 'subagent',
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
// Process-mode fallback for non-supporting primaries
|
|
55
|
+
const fallbackOrder = [];
|
|
56
|
+
if (preferred)
|
|
57
|
+
fallbackOrder.push(preferred);
|
|
58
|
+
fallbackOrder.push('claude', 'codex');
|
|
59
|
+
const seen = new Set();
|
|
60
|
+
for (const candidate of fallbackOrder) {
|
|
61
|
+
if (seen.has(candidate))
|
|
62
|
+
continue;
|
|
63
|
+
seen.add(candidate);
|
|
64
|
+
const authenticatedCommand = await detectAuthenticatedCommand(candidate);
|
|
65
|
+
if (authenticatedCommand) {
|
|
66
|
+
return {
|
|
67
|
+
cli: candidate,
|
|
68
|
+
command: authenticatedCommand,
|
|
69
|
+
mode: 'process',
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
throw new Error('No shadow-capable CLI authenticated. Install Claude or OpenCode (codex) and try again.');
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=shadow-cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shadow-cli.js","sourceRoot":"","sources":["../src/shadow-cli.ts"],"names":[],"mappings":"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"}
|