agent-yes 1.120.0 → 1.122.0

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.
Files changed (45) hide show
  1. package/default.config.yaml +27 -4
  2. package/dist/SUPPORTED_CLIS-BVqe3k7F.js +8 -0
  3. package/dist/{SUPPORTED_CLIS-BD8zWc7O.js → SUPPORTED_CLIS-DFYbm2uk.js} +2 -2
  4. package/dist/{agent-yes.config-kmtJKJHk.js → agent-yes.config-z-IPzH5U.js} +3 -2
  5. package/dist/cli.js +5 -5
  6. package/dist/index.js +2 -2
  7. package/dist/reaper-Dj8R7ltI.js +64 -0
  8. package/dist/reaper-HqcUms2d.js +3 -0
  9. package/dist/{remotes-DavR4Hca.js → remotes-CpGcTr7A.js} +1 -1
  10. package/dist/{remotes-BufkGk0e.js → remotes-D2fqaRU8.js} +1 -1
  11. package/dist/schedule-CJaNc82S.js +144 -0
  12. package/dist/{serve-6RqphTG0.js → serve-VczpuYDk.js} +133 -29
  13. package/dist/{setup-B5TPF2MV.js → setup-DveWqmSR.js} +5 -42
  14. package/dist/share-ClsUSd_0.js +582 -0
  15. package/dist/{subcommands-CCgzXvQ-.js → subcommands-CmNGbbIA.js} +15 -6
  16. package/dist/{subcommands-BVcos4UW.js → subcommands-CzZ4uBIa.js} +2 -2
  17. package/dist/{tray-B8_rx1iu.js → tray-DjCIyakK.js} +22 -10
  18. package/dist/{ts-C78N0K4F.js → ts-7kSDmCpQ.js} +76 -7
  19. package/dist/{versionChecker-CYZtJKMG.js → versionChecker-B5vxV_hH.js} +13 -19
  20. package/dist/workspaceConfig-XP2NEWmV.js +56 -0
  21. package/lab/ui/blog/e2ee-share-links/index.html +299 -0
  22. package/lab/ui/e2e.d.ts +47 -0
  23. package/lab/ui/e2e.js +245 -0
  24. package/lab/ui/index.html +227 -42
  25. package/package.json +6 -2
  26. package/scripts/check-e2e.ts +40 -0
  27. package/ts/autoRetry.spec.ts +19 -0
  28. package/ts/autoRetry.ts +16 -0
  29. package/ts/configShared.ts +4 -0
  30. package/ts/e2e-crypto.spec.ts +235 -0
  31. package/ts/index.ts +102 -0
  32. package/ts/oxmgrService.ts +36 -0
  33. package/ts/pty.ts +19 -1
  34. package/ts/reaper.spec.ts +45 -0
  35. package/ts/reaper.ts +77 -0
  36. package/ts/schedule.spec.ts +30 -0
  37. package/ts/schedule.ts +161 -0
  38. package/ts/serve.ts +205 -28
  39. package/ts/share.ts +286 -42
  40. package/ts/subcommands.ts +0 -0
  41. package/ts/tray.spec.ts +9 -1
  42. package/ts/tray.ts +30 -14
  43. package/ts/versionChecker.ts +24 -27
  44. package/dist/SUPPORTED_CLIS-CoYWGWbP.js +0 -8
  45. package/dist/share-B7J79Wq9.js +0 -254
@@ -1,45 +1,8 @@
1
- import { t as agentYesHome } from "./agentYesHome-BvaUOzCV.js";
2
- import { mkdirSync, readFileSync, writeFileSync } from "fs";
3
- import { homedir } from "os";
4
- import path from "path";
5
- import { existsSync as existsSync$1 } from "node:fs";
1
+ import { r as setWorkspaceRoot, t as getWorkspaceRoot } from "./workspaceConfig-XP2NEWmV.js";
2
+ import { existsSync } from "node:fs";
6
3
  import { stdin, stdout } from "node:process";
7
4
  import { createInterface } from "node:readline/promises";
8
5
 
9
- //#region ts/workspaceConfig.ts
10
- function configPath() {
11
- return path.join(agentYesHome(), "config.json");
12
- }
13
- function readConfig() {
14
- try {
15
- return JSON.parse(readFileSync(configPath(), "utf-8"));
16
- } catch {
17
- return {};
18
- }
19
- }
20
- /** Expand a leading `~` (`~` or `~/x`) to an absolute home-based path. */
21
- function expandTilde(p) {
22
- const s = p.trim();
23
- if (s === "~") return homedir();
24
- if (s.startsWith("~/") || s.startsWith("~\\")) return path.join(homedir(), s.slice(2));
25
- return s;
26
- }
27
- /** The configured workspace root (absolute), or the home dir if unset. */
28
- function getWorkspaceRoot() {
29
- const w = readConfig().workspace;
30
- return w && w.trim() ? w : homedir();
31
- }
32
- /** Persist the workspace root, tilde-expanded and resolved to an absolute path. */
33
- function setWorkspaceRoot(dir) {
34
- const abs = path.resolve(expandTilde(dir));
35
- const cfg = readConfig();
36
- cfg.workspace = abs;
37
- mkdirSync(agentYesHome(), { recursive: true });
38
- writeFileSync(configPath(), JSON.stringify(cfg, null, 2));
39
- return abs;
40
- }
41
-
42
- //#endregion
43
6
  //#region ts/setup.ts
44
7
  async function cmdSetup(rest) {
45
8
  if (rest.includes("-h") || rest.includes("--help")) {
@@ -66,10 +29,10 @@ async function cmdSetup(rest) {
66
29
  }
67
30
  const abs = setWorkspaceRoot(ws);
68
31
  process.stdout.write(`workspace root: ${abs}\n`);
69
- if (!existsSync$1(abs)) process.stderr.write(` note: that directory doesn't exist yet — create it, or agents spawned there will fail\n`);
32
+ if (!existsSync(abs)) process.stderr.write(` note: that directory doesn't exist yet — create it, or agents spawned there will fail\n`);
70
33
  if (noShare) return 0;
71
34
  process.stdout.write(`\nsharing this machine to agent-yes.com…\n`);
72
- const { cmdServe } = await import("./serve-6RqphTG0.js");
35
+ const { cmdServe } = await import("./serve-VczpuYDk.js");
73
36
  return cmdServe([
74
37
  "install",
75
38
  "--share",
@@ -79,4 +42,4 @@ async function cmdSetup(rest) {
79
42
 
80
43
  //#endregion
81
44
  export { cmdSetup };
82
- //# sourceMappingURL=setup-B5TPF2MV.js.map
45
+ //# sourceMappingURL=setup-DveWqmSR.js.map
@@ -0,0 +1,582 @@
1
+ import { mkdir, readFile, writeFile } from "fs/promises";
2
+ import { homedir } from "os";
3
+ import path from "path";
4
+ import { randomBytes } from "crypto";
5
+
6
+ //#region lab/ui/e2e.js
7
+ const V = 1;
8
+ const PROTO = `ay-e2e-${V}`;
9
+ const MARKER = `e${V}.`;
10
+ const INFO_AUTH = `ay/${PROTO}/auth`;
11
+ const INFO_H2C = `ay/${PROTO}/key/host->client`;
12
+ const INFO_C2H = `ay/${PROTO}/key/client->host`;
13
+ const MAX_CHUNK = 12e3;
14
+ const CONFIRM_TIMEOUT_MS = 5e3;
15
+ const VER = 1;
16
+ const FLAG_CONFIRM = 1;
17
+ const HEADER_LEN = 14;
18
+ const NONCE_LEN = 12;
19
+ const TAG_LEN = 16;
20
+ const COUNTER_MAX = (1n << 64n) - 1n;
21
+ if (PROTO !== `ay-e2e-${V}` || MARKER !== `e${V}.` || !INFO_AUTH.startsWith(`ay/${PROTO}/`)) throw new Error("e2e: version constants disagree");
22
+ const subtle = globalThis.crypto.subtle;
23
+ const enc = new TextEncoder();
24
+ const dec = new TextDecoder();
25
+ const HEX64 = /^[0-9a-f]{64}$/;
26
+ function concatBytes(...arrs) {
27
+ let len = 0;
28
+ for (const a of arrs) len += a.length;
29
+ const out = new Uint8Array(len);
30
+ let o = 0;
31
+ for (const a of arrs) {
32
+ out.set(a, o);
33
+ o += a.length;
34
+ }
35
+ return out;
36
+ }
37
+ function hexToBytes(hex) {
38
+ const out = new Uint8Array(hex.length / 2);
39
+ for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.substr(i * 2, 2), 16);
40
+ return out;
41
+ }
42
+ function bytesToHex(b) {
43
+ let s = "";
44
+ for (let i = 0; i < b.length; i++) s += b[i].toString(16).padStart(2, "0");
45
+ return s;
46
+ }
47
+ async function sha256(bytes) {
48
+ return new Uint8Array(await subtle.digest("SHA-256", bytes));
49
+ }
50
+ async function hkdf32(ikm, salt, info) {
51
+ const base = await subtle.importKey("raw", ikm, "HKDF", false, ["deriveBits"]);
52
+ const bits = await subtle.deriveBits({
53
+ name: "HKDF",
54
+ hash: "SHA-256",
55
+ salt,
56
+ info: enc.encode(info)
57
+ }, base, 256);
58
+ return new Uint8Array(bits);
59
+ }
60
+ function validateS(s) {
61
+ if (typeof s !== "string" || !HEX64.test(s)) throw new Error("invalid share token");
62
+ return s;
63
+ }
64
+ function ikmFromS(s) {
65
+ return hexToBytes(validateS(s));
66
+ }
67
+ async function deriveAuthToken(s, room, sighost) {
68
+ const salt = await sha256(enc.encode(`${room}\n${sighost}`));
69
+ return bytesToHex(await hkdf32(ikmFromS(s), salt, INFO_AUTH));
70
+ }
71
+ async function importAesKey(raw) {
72
+ return subtle.importKey("raw", raw, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
73
+ }
74
+ async function deriveDirKeys(s, transcriptHash) {
75
+ const ikm = ikmFromS(s);
76
+ const h2c = await hkdf32(ikm, transcriptHash, INFO_H2C);
77
+ const c2h = await hkdf32(ikm, transcriptHash, INFO_C2H);
78
+ return {
79
+ keyH2C: await importAesKey(h2c),
80
+ keyC2H: await importAesKey(c2h)
81
+ };
82
+ }
83
+ function allFingerprints(sdp) {
84
+ const out = [];
85
+ const re = /^a=fingerprint:(.*)$/gim;
86
+ let m;
87
+ while (m = re.exec(sdp)) out.push(m[1].trim().toLowerCase());
88
+ return out;
89
+ }
90
+ function firstAttr(sdp, name) {
91
+ const m = new RegExp(`^a=${name}:(.*)$`, "im").exec(sdp);
92
+ return m ? m[1].trim().toLowerCase() : "";
93
+ }
94
+ async function computeTranscriptHash(offerSdp, answerSdp) {
95
+ const offerFps = allFingerprints(offerSdp).sort();
96
+ const answerFps = allFingerprints(answerSdp).sort();
97
+ if (!offerFps.length || !answerFps.length) throw new Error("e2e: missing DTLS fingerprint");
98
+ for (const fp of offerFps.concat(answerFps)) if (!fp.startsWith("sha-256")) throw new Error("e2e: non-sha-256 DTLS fingerprint");
99
+ const input = `${PROTO}\noffer=${offerFps.join(",")};setup=${firstAttr(offerSdp, "setup")};ufrag=${firstAttr(offerSdp, "ice-ufrag")}\nanswer=${answerFps.join(",")};setup=${firstAttr(answerSdp, "setup")};ufrag=${firstAttr(answerSdp, "ice-ufrag")}`;
100
+ return await sha256(enc.encode(input));
101
+ }
102
+ function nonceFromCounter(ctr) {
103
+ const n = new Uint8Array(NONCE_LEN);
104
+ new DataView(n.buffer).setBigUint64(4, ctr, false);
105
+ return n;
106
+ }
107
+ async function seal(key, sendState, flags, transcriptHash, plaintext) {
108
+ const ctr = sendState.sendCtr;
109
+ if (ctr >= COUNTER_MAX) throw new Error("e2e: nonce counter overflow");
110
+ sendState.sendCtr = ctr + 1n;
111
+ const nonce = nonceFromCounter(ctr);
112
+ const header = new Uint8Array(HEADER_LEN);
113
+ header[0] = VER;
114
+ header[1] = flags & 255;
115
+ header.set(nonce, 2);
116
+ const aad = concatBytes(header, transcriptHash);
117
+ return concatBytes(header, new Uint8Array(await subtle.encrypt({
118
+ name: "AES-GCM",
119
+ iv: nonce,
120
+ additionalData: aad,
121
+ tagLength: 128
122
+ }, key, plaintext))).buffer;
123
+ }
124
+ async function open$1(key, frame, transcriptHash, recvState) {
125
+ const buf = frame instanceof Uint8Array ? frame : new Uint8Array(frame);
126
+ if (buf.length < HEADER_LEN + TAG_LEN) throw new Error("e2e: short frame");
127
+ if (buf[0] !== VER) throw new Error("e2e: bad version");
128
+ const header = buf.subarray(0, HEADER_LEN);
129
+ const nonce = buf.subarray(2, HEADER_LEN);
130
+ const ndv = new DataView(nonce.buffer, nonce.byteOffset, NONCE_LEN);
131
+ if (ndv.getUint32(0, false) !== 0) throw new Error("e2e: bad epoch");
132
+ const ctr = ndv.getBigUint64(4, false);
133
+ const sealed = buf.subarray(HEADER_LEN);
134
+ const aad = concatBytes(header, transcriptHash);
135
+ const ptBuf = await subtle.decrypt({
136
+ name: "AES-GCM",
137
+ iv: nonce,
138
+ additionalData: aad,
139
+ tagLength: 128
140
+ }, key, sealed);
141
+ if (recvState.lastSeen === -1n && ctr !== 0n) throw new Error("e2e: first frame must be counter-0");
142
+ if (ctr <= recvState.lastSeen) throw new Error("e2e: replay/reorder");
143
+ recvState.lastSeen = ctr;
144
+ return {
145
+ counter: ctr,
146
+ flags: header[1],
147
+ plaintext: new Uint8Array(ptBuf)
148
+ };
149
+ }
150
+ function packEnvelope(obj) {
151
+ return enc.encode(JSON.stringify(obj));
152
+ }
153
+ function unpackEnvelope(bytes) {
154
+ return JSON.parse(dec.decode(bytes));
155
+ }
156
+ function parseSecret(token) {
157
+ const mk = /^e(\d+)\.(.*)$/.exec(token);
158
+ if (mk) {
159
+ if (mk[1] !== String(V)) throw new Error("update required");
160
+ if (!HEX64.test(mk[2])) throw new Error("malformed encrypted link");
161
+ return {
162
+ s: mk[2],
163
+ v2: true
164
+ };
165
+ }
166
+ if (/^e\d/i.test(token)) throw new Error("malformed encrypted link");
167
+ return {
168
+ s: token,
169
+ v2: false
170
+ };
171
+ }
172
+ function randomHex(n) {
173
+ const b = new Uint8Array(n);
174
+ globalThis.crypto.getRandomValues(b);
175
+ return bytesToHex(b);
176
+ }
177
+
178
+ //#endregion
179
+ //#region ts/share.ts
180
+ const SUB = "ay-signal-1";
181
+ const DEFAULT_SIGHOST = "s.agent-yes.com";
182
+ const HOST_HEARTBEAT_MS = 2e4;
183
+ const STUN = [{ urls: "stun:stun.l.google.com:19302" }];
184
+ let iceCache = null;
185
+ async function getIceServers() {
186
+ const keyId = process.env.CF_TURN_KEY_ID;
187
+ const apiToken = process.env.CF_TURN_API_TOKEN;
188
+ if (!keyId || !apiToken) return STUN;
189
+ if (iceCache && iceCache.exp > Date.now()) return iceCache.servers;
190
+ const ttl = 3600;
191
+ try {
192
+ const r = await fetch(`https://rtc.live.cloudflare.com/v1/turn/keys/${keyId}/credentials/generate-ice-servers`, {
193
+ method: "POST",
194
+ headers: {
195
+ Authorization: `Bearer ${apiToken}`,
196
+ "Content-Type": "application/json"
197
+ },
198
+ body: JSON.stringify({ ttl }),
199
+ signal: AbortSignal.timeout(5e3)
200
+ });
201
+ if (!r.ok) throw new Error(`Cloudflare TURN ${r.status}`);
202
+ const j = await r.json();
203
+ const servers = j.iceServers?.length ? j.iceServers : STUN;
204
+ iceCache = {
205
+ servers,
206
+ exp: Date.now() + (ttl - 300) * 1e3
207
+ };
208
+ return servers;
209
+ } catch (e) {
210
+ console.error(`[share] Cloudflare TURN credential fetch failed; using STUN only: ${e}`);
211
+ return STUN;
212
+ }
213
+ }
214
+ function shareRoomPath() {
215
+ const home = process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
216
+ return path.join(home, ".share-room");
217
+ }
218
+ async function loadOrCreateShareRoom(sighost = DEFAULT_SIGHOST) {
219
+ try {
220
+ const url = (await readFile(shareRoomPath(), "utf-8")).trim();
221
+ if (url.startsWith("webrtc://")) {
222
+ if (parseShareUrl(url).token.startsWith(MARKER)) return url;
223
+ }
224
+ } catch {}
225
+ const url = `webrtc://${"r" + randomBytes(3).toString("hex")}:${MARKER}${randomBytes(32).toString("hex")}@${sighost}`;
226
+ await mkdir(path.dirname(shareRoomPath()), { recursive: true });
227
+ await writeFile(shareRoomPath(), url, { mode: 384 });
228
+ return url;
229
+ }
230
+ function parseShareUrl(s) {
231
+ const m = /^webrtc:\/\/([^:@/]+):([^@/]+)@(.+)$/.exec(s);
232
+ if (!m) throw new Error(`bad --share url: ${s} (want webrtc://room:token@host)`);
233
+ return {
234
+ room: m[1],
235
+ token: m[2],
236
+ host: m[3]
237
+ };
238
+ }
239
+ async function linkFromBunCache() {
240
+ const { existsSync, symlinkSync, mkdirSync, readdirSync } = await import("fs");
241
+ const path = (await import("path")).default;
242
+ const { createRequire } = await import("module");
243
+ const require = createRequire(import.meta.url);
244
+ const pkg = path.dirname(require.resolve("node-datachannel/package.json"));
245
+ const bin = path.join(pkg, "build", "Release", "node_datachannel.node");
246
+ const cacheRoot = path.join((await import("os")).homedir(), ".bun", "install", "cache");
247
+ if (existsSync(bin) && existsSync(cacheRoot)) for (const d of readdirSync(cacheRoot)) {
248
+ if (!d.startsWith("node-datachannel@")) continue;
249
+ const dst = path.join(cacheRoot, d, "build", "Release");
250
+ mkdirSync(dst, { recursive: true });
251
+ const link = path.join(dst, "node_datachannel.node");
252
+ if (!existsSync(link)) symlinkSync(bin, link);
253
+ }
254
+ }
255
+ async function ndPackageDir() {
256
+ try {
257
+ const path = (await import("path")).default;
258
+ const { createRequire } = await import("module");
259
+ const require = createRequire(import.meta.url);
260
+ return path.dirname(require.resolve("node-datachannel/package.json"));
261
+ } catch {
262
+ return null;
263
+ }
264
+ }
265
+ async function ensureAddon(ndDir) {
266
+ const { existsSync } = await import("fs");
267
+ const path = (await import("path")).default;
268
+ if (existsSync(path.join(ndDir, "build", "Release", "node_datachannel.node"))) return;
269
+ try {
270
+ const { createRequire } = await import("module");
271
+ const binJs = createRequire(import.meta.url).resolve("prebuild-install/bin.js", { paths: [ndDir] });
272
+ const { spawnSync } = await import("child_process");
273
+ process.stderr.write("fetching node-datachannel prebuilt binary (one-time)…\n");
274
+ spawnSync(process.execPath, [
275
+ binJs,
276
+ "-r",
277
+ "napi"
278
+ ], {
279
+ cwd: ndDir,
280
+ stdio: "ignore"
281
+ });
282
+ } catch {}
283
+ }
284
+ async function importRTC() {
285
+ const ndDir = await ndPackageDir();
286
+ if (ndDir) await ensureAddon(ndDir);
287
+ try {
288
+ return (await import("node-datachannel/polyfill")).RTCPeerConnection;
289
+ } catch (firstErr) {
290
+ await linkFromBunCache().catch(() => {});
291
+ try {
292
+ return (await import("node-datachannel/polyfill")).RTCPeerConnection;
293
+ } catch {
294
+ throw firstErr;
295
+ }
296
+ }
297
+ }
298
+ /** Start the share bridge. Resolves once signaling is connected; runs until the
299
+ * process exits, reconnecting signaling on drop. Returns the shareable link. */
300
+ async function startShare(opts) {
301
+ opts.url;
302
+ const sighost = opts.sighost ?? DEFAULT_SIGHOST;
303
+ const { room, token, host } = opts.url ? parseShareUrl(opts.url) : {
304
+ room: "r" + randomBytes(3).toString("hex"),
305
+ token: `${MARKER}${randomBytes(32).toString("hex")}`,
306
+ host: sighost
307
+ };
308
+ const { s: S, v2 } = parseSecret(token);
309
+ if (!v2) throw new Error("refusing to host an unencrypted room — delete ~/.agent-yes/.share-room to rotate to an encrypted link");
310
+ const authToken = await deriveAuthToken(S, room, host);
311
+ const RTCPeerConnection = await importRTC();
312
+ const wsScheme = host.startsWith("localhost") || host.startsWith("127.") ? "ws" : "wss";
313
+ const link = `${host === "s.agent-yes.com" ? "https://agent-yes.com" : "http://localhost:7778"}/#${room}:${MARKER}${S}${host === "s.agent-yes.com" ? "" : "@" + host}`;
314
+ const peers = /* @__PURE__ */ new Map();
315
+ let closed = false;
316
+ let currentWs;
317
+ const connectSignaling = (onReady) => {
318
+ if (closed) return;
319
+ const ws = new WebSocket(`${wsScheme}://${host}/${room}`, [SUB]);
320
+ currentWs = ws;
321
+ let ready = false;
322
+ let lastRecv = Date.now();
323
+ let hb;
324
+ const stopHb = () => {
325
+ if (hb) {
326
+ clearInterval(hb);
327
+ hb = void 0;
328
+ }
329
+ };
330
+ ws.onopen = () => {
331
+ ws.send(JSON.stringify({
332
+ type: "hello",
333
+ role: "host",
334
+ v: 2,
335
+ token: authToken
336
+ }));
337
+ ready = true;
338
+ lastRecv = Date.now();
339
+ stopHb();
340
+ hb = setInterval(() => {
341
+ if (Date.now() - lastRecv > HOST_HEARTBEAT_MS * 2 + 5e3) {
342
+ stopHb();
343
+ try {
344
+ ws.close();
345
+ } catch {}
346
+ return;
347
+ }
348
+ try {
349
+ ws.send(JSON.stringify({ type: "ping" }));
350
+ } catch {}
351
+ }, HOST_HEARTBEAT_MS);
352
+ onReady();
353
+ };
354
+ ws.onmessage = async (ev) => {
355
+ if (closed) return;
356
+ lastRecv = Date.now();
357
+ const m = JSON.parse(ev.data);
358
+ if (m.type === "pong") return;
359
+ if (m.type === "peer-join") startPeer(ws, m.peer).catch(() => {});
360
+ else if (m.type === "answer") {
361
+ const peer = peers.get(m.from);
362
+ if (!peer) return;
363
+ try {
364
+ await peer.pc.setRemoteDescription({
365
+ type: "answer",
366
+ sdp: m.sdp
367
+ });
368
+ peer.th = await computeTranscriptHash(peer.pc.localDescription.sdp, peer.pc.remoteDescription.sdp);
369
+ const { keyH2C, keyC2H } = await deriveDirKeys(S, peer.th);
370
+ peer.keyH2C = keyH2C;
371
+ peer.keyC2H = keyC2H;
372
+ peer.resolveKeys();
373
+ } catch {
374
+ closePeer(m.from);
375
+ }
376
+ } else if (m.type === "candidate") await peers.get(m.from)?.pc.addIceCandidate(m.candidate).catch(() => {});
377
+ else if (m.type === "peer-leave") closePeer(m.peer);
378
+ };
379
+ ws.onclose = (ev) => {
380
+ stopHb();
381
+ if (closed) return;
382
+ if (ev?.code === 1008) {
383
+ closed = true;
384
+ process.stderr.write("[share] room rejected by signaling server — delete ~/.agent-yes/.share-room to rotate the room\n");
385
+ return;
386
+ }
387
+ setTimeout(() => connectSignaling(() => {}), ready ? 1e3 : 2e3);
388
+ };
389
+ ws.onerror = () => {};
390
+ return ws;
391
+ };
392
+ async function startPeer(ws, peerId) {
393
+ const iceServers = await getIceServers();
394
+ const pc = new RTCPeerConnection({ iceServers });
395
+ let resolveKeys;
396
+ const keysReady = new Promise((r) => resolveKeys = r);
397
+ const peer = {
398
+ pc,
399
+ aborts: /* @__PURE__ */ new Map(),
400
+ send: { sendCtr: 0n },
401
+ recv: { lastSeen: -1n },
402
+ keysReady,
403
+ resolveKeys,
404
+ myNonce: randomHex(16),
405
+ confirmedIn: false,
406
+ confirmedOut: false,
407
+ confirmed: false,
408
+ recvChain: Promise.resolve(),
409
+ sendChain: Promise.resolve()
410
+ };
411
+ peers.set(peerId, peer);
412
+ pc.onicecandidate = (e) => {
413
+ if (e.candidate) ws.send(JSON.stringify({
414
+ type: "candidate",
415
+ to: peerId,
416
+ candidate: e.candidate
417
+ }));
418
+ };
419
+ pc.onconnectionstatechange = () => {
420
+ if ([
421
+ "failed",
422
+ "closed",
423
+ "disconnected"
424
+ ].includes(pc.connectionState)) closePeer(peerId);
425
+ };
426
+ const dc = pc.createDataChannel("api");
427
+ dc.binaryType = "arraybuffer";
428
+ dc.onopen = async () => {
429
+ try {
430
+ await peer.keysReady;
431
+ enqueueSeal(peerId, dc, peer, FLAG_CONFIRM, {
432
+ t: "confirm",
433
+ nonce: peer.myNonce
434
+ });
435
+ peer.confirmTimer = setTimeout(() => {
436
+ if (!peer.confirmed) closePeer(peerId);
437
+ }, CONFIRM_TIMEOUT_MS);
438
+ } catch {
439
+ closePeer(peerId);
440
+ }
441
+ };
442
+ dc.onmessage = (e) => {
443
+ peer.recvChain = peer.recvChain.then(() => onFrame(peerId, dc, peer, e.data)).catch(() => {});
444
+ };
445
+ const offer = await pc.createOffer();
446
+ await pc.setLocalDescription(offer);
447
+ ws.send(JSON.stringify({
448
+ type: "offer",
449
+ to: peerId,
450
+ sdp: pc.localDescription.sdp,
451
+ iceServers
452
+ }));
453
+ }
454
+ function closePeer(peerId) {
455
+ const p = peers.get(peerId);
456
+ if (!p) return;
457
+ if (p.confirmTimer) clearTimeout(p.confirmTimer);
458
+ for (const a of p.aborts.values()) a.abort();
459
+ try {
460
+ p.pc.close();
461
+ } catch {}
462
+ peers.delete(peerId);
463
+ }
464
+ function enqueueSeal(peerId, dc, peer, flags, obj) {
465
+ peer.sendChain = peer.sendChain.then(async () => {
466
+ if (dc.readyState !== "open" || !peer.keyH2C || !peer.th) return;
467
+ let frame;
468
+ try {
469
+ frame = await seal(peer.keyH2C, peer.send, flags, peer.th, packEnvelope(obj));
470
+ } catch {
471
+ closePeer(peerId);
472
+ return;
473
+ }
474
+ try {
475
+ dc.send(frame);
476
+ } catch {}
477
+ });
478
+ return peer.sendChain;
479
+ }
480
+ async function onFrame(peerId, dc, peer, data) {
481
+ if (!peers.has(peerId)) return;
482
+ if (typeof data === "string" || !peer.keyC2H || !peer.th) return closePeer(peerId);
483
+ let env;
484
+ try {
485
+ const { plaintext } = await open$1(peer.keyC2H, data, peer.th, peer.recv);
486
+ env = unpackEnvelope(plaintext);
487
+ } catch {
488
+ return closePeer(peerId);
489
+ }
490
+ if (!peer.confirmed) {
491
+ if (!env || env.t !== "confirm") return closePeer(peerId);
492
+ if (typeof env.nonce === "string" && !peer.confirmedOut) {
493
+ await enqueueSeal(peerId, dc, peer, FLAG_CONFIRM, {
494
+ t: "confirm",
495
+ nonce: peer.myNonce,
496
+ echo: env.nonce
497
+ });
498
+ peer.confirmedOut = true;
499
+ }
500
+ if (env.echo && env.echo === peer.myNonce) peer.confirmedIn = true;
501
+ if (peer.confirmedIn && peer.confirmedOut) {
502
+ peer.confirmed = true;
503
+ if (peer.confirmTimer) clearTimeout(peer.confirmTimer);
504
+ }
505
+ return;
506
+ }
507
+ if (!env || env.t === "confirm") return;
508
+ onReq(peerId, dc, peer, env);
509
+ }
510
+ async function onReq(peerId, dc, peer, req) {
511
+ if (req.t === "abort") {
512
+ peer.aborts.get(req.id)?.abort();
513
+ peer.aborts.delete(req.id);
514
+ return;
515
+ }
516
+ if (req.t !== "req") return;
517
+ const { id, method, path: p, body } = req;
518
+ const ac = new AbortController();
519
+ peer.aborts.set(id, ac);
520
+ try {
521
+ const res = await opts.localFetch(new Request(`http://ay.local${p}`, {
522
+ method,
523
+ headers: {
524
+ Authorization: `Bearer ${opts.apiToken}`,
525
+ ...body ? { "Content-Type": "application/json" } : {}
526
+ },
527
+ body: body ?? void 0,
528
+ signal: ac.signal
529
+ }));
530
+ enqueueSeal(peerId, dc, peer, 0, {
531
+ t: "res",
532
+ id,
533
+ status: res.status,
534
+ ct: res.headers.get("content-type") ?? ""
535
+ });
536
+ const reader = res.body.getReader();
537
+ const dec = new TextDecoder();
538
+ let seq = 0;
539
+ for (;;) {
540
+ const { done, value } = await reader.read();
541
+ if (done) break;
542
+ const text = dec.decode(value, { stream: true });
543
+ for (let i = 0; i < text.length; i += MAX_CHUNK) enqueueSeal(peerId, dc, peer, 0, {
544
+ t: "data",
545
+ id,
546
+ seq: seq++,
547
+ chunk: text.slice(i, i + MAX_CHUNK)
548
+ });
549
+ }
550
+ enqueueSeal(peerId, dc, peer, 0, {
551
+ t: "end",
552
+ id,
553
+ seq
554
+ });
555
+ } catch (e) {
556
+ if (e.name !== "AbortError") enqueueSeal(peerId, dc, peer, 0, {
557
+ t: "end",
558
+ id,
559
+ error: String(e.message ?? e)
560
+ });
561
+ } finally {
562
+ peer.aborts.delete(id);
563
+ }
564
+ }
565
+ await new Promise((resolve) => connectSignaling(resolve));
566
+ const close = () => {
567
+ closed = true;
568
+ try {
569
+ currentWs?.close();
570
+ } catch {}
571
+ for (const peerId of [...peers.keys()]) closePeer(peerId);
572
+ };
573
+ return {
574
+ room,
575
+ link,
576
+ close
577
+ };
578
+ }
579
+
580
+ //#endregion
581
+ export { loadOrCreateShareRoom, startShare };
582
+ //# sourceMappingURL=share-ClsUSd_0.js.map