@robota-sdk/agent-transport-webrtc 3.0.0-beta.99 → 3.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,568 @@
1
1
  # @robota-sdk/agent-transport-webrtc
2
2
 
3
+ ## 3.0.1
4
+ ### Patch Changes
5
+
6
+ - @robota-sdk/agent-interface-session@3.0.1
7
+ - @robota-sdk/agent-interface-session-mobility@3.0.1
8
+ - @robota-sdk/agent-interface-transport@3.0.1
9
+ - @robota-sdk/agent-remote-pairing@3.0.1
10
+ - @robota-sdk/agent-transport@3.0.1
11
+
12
+ ## 3.0.0
13
+ ### Major Changes
14
+
15
+ - e82215f: **ARCH-011: replace the ambiguous transport lifecycle stub with executable conformance.**
16
+
17
+ `ITransportAdapter` now requires a frozen `service | runner` lifecycle descriptor. `start()` resolves
18
+ at the concrete transport's documented readiness boundary; start before attach and repeated active
19
+ start reject a stable lifecycle error, repeated stop is safe, and stopped adapters can reattach and
20
+ restart.
21
+
22
+ Runner adapters launch separately and expose a typed terminal outcome through
23
+ `waitForCompletion()`. The registry accepts base adapters, rejects duplicate names, keeps
24
+ configuration as an optional capability, returns complete ordered records whose pending slots become
25
+ registry-owned `abandoned` outcomes on stop/rollback, and exposes a real-runner-only first-failure
26
+ wait. It serializes startup/stop, rejects active restart before mutation, and reverses partial startup
27
+ from the currently failing adapter with typed safe rollback details. Runtime host and serve mode
28
+ propagate real nonzero runner results without treating normal shutdown abandonment as failure.
29
+
30
+ HTTP, MCP, both WebSocket adapters, WebRTC, and headless invoke one shared public conformance kit.
31
+ The former `TuiTransport` export is removed because it ignored the attached session; use `renderApp`
32
+ or `TuiInteractionChannel`, which honestly own their session lifecycle.
33
+
34
+ ### Minor Changes
35
+
36
+ - e15e22b: Two of one user's devices can connect to each other over WebRTC, admitted by the device handshake.
37
+
38
+ - `agent-transport-webrtc` — `DeviceMeshNode` keeps one connection per device pair, in either role: the
39
+ device with the lower id offers and the other answers, so concurrent attempts resolve by rule. Both
40
+ roles bind the handshake to the certificate the DTLS layer verified and take one remote description
41
+ with exactly one fingerprint; before admission only handshake frames cross, and a connection counts
42
+ as admitted only once both sides admitted each other. Each connection gets its own DTLS certificate
43
+ (`createDtlsKeys`). A new attempt never displaces an admitted connection until it is admitted
44
+ itself, and attempts per pair are paced. Lists adopted in a handshake, or handed over through
45
+ `refresh`, apply at once, and a device they revoke loses its connection. Signaling runs through
46
+ `IMeshRelay`: `WsMeshRelayClient` for the self-hosted relay's `presence` / `message` frames
47
+ (topics capped per source and relay-wide), `createInMemoryMeshRelayHub` for tests.
48
+ - `agent-remote-pairing` — the pair's two relay inbox topics, one per direction, come from their
49
+ pairwise secret (`derivePairRendezvous(...).relayInbox()`).
50
+ - `agent-cli` — a device holding the signing key reissues its roster and revocation list before they
51
+ expire while an interactive session runs. The host can open this device's mesh endpoint from the
52
+ identity under `the configured user data directory`, saving newer lists a peer hands over; no command starts it yet.
53
+ - `agent-command` — `/devices add|join` still reports that enrolment is not available yet.
54
+ - 28fa8a7: `/devices add` and `/devices join` enrol a new device into your devices.
55
+
56
+ - On a device that holds the signing key, `/devices add` shows a one-time code on the terminal. It
57
+ works once, for five minutes.
58
+ - On the new device, `/devices join [name]` asks for the code on the terminal, creates the device's
59
+ keys, and meets the other device through the signaling relay in
60
+ `transports.webrtc.options.relayUrl`. Both devices need that setting.
61
+ - The new device proves the code over the WebRTC connection's DTLS fingerprints before anything else
62
+ crosses, so a relay in the middle cannot enrol anyone.
63
+ - Both devices then show the same six digits, and both operators compare them and confirm. Someone
64
+ who saw the code cannot choose the digits: the new device commits to its part before it sees the
65
+ existing device's.
66
+ - Only when both operators say yes does the existing device certify the new one and issue a new
67
+ roster. The new device keeps its identity only once the chain it receives verifies.
68
+ - A wrong, expired or already used code is refused. So is a code after a few failed attempts. When
69
+ either operator declines, nothing is issued or kept.
70
+ - The code appears only on the two terminals. It never reaches history, transcripts or the model. A
71
+ code typed as a command argument is refused. Both commands stay user-only and refuse remote
72
+ surfaces.
73
+ - `agent-remote-pairing`: enrollment codes, the enrollment proof, the signed request with its
74
+ commitment, the short authentication string and the frame decoder.
75
+ - `agent-transport-webrtc`: `dialEnrollment` and `listenForEnrollment` provide a data channel bound to
76
+ the negotiated fingerprints.
77
+ - e15e22b: The Node WebRTC transport runs on `node-datachannel` (libdatachannel, DTLS by OpenSSL). The DTLS stack
78
+ verifies handshake signatures, so a channel binding names the party that holds the certificate's key.
79
+ It is an optional dependency with a prebuilt binary per platform; where it cannot load, the WebRTC
80
+ transport reports itself unavailable instead of falling back to another implementation.
81
+
82
+ - `agent-transport-webrtc` — `RtcPeer` / `RtcChannel` wrap one connection; `loadDataChannel` replaces
83
+ `loadWerift` (and the `loadWerift` option becomes `loadDataChannel`). Every connection has its own DTLS
84
+ certificate, no ICE server is contacted unless configured, and `werift` is no longer a peer dependency.
85
+ A pairing peer that finishes the handshake first and speaks at once no longer has that frame dropped: it
86
+ is held until this side accepts, and discarded if it does not.
87
+ - `agent-cli` — depends on `node-datachannel` (optional) instead of `werift`.
88
+ - 007fd90: Authority per connection: pairing stays with the local operator, and driving needs the operator's yes.
89
+
90
+ - `agent-interface-session-mobility` — `ConnectionAuthority` decides what one connection may do.
91
+ `presence` and `message` are allowed; `delegate` and `handoff` ask the receiving operator for every
92
+ request; `observe` and `drive` ask once per connection. With no `IOperatorApprover` the answer is
93
+ no. `authorizeDelegation` turns an approved task into a peer turn from where admission placed the
94
+ peer, ignoring anything else on the request, so the receiver's policy decides what it may do.
95
+ - `agent-transport-webrtc` — `connectionApproval` asks the operator before a connection reaches the
96
+ session, after every proof has run. Frames the peer sends meanwhile are held (bounded) and delivered
97
+ only after a yes. A channel that closes first withdraws the question (the approval context carries
98
+ an `AbortSignal`), and a later answer admits nothing. A first-pairing device is pinned for
99
+ reconnect only once it is admitted.
100
+ - `agent-command` — `/remote-control enable` and `revoke` from a connected surface are refused, and
101
+ `status` never shows a connected surface the pairing link.
102
+ - `agent-framework` — the `remote-control-enable` host action runs only for the operator's own
103
+ command, whichever command asked for it.
104
+ - `agent-cli` — every remote-control connection, a returning trusted device included, is put to the
105
+ operator on the host terminal; without an interactive terminal it is refused.
106
+ - c7f9203: Connected sessions can send each other files.
107
+
108
+ - `/peers send-file <session-id> <path>` sends a copy of any file the operator can read to another
109
+ live session on this host.
110
+ - The model sends a file only through the `peer_send_file` tool. Every call asks the user, showing
111
+ the path, size, hash and destination; no permission mode, rule or remembered consent answers it.
112
+ The tool reaches only files inside the workspace whose path does not look like it holds secrets
113
+ (`.env*`, `~/.ssh`, keys and credentials), and it does not exist in a turn a peer's message started.
114
+ - The receiving operator approves every file. A received file is kept as an inert copy (mode 0600)
115
+ under `the configured user data directory<sender>/`. It is never run and never placed in the model's context.
116
+ The conversation is told only its name, size and sha256. A name that leaves that directory is
117
+ refused, a symbolic link is never written through, and nothing is overwritten.
118
+ - Transfers travel on a channel of their own (a separate connection on this host, a separate data
119
+ channel between devices), in chunks the receiver paces, up to 32 MiB, and are kept only when the
120
+ whole content matches the offered sha256. A transfer that ends early is discarded; there is no
121
+ resume.
122
+
123
+ **API**
124
+
125
+ - `agent-interface-session-mobility`: the `file` capability, which asks the operator for every
126
+ request; `ConnectionAuthority.authorizeFile`; `IFileOffer` and `IFileFrameChannel`.
127
+ - `agent-transport/node`: `sendFileOverChannel` and `receiveFileOverChannel`, the carrier over any
128
+ `IFileFrameChannel`; `DEFAULT_MAX_FILE_BYTES`.
129
+ - `agent-transport-webrtc`: `IDeviceMeshLink.openFileChannel` and `onFileChannel`.
130
+ - `agent-remote-pairing`: `file` joins `DEVICE_CAPABILITIES`. A device certificate that names it is
131
+ refused as malformed by an earlier version.
132
+ - `agent-core`: `IToolPermissionProfile.notInPeerTurn` withholds a tool from a turn a peer's message
133
+ started.
134
+ - `agent-framework`: `ICommandLocalPeersAdapter.prepareFile`.
135
+ - 004fe7f: `/handoff` moves a session over a real connection.
136
+
137
+ - `/handoff <session-id>` pushes this conversation to another the agent runtime session on this machine. The
138
+ same carrier also runs between two of the user's devices over their mesh connection; no command
139
+ opens that connection yet.
140
+ - A hand-off is push-only: only the operator of the session that holds it starts one. A session or
141
+ device that asks another for its session is refused.
142
+ - The source signs a grant for that one transfer over that one channel with its device key, so a
143
+ hand-off needs the device identity from `/devices init`. The receiving side checks it against the
144
+ sender's certificate, then asks its own operator; without a yes nothing is sent.
145
+ - The session travels on the file-transfer carrier, is kept aside until it matches the manifest, and
146
+ is saved without being started. The operator there resumes it with `robota --resume <id>`.
147
+ - The source gives the session up, and ends, only once the receiving side confirms it saved it.
148
+ Every other outcome leaves the session where it was; if the confirmation is lost, `/handoff` to the
149
+ same session again resends the same transfer, which the receiver settles without saving it twice.
150
+ Peer attribution (`driverId`, `turnSource`)
151
+ travels with it.
152
+ - `/handoff` stays user-only. Its description tells the model to suggest the command to the user.
153
+ - `agent-transport-webrtc`: an admitted mesh link exposes the DTLS fingerprints it is bound to, and
154
+ `judgeHandoffGrant` is exported.
155
+ - `agent-interface-session-mobility`: a hand-off carrier may move the sealed payload whole
156
+ (`sendPayload`), the destination verifies it with `receivePayload`, and the source can report its
157
+ open transfer (`status`) and abandon it for any refusal the destination names.
158
+ - 189c21e: The device mesh can find and signal a peer device beyond the local network, through public
159
+ infrastructure that sees only signed ciphertext.
160
+
161
+ - **Rendezvous records (BEP 44):** each device publishes, per peer, its connection hints and the newest
162
+ device revocation lists it holds as mutable items on the BitTorrent Mainline DHT (`bittorrent-dht`,
163
+ directly over UDP), or through pkarr relays (`createPkarrRelayStore`, for clients that cannot reach
164
+ the DHT). Every record is signed by a one-time key of the pair, direction, epoch and purpose, stored
165
+ under a rotating salt, AEAD-sealed and padded to a fixed size; publish times are jittered per pair.
166
+ Lookups read the peer's records at the current and adjacent epochs and reject anything that does not
167
+ verify or open for the pair. Lists too large for one record are split into fixed-size chunks.
168
+ `MeshDht` is the candidate source and publisher, and its `latestLists` feeds the freshness lookup
169
+ before a remote admission with every list candidate found, newest first.
170
+ - **Nostr signaling:** `NostrMeshRelay` carries live SDP/ICE as ephemeral events on several relays,
171
+ under per-epoch, per-direction keys and kinds, with our own AEAD over the payload and no tags.
172
+ - **Order:** `DiscoveringMeshRelay` tries the address cache, mDNS, then DHT records for candidates,
173
+ and when none answers the signaling carriers in order — Nostr, then the self-hosted relay. A
174
+ carrier that carries no admission in time is set aside for the next one.
175
+ - **Defaults:** `DEFAULT_NOSTR_RELAYS` and `DEFAULT_PKARR_RELAYS` list well-known relays of several
176
+ operators; `transports.mesh.options` (`dht`, `pkarrRelays`, `nostrRelays`) replaces them, parsed by
177
+ the CLI's `parseMeshInternetSettings`, and `openDeviceMesh` takes them as `internet`. No command
178
+ starts the mesh yet.
179
+ - `agent-remote-pairing`: `signingSeed`, `sealRecord` and `openRecord` take a record purpose
180
+ (`hints`, the default and unchanged; `revocation`; `signal`), and two tag purposes are added. The
181
+ device handshake's `fetchLatestLists` may return several candidates per list kind; each is verified
182
+ and the newest that verifies counts, so a forged "newer" list cannot hide a real one.
183
+ - `agent-cli` declares the mesh's runtime dependencies (`bittorrent-dht`, `nostr-tools`,
184
+ `multicast-dns`), which its bundle leaves external.
185
+
186
+ **Breaking (pre-release, hence minor):** `IDiscoveringMeshRelayOptions.advertiser` becomes
187
+ `advertisers` (a list).
188
+ - 963a4e0: Where no direct path joins two of one user's devices, a TURN relay on one of the user's own devices carries the
189
+ connection.
190
+
191
+ - `agent-transport-webrtc` — `TurnServer`, a pure-JavaScript TURN server over UDP (Allocate, Refresh,
192
+ CreatePermission, ChannelBind, Send/Data indications, ChannelData, long-term credentials) with quotas for
193
+ allocations per owner and in all, relayed bytes per second per owner, and allocation lifetime, and an optional
194
+ relayed-port range for a relay behind a NAT. Only what MESSAGE-INTEGRITY covers is read. Requests nobody has
195
+ authenticated are answered at a limited rate (per source and in all) and never with more bytes than they
196
+ carried, and forwarding into private ranges can be turned off (`allowPrivatePeers`). `MeshTurnRelay` runs it
197
+ for the devices of the roster: each pair derives a short-lived credential of its own (`meshRelayCredential`), a
198
+ device the lists drop or revoke can no longer allocate and loses its allocations. `DeviceMeshNode` takes
199
+ `relays` (the relays paired devices advertise, then configured TURN servers, and `relayOnly`) and
200
+ `relayServer`; a connection that needs a relay and has none is refused with `MeshRelayNeededError`, which says
201
+ why a relay was needed and carries the direct attempt's failure. `MeshDht` publishes this device's relay
202
+ endpoints in the sealed hints records (`relayEndpoints`) and reads the peers' (`relayAdverts`). DTLS stays end
203
+ to end; the relay only forwards it.
204
+ - `agent-remote-pairing` — the pair rendezvous derives a `relay-user` tag and `relayPassword`, the relay
205
+ credential's password for one direction and username.
206
+ - `agent-cli` — `transports.mesh.options` takes `relay` (`serve`, `port`, `host`, `publicAddress`, `relayPorts`,
207
+ `allowPrivatePeers`), `turnServers` and `relayOnly`. When the mesh is on, the session's mesh runs the relay,
208
+ advertises it to paired devices only, and uses the fallback order; `/devices` names the relays, and a device
209
+ that needs a relay is reported once with why. Running a relay, or relay-only without TURN servers, needs the
210
+ DHT or pkarr relays, which carry a relay's address to the other devices; a setting that could not work is
211
+ refused, naming it.
212
+ - 5e924ee: Two of one user's devices can find each other on the local network before the relay.
213
+
214
+ - `agent-remote-pairing` — `derivePairRendezvous` derives every place a device pair meets from their
215
+ pairwise secret, separated by direction: rotating tags (hourly epochs, looked up one epoch either
216
+ way), the seed of each epoch's one-time signing key, sealed connection-hint records, and the relay
217
+ inbox topics. It takes the lists in force and refuses a device they do not name with the same
218
+ key-agreement key, or revoke, so a rotated or revoked key stops deriving.
219
+ - `agent-transport-webrtc` — `startLanMeshRelay` / `DiscoveringMeshRelay` look for a peer in the
220
+ address cache, then with mDNS (`MeshMdns`, over `multicast-dns`), then on the self-hosted relay, and
221
+ carry signals to the peer's direct endpoint (`startMeshLanListener`) on rotating pairwise topics,
222
+ sent only as hashes. An endpoint must prove it holds the pair's topic before it carries signals,
223
+ and is set aside when no admission follows. The mDNS announcement names no product, device or
224
+ host, pads its instance count with names that hold for the epoch, and answers queries at a bounded
225
+ rate. Discovery yields candidates only: admission is still the device handshake, and an address is
226
+ remembered only after an admission it carried.
227
+ - `agent-cli` — the device mesh endpoint can look on the local network (`lan` option), remembering
228
+ the addresses that worked in an owner-only `the configured user data directory`; no command starts
229
+ it yet.
230
+ - 9db63ee: Add named session capability roles and explicit capability-host queries while preserving the legacy
231
+ `IInteractiveSession` interface shape. HTTP, MCP, protocol, WS, WebRTC, and headless transports now
232
+ declare only the session roles they consume, and the direct aggregate-cast floor is zero.
233
+ - 2ebff01: Emit the complete persisted checkpoint and branch lifecycle, forward plan, context-refresh, and
234
+ branch events through protocol transports, and render deterministic bounded notices in the TUI.
235
+ Transport-owned delivery failures now enter the owning carrier cleanup lifecycle without reversing
236
+ an already-committed session operation.
237
+ - 94cc355: REMOTE-002 Stage A: extract the transport-neutral session bridge + wire protocol into a new
238
+ `@robota-sdk/agent-transport-protocol` package (`createWsHandler`, `TClientMessage`/`TServerMessage`); repoint
239
+ `agent-transport-ws` and `agent-web-ui` at it (no pass-through re-export). Add a new
240
+ `@robota-sdk/agent-transport-webrtc` package: a `WebRtcTransport` (`IConfigurableTransport`, `defaultEnabled:false`)
241
+ that carries an `IInteractiveSession` over an `RTCDataChannel` reusing the shared handler, with a lazily-loaded
242
+ optional `werift` peer dependency that throws an explicit "WebRTC transport unavailable" error on absence (never a
243
+ silent no-op). No user-facing enable path and no auth in Stage A (that lands in Stage B); the transport is proven
244
+ by an in-process loopback data-channel round-trip only.
245
+
246
+ (Bump target corrected during REL-023 triage: `@robota-sdk/agent-web-ui` was dissolved by GUI-006 (#1141, 2026-07-12) before this work was ever published; its protocol-consumer role now lives in `@robota-sdk/agent-transport-webrtc-web`.)
247
+ - 2c69a3f: REMOTE-004 Stage B2: production WebRTC signaling + relay abuse-hardening (still no user-facing enable path).
248
+
249
+ - Add `WsSignalingClient` — a production `ISignalingClient` over a `ws` socket to the `@robota-sdk/remote-signaling`
250
+ relay (Node host-side): joins a rendezvous, buffers signals produced before the socket opens and flushes them,
251
+ and surfaces relay/socket errors through an explicit `onError` (no silent degrade). An `onReady` callback fires
252
+ once the rendezvous join is confirmed.
253
+ - Expose opt-in `forceTurn` on `IWebRtcTransportOptions` (relay-only ICE) as defense-in-depth.
254
+ - The private `@robota-sdk/remote-signaling` relay is hardened in-layer (safe by default): a per-source
255
+ token-bucket bounds join floods, rendezvous ids are single-use (a distinct third peer is refused for the id's
256
+ lifetime, even after one of the pair leaves), a half-open rendezvous expires after a TTL, and concurrent
257
+ rendezvous are capped — all with injected clock/scheduler for deterministic tests.
258
+ - `CVE-2024-29415` (werift-transitive `ip` SSRF) is discharged as a reviewed re-accept: werift never calls the
259
+ vulnerable `ip.isPublic`/`isPrivate`/`address` (verified + guarded by a regression test), so the
260
+ `ignoreCves` entry is retained with a documented non-reachability rationale.
261
+ - 92bf33e: Add the pairing gate to the WebRTC transport (REMOTE-008 Step 1, security milestone). When a pairing
262
+ `secret` is configured, the data channel is phase-separated: pre-accept it carries only pairing frames
263
+ (routed to the directional-HMAC handshake bound to the DTLS fingerprints; any non-pairing frame is
264
+ dropped), and only after the handshake accepts is the session bridge built — fail closed on
265
+ mismatch/timeout (channel closed, session never exposed). Without a `secret` the channel is exposed
266
+ immediately, unchanged. Introduces a dependency on the zero-dep `@robota-sdk/agent-remote-pairing` leaf
267
+ (the gate must live where the SDP fingerprints and channel frames are visible).
268
+ - 9f602df: Add user-supplied TURN fallback for remote control (REMOTE-010 / Stage E1) so P2P works behind symmetric
269
+ NAT / restrictive firewalls. The host reads + validates `transports.webrtc.options.iceServers`/`forceTurn`
270
+ at the agent-cli composition root (a fail-closed validator narrowing the untyped value → `IIceServer[]`;
271
+ `IWebRtcTransportOptions.iceServers` widened to carry TURN `username`/`credential`), and the browser reads a
272
+ validated `ice`/`forceTurn` pairing-URL query param (fail-closed decoder for the attacker-influenceable value;
273
+ `forceTurn` → `iceTransportPolicy: 'relay'`) — both threaded into their `RTCPeerConnection`. `forceTurn` without
274
+ a TURN server fails closed (else ICE gathers no candidates and silently never connects). Absent ICE config ⇒
275
+ host-candidate-only, unchanged.
276
+
277
+ (Bump target corrected during REL-023 triage: `@robota-sdk/agent-web-ui` was dissolved by GUI-006 (#1141, 2026-07-12) before this work was ever published; the browser-side ICE/`forceTurn` handling described here now lives in `@robota-sdk/agent-transport-webrtc-web`.)
278
+
279
+ ### Patch Changes
280
+
281
+ - 6e4c6ee: Device mesh discovery holds up better against endpoints and peers that misbehave.
282
+
283
+ - A direct path's admission deadline starts only from a `hello`, `offer` or `answer`, so ICE candidates
284
+ that trail an admitted connection no longer set a working path aside.
285
+ - An endpoint or signaling carrier that keeps failing is set aside for longer each time it fails
286
+ again, until a connection over it is admitted.
287
+ - An mDNS lookup keeps listening briefly after the first matching answer, so a faster answer from
288
+ another host cannot hide the peer's own.
289
+ - The device lists read from public records are bounded per paired device, each device's newest
290
+ before any device's next, and the chunks of one list carry a shared version, so a read that finds
291
+ chunks of two versions yields no list. Records written before this change are not read as lists.
292
+ - CLI processes that share `the configured user data directory` apply each change to the file as it
293
+ is on disk, so one process no longer overwrites what another learned.
294
+ - 4241fc5: Device mesh follow-ups.
295
+
296
+ - A device with no identity is pointed to `/devices add` on one of the user's devices and `/devices join`
297
+ here, as well as to `/devices init`. The message says that `init` is for the first device only,
298
+ because it creates a separate identity that can never link to the user's other devices. The `/devices`
299
+ description and the `init` subcommand say the same.
300
+ - An identity created mid-session (`/devices init`, or a successful `/devices join`) opens the mesh
301
+ without a restart when `transports.mesh.enabled` is on.
302
+ - If a session stalls for longer than the mesh lock's stale window (for example while the machine
303
+ sleeps), another session can take the mesh over. The stalled session now notices this on its next
304
+ lock refresh, closes its own mesh, and says why. Two sessions no longer run it together.
305
+ `holdExclusiveFileLock` has a new `onLost` option for this.
306
+ - `/peers` and `/handoff` still list and reach linked mesh devices when local same-host peer discovery
307
+ fails. `/peers` says why sessions on this host are not listed. `ICommandLocalPeersAdapter` has a new
308
+ optional `localDiscoveryOff` field for this.
309
+ - `agent-transport-webrtc`: when lists become newer on a node (reissued, revoked, or adopted from a
310
+ peer), the node sends them over every admitted connection instead of waiting for the next handshake.
311
+ Reissues, revocations and enrolments in the CLI take effect this way at once. A receiver adopts a
312
+ pushed list only if it is newer, issued by this user's signing key, and verifies. The push does not
313
+ depend on the peer's capabilities, and it never reaches the application's message handlers.
314
+ - 05d5391: The embedded TURN relay says why it cannot listen — the port is taken, the host is not an address of
315
+ this machine, or the port needs privileges — and keeps the bind error as `cause`; the CLI names the
316
+ relay setting that cause asks you to change. Mesh options are no longer checked while
317
+ `transports.mesh.enabled` is off, so a mistake in them does not report that the mesh could not start.
318
+ - 598b180: A WebRTC peer hands out its own ICE candidates only once it holds the remote description, so an answerer can no
319
+ longer reach the offerer while the answer is still being applied and have its certificate refused. A data channel
320
+ closed right after a send no longer loses that send: it reads closed at once and its stream is reset after a
321
+ grace. A LAN probe answered with anything but a valid proof is dropped at once.
322
+ - 57280bf: Every published package now declares `"engines": { "node": ">=22.12.0" }`. Before, 27 of the 38
323
+ packages declared no floor (`agent-core`, `agent-tools` and every provider among them),
324
+ `agent-session` and `agent-file-authority` declared `>=20.19.0`, and the other nine declared
325
+ `>=22.0.0`, so a consumer on Node 20 saw at most a warning from a transitive dependency.
326
+
327
+ Why 22.12: `agent-cli` and `agent-ui-terminal` need Node 22 through `ink` 7, and the CommonJS entries
328
+ of `agent-tools` and its dependents, `agent-transport`/`node` and its dependents, and
329
+ `agent-ui-terminal` `require()` ESM-only dependencies (`p-limit`, `jose`, `chalk`), which Node 22
330
+ supports unflagged only from 22.12. `engines` is advisory unless the consumer enables `engine-strict`.
331
+
332
+ No code changes: `tsdown` now reads `node22.12.0` as its build target from the field.
333
+ - 18c0d5c: Every published package now exports `./package.json`, so `require('<package>/package.json')` and
334
+ `import('<package>/package.json', { with: { type: 'json' } })` work instead of failing with
335
+ `ERR_PACKAGE_PATH_NOT_EXPORTED`, and each tarball now ships the package's `CHANGELOG.md`.
336
+ - 2c612ba: Release the migrated Robota product with its public SDK namespace, robota command, compatible SDK exports, internal product configuration, and verified CLI startup and delivery corrections.
337
+ - 235da81: **BREAKING — ARCH-030: `createWsHandler` takes the carrier's delivery boundary, not a raw `send`.**
338
+
339
+ `createWsHandler` had two outbound semantics on one connection. The session-event fan-out went through
340
+ a guard that reported carrier failures through `onDeliveryError`; every reply to an inbound frame got
341
+ the raw `send`. Eleven reply families were unguarded — five resolving from a Promise continuation, so a
342
+ reply landing after a disconnect escaped as an **unhandled rejection** while the carrier's cleanup was
343
+ never notified, and six synchronous ones that threw into the carrier's inbound listener instead.
344
+
345
+ `IWsHandlerOptions` now takes a single `deliver: TOutboundDeliver` in place of `send` and
346
+ `onDeliveryError`. **The carrier builds the boundary** from its own sink and its own failure policy and
347
+ passes it down — not the reverse, because a protocol layer handed a raw sink so it can hand a wrapper
348
+ back leaves the raw sink reachable, which is how the twelfth reply family gets added unguarded.
349
+
350
+ ```ts
351
+ // before
352
+ const { onMessage, cleanup } = createWsHandler({
353
+ session,
354
+ send: (msg) => ws.send(JSON.stringify(msg)),
355
+ onDeliveryError: (error) => ws.close(1011, error.message),
356
+ });
357
+
358
+ // after
359
+ const deliver = createOutboundDelivery(
360
+ (msg) => ws.send(JSON.stringify(msg)),
361
+ (error) => ws.close(1011, error.message),
362
+ );
363
+ const { onMessage, cleanup } = createWsHandler({ session, deliver });
364
+ ```
365
+
366
+ `TOutboundDeliver` is branded and `createOutboundDelivery` is its only producer, so a plain
367
+ `(message: TServerMessage) => void` is refused by the compiler wherever a boundary is required.
368
+
369
+ **The boundary latches:** it reports at most one delivery failure per connection, after which frames are
370
+ dropped without a further report. All three carriers already treated a delivery failure as terminal and
371
+ each had grown its own latch; it belongs upstream of all three. `SessionResumeBridge` builds a fresh
372
+ boundary per `attach`, which is what un-latches the session after a reconnect, and buffers a frame
373
+ before the boundary so a dropped one still replays.
374
+
375
+ **`ISubscribeSessionEventsOptions` is no longer exported** from the package barrel. It is the options bag
376
+ of `subscribeSessionEvents`, which is package-internal, and it was already absent from the SPEC's public
377
+ API table. Its `onDeliveryError` member is gone regardless — carrier-failure containment is the
378
+ boundary's job now.
379
+
380
+ `agent-transport-ws` and `agent-transport-webrtc` are `patch`: `WsSessionDelivery` (whose raw `send` is
381
+ now private, with `deliver` the only public sink) and `PairingGate` are not on their packages' barrels,
382
+ and every barrel export of both packages keeps its signature.
383
+ - 4f3c075: Assemble complete, verified package generations before switching build output; preserve the previous generation on build failure and pack only verified regular-file images. Include copied CLI web assets in affected-build ordering and artifact transfer. Preserve the CLI version in managed build paths. Public runtime contracts remain compatible (patch).
384
+ - 2db1b97: Remote-control pairing binds to the negotiated DTLS certificate.
385
+
386
+ - The Node host reads the remote fingerprint from the certificate the DTLS layer verified, not from the answer's
387
+ SDP text, and builds the pairing gate once the DTLS handshake completes.
388
+ - An SDP must advertise exactly one DTLS fingerprint. `extractDtlsFingerprint` now throws when two different
389
+ fingerprints are present, and `extractDtlsFingerprintAttribute` returns the algorithm with the value.
390
+ - A start takes one answer (host) and a connection takes one offer (browser client); a later description is
391
+ ignored.
392
+ - 5783499: Harden file, HTTP, relay, hosted-runtime, editor, and native path boundaries against resource exhaustion, short writes, path races, and integer narrowing.
393
+
394
+ Editor integrations now carry `Read`, `Write`, and `Edit` only when their `IEditorFileAccess` adapter declares `pathAuthority: 'stable-root'`; pathname-only adapters continue to receive disk-search notices, while file operations use the tools' stable disk authority. Existing adapters that can prove stable rooted access should add the marker to retain editor-buffer reads and writes.
395
+
396
+ `HttpDagRuntimeProvider` accepts a `token` option and applies it to every orchestration and event-stream request so it interoperates with the authenticated native DAG runtime server.
397
+ - 9c6a8db: The wire protocol gained several request/reply pairs so a GUI never has to scrape command output for
398
+ data it can read directly.
399
+
400
+ `get-settings`/`update-settings` read and write the Settings screen; a patch applies through the same
401
+ function its matching slash command uses, so the two paths cannot drift. `list-models` returns the
402
+ configured models grouped by provider profile, with the current profile and model. `get-agent-definitions`
403
+ returns the agent switcher's roster and current selection. `project-status`, `project-diff` and
404
+ `project-memory` back the Project panel's git status, one file's diff, and project memory; a host
405
+ without this capability answers `protocol_error` instead of hanging. `rename-session` and
406
+ `delete-session` rename or remove a stored session from the list, current or not, each with its own
407
+ success/failure reply.
408
+
409
+ The `messages` frame gained an optional `display` field — the same history projected into display
410
+ segments (text runs and finished tool calls, diffs included) — and the connection's own `driverId`, so
411
+ a reload, reconnect or resume shows tool rows and tells a co-driver's turns from its own. The `error`
412
+ frame gained optional `code`, `provider`, `retryAfterSeconds` and `model` fields so a renderer can say
413
+ what kind of failure happened (`auth`, `rate_limit`, `model_unavailable`, `network`, `provider`) instead
414
+ of showing only raw text; a session error this classification does not recognize is unchanged.
415
+
416
+ `TCommandSurfaceLocality` names whether a `'remote'`-sourced command is provably on this machine. The
417
+ WebRTC/device-mesh transport now forwards `'remote'` for it (it proves no locality, the same rule
418
+ already applied to pairing), and both the WS and WebRTC transports forward it into the Settings
419
+ read/write path, so a command that refuses a remote surface (installing a plugin) refuses identically
420
+ regardless of which transport carried it.
421
+ - Updated dependencies [37b4bd7]
422
+ - Updated dependencies [50d2c9f]
423
+ - Updated dependencies [724fabb]
424
+ - Updated dependencies [d877de2]
425
+ - Updated dependencies [d61e159]
426
+ - Updated dependencies [bfe8ed5]
427
+ - Updated dependencies [a5961c9]
428
+ - Updated dependencies [3038eb7]
429
+ - Updated dependencies [4dd45cc]
430
+ - Updated dependencies [b2e0afe]
431
+ - Updated dependencies [e15e22b]
432
+ - Updated dependencies [28fa8a7]
433
+ - Updated dependencies [44fc732]
434
+ - Updated dependencies [040f31f]
435
+ - Updated dependencies [e15e22b]
436
+ - Updated dependencies [007fd90]
437
+ - Updated dependencies [c7f9203]
438
+ - Updated dependencies [004fe7f]
439
+ - Updated dependencies [189c21e]
440
+ - Updated dependencies [963a4e0]
441
+ - Updated dependencies [5e924ee]
442
+ - Updated dependencies [7b72344]
443
+ - Updated dependencies [6ae3f28]
444
+ - Updated dependencies [57f57f5]
445
+ - Updated dependencies [ba822c1]
446
+ - Updated dependencies [6e6b06b]
447
+ - Updated dependencies [57280bf]
448
+ - Updated dependencies [18c0d5c]
449
+ - Updated dependencies [9a01e78]
450
+ - Updated dependencies [e8d70ac]
451
+ - Updated dependencies [2c612ba]
452
+ - Updated dependencies [3427887]
453
+ - Updated dependencies [c967c57]
454
+ - Updated dependencies [60f3965]
455
+ - Updated dependencies [115e104]
456
+ - Updated dependencies [04719a9]
457
+ - Updated dependencies [04a6de4]
458
+ - Updated dependencies [0ba247b]
459
+ - Updated dependencies [4c5148e]
460
+ - Updated dependencies [0116a29]
461
+ - Updated dependencies [e82215f]
462
+ - Updated dependencies [52b7346]
463
+ - Updated dependencies [9db63ee]
464
+ - Updated dependencies [b078afa]
465
+ - Updated dependencies [2ebff01]
466
+ - Updated dependencies [9db63ee]
467
+ - Updated dependencies [2ebff01]
468
+ - Updated dependencies [4772067]
469
+ - Updated dependencies [0f98419]
470
+ - Updated dependencies [64ba748]
471
+ - Updated dependencies [d312755]
472
+ - Updated dependencies [c207a31]
473
+ - Updated dependencies [3244fb8]
474
+ - Updated dependencies [1e3f91a]
475
+ - Updated dependencies [4f3c075]
476
+ - Updated dependencies [1e40b5b]
477
+ - Updated dependencies [7669851]
478
+ - Updated dependencies [4b76cfa]
479
+ - Updated dependencies [2db1b97]
480
+ - Updated dependencies [07b627f]
481
+ - Updated dependencies [1f45110]
482
+ - Updated dependencies [9665c6e]
483
+ - Updated dependencies [44393be]
484
+ - Updated dependencies [c7fa299]
485
+ - Updated dependencies [5134b3b]
486
+ - Updated dependencies [7911a7d]
487
+ - Updated dependencies [833afe1]
488
+ - Updated dependencies [7d9cc66]
489
+ - Updated dependencies [5c5ff23]
490
+ - Updated dependencies [9814afc]
491
+ - Updated dependencies [9c6a8db]
492
+ - @robota-sdk/agent-interface-session@3.0.0
493
+ - @robota-sdk/agent-interface-transport@3.0.0
494
+ - @robota-sdk/agent-transport@3.0.0
495
+ - @robota-sdk/agent-interface-session-mobility@3.0.0
496
+ - @robota-sdk/agent-remote-pairing@3.0.0
497
+
498
+ ## 3.0.0-beta.106
499
+ ### Patch Changes
500
+
501
+ - @robota-sdk/agent-interface-session@3.0.0-beta.106
502
+ - @robota-sdk/agent-interface-session-mobility@3.0.0-beta.106
503
+ - @robota-sdk/agent-interface-transport@3.0.0-beta.106
504
+ - @robota-sdk/agent-remote-pairing@3.0.0-beta.106
505
+ - @robota-sdk/agent-transport@3.0.0-beta.106
506
+
507
+ ## 3.0.0-beta.105
508
+ ### Patch Changes
509
+
510
+ - @robota-sdk/agent-interface-session@3.0.0-beta.105
511
+ - @robota-sdk/agent-interface-session-mobility@3.0.0-beta.105
512
+ - @robota-sdk/agent-interface-transport@3.0.0-beta.105
513
+ - @robota-sdk/agent-remote-pairing@3.0.0-beta.105
514
+ - @robota-sdk/agent-transport@3.0.0-beta.105
515
+
516
+ ## 3.0.0-beta.104
517
+ ### Patch Changes
518
+
519
+ - @robota-sdk/agent-interface-session@3.0.0-beta.104
520
+ - @robota-sdk/agent-interface-session-mobility@3.0.0-beta.104
521
+ - @robota-sdk/agent-interface-transport@3.0.0-beta.104
522
+ - @robota-sdk/agent-remote-pairing@3.0.0-beta.104
523
+ - @robota-sdk/agent-transport@3.0.0-beta.104
524
+
525
+ ## 3.0.0-beta.103
526
+ ### Patch Changes
527
+
528
+ - @robota-sdk/agent-interface-session@3.0.0-beta.103
529
+ - @robota-sdk/agent-interface-session-mobility@3.0.0-beta.103
530
+ - @robota-sdk/agent-interface-transport@3.0.0-beta.103
531
+ - @robota-sdk/agent-transport@3.0.0-beta.103
532
+ - @robota-sdk/agent-remote-pairing@3.0.0-beta.103
533
+
534
+ ## 3.0.0-beta.102
535
+ ### Patch Changes
536
+
537
+ - @robota-sdk/agent-interface-session@3.0.0-beta.102
538
+ - @robota-sdk/agent-interface-session-mobility@3.0.0-beta.102
539
+ - @robota-sdk/agent-interface-transport@3.0.0-beta.102
540
+ - @robota-sdk/agent-remote-pairing@3.0.0-beta.102
541
+ - @robota-sdk/agent-transport@3.0.0-beta.102
542
+
543
+ ## 3.0.0-beta.101
544
+ ### Patch Changes
545
+
546
+ - 5783499: Harden file, HTTP, relay, hosted-runtime, editor, and native path boundaries against resource exhaustion, short writes, path races, and integer narrowing.
547
+
548
+ Editor integrations now carry `Read`, `Write`, and `Edit` only when their `IEditorFileAccess` adapter declares `pathAuthority: 'stable-root'`; pathname-only adapters continue to receive disk-search notices, while file operations use the tools' stable disk authority. Existing adapters that can prove stable rooted access should add the marker to retain editor-buffer reads and writes.
549
+
550
+ `HttpDagRuntimeProvider` accepts a `token` option and applies it to every orchestration and event-stream request so it interoperates with the authenticated native DAG runtime server.
551
+ - @robota-sdk/agent-interface-session@3.0.0-beta.101
552
+ - @robota-sdk/agent-interface-session-mobility@3.0.0-beta.101
553
+ - @robota-sdk/agent-interface-transport@3.0.0-beta.101
554
+ - @robota-sdk/agent-transport@3.0.0-beta.101
555
+ - @robota-sdk/agent-remote-pairing@3.0.0-beta.101
556
+
557
+ ## 3.0.0-beta.100
558
+ ### Patch Changes
559
+
560
+ - @robota-sdk/agent-interface-session@3.0.0-beta.100
561
+ - @robota-sdk/agent-interface-session-mobility@3.0.0-beta.100
562
+ - @robota-sdk/agent-interface-transport@3.0.0-beta.100
563
+ - @robota-sdk/agent-remote-pairing@3.0.0-beta.100
564
+ - @robota-sdk/agent-transport@3.0.0-beta.100
565
+
3
566
  ## 3.0.0-beta.99
4
567
  ### Patch Changes
5
568