Watches local directories and auto-pushes new/changed files to a
known daemon peer via fsnotify, with startup reconciliation, a
stable_after debounce, and per-file JSON state so failed sends retry
on the next connection. Multiple watch entries targeting the same
peer share a single connection/room (watchPeerGroup), resolving the
room-name-collision question flagged in PROPOSAL-watch.md. Push-only,
never propagates deletes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
For small files, file-done arrives before wireFileRecv's goroutine
reaches the pendingDones registration — the message was discarded and
the goroutine timed out. Fix by registering doneCh in AcceptOffer
(before sending file-accept), carrying it through recvState, and
reading it in wireFileRecv without re-registering.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Closing the file DataChannel can trigger SCTP/ICE cleanup before the
file-done control message is flushed, causing a 15 s timeout on the
receiver. Send file-done first on both the Go and TS sender paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The anchor keys its clients map by peer ID. With the daemon joining one
room per trusted peer, both sessions authenticated with the same real
Ed25519 ID — the second register() overwrote the first, orphaning the
earlier room's WS connection.
Fix mirrors waste-go/internal/netmgr: derive a deterministic per-room
Ed25519 keypair from HKDF(master_private_key, room_hash). Same inputs
always produce the same derived ID; different rooms produce different
IDs. No anchor changes required.
cli/internal/crypto: add DeriveForNetwork (HKDF-SHA256, same KDF as
waste-go) so the daemon can derive stable per-room signaling identities.
cli/internal/transport: Join derives a per-room signaling identity
before calling dialSignaling. Real identity is still used for hello
verification and seal/open crypto inside the DataChannel.
pwa/src/transport/flit.ts: split PeerConn.peerId into signalingId
(anchor routing) and cryptoId (seal/open, hello). In pair-room mode
the daemon's signaling ID differs from its real ID, so connectTo and
_onFrom now use trustedPeerId as the cryptoId regardless of what the
anchor reports as the sender. offerByOrder comparison uses real IDs
(trustedPeerId ?? signalingId) so both sides agree. hello verification
now also checks the Ed25519 signature, not just the claimed ID.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- wireFileRecv: replace OnMessage/OnClose channel approach with pion
Detach API so SCTP buffers data regardless of callback timing — fixes
0-byte files caused by pion dispatching data goroutines before OnMessage
was registered
- yaw DC: same Detach API treatment for the control channel
- connectTo: use DetachDataChannels SettingEngine, replace stale PCs
(Connecting/Failed/Disconnected state) instead of returning early;
isCurrent guard in OnConnectionStateChange prevents stale-PC close
from firing OnDisconnected
- offerByOrder: gate maybeOffer on peer ID order so only one side
creates the yaw DC and SDP offer — fixes signaling glare causing
triple connected (verified) logs
- pendingRecvs: promote single pendingRecv slot to map[string]*recvState
so concurrent file offers don't overwrite each other
- peer-left: stop calling resetPeer() so active file DCs aren't closed
before data arrives
- signaling ping: wrap conn.Ping with 10s timeout context so a dead TCP
connection is detected within 30s rather than hanging forever
- signaling read: 3-minute read deadline forces reconnect if anchor
silently evicts the peer from the room (idle timeout)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ports the yaw/2.1 identity/signaling/WebRTC transport from waste-go to
both a browser PWA (with QR pairing and Web Share Target) and a headless
Go CLI, trimmed to 1:1 ephemeral file transfer only.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>