agent-relay 2.0.32 → 2.0.34
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 +44 -0
- package/dist/index.cjs +7231 -6234
- package/package.json +19 -18
- package/packages/api-types/.trajectories/active/traj_xbsvuzogscey.json +15 -0
- package/packages/api-types/.trajectories/index.json +12 -0
- package/packages/api-types/package.json +1 -1
- package/packages/benchmark/package.json +4 -4
- package/packages/bridge/dist/spawner.d.ts.map +1 -1
- package/packages/bridge/dist/spawner.js +127 -0
- package/packages/bridge/dist/spawner.js.map +1 -1
- package/packages/bridge/package.json +8 -8
- package/packages/bridge/src/spawner.ts +137 -0
- package/packages/cli-tester/package.json +1 -1
- package/packages/config/package.json +2 -2
- package/packages/continuity/package.json +1 -1
- package/packages/daemon/package.json +12 -12
- package/packages/hooks/package.json +4 -4
- package/packages/mcp/package.json +3 -3
- package/packages/memory/package.json +2 -2
- package/packages/policy/package.json +2 -2
- package/packages/protocol/package.json +1 -1
- package/packages/resiliency/package.json +1 -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/package.json +1 -1
- package/packages/trajectory/package.json +2 -2
- package/packages/user-directory/package.json +2 -2
- package/packages/utils/package.json +2 -2
- package/packages/wrapper/dist/base-wrapper.d.ts.map +1 -1
- package/packages/wrapper/dist/base-wrapper.js +27 -7
- package/packages/wrapper/dist/base-wrapper.js.map +1 -1
- package/packages/wrapper/dist/client.d.ts +27 -0
- package/packages/wrapper/dist/client.d.ts.map +1 -1
- package/packages/wrapper/dist/client.js +116 -0
- package/packages/wrapper/dist/client.js.map +1 -1
- package/packages/wrapper/dist/index.d.ts +3 -0
- package/packages/wrapper/dist/index.d.ts.map +1 -1
- package/packages/wrapper/dist/index.js +6 -0
- package/packages/wrapper/dist/index.js.map +1 -1
- package/packages/wrapper/dist/opencode-api.d.ts +106 -0
- package/packages/wrapper/dist/opencode-api.d.ts.map +1 -0
- package/packages/wrapper/dist/opencode-api.js +219 -0
- package/packages/wrapper/dist/opencode-api.js.map +1 -0
- package/packages/wrapper/dist/opencode-wrapper.d.ts +157 -0
- package/packages/wrapper/dist/opencode-wrapper.d.ts.map +1 -0
- package/packages/wrapper/dist/opencode-wrapper.js +414 -0
- package/packages/wrapper/dist/opencode-wrapper.js.map +1 -0
- package/packages/wrapper/dist/relay-pty-orchestrator.d.ts.map +1 -1
- package/packages/wrapper/dist/relay-pty-orchestrator.js +18 -0
- package/packages/wrapper/dist/relay-pty-orchestrator.js.map +1 -1
- package/packages/wrapper/dist/wrapper-events.d.ts +489 -0
- package/packages/wrapper/dist/wrapper-events.d.ts.map +1 -0
- package/packages/wrapper/dist/wrapper-events.js +252 -0
- package/packages/wrapper/dist/wrapper-events.js.map +1 -0
- package/packages/wrapper/package.json +7 -6
- package/packages/wrapper/src/base-wrapper.ts +23 -7
- package/packages/wrapper/src/client.test.ts +92 -3
- package/packages/wrapper/src/client.ts +163 -0
- package/packages/wrapper/src/index.ts +29 -0
- package/packages/wrapper/src/opencode-api.test.ts +292 -0
- package/packages/wrapper/src/opencode-api.ts +285 -0
- package/packages/wrapper/src/opencode-wrapper.ts +513 -0
- package/packages/wrapper/src/relay-pty-orchestrator.test.ts +176 -0
- package/packages/wrapper/src/relay-pty-orchestrator.ts +20 -0
- package/packages/wrapper/src/wrapper-events.ts +395 -0
- package/scripts/postinstall.js +147 -2
|
@@ -57,6 +57,13 @@ import {
|
|
|
57
57
|
|
|
58
58
|
const MAX_SOCKET_PATH_LENGTH = 107;
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Maximum size for output buffers (rawBuffer, outputBuffer) in bytes.
|
|
62
|
+
* Prevents RangeError: Invalid string length when agents produce lots of output.
|
|
63
|
+
* Set to 10MB - enough to capture context but won't exhaust memory.
|
|
64
|
+
*/
|
|
65
|
+
const MAX_OUTPUT_BUFFER_SIZE = 10 * 1024 * 1024; // 10MB
|
|
66
|
+
|
|
60
67
|
function hashWorkspaceId(workspaceId: string): string {
|
|
61
68
|
return createHash('sha256').update(workspaceId).digest('hex').slice(0, 12);
|
|
62
69
|
}
|
|
@@ -956,6 +963,19 @@ export class RelayPtyOrchestrator extends BaseWrapper {
|
|
|
956
963
|
this.outputBuffer += data;
|
|
957
964
|
this.hasReceivedOutput = true;
|
|
958
965
|
|
|
966
|
+
// Trim buffers if they exceed max size to prevent RangeError: Invalid string length
|
|
967
|
+
// Keep the most recent output (tail) as it's more relevant for pattern matching
|
|
968
|
+
if (this.rawBuffer.length > MAX_OUTPUT_BUFFER_SIZE) {
|
|
969
|
+
const trimAmount = this.rawBuffer.length - MAX_OUTPUT_BUFFER_SIZE;
|
|
970
|
+
this.rawBuffer = this.rawBuffer.slice(-MAX_OUTPUT_BUFFER_SIZE);
|
|
971
|
+
// Adjust lastParsedLength to stay in sync with the trimmed buffer
|
|
972
|
+
// This ensures parseRelayCommands() doesn't skip content or re-parse old content
|
|
973
|
+
this.lastParsedLength = Math.max(0, this.lastParsedLength - trimAmount);
|
|
974
|
+
}
|
|
975
|
+
if (this.outputBuffer.length > MAX_OUTPUT_BUFFER_SIZE) {
|
|
976
|
+
this.outputBuffer = this.outputBuffer.slice(-MAX_OUTPUT_BUFFER_SIZE);
|
|
977
|
+
}
|
|
978
|
+
|
|
959
979
|
// Feed to idle detector
|
|
960
980
|
this.feedIdleDetectorOutput(data);
|
|
961
981
|
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed Event Definitions for Agent Relay Wrapper
|
|
3
|
+
*
|
|
4
|
+
* Inspired by opencode's BusEvent pattern, this provides type-safe event
|
|
5
|
+
* definitions with Zod schema validation. Events can be used for:
|
|
6
|
+
* - Wrapper internal notifications
|
|
7
|
+
* - SDK client subscriptions
|
|
8
|
+
* - OpenAPI spec generation
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { RelayEvent, emitEvent, onEvent } from './wrapper-events.js';
|
|
13
|
+
*
|
|
14
|
+
* // Subscribe to events
|
|
15
|
+
* onEvent(RelayEvent.AgentConnected, (event) => {
|
|
16
|
+
* console.log(`Agent ${event.properties.agentName} connected`);
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* // Emit events
|
|
20
|
+
* emitEvent(RelayEvent.AgentConnected, {
|
|
21
|
+
* agentName: 'MyAgent',
|
|
22
|
+
* connectionId: 'abc123',
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { z } from 'zod';
|
|
28
|
+
import { EventEmitter } from 'node:events';
|
|
29
|
+
|
|
30
|
+
// =========================================================================
|
|
31
|
+
// Event Definition Factory
|
|
32
|
+
// =========================================================================
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Event definition with type and schema
|
|
36
|
+
*/
|
|
37
|
+
export interface EventDefinition<
|
|
38
|
+
Type extends string = string,
|
|
39
|
+
Schema extends z.ZodType = z.ZodType
|
|
40
|
+
> {
|
|
41
|
+
type: Type;
|
|
42
|
+
schema: Schema;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Define a typed event with Zod schema validation
|
|
47
|
+
*/
|
|
48
|
+
export function defineEvent<Type extends string, Schema extends z.ZodType>(
|
|
49
|
+
type: Type,
|
|
50
|
+
schema: Schema
|
|
51
|
+
): EventDefinition<Type, Schema> {
|
|
52
|
+
return { type, schema };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Infer the properties type from an event definition
|
|
57
|
+
*/
|
|
58
|
+
export type EventProperties<E extends EventDefinition> = z.infer<E['schema']>;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Event payload with type and properties
|
|
62
|
+
*/
|
|
63
|
+
export interface EventPayload<E extends EventDefinition = EventDefinition> {
|
|
64
|
+
type: E['type'];
|
|
65
|
+
properties: EventProperties<E>;
|
|
66
|
+
timestamp: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// =========================================================================
|
|
70
|
+
// Daemon Event Definitions
|
|
71
|
+
// =========================================================================
|
|
72
|
+
|
|
73
|
+
export namespace RelayEvent {
|
|
74
|
+
// --- Agent Lifecycle Events ---
|
|
75
|
+
|
|
76
|
+
export const AgentConnected = defineEvent(
|
|
77
|
+
'daemon.agent.connected',
|
|
78
|
+
z.object({
|
|
79
|
+
agentName: z.string(),
|
|
80
|
+
connectionId: z.string(),
|
|
81
|
+
cli: z.string().optional(),
|
|
82
|
+
task: z.string().optional(),
|
|
83
|
+
workingDirectory: z.string().optional(),
|
|
84
|
+
})
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
export const AgentDisconnected = defineEvent(
|
|
88
|
+
'daemon.agent.disconnected',
|
|
89
|
+
z.object({
|
|
90
|
+
agentName: z.string(),
|
|
91
|
+
connectionId: z.string(),
|
|
92
|
+
reason: z.enum(['clean', 'error', 'timeout', 'replaced']).optional(),
|
|
93
|
+
})
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
export const AgentSpawned = defineEvent(
|
|
97
|
+
'daemon.agent.spawned',
|
|
98
|
+
z.object({
|
|
99
|
+
agentName: z.string(),
|
|
100
|
+
parentAgent: z.string(),
|
|
101
|
+
cli: z.string(),
|
|
102
|
+
task: z.string(),
|
|
103
|
+
})
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
export const AgentReleased = defineEvent(
|
|
107
|
+
'daemon.agent.released',
|
|
108
|
+
z.object({
|
|
109
|
+
agentName: z.string(),
|
|
110
|
+
releasedBy: z.string(),
|
|
111
|
+
})
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
// --- Message Events ---
|
|
115
|
+
|
|
116
|
+
export const MessageRouted = defineEvent(
|
|
117
|
+
'daemon.message.routed',
|
|
118
|
+
z.object({
|
|
119
|
+
messageId: z.string(),
|
|
120
|
+
from: z.string(),
|
|
121
|
+
to: z.string(),
|
|
122
|
+
kind: z.string().optional(),
|
|
123
|
+
bodyPreview: z.string().optional(),
|
|
124
|
+
})
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
export const MessageDelivered = defineEvent(
|
|
128
|
+
'daemon.message.delivered',
|
|
129
|
+
z.object({
|
|
130
|
+
messageId: z.string(),
|
|
131
|
+
to: z.string(),
|
|
132
|
+
deliverySeq: z.number(),
|
|
133
|
+
})
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
export const MessageFailed = defineEvent(
|
|
137
|
+
'daemon.message.failed',
|
|
138
|
+
z.object({
|
|
139
|
+
messageId: z.string(),
|
|
140
|
+
to: z.string(),
|
|
141
|
+
error: z.string(),
|
|
142
|
+
})
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// --- Channel Events ---
|
|
146
|
+
|
|
147
|
+
export const ChannelJoined = defineEvent(
|
|
148
|
+
'daemon.channel.joined',
|
|
149
|
+
z.object({
|
|
150
|
+
channel: z.string(),
|
|
151
|
+
member: z.string(),
|
|
152
|
+
})
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
export const ChannelLeft = defineEvent(
|
|
156
|
+
'daemon.channel.left',
|
|
157
|
+
z.object({
|
|
158
|
+
channel: z.string(),
|
|
159
|
+
member: z.string(),
|
|
160
|
+
})
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
export const ChannelMessage = defineEvent(
|
|
164
|
+
'daemon.channel.message',
|
|
165
|
+
z.object({
|
|
166
|
+
channel: z.string(),
|
|
167
|
+
from: z.string(),
|
|
168
|
+
bodyPreview: z.string().optional(),
|
|
169
|
+
})
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
// --- Processing State Events ---
|
|
173
|
+
|
|
174
|
+
export const AgentProcessingStarted = defineEvent(
|
|
175
|
+
'daemon.agent.processing.started',
|
|
176
|
+
z.object({
|
|
177
|
+
agentName: z.string(),
|
|
178
|
+
messageId: z.string(),
|
|
179
|
+
})
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
export const AgentProcessingEnded = defineEvent(
|
|
183
|
+
'daemon.agent.processing.ended',
|
|
184
|
+
z.object({
|
|
185
|
+
agentName: z.string(),
|
|
186
|
+
durationMs: z.number().optional(),
|
|
187
|
+
})
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// --- Shadow Events ---
|
|
191
|
+
|
|
192
|
+
export const ShadowBound = defineEvent(
|
|
193
|
+
'daemon.shadow.bound',
|
|
194
|
+
z.object({
|
|
195
|
+
shadowAgent: z.string(),
|
|
196
|
+
primaryAgent: z.string(),
|
|
197
|
+
speakOn: z.array(z.string()),
|
|
198
|
+
})
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
export const ShadowUnbound = defineEvent(
|
|
202
|
+
'daemon.shadow.unbound',
|
|
203
|
+
z.object({
|
|
204
|
+
shadowAgent: z.string(),
|
|
205
|
+
primaryAgent: z.string(),
|
|
206
|
+
})
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
// --- System Events ---
|
|
210
|
+
|
|
211
|
+
export const DaemonStarted = defineEvent(
|
|
212
|
+
'daemon.system.started',
|
|
213
|
+
z.object({
|
|
214
|
+
socketPath: z.string(),
|
|
215
|
+
version: z.string().optional(),
|
|
216
|
+
})
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
export const DaemonStopped = defineEvent(
|
|
220
|
+
'daemon.system.stopped',
|
|
221
|
+
z.object({
|
|
222
|
+
reason: z.string().optional(),
|
|
223
|
+
})
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
export const RateLimitExceeded = defineEvent(
|
|
227
|
+
'daemon.system.rate_limit_exceeded',
|
|
228
|
+
z.object({
|
|
229
|
+
agentName: z.string(),
|
|
230
|
+
})
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
// --- All event definitions for iteration ---
|
|
234
|
+
|
|
235
|
+
export const all = [
|
|
236
|
+
AgentConnected,
|
|
237
|
+
AgentDisconnected,
|
|
238
|
+
AgentSpawned,
|
|
239
|
+
AgentReleased,
|
|
240
|
+
MessageRouted,
|
|
241
|
+
MessageDelivered,
|
|
242
|
+
MessageFailed,
|
|
243
|
+
ChannelJoined,
|
|
244
|
+
ChannelLeft,
|
|
245
|
+
ChannelMessage,
|
|
246
|
+
AgentProcessingStarted,
|
|
247
|
+
AgentProcessingEnded,
|
|
248
|
+
ShadowBound,
|
|
249
|
+
ShadowUnbound,
|
|
250
|
+
DaemonStarted,
|
|
251
|
+
DaemonStopped,
|
|
252
|
+
RateLimitExceeded,
|
|
253
|
+
] as const;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// =========================================================================
|
|
257
|
+
// Event Bus
|
|
258
|
+
// =========================================================================
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Type-safe event bus for daemon events
|
|
262
|
+
*/
|
|
263
|
+
class RelayEventBus extends EventEmitter {
|
|
264
|
+
private static instance: RelayEventBus;
|
|
265
|
+
|
|
266
|
+
private constructor() {
|
|
267
|
+
super();
|
|
268
|
+
this.setMaxListeners(100); // Allow many subscribers
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
static getInstance(): RelayEventBus {
|
|
272
|
+
if (!RelayEventBus.instance) {
|
|
273
|
+
RelayEventBus.instance = new RelayEventBus();
|
|
274
|
+
}
|
|
275
|
+
return RelayEventBus.instance;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Emit a typed event
|
|
280
|
+
*/
|
|
281
|
+
emitEvent<E extends EventDefinition>(
|
|
282
|
+
definition: E,
|
|
283
|
+
properties: EventProperties<E>
|
|
284
|
+
): void {
|
|
285
|
+
const payload: EventPayload<E> = {
|
|
286
|
+
type: definition.type,
|
|
287
|
+
properties,
|
|
288
|
+
timestamp: Date.now(),
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// Validate properties against schema
|
|
292
|
+
const result = definition.schema.safeParse(properties);
|
|
293
|
+
if (!result.success) {
|
|
294
|
+
console.error(`[RelayEventBus] Invalid event properties for ${definition.type}:`, result.error);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Emit to specific subscribers and wildcard subscribers
|
|
299
|
+
this.emit(definition.type, payload);
|
|
300
|
+
this.emit('*', payload);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Subscribe to a typed event
|
|
305
|
+
*/
|
|
306
|
+
onEvent<E extends EventDefinition>(
|
|
307
|
+
definition: E,
|
|
308
|
+
callback: (event: EventPayload<E>) => void
|
|
309
|
+
): () => void {
|
|
310
|
+
this.on(definition.type, callback);
|
|
311
|
+
return () => this.off(definition.type, callback);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Subscribe to all events (wildcard)
|
|
316
|
+
*/
|
|
317
|
+
onAnyEvent(callback: (event: EventPayload) => void): () => void {
|
|
318
|
+
this.on('*', callback);
|
|
319
|
+
return () => this.off('*', callback);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Subscribe to an event once
|
|
324
|
+
*/
|
|
325
|
+
onceEvent<E extends EventDefinition>(
|
|
326
|
+
definition: E,
|
|
327
|
+
callback: (event: EventPayload<E>) => void
|
|
328
|
+
): void {
|
|
329
|
+
this.once(definition.type, callback);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// =========================================================================
|
|
334
|
+
// Exports
|
|
335
|
+
// =========================================================================
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Global daemon event bus instance
|
|
339
|
+
*/
|
|
340
|
+
export const relayEventBus = RelayEventBus.getInstance();
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Emit a daemon event
|
|
344
|
+
*/
|
|
345
|
+
export function emitEvent<E extends EventDefinition>(
|
|
346
|
+
definition: E,
|
|
347
|
+
properties: EventProperties<E>
|
|
348
|
+
): void {
|
|
349
|
+
relayEventBus.emitEvent(definition, properties);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Subscribe to a daemon event
|
|
354
|
+
*/
|
|
355
|
+
export function onEvent<E extends EventDefinition>(
|
|
356
|
+
definition: E,
|
|
357
|
+
callback: (event: EventPayload<E>) => void
|
|
358
|
+
): () => void {
|
|
359
|
+
return relayEventBus.onEvent(definition, callback);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Subscribe to all daemon events
|
|
364
|
+
*/
|
|
365
|
+
export function onAnyEvent(callback: (event: EventPayload) => void): () => void {
|
|
366
|
+
return relayEventBus.onAnyEvent(callback);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// =========================================================================
|
|
370
|
+
// OpenAPI Schema Generation
|
|
371
|
+
// =========================================================================
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Generate OpenAPI-compatible schema for all daemon events
|
|
375
|
+
* This can be used to auto-generate SDK types
|
|
376
|
+
*/
|
|
377
|
+
export function generateEventSchemas(): Record<string, unknown> {
|
|
378
|
+
const schemas: Record<string, unknown> = {};
|
|
379
|
+
|
|
380
|
+
for (const definition of RelayEvent.all) {
|
|
381
|
+
// Extract JSON Schema from Zod schema
|
|
382
|
+
// Note: This is a simplified version - production use should use zod-to-json-schema
|
|
383
|
+
schemas[definition.type] = {
|
|
384
|
+
type: 'object',
|
|
385
|
+
properties: {
|
|
386
|
+
type: { type: 'string', const: definition.type },
|
|
387
|
+
properties: definition.schema._def, // Simplified - use zod-to-json-schema for production
|
|
388
|
+
timestamp: { type: 'number' },
|
|
389
|
+
},
|
|
390
|
+
required: ['type', 'properties', 'timestamp'],
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return schemas;
|
|
395
|
+
}
|
package/scripts/postinstall.js
CHANGED
|
@@ -279,6 +279,132 @@ function hasSystemTmux() {
|
|
|
279
279
|
}
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
+
/**
|
|
283
|
+
* Setup workspace package symlinks for global/bundled installs.
|
|
284
|
+
*
|
|
285
|
+
* When agent-relay is installed globally (npm install -g), the workspace packages
|
|
286
|
+
* are included in the tarball at packages/* but Node.js module resolution expects
|
|
287
|
+
* them at node_modules/@agent-relay/*. This function creates symlinks to bridge
|
|
288
|
+
* the gap.
|
|
289
|
+
*
|
|
290
|
+
* This is needed because npm's bundledDependencies doesn't properly handle
|
|
291
|
+
* workspace packages (which are symlinks during development).
|
|
292
|
+
*/
|
|
293
|
+
function setupWorkspacePackageLinks() {
|
|
294
|
+
const pkgRoot = getPackageRoot();
|
|
295
|
+
const packagesDir = path.join(pkgRoot, 'packages');
|
|
296
|
+
const nodeModulesDir = path.join(pkgRoot, 'node_modules');
|
|
297
|
+
const scopeDir = path.join(nodeModulesDir, '@agent-relay');
|
|
298
|
+
|
|
299
|
+
// Check if packages/ exists (we're in a bundled/global install)
|
|
300
|
+
if (!fs.existsSync(packagesDir)) {
|
|
301
|
+
// Not a bundled install, workspace packages should be in node_modules already
|
|
302
|
+
return { needed: false };
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Check if node_modules/@agent-relay/daemon exists
|
|
306
|
+
const testPackage = path.join(scopeDir, 'daemon');
|
|
307
|
+
if (fs.existsSync(testPackage)) {
|
|
308
|
+
// Already set up (either normal npm install or previously linked)
|
|
309
|
+
info('Workspace packages already available in node_modules');
|
|
310
|
+
return { needed: false, alreadySetup: true };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// We need to create symlinks
|
|
314
|
+
info('Setting up workspace package links for global install...');
|
|
315
|
+
|
|
316
|
+
// Create node_modules/@agent-relay/ directory
|
|
317
|
+
try {
|
|
318
|
+
fs.mkdirSync(scopeDir, { recursive: true });
|
|
319
|
+
} catch (err) {
|
|
320
|
+
warn(`Failed to create @agent-relay scope directory: ${err.message}`);
|
|
321
|
+
return { needed: true, success: false, error: err.message };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Map from package directory name to npm package name
|
|
325
|
+
const packageDirs = fs.readdirSync(packagesDir).filter(dir => {
|
|
326
|
+
const pkgJsonPath = path.join(packagesDir, dir, 'package.json');
|
|
327
|
+
return fs.existsSync(pkgJsonPath);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
let linked = 0;
|
|
331
|
+
let failed = 0;
|
|
332
|
+
const errors = [];
|
|
333
|
+
|
|
334
|
+
for (const dir of packageDirs) {
|
|
335
|
+
const sourcePath = path.join(packagesDir, dir);
|
|
336
|
+
const targetPath = path.join(scopeDir, dir);
|
|
337
|
+
|
|
338
|
+
// Skip if already exists
|
|
339
|
+
if (fs.existsSync(targetPath)) {
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
try {
|
|
344
|
+
// Use relative symlink for portability
|
|
345
|
+
const relativeSource = path.relative(scopeDir, sourcePath);
|
|
346
|
+
fs.symlinkSync(relativeSource, targetPath, 'dir');
|
|
347
|
+
linked++;
|
|
348
|
+
} catch (err) {
|
|
349
|
+
// If symlink fails (e.g., on Windows without admin), try copying
|
|
350
|
+
try {
|
|
351
|
+
// Copy the package directory
|
|
352
|
+
copyDirSync(sourcePath, targetPath);
|
|
353
|
+
linked++;
|
|
354
|
+
} catch (copyErr) {
|
|
355
|
+
failed++;
|
|
356
|
+
errors.push(`${dir}: ${copyErr.message}`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (linked > 0) {
|
|
362
|
+
success(`Linked ${linked} workspace packages to node_modules/@agent-relay/`);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (failed > 0) {
|
|
366
|
+
warn(`Failed to link ${failed} packages: ${errors.join(', ')}`);
|
|
367
|
+
return { needed: true, success: false, linked, failed, errors };
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return { needed: true, success: true, linked };
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Recursively copy a directory
|
|
375
|
+
*/
|
|
376
|
+
function copyDirSync(src, dest) {
|
|
377
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
378
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
379
|
+
|
|
380
|
+
for (const entry of entries) {
|
|
381
|
+
const srcPath = path.join(src, entry.name);
|
|
382
|
+
const destPath = path.join(dest, entry.name);
|
|
383
|
+
|
|
384
|
+
// Skip node_modules in package copies
|
|
385
|
+
if (entry.name === 'node_modules') {
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (entry.isDirectory()) {
|
|
390
|
+
copyDirSync(srcPath, destPath);
|
|
391
|
+
} else if (entry.isSymbolicLink()) {
|
|
392
|
+
// Resolve symlink and copy the target
|
|
393
|
+
const linkTarget = fs.readlinkSync(srcPath);
|
|
394
|
+
const resolvedTarget = path.resolve(path.dirname(srcPath), linkTarget);
|
|
395
|
+
if (fs.existsSync(resolvedTarget)) {
|
|
396
|
+
if (fs.statSync(resolvedTarget).isDirectory()) {
|
|
397
|
+
copyDirSync(resolvedTarget, destPath);
|
|
398
|
+
} else {
|
|
399
|
+
fs.copyFileSync(resolvedTarget, destPath);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
} else {
|
|
403
|
+
fs.copyFileSync(srcPath, destPath);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
282
408
|
/**
|
|
283
409
|
* Install dashboard dependencies
|
|
284
410
|
*/
|
|
@@ -362,7 +488,16 @@ function patchAgentTrajectories() {
|
|
|
362
488
|
success('Patched agent-trajectories to record agent on trail start');
|
|
363
489
|
}
|
|
364
490
|
|
|
365
|
-
function logPostinstallDiagnostics(hasRelayPty, sqliteStatus) {
|
|
491
|
+
function logPostinstallDiagnostics(hasRelayPty, sqliteStatus, linkResult) {
|
|
492
|
+
// Workspace packages status (for global installs)
|
|
493
|
+
if (linkResult && linkResult.needed) {
|
|
494
|
+
if (linkResult.success) {
|
|
495
|
+
console.log(`✓ Workspace packages linked (${linkResult.linked} packages)`);
|
|
496
|
+
} else {
|
|
497
|
+
console.log('⚠ Workspace package linking failed - CLI may not work');
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
366
501
|
if (hasRelayPty) {
|
|
367
502
|
console.log('✓ relay-pty binary installed');
|
|
368
503
|
} else {
|
|
@@ -388,6 +523,16 @@ function logPostinstallDiagnostics(hasRelayPty, sqliteStatus) {
|
|
|
388
523
|
* Main postinstall routine
|
|
389
524
|
*/
|
|
390
525
|
async function main() {
|
|
526
|
+
// Setup workspace package links for global installs
|
|
527
|
+
// This MUST run first so that other postinstall steps can find the packages
|
|
528
|
+
const linkResult = setupWorkspacePackageLinks();
|
|
529
|
+
if (linkResult.needed && !linkResult.success) {
|
|
530
|
+
warn('Workspace package linking failed - CLI may not work correctly');
|
|
531
|
+
if (linkResult.errors) {
|
|
532
|
+
linkResult.errors.forEach(e => warn(` ${e}`));
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
391
536
|
// Install relay-pty binary for current platform (primary mode)
|
|
392
537
|
const hasRelayPty = installRelayPtyBinary();
|
|
393
538
|
|
|
@@ -401,7 +546,7 @@ async function main() {
|
|
|
401
546
|
installDashboardDeps();
|
|
402
547
|
|
|
403
548
|
// Always print diagnostics (even in CI)
|
|
404
|
-
logPostinstallDiagnostics(hasRelayPty, sqliteStatus);
|
|
549
|
+
logPostinstallDiagnostics(hasRelayPty, sqliteStatus, linkResult);
|
|
405
550
|
|
|
406
551
|
// Skip tmux check in CI environments
|
|
407
552
|
if (process.env.CI === 'true') {
|