agent-relay 2.0.33 → 2.0.35

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 (67) hide show
  1. package/README.md +44 -0
  2. package/dist/index.cjs +10323 -15012
  3. package/package.json +26 -19
  4. package/packages/api-types/.trajectories/active/traj_xbsvuzogscey.json +15 -0
  5. package/packages/api-types/.trajectories/index.json +12 -0
  6. package/packages/api-types/package.json +1 -1
  7. package/packages/benchmark/package.json +4 -4
  8. package/packages/bridge/dist/spawner.d.ts.map +1 -1
  9. package/packages/bridge/dist/spawner.js +127 -0
  10. package/packages/bridge/dist/spawner.js.map +1 -1
  11. package/packages/bridge/package.json +8 -8
  12. package/packages/bridge/src/spawner.ts +137 -0
  13. package/packages/cli-tester/package.json +1 -1
  14. package/packages/config/package.json +2 -2
  15. package/packages/continuity/package.json +1 -1
  16. package/packages/daemon/package.json +12 -12
  17. package/packages/hooks/package.json +4 -4
  18. package/packages/mcp/package.json +3 -3
  19. package/packages/memory/package.json +2 -2
  20. package/packages/policy/package.json +2 -2
  21. package/packages/protocol/package.json +1 -1
  22. package/packages/resiliency/package.json +1 -1
  23. package/packages/sdk/package.json +2 -2
  24. package/packages/spawner/package.json +1 -1
  25. package/packages/state/package.json +1 -1
  26. package/packages/storage/package.json +2 -2
  27. package/packages/telemetry/package.json +1 -1
  28. package/packages/trajectory/package.json +2 -2
  29. package/packages/user-directory/package.json +2 -2
  30. package/packages/utils/package.json +2 -2
  31. package/packages/wrapper/dist/base-wrapper.d.ts.map +1 -1
  32. package/packages/wrapper/dist/base-wrapper.js +27 -7
  33. package/packages/wrapper/dist/base-wrapper.js.map +1 -1
  34. package/packages/wrapper/dist/client.d.ts +27 -0
  35. package/packages/wrapper/dist/client.d.ts.map +1 -1
  36. package/packages/wrapper/dist/client.js +116 -0
  37. package/packages/wrapper/dist/client.js.map +1 -1
  38. package/packages/wrapper/dist/index.d.ts +3 -0
  39. package/packages/wrapper/dist/index.d.ts.map +1 -1
  40. package/packages/wrapper/dist/index.js +6 -0
  41. package/packages/wrapper/dist/index.js.map +1 -1
  42. package/packages/wrapper/dist/opencode-api.d.ts +106 -0
  43. package/packages/wrapper/dist/opencode-api.d.ts.map +1 -0
  44. package/packages/wrapper/dist/opencode-api.js +219 -0
  45. package/packages/wrapper/dist/opencode-api.js.map +1 -0
  46. package/packages/wrapper/dist/opencode-wrapper.d.ts +157 -0
  47. package/packages/wrapper/dist/opencode-wrapper.d.ts.map +1 -0
  48. package/packages/wrapper/dist/opencode-wrapper.js +414 -0
  49. package/packages/wrapper/dist/opencode-wrapper.js.map +1 -0
  50. package/packages/wrapper/dist/relay-pty-orchestrator.d.ts.map +1 -1
  51. package/packages/wrapper/dist/relay-pty-orchestrator.js +18 -0
  52. package/packages/wrapper/dist/relay-pty-orchestrator.js.map +1 -1
  53. package/packages/wrapper/dist/wrapper-events.d.ts +489 -0
  54. package/packages/wrapper/dist/wrapper-events.d.ts.map +1 -0
  55. package/packages/wrapper/dist/wrapper-events.js +252 -0
  56. package/packages/wrapper/dist/wrapper-events.js.map +1 -0
  57. package/packages/wrapper/package.json +7 -6
  58. package/packages/wrapper/src/base-wrapper.ts +23 -7
  59. package/packages/wrapper/src/client.test.ts +92 -3
  60. package/packages/wrapper/src/client.ts +163 -0
  61. package/packages/wrapper/src/index.ts +29 -0
  62. package/packages/wrapper/src/opencode-api.test.ts +292 -0
  63. package/packages/wrapper/src/opencode-api.ts +285 -0
  64. package/packages/wrapper/src/opencode-wrapper.ts +513 -0
  65. package/packages/wrapper/src/relay-pty-orchestrator.test.ts +176 -0
  66. package/packages/wrapper/src/relay-pty-orchestrator.ts +20 -0
  67. package/packages/wrapper/src/wrapper-events.ts +395 -0
@@ -0,0 +1,252 @@
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
+ import { z } from 'zod';
27
+ import { EventEmitter } from 'node:events';
28
+ /**
29
+ * Define a typed event with Zod schema validation
30
+ */
31
+ export function defineEvent(type, schema) {
32
+ return { type, schema };
33
+ }
34
+ // =========================================================================
35
+ // Daemon Event Definitions
36
+ // =========================================================================
37
+ export var RelayEvent;
38
+ (function (RelayEvent) {
39
+ // --- Agent Lifecycle Events ---
40
+ RelayEvent.AgentConnected = defineEvent('daemon.agent.connected', z.object({
41
+ agentName: z.string(),
42
+ connectionId: z.string(),
43
+ cli: z.string().optional(),
44
+ task: z.string().optional(),
45
+ workingDirectory: z.string().optional(),
46
+ }));
47
+ RelayEvent.AgentDisconnected = defineEvent('daemon.agent.disconnected', z.object({
48
+ agentName: z.string(),
49
+ connectionId: z.string(),
50
+ reason: z.enum(['clean', 'error', 'timeout', 'replaced']).optional(),
51
+ }));
52
+ RelayEvent.AgentSpawned = defineEvent('daemon.agent.spawned', z.object({
53
+ agentName: z.string(),
54
+ parentAgent: z.string(),
55
+ cli: z.string(),
56
+ task: z.string(),
57
+ }));
58
+ RelayEvent.AgentReleased = defineEvent('daemon.agent.released', z.object({
59
+ agentName: z.string(),
60
+ releasedBy: z.string(),
61
+ }));
62
+ // --- Message Events ---
63
+ RelayEvent.MessageRouted = defineEvent('daemon.message.routed', z.object({
64
+ messageId: z.string(),
65
+ from: z.string(),
66
+ to: z.string(),
67
+ kind: z.string().optional(),
68
+ bodyPreview: z.string().optional(),
69
+ }));
70
+ RelayEvent.MessageDelivered = defineEvent('daemon.message.delivered', z.object({
71
+ messageId: z.string(),
72
+ to: z.string(),
73
+ deliverySeq: z.number(),
74
+ }));
75
+ RelayEvent.MessageFailed = defineEvent('daemon.message.failed', z.object({
76
+ messageId: z.string(),
77
+ to: z.string(),
78
+ error: z.string(),
79
+ }));
80
+ // --- Channel Events ---
81
+ RelayEvent.ChannelJoined = defineEvent('daemon.channel.joined', z.object({
82
+ channel: z.string(),
83
+ member: z.string(),
84
+ }));
85
+ RelayEvent.ChannelLeft = defineEvent('daemon.channel.left', z.object({
86
+ channel: z.string(),
87
+ member: z.string(),
88
+ }));
89
+ RelayEvent.ChannelMessage = defineEvent('daemon.channel.message', z.object({
90
+ channel: z.string(),
91
+ from: z.string(),
92
+ bodyPreview: z.string().optional(),
93
+ }));
94
+ // --- Processing State Events ---
95
+ RelayEvent.AgentProcessingStarted = defineEvent('daemon.agent.processing.started', z.object({
96
+ agentName: z.string(),
97
+ messageId: z.string(),
98
+ }));
99
+ RelayEvent.AgentProcessingEnded = defineEvent('daemon.agent.processing.ended', z.object({
100
+ agentName: z.string(),
101
+ durationMs: z.number().optional(),
102
+ }));
103
+ // --- Shadow Events ---
104
+ RelayEvent.ShadowBound = defineEvent('daemon.shadow.bound', z.object({
105
+ shadowAgent: z.string(),
106
+ primaryAgent: z.string(),
107
+ speakOn: z.array(z.string()),
108
+ }));
109
+ RelayEvent.ShadowUnbound = defineEvent('daemon.shadow.unbound', z.object({
110
+ shadowAgent: z.string(),
111
+ primaryAgent: z.string(),
112
+ }));
113
+ // --- System Events ---
114
+ RelayEvent.DaemonStarted = defineEvent('daemon.system.started', z.object({
115
+ socketPath: z.string(),
116
+ version: z.string().optional(),
117
+ }));
118
+ RelayEvent.DaemonStopped = defineEvent('daemon.system.stopped', z.object({
119
+ reason: z.string().optional(),
120
+ }));
121
+ RelayEvent.RateLimitExceeded = defineEvent('daemon.system.rate_limit_exceeded', z.object({
122
+ agentName: z.string(),
123
+ }));
124
+ // --- All event definitions for iteration ---
125
+ RelayEvent.all = [
126
+ RelayEvent.AgentConnected,
127
+ RelayEvent.AgentDisconnected,
128
+ RelayEvent.AgentSpawned,
129
+ RelayEvent.AgentReleased,
130
+ RelayEvent.MessageRouted,
131
+ RelayEvent.MessageDelivered,
132
+ RelayEvent.MessageFailed,
133
+ RelayEvent.ChannelJoined,
134
+ RelayEvent.ChannelLeft,
135
+ RelayEvent.ChannelMessage,
136
+ RelayEvent.AgentProcessingStarted,
137
+ RelayEvent.AgentProcessingEnded,
138
+ RelayEvent.ShadowBound,
139
+ RelayEvent.ShadowUnbound,
140
+ RelayEvent.DaemonStarted,
141
+ RelayEvent.DaemonStopped,
142
+ RelayEvent.RateLimitExceeded,
143
+ ];
144
+ })(RelayEvent || (RelayEvent = {}));
145
+ // =========================================================================
146
+ // Event Bus
147
+ // =========================================================================
148
+ /**
149
+ * Type-safe event bus for daemon events
150
+ */
151
+ class RelayEventBus extends EventEmitter {
152
+ static instance;
153
+ constructor() {
154
+ super();
155
+ this.setMaxListeners(100); // Allow many subscribers
156
+ }
157
+ static getInstance() {
158
+ if (!RelayEventBus.instance) {
159
+ RelayEventBus.instance = new RelayEventBus();
160
+ }
161
+ return RelayEventBus.instance;
162
+ }
163
+ /**
164
+ * Emit a typed event
165
+ */
166
+ emitEvent(definition, properties) {
167
+ const payload = {
168
+ type: definition.type,
169
+ properties,
170
+ timestamp: Date.now(),
171
+ };
172
+ // Validate properties against schema
173
+ const result = definition.schema.safeParse(properties);
174
+ if (!result.success) {
175
+ console.error(`[RelayEventBus] Invalid event properties for ${definition.type}:`, result.error);
176
+ return;
177
+ }
178
+ // Emit to specific subscribers and wildcard subscribers
179
+ this.emit(definition.type, payload);
180
+ this.emit('*', payload);
181
+ }
182
+ /**
183
+ * Subscribe to a typed event
184
+ */
185
+ onEvent(definition, callback) {
186
+ this.on(definition.type, callback);
187
+ return () => this.off(definition.type, callback);
188
+ }
189
+ /**
190
+ * Subscribe to all events (wildcard)
191
+ */
192
+ onAnyEvent(callback) {
193
+ this.on('*', callback);
194
+ return () => this.off('*', callback);
195
+ }
196
+ /**
197
+ * Subscribe to an event once
198
+ */
199
+ onceEvent(definition, callback) {
200
+ this.once(definition.type, callback);
201
+ }
202
+ }
203
+ // =========================================================================
204
+ // Exports
205
+ // =========================================================================
206
+ /**
207
+ * Global daemon event bus instance
208
+ */
209
+ export const relayEventBus = RelayEventBus.getInstance();
210
+ /**
211
+ * Emit a daemon event
212
+ */
213
+ export function emitEvent(definition, properties) {
214
+ relayEventBus.emitEvent(definition, properties);
215
+ }
216
+ /**
217
+ * Subscribe to a daemon event
218
+ */
219
+ export function onEvent(definition, callback) {
220
+ return relayEventBus.onEvent(definition, callback);
221
+ }
222
+ /**
223
+ * Subscribe to all daemon events
224
+ */
225
+ export function onAnyEvent(callback) {
226
+ return relayEventBus.onAnyEvent(callback);
227
+ }
228
+ // =========================================================================
229
+ // OpenAPI Schema Generation
230
+ // =========================================================================
231
+ /**
232
+ * Generate OpenAPI-compatible schema for all daemon events
233
+ * This can be used to auto-generate SDK types
234
+ */
235
+ export function generateEventSchemas() {
236
+ const schemas = {};
237
+ for (const definition of RelayEvent.all) {
238
+ // Extract JSON Schema from Zod schema
239
+ // Note: This is a simplified version - production use should use zod-to-json-schema
240
+ schemas[definition.type] = {
241
+ type: 'object',
242
+ properties: {
243
+ type: { type: 'string', const: definition.type },
244
+ properties: definition.schema._def, // Simplified - use zod-to-json-schema for production
245
+ timestamp: { type: 'number' },
246
+ },
247
+ required: ['type', 'properties', 'timestamp'],
248
+ };
249
+ }
250
+ return schemas;
251
+ }
252
+ //# sourceMappingURL=wrapper-events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapper-events.js","sourceRoot":"","sources":["../src/wrapper-events.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-relay/wrapper",
3
- "version": "2.0.33",
3
+ "version": "2.0.35",
4
4
  "description": "CLI agent wrappers for Agent Relay - tmux, pty integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -30,11 +30,12 @@
30
30
  "clean": "rm -rf dist"
31
31
  },
32
32
  "dependencies": {
33
- "@agent-relay/api-types": "2.0.33",
34
- "@agent-relay/protocol": "2.0.33",
35
- "@agent-relay/config": "2.0.33",
36
- "@agent-relay/continuity": "2.0.33",
37
- "@agent-relay/resiliency": "2.0.33"
33
+ "@agent-relay/api-types": "2.0.35",
34
+ "@agent-relay/protocol": "2.0.35",
35
+ "@agent-relay/config": "2.0.35",
36
+ "@agent-relay/continuity": "2.0.35",
37
+ "@agent-relay/resiliency": "2.0.35",
38
+ "zod": "^3.23.8"
38
39
  },
39
40
  "devDependencies": {
40
41
  "typescript": "^5.9.3",
@@ -576,11 +576,19 @@ export abstract class BaseWrapper extends EventEmitter {
576
576
  * Execute a spawn command
577
577
  */
578
578
  protected async executeSpawn(name: string, cli: string, task: string): Promise<void> {
579
- // TODO: Re-enable daemon socket spawn when client.spawn() is implemented
580
- // See: docs/SDK-MIGRATION-PLAN.md for planned implementation
581
- // For now, go directly to dashboard API or callback
579
+ // Try daemon socket spawn first (most reliable)
580
+ if (this.client.state === 'READY') {
581
+ try {
582
+ const result = await this.client.spawn({ name, cli, task });
583
+ if (result.success) return;
584
+ // If spawn failed, log and fall through to other methods
585
+ console.warn(`[base-wrapper] Daemon spawn failed: ${result.error}`);
586
+ } catch (e) {
587
+ console.warn(`[base-wrapper] Daemon spawn error: ${e instanceof Error ? e.message : String(e)}`);
588
+ }
589
+ }
582
590
 
583
- // Try dashboard API
591
+ // Try dashboard API as fallback
584
592
  if (this.config.dashboardPort) {
585
593
  try {
586
594
  const response = await fetch(
@@ -607,9 +615,17 @@ export abstract class BaseWrapper extends EventEmitter {
607
615
  * Execute a release command
608
616
  */
609
617
  protected async executeRelease(name: string): Promise<void> {
610
- // TODO: Re-enable daemon socket release when client.release() is implemented
611
- // See: docs/SDK-MIGRATION-PLAN.md for planned implementation
612
- // For now, go directly to dashboard API or callback
618
+ // Try daemon socket release first (most reliable)
619
+ if (this.client.state === 'READY') {
620
+ try {
621
+ const result = await this.client.release(name);
622
+ if (result.success) return;
623
+ // If release failed, log and fall through to other methods
624
+ console.warn(`[base-wrapper] Daemon release failed: ${result.error}`);
625
+ } catch (e) {
626
+ console.warn(`[base-wrapper] Daemon release error: ${e instanceof Error ? e.message : String(e)}`);
627
+ }
628
+ }
613
629
 
614
630
  // Try dashboard API as fallback (backwards compatibility)
615
631
  if (this.config.dashboardPort) {
@@ -256,7 +256,96 @@ describe('RelayClient', () => {
256
256
  });
257
257
  });
258
258
 
259
- // TODO: Re-add spawn/release tests when daemon-based spawning is implemented
260
- // See: docs/SDK-MIGRATION-PLAN.md for planned implementation
261
- // These methods will be added to RelayClient as part of the SDK extraction work
259
+ describe('spawn', () => {
260
+ it('should return error when client not ready', async () => {
261
+ const client = new RelayClient({ reconnect: false });
262
+ const result = await client.spawn({ name: 'Worker1', cli: 'claude', task: 'Test task' });
263
+ expect(result.success).toBe(false);
264
+ expect(result.error).toBe('Client not ready');
265
+ });
266
+
267
+ it('should send SPAWN envelope when ready', async () => {
268
+ const client = new RelayClient({ reconnect: false });
269
+ (client as any)._state = 'READY';
270
+ (client as any).socket = { write: vi.fn() };
271
+
272
+ // Mock requestResponse to return a successful result
273
+ const mockResult = { replyTo: 'test-id', success: true, name: 'Worker1' };
274
+ vi.spyOn(client as any, 'requestResponse').mockResolvedValue(mockResult);
275
+
276
+ const result = await client.spawn({
277
+ name: 'Worker1',
278
+ cli: 'claude',
279
+ task: 'Test task',
280
+ model: 'opus',
281
+ cwd: '/test/path',
282
+ });
283
+
284
+ expect((client as any).requestResponse).toHaveBeenCalledWith(
285
+ 'SPAWN',
286
+ expect.objectContaining({
287
+ name: 'Worker1',
288
+ cli: 'claude',
289
+ task: 'Test task',
290
+ model: 'opus',
291
+ cwd: '/test/path',
292
+ }),
293
+ 30000
294
+ );
295
+ expect(result.success).toBe(true);
296
+ });
297
+
298
+ it('should handle spawn failure', async () => {
299
+ const client = new RelayClient({ reconnect: false });
300
+ (client as any)._state = 'READY';
301
+ (client as any).socket = { write: vi.fn() };
302
+
303
+ vi.spyOn(client as any, 'requestResponse').mockRejectedValue(new Error('Spawn failed'));
304
+
305
+ const result = await client.spawn({ name: 'Worker1', cli: 'claude', task: 'Test task' });
306
+
307
+ expect(result.success).toBe(false);
308
+ expect(result.error).toBe('Spawn failed');
309
+ });
310
+ });
311
+
312
+ describe('release', () => {
313
+ it('should return error when client not ready', async () => {
314
+ const client = new RelayClient({ reconnect: false });
315
+ const result = await client.release('Worker1');
316
+ expect(result.success).toBe(false);
317
+ expect(result.error).toBe('Client not ready');
318
+ });
319
+
320
+ it('should send RELEASE envelope when ready', async () => {
321
+ const client = new RelayClient({ reconnect: false });
322
+ (client as any)._state = 'READY';
323
+ (client as any).socket = { write: vi.fn() };
324
+
325
+ const mockResult = { replyTo: 'test-id', success: true, name: 'Worker1' };
326
+ vi.spyOn(client as any, 'requestResponse').mockResolvedValue(mockResult);
327
+
328
+ const result = await client.release('Worker1', 'Task completed');
329
+
330
+ expect((client as any).requestResponse).toHaveBeenCalledWith(
331
+ 'RELEASE',
332
+ { name: 'Worker1', reason: 'Task completed' },
333
+ 10000
334
+ );
335
+ expect(result.success).toBe(true);
336
+ });
337
+
338
+ it('should handle release failure', async () => {
339
+ const client = new RelayClient({ reconnect: false });
340
+ (client as any)._state = 'READY';
341
+ (client as any).socket = { write: vi.fn() };
342
+
343
+ vi.spyOn(client as any, 'requestResponse').mockRejectedValue(new Error('Release failed'));
344
+
345
+ const result = await client.release('Worker1');
346
+
347
+ expect(result.success).toBe(false);
348
+ expect(result.error).toBe('Release failed');
349
+ });
350
+ });
262
351
  });
@@ -56,8 +56,21 @@ import {
56
56
  type SpeakOnTrigger,
57
57
  type LogPayload,
58
58
  type EntityType,
59
+ type SpawnPayload,
60
+ type SpawnResultPayload,
61
+ type ReleasePayload,
62
+ type ReleaseResultPayload,
59
63
  PROTOCOL_VERSION,
60
64
  } from '@agent-relay/protocol/types';
65
+ import {
66
+ type SpawnResult,
67
+ type ReleaseResult,
68
+ toSpawnResult,
69
+ toReleaseResult,
70
+ } from '@agent-relay/utils/client-helpers';
71
+
72
+ // Re-export types for consumers
73
+ export type { SpawnResult, ReleaseResult };
61
74
  import type {
62
75
  ChannelMessagePayload,
63
76
  ChannelJoinEnvelope,
@@ -684,6 +697,139 @@ export class RelayClient {
684
697
 
685
698
  return this.send(envelope);
686
699
  }
700
+
701
+ // =============================================================================
702
+ // Spawn/Release Operations
703
+ // =============================================================================
704
+
705
+ /**
706
+ * Spawn a worker agent via the daemon.
707
+ * @param options - Spawn configuration
708
+ * @returns Promise resolving to spawn result
709
+ */
710
+ async spawn(options: {
711
+ name: string;
712
+ cli: string;
713
+ task: string;
714
+ model?: string;
715
+ cwd?: string;
716
+ }): Promise<SpawnResult> {
717
+ if (this._state !== 'READY') {
718
+ return { success: false, error: 'Client not ready' };
719
+ }
720
+
721
+ try {
722
+ const payload: SpawnPayload = {
723
+ name: options.name,
724
+ cli: options.cli,
725
+ task: options.task,
726
+ model: options.model,
727
+ cwd: options.cwd,
728
+ spawnerName: this.config.agentName,
729
+ };
730
+
731
+ const result = await this.requestResponse<SpawnResultPayload>(
732
+ 'SPAWN',
733
+ payload as unknown as Record<string, unknown>,
734
+ 30000 // 30 second timeout for spawn
735
+ );
736
+
737
+ return toSpawnResult(result);
738
+ } catch (e) {
739
+ return { success: false, error: e instanceof Error ? e.message : String(e) };
740
+ }
741
+ }
742
+
743
+ /**
744
+ * Release (terminate) a worker agent via the daemon.
745
+ * @param name - Name of the agent to release
746
+ * @param reason - Optional reason for release
747
+ * @returns Promise resolving to release result
748
+ */
749
+ async release(name: string, reason?: string): Promise<ReleaseResult> {
750
+ if (this._state !== 'READY') {
751
+ return { success: false, error: 'Client not ready' };
752
+ }
753
+
754
+ try {
755
+ const payload: ReleasePayload = { name, reason };
756
+
757
+ const result = await this.requestResponse<ReleaseResultPayload>(
758
+ 'RELEASE',
759
+ payload as unknown as Record<string, unknown>,
760
+ 10000 // 10 second timeout for release
761
+ );
762
+
763
+ return toReleaseResult(result);
764
+ } catch (e) {
765
+ return { success: false, error: e instanceof Error ? e.message : String(e) };
766
+ }
767
+ }
768
+
769
+ /**
770
+ * Send an envelope and wait for a matching response.
771
+ * Used for request/response operations like spawn/release.
772
+ */
773
+ private requestResponse<T>(
774
+ type: string,
775
+ payload: Record<string, unknown>,
776
+ timeoutMs: number
777
+ ): Promise<T> {
778
+ return new Promise<T>((resolve, reject) => {
779
+ if (!this.socket) {
780
+ reject(new Error('Not connected'));
781
+ return;
782
+ }
783
+
784
+ const requestId = generateId();
785
+ let timeoutHandle: NodeJS.Timeout;
786
+ let responseHandler: (envelope: Envelope) => void;
787
+
788
+ const cleanup = (): void => {
789
+ clearTimeout(timeoutHandle);
790
+ // Remove the handler from pending response handlers
791
+ this.pendingResponses.delete(requestId);
792
+ };
793
+
794
+ timeoutHandle = setTimeout(() => {
795
+ cleanup();
796
+ reject(new Error(`Request timeout after ${timeoutMs}ms`));
797
+ }, timeoutMs);
798
+
799
+ responseHandler = (envelope: Envelope): void => {
800
+ // Match by replyTo or id
801
+ const replyTo = (envelope.payload as { replyTo?: string })?.replyTo;
802
+ if (replyTo === requestId || envelope.id === requestId) {
803
+ cleanup();
804
+
805
+ if (envelope.type === 'ERROR') {
806
+ const errorPayload = envelope.payload as { message?: string; code?: string };
807
+ reject(new Error(errorPayload.message || errorPayload.code || 'Unknown error'));
808
+ } else {
809
+ resolve(envelope.payload as T);
810
+ }
811
+ }
812
+ };
813
+
814
+ // Register handler for this request
815
+ this.pendingResponses.set(requestId, responseHandler);
816
+
817
+ // Send the request envelope
818
+ const envelope: Envelope = {
819
+ v: PROTOCOL_VERSION,
820
+ type: type as Envelope['type'],
821
+ id: requestId,
822
+ ts: Date.now(),
823
+ payload,
824
+ };
825
+
826
+ this.send(envelope);
827
+ });
828
+ }
829
+
830
+ // Map to track pending request/response handlers
831
+ private pendingResponses: Map<string, (envelope: Envelope) => void> = new Map();
832
+
687
833
  private setState(state: ClientState): void {
688
834
  this._state = state;
689
835
  if (this.onStateChange) {
@@ -768,6 +914,17 @@ export class RelayClient {
768
914
  }
769
915
 
770
916
  private processFrame(envelope: Envelope): void {
917
+ // Check for pending request/response handlers first
918
+ // This handles SPAWN_RESULT, RELEASE_RESULT, and ERROR responses
919
+ const replyTo = (envelope.payload as { replyTo?: string })?.replyTo;
920
+ if (replyTo && this.pendingResponses.has(replyTo)) {
921
+ const handler = this.pendingResponses.get(replyTo);
922
+ if (handler) {
923
+ handler(envelope);
924
+ return;
925
+ }
926
+ }
927
+
771
928
  switch (envelope.type) {
772
929
  case 'WELCOME':
773
930
  this.handleWelcome(envelope as Envelope<WelcomePayload>);
@@ -793,6 +950,12 @@ export class RelayClient {
793
950
  this.handleErrorFrame(envelope as Envelope<ErrorPayload>);
794
951
  break;
795
952
 
953
+ case 'SPAWN_RESULT':
954
+ case 'RELEASE_RESULT':
955
+ // These should be handled by pending response handlers above
956
+ // If we get here, it's an orphaned response (no handler waiting)
957
+ break;
958
+
796
959
  case 'BUSY':
797
960
  console.warn('[client] Server busy, backing off');
798
961
  break;
@@ -168,3 +168,32 @@ export {
168
168
  RelayPtyOrchestrator,
169
169
  type RelayPtyOrchestratorConfig,
170
170
  } from './relay-pty-orchestrator.js';
171
+
172
+ // OpenCode HTTP API integration
173
+ export {
174
+ OpenCodeApi,
175
+ openCodeApi,
176
+ type OpenCodeApiConfig,
177
+ type OpenCodeSession,
178
+ type OpenCodeApiResponse,
179
+ } from './opencode-api.js';
180
+
181
+ // OpenCode wrapper (HTTP API + PTY fallback)
182
+ export {
183
+ OpenCodeWrapper,
184
+ type OpenCodeWrapperConfig,
185
+ } from './opencode-wrapper.js';
186
+
187
+ // Typed event definitions (inspired by opencode's BusEvent pattern)
188
+ export {
189
+ RelayEvent,
190
+ defineEvent,
191
+ relayEventBus,
192
+ emitEvent,
193
+ onEvent,
194
+ onAnyEvent,
195
+ generateEventSchemas,
196
+ type EventDefinition,
197
+ type EventProperties,
198
+ type EventPayload,
199
+ } from './wrapper-events.js';