@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.
- package/dist/__fixtures__/claude-outputs.d.ts +49 -0
- package/dist/__fixtures__/claude-outputs.d.ts.map +1 -0
- package/dist/__fixtures__/claude-outputs.js +443 -0
- package/dist/__fixtures__/claude-outputs.js.map +1 -0
- package/dist/__fixtures__/codex-outputs.d.ts +9 -0
- package/dist/__fixtures__/codex-outputs.d.ts.map +1 -0
- package/dist/__fixtures__/codex-outputs.js +94 -0
- package/dist/__fixtures__/codex-outputs.js.map +1 -0
- package/dist/__fixtures__/gemini-outputs.d.ts +19 -0
- package/dist/__fixtures__/gemini-outputs.d.ts.map +1 -0
- package/dist/__fixtures__/gemini-outputs.js +144 -0
- package/dist/__fixtures__/gemini-outputs.js.map +1 -0
- package/dist/__fixtures__/index.d.ts +68 -0
- package/dist/__fixtures__/index.d.ts.map +1 -0
- package/dist/__fixtures__/index.js +44 -0
- package/dist/__fixtures__/index.js.map +1 -0
- package/dist/auth-detection.d.ts +49 -0
- package/dist/auth-detection.d.ts.map +1 -0
- package/dist/auth-detection.js +199 -0
- package/dist/auth-detection.js.map +1 -0
- package/dist/base-wrapper.d.ts +225 -0
- package/dist/base-wrapper.d.ts.map +1 -0
- package/dist/base-wrapper.js +572 -0
- package/dist/base-wrapper.js.map +1 -0
- package/dist/client.d.ts +254 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +801 -0
- package/dist/client.js.map +1 -0
- package/dist/id-generator.d.ts +35 -0
- package/dist/id-generator.d.ts.map +1 -0
- package/dist/id-generator.js +60 -0
- package/dist/id-generator.js.map +1 -0
- package/dist/idle-detector.d.ts +110 -0
- package/dist/idle-detector.d.ts.map +1 -0
- package/dist/idle-detector.js +304 -0
- package/dist/idle-detector.js.map +1 -0
- package/dist/inbox.d.ts +37 -0
- package/dist/inbox.d.ts.map +1 -0
- package/dist/inbox.js +73 -0
- package/dist/inbox.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +47 -0
- package/dist/index.js.map +1 -0
- package/dist/parser.d.ts +236 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +1238 -0
- package/dist/parser.js.map +1 -0
- package/dist/prompt-composer.d.ts +67 -0
- package/dist/prompt-composer.d.ts.map +1 -0
- package/dist/prompt-composer.js +168 -0
- package/dist/prompt-composer.js.map +1 -0
- package/dist/relay-pty-orchestrator.d.ts +407 -0
- package/dist/relay-pty-orchestrator.d.ts.map +1 -0
- package/dist/relay-pty-orchestrator.js +1885 -0
- package/dist/relay-pty-orchestrator.js.map +1 -0
- package/dist/shared.d.ts +201 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +341 -0
- package/dist/shared.js.map +1 -0
- package/dist/stuck-detector.d.ts +161 -0
- package/dist/stuck-detector.d.ts.map +1 -0
- package/dist/stuck-detector.js +402 -0
- package/dist/stuck-detector.js.map +1 -0
- package/dist/tmux-resolver.d.ts +55 -0
- package/dist/tmux-resolver.d.ts.map +1 -0
- package/dist/tmux-resolver.js +175 -0
- package/dist/tmux-resolver.js.map +1 -0
- package/dist/tmux-wrapper.d.ts +345 -0
- package/dist/tmux-wrapper.d.ts.map +1 -0
- package/dist/tmux-wrapper.js +1747 -0
- package/dist/tmux-wrapper.js.map +1 -0
- package/dist/trajectory-integration.d.ts +292 -0
- package/dist/trajectory-integration.d.ts.map +1 -0
- package/dist/trajectory-integration.js +979 -0
- package/dist/trajectory-integration.js.map +1 -0
- package/dist/wrapper-types.d.ts +41 -0
- package/dist/wrapper-types.d.ts.map +1 -0
- package/dist/wrapper-types.js +7 -0
- package/dist/wrapper-types.js.map +1 -0
- package/package.json +63 -0
- package/src/__fixtures__/claude-outputs.ts +471 -0
- package/src/__fixtures__/codex-outputs.ts +99 -0
- package/src/__fixtures__/gemini-outputs.ts +151 -0
- package/src/__fixtures__/index.ts +47 -0
- package/src/auth-detection.ts +244 -0
- package/src/base-wrapper.test.ts +540 -0
- package/src/base-wrapper.ts +741 -0
- package/src/client.test.ts +262 -0
- package/src/client.ts +984 -0
- package/src/id-generator.test.ts +71 -0
- package/src/id-generator.ts +69 -0
- package/src/idle-detector.test.ts +390 -0
- package/src/idle-detector.ts +370 -0
- package/src/inbox.test.ts +233 -0
- package/src/inbox.ts +89 -0
- package/src/index.ts +170 -0
- package/src/parser.regression.test.ts +251 -0
- package/src/parser.test.ts +1359 -0
- package/src/parser.ts +1477 -0
- package/src/prompt-composer.test.ts +219 -0
- package/src/prompt-composer.ts +231 -0
- package/src/relay-pty-orchestrator.test.ts +1027 -0
- package/src/relay-pty-orchestrator.ts +2270 -0
- package/src/shared.test.ts +221 -0
- package/src/shared.ts +454 -0
- package/src/stuck-detector.test.ts +303 -0
- package/src/stuck-detector.ts +511 -0
- package/src/tmux-resolver.test.ts +104 -0
- package/src/tmux-resolver.ts +207 -0
- package/src/tmux-wrapper.test.ts +316 -0
- package/src/tmux-wrapper.ts +2010 -0
- package/src/trajectory-detection.test.ts +151 -0
- package/src/trajectory-integration.ts +1261 -0
- 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
|
+
});
|