@robota-sdk/agent-transport-webrtc 3.0.0-beta.99 → 3.0.2

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