@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.
Files changed (113) hide show
  1. package/README.md +84 -26
  2. package/dist/browser/client.d.ts +2 -0
  3. package/dist/browser/client.js +1 -0
  4. package/dist/browser/index.d.ts +520 -0
  5. package/dist/browser/index.d.ts.map +1 -0
  6. package/dist/browser/index.js +2 -0
  7. package/dist/browser/index.js.map +1 -0
  8. package/dist/browser/message-decoders-BB8n-H2X.d.ts +252 -0
  9. package/dist/browser/message-decoders-BB8n-H2X.d.ts.map +1 -0
  10. package/dist/browser/message-decoders-CDmDoZPR.js +2 -0
  11. package/dist/browser/message-decoders-CDmDoZPR.js.map +1 -0
  12. package/dist/node/index.cjs +1 -1
  13. package/dist/node/index.d.cts +766 -0
  14. package/dist/node/index.d.cts.map +1 -0
  15. package/dist/node/index.d.ts +765 -27
  16. package/dist/node/index.d.ts.map +1 -1
  17. package/dist/node/index.js +1 -1
  18. package/dist/node/index.js.map +1 -1
  19. package/dist/node/node.cjs +1 -0
  20. package/dist/node/node.d.cts +216 -0
  21. package/dist/node/node.d.cts.map +1 -0
  22. package/dist/node/node.d.ts +216 -0
  23. package/dist/node/node.d.ts.map +1 -0
  24. package/dist/node/node.js +2 -0
  25. package/dist/node/node.js.map +1 -0
  26. package/package.json +68 -47
  27. package/src/__tests__/access-token-verifier.test.ts +672 -0
  28. package/src/__tests__/admission.test.ts +130 -0
  29. package/src/__tests__/channel-frames.test.ts +214 -0
  30. package/src/__tests__/driver-attribution-capability.test.ts +83 -0
  31. package/src/__tests__/file-transfer.test.ts +281 -0
  32. package/src/__tests__/handoff-chunking.test.ts +215 -0
  33. package/src/__tests__/handoff-manifest.test.ts +52 -0
  34. package/src/__tests__/message-decoders.test.ts +249 -0
  35. package/src/__tests__/outbound-delivery.test.ts +558 -0
  36. package/src/__tests__/peer-message-ledger.test.ts +200 -0
  37. package/src/__tests__/personal-usage-report.test.ts +193 -0
  38. package/src/__tests__/resume-buffer.test.ts +93 -0
  39. package/src/__tests__/session-event-delivery.test.ts +101 -0
  40. package/src/__tests__/session-message-handler-e5.test.ts +99 -0
  41. package/src/__tests__/session-message-handler.test.ts +590 -0
  42. package/src/__tests__/session-resume-bridge.test.ts +406 -0
  43. package/src/__tests__/submit-prompt-shape.test.ts +78 -0
  44. package/src/__tests__/usage-report-carrier.test.ts +98 -0
  45. package/src/__tests__/ws-broadcast-events.test.ts +84 -0
  46. package/src/background-messages.ts +169 -0
  47. package/src/channel-frames.ts +182 -0
  48. package/src/client.ts +9 -0
  49. package/src/handoff-chunking.ts +235 -0
  50. package/src/index.ts +112 -3
  51. package/src/message-decoders.ts +229 -0
  52. package/src/message-parser.ts +33 -0
  53. package/src/node/access-token-verifier.ts +363 -0
  54. package/src/node/admission.ts +153 -0
  55. package/src/node/file-transfer.ts +530 -0
  56. package/src/node/handoff-manifest.ts +77 -0
  57. package/src/node/index.ts +27 -0
  58. package/src/outbound-delivery.ts +231 -0
  59. package/src/peer-message-ledger.ts +193 -0
  60. package/src/protocol-session.ts +28 -0
  61. package/src/resume-buffer.ts +123 -0
  62. package/src/session-events.ts +198 -0
  63. package/src/session-message-handler.ts +282 -0
  64. package/src/session-query-messages.ts +42 -0
  65. package/src/session-resume-bridge.ts +252 -0
  66. package/src/usage-messages.ts +195 -0
  67. package/src/wire-messages.ts +165 -0
  68. package/dist/node/headless/index.cjs +0 -1
  69. package/dist/node/headless/index.d.ts +0 -2
  70. package/dist/node/headless/index.js +0 -1
  71. package/dist/node/headless-DG_MEbf0.js +0 -19
  72. package/dist/node/headless-DG_MEbf0.js.map +0 -1
  73. package/dist/node/headless-xq77JBq0.cjs +0 -18
  74. package/dist/node/index-CrJ2Lhn3.d.ts +0 -96
  75. package/dist/node/index-CrJ2Lhn3.d.ts.map +0 -1
  76. package/dist/node/index-JIvzrtaA.d.ts +0 -52
  77. package/dist/node/index-JIvzrtaA.d.ts.map +0 -1
  78. package/dist/node/index-JTD_xtEQ.d.ts +0 -96
  79. package/dist/node/index-JTD_xtEQ.d.ts.map +0 -1
  80. package/dist/node/index-y_HBOfzO.d.ts +0 -52
  81. package/dist/node/index-y_HBOfzO.d.ts.map +0 -1
  82. package/dist/node/programmatic/index.cjs +0 -1
  83. package/dist/node/programmatic/index.d.ts +0 -2
  84. package/dist/node/programmatic/index.js +0 -1
  85. package/dist/node/programmatic-6wLf68qJ.cjs +0 -1
  86. package/dist/node/programmatic-70dxIXtS.js +0 -2
  87. package/dist/node/programmatic-70dxIXtS.js.map +0 -1
  88. package/dist/node/testing/index.cjs +0 -1
  89. package/dist/node/testing/index.d.ts +0 -2
  90. package/dist/node/testing/index.js +0 -1
  91. package/src/__tests__/programmatic/programmatic-ask-user.test.ts +0 -32
  92. package/src/__tests__/programmatic/programmatic-driver.test.ts +0 -107
  93. package/src/__tests__/programmatic/programmatic-flows.test.ts +0 -140
  94. package/src/__tests__/transport-registry.stop-all.test.ts +0 -53
  95. package/src/headless/HeadlessInteractionChannel.ts +0 -129
  96. package/src/headless/__tests__/headless-channel-options.test.ts +0 -106
  97. package/src/headless/__tests__/headless-goal-runner.test.ts +0 -108
  98. package/src/headless/__tests__/headless-provider-failure.integration.test.ts +0 -143
  99. package/src/headless/__tests__/headless-runner-initialization.test.ts +0 -45
  100. package/src/headless/__tests__/headless-runner.test.ts +0 -508
  101. package/src/headless/__tests__/headless-skill-activation.integration.test.ts +0 -430
  102. package/src/headless/__tests__/headless-transport.test.ts +0 -267
  103. package/src/headless/cli-input.ts +0 -50
  104. package/src/headless/headless-runner.ts +0 -230
  105. package/src/headless/headless-stream-json.ts +0 -148
  106. package/src/headless/headless-transport.ts +0 -43
  107. package/src/headless/index.ts +0 -8
  108. package/src/headless/print-terminal.ts +0 -57
  109. package/src/programmatic/ProgrammaticInteractionChannel.ts +0 -82
  110. package/src/programmatic/createProgrammaticAgent.ts +0 -71
  111. package/src/programmatic/index.ts +0 -5
  112. package/src/testing/index.ts +0 -10
  113. 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
+ });