@robota-sdk/agent-transport 3.0.0-beta.79 → 3.0.0-beta.82
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 +84 -26
- package/dist/browser/client.d.ts +2 -0
- package/dist/browser/client.js +1 -0
- package/dist/browser/index.d.ts +520 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +2 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/message-decoders-BB8n-H2X.d.ts +252 -0
- package/dist/browser/message-decoders-BB8n-H2X.d.ts.map +1 -0
- package/dist/browser/message-decoders-CDmDoZPR.js +2 -0
- package/dist/browser/message-decoders-CDmDoZPR.js.map +1 -0
- package/dist/node/index.cjs +1 -1
- package/dist/node/index.d.cts +766 -0
- package/dist/node/index.d.cts.map +1 -0
- package/dist/node/index.d.ts +765 -27
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +1 -1
- package/dist/node/index.js.map +1 -1
- package/dist/node/node.cjs +1 -0
- package/dist/node/node.d.cts +216 -0
- package/dist/node/node.d.cts.map +1 -0
- package/dist/node/node.d.ts +216 -0
- package/dist/node/node.d.ts.map +1 -0
- package/dist/node/node.js +2 -0
- package/dist/node/node.js.map +1 -0
- package/package.json +68 -47
- package/src/__tests__/access-token-verifier.test.ts +672 -0
- package/src/__tests__/admission.test.ts +130 -0
- package/src/__tests__/channel-frames.test.ts +214 -0
- package/src/__tests__/driver-attribution-capability.test.ts +83 -0
- package/src/__tests__/file-transfer.test.ts +281 -0
- package/src/__tests__/handoff-chunking.test.ts +215 -0
- package/src/__tests__/handoff-manifest.test.ts +52 -0
- package/src/__tests__/message-decoders.test.ts +249 -0
- package/src/__tests__/outbound-delivery.test.ts +558 -0
- package/src/__tests__/peer-message-ledger.test.ts +200 -0
- package/src/__tests__/personal-usage-report.test.ts +193 -0
- package/src/__tests__/resume-buffer.test.ts +93 -0
- package/src/__tests__/session-event-delivery.test.ts +101 -0
- package/src/__tests__/session-message-handler-e5.test.ts +99 -0
- package/src/__tests__/session-message-handler.test.ts +590 -0
- package/src/__tests__/session-resume-bridge.test.ts +406 -0
- package/src/__tests__/submit-prompt-shape.test.ts +78 -0
- package/src/__tests__/usage-report-carrier.test.ts +98 -0
- package/src/__tests__/ws-broadcast-events.test.ts +84 -0
- package/src/background-messages.ts +169 -0
- package/src/channel-frames.ts +182 -0
- package/src/client.ts +9 -0
- package/src/handoff-chunking.ts +235 -0
- package/src/index.ts +112 -3
- package/src/message-decoders.ts +229 -0
- package/src/message-parser.ts +33 -0
- package/src/node/access-token-verifier.ts +363 -0
- package/src/node/admission.ts +153 -0
- package/src/node/file-transfer.ts +530 -0
- package/src/node/handoff-manifest.ts +77 -0
- package/src/node/index.ts +27 -0
- package/src/outbound-delivery.ts +231 -0
- package/src/peer-message-ledger.ts +193 -0
- package/src/protocol-session.ts +28 -0
- package/src/resume-buffer.ts +123 -0
- package/src/session-events.ts +198 -0
- package/src/session-message-handler.ts +282 -0
- package/src/session-query-messages.ts +42 -0
- package/src/session-resume-bridge.ts +252 -0
- package/src/usage-messages.ts +195 -0
- package/src/wire-messages.ts +165 -0
- package/dist/node/headless/index.cjs +0 -1
- package/dist/node/headless/index.d.ts +0 -2
- package/dist/node/headless/index.js +0 -1
- package/dist/node/headless-DG_MEbf0.js +0 -19
- package/dist/node/headless-DG_MEbf0.js.map +0 -1
- package/dist/node/headless-xq77JBq0.cjs +0 -18
- package/dist/node/index-CrJ2Lhn3.d.ts +0 -96
- package/dist/node/index-CrJ2Lhn3.d.ts.map +0 -1
- package/dist/node/index-JIvzrtaA.d.ts +0 -52
- package/dist/node/index-JIvzrtaA.d.ts.map +0 -1
- package/dist/node/index-JTD_xtEQ.d.ts +0 -96
- package/dist/node/index-JTD_xtEQ.d.ts.map +0 -1
- package/dist/node/index-y_HBOfzO.d.ts +0 -52
- package/dist/node/index-y_HBOfzO.d.ts.map +0 -1
- package/dist/node/programmatic/index.cjs +0 -1
- package/dist/node/programmatic/index.d.ts +0 -2
- package/dist/node/programmatic/index.js +0 -1
- package/dist/node/programmatic-6wLf68qJ.cjs +0 -1
- package/dist/node/programmatic-70dxIXtS.js +0 -2
- package/dist/node/programmatic-70dxIXtS.js.map +0 -1
- package/dist/node/testing/index.cjs +0 -1
- package/dist/node/testing/index.d.ts +0 -2
- package/dist/node/testing/index.js +0 -1
- package/src/__tests__/programmatic/programmatic-ask-user.test.ts +0 -32
- package/src/__tests__/programmatic/programmatic-driver.test.ts +0 -107
- package/src/__tests__/programmatic/programmatic-flows.test.ts +0 -140
- package/src/__tests__/transport-registry.stop-all.test.ts +0 -53
- package/src/headless/HeadlessInteractionChannel.ts +0 -129
- package/src/headless/__tests__/headless-channel-options.test.ts +0 -106
- package/src/headless/__tests__/headless-goal-runner.test.ts +0 -108
- package/src/headless/__tests__/headless-provider-failure.integration.test.ts +0 -143
- package/src/headless/__tests__/headless-runner-initialization.test.ts +0 -45
- package/src/headless/__tests__/headless-runner.test.ts +0 -508
- package/src/headless/__tests__/headless-skill-activation.integration.test.ts +0 -430
- package/src/headless/__tests__/headless-transport.test.ts +0 -267
- package/src/headless/cli-input.ts +0 -50
- package/src/headless/headless-runner.ts +0 -230
- package/src/headless/headless-stream-json.ts +0 -148
- package/src/headless/headless-transport.ts +0 -43
- package/src/headless/index.ts +0 -8
- package/src/headless/print-terminal.ts +0 -57
- package/src/programmatic/ProgrammaticInteractionChannel.ts +0 -82
- package/src/programmatic/createProgrammaticAgent.ts +0 -71
- package/src/programmatic/index.ts +0 -5
- package/src/testing/index.ts +0 -10
- package/src/transport-registry.ts +0 -103
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
CLIENT_MESSAGE_SHAPES,
|
|
5
|
+
MAX_INBOUND_FRAME_BYTES,
|
|
6
|
+
SERVER_MESSAGE_SHAPES,
|
|
7
|
+
decodeClientMessage,
|
|
8
|
+
decodeFrame,
|
|
9
|
+
decodeServerMessage,
|
|
10
|
+
} from '../message-decoders.js';
|
|
11
|
+
|
|
12
|
+
import type { TClientMessage, TServerMessage } from '../wire-messages.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Issue #2045 — the owner-side runtime decoders. One well-formed sample per union variant round-trips
|
|
16
|
+
* through JSON; a shared malformed corpus (null, arrays, missing fields, wrong primitives, malformed
|
|
17
|
+
* nested values, oversized frames) is refused consistently. Both carriers call these same functions.
|
|
18
|
+
*/
|
|
19
|
+
const CLIENT_SAMPLES: Readonly<Record<TClientMessage['type'], TClientMessage>> = {
|
|
20
|
+
submit: { type: 'submit', prompt: 'hi' },
|
|
21
|
+
command: { type: 'command', name: 'help', args: 'x' },
|
|
22
|
+
abort: { type: 'abort' },
|
|
23
|
+
'cancel-queue': { type: 'cancel-queue' },
|
|
24
|
+
'get-messages': { type: 'get-messages' },
|
|
25
|
+
'get-context': { type: 'get-context' },
|
|
26
|
+
'get-usage-report': { type: 'get-usage-report' },
|
|
27
|
+
'get-personal-usage-report': {
|
|
28
|
+
type: 'get-personal-usage-report',
|
|
29
|
+
requestId: 'request-1',
|
|
30
|
+
period: '7d',
|
|
31
|
+
timezone: 'UTC',
|
|
32
|
+
},
|
|
33
|
+
'get-stored-session-usage-report': {
|
|
34
|
+
type: 'get-stored-session-usage-report',
|
|
35
|
+
requestId: 'request-2',
|
|
36
|
+
sessionId: 'session-1',
|
|
37
|
+
},
|
|
38
|
+
'get-executing': { type: 'get-executing' },
|
|
39
|
+
'get-pending': { type: 'get-pending' },
|
|
40
|
+
'get-execution-workspace': { type: 'get-execution-workspace' },
|
|
41
|
+
'get-background-tasks': {
|
|
42
|
+
type: 'get-background-tasks',
|
|
43
|
+
filter: { kind: 'agent', includeClosed: true },
|
|
44
|
+
},
|
|
45
|
+
'get-background-task': { type: 'get-background-task', taskId: 't' },
|
|
46
|
+
'get-background-job-groups': { type: 'get-background-job-groups' },
|
|
47
|
+
'get-background-job-group': { type: 'get-background-job-group', groupId: 'g' },
|
|
48
|
+
'wait-background-job-group': { type: 'wait-background-job-group', groupId: 'g' },
|
|
49
|
+
'cancel-background-task': { type: 'cancel-background-task', taskId: 't', reason: 'r' },
|
|
50
|
+
'close-background-task': { type: 'close-background-task', taskId: 't' },
|
|
51
|
+
'send-background-task': { type: 'send-background-task', taskId: 't', input: { prompt: 'p' } },
|
|
52
|
+
'read-background-task-log': {
|
|
53
|
+
type: 'read-background-task-log',
|
|
54
|
+
taskId: 't',
|
|
55
|
+
cursor: { offset: 3 },
|
|
56
|
+
},
|
|
57
|
+
'permission-response': { type: 'permission-response', id: 'p1', result: 'allow-session' },
|
|
58
|
+
'ask-response': { type: 'ask-response', id: 'a1', response: { type: 'answer', values: ['y'] } },
|
|
59
|
+
resume: { type: 'resume', lastSeq: 4 },
|
|
60
|
+
ack: { type: 'ack', seq: 9 },
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const SERVER_SAMPLES: Readonly<Record<TServerMessage['type'], TServerMessage>> = {
|
|
64
|
+
text_delta: { type: 'text_delta', delta: 'd', driverId: 'drv' },
|
|
65
|
+
user_message: { type: 'user_message', content: 'c' },
|
|
66
|
+
tool_start: { type: 'tool_start', state: {} as never },
|
|
67
|
+
tool_end: { type: 'tool_end', state: {} as never },
|
|
68
|
+
thinking: { type: 'thinking', isThinking: true },
|
|
69
|
+
complete: { type: 'complete', result: {} as never },
|
|
70
|
+
interrupted: { type: 'interrupted', result: {} as never },
|
|
71
|
+
error: { type: 'error', message: 'm' },
|
|
72
|
+
command_result: { type: 'command_result', name: 'n', message: 'm', success: true },
|
|
73
|
+
messages: { type: 'messages', messages: [] },
|
|
74
|
+
context: { type: 'context', state: {} as never },
|
|
75
|
+
usage_report: { type: 'usage_report', report: {} as never },
|
|
76
|
+
personal_usage_report: {
|
|
77
|
+
type: 'personal_usage_report',
|
|
78
|
+
requestId: 'request-1',
|
|
79
|
+
report: {} as never,
|
|
80
|
+
},
|
|
81
|
+
personal_usage_report_error: {
|
|
82
|
+
type: 'personal_usage_report_error',
|
|
83
|
+
requestId: 'request-1',
|
|
84
|
+
code: 'not_available',
|
|
85
|
+
message: 'not available',
|
|
86
|
+
},
|
|
87
|
+
stored_session_usage_report: {
|
|
88
|
+
type: 'stored_session_usage_report',
|
|
89
|
+
requestId: 'request-2',
|
|
90
|
+
sessionId: 'session-1',
|
|
91
|
+
report: {} as never,
|
|
92
|
+
},
|
|
93
|
+
stored_session_usage_report_error: {
|
|
94
|
+
type: 'stored_session_usage_report_error',
|
|
95
|
+
requestId: 'request-2',
|
|
96
|
+
sessionId: 'session-1',
|
|
97
|
+
code: 'report_failed',
|
|
98
|
+
message: 'not found',
|
|
99
|
+
},
|
|
100
|
+
executing: { type: 'executing', executing: false },
|
|
101
|
+
pending: { type: 'pending', pending: null },
|
|
102
|
+
execution_workspace_event: { type: 'execution_workspace_event', snapshot: {} as never },
|
|
103
|
+
background_task_event: { type: 'background_task_event', event: {} as never },
|
|
104
|
+
background_job_group_event: { type: 'background_job_group_event', event: {} as never },
|
|
105
|
+
plan_event: { type: 'plan_event', event: {} as never },
|
|
106
|
+
context_file_refreshed: { type: 'context_file_refreshed', event: {} as never },
|
|
107
|
+
branch_event: { type: 'branch_event', event: {} as never },
|
|
108
|
+
background_tasks: { type: 'background_tasks', tasks: [] },
|
|
109
|
+
background_task: { type: 'background_task', taskId: 't', task: null },
|
|
110
|
+
background_job_groups: { type: 'background_job_groups', groups: [] },
|
|
111
|
+
background_job_group: { type: 'background_job_group', groupId: 'g', group: null },
|
|
112
|
+
background_task_log: { type: 'background_task_log', taskId: 't', page: {} as never },
|
|
113
|
+
permission_request: { type: 'permission_request', event: {} as never },
|
|
114
|
+
ask_request: { type: 'ask_request', event: {} as never },
|
|
115
|
+
prompt_resolved: { type: 'prompt_resolved', event: {} as never },
|
|
116
|
+
ui_intent: { type: 'ui_intent', event: {} as never },
|
|
117
|
+
session_renamed: { type: 'session_renamed', event: {} as never },
|
|
118
|
+
history_cleared: { type: 'history_cleared' },
|
|
119
|
+
background_task_control_result: {
|
|
120
|
+
type: 'background_task_control_result',
|
|
121
|
+
action: 'cancel',
|
|
122
|
+
taskId: 't',
|
|
123
|
+
success: true,
|
|
124
|
+
},
|
|
125
|
+
protocol_error: { type: 'protocol_error', message: 'm' },
|
|
126
|
+
resume_gap: { type: 'resume_gap' },
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const MALFORMED_CLIENT: ReadonlyArray<[string, unknown]> = [
|
|
130
|
+
['null', null],
|
|
131
|
+
['an array', [{ type: 'abort' }]],
|
|
132
|
+
['a string', 'abort'],
|
|
133
|
+
['no type', { prompt: 'x' }],
|
|
134
|
+
['a numeric type', { type: 1 }],
|
|
135
|
+
['an unknown type', { type: 'nope' }],
|
|
136
|
+
['a prototype-only type', { type: 'toString' }],
|
|
137
|
+
['submit without prompt', { type: 'submit' }],
|
|
138
|
+
['submit with an empty prompt', { type: 'submit', prompt: '' }],
|
|
139
|
+
['submit with an array prompt', { type: 'submit', prompt: ['x'] }],
|
|
140
|
+
['command with numeric args', { type: 'command', name: 'n', args: 1 }],
|
|
141
|
+
['get-background-tasks with an array filter', { type: 'get-background-tasks', filter: [] }],
|
|
142
|
+
[
|
|
143
|
+
'get-background-tasks with an unknown kind',
|
|
144
|
+
{ type: 'get-background-tasks', filter: { kind: 'x' } },
|
|
145
|
+
],
|
|
146
|
+
[
|
|
147
|
+
'send-background-task with a string input',
|
|
148
|
+
{ type: 'send-background-task', taskId: 't', input: 'p' },
|
|
149
|
+
],
|
|
150
|
+
[
|
|
151
|
+
'send-background-task with a numeric prompt',
|
|
152
|
+
{ type: 'send-background-task', taskId: 't', input: { prompt: 1 } },
|
|
153
|
+
],
|
|
154
|
+
[
|
|
155
|
+
'read-background-task-log with a string offset',
|
|
156
|
+
{ type: 'read-background-task-log', taskId: 't', cursor: { offset: '3' } },
|
|
157
|
+
],
|
|
158
|
+
[
|
|
159
|
+
'permission-response with a string result',
|
|
160
|
+
{ type: 'permission-response', id: 'p', result: 'yes' },
|
|
161
|
+
],
|
|
162
|
+
[
|
|
163
|
+
'ask-response with values not strings',
|
|
164
|
+
{ type: 'ask-response', id: 'a', response: { type: 'answer', values: [1] } },
|
|
165
|
+
],
|
|
166
|
+
[
|
|
167
|
+
'ask-response cancelled carrying values',
|
|
168
|
+
{ type: 'ask-response', id: 'a', response: { type: 'cancelled', values: [] } },
|
|
169
|
+
],
|
|
170
|
+
['resume with a string lastSeq', { type: 'resume', lastSeq: '4' }],
|
|
171
|
+
['ack with NaN', { type: 'ack', seq: Number.NaN }],
|
|
172
|
+
['cancel-background-task with an empty taskId', { type: 'cancel-background-task', taskId: '' }],
|
|
173
|
+
];
|
|
174
|
+
|
|
175
|
+
const MALFORMED_SERVER: ReadonlyArray<[string, unknown]> = [
|
|
176
|
+
['null', null],
|
|
177
|
+
['an array', [{ type: 'resume_gap' }]],
|
|
178
|
+
['an unknown type', { type: 'nope' }],
|
|
179
|
+
['text_delta without delta', { type: 'text_delta' }],
|
|
180
|
+
['text_delta with a numeric driverId', { type: 'text_delta', delta: 'd', driverId: 1 }],
|
|
181
|
+
['tool_start with an array state', { type: 'tool_start', state: [] }],
|
|
182
|
+
['thinking with a string flag', { type: 'thinking', isThinking: 'yes' }],
|
|
183
|
+
['messages with a non-array', { type: 'messages', messages: {} }],
|
|
184
|
+
['messages with a primitive entry', { type: 'messages', messages: ['x'] }],
|
|
185
|
+
['pending with a number', { type: 'pending', pending: 1 }],
|
|
186
|
+
['background_task with an array task', { type: 'background_task', taskId: 't', task: [] }],
|
|
187
|
+
[
|
|
188
|
+
'control result with an unknown action',
|
|
189
|
+
{ type: 'background_task_control_result', action: 'nuke', taskId: 't', success: true },
|
|
190
|
+
],
|
|
191
|
+
['command_result without success', { type: 'command_result', name: 'n', message: 'm' }],
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
describe('decodeClientMessage (issue #2045)', () => {
|
|
195
|
+
it('has a shape for every union variant and every sample decodes through JSON', () => {
|
|
196
|
+
for (const [type, sample] of Object.entries(CLIENT_SAMPLES)) {
|
|
197
|
+
expect(CLIENT_MESSAGE_SHAPES).toHaveProperty(type);
|
|
198
|
+
expect(decodeClientMessage(JSON.parse(JSON.stringify(sample)))).toEqual({
|
|
199
|
+
ok: true,
|
|
200
|
+
message: sample,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it.each(MALFORMED_CLIENT)('refuses %s', (_label, value) => {
|
|
206
|
+
expect(decodeClientMessage(value).ok).toBe(false);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('keeps the protocol wording for a bad submit prompt', () => {
|
|
210
|
+
expect(decodeClientMessage({ type: 'submit', prompt: 1 })).toEqual({
|
|
211
|
+
ok: false,
|
|
212
|
+
reason: 'prompt must be a non-empty string',
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe('decodeServerMessage (issue #2045)', () => {
|
|
218
|
+
it('has a shape for every union variant and every sample decodes through JSON', () => {
|
|
219
|
+
for (const [type, sample] of Object.entries(SERVER_SAMPLES)) {
|
|
220
|
+
expect(SERVER_MESSAGE_SHAPES).toHaveProperty(type);
|
|
221
|
+
expect(decodeServerMessage(JSON.parse(JSON.stringify(sample)))).toEqual({
|
|
222
|
+
ok: true,
|
|
223
|
+
message: sample,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it.each(MALFORMED_SERVER)('refuses %s', (_label, value) => {
|
|
229
|
+
expect(decodeServerMessage(value).ok).toBe(false);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
describe('decodeFrame — the one carrier path', () => {
|
|
234
|
+
it('refuses invalid JSON with the protocol wording and an oversized frame before parsing', () => {
|
|
235
|
+
expect(decodeFrame('not json', decodeClientMessage)).toEqual({
|
|
236
|
+
ok: false,
|
|
237
|
+
reason: 'Invalid JSON',
|
|
238
|
+
});
|
|
239
|
+
const huge = `{"type":"submit","prompt":"${'x'.repeat(MAX_INBOUND_FRAME_BYTES)}"}`;
|
|
240
|
+
expect(decodeFrame(huge, decodeClientMessage)).toEqual({
|
|
241
|
+
ok: false,
|
|
242
|
+
reason: 'Frame too large',
|
|
243
|
+
});
|
|
244
|
+
expect(decodeFrame('{"type":"abort"}', decodeClientMessage)).toEqual({
|
|
245
|
+
ok: true,
|
|
246
|
+
message: { type: 'abort' },
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
});
|