agent-yes 1.98.0 → 1.99.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.
@@ -0,0 +1,8 @@
1
+ import "./ts-BECoCPV1.js";
2
+ import "./logger-B9h0djqx.js";
3
+ import "./versionChecker-pct2j3wR.js";
4
+ import "./pidStore-DBjlqzo8.js";
5
+ import "./globalPidIndex-yVd3mbsV.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-eIjVu8HF.js";
7
+
8
+ export { SUPPORTED_CLIS };
@@ -1,8 +1,8 @@
1
- import { t as CLIS_CONFIG } from "./ts-BuFWTNL9.js";
1
+ import { t as CLIS_CONFIG } from "./ts-BECoCPV1.js";
2
2
 
3
3
  //#region ts/SUPPORTED_CLIS.ts
4
4
  const SUPPORTED_CLIS = Object.keys(CLIS_CONFIG);
5
5
 
6
6
  //#endregion
7
7
  export { SUPPORTED_CLIS as t };
8
- //# sourceMappingURL=SUPPORTED_CLIS-C0a9K6I5.js.map
8
+ //# sourceMappingURL=SUPPORTED_CLIS-eIjVu8HF.js.map
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
  import { n as logger } from "./logger-B9h0djqx.js";
3
- import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-CpNUvHBx.js";
3
+ import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-pct2j3wR.js";
4
4
  import { argv } from "process";
5
5
  import { execFileSync, spawn } from "child_process";
6
6
  import ms from "ms";
@@ -482,7 +482,7 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
482
482
  {
483
483
  const rawArg = process.argv[2];
484
484
  const isHelpFlag = rawArg === "-h" || rawArg === "--help";
485
- const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-fCkYXyTe.js");
485
+ const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-D3Z9cD9u.js");
486
486
  if (isHelpFlag && process.argv.length === 3) {
487
487
  cmdHelp();
488
488
  process.exit(0);
@@ -515,7 +515,7 @@ if (config.useRust) {
515
515
  }
516
516
  }
517
517
  if (rustBinary) {
518
- const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-BtLklR5y.js");
518
+ const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-BGUPuqya.js");
519
519
  const rustArgs = buildRustArgs(process.argv, config.cli, SUPPORTED_CLIS);
520
520
  if (config.verbose) {
521
521
  console.log(`[rust] Using binary: ${rustBinary}`);
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-BuFWTNL9.js";
1
+ import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-BECoCPV1.js";
2
2
  import "./logger-B9h0djqx.js";
3
- import "./versionChecker-CpNUvHBx.js";
3
+ import "./versionChecker-pct2j3wR.js";
4
4
  import "./pidStore-DBjlqzo8.js";
5
5
  import "./globalPidIndex-yVd3mbsV.js";
6
6
 
@@ -1,11 +1,11 @@
1
- import "./ts-BuFWTNL9.js";
1
+ import "./ts-BECoCPV1.js";
2
2
  import "./logger-B9h0djqx.js";
3
- import "./versionChecker-CpNUvHBx.js";
3
+ import "./versionChecker-pct2j3wR.js";
4
4
  import "./pidStore-DBjlqzo8.js";
5
5
  import "./globalPidIndex-yVd3mbsV.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-C0a9K6I5.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-eIjVu8HF.js";
7
7
  import "./remotes-C3xPRtfg.js";
8
- import { c as readNotes, f as snapshotStatus, l as renderRawLog, m as writeToIpc, o as listRecords, r as controlCodeFromName, u as resolveOne } from "./subcommands-D4Muugfr.js";
8
+ import { c as readNotes, f as snapshotStatus, l as renderRawLog, m as writeToIpc, o as listRecords, r as controlCodeFromName, u as resolveOne } from "./subcommands-z8Y8gcD_.js";
9
9
  import yargs from "yargs";
10
10
  import { mkdir, open, readFile, writeFile } from "fs/promises";
11
11
  import { homedir } from "os";
@@ -60,8 +60,9 @@ async function cmdServeDaemon(sub, args) {
60
60
  }
61
61
  if (sub === "install") {
62
62
  const token = await loadOrCreateToken(void 0);
63
+ const ayBin = Bun.which("ay");
63
64
  const serveCmd = [
64
- "ay",
65
+ ...ayBin ? [process.execPath, ayBin] : ["ay"],
65
66
  "serve",
66
67
  ...args
67
68
  ].join(" ");
@@ -81,13 +82,17 @@ async function cmdServeDaemon(sub, args) {
81
82
  if (code === 0) {
82
83
  const portM = /--port[=\s](\d+)/.exec(args.join(" "));
83
84
  const port = portM ? Number(portM[1]) : DEFAULT_PORT;
85
+ const webrtcish = args.some((a) => a.startsWith("--webrtc") || a.startsWith("--share"));
86
+ const httpish = args.some((a) => a.startsWith("--http") || a.startsWith("--share")) || !args.some((a) => a.startsWith("--webrtc"));
84
87
  process.stdout.write(`\ninstalled '${DAEMON_NAME}' as a daemon via oxmgr\n`);
85
88
  process.stdout.write(`token: ${token}\n\n`);
86
- process.stdout.write(` ay ls ${token}@<host>:${port}\n`);
87
- process.stdout.write(` ay remote add <alias> http://${token}@<host>:${port}\n`);
89
+ if (httpish) {
90
+ process.stdout.write(` ay ls ${token}@<host>:${port}\n`);
91
+ process.stdout.write(` ay remote add <alias> http://${token}@<host>:${port}\n`);
92
+ }
88
93
  process.stdout.write(` ay serve logs # view server logs\n`);
89
94
  process.stdout.write(` ay serve uninstall # remove daemon\n`);
90
- if (args.some((a) => a.startsWith("--webrtc") || a.startsWith("--share"))) process.stdout.write(`\nthe WebRTC share link is printed by the daemon — see: ay serve logs\n`);
95
+ if (webrtcish) process.stdout.write("\nthe WebRTC share link is printed by the daemon — see: ay serve logs\n(the room persists in ~/.agent-yes/.share-room, so the link survives restarts)\n");
91
96
  }
92
97
  return code ?? 1;
93
98
  }
@@ -124,10 +129,12 @@ Modes (default: --http):
124
129
  --webrtc [URL] Share over WebRTC (bare flag mints a room+link on
125
130
  agent-yes.com, or pass webrtc://room:token@host).
126
131
  Alone it needs NO port — combine with --http for both.
132
+ The minted room persists in ~/.agent-yes/.share-room
133
+ (stable link across restarts; delete the file to rotate).
127
134
  --share [URL] Legacy alias for --http --webrtc
128
135
 
129
136
  Options:
130
- --port N Port to listen on (default: ${DEFAULT_PORT})\n --host HOST Interface to bind (default: 127.0.0.1; use 0.0.0.0 to expose)\n --token TOKEN Auth token (auto-generated and saved if omitted)\n --allow-spawn Deprecated no-op — the console can always spawn agents\n --tls-cert FILE TLS certificate PEM\n --tls-key FILE TLS private key PEM\n\nSubcommands:\n ay serve install install as background daemon via oxmgr\n ay serve uninstall remove daemon\n ay serve logs view daemon logs\n\nOnce running, connect from another machine:\n ay ls <token>@<host>:${DEFAULT_PORT}\n ay remote add <alias> http://<token>@<host>:${DEFAULT_PORT}\n`);
137
+ --port N Port to listen on (default: ${DEFAULT_PORT})\n --host HOST Interface to bind (default: 127.0.0.1; use 0.0.0.0 to expose)\n --token TOKEN Auth token (auto-generated and saved if omitted)\n -d, --daemon Install these flags as a background daemon via oxmgr\n (same as: ay serve install <flags>)\n --allow-spawn Deprecated no-op — the console can always spawn agents\n --tls-cert FILE TLS certificate PEM\n --tls-key FILE TLS private key PEM\n\nSubcommands:\n ay serve install install as background daemon via oxmgr\n ay serve uninstall remove daemon\n ay serve logs view daemon logs\n\nOnce running, connect from another machine:\n ay ls <token>@<host>:${DEFAULT_PORT}\n ay remote add <alias> http://<token>@<host>:${DEFAULT_PORT}\n`);
131
138
  return 0;
132
139
  }
133
140
  const sub = rest[0];
@@ -168,6 +175,7 @@ Options:
168
175
  default: false,
169
176
  description: "Deprecated no-op — the console can always spawn agents"
170
177
  }).help(false).version(false).exitProcess(false).parseAsync();
178
+ if (argv.daemon) return cmdServeDaemon("install", rest.filter((a) => a !== "--daemon" && a !== "-d"));
171
179
  const port = argv.port ?? DEFAULT_PORT;
172
180
  const host = argv.host ?? "127.0.0.1";
173
181
  const tokenFlag = typeof argv.token === "string" ? argv.token : void 0;
@@ -513,15 +521,15 @@ Options:
513
521
  }
514
522
  if (wantWebrtc) {
515
523
  const webrtcVal = argv.webrtc ?? argv.share;
516
- const shareUrl = typeof webrtcVal === "string" && webrtcVal.startsWith("webrtc://") ? webrtcVal : void 0;
524
+ const explicitUrl = typeof webrtcVal === "string" && webrtcVal.startsWith("webrtc://") ? webrtcVal : void 0;
517
525
  try {
518
- const { startShare } = await import("./share-D-r6y3xD.js");
526
+ const { startShare, loadOrCreateShareRoom } = await import("./share-BsCeIfQM.js");
519
527
  const { link } = await startShare({
520
- url: shareUrl,
528
+ url: explicitUrl ?? await loadOrCreateShareRoom(),
521
529
  localFetch: apiFetch,
522
530
  apiToken: token
523
531
  });
524
- process.stdout.write(`${wantHttp ? "\n" : ""}shared over WebRTC — open this link (the token is eaten from the URL on open):\n ${link}\n\n`);
532
+ process.stdout.write(`${wantHttp ? "\n" : ""}shared over WebRTC — open this link (the token is eaten from the URL on open):\n ${link}\n` + (explicitUrl ? "\n" : ` (persistent room — same link across restarts; delete ~/.agent-yes/.share-room to rotate)\n\n`));
525
533
  } catch (e) {
526
534
  process.stderr.write(`ay serve --webrtc failed: ${e.message}\n`);
527
535
  if (!wantHttp) return 1;
@@ -543,4 +551,4 @@ Options:
543
551
 
544
552
  //#endregion
545
553
  export { cmdServe };
546
- //# sourceMappingURL=serve-DPY37v0u.js.map
554
+ //# sourceMappingURL=serve-SQYFRbm3.js.map
@@ -1,3 +1,6 @@
1
+ import { mkdir, readFile, writeFile } from "fs/promises";
2
+ import { homedir } from "os";
3
+ import path from "path";
1
4
  import { randomBytes } from "crypto";
2
5
 
3
6
  //#region ts/share.ts
@@ -5,6 +8,20 @@ const SUB = "ay-signal-1";
5
8
  const ICE = [{ urls: "stun:stun.l.google.com:19302" }];
6
9
  const MAX_CHUNK = 15e3;
7
10
  const DEFAULT_SIGHOST = "s.agent-yes.com";
11
+ function shareRoomPath() {
12
+ const home = process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
13
+ return path.join(home, ".share-room");
14
+ }
15
+ async function loadOrCreateShareRoom(sighost = DEFAULT_SIGHOST) {
16
+ try {
17
+ const url = (await readFile(shareRoomPath(), "utf-8")).trim();
18
+ if (url.startsWith("webrtc://")) return url;
19
+ } catch {}
20
+ const url = `webrtc://${"r" + randomBytes(3).toString("hex")}:${randomBytes(32).toString("hex")}@${sighost}`;
21
+ await mkdir(path.dirname(shareRoomPath()), { recursive: true });
22
+ await writeFile(shareRoomPath(), url, { mode: 384 });
23
+ return url;
24
+ }
8
25
  function parseShareUrl(s) {
9
26
  const m = /^webrtc:\/\/([^:@/]+):([^@/]+)@(.+)$/.exec(s);
10
27
  if (!m) throw new Error(`bad --share url: ${s} (want webrtc://room:token@host)`);
@@ -180,5 +197,5 @@ async function startShare(opts) {
180
197
  }
181
198
 
182
199
  //#endregion
183
- export { startShare };
184
- //# sourceMappingURL=share-D-r6y3xD.js.map
200
+ export { loadOrCreateShareRoom, startShare };
201
+ //# sourceMappingURL=share-BsCeIfQM.js.map
@@ -1,6 +1,6 @@
1
1
  import "./logger-B9h0djqx.js";
2
2
  import "./globalPidIndex-yVd3mbsV.js";
3
3
  import "./remotes-C3xPRtfg.js";
4
- import { a as isSubcommand, c as readNotes, d as runSubcommand, f as snapshotStatus, i as isPidAlive, l as renderRawLog, m as writeToIpc, n as cmdHelp, o as listRecords, p as stopTipForCli, r as controlCodeFromName, s as matchKeyword, t as GRACEFUL_EXIT_COMMANDS, u as resolveOne } from "./subcommands-D4Muugfr.js";
4
+ import { a as isSubcommand, c as readNotes, d as runSubcommand, f as snapshotStatus, i as isPidAlive, l as renderRawLog, m as writeToIpc, n as cmdHelp, o as listRecords, p as stopTipForCli, r as controlCodeFromName, s as matchKeyword, t as GRACEFUL_EXIT_COMMANDS, u as resolveOne } from "./subcommands-z8Y8gcD_.js";
5
5
 
6
6
  export { cmdHelp, isSubcommand, runSubcommand };
@@ -163,7 +163,7 @@ async function runSubcommand(argv) {
163
163
  case "restart": return await cmdRestart(rest);
164
164
  case "note": return await cmdNote(rest);
165
165
  case "serve": {
166
- const { cmdServe } = await import("./serve-DPY37v0u.js");
166
+ const { cmdServe } = await import("./serve-SQYFRbm3.js");
167
167
  return cmdServe(rest);
168
168
  }
169
169
  case "setup": {
@@ -1452,4 +1452,4 @@ async function cmdStatus(rest) {
1452
1452
 
1453
1453
  //#endregion
1454
1454
  export { isSubcommand as a, readNotes as c, runSubcommand as d, snapshotStatus as f, isPidAlive as i, renderRawLog as l, writeToIpc as m, cmdHelp as n, listRecords as o, stopTipForCli as p, controlCodeFromName as r, matchKeyword as s, GRACEFUL_EXIT_COMMANDS as t, resolveOne as u };
1455
- //# sourceMappingURL=subcommands-D4Muugfr.js.map
1455
+ //# sourceMappingURL=subcommands-z8Y8gcD_.js.map
@@ -1,5 +1,5 @@
1
1
  import { n as logger, t as addTransport } from "./logger-B9h0djqx.js";
2
- import { r as getInstalledPackage } from "./versionChecker-CpNUvHBx.js";
2
+ import { r as getInstalledPackage } from "./versionChecker-pct2j3wR.js";
3
3
  import { n as agentYesHome, t as PidStore } from "./pidStore-DBjlqzo8.js";
4
4
  import { i as shouldUseLock, r as releaseLock, t as acquireLock } from "./runningLock-CJxsoGdb.js";
5
5
  import { i as readGlobalPids } from "./globalPidIndex-yVd3mbsV.js";
@@ -1714,4 +1714,4 @@ function sleep(ms) {
1714
1714
 
1715
1715
  //#endregion
1716
1716
  export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
1717
- //# sourceMappingURL=ts-BuFWTNL9.js.map
1717
+ //# sourceMappingURL=ts-BECoCPV1.js.map
@@ -7,7 +7,7 @@ import { fileURLToPath } from "url";
7
7
 
8
8
  //#region package.json
9
9
  var name = "agent-yes";
10
- var version = "1.98.0";
10
+ var version = "1.99.0";
11
11
 
12
12
  //#endregion
13
13
  //#region ts/versionChecker.ts
@@ -221,4 +221,4 @@ async function displayVersion() {
221
221
 
222
222
  //#endregion
223
223
  export { versionString as i, displayVersion as n, getInstalledPackage as r, checkAndAutoUpdate as t };
224
- //# sourceMappingURL=versionChecker-CpNUvHBx.js.map
224
+ //# sourceMappingURL=versionChecker-pct2j3wR.js.map
package/lab/ui/index.html CHANGED
@@ -316,6 +316,24 @@
316
316
  .newbtn:hover {
317
317
  filter: brightness(1.12);
318
318
  }
319
+ .viewbtn {
320
+ background: transparent;
321
+ border: 1px solid var(--line);
322
+ border-radius: 7px;
323
+ color: var(--muted);
324
+ font-size: 11.5px;
325
+ padding: 3px 9px;
326
+ cursor: pointer;
327
+ line-height: 1.6;
328
+ }
329
+ .viewbtn:hover {
330
+ color: var(--fg);
331
+ border-color: var(--accent);
332
+ }
333
+ .viewbtn.on {
334
+ color: var(--accent);
335
+ border-color: var(--accent);
336
+ }
319
337
  .lcard .nfield {
320
338
  margin-top: 12px;
321
339
  }
@@ -428,6 +446,32 @@
428
446
  text-overflow: ellipsis;
429
447
  white-space: nowrap;
430
448
  }
449
+ /* compact view: one line per agent — dot + cli + live title (or prompt), age */
450
+ .row.crow {
451
+ display: flex;
452
+ align-items: center;
453
+ gap: 8px;
454
+ padding: 6px 18px;
455
+ }
456
+ .crow .cname {
457
+ font-weight: 600;
458
+ flex: none;
459
+ }
460
+ .crow .ctitle {
461
+ flex: 1;
462
+ min-width: 0;
463
+ font-size: 12.5px;
464
+ overflow: hidden;
465
+ text-overflow: ellipsis;
466
+ white-space: nowrap;
467
+ }
468
+ .crow .ctitle.dim {
469
+ color: var(--muted);
470
+ }
471
+ .crow .age {
472
+ margin-left: 0;
473
+ flex: none;
474
+ }
431
475
  .rowtags {
432
476
  display: flex;
433
477
  flex-wrap: wrap;
@@ -589,6 +633,7 @@
589
633
  <div class="meta">
590
634
  <span id="count"></span>
591
635
  <span class="metaright">
636
+ <button id="viewbtn" class="viewbtn" title="toggle compact list">☰</button>
592
637
  <button id="newbtn" class="newbtn" title="spawn a new agent on this fleet">
593
638
  + New agent
594
639
  </button>
@@ -1005,10 +1050,28 @@
1005
1050
  });
1006
1051
  }
1007
1052
 
1053
+ // Compact list: one line per agent (dot + cli + title), persisted per device.
1054
+ let compactList = localStorage.getItem("ay.compactList") === "1";
1055
+
1008
1056
  function renderList() {
1009
1057
  const toks = $("q").value.trim().split(/\s+/).filter(Boolean);
1010
1058
  const shown = entries.filter((e) => matches(e, toks));
1011
1059
  $("count").textContent = `${shown.length} / ${entries.length} agents`;
1060
+ $("viewbtn").classList.toggle("on", compactList);
1061
+ if (compactList) {
1062
+ $("list").innerHTML =
1063
+ shown
1064
+ .map((e) => {
1065
+ const t = e.title || e.prompt || "";
1066
+ return `<div class="row crow ${String(e.pid) === sel ? "sel" : ""}" data-pid="${e.pid}">
1067
+ <span class="dot ${esc(e.status)}"></span>
1068
+ <span class="cname">${esc(e.cli)}</span>
1069
+ <span class="ctitle ${e.title ? "" : "dim"}" title="${esc(t)}">${esc(t)}</span>
1070
+ <span class="age">${age(e)}</span></div>`;
1071
+ })
1072
+ .join("") || `<div class="empty">no match</div>`;
1073
+ return;
1074
+ }
1012
1075
  $("list").innerHTML =
1013
1076
  shown
1014
1077
  .map((e) => {
@@ -1186,6 +1249,11 @@
1186
1249
  if (row) select(row.dataset.pid);
1187
1250
  });
1188
1251
  $("q").addEventListener("input", renderList);
1252
+ $("viewbtn").addEventListener("click", () => {
1253
+ compactList = !compactList;
1254
+ localStorage.setItem("ay.compactList", compactList ? "1" : "0");
1255
+ renderList();
1256
+ });
1189
1257
  window.addEventListener("resize", () => {
1190
1258
  if (fit)
1191
1259
  try {
@@ -1,97 +1,105 @@
1
- function W() {
1
+ function F() {
2
2
  return crypto.randomUUID();
3
3
  }
4
- var E = 1e4,
5
- T = 1e4;
6
- class U {
4
+ var v = 60000,
5
+ w = 1e4,
6
+ M = 1000,
7
+ u = 120000,
8
+ b = 25000;
9
+ class H {
7
10
  opts;
8
11
  peerId;
9
12
  ws = null;
10
13
  closed = !1;
11
- reconnectDelay = 1000;
14
+ reconnectDelay = M;
12
15
  reconnectTimer = null;
16
+ dormant = !1;
13
17
  heartbeat = null;
14
18
  stableTimer = null;
15
19
  openedAt = 0;
16
- constructor(q) {
17
- this.opts = q;
18
- this.peerId = q.peerId ?? W();
20
+ constructor(z) {
21
+ this.opts = z;
22
+ this.peerId = z.peerId ?? F();
19
23
  }
20
24
  connect() {
21
25
  ((this.closed = !1), this.attachWakeListeners(), this.open());
22
26
  }
23
27
  onWake = () => {
24
28
  if (this.closed) return;
25
- let q = this.ws?.readyState;
26
- if (q === 1) return;
27
- if (q === 0) {
29
+ let z = this.ws?.readyState;
30
+ if (z === 1) return;
31
+ if (z === 0) {
28
32
  try {
29
33
  this.ws?.close();
30
34
  } catch {}
31
35
  return;
32
36
  }
33
- if (this.reconnectTimer != null) (this.clearReconnectTimer(), this.open());
37
+ if (this.dormant || this.reconnectTimer != null)
38
+ ((this.dormant = !1), this.clearReconnectTimer(), this.open());
34
39
  };
40
+ hidden() {
41
+ return globalThis.document?.visibilityState === "hidden";
42
+ }
35
43
  attachWakeListeners() {
36
44
  globalThis.document?.addEventListener("visibilitychange", this.onWake);
37
- let z = globalThis.window;
38
- (z?.addEventListener("focus", this.onWake), z?.addEventListener("online", this.onWake));
45
+ let K = globalThis.window;
46
+ (K?.addEventListener("focus", this.onWake), K?.addEventListener("online", this.onWake));
39
47
  }
40
48
  detachWakeListeners() {
41
49
  globalThis.document?.removeEventListener("visibilitychange", this.onWake);
42
- let z = globalThis.window;
43
- (z?.removeEventListener("focus", this.onWake), z?.removeEventListener("online", this.onWake));
50
+ let K = globalThis.window;
51
+ (K?.removeEventListener("focus", this.onWake), K?.removeEventListener("online", this.onWake));
44
52
  }
45
53
  roomUrl() {
46
54
  return `${this.opts.url.replace(/\/+$/, "")}/room/${encodeURIComponent(this.opts.token)}`;
47
55
  }
48
56
  open() {
49
- let q = new WebSocket(this.roomUrl());
50
- this.ws = q;
51
- let z = setTimeout(() => {
52
- if (q.readyState === 0)
57
+ let z = new WebSocket(this.roomUrl());
58
+ this.ws = z;
59
+ let K = setTimeout(() => {
60
+ if (z.readyState === 0)
53
61
  try {
54
- q.close();
62
+ z.close();
55
63
  } catch {}
56
- }, T);
57
- ((q.onopen = () => {
58
- (clearTimeout(z),
64
+ }, w);
65
+ ((z.onopen = () => {
66
+ (clearTimeout(K),
59
67
  (this.openedAt = Date.now()),
60
68
  this.clearStableTimer(),
61
69
  (this.stableTimer = setTimeout(() => {
62
- this.reconnectDelay = 1000;
63
- }, E)));
64
- let K = {
70
+ this.reconnectDelay = M;
71
+ }, v)));
72
+ let Q = {
65
73
  type: "hello",
66
74
  role: this.opts.role,
67
75
  peerId: this.peerId,
68
76
  ...(this.opts.meta ? { meta: this.opts.meta } : {}),
69
77
  };
70
- (q.send(JSON.stringify(K)), this.startHeartbeat(), this.opts.onOpen?.());
78
+ (z.send(JSON.stringify(Q)), this.startHeartbeat(), this.opts.onOpen?.());
71
79
  }),
72
- (q.onmessage = (K) => {
73
- let Q;
80
+ (z.onmessage = (Q) => {
81
+ let Y;
74
82
  try {
75
- Q = JSON.parse(String(K.data));
83
+ Y = JSON.parse(String(Q.data));
76
84
  } catch {
77
85
  return;
78
86
  }
79
- if (Q.type === "peers") this.opts.onPeers?.(Q.peers);
80
- else if (Q.type === "signal") this.opts.onSignal?.(Q.from, Q.data);
87
+ if (Y.type === "peers") this.opts.onPeers?.(Y.peers);
88
+ else if (Y.type === "signal") this.opts.onSignal?.(Y.from, Y.data);
81
89
  }),
82
- (q.onclose = (K) => {
83
- (clearTimeout(z), this.clearStableTimer(), this.stopHeartbeat());
84
- let Q = this.openedAt ? Date.now() - this.openedAt : 0;
90
+ (z.onclose = (Q) => {
91
+ (clearTimeout(K), this.clearStableTimer(), this.stopHeartbeat());
92
+ let Y = this.openedAt ? Date.now() - this.openedAt : 0;
85
93
  if (
86
94
  ((this.openedAt = 0),
87
- this.opts.onClose?.({ code: K?.code ?? 0, reason: K?.reason ?? "", ms: Q }),
95
+ this.opts.onClose?.({ code: Q?.code ?? 0, reason: Q?.reason ?? "", ms: Y }),
88
96
  !this.closed)
89
97
  )
90
98
  this.scheduleReconnect();
91
99
  }),
92
- (q.onerror = () => {
100
+ (z.onerror = () => {
93
101
  try {
94
- q.close();
102
+ z.close();
95
103
  } catch {}
96
104
  }));
97
105
  }
@@ -101,7 +109,7 @@ class U {
101
109
  try {
102
110
  this.ws?.send(JSON.stringify({ type: "ping" }));
103
111
  } catch {}
104
- }, 1e4)));
112
+ }, b)));
105
113
  }
106
114
  stopHeartbeat() {
107
115
  if (this.heartbeat != null) (clearInterval(this.heartbeat), (this.heartbeat = null));
@@ -110,29 +118,39 @@ class U {
110
118
  if (this.stableTimer != null) (clearTimeout(this.stableTimer), (this.stableTimer = null));
111
119
  }
112
120
  scheduleReconnect() {
113
- let q = this.reconnectDelay;
114
- ((this.reconnectDelay = Math.min(q * 2, 15000)),
121
+ if (this.hidden()) {
122
+ this.dormant = !0;
123
+ return;
124
+ }
125
+ let z = Math.round(this.reconnectDelay * (0.75 + Math.random() * 0.5));
126
+ ((this.reconnectDelay = Math.min(this.reconnectDelay * 2, u)),
115
127
  this.clearReconnectTimer(),
116
128
  (this.reconnectTimer = setTimeout(() => {
117
- if (((this.reconnectTimer = null), !this.closed)) this.open();
118
- }, q)));
129
+ if (((this.reconnectTimer = null), this.closed)) return;
130
+ if (this.hidden()) {
131
+ this.dormant = !0;
132
+ return;
133
+ }
134
+ this.open();
135
+ }, z)));
119
136
  }
120
137
  clearReconnectTimer() {
121
138
  if (this.reconnectTimer != null)
122
139
  (clearTimeout(this.reconnectTimer), (this.reconnectTimer = null));
123
140
  }
124
- sendSignal(q, z) {
125
- let K = { type: "signal", to: q, data: z };
126
- this.ws?.send(JSON.stringify(K));
141
+ sendSignal(z, K) {
142
+ let Q = { type: "signal", to: z, data: K };
143
+ this.ws?.send(JSON.stringify(Q));
127
144
  }
128
- updateMeta(q) {
129
- if (((this.opts.meta = q), this.ws?.readyState === 1)) {
130
- let z = { type: "meta", meta: q };
131
- this.ws.send(JSON.stringify(z));
145
+ updateMeta(z) {
146
+ if (((this.opts.meta = z), this.ws?.readyState === 1)) {
147
+ let K = { type: "meta", meta: z };
148
+ this.ws.send(JSON.stringify(K));
132
149
  }
133
150
  }
134
151
  close() {
135
152
  ((this.closed = !0),
153
+ (this.dormant = !1),
136
154
  this.detachWakeListeners(),
137
155
  this.clearReconnectTimer(),
138
156
  this.stopHeartbeat(),
@@ -142,21 +160,21 @@ class U {
142
160
  } catch {}
143
161
  }
144
162
  }
145
- var A = ["stun:stun.l.google.com:19302", "stun:stun1.l.google.com:19302"],
146
- _ = "codehost";
147
- class B {
163
+ var R = ["stun:stun.l.google.com:19302", "stun:stun1.l.google.com:19302"],
164
+ A = "codehost";
165
+ class x {
148
166
  opts;
149
167
  pc;
150
168
  channel = null;
151
- constructor(q) {
152
- this.opts = q;
153
- ((this.pc = new RTCPeerConnection({ iceServers: A.map((z) => ({ urls: z })) })),
154
- (this.pc.onicecandidate = (z) => {
155
- if (z.candidate)
169
+ constructor(z) {
170
+ this.opts = z;
171
+ ((this.pc = new RTCPeerConnection({ iceServers: R.map((K) => ({ urls: K })) })),
172
+ (this.pc.onicecandidate = (K) => {
173
+ if (K.candidate)
156
174
  this.opts.sendSignal({
157
175
  kind: "candidate",
158
- candidate: z.candidate.candidate,
159
- mid: z.candidate.sdpMid ?? "0",
176
+ candidate: K.candidate.candidate,
177
+ mid: K.candidate.sdpMid ?? "0",
160
178
  });
161
179
  }),
162
180
  (this.pc.onconnectionstatechange = () => {
@@ -164,29 +182,64 @@ class B {
164
182
  }));
165
183
  }
166
184
  async start() {
167
- let q = this.pc.createDataChannel(_, { ordered: !0 });
168
- ((q.binaryType = "arraybuffer"),
169
- (this.channel = q),
170
- (q.onopen = () => this.opts.onOpen?.(q)),
171
- (q.onclose = () => this.opts.onClose?.()));
172
- let z = await this.pc.createOffer();
173
- (await this.pc.setLocalDescription(z),
174
- this.opts.sendSignal({ kind: "offer", type: "offer", sdp: z.sdp ?? "" }));
175
- }
176
- async handleSignal(q) {
177
- let z = q;
178
- if (!z || typeof z !== "object") return;
179
- if (z.kind === "answer") await this.pc.setRemoteDescription({ type: "answer", sdp: z.sdp });
180
- else if (z.kind === "candidate")
185
+ let z = this.pc.createDataChannel(A, { ordered: !0 });
186
+ ((z.binaryType = "arraybuffer"),
187
+ (this.channel = z),
188
+ (z.onopen = () => this.opts.onOpen?.(z)),
189
+ (z.onclose = () => this.opts.onClose?.()));
190
+ let K = await this.pc.createOffer();
191
+ (await this.pc.setLocalDescription(K),
192
+ this.opts.sendSignal({ kind: "offer", type: "offer", sdp: K.sdp ?? "" }));
193
+ }
194
+ async handleSignal(z) {
195
+ let K = z;
196
+ if (!K || typeof K !== "object") return;
197
+ if (K.kind === "answer") await this.pc.setRemoteDescription({ type: "answer", sdp: K.sdp });
198
+ else if (K.kind === "candidate")
181
199
  try {
182
- await this.pc.addIceCandidate({ candidate: z.candidate, sdpMid: z.mid });
183
- } catch (K) {
184
- console.error("[rtc] addIceCandidate failed:", K);
200
+ await this.pc.addIceCandidate({ candidate: K.candidate, sdpMid: K.mid });
201
+ } catch (Q) {
202
+ console.error("[rtc] addIceCandidate failed:", Q);
185
203
  }
186
204
  }
187
205
  get dataChannel() {
188
206
  return this.channel;
189
207
  }
208
+ async selectedPath() {
209
+ try {
210
+ let z = await this.pc.getStats(),
211
+ K = null;
212
+ z.forEach(($) => {
213
+ if ($.type === "transport" && $.selectedCandidatePairId) K = $.selectedCandidatePairId;
214
+ });
215
+ let Q = null;
216
+ if (
217
+ (z.forEach(($) => {
218
+ if (
219
+ K ? $.id === K : $.type === "candidate-pair" && $.state === "succeeded" && $.nominated
220
+ )
221
+ Q = $;
222
+ }),
223
+ !Q)
224
+ )
225
+ return null;
226
+ let { localCandidateId: Y, remoteCandidateId: Z } = Q,
227
+ q = !0,
228
+ X = 0;
229
+ if (
230
+ (z.forEach(($) => {
231
+ if ($.id === Y || $.id === Z) {
232
+ if ((X++, $.candidateType !== "host")) q = !1;
233
+ }
234
+ }),
235
+ X < 2)
236
+ )
237
+ return null;
238
+ return q ? "lan" : "p2p";
239
+ } catch {
240
+ return null;
241
+ }
242
+ }
190
243
  close() {
191
244
  try {
192
245
  this.channel?.close();
@@ -196,254 +249,272 @@ class B {
196
249
  } catch {}
197
250
  }
198
251
  }
199
- var F = new TextEncoder(),
200
- x = new TextDecoder();
201
- function G(q, z, K) {
202
- let Q = K?.byteLength ?? 0,
203
- X = new Uint8Array(5 + Q);
204
- if (((X[0] = q), new DataView(X.buffer).setUint32(1, z >>> 0, !1), K && Q)) X.set(K, 5);
205
- return X;
252
+ var g = new TextEncoder(),
253
+ N = new TextDecoder();
254
+ function D(z, K, Q) {
255
+ let Y = Q?.byteLength ?? 0,
256
+ Z = new Uint8Array(5 + Y);
257
+ if (((Z[0] = z), new DataView(Z.buffer).setUint32(1, K >>> 0, !1), Q && Y)) Z.set(Q, 5);
258
+ return Z;
206
259
  }
207
- function P(q, z, K) {
208
- return G(q, z, F.encode(JSON.stringify(K)));
260
+ function k(z, K, Q) {
261
+ return D(z, K, g.encode(JSON.stringify(Q)));
209
262
  }
210
- function J(q) {
211
- let z = q instanceof Uint8Array ? q : new Uint8Array(q),
212
- K = z[0],
213
- Q = new DataView(z.buffer, z.byteOffset, z.byteLength).getUint32(1, !1),
214
- X = z.subarray(5);
215
- return { op: K, streamId: Q, payload: X };
263
+ function S(z) {
264
+ let K = z instanceof Uint8Array ? z : new Uint8Array(z),
265
+ Q = K[0],
266
+ Y = new DataView(K.buffer, K.byteOffset, K.byteLength).getUint32(1, !1),
267
+ Z = K.subarray(5);
268
+ return { op: Q, streamId: Y, payload: Z };
216
269
  }
217
- function H(q) {
218
- return JSON.parse(x.decode(q));
270
+ function U(z) {
271
+ return JSON.parse(N.decode(z));
219
272
  }
220
- function L(q) {
221
- return x.decode(q);
273
+ function T(z) {
274
+ return N.decode(z);
222
275
  }
223
- function* S(q) {
224
- for (let z = 0; z < q.byteLength; z += 16379) yield q.slice(z, Math.min(z + 16379, q.byteLength));
276
+ function* _(z) {
277
+ for (let K = 0; K < z.byteLength; K += 65531) yield z.slice(K, Math.min(K + 65531, z.byteLength));
225
278
  }
226
- function C(q) {
227
- if (q.length === 1) return q[0];
228
- let z = q.reduce((X, Z) => X + Z.byteLength, 0),
229
- K = new Uint8Array(z),
230
- Q = 0;
231
- for (let X of q) (K.set(X, Q), (Q += X.byteLength));
232
- return K;
279
+ function O(z) {
280
+ if (z.length === 1) return z[0];
281
+ let K = z.reduce((Z, q) => Z + q.byteLength, 0),
282
+ Q = new Uint8Array(K),
283
+ Y = 0;
284
+ for (let Z of z) (Q.set(Z, Y), (Y += Z.byteLength));
285
+ return Q;
233
286
  }
234
- function* M(q, z, K) {
235
- let Q = 0;
236
- while (K.byteLength - Q > 16379) (yield G(13, z, K.subarray(Q, Q + 16379)), (Q += 16379));
237
- yield G(q, z, K.subarray(Q));
287
+ function* J(z, K, Q) {
288
+ let Y = 0;
289
+ while (Q.byteLength - Y > 65531) (yield D(13, K, Q.subarray(Y, Y + 65531)), (Y += 65531));
290
+ yield D(z, K, Q.subarray(Y));
238
291
  }
239
- class k {
292
+ class W {
240
293
  pending = new Map();
241
- cont(q, z) {
242
- let K = this.pending.get(q);
243
- if (K) K.push(z.slice());
244
- else this.pending.set(q, [z.slice()]);
294
+ cont(z, K) {
295
+ let Q = this.pending.get(z);
296
+ if (Q) Q.push(K.slice());
297
+ else this.pending.set(z, [K.slice()]);
245
298
  }
246
- finish(q, z) {
247
- let K = this.pending.get(q);
248
- if (!K) return z;
249
- return (this.pending.delete(q), K.push(z), C(K));
299
+ finish(z, K) {
300
+ let Q = this.pending.get(z);
301
+ if (!Q) return K;
302
+ return (this.pending.delete(z), Q.push(K), O(Q));
250
303
  }
251
- drop(q) {
252
- this.pending.delete(q);
304
+ drop(z) {
305
+ this.pending.delete(z);
253
306
  }
254
307
  }
255
- class N {
308
+ class j {
256
309
  channel;
257
310
  nextStreamId = 1;
258
311
  https = new Map();
259
312
  wss = new Map();
260
- wsRx = new k();
313
+ wsRx = new W();
261
314
  textEncoder = new TextEncoder();
262
- constructor(q) {
263
- this.channel = q;
264
- ((q.binaryType = "arraybuffer"), q.addEventListener("message", (z) => this.onFrame(z.data)));
315
+ constructor(z) {
316
+ this.channel = z;
317
+ ((z.binaryType = "arraybuffer"), z.addEventListener("message", (K) => this.onFrame(K.data)));
265
318
  }
266
319
  allocId() {
267
- let q = this.nextStreamId;
268
- return ((this.nextStreamId = (this.nextStreamId + 1) >>> 0 || 1), q);
320
+ let z = this.nextStreamId;
321
+ return ((this.nextStreamId = (this.nextStreamId + 1) >>> 0 || 1), z);
269
322
  }
270
- onFrame(q) {
271
- if (typeof q === "string") return;
272
- let { op: z, streamId: K, payload: Q } = J(q);
273
- switch (z) {
323
+ onFrame(z) {
324
+ if (typeof z === "string") return;
325
+ let { op: K, streamId: Q, payload: Y } = S(z);
326
+ switch (K) {
274
327
  case 4:
275
- this.https.get(K)?.onHead(H(Q));
328
+ this.https.get(Q)?.onHead(U(Y));
276
329
  break;
277
330
  case 5:
278
- this.https.get(K)?.onBody(Q.slice());
331
+ this.https.get(Q)?.onBody(Y.slice());
279
332
  break;
280
333
  case 6:
281
- (this.https.get(K)?.onEnd(), this.https.delete(K));
334
+ (this.https.get(Q)?.onEnd(), this.https.delete(Q));
282
335
  break;
283
336
  case 12: {
284
- let X = this.https.get(K);
285
- if (X) (X.onError(H(Q).message), this.https.delete(K));
337
+ let Z = this.https.get(Q);
338
+ if (Z) (Z.onError(U(Y).message), this.https.delete(Q));
286
339
  break;
287
340
  }
288
341
  case 8: {
289
- let X = H(Q);
290
- this.wss.get(K)?.onOpenAck(X.ok, X.protocol);
342
+ let Z = U(Y);
343
+ this.wss.get(Q)?.onOpenAck(Z.ok, Z.protocol);
291
344
  break;
292
345
  }
293
346
  case 13:
294
- this.wsRx.cont(K, Q);
347
+ this.wsRx.cont(Q, Y);
295
348
  break;
296
349
  case 9:
297
- this.wss.get(K)?.onText(L(this.wsRx.finish(K, Q)));
350
+ this.wss.get(Q)?.onText(T(this.wsRx.finish(Q, Y)));
298
351
  break;
299
352
  case 10:
300
- this.wss.get(K)?.onBin(this.wsRx.finish(K, Q).slice());
353
+ this.wss.get(Q)?.onBin(this.wsRx.finish(Q, Y).slice());
301
354
  break;
302
355
  case 11: {
303
- let X = H(Q);
304
- (this.wsRx.drop(K),
305
- this.wss.get(K)?.onClose(X.code ?? 1000, X.reason ?? ""),
306
- this.wss.delete(K));
356
+ let Z = U(Y);
357
+ (this.wsRx.drop(Q),
358
+ this.wss.get(Q)?.onClose(Z.code ?? 1000, Z.reason ?? ""),
359
+ this.wss.delete(Q));
307
360
  break;
308
361
  }
309
362
  }
310
363
  }
311
- fetch(q, z, K, Q) {
312
- let X = this.allocId();
313
- return new Promise((Z, V) => {
314
- let D = null,
315
- $ = null,
316
- j = new ReadableStream({
317
- start: (Y) => {
318
- $ = Y;
364
+ fetch(z, K, Q, Y) {
365
+ let Z = this.allocId();
366
+ return new Promise((q, X) => {
367
+ let $ = null,
368
+ P = null,
369
+ V = new ReadableStream({
370
+ start: (G) => {
371
+ P = G;
319
372
  },
320
- });
373
+ }),
374
+ C = typeof DecompressionStream < "u" ? { ...Q, "x-codehost-accept-gzip": "1" } : Q;
321
375
  if (
322
- (this.https.set(X, {
323
- onHead: (Y) => {
324
- ((D = Y),
325
- Z(
326
- new Response(j, {
327
- status: Y.status === 204 || Y.status === 304 ? Y.status : Y.status,
328
- statusText: Y.statusText,
329
- headers: Y.headers,
330
- }),
331
- ));
376
+ (this.https.set(Z, {
377
+ onHead: (G) => {
378
+ $ = G;
379
+ let B = new Headers(G.headers),
380
+ L = V;
381
+ if (B.get("content-encoding") === "gzip")
382
+ ((L = V.pipeThrough(new DecompressionStream("gzip"))),
383
+ B.delete("content-encoding"),
384
+ B.delete("content-length"));
385
+ q(
386
+ new Response(L, {
387
+ status: G.status === 204 || G.status === 304 ? G.status : G.status,
388
+ statusText: G.statusText,
389
+ headers: B,
390
+ }),
391
+ );
332
392
  },
333
- onBody: (Y) => {
393
+ onBody: (G) => {
334
394
  try {
335
- $?.enqueue(Y);
395
+ P?.enqueue(G);
336
396
  } catch {}
337
397
  },
338
398
  onEnd: () => {
339
399
  try {
340
- $?.close();
400
+ P?.close();
341
401
  } catch {}
342
- if (!D) V(Error("stream ended before head"));
402
+ if (!$) X(Error("stream ended before head"));
343
403
  },
344
- onError: (Y) => {
404
+ onError: (G) => {
345
405
  try {
346
- $?.error(Error(Y));
406
+ P?.error(Error(G));
347
407
  } catch {}
348
- if (!D) V(Error(Y));
408
+ if (!$) X(Error(G));
349
409
  },
350
410
  }),
351
- this.send(P(1, X, { method: q, path: z, headers: K })),
352
- Q && Q.byteLength)
411
+ this.send(k(1, Z, { method: z, path: K, headers: C })),
412
+ Y && Y.byteLength)
353
413
  )
354
- for (let Y of S(Q)) this.send(G(2, X, Y));
355
- this.send(G(3, X));
414
+ for (let G of _(Y)) this.send(D(2, Z, G));
415
+ this.send(D(3, Z));
356
416
  });
357
417
  }
358
- openWs(q, z, K) {
359
- let Q = this.allocId();
418
+ openWs(z, K, Q) {
419
+ let Y = this.allocId();
360
420
  return (
361
- this.wss.set(Q, K),
362
- this.send(P(7, Q, { path: q, protocols: z })),
421
+ this.wss.set(Y, Q),
422
+ this.send(k(7, Y, { path: z, protocols: K })),
363
423
  {
364
- sendText: (X) => {
365
- for (let Z of M(9, Q, this.textEncoder.encode(X))) this.send(Z);
424
+ sendText: (Z) => {
425
+ for (let q of J(9, Y, this.textEncoder.encode(Z))) this.send(q);
366
426
  },
367
- sendBin: (X) => {
368
- for (let Z of M(10, Q, X)) this.send(Z);
427
+ sendBin: (Z) => {
428
+ for (let q of J(10, Y, Z)) this.send(q);
369
429
  },
370
- close: (X, Z) => {
371
- (this.send(P(11, Q, { code: X, reason: Z })), this.wss.delete(Q));
430
+ close: (Z, q) => {
431
+ (this.send(k(11, Y, { code: Z, reason: q })), this.wss.delete(Y));
372
432
  },
373
433
  }
374
434
  );
375
435
  }
376
- send(q) {
436
+ send(z) {
377
437
  if (this.channel.readyState === "open") {
378
- let z = new Uint8Array(q.byteLength);
379
- (z.set(q), this.channel.send(z.buffer));
438
+ let K = new Uint8Array(z.byteLength);
439
+ (K.set(z), this.channel.send(K.buffer));
380
440
  }
381
441
  }
382
442
  get ready() {
383
443
  return this.channel.readyState === "open";
384
444
  }
385
445
  }
386
- var w = "wss://signal.codehost.dev";
387
- class R {
446
+ var f = "wss://signal.codehost.dev",
447
+ y = 1e4;
448
+ class E {
388
449
  peers = [];
389
450
  signaling;
390
451
  rtcs = new Map();
391
452
  tunnels = new Map();
453
+ dialFailedAt = new Map();
392
454
  closed = !1;
393
- constructor(q) {
394
- ((this.signaling = new U({
395
- url: q.signalUrl ?? w,
396
- token: q.token,
455
+ constructor(z) {
456
+ ((this.signaling = new H({
457
+ url: z.signalUrl ?? f,
458
+ token: z.token,
397
459
  role: "viewer",
398
- onOpen: () => q.onStatus?.(!0),
399
- onClose: () => q.onStatus?.(!1),
400
- onPeers: (z) => {
401
- ((this.peers = z.filter((K) => K.role === "server")), q.onPeers?.(this.peers));
460
+ onOpen: () => z.onStatus?.(!0),
461
+ onClose: () => z.onStatus?.(!1),
462
+ onPeers: (K) => {
463
+ ((this.peers = K.filter((Q) => Q.role === "server")), z.onPeers?.(this.peers));
402
464
  },
403
- onSignal: (z, K) => void this.rtcs.get(z)?.handleSignal(K),
465
+ onSignal: (K, Q) => void this.rtcs.get(K)?.handleSignal(Q),
404
466
  })),
405
467
  this.signaling.connect());
406
468
  }
407
- async fetch(q, z, K, Q = {}) {
408
- let X = await this.dial(q),
409
- Z = typeof Q.body === "string" ? new TextEncoder().encode(Q.body) : Q.body;
410
- return X.fetch(z, K, Q.headers ?? {}, Z);
411
- }
412
- dial(q) {
413
- let z = this.tunnels.get(q);
414
- if (z) return z;
415
- let K = () => {
416
- (this.tunnels.delete(q), this.rtcs.get(q)?.close(), this.rtcs.delete(q));
469
+ async fetch(z, K, Q, Y = {}) {
470
+ let Z = await this.dial(z),
471
+ q = typeof Y.body === "string" ? new TextEncoder().encode(Y.body) : Y.body;
472
+ return Z.fetch(K, Q, Y.headers ?? {}, q);
473
+ }
474
+ dial(z) {
475
+ let K = this.tunnels.get(z);
476
+ if (K) return K;
477
+ let Q = this.dialFailedAt.get(z);
478
+ if (Q != null && Date.now() - Q < y)
479
+ return Promise.reject(Error("dial failed recently; cooling down"));
480
+ let Y = () => {
481
+ (this.tunnels.delete(z), this.rtcs.get(z)?.close(), this.rtcs.delete(z));
417
482
  },
418
- Q = new Promise((X, Z) => {
419
- let V = setTimeout(() => {
420
- (K(), Z(Error("dial timed out")));
483
+ Z = new Promise((q, X) => {
484
+ let $ = setTimeout(() => {
485
+ (Y(), X(Error("dial timed out")));
421
486
  }, 15000),
422
- D = new B({
423
- sendSignal: ($) => this.signaling.sendSignal(q, $),
424
- onOpen: ($) => {
425
- (clearTimeout(V), X(new N($)));
487
+ P = new x({
488
+ sendSignal: (V) => this.signaling.sendSignal(z, V),
489
+ onOpen: (V) => {
490
+ (clearTimeout($), this.dialFailedAt.delete(z), q(new j(V)));
426
491
  },
427
- onClose: K,
428
- onState: ($) => {
429
- if ($ === "failed" || $ === "disconnected") K();
492
+ onClose: Y,
493
+ onState: (V) => {
494
+ if (V === "failed" || V === "disconnected") Y();
430
495
  },
431
496
  });
432
- (this.rtcs.set(q, D),
433
- D.start().catch(($) => {
434
- (clearTimeout(V), K(), Z($));
497
+ (this.rtcs.set(z, P),
498
+ P.start().catch((V) => {
499
+ (clearTimeout($), Y(), X(V));
435
500
  }));
436
501
  });
437
- return (this.tunnels.set(q, Q), Q.catch(() => this.tunnels.delete(q)), Q);
502
+ return (
503
+ this.tunnels.set(z, Z),
504
+ Z.catch(() => {
505
+ (this.dialFailedAt.set(z, Date.now()), this.tunnels.delete(z));
506
+ }),
507
+ Z
508
+ );
438
509
  }
439
510
  close() {
440
511
  if (this.closed) return;
441
512
  this.closed = !0;
442
- for (let q of this.rtcs.values()) q.close();
513
+ for (let z of this.rtcs.values()) z.close();
443
514
  (this.rtcs.clear(), this.tunnels.clear(), this.signaling.close());
444
515
  }
445
516
  }
446
- function n(q) {
447
- return new R(q);
517
+ function a(z) {
518
+ return new E(z);
448
519
  }
449
- export { n as joinRoom, w as DEFAULT_SIGNAL_URL, R as CodehostRoom };
520
+ export { a as joinRoom, f as DEFAULT_SIGNAL_URL, E as CodehostRoom };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-yes",
3
- "version": "1.98.0",
3
+ "version": "1.99.0",
4
4
  "description": "A wrapper tool that automates interactions with various AI CLI tools by automatically handling common prompts and responses.",
5
5
  "keywords": [
6
6
  "ai",
package/ts/serve.ts CHANGED
@@ -83,8 +83,11 @@ async function cmdServeDaemon(sub: string, args: string[]): Promise<number> {
83
83
 
84
84
  if (sub === "install") {
85
85
  const token = await loadOrCreateToken(undefined);
86
- // Build the ay serve command with forwarded args (port, host, --webrtc, etc.)
87
- const serveCmd = ["ay", "serve", ...args].join(" ");
86
+ // Build the ay serve command with forwarded args (port, host, --webrtc, etc.).
87
+ // Absolute paths: oxmgr's daemon environment may not have ~/.bun/bin in
88
+ // PATH, so a bare `ay` (or its `#!/usr/bin/env bun` shebang) fails to spawn.
89
+ const ayBin = Bun.which("ay");
90
+ const serveCmd = [...(ayBin ? [process.execPath, ayBin] : ["ay"]), "serve", ...args].join(" ");
88
91
  const proc = Bun.spawn(
89
92
  [oxmgrBin, "start", serveCmd, "--name", DAEMON_NAME, "--restart", "always"],
90
93
  { stdio: ["ignore", "inherit", "inherit"] },
@@ -93,15 +96,23 @@ async function cmdServeDaemon(sub: string, args: string[]): Promise<number> {
93
96
  if (code === 0) {
94
97
  const portM = /--port[=\s](\d+)/.exec(args.join(" "));
95
98
  const port = portM ? Number(portM[1]) : DEFAULT_PORT;
99
+ // Mirror cmdServe's mode resolution: webrtc-only daemons open no HTTP port.
100
+ const webrtcish = args.some((a) => a.startsWith("--webrtc") || a.startsWith("--share"));
101
+ const httpish =
102
+ args.some((a) => a.startsWith("--http") || a.startsWith("--share")) ||
103
+ !args.some((a) => a.startsWith("--webrtc"));
96
104
  process.stdout.write(`\ninstalled '${DAEMON_NAME}' as a daemon via oxmgr\n`);
97
105
  process.stdout.write(`token: ${token}\n\n`);
98
- process.stdout.write(` ay ls ${token}@<host>:${port}\n`);
99
- process.stdout.write(` ay remote add <alias> http://${token}@<host>:${port}\n`);
106
+ if (httpish) {
107
+ process.stdout.write(` ay ls ${token}@<host>:${port}\n`);
108
+ process.stdout.write(` ay remote add <alias> http://${token}@<host>:${port}\n`);
109
+ }
100
110
  process.stdout.write(` ay serve logs # view server logs\n`);
101
111
  process.stdout.write(` ay serve uninstall # remove daemon\n`);
102
- if (args.some((a) => a.startsWith("--webrtc") || a.startsWith("--share"))) {
112
+ if (webrtcish) {
103
113
  process.stdout.write(
104
- `\nthe WebRTC share link is printed by the daemon — see: ay serve logs\n`,
114
+ `\nthe WebRTC share link is printed by the daemon — see: ay serve logs\n` +
115
+ `(the room persists in ~/.agent-yes/.share-room, so the link survives restarts)\n`,
105
116
  );
106
117
  }
107
118
  }
@@ -140,11 +151,15 @@ export async function cmdServe(rest: string[]): Promise<number> {
140
151
  ` --webrtc [URL] Share over WebRTC (bare flag mints a room+link on\n` +
141
152
  ` agent-yes.com, or pass webrtc://room:token@host).\n` +
142
153
  ` Alone it needs NO port — combine with --http for both.\n` +
154
+ ` The minted room persists in ~/.agent-yes/.share-room\n` +
155
+ ` (stable link across restarts; delete the file to rotate).\n` +
143
156
  ` --share [URL] Legacy alias for --http --webrtc\n\n` +
144
157
  `Options:\n` +
145
158
  ` --port N Port to listen on (default: ${DEFAULT_PORT})\n` +
146
159
  ` --host HOST Interface to bind (default: 127.0.0.1; use 0.0.0.0 to expose)\n` +
147
160
  ` --token TOKEN Auth token (auto-generated and saved if omitted)\n` +
161
+ ` -d, --daemon Install these flags as a background daemon via oxmgr\n` +
162
+ ` (same as: ay serve install <flags>)\n` +
148
163
  ` --allow-spawn Deprecated no-op — the console can always spawn agents\n` +
149
164
  ` --tls-cert FILE TLS certificate PEM\n` +
150
165
  ` --tls-key FILE TLS private key PEM\n\n` +
@@ -205,6 +220,14 @@ export async function cmdServe(rest: string[]): Promise<number> {
205
220
  .exitProcess(false);
206
221
 
207
222
  const argv = await y.parseAsync();
223
+
224
+ // --daemon/-d: install these exact flags as the oxmgr daemon instead of
225
+ // serving in the foreground (sugar for `ay serve install <flags>`).
226
+ if (argv.daemon) {
227
+ const fwd = rest.filter((a) => a !== "--daemon" && a !== "-d");
228
+ return cmdServeDaemon("install", fwd);
229
+ }
230
+
208
231
  const port = (argv.port as number) ?? DEFAULT_PORT;
209
232
  const host = (argv.host as string) ?? "127.0.0.1";
210
233
  const tokenFlag = typeof argv.token === "string" ? argv.token : undefined;
@@ -651,17 +674,22 @@ export async function cmdServe(rest: string[]): Promise<number> {
651
674
  // webrtc:// value joins an explicit one.
652
675
  if (wantWebrtc) {
653
676
  const webrtcVal = (argv.webrtc ?? argv.share) as string | undefined;
654
- const shareUrl =
677
+ const explicitUrl =
655
678
  typeof webrtcVal === "string" && webrtcVal.startsWith("webrtc://") ? webrtcVal : undefined;
656
679
  try {
657
- const { startShare } = await import("./share.ts");
680
+ const { startShare, loadOrCreateShareRoom } = await import("./share.ts");
681
+ // No explicit webrtc:// URL → reuse the persisted room (minted once and
682
+ // saved like the serve token), so the link is stable across restarts.
658
683
  const { link } = await startShare({
659
- url: shareUrl,
684
+ url: explicitUrl ?? (await loadOrCreateShareRoom()),
660
685
  localFetch: apiFetch,
661
686
  apiToken: token,
662
687
  });
663
688
  process.stdout.write(
664
- `${wantHttp ? "\n" : ""}shared over WebRTC — open this link (the token is eaten from the URL on open):\n ${link}\n\n`,
689
+ `${wantHttp ? "\n" : ""}shared over WebRTC — open this link (the token is eaten from the URL on open):\n ${link}\n` +
690
+ (explicitUrl
691
+ ? "\n"
692
+ : ` (persistent room — same link across restarts; delete ~/.agent-yes/.share-room to rotate)\n\n`),
665
693
  );
666
694
  } catch (e) {
667
695
  process.stderr.write(`ay serve --webrtc failed: ${(e as Error).message}\n`);
package/ts/share.ts CHANGED
@@ -5,6 +5,9 @@
5
5
  // lab/ui/cf/worker.ts for the signaling protocol and lab/ui/index.html for the
6
6
  // browser side.
7
7
  import { randomBytes } from "crypto";
8
+ import { mkdir, readFile, writeFile } from "fs/promises";
9
+ import { homedir } from "os";
10
+ import path from "path";
8
11
 
9
12
  const SUB = "ay-signal-1";
10
13
  const ICE = [{ urls: "stun:stun.l.google.com:19302" }];
@@ -12,7 +15,8 @@ const MAX_CHUNK = 15_000; // keep DataChannel messages under the SCTP limit
12
15
  const DEFAULT_SIGHOST = "s.agent-yes.com";
13
16
 
14
17
  export interface ShareOpts {
15
- /** webrtc://room:token@host, or undefined to mint a fresh room+token */
18
+ /** webrtc://room:token@host, or undefined to mint a fresh (unpersisted)
19
+ * room+token — callers wanting a stable room use loadOrCreateShareRoom() */
16
20
  url?: string;
17
21
  /** signaling host when minting (default s.agent-yes.com) */
18
22
  sighost?: string;
@@ -22,6 +26,30 @@ export interface ShareOpts {
22
26
  apiToken: string;
23
27
  }
24
28
 
29
+ // The room+token persist like the serve token, so the share link (and any
30
+ // browser that saved the room) survives restarts — important for daemons,
31
+ // which would otherwise mint a new link on every restart. Delete the file to
32
+ // rotate the room.
33
+ function shareRoomPath(): string {
34
+ const home = process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
35
+ return path.join(home, ".share-room");
36
+ }
37
+
38
+ export async function loadOrCreateShareRoom(sighost = DEFAULT_SIGHOST): Promise<string> {
39
+ try {
40
+ const url = (await readFile(shareRoomPath(), "utf-8")).trim();
41
+ if (url.startsWith("webrtc://")) return url;
42
+ } catch {
43
+ /* not yet minted */
44
+ }
45
+ const room = "r" + randomBytes(3).toString("hex");
46
+ const token = randomBytes(32).toString("hex");
47
+ const url = `webrtc://${room}:${token}@${sighost}`;
48
+ await mkdir(path.dirname(shareRoomPath()), { recursive: true });
49
+ await writeFile(shareRoomPath(), url, { mode: 0o600 });
50
+ return url;
51
+ }
52
+
25
53
  function parseShareUrl(s: string): { room: string; token: string; host: string } {
26
54
  const m = /^webrtc:\/\/([^:@/]+):([^@/]+)@(.+)$/.exec(s);
27
55
  if (!m) throw new Error(`bad --share url: ${s} (want webrtc://room:token@host)`);
@@ -1,8 +0,0 @@
1
- import "./ts-BuFWTNL9.js";
2
- import "./logger-B9h0djqx.js";
3
- import "./versionChecker-CpNUvHBx.js";
4
- import "./pidStore-DBjlqzo8.js";
5
- import "./globalPidIndex-yVd3mbsV.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-C0a9K6I5.js";
7
-
8
- export { SUPPORTED_CLIS };