@agent-relay/wrapper 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.
Files changed (115) hide show
  1. package/dist/__fixtures__/claude-outputs.d.ts +49 -0
  2. package/dist/__fixtures__/claude-outputs.d.ts.map +1 -0
  3. package/dist/__fixtures__/claude-outputs.js +443 -0
  4. package/dist/__fixtures__/claude-outputs.js.map +1 -0
  5. package/dist/__fixtures__/codex-outputs.d.ts +9 -0
  6. package/dist/__fixtures__/codex-outputs.d.ts.map +1 -0
  7. package/dist/__fixtures__/codex-outputs.js +94 -0
  8. package/dist/__fixtures__/codex-outputs.js.map +1 -0
  9. package/dist/__fixtures__/gemini-outputs.d.ts +19 -0
  10. package/dist/__fixtures__/gemini-outputs.d.ts.map +1 -0
  11. package/dist/__fixtures__/gemini-outputs.js +144 -0
  12. package/dist/__fixtures__/gemini-outputs.js.map +1 -0
  13. package/dist/__fixtures__/index.d.ts +68 -0
  14. package/dist/__fixtures__/index.d.ts.map +1 -0
  15. package/dist/__fixtures__/index.js +44 -0
  16. package/dist/__fixtures__/index.js.map +1 -0
  17. package/dist/auth-detection.d.ts +49 -0
  18. package/dist/auth-detection.d.ts.map +1 -0
  19. package/dist/auth-detection.js +199 -0
  20. package/dist/auth-detection.js.map +1 -0
  21. package/dist/base-wrapper.d.ts +225 -0
  22. package/dist/base-wrapper.d.ts.map +1 -0
  23. package/dist/base-wrapper.js +572 -0
  24. package/dist/base-wrapper.js.map +1 -0
  25. package/dist/client.d.ts +254 -0
  26. package/dist/client.d.ts.map +1 -0
  27. package/dist/client.js +801 -0
  28. package/dist/client.js.map +1 -0
  29. package/dist/id-generator.d.ts +35 -0
  30. package/dist/id-generator.d.ts.map +1 -0
  31. package/dist/id-generator.js +60 -0
  32. package/dist/id-generator.js.map +1 -0
  33. package/dist/idle-detector.d.ts +110 -0
  34. package/dist/idle-detector.d.ts.map +1 -0
  35. package/dist/idle-detector.js +304 -0
  36. package/dist/idle-detector.js.map +1 -0
  37. package/dist/inbox.d.ts +37 -0
  38. package/dist/inbox.d.ts.map +1 -0
  39. package/dist/inbox.js +73 -0
  40. package/dist/inbox.js.map +1 -0
  41. package/dist/index.d.ts +37 -0
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +47 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/parser.d.ts +236 -0
  46. package/dist/parser.d.ts.map +1 -0
  47. package/dist/parser.js +1238 -0
  48. package/dist/parser.js.map +1 -0
  49. package/dist/prompt-composer.d.ts +67 -0
  50. package/dist/prompt-composer.d.ts.map +1 -0
  51. package/dist/prompt-composer.js +168 -0
  52. package/dist/prompt-composer.js.map +1 -0
  53. package/dist/relay-pty-orchestrator.d.ts +407 -0
  54. package/dist/relay-pty-orchestrator.d.ts.map +1 -0
  55. package/dist/relay-pty-orchestrator.js +1885 -0
  56. package/dist/relay-pty-orchestrator.js.map +1 -0
  57. package/dist/shared.d.ts +201 -0
  58. package/dist/shared.d.ts.map +1 -0
  59. package/dist/shared.js +341 -0
  60. package/dist/shared.js.map +1 -0
  61. package/dist/stuck-detector.d.ts +161 -0
  62. package/dist/stuck-detector.d.ts.map +1 -0
  63. package/dist/stuck-detector.js +402 -0
  64. package/dist/stuck-detector.js.map +1 -0
  65. package/dist/tmux-resolver.d.ts +55 -0
  66. package/dist/tmux-resolver.d.ts.map +1 -0
  67. package/dist/tmux-resolver.js +175 -0
  68. package/dist/tmux-resolver.js.map +1 -0
  69. package/dist/tmux-wrapper.d.ts +345 -0
  70. package/dist/tmux-wrapper.d.ts.map +1 -0
  71. package/dist/tmux-wrapper.js +1747 -0
  72. package/dist/tmux-wrapper.js.map +1 -0
  73. package/dist/trajectory-integration.d.ts +292 -0
  74. package/dist/trajectory-integration.d.ts.map +1 -0
  75. package/dist/trajectory-integration.js +979 -0
  76. package/dist/trajectory-integration.js.map +1 -0
  77. package/dist/wrapper-types.d.ts +41 -0
  78. package/dist/wrapper-types.d.ts.map +1 -0
  79. package/dist/wrapper-types.js +7 -0
  80. package/dist/wrapper-types.js.map +1 -0
  81. package/package.json +63 -0
  82. package/src/__fixtures__/claude-outputs.ts +471 -0
  83. package/src/__fixtures__/codex-outputs.ts +99 -0
  84. package/src/__fixtures__/gemini-outputs.ts +151 -0
  85. package/src/__fixtures__/index.ts +47 -0
  86. package/src/auth-detection.ts +244 -0
  87. package/src/base-wrapper.test.ts +540 -0
  88. package/src/base-wrapper.ts +741 -0
  89. package/src/client.test.ts +262 -0
  90. package/src/client.ts +984 -0
  91. package/src/id-generator.test.ts +71 -0
  92. package/src/id-generator.ts +69 -0
  93. package/src/idle-detector.test.ts +390 -0
  94. package/src/idle-detector.ts +370 -0
  95. package/src/inbox.test.ts +233 -0
  96. package/src/inbox.ts +89 -0
  97. package/src/index.ts +170 -0
  98. package/src/parser.regression.test.ts +251 -0
  99. package/src/parser.test.ts +1359 -0
  100. package/src/parser.ts +1477 -0
  101. package/src/prompt-composer.test.ts +219 -0
  102. package/src/prompt-composer.ts +231 -0
  103. package/src/relay-pty-orchestrator.test.ts +1027 -0
  104. package/src/relay-pty-orchestrator.ts +2270 -0
  105. package/src/shared.test.ts +221 -0
  106. package/src/shared.ts +454 -0
  107. package/src/stuck-detector.test.ts +303 -0
  108. package/src/stuck-detector.ts +511 -0
  109. package/src/tmux-resolver.test.ts +104 -0
  110. package/src/tmux-resolver.ts +207 -0
  111. package/src/tmux-wrapper.test.ts +316 -0
  112. package/src/tmux-wrapper.ts +2010 -0
  113. package/src/trajectory-detection.test.ts +151 -0
  114. package/src/trajectory-integration.ts +1261 -0
  115. package/src/wrapper-types.ts +45 -0
@@ -0,0 +1,262 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import type { Envelope, ErrorPayload, WelcomePayload, DeliverEnvelope, AckPayload } from '@agent-relay/protocol/types';
3
+ import { RelayClient } from './client.js';
4
+
5
+ describe('RelayClient', () => {
6
+ describe('configuration', () => {
7
+ it('should use default config values', () => {
8
+ const client = new RelayClient({});
9
+ expect(client.state).toBe('DISCONNECTED');
10
+ });
11
+
12
+ it('should accept custom config', () => {
13
+ const client = new RelayClient({
14
+ agentName: 'TestAgent',
15
+ socketPath: '/custom/socket.sock',
16
+ reconnect: false,
17
+ maxReconnectAttempts: 5,
18
+ });
19
+ expect(client.state).toBe('DISCONNECTED');
20
+ });
21
+
22
+ it('should use agentName from config', () => {
23
+ const client = new RelayClient({ agentName: 'CustomAgent' });
24
+ // agentName is stored internally
25
+ expect((client as any).config.agentName).toBe('CustomAgent');
26
+ });
27
+ });
28
+
29
+ describe('state management', () => {
30
+ it('should start in DISCONNECTED state', () => {
31
+ const client = new RelayClient({});
32
+ expect(client.state).toBe('DISCONNECTED');
33
+ });
34
+
35
+ it('should notify on state change', () => {
36
+ const client = new RelayClient({ reconnect: false });
37
+ const states: string[] = [];
38
+ client.onStateChange = (state) => states.push(state);
39
+
40
+ // Trigger internal state changes using setState
41
+ (client as any).setState('CONNECTING');
42
+ (client as any).setState('READY');
43
+
44
+ expect(states).toContain('CONNECTING');
45
+ expect(states).toContain('READY');
46
+ });
47
+ });
48
+
49
+ describe('message handling', () => {
50
+ it('should call onMessage when DELIVER received', () => {
51
+ const client = new RelayClient({ reconnect: false });
52
+ const messages: any[] = [];
53
+ client.onMessage = (from, payload, id, meta, originalTo) => messages.push({ from, payload, id, originalTo });
54
+
55
+ // DELIVER envelope has delivery info and from at envelope level
56
+ const deliverEnvelope: DeliverEnvelope = {
57
+ v: 1,
58
+ type: 'DELIVER',
59
+ id: 'msg-1',
60
+ ts: Date.now(),
61
+ from: 'Alice',
62
+ payload: {
63
+ kind: 'message',
64
+ body: 'Hello!',
65
+ },
66
+ delivery: {
67
+ seq: 1,
68
+ session_id: 'session-1',
69
+ },
70
+ };
71
+
72
+ (client as any).processFrame(deliverEnvelope);
73
+
74
+ expect(messages).toHaveLength(1);
75
+ expect(messages[0].from).toBe('Alice');
76
+ expect(messages[0].payload.body).toBe('Hello!');
77
+ expect(messages[0].originalTo).toBeUndefined();
78
+ });
79
+
80
+ it('should pass originalTo for broadcast messages', () => {
81
+ const client = new RelayClient({ reconnect: false });
82
+ const messages: any[] = [];
83
+ client.onMessage = (from, payload, id, meta, originalTo) => messages.push({ from, payload, id, originalTo });
84
+
85
+ // DELIVER envelope for a broadcast message includes originalTo: '*'
86
+ const deliverEnvelope: DeliverEnvelope = {
87
+ v: 1,
88
+ type: 'DELIVER',
89
+ id: 'msg-2',
90
+ ts: Date.now(),
91
+ from: 'Dashboard',
92
+ to: 'Bob',
93
+ payload: {
94
+ kind: 'message',
95
+ body: 'Hello everyone!',
96
+ },
97
+ delivery: {
98
+ seq: 1,
99
+ session_id: 'session-1',
100
+ originalTo: '*', // This was a broadcast
101
+ },
102
+ };
103
+
104
+ (client as any).processFrame(deliverEnvelope);
105
+
106
+ expect(messages).toHaveLength(1);
107
+ expect(messages[0].from).toBe('Dashboard');
108
+ expect(messages[0].payload.body).toBe('Hello everyone!');
109
+ expect(messages[0].originalTo).toBe('*');
110
+ });
111
+
112
+ it('should handle WELCOME and transition to READY', () => {
113
+ const client = new RelayClient({ reconnect: false });
114
+ (client as any)._state = 'CONNECTING';
115
+
116
+ const welcomeEnvelope: Envelope<WelcomePayload> = {
117
+ v: 1,
118
+ type: 'WELCOME',
119
+ id: 'welcome-1',
120
+ ts: Date.now(),
121
+ payload: {
122
+ session_id: 'session-123',
123
+ server: {
124
+ max_frame_bytes: 1024 * 1024,
125
+ heartbeat_ms: 5000,
126
+ },
127
+ },
128
+ };
129
+
130
+ (client as any).processFrame(welcomeEnvelope);
131
+
132
+ expect(client.state).toBe('READY');
133
+ });
134
+ });
135
+
136
+ describe('error handling', () => {
137
+ it('clears resume token after RESUME_TOO_OLD error', () => {
138
+ const client = new RelayClient({ reconnect: false });
139
+
140
+ // Simulate a stored resume token that the server rejects
141
+ (client as any).resumeToken = 'stale-token';
142
+
143
+ const errorEnvelope: Envelope<ErrorPayload> = {
144
+ v: 1,
145
+ type: 'ERROR',
146
+ id: 'err-1',
147
+ ts: Date.now(),
148
+ payload: {
149
+ code: 'RESUME_TOO_OLD',
150
+ message: 'Session resume not yet supported; starting new session',
151
+ fatal: false,
152
+ },
153
+ };
154
+
155
+ (client as any).processFrame(errorEnvelope);
156
+
157
+ expect((client as any).resumeToken).toBeUndefined();
158
+ });
159
+
160
+ it('should handle ERROR frames without crashing', () => {
161
+ const client = new RelayClient({ reconnect: false });
162
+
163
+ const errorEnvelope: Envelope<ErrorPayload> = {
164
+ v: 1,
165
+ type: 'ERROR',
166
+ id: 'err-1',
167
+ ts: Date.now(),
168
+ payload: {
169
+ code: 'INTERNAL_ERROR',
170
+ message: 'Something went wrong',
171
+ fatal: true,
172
+ },
173
+ };
174
+
175
+ // Should not throw
176
+ expect(() => (client as any).processFrame(errorEnvelope)).not.toThrow();
177
+ });
178
+ });
179
+
180
+ describe('sendMessage', () => {
181
+ it('should return false when not connected', () => {
182
+ const client = new RelayClient({ reconnect: false });
183
+ const result = client.sendMessage('Alice', 'Hello');
184
+ expect(result).toBe(false);
185
+ });
186
+
187
+ it('should return false when in wrong state', () => {
188
+ const client = new RelayClient({ reconnect: false });
189
+ (client as any)._state = 'CONNECTING';
190
+ const result = client.sendMessage('Alice', 'Hello');
191
+ expect(result).toBe(false);
192
+ });
193
+ });
194
+
195
+ describe('sendAndWait', () => {
196
+ it('resolves when matching ACK arrives', async () => {
197
+ const client = new RelayClient({ reconnect: false });
198
+ (client as any)._state = 'READY';
199
+ const sendMock = vi.fn().mockReturnValue(true);
200
+ (client as any).send = sendMock;
201
+
202
+ const promise = client.sendAndWait('Bob', 'ping', { timeoutMs: 1000 });
203
+ const sentEnvelope = sendMock.mock.calls[0][0];
204
+ const correlationId = sentEnvelope.payload_meta.sync.correlationId;
205
+
206
+ const ackEnvelope: Envelope<AckPayload> = {
207
+ v: 1,
208
+ type: 'ACK',
209
+ id: 'ack-1',
210
+ ts: Date.now(),
211
+ payload: {
212
+ ack_id: 'd-1',
213
+ seq: 1,
214
+ correlationId,
215
+ response: 'OK',
216
+ },
217
+ };
218
+
219
+ (client as any).processFrame(ackEnvelope);
220
+
221
+ await expect(promise).resolves.toMatchObject({ correlationId, response: 'OK' });
222
+ });
223
+
224
+ it('rejects on timeout', async () => {
225
+ vi.useFakeTimers();
226
+ try {
227
+ const client = new RelayClient({ reconnect: false });
228
+ (client as any)._state = 'READY';
229
+ const sendMock = vi.fn().mockReturnValue(true);
230
+ (client as any).send = sendMock;
231
+
232
+ const promise = client.sendAndWait('Bob', 'ping', { timeoutMs: 50 });
233
+ const rejection = expect(promise).rejects.toThrow('ACK timeout');
234
+ await vi.advanceTimersByTimeAsync(60);
235
+
236
+ await rejection;
237
+ } finally {
238
+ vi.useRealTimers();
239
+ }
240
+ });
241
+
242
+ it('rejects when not ready', async () => {
243
+ const client = new RelayClient({ reconnect: false });
244
+ await expect(client.sendAndWait('Bob', 'ping')).rejects.toThrow('Client not ready');
245
+ });
246
+ });
247
+
248
+ describe('disconnect', () => {
249
+ it('should transition to DISCONNECTED state', () => {
250
+ const client = new RelayClient({ reconnect: false });
251
+ (client as any)._state = 'READY';
252
+
253
+ client.disconnect();
254
+
255
+ expect(client.state).toBe('DISCONNECTED');
256
+ });
257
+ });
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
262
+ });