@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,766 @@
|
|
|
1
|
+
import { IPersonalUsageReport, IPersonalUsageRequest, IUsageBySourceReport, TUsageSurface } from "@robota-sdk/agent-interface-analytics";
|
|
2
|
+
import { ICommandResult } from "@robota-sdk/agent-interface-command";
|
|
3
|
+
import { IBackgroundJobGroupState, IBackgroundTaskInput, IBackgroundTaskListFilter, IBackgroundTaskLogCursor, IBackgroundTaskLogPage, IBackgroundTaskState, IExecutionWorkspaceSnapshot, TBackgroundJobGroupEvent, TBackgroundTaskEvent } from "@robota-sdk/agent-interface-execution";
|
|
4
|
+
import { IAskRequestEvent, IBranchEvent, IContextFileRefreshedEvent, IExecutionResult, IPermissionRequestEvent, IPlanApprovalEvent, IPromptResolvedEvent, ISessionBackgroundGroups, ISessionBackgroundTasks, ISessionCommands, ISessionConversationRead, ISessionDriverAttribution, ISessionEvents, ISessionExecutionState, ISessionExecutionWorkspace, ISessionPromptResolution, ISessionRenamedEvent, ISessionTurnControl, ISessionTurnSubmission, IToolState, IUiIntentEvent, TDriverId, TPermissionResultValue } from "@robota-sdk/agent-interface-session";
|
|
5
|
+
import { IBinaryFrame, IChannelEventFrame, TActionResponse, TChannelReceiveResult } from "@robota-sdk/agent-interface-transport";
|
|
6
|
+
import { IHandoffIntegrity, IPeerMessage, IPeerMessageAck, TPeerDeliveryState } from "@robota-sdk/agent-interface-session-mobility";
|
|
7
|
+
//#region src/wire-messages.d.ts
|
|
8
|
+
type TBackgroundControlAction = 'cancel' | 'close' | 'send';
|
|
9
|
+
/** Inbound message from client to server. */
|
|
10
|
+
type TClientMessage = {
|
|
11
|
+
type: 'submit';
|
|
12
|
+
prompt: string;
|
|
13
|
+
} | {
|
|
14
|
+
type: 'command';
|
|
15
|
+
name: string;
|
|
16
|
+
args?: string;
|
|
17
|
+
} | {
|
|
18
|
+
type: 'abort';
|
|
19
|
+
} | {
|
|
20
|
+
type: 'cancel-queue';
|
|
21
|
+
} | {
|
|
22
|
+
type: 'get-messages';
|
|
23
|
+
} | {
|
|
24
|
+
type: 'get-context';
|
|
25
|
+
} | {
|
|
26
|
+
type: 'get-usage-report';
|
|
27
|
+
} | {
|
|
28
|
+
type: 'get-personal-usage-report';
|
|
29
|
+
requestId: string;
|
|
30
|
+
period: IPersonalUsageRequest['period'];
|
|
31
|
+
timezone: string;
|
|
32
|
+
} | {
|
|
33
|
+
type: 'get-stored-session-usage-report';
|
|
34
|
+
requestId: string;
|
|
35
|
+
sessionId: string;
|
|
36
|
+
} | {
|
|
37
|
+
type: 'get-executing';
|
|
38
|
+
} | {
|
|
39
|
+
type: 'get-pending';
|
|
40
|
+
} | {
|
|
41
|
+
type: 'get-execution-workspace';
|
|
42
|
+
} | {
|
|
43
|
+
type: 'get-background-tasks';
|
|
44
|
+
filter?: IBackgroundTaskListFilter;
|
|
45
|
+
} | {
|
|
46
|
+
type: 'get-background-task';
|
|
47
|
+
taskId: string;
|
|
48
|
+
} | {
|
|
49
|
+
type: 'get-background-job-groups';
|
|
50
|
+
} | {
|
|
51
|
+
type: 'get-background-job-group';
|
|
52
|
+
groupId: string;
|
|
53
|
+
} | {
|
|
54
|
+
type: 'wait-background-job-group';
|
|
55
|
+
groupId: string;
|
|
56
|
+
} | {
|
|
57
|
+
type: 'cancel-background-task';
|
|
58
|
+
taskId: string;
|
|
59
|
+
reason?: string;
|
|
60
|
+
} | {
|
|
61
|
+
type: 'close-background-task';
|
|
62
|
+
taskId: string;
|
|
63
|
+
} | {
|
|
64
|
+
type: 'send-background-task';
|
|
65
|
+
taskId: string;
|
|
66
|
+
input: IBackgroundTaskInput;
|
|
67
|
+
} | {
|
|
68
|
+
type: 'read-background-task-log';
|
|
69
|
+
taskId: string;
|
|
70
|
+
cursor?: IBackgroundTaskLogCursor;
|
|
71
|
+
} | {
|
|
72
|
+
type: 'permission-response';
|
|
73
|
+
id: string;
|
|
74
|
+
result: TPermissionResultValue;
|
|
75
|
+
} | {
|
|
76
|
+
type: 'ask-response';
|
|
77
|
+
id: string;
|
|
78
|
+
response: TActionResponse;
|
|
79
|
+
} | {
|
|
80
|
+
type: 'resume';
|
|
81
|
+
lastSeq: number;
|
|
82
|
+
} | {
|
|
83
|
+
type: 'ack';
|
|
84
|
+
seq: number;
|
|
85
|
+
};
|
|
86
|
+
/** Outbound message from server to client. */
|
|
87
|
+
type TServerMessage = {
|
|
88
|
+
type: 'text_delta';
|
|
89
|
+
delta: string;
|
|
90
|
+
driverId?: TDriverId;
|
|
91
|
+
} | {
|
|
92
|
+
type: 'user_message';
|
|
93
|
+
content: string;
|
|
94
|
+
driverId?: TDriverId;
|
|
95
|
+
} | {
|
|
96
|
+
type: 'tool_start';
|
|
97
|
+
state: IToolState;
|
|
98
|
+
driverId?: TDriverId;
|
|
99
|
+
} | {
|
|
100
|
+
type: 'tool_end';
|
|
101
|
+
state: IToolState;
|
|
102
|
+
driverId?: TDriverId;
|
|
103
|
+
} | {
|
|
104
|
+
type: 'thinking';
|
|
105
|
+
isThinking: boolean;
|
|
106
|
+
driverId?: TDriverId;
|
|
107
|
+
} | {
|
|
108
|
+
type: 'complete';
|
|
109
|
+
result: IExecutionResult;
|
|
110
|
+
driverId?: TDriverId;
|
|
111
|
+
} | {
|
|
112
|
+
type: 'interrupted';
|
|
113
|
+
result: IExecutionResult;
|
|
114
|
+
driverId?: TDriverId;
|
|
115
|
+
} | {
|
|
116
|
+
type: 'error';
|
|
117
|
+
message: string;
|
|
118
|
+
driverId?: TDriverId;
|
|
119
|
+
} | {
|
|
120
|
+
type: 'command_result';
|
|
121
|
+
name: string;
|
|
122
|
+
message: string;
|
|
123
|
+
success: boolean;
|
|
124
|
+
data?: ICommandResult['data'];
|
|
125
|
+
} | {
|
|
126
|
+
type: 'messages';
|
|
127
|
+
messages: ReturnType<ISessionConversationRead['getMessages']>;
|
|
128
|
+
} | {
|
|
129
|
+
type: 'context';
|
|
130
|
+
state: ReturnType<ISessionConversationRead['getContextState']>;
|
|
131
|
+
} | {
|
|
132
|
+
type: 'usage_report';
|
|
133
|
+
report: IUsageBySourceReport;
|
|
134
|
+
} | {
|
|
135
|
+
type: 'personal_usage_report';
|
|
136
|
+
requestId: string;
|
|
137
|
+
report: IPersonalUsageReport;
|
|
138
|
+
} | {
|
|
139
|
+
type: 'personal_usage_report_error';
|
|
140
|
+
requestId: string;
|
|
141
|
+
code: 'not_available' | 'report_failed';
|
|
142
|
+
message: string;
|
|
143
|
+
} | {
|
|
144
|
+
type: 'stored_session_usage_report';
|
|
145
|
+
requestId: string;
|
|
146
|
+
sessionId: string;
|
|
147
|
+
report: IUsageBySourceReport;
|
|
148
|
+
} | {
|
|
149
|
+
type: 'stored_session_usage_report_error';
|
|
150
|
+
requestId: string;
|
|
151
|
+
sessionId: string;
|
|
152
|
+
code: 'not_available' | 'report_failed';
|
|
153
|
+
message: string;
|
|
154
|
+
} | {
|
|
155
|
+
type: 'executing';
|
|
156
|
+
executing: boolean;
|
|
157
|
+
} | {
|
|
158
|
+
type: 'pending';
|
|
159
|
+
pending: string | null;
|
|
160
|
+
} | {
|
|
161
|
+
type: 'execution_workspace_event';
|
|
162
|
+
snapshot: IExecutionWorkspaceSnapshot;
|
|
163
|
+
} | {
|
|
164
|
+
type: 'background_task_event';
|
|
165
|
+
event: TBackgroundTaskEvent;
|
|
166
|
+
} | {
|
|
167
|
+
type: 'background_job_group_event';
|
|
168
|
+
event: TBackgroundJobGroupEvent;
|
|
169
|
+
} | {
|
|
170
|
+
type: 'plan_event';
|
|
171
|
+
event: IPlanApprovalEvent;
|
|
172
|
+
} | {
|
|
173
|
+
type: 'context_file_refreshed';
|
|
174
|
+
event: IContextFileRefreshedEvent;
|
|
175
|
+
} | {
|
|
176
|
+
type: 'branch_event';
|
|
177
|
+
event: IBranchEvent;
|
|
178
|
+
} | {
|
|
179
|
+
type: 'background_tasks';
|
|
180
|
+
tasks: IBackgroundTaskState[];
|
|
181
|
+
} | {
|
|
182
|
+
type: 'background_task';
|
|
183
|
+
taskId: string;
|
|
184
|
+
task: IBackgroundTaskState | null;
|
|
185
|
+
} | {
|
|
186
|
+
type: 'background_job_groups';
|
|
187
|
+
groups: IBackgroundJobGroupState[];
|
|
188
|
+
} | {
|
|
189
|
+
type: 'background_job_group';
|
|
190
|
+
groupId: string;
|
|
191
|
+
group: IBackgroundJobGroupState | null;
|
|
192
|
+
} | {
|
|
193
|
+
type: 'background_task_log';
|
|
194
|
+
taskId: string;
|
|
195
|
+
page: IBackgroundTaskLogPage;
|
|
196
|
+
} | {
|
|
197
|
+
type: 'permission_request';
|
|
198
|
+
event: IPermissionRequestEvent;
|
|
199
|
+
} | {
|
|
200
|
+
type: 'ask_request';
|
|
201
|
+
event: IAskRequestEvent;
|
|
202
|
+
} | {
|
|
203
|
+
type: 'prompt_resolved';
|
|
204
|
+
event: IPromptResolvedEvent;
|
|
205
|
+
} | {
|
|
206
|
+
type: 'ui_intent';
|
|
207
|
+
event: IUiIntentEvent;
|
|
208
|
+
} | {
|
|
209
|
+
type: 'session_renamed';
|
|
210
|
+
event: ISessionRenamedEvent;
|
|
211
|
+
} | {
|
|
212
|
+
type: 'history_cleared';
|
|
213
|
+
} | {
|
|
214
|
+
type: 'background_task_control_result';
|
|
215
|
+
action: TBackgroundControlAction;
|
|
216
|
+
taskId: string;
|
|
217
|
+
success: boolean;
|
|
218
|
+
message?: string;
|
|
219
|
+
} | {
|
|
220
|
+
type: 'protocol_error';
|
|
221
|
+
message: string;
|
|
222
|
+
} | {
|
|
223
|
+
type: 'resume_gap';
|
|
224
|
+
};
|
|
225
|
+
/**
|
|
226
|
+
* REMOTE-013 E4: a server message stamped with its monotonic session sequence number (added by the
|
|
227
|
+
* {@link SessionResumeBridge} on the reconnectable WebRTC path). Intersecting over the union distributes the
|
|
228
|
+
* `seq` field onto every variant. The WS localhost path never stamps it (a `type`-dispatching client ignores
|
|
229
|
+
* an absent/extra `seq`).
|
|
230
|
+
*/
|
|
231
|
+
type TSeqServerMessage = TServerMessage & {
|
|
232
|
+
seq: number;
|
|
233
|
+
};
|
|
234
|
+
//#endregion
|
|
235
|
+
//#region src/outbound-delivery.d.ts
|
|
236
|
+
declare const outboundDeliveryBrand: unique symbol;
|
|
237
|
+
/**
|
|
238
|
+
* Deliver one outbound frame on a connection. A carrier failure is REPORTED through the boundary's
|
|
239
|
+
* error handler, never thrown at the caller — so a reply resolving after a disconnect cannot escape as
|
|
240
|
+
* an unhandled rejection or out of the carrier's inbound listener.
|
|
241
|
+
*
|
|
242
|
+
* Only {@link createOutboundDelivery} produces one. The brand is the mechanism, not decoration: it is
|
|
243
|
+
* what makes a raw carrier `send` unusable where a boundary is required.
|
|
244
|
+
*/
|
|
245
|
+
type TOutboundDeliver = ((message: TServerMessage) => void) & {
|
|
246
|
+
readonly [outboundDeliveryBrand]: true;
|
|
247
|
+
/**
|
|
248
|
+
* Bytes this carrier has ACCEPTED and not yet written to the peer — or `undefined` when it cannot
|
|
249
|
+
* say.
|
|
250
|
+
*
|
|
251
|
+
* `deliver` returning is not delivery. Both carriers hand this boundary a fire-and-forget sink, so
|
|
252
|
+
* a frame that has "been sent" may be sitting in a socket buffer the boundary cannot see. Anything
|
|
253
|
+
* this boundary counted itself would be a count of what it HANDED OVER, which is a different
|
|
254
|
+
* quantity from what the peer has not read — and a budget over the wrong quantity reports a
|
|
255
|
+
* healthy connection for a peer that stopped reading (ARCH-030 / issue #1734).
|
|
256
|
+
*
|
|
257
|
+
* `undefined` is deliberately not `0`. A carrier that cannot report backpressure and a carrier with
|
|
258
|
+
* nothing pending are different states, and collapsing them would let "unknown" satisfy any
|
|
259
|
+
* threshold placed on this number.
|
|
260
|
+
*/
|
|
261
|
+
readonly pendingBytes: () => number | undefined;
|
|
262
|
+
};
|
|
263
|
+
/**
|
|
264
|
+
* Observe THIS connection's first outbound delivery failure. `event` is the `type` of the frame that
|
|
265
|
+
* could not be delivered — a session event's own name for the fan-out (they are identical), and the
|
|
266
|
+
* reply's type for a reply (`command_result`, `protocol_error`, …).
|
|
267
|
+
*
|
|
268
|
+
* Required, never optional: a carrier that could opt out of observing its own delivery failures is the
|
|
269
|
+
* silent-failure shape this boundary exists to remove.
|
|
270
|
+
*/
|
|
271
|
+
type TDeliveryErrorHandler = (error: Error, event: TServerMessage['type']) => void;
|
|
272
|
+
declare const DEFAULT_MAX_PENDING_BYTES: number;
|
|
273
|
+
/**
|
|
274
|
+
* Is this carrier holding more than the budget allows?
|
|
275
|
+
*
|
|
276
|
+
* Exported because a WebSocket carries TWO kinds of outbound frame over one socket — the text
|
|
277
|
+
* protocol and TRANS-001's payload channels — and only one of them goes through
|
|
278
|
+
* {@link createOutboundDelivery}. A budget the binary path re-implemented would be a second opinion
|
|
279
|
+
* about one socket's backpressure; a budget it skipped would leave the same non-reading peer
|
|
280
|
+
* unbounded on the other half of the connection.
|
|
281
|
+
*
|
|
282
|
+
* `undefined` is unknown, not zero: a carrier that cannot report backpressure is never over budget.
|
|
283
|
+
*/
|
|
284
|
+
declare function isOverPendingBudget(pending: number | undefined, limit?: number): boolean;
|
|
285
|
+
/**
|
|
286
|
+
* Default outbound DRAIN budget (issue #2306): how long a carrier may stay non-empty before the peer
|
|
287
|
+
* is treated as not reading.
|
|
288
|
+
*
|
|
289
|
+
* 120 seconds. The byte budget bounds HOW MUCH a peer holds; it cannot bound HOW LONG, because
|
|
290
|
+
* `bufferedAmount` is a quantity with no history — a peer draining just enough to stay under 8 MiB
|
|
291
|
+
* forever is never over it. The figure is generous on purpose: a legitimately slow link drains to
|
|
292
|
+
* zero between bursts, so the clock resets, whereas a peer that keeps SOMETHING pending for two
|
|
293
|
+
* minutes straight is holding a session, its resume buffer, and a socket it does not intend to read.
|
|
294
|
+
* A badly chosen figure here disconnects real users, which is why it is one named constant and an
|
|
295
|
+
* argument, never an inline number. `Infinity` disables the budget.
|
|
296
|
+
*/
|
|
297
|
+
declare const DEFAULT_MAX_PENDING_MS: number;
|
|
298
|
+
/**
|
|
299
|
+
* The drain clock the boundary keeps for a carrier (issue #2306). One timestamp, not per-frame
|
|
300
|
+
* bookkeeping: it starts on the first reading that finds the carrier non-empty, RESETS only when a
|
|
301
|
+
* reading finds it empty (a peer that drains to one byte and stalls again has not drained), and is
|
|
302
|
+
* consulted synchronously at each send — the boundary has no timer and gains none here.
|
|
303
|
+
*/
|
|
304
|
+
interface IPendingStallClock {
|
|
305
|
+
readonly maxPendingMs: number;
|
|
306
|
+
/**
|
|
307
|
+
* Record one reading of the carrier's pending bytes. Returns how long the carrier has been
|
|
308
|
+
* continuously non-empty when that exceeds the budget; `undefined` otherwise. An `undefined`
|
|
309
|
+
* reading is unknown, not zero: it neither starts nor resets the clock.
|
|
310
|
+
*/
|
|
311
|
+
observe: (pending: number | undefined) => number | undefined;
|
|
312
|
+
}
|
|
313
|
+
declare function createPendingStallClock(maxPendingMs?: number, now?: () => number): IPendingStallClock;
|
|
314
|
+
declare function createOutboundDelivery(send: (message: TServerMessage) => void, onDeliveryError: TDeliveryErrorHandler, pendingBytes?: () => number | undefined, maxPendingBytes?: number, stallClock?: IPendingStallClock): TOutboundDeliver;
|
|
315
|
+
//#endregion
|
|
316
|
+
//#region src/protocol-session.d.ts
|
|
317
|
+
/** Session roles required by the shared WebSocket/WebRTC protocol. */
|
|
318
|
+
interface IProtocolSession extends ISessionTurnSubmission, ISessionTurnControl, ISessionCommands, ISessionEvents, ISessionPromptResolution, ISessionConversationRead, ISessionExecutionState, ISessionDriverAttribution, ISessionBackgroundTasks, ISessionBackgroundGroups, ISessionExecutionWorkspace {}
|
|
319
|
+
//#endregion
|
|
320
|
+
//#region src/usage-messages.d.ts
|
|
321
|
+
interface IUsageQueryReporters {
|
|
322
|
+
readonly personalUsageReporter: ((request: IPersonalUsageRequest) => IPersonalUsageReport | Promise<IPersonalUsageReport>) | undefined;
|
|
323
|
+
readonly usageReporter: ((session: IProtocolSession) => IUsageBySourceReport | Promise<IUsageBySourceReport>) | undefined;
|
|
324
|
+
readonly storedSessionUsageReporter: ((sessionId: string) => IUsageBySourceReport | Promise<IUsageBySourceReport>) | undefined;
|
|
325
|
+
}
|
|
326
|
+
//#endregion
|
|
327
|
+
//#region src/session-events.d.ts
|
|
328
|
+
type TProtocolSessionEventClassification = 'forwarded' | 'requester-routed' | 'non-surface';
|
|
329
|
+
/** Exhaustive protocol-owner policy over the shared session-event vocabulary. */
|
|
330
|
+
declare const PROTOCOL_SESSION_EVENT_CLASSIFICATION: {
|
|
331
|
+
readonly text_delta: "forwarded";
|
|
332
|
+
readonly tool_start: "forwarded";
|
|
333
|
+
readonly tool_end: "forwarded";
|
|
334
|
+
readonly thinking: "forwarded";
|
|
335
|
+
readonly complete: "forwarded";
|
|
336
|
+
readonly error: "forwarded";
|
|
337
|
+
readonly context_update: "non-surface";
|
|
338
|
+
readonly compact: "non-surface";
|
|
339
|
+
readonly interrupted: "forwarded";
|
|
340
|
+
readonly skill_activation: "non-surface";
|
|
341
|
+
readonly background_task_event: "forwarded";
|
|
342
|
+
readonly background_job_group_event: "forwarded";
|
|
343
|
+
readonly execution_workspace_event: "forwarded";
|
|
344
|
+
readonly user_message: "forwarded";
|
|
345
|
+
readonly turn_source: "non-surface";
|
|
346
|
+
readonly context_file_refreshed: "forwarded";
|
|
347
|
+
readonly memory_event: "non-surface";
|
|
348
|
+
readonly goal_event: "non-surface";
|
|
349
|
+
readonly plan_event: "forwarded";
|
|
350
|
+
readonly branch_event: "forwarded";
|
|
351
|
+
readonly permission_request: "forwarded";
|
|
352
|
+
readonly ask_request: "forwarded";
|
|
353
|
+
readonly prompt_resolved: "forwarded";
|
|
354
|
+
readonly ui_intent: "requester-routed";
|
|
355
|
+
readonly session_renamed: "forwarded";
|
|
356
|
+
readonly history_cleared: "forwarded";
|
|
357
|
+
};
|
|
358
|
+
//#endregion
|
|
359
|
+
//#region src/session-message-handler.d.ts
|
|
360
|
+
interface ISessionMessageHandlerOptions {
|
|
361
|
+
/** IProtocolSession to expose. */
|
|
362
|
+
session: IProtocolSession;
|
|
363
|
+
/**
|
|
364
|
+
* ARCH-030: the CARRIER's connection-scoped outbound delivery boundary — not a raw `send`, and not a
|
|
365
|
+
* `send` plus an error callback for this handler to assemble into one. The carrier owns both the sink
|
|
366
|
+
* and the "what does a failed send mean for this connection" policy, so it builds the boundary and
|
|
367
|
+
* passes it down; the raw sink never crosses this parameter, which is what stops a future reply family
|
|
368
|
+
* from reaching the wire unguarded.
|
|
369
|
+
*/
|
|
370
|
+
deliver: TOutboundDeliver;
|
|
371
|
+
/**
|
|
372
|
+
* REMOTE-014 E5: the SERVER-ASSIGNED driver id for THIS remote surface (the E3 `deviceId`). Injected into
|
|
373
|
+
* every inbound `submit`/`command`/prompt-response so a co-drive turn/answer is attributed to this driver —
|
|
374
|
+
* a client-supplied driver id is NEVER trusted. Absent → unattributed (the session defaults to the owner).
|
|
375
|
+
*/
|
|
376
|
+
driverId?: TDriverId;
|
|
377
|
+
/** Trusted carrier-owned product surface, kept separate from driver identity. */
|
|
378
|
+
surface?: TUsageSurface;
|
|
379
|
+
/** Host-owned cross-session read model. The protocol only correlates and carries its result. */
|
|
380
|
+
personalUsageReporter?: NonNullable<IUsageQueryReporters['personalUsageReporter']>;
|
|
381
|
+
/** Host-owned current-session trace/cost producer for the pre-existing message family. */
|
|
382
|
+
usageReporter?: NonNullable<IUsageQueryReporters['usageReporter']>;
|
|
383
|
+
/** Host-owned stored-session producer used by cross-session drill-down. */
|
|
384
|
+
storedSessionUsageReporter?: NonNullable<IUsageQueryReporters['storedSessionUsageReporter']>;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Create a carrier-neutral message handler for an IProtocolSession.
|
|
388
|
+
*
|
|
389
|
+
* Returns:
|
|
390
|
+
* - `onMessage(data)`: call this when the WebSocket receives a message
|
|
391
|
+
* - `cleanup()`: call this when the WebSocket disconnects
|
|
392
|
+
*
|
|
393
|
+
* Usage:
|
|
394
|
+
* ```typescript
|
|
395
|
+
* const delivery = createOutboundDelivery(
|
|
396
|
+
* (msg) => ws.send(JSON.stringify(msg)),
|
|
397
|
+
* (error) => ws.close(1011, error.message),
|
|
398
|
+
* );
|
|
399
|
+
* const { onMessage, cleanup } = createSessionMessageHandler({ session: interactiveSession, deliver: delivery });
|
|
400
|
+
*
|
|
401
|
+
* ws.on('message', (data) => onMessage(String(data)));
|
|
402
|
+
* ws.on('close', cleanup);
|
|
403
|
+
* ```
|
|
404
|
+
*/
|
|
405
|
+
declare function createSessionMessageHandler(options: ISessionMessageHandlerOptions): {
|
|
406
|
+
onMessage: (data: string) => void;
|
|
407
|
+
cleanup: () => void;
|
|
408
|
+
};
|
|
409
|
+
//#endregion
|
|
410
|
+
//#region src/message-decoders.d.ts
|
|
411
|
+
type TMessageDecodeResult<TMessage> = {
|
|
412
|
+
readonly ok: true;
|
|
413
|
+
readonly message: TMessage;
|
|
414
|
+
} | {
|
|
415
|
+
readonly ok: false;
|
|
416
|
+
readonly reason: string;
|
|
417
|
+
};
|
|
418
|
+
/** Serialized frames larger than this are refused before any parse (a bounded inbound budget). */
|
|
419
|
+
declare const MAX_INBOUND_FRAME_BYTES: number;
|
|
420
|
+
/** Total decode of a parsed client frame. */
|
|
421
|
+
declare function decodeClientMessage(value: unknown): TMessageDecodeResult<TClientMessage>;
|
|
422
|
+
/** Variant-level decode of a parsed server frame (foreign-owned payloads as records, see header). */
|
|
423
|
+
declare function decodeServerMessage(value: unknown): TMessageDecodeResult<TServerMessage>;
|
|
424
|
+
/** `raw string → JSON → owner decoder`, the one path a carrier implements. */
|
|
425
|
+
declare function decodeFrame<TMessage>(data: string, decode: (value: unknown) => TMessageDecodeResult<TMessage>): TMessageDecodeResult<TMessage>;
|
|
426
|
+
//#endregion
|
|
427
|
+
//#region src/resume-buffer.d.ts
|
|
428
|
+
/** One retained frame: its sequence number and the message. */
|
|
429
|
+
interface IBufferedFrame {
|
|
430
|
+
readonly seq: number;
|
|
431
|
+
readonly message: TServerMessage;
|
|
432
|
+
}
|
|
433
|
+
/** The result of a `tailAfter` — either the ordered replay slice, or an overrun (client must full-refresh). */
|
|
434
|
+
type TResumeTail = {
|
|
435
|
+
readonly kind: 'tail';
|
|
436
|
+
readonly frames: readonly IBufferedFrame[];
|
|
437
|
+
} | {
|
|
438
|
+
readonly kind: 'overrun';
|
|
439
|
+
};
|
|
440
|
+
interface IResumeBufferOptions {
|
|
441
|
+
/** Max retained frames (drop-oldest beyond this). Default 512. */
|
|
442
|
+
readonly maxFrames?: number;
|
|
443
|
+
/** Max retained bytes across frames (drop-oldest beyond this). Default 4 MiB. */
|
|
444
|
+
readonly maxBytes?: number;
|
|
445
|
+
/** Serialized size of a message (for the byte budget). Default: JSON length. Injectable for tests. */
|
|
446
|
+
readonly sizeOf?: (message: TServerMessage) => number;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* A monotonic, bounded ring of un-acked frames. Sequence numbers are assigned by {@link append} and never
|
|
450
|
+
* reused; `tailAfter` / `ackThrough` operate over the retained window.
|
|
451
|
+
*/
|
|
452
|
+
declare class ResumeBuffer {
|
|
453
|
+
private readonly frames;
|
|
454
|
+
private readonly sizes;
|
|
455
|
+
private nextSeq;
|
|
456
|
+
private bytes;
|
|
457
|
+
private readonly maxFrames;
|
|
458
|
+
private readonly maxBytes;
|
|
459
|
+
private readonly sizeOf;
|
|
460
|
+
constructor(options?: IResumeBufferOptions);
|
|
461
|
+
/** Append a message, assign + return its monotonic seq, and evict oldest frames beyond the bounds. */
|
|
462
|
+
append(message: TServerMessage): number;
|
|
463
|
+
/** Drop all retained frames with `seq <= throughSeq` (the client confirmed receipt up to there). */
|
|
464
|
+
ackThrough(throughSeq: number): void;
|
|
465
|
+
/**
|
|
466
|
+
* The ordered frames with `seq > lastSeq`. Returns an `overrun` marker when `lastSeq` is older than the
|
|
467
|
+
* oldest retained frame (i.e. some frame after `lastSeq` was already evicted) — the client must full-refresh.
|
|
468
|
+
* `lastSeq` at or beyond the newest seq yields an empty tail (nothing to replay).
|
|
469
|
+
*/
|
|
470
|
+
tailAfter(lastSeq: number): TResumeTail;
|
|
471
|
+
/** The last seq assigned (0 when nothing has been appended). */
|
|
472
|
+
get lastSeq(): number;
|
|
473
|
+
/** Retained frame count (diagnostics/tests). */
|
|
474
|
+
get size(): number;
|
|
475
|
+
private evict;
|
|
476
|
+
/** The single place that mutates `frames`/`sizes`/`bytes` for removal, so byte accounting can't drift. */
|
|
477
|
+
private removeOldest;
|
|
478
|
+
}
|
|
479
|
+
//#endregion
|
|
480
|
+
//#region src/channel-frames.d.ts
|
|
481
|
+
/** `R`, `B`, `F` — the shared prefix every channel frame starts with. */
|
|
482
|
+
declare const CHANNEL_FRAME_MAGIC: Uint8Array;
|
|
483
|
+
/** Envelope version. Bumped only on an incompatible layout change. */
|
|
484
|
+
declare const CHANNEL_FRAME_VERSION = 1;
|
|
485
|
+
/** Encode an opaque binary frame. The payload bytes are copied verbatim. */
|
|
486
|
+
declare function encodeBinaryFrame(frame: IBinaryFrame): Uint8Array;
|
|
487
|
+
/** Encode a structured, consumer-declared event frame (UTF-8 JSON body). */
|
|
488
|
+
declare function encodeChannelEventFrame(frame: IChannelEventFrame): Uint8Array;
|
|
489
|
+
/** True when `bytes` starts with the channel-frame magic — a cheap, non-parsing routing check. */
|
|
490
|
+
declare function isChannelFrame(bytes: Uint8Array): boolean;
|
|
491
|
+
/**
|
|
492
|
+
* Decode a channel frame. Returns an explicit result union — a non-channel, truncated, or malformed
|
|
493
|
+
* input is a stated error the carrier reports to the peer, never a thrown exception on the hot path
|
|
494
|
+
* and never a silently dropped frame.
|
|
495
|
+
*/
|
|
496
|
+
declare function decodeChannelFrame(bytes: Uint8Array): TChannelReceiveResult;
|
|
497
|
+
//#endregion
|
|
498
|
+
//#region src/session-resume-bridge.d.ts
|
|
499
|
+
/** The current channel sink — receives a serialized JSON frame to put on the wire. */
|
|
500
|
+
type TResumeSink = (data: string) => void;
|
|
501
|
+
/** Options for {@link SessionResumeBridge.attach}. */
|
|
502
|
+
interface IAttachOptions {
|
|
503
|
+
/**
|
|
504
|
+
* REMOTE-013 E4: on a RECONNECT, HOLD live sink-forwarding until the client's `resume` has replayed the
|
|
505
|
+
* buffered tail — otherwise a live frame emitted between attach and the inbound `resume` would leapfrog the
|
|
506
|
+
* gap frames on the wire, and the client's seq-dedup would then discard the (older, lower-seq) gap. Every
|
|
507
|
+
* live event is already appended to the buffer, so holding loses nothing; `replay()` flushes in order and
|
|
508
|
+
* un-holds. First-pair attach does NOT hold (the client starts at seq 0, frames arrive in order).
|
|
509
|
+
*/
|
|
510
|
+
readonly awaitResume?: boolean;
|
|
511
|
+
/** Sink/carrier-owned failure lifecycle for this attachment only. */
|
|
512
|
+
readonly onDeliveryError: (error: Error, event: string) => void;
|
|
513
|
+
/**
|
|
514
|
+
* ARCH-030: this attachment's carrier backpressure reading, if it has one.
|
|
515
|
+
*
|
|
516
|
+
* Threaded through because the replay path is the burst this budget exists for — a reconnecting
|
|
517
|
+
* peer is handed the whole retained tail at once, and without a reading the boundary here would
|
|
518
|
+
* have no budget at all while the per-channel boundary did.
|
|
519
|
+
*/
|
|
520
|
+
readonly pendingBytes?: () => number | undefined;
|
|
521
|
+
}
|
|
522
|
+
interface ISessionResumeBridgeOptions {
|
|
523
|
+
readonly session: IProtocolSession;
|
|
524
|
+
readonly buffer?: IResumeBufferOptions;
|
|
525
|
+
/** REMOTE-014 E5: the SERVER-ASSIGNED driver id for this surface, injected into inbound submit/command/prompt-response. */
|
|
526
|
+
readonly driverId?: TDriverId;
|
|
527
|
+
readonly surface?: TUsageSurface;
|
|
528
|
+
readonly personalUsageReporter?: IUsageQueryReporters['personalUsageReporter'];
|
|
529
|
+
readonly usageReporter?: IUsageQueryReporters['usageReporter'];
|
|
530
|
+
readonly storedSessionUsageReporter?: IUsageQueryReporters['storedSessionUsageReporter'];
|
|
531
|
+
}
|
|
532
|
+
declare class SessionResumeBridge {
|
|
533
|
+
private readonly session;
|
|
534
|
+
private readonly buffer;
|
|
535
|
+
private driverId?;
|
|
536
|
+
private readonly reporters;
|
|
537
|
+
private readonly surface?;
|
|
538
|
+
private readonly unsubscribe;
|
|
539
|
+
private onDeliveryError?;
|
|
540
|
+
/**
|
|
541
|
+
* ARCH-030: the attachment's outbound boundary — and the only reference this class keeps to the current
|
|
542
|
+
* channel, which is why `dispose()` goes through `detach()` rather than clearing a separate field.
|
|
543
|
+
* Built per `attach` because a boundary LATCHES on its first failure, so a fresh one is exactly what
|
|
544
|
+
* un-latches the session for the next channel. It holds the only try/catch on the frame path.
|
|
545
|
+
*/
|
|
546
|
+
private outbound?;
|
|
547
|
+
/**
|
|
548
|
+
* The session-lifetime entry every frame takes — event fan-out AND every reply to an inbound frame —
|
|
549
|
+
* before it is seq-stamped and buffered by {@link emit}. Distinct from {@link outbound}, which is the
|
|
550
|
+
* per-attachment exit to the current channel; this one outlives channels because the subscription does.
|
|
551
|
+
*
|
|
552
|
+
* It guards the BUFFERING step, not a carrier, and it latches for the life of the session on purpose:
|
|
553
|
+
* the failure it is placed against is the buffer refusing a frame on capacity, and a resume buffer that
|
|
554
|
+
* cannot accept frames cannot honour a later `resume` either, so there is nothing a reconnect recovers.
|
|
555
|
+
* That is the opposite of {@link outbound}, where the next channel IS the recovery — the two scopes
|
|
556
|
+
* differ because the failures do. (A per-frame serialization failure would leave the buffer intact, but
|
|
557
|
+
* such a frame is undeliverable on every carrier, since each one stringifies it too; it is reported and
|
|
558
|
+
* the carrier torn down, never silently dropped.)
|
|
559
|
+
*/
|
|
560
|
+
private readonly emitBoundary;
|
|
561
|
+
private disposed;
|
|
562
|
+
/** REMOTE-013 E4: while true, live emits are buffered but NOT forwarded — released by `replay()` on reconnect. */
|
|
563
|
+
private holding;
|
|
564
|
+
constructor(options: ISessionResumeBridgeOptions);
|
|
565
|
+
/** Set the current channel sink (on connect / reconnect). Live messages reach the client; replay is `resume`-driven. */
|
|
566
|
+
attach(sink: TResumeSink, options: IAttachOptions): void;
|
|
567
|
+
/** Clear the sink (channel drop). Buffering continues so gap output is retained for the next `resume`. */
|
|
568
|
+
detach(): void;
|
|
569
|
+
/**
|
|
570
|
+
* REMOTE-014 E5: bind the SERVER-ASSIGNED driver id (the E3 `deviceId`) once the peer pairs — the bridge
|
|
571
|
+
* injects it into every subsequent inbound submit/command/prompt-response for co-drive attribution.
|
|
572
|
+
*/
|
|
573
|
+
setDriverId(driverId: TDriverId): void;
|
|
574
|
+
/** Route one inbound channel frame: `resume`/`ack` handled here; everything else → the session. */
|
|
575
|
+
onClientMessage(data: string): void;
|
|
576
|
+
/** Unsubscribe from the session (host reconnect-window ceiling / teardown). Idempotent. */
|
|
577
|
+
dispose(): void;
|
|
578
|
+
/** Diagnostics/tests: the last seq assigned. */
|
|
579
|
+
get lastSeq(): number;
|
|
580
|
+
/**
|
|
581
|
+
* Stamp a seq, retain in the buffer, and forward to the current sink — UNLESS holding for a reconnect replay.
|
|
582
|
+
*
|
|
583
|
+
* ARCH-030: the `buffer.append` happens BEFORE the boundary, deliberately. The boundary latches on its
|
|
584
|
+
* first failure and drops everything after it, so a frame appended afterwards would be lost — appending
|
|
585
|
+
* first means a dropped frame is still in the un-acked tail and still replays on the next `resume`.
|
|
586
|
+
*/
|
|
587
|
+
private emit;
|
|
588
|
+
/**
|
|
589
|
+
* Replay the buffered tail after `lastSeq` (or `resume_gap` on overrun), then RELEASE the reconnect hold so
|
|
590
|
+
* subsequent live frames flow in order behind the flushed gap.
|
|
591
|
+
*
|
|
592
|
+
* A failing sink reports ONCE across the whole tail: the boundary latches on the first failure and every
|
|
593
|
+
* remaining frame is dropped silently. Those frames stay un-acked in the buffer and replay on the next
|
|
594
|
+
* attachment. Before ARCH-030 the single report was a side effect of `detach()` running mid-loop; now it
|
|
595
|
+
* is the boundary's stated contract.
|
|
596
|
+
*/
|
|
597
|
+
private replay;
|
|
598
|
+
/** The ONE outbound exit from this class. No frame reaches the sink except through the attachment's boundary. */
|
|
599
|
+
private deliverFrame;
|
|
600
|
+
/** Detach the failed sink but retain the frame, then notify its attachment owner. */
|
|
601
|
+
private reportDeliveryError;
|
|
602
|
+
}
|
|
603
|
+
//#endregion
|
|
604
|
+
//#region src/peer-message-ledger.d.ts
|
|
605
|
+
/**
|
|
606
|
+
* What the receiver has seen from one origin.
|
|
607
|
+
*
|
|
608
|
+
* `delivered` is a SET, not just a high-water mark, and that distinction is the whole of review
|
|
609
|
+
* finding #1. A high-water mark cannot tell "sequence 2 was already used" from "sequence 2 is the
|
|
610
|
+
* gap we reported and it has just arrived" — and answering the second as if it were the first
|
|
611
|
+
* refuses a message that was never delivered, permanently. `highestSequence` is kept alongside it
|
|
612
|
+
* because reporting a gap needs the frontier, not the membership.
|
|
613
|
+
*
|
|
614
|
+
* Exported because it is reachable from `IPeerMessageLedger`. A public interface whose field type is
|
|
615
|
+
* unexported is a name a consumer cannot write down (review finding #2). Measured: `tsdown` inlines
|
|
616
|
+
* it into the bundled declaration so no TS4033 is raised today — the finding's build-breakage claim
|
|
617
|
+
* does not reproduce — but a type nobody can name is still a bad public surface.
|
|
618
|
+
*/
|
|
619
|
+
interface IOriginRecord {
|
|
620
|
+
/** The highest sequence delivered from this origin — the frontier a gap is measured against. */
|
|
621
|
+
highestSequence: number;
|
|
622
|
+
/** Every sequence actually delivered. Membership, not a range: gaps get filled out of order. */
|
|
623
|
+
delivered: Set<number>;
|
|
624
|
+
/** Acks already issued, by message id, so a retry gets the original answer back. */
|
|
625
|
+
issued: Map<string, IPeerMessageAck>;
|
|
626
|
+
}
|
|
627
|
+
/** Per-connection receive state. Created empty; discarded when the connection ends. */
|
|
628
|
+
interface IPeerMessageLedger {
|
|
629
|
+
readonly origins: Map<string, IOriginRecord>;
|
|
630
|
+
}
|
|
631
|
+
declare function createPeerMessageLedger(): IPeerMessageLedger;
|
|
632
|
+
/** Why a message was not accepted, when it was not. */
|
|
633
|
+
interface IPeerMessageRejection {
|
|
634
|
+
readonly state: Extract<TPeerDeliveryState, 'refused' | 'failed'>;
|
|
635
|
+
readonly reason: string;
|
|
636
|
+
}
|
|
637
|
+
interface IPeerMessageVerdict {
|
|
638
|
+
readonly ack: IPeerMessageAck;
|
|
639
|
+
/**
|
|
640
|
+
* Should the session deliver this to the agent?
|
|
641
|
+
*
|
|
642
|
+
* False for a duplicate and for a refusal. Separate from `ack.state` because a caller that
|
|
643
|
+
* re-derived it from the state string would have to re-list which states mean "deliver", and that
|
|
644
|
+
* list is exactly what drifts.
|
|
645
|
+
*/
|
|
646
|
+
readonly deliver: boolean;
|
|
647
|
+
/**
|
|
648
|
+
* Set when this message's sequence is ahead of the last one delivered from its origin.
|
|
649
|
+
*
|
|
650
|
+
* Reported, not resolved. The session layer decides whether a gap is worth waiting for; the
|
|
651
|
+
* ledger's job is to make sure it is never invisible.
|
|
652
|
+
*/
|
|
653
|
+
readonly gapBefore?: number;
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Record a message and say what should happen to it.
|
|
657
|
+
*
|
|
658
|
+
* Pure with respect to its inputs except for the ledger it is handed, which it updates in place —
|
|
659
|
+
* the caller owns the ledger's lifetime, so a connection ending is a reference going away rather
|
|
660
|
+
* than a cleanup step someone can forget.
|
|
661
|
+
*/
|
|
662
|
+
declare function admitPeerMessage(ledger: IPeerMessageLedger, message: IPeerMessage, rejection?: IPeerMessageRejection): IPeerMessageVerdict;
|
|
663
|
+
/**
|
|
664
|
+
* Mark a delivered message as acknowledged by the agent side.
|
|
665
|
+
*
|
|
666
|
+
* Separate from `admitPeerMessage` because delivery and acknowledgement answer different questions —
|
|
667
|
+
* "did the receiver take it" versus "did the agent act on it" — and a sender waiting for the second
|
|
668
|
+
* must not be told the first.
|
|
669
|
+
*/
|
|
670
|
+
declare function acknowledgePeerMessage(ledger: IPeerMessageLedger, originSessionId: string, id: string): IPeerMessageAck | undefined;
|
|
671
|
+
/** Forget one origin — used when a peer disconnects and its sequence space is no longer meaningful. */
|
|
672
|
+
declare function forgetPeerOrigin(ledger: IPeerMessageLedger, originSessionId: string): void;
|
|
673
|
+
//#endregion
|
|
674
|
+
//#region src/handoff-chunking.d.ts
|
|
675
|
+
/**
|
|
676
|
+
* One piece of a payload in flight.
|
|
677
|
+
*
|
|
678
|
+
* `total` travels on every chunk rather than in a separate header frame: a header can be the frame
|
|
679
|
+
* that gets lost, and a receiver that learned the count from a message it never got would wait
|
|
680
|
+
* forever for a number it cannot ask for again.
|
|
681
|
+
*/
|
|
682
|
+
interface IHandoffChunk {
|
|
683
|
+
readonly handoffId: string;
|
|
684
|
+
/** 0-based position in the sequence. */
|
|
685
|
+
readonly index: number;
|
|
686
|
+
/** How many chunks make up the whole payload. */
|
|
687
|
+
readonly total: number;
|
|
688
|
+
/** base64 of this slice of the UTF-8 encoded payload. */
|
|
689
|
+
readonly data: string;
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Default frame budget, in bytes of encoded chunk data.
|
|
693
|
+
*
|
|
694
|
+
* 16 KiB because that is the size every SCTP implementation carries without negotiation. Larger
|
|
695
|
+
* works between two peers that both support it and fails between two that do not — and it fails at
|
|
696
|
+
* transfer time, on the user's session, not in a test.
|
|
697
|
+
*/
|
|
698
|
+
declare const DEFAULT_MAX_CHUNK_BYTES: number;
|
|
699
|
+
/**
|
|
700
|
+
* Cut a sealed payload into chunks.
|
|
701
|
+
*
|
|
702
|
+
* Takes the serialized string the seal produced, not the record — the digest is over those exact
|
|
703
|
+
* bytes, and re-serializing here would let a chunked payload differ from the one the manifest
|
|
704
|
+
* describes.
|
|
705
|
+
*/
|
|
706
|
+
declare function chunkHandoffPayload(handoffId: string, serialized: string, maxChunkBytes?: number): readonly IHandoffChunk[];
|
|
707
|
+
/** Why a chunk was not accepted. */
|
|
708
|
+
type TChunkRejection =
|
|
709
|
+
/** Belongs to a different transfer. */
|
|
710
|
+
'wrong-handoff' |
|
|
711
|
+
/** `index` is negative, or beyond the declared `total`. */
|
|
712
|
+
'out-of-range' |
|
|
713
|
+
/** Its `total` disagrees with what earlier chunks declared. */
|
|
714
|
+
'inconsistent-total' |
|
|
715
|
+
/** Its `data` is not decodable base64. */
|
|
716
|
+
'undecodable';
|
|
717
|
+
type TChunkOutcome =
|
|
718
|
+
/** Stored; more are still needed. */
|
|
719
|
+
'accepted' |
|
|
720
|
+
/** Already had this index, and it matched. A retry, not a problem. */
|
|
721
|
+
'duplicate' |
|
|
722
|
+
/** Stored, and that was the last one missing. */
|
|
723
|
+
'complete' | 'refused';
|
|
724
|
+
interface IChunkResult {
|
|
725
|
+
readonly outcome: TChunkOutcome;
|
|
726
|
+
readonly rejection?: TChunkRejection;
|
|
727
|
+
/** Present when complete: the reassembled payload, ready for the integrity check. */
|
|
728
|
+
readonly serialized?: string;
|
|
729
|
+
/** How many distinct chunks are held. */
|
|
730
|
+
readonly received: number;
|
|
731
|
+
/** How many are expected, once anything has declared it. */
|
|
732
|
+
readonly expected?: number;
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Collects the chunks of ONE transfer.
|
|
736
|
+
*
|
|
737
|
+
* One assembler per `handoffId`, because a shared one would have to key everything by transfer and
|
|
738
|
+
* would then be a place where two transfers can be confused for each other. A chunk naming a
|
|
739
|
+
* different transfer is refused rather than routed — routing is the caller's job and doing it here
|
|
740
|
+
* would make this a dispatcher as well as a buffer.
|
|
741
|
+
*/
|
|
742
|
+
declare class HandoffChunkAssembler {
|
|
743
|
+
private readonly handoffId;
|
|
744
|
+
private readonly chunks;
|
|
745
|
+
private declaredTotal;
|
|
746
|
+
constructor(handoffId: string);
|
|
747
|
+
get received(): number;
|
|
748
|
+
get expected(): number | undefined;
|
|
749
|
+
/** Take one chunk. Reordering and retries are normal; inconsistency is not. */
|
|
750
|
+
accept(chunk: IHandoffChunk): IChunkResult;
|
|
751
|
+
/** Which indices are still missing, so a caller can ask for them rather than restarting. */
|
|
752
|
+
missing(): readonly number[];
|
|
753
|
+
/** Drop everything held. The transfer was abandoned, or its integrity check failed. */
|
|
754
|
+
discard(): void;
|
|
755
|
+
private reassemble;
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* How many chunks a payload of this size will take, without building them.
|
|
759
|
+
*
|
|
760
|
+
* For a progress indicator at the sending end, and for a receiver deciding whether it is willing to
|
|
761
|
+
* hold that much before the first chunk arrives.
|
|
762
|
+
*/
|
|
763
|
+
declare function chunkCountFor(integrity: IHandoffIntegrity, maxChunkBytes?: number): number;
|
|
764
|
+
//#endregion
|
|
765
|
+
export { CHANNEL_FRAME_MAGIC, CHANNEL_FRAME_VERSION, DEFAULT_MAX_CHUNK_BYTES, DEFAULT_MAX_PENDING_BYTES, DEFAULT_MAX_PENDING_MS, HandoffChunkAssembler, type IAttachOptions, type IBufferedFrame, type IChunkResult, type IHandoffChunk, type IPeerMessageLedger, type IPeerMessageRejection, type IPeerMessageVerdict, type IPendingStallClock, type IProtocolSession, type IResumeBufferOptions, type ISessionMessageHandlerOptions, type ISessionResumeBridgeOptions, MAX_INBOUND_FRAME_BYTES, PROTOCOL_SESSION_EVENT_CLASSIFICATION, ResumeBuffer, SessionResumeBridge, type TChunkOutcome, type TChunkRejection, type TClientMessage, type TDeliveryErrorHandler, type TMessageDecodeResult, type TOutboundDeliver, type TProtocolSessionEventClassification, type TResumeSink, type TResumeTail, type TSeqServerMessage, type TServerMessage, acknowledgePeerMessage, admitPeerMessage, chunkCountFor, chunkHandoffPayload, createOutboundDelivery, createPeerMessageLedger, createPendingStallClock, createSessionMessageHandler, decodeChannelFrame, decodeClientMessage, decodeFrame, decodeServerMessage, encodeBinaryFrame, encodeChannelEventFrame, forgetPeerOrigin, isChannelFrame, isOverPendingBudget };
|
|
766
|
+
//# sourceMappingURL=index.d.cts.map
|