@byok-sdk/server 0.5.0 → 0.6.1
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/hub.d.ts +37 -18
- package/dist/index.d.ts +8 -2
- package/dist/index.js +271 -20
- package/dist/index.js.map +1 -1
- package/dist/task-store.d.ts +2 -1
- package/dist/types.d.ts +26 -3
- package/package.json +3 -3
package/dist/hub.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { type Envelope, type RuntimeId, type RuntimeInfo, type TaskState, type T
|
|
|
3
3
|
import type { DeviceRegistry } from './auth';
|
|
4
4
|
import { RateLimiter } from './rate-limiter';
|
|
5
5
|
import type { TaskStore } from './task-store';
|
|
6
|
-
import type { ByokServerEvent, DispatchInput, HubStats, MachineInfo, TaskHandle, TaskSnapshot } from './types';
|
|
6
|
+
import type { ByokServerEvent, AgentContentReadRequest, AgentEgressReceipt, DispatchInput, HubStats, MachineInfo, TaskHandle, TaskSnapshot } from './types';
|
|
7
7
|
/**
|
|
8
8
|
* The connection hub: tracks each device's live transport (WS or long-poll —
|
|
9
9
|
* never both at once, see {@link takeOverAsLongPoll}), routes `dispatch()`'d
|
|
@@ -18,7 +18,8 @@ import type { ByokServerEvent, DispatchInput, HubStats, MachineInfo, TaskHandle,
|
|
|
18
18
|
* transports (`ws-server.ts`'s WS message handler, `http.ts`'s
|
|
19
19
|
* `POST /byok/messages`) call instead of reaching into per-type handlers
|
|
20
20
|
* directly. Runs, in order: (1) type-allow — only `DAEMON_TO_SERVER_TYPES`
|
|
21
|
-
*
|
|
21
|
+
* plus the authenticated long-poll `conn.hello` snapshot may pass, a server
|
|
22
|
+
* -> daemon type arriving inbound is rejected (P2); (2)
|
|
22
23
|
* ownership (N2) — an envelope for a task already owned by a *different*
|
|
23
24
|
* device is dropped and logged, never force-failed (force-failing on an
|
|
24
25
|
* authz mismatch would let an attacker who merely guesses a `taskId` kill
|
|
@@ -169,15 +170,11 @@ export type SteerRejectionCode = 'steer_unsupported_runtime' | 'task_not_running
|
|
|
169
170
|
* Two reasons, either sufficient. First, scope: connection-level data is
|
|
170
171
|
* discovery describing a daemon BUILD, not the per-runtime, per-task,
|
|
171
172
|
* claim-time truth this gate needs — conflating the two is the original bug.
|
|
172
|
-
* Second,
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
* contrast, is the message that establishes the task↔runtime binding on every
|
|
178
|
-
* transport, so the gate's input now shares a lifecycle with the thing it
|
|
179
|
-
* judges. Adding a connection-level fallback here would restore both defects
|
|
180
|
-
* at once and is what this design exists to forbid.
|
|
173
|
+
* Second, lifecycle: the authenticated `conn.hello` snapshot on either
|
|
174
|
+
* transport describes the device build, while the claim establishes the
|
|
175
|
+
* exact task↔runtime binding. Only the latter shares a lifecycle with the
|
|
176
|
+
* thing this gate judges. Adding a connection-level fallback here would
|
|
177
|
+
* restore the scope defect and is what this design exists to forbid.
|
|
181
178
|
*/
|
|
182
179
|
export declare class SteerRejectedError extends Error {
|
|
183
180
|
readonly taskId: string;
|
|
@@ -214,6 +211,12 @@ export declare class ConnectionHub {
|
|
|
214
211
|
private readonly longPollWaiters;
|
|
215
212
|
private readonly runtimes;
|
|
216
213
|
private readonly serverEvents;
|
|
214
|
+
/** First-write-wins reliable facts; the reference composition's bounded in-memory readback. */
|
|
215
|
+
private readonly agentEgressReceipts;
|
|
216
|
+
/** Accepted requests are the authority that later receipts/transfers must echo exactly. */
|
|
217
|
+
private readonly agentContentReadRequests;
|
|
218
|
+
/** Content-free explicit-read audit facts keyed by exact authenticated device/request identity. */
|
|
219
|
+
private readonly agentContentReceipts;
|
|
217
220
|
/**
|
|
218
221
|
* Per-task last-inbound-activity timestamp (epoch ms) — the task-lease
|
|
219
222
|
* reaper's condition (c), see the "task-lease reaper" section below. Reset
|
|
@@ -276,7 +279,7 @@ export declare class ConnectionHub {
|
|
|
276
279
|
* connection this hub never learns capabilities for simply reads back
|
|
277
280
|
* `undefined` from {@link getDeviceCapabilities}.
|
|
278
281
|
*/
|
|
279
|
-
registerConnection(deviceId: string, ws: WebSocket, runtimes: RuntimeInfo[] | undefined, capabilities?: readonly string[], configuredToolsets?: readonly ToolsetId[]): void;
|
|
282
|
+
registerConnection(deviceId: string, ws: WebSocket, runtimes: RuntimeInfo[] | undefined, capabilities?: readonly string[], configuredToolsets?: readonly ToolsetId[], clientVersion?: string): void;
|
|
280
283
|
sendConnAck(deviceId: string, capabilities: string[]): void;
|
|
281
284
|
/**
|
|
282
285
|
* Reconnection procedure step 3 (§9): redeliver, in `seq` order, every
|
|
@@ -338,9 +341,10 @@ export declare class ConnectionHub {
|
|
|
338
341
|
* taskStore lookup or dedup bookkeeping. See {@link handleRateLimited}
|
|
339
342
|
* for what happens on exceed (never a silent drop).
|
|
340
343
|
* 1. **type-allow (P2)** — only {@link DAEMON_TO_SERVER_TYPES} may pass; a
|
|
341
|
-
* server -> daemon type
|
|
342
|
-
* `conn.hello`
|
|
343
|
-
*
|
|
344
|
+
* server -> daemon type arriving inbound is rejected before it's
|
|
345
|
+
* dispatched or counted accepted. `conn.hello` is the one non-task
|
|
346
|
+
* exception, and is accepted only from the bearer-authenticated
|
|
347
|
+
* long-poll route with an exact device/product/protocol match.
|
|
344
348
|
* 2. **ownership (N2)** — an envelope for a task already owned by a
|
|
345
349
|
* *different* device is dropped (logged), never force-failed:
|
|
346
350
|
* force-failing on an authz mismatch would let an attacker who merely
|
|
@@ -360,7 +364,19 @@ export declare class ConnectionHub {
|
|
|
360
364
|
* wire-level success even though no handler ran a second time; only
|
|
361
365
|
* `rejected`/`rate_limited` (gate steps 0-2) are excluded from that count.
|
|
362
366
|
*/
|
|
363
|
-
handleInbound(deviceId: string, envelope: Envelope): 'accepted' | 'duplicate' | 'rejected' | 'rate_limited';
|
|
367
|
+
handleInbound(deviceId: string, envelope: Envelope, authenticatedProductId?: string): 'accepted' | 'duplicate' | 'rejected' | 'rate_limited';
|
|
368
|
+
/**
|
|
369
|
+
* Store before acking. Replays must agree on every identity/cursor/hash
|
|
370
|
+
* field and receive the original receipt id; a same event id with changed
|
|
371
|
+
* facts is rejected rather than treated as an update.
|
|
372
|
+
*/
|
|
373
|
+
private handleAgentEgressReliable;
|
|
374
|
+
private handleAgentContentReceipt;
|
|
375
|
+
private sendAgentEgressAck;
|
|
376
|
+
private sendAgentContentReceiptAck;
|
|
377
|
+
private agentEgressReceiptKey;
|
|
378
|
+
/** Record the authenticated long-poll equivalent of the WS opening frame. */
|
|
379
|
+
private registerLongPollHello;
|
|
364
380
|
/**
|
|
365
381
|
* M4 Phase 4 (part A): `deviceId` just exceeded its inbound-envelope rate
|
|
366
382
|
* limit. Never a silent drop: counts the occurrence
|
|
@@ -733,6 +749,8 @@ export declare class ConnectionHub {
|
|
|
733
749
|
/** Reap one lease-expired task through the exact same TaskStore/canTransition path — and terminal-event emission — as any other `task.fail` (see {@link applyOrFail}). */
|
|
734
750
|
private reapTask;
|
|
735
751
|
dispatch(input: DispatchInput): Promise<TaskHandle>;
|
|
752
|
+
/** Capability-gated control-plane read request; no request enters the outbox on omission. */
|
|
753
|
+
requestAgentContentRead(input: AgentContentReadRequest): Promise<void>;
|
|
736
754
|
private buildTaskHandle;
|
|
737
755
|
/** Idempotent: cancelling an already-terminal task is a no-op, not an error. */
|
|
738
756
|
private cancelTask;
|
|
@@ -800,8 +818,7 @@ export declare class ConnectionHub {
|
|
|
800
818
|
* not {@link getDeviceCapabilities}, not `ConnectionState.runtimes`, and
|
|
801
819
|
* with no fallback to either when the snapshot is absent. See
|
|
802
820
|
* {@link SteerRejectedError} for why a connection-sourced input is wrong
|
|
803
|
-
*
|
|
804
|
-
* reach (absent entirely on long-poll-only daemons).
|
|
821
|
+
* in scope (it describes a daemon build, not this task's runtime).
|
|
805
822
|
*/
|
|
806
823
|
private steerTask;
|
|
807
824
|
private pickFirstConnectedDevice;
|
|
@@ -848,6 +865,8 @@ export declare class ConnectionHub {
|
|
|
848
865
|
* only, never a correctness gate).
|
|
849
866
|
*/
|
|
850
867
|
getDeviceCapabilities(deviceId: string): readonly string[] | undefined;
|
|
868
|
+
private hasDeviceCapabilities;
|
|
869
|
+
getAgentEgressReceipt(deviceId: string, eventId: string): AgentEgressReceipt | undefined;
|
|
851
870
|
getTask(taskId: string): TaskSnapshot | undefined;
|
|
852
871
|
listTasks(): TaskSnapshot[];
|
|
853
872
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ import type { Server as HttpServer } from 'node:http';
|
|
|
2
2
|
import type { Hono } from 'hono';
|
|
3
3
|
import { type TenantId } from './auth';
|
|
4
4
|
import { type PairingCodeClaims, type PairingCodeInfo } from './pairing';
|
|
5
|
-
import type { ByokServerEvent, CreateByokServerOptions, DispatchInput, HubStats, MachineInfo, TaskHandle, TaskSnapshot } from './types';
|
|
6
|
-
export type { ByokServerEvent, CreateByokServerOptions, DispatchInput, HubStats, MachineInfo, ServerTaskEvent, TaskHandle, TaskResult, TaskSnapshot, } from './types';
|
|
5
|
+
import type { ByokServerEvent, AgentContentReadRequest, AgentEgressReceipt, CreateByokServerOptions, DispatchInput, HubStats, MachineInfo, TaskHandle, TaskSnapshot } from './types';
|
|
6
|
+
export type { ByokServerEvent, AgentContentReadRequest, AgentEgressReceipt, CreateByokServerOptions, DispatchInput, HubStats, MachineInfo, ServerTaskEvent, TaskHandle, TaskResult, TaskSnapshot, } from './types';
|
|
7
7
|
export type { CreateTaskInput, TaskRecord, TaskStore } from './task-store';
|
|
8
8
|
export { IllegalTaskTransitionError, InMemoryTaskStore } from './task-store';
|
|
9
9
|
/**
|
|
@@ -60,10 +60,16 @@ export interface ByokServer {
|
|
|
60
60
|
createPairingCode(claims: PairingCodeClaims): PairingCodeInfo;
|
|
61
61
|
};
|
|
62
62
|
dispatch(input: DispatchInput): Promise<TaskHandle>;
|
|
63
|
+
/** Enqueue one capability-gated, exact-identity content-read request. */
|
|
64
|
+
requestAgentContentRead(input: AgentContentReadRequest): Promise<void>;
|
|
63
65
|
tasks: {
|
|
64
66
|
get(taskId: string): TaskSnapshot | undefined;
|
|
65
67
|
list(): TaskSnapshot[];
|
|
66
68
|
};
|
|
69
|
+
/** Reference-server reliable egress receipt readback. */
|
|
70
|
+
egress: {
|
|
71
|
+
get(deviceId: string, eventId: string): AgentEgressReceipt | undefined;
|
|
72
|
+
};
|
|
67
73
|
machines: {
|
|
68
74
|
list(): MachineInfo[];
|
|
69
75
|
};
|