agent-yes 1.122.0 → 1.122.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,8 @@
1
+ import "./ts-DyDU_Dae.js";
2
+ import "./logger-B9h0djqx.js";
3
+ import "./versionChecker-DmCadDPY.js";
4
+ import "./pidStore-B5vBu8Px.js";
5
+ import "./globalPidIndex-gZuTvTBs.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-f50t1rrA.js";
7
+
8
+ export { SUPPORTED_CLIS };
@@ -1,8 +1,8 @@
1
- import { t as CLIS_CONFIG } from "./ts-7kSDmCpQ.js";
1
+ import { t as CLIS_CONFIG } from "./ts-DyDU_Dae.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-DFYbm2uk.js.map
8
+ //# sourceMappingURL=SUPPORTED_CLIS-f50t1rrA.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-B5vxV_hH.js";
3
+ import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-DmCadDPY.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-CzZ4uBIa.js");
485
+ const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-BkR-nSAB.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-BVqe3k7F.js");
518
+ const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-DcWAr8NI.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-7kSDmCpQ.js";
1
+ import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-DyDU_Dae.js";
2
2
  import "./logger-B9h0djqx.js";
3
- import "./versionChecker-B5vxV_hH.js";
3
+ import "./versionChecker-DmCadDPY.js";
4
4
  import "./pidStore-B5vBu8Px.js";
5
5
  import "./globalPidIndex-gZuTvTBs.js";
6
6
 
@@ -1,9 +1,9 @@
1
- import "./ts-7kSDmCpQ.js";
1
+ import "./ts-DyDU_Dae.js";
2
2
  import "./logger-B9h0djqx.js";
3
- import "./versionChecker-B5vxV_hH.js";
3
+ import "./versionChecker-DmCadDPY.js";
4
4
  import "./pidStore-B5vBu8Px.js";
5
5
  import "./globalPidIndex-gZuTvTBs.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DFYbm2uk.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-f50t1rrA.js";
7
7
  import { n as resolveSpawnCwd } from "./workspaceConfig-XP2NEWmV.js";
8
8
  import { createHash } from "node:crypto";
9
9
 
@@ -141,4 +141,4 @@ async function cmdSchedule(rest) {
141
141
 
142
142
  //#endregion
143
143
  export { cmdSchedule };
144
- //# sourceMappingURL=schedule-CJaNc82S.js.map
144
+ //# sourceMappingURL=schedule-OJeQo0Da.js.map
@@ -1,11 +1,11 @@
1
- import "./ts-7kSDmCpQ.js";
1
+ import "./ts-DyDU_Dae.js";
2
2
  import "./logger-B9h0djqx.js";
3
- import { r as getInstalledPackage } from "./versionChecker-B5vxV_hH.js";
3
+ import { r as getInstalledPackage } from "./versionChecker-DmCadDPY.js";
4
4
  import "./pidStore-B5vBu8Px.js";
5
5
  import "./globalPidIndex-gZuTvTBs.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DFYbm2uk.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-f50t1rrA.js";
7
7
  import "./remotes-D2fqaRU8.js";
8
- import { c as listRecords, d as renderRawLog, f as resolveOne, g as writeToIpc, m as snapshotStatus, r as controlCodeFromName, u as readNotes } from "./subcommands-CmNGbbIA.js";
8
+ import { c as listRecords, d as renderRawLog, f as resolveOne, g as writeToIpc, m as snapshotStatus, r as controlCodeFromName, u as readNotes } from "./subcommands-CT1z9Jl4.js";
9
9
  import yargs from "yargs";
10
10
  import { mkdir, open, readFile, writeFile } from "fs/promises";
11
11
  import { homedir, hostname, userInfo } from "os";
@@ -918,22 +918,28 @@ Options:
918
918
  const webrtcVal = argv.webrtc ?? argv.share;
919
919
  const explicitUrl = typeof webrtcVal === "string" && webrtcVal.startsWith("webrtc://") ? webrtcVal : void 0;
920
920
  try {
921
- const { startShare, loadOrCreateShareRoom } = await import("./share-ClsUSd_0.js");
921
+ const { startShare, loadOrCreateShareRoom } = await import("./share-CksllWW-.js");
922
+ const linkFile = path.join(process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes"), ".share-link");
923
+ const announce = async (room, link, rotated) => {
924
+ const lead = rotated ? "the room was rejected by signaling (stale generation) — rotated to a fresh link" : "shared over WebRTC — open this link (the token is eaten from the URL on open)";
925
+ if (process.stdout.isTTY) {
926
+ const persistNote = explicitUrl ? "\n" : ` (persistent room — same link across restarts; delete ~/.agent-yes/.share-room to rotate)\n\n`;
927
+ process.stdout.write(`${wantHttp ? "\n" : ""}${lead}:\n ${link}\n` + persistNote);
928
+ } else {
929
+ try {
930
+ await writeFile(linkFile, link + "\n", { mode: 384 });
931
+ } catch {}
932
+ process.stdout.write(`${wantHttp ? "\n" : ""}${rotated ? "rotated WebRTC room" : "shared over WebRTC"} · room ${room} — the link carries a secret, so it is NOT logged.\n read it from ${linkFile} (mode 0600); delete ~/.agent-yes/.share-room to rotate\n\n`);
933
+ }
934
+ };
922
935
  const { room, link, close } = await startShare({
923
936
  url: explicitUrl ?? await loadOrCreateShareRoom(),
924
937
  localFetch: apiFetch,
925
- apiToken: token
938
+ apiToken: token,
939
+ onRotate: explicitUrl ? void 0 : (info) => announce(info.room, info.link, true)
926
940
  });
927
941
  closeShare = close;
928
- const persistNote = explicitUrl ? "\n" : ` (persistent room — same link across restarts; delete ~/.agent-yes/.share-room to rotate)\n\n`;
929
- if (process.stdout.isTTY) process.stdout.write(`${wantHttp ? "\n" : ""}shared over WebRTC — open this link (the token is eaten from the URL on open):\n ${link}\n` + persistNote);
930
- else {
931
- const linkFile = path.join(process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes"), ".share-link");
932
- try {
933
- await writeFile(linkFile, link + "\n", { mode: 384 });
934
- } catch {}
935
- process.stdout.write(`${wantHttp ? "\n" : ""}shared over WebRTC · room ${room} — the link carries a secret, so it is NOT logged.\n read it from ${linkFile} (mode 0600); delete ~/.agent-yes/.share-room to rotate\n\n`);
936
- }
942
+ await announce(room, link, false);
937
943
  } catch (e) {
938
944
  process.stderr.write(`ay serve --webrtc failed: ${e.message}\n`);
939
945
  if (!wantHttp) return 1;
@@ -957,4 +963,4 @@ Options:
957
963
 
958
964
  //#endregion
959
965
  export { cmdServe };
960
- //# sourceMappingURL=serve-VczpuYDk.js.map
966
+ //# sourceMappingURL=serve-O3e2YFfp.js.map
@@ -32,7 +32,7 @@ async function cmdSetup(rest) {
32
32
  if (!existsSync(abs)) process.stderr.write(` note: that directory doesn't exist yet — create it, or agents spawned there will fail\n`);
33
33
  if (noShare) return 0;
34
34
  process.stdout.write(`\nsharing this machine to agent-yes.com…\n`);
35
- const { cmdServe } = await import("./serve-VczpuYDk.js");
35
+ const { cmdServe } = await import("./serve-O3e2YFfp.js");
36
36
  return cmdServe([
37
37
  "install",
38
38
  "--share",
@@ -42,4 +42,4 @@ async function cmdSetup(rest) {
42
42
 
43
43
  //#endregion
44
44
  export { cmdSetup };
45
- //# sourceMappingURL=setup-DveWqmSR.js.map
45
+ //# sourceMappingURL=setup-yKMfadhq.js.map
@@ -180,6 +180,7 @@ function randomHex(n) {
180
180
  const SUB = "ay-signal-1";
181
181
  const DEFAULT_SIGHOST = "s.agent-yes.com";
182
182
  const HOST_HEARTBEAT_MS = 2e4;
183
+ const SIG_REFRESH_MS = 4 * 6e4;
183
184
  const STUN = [{ urls: "stun:stun.l.google.com:19302" }];
184
185
  let iceCache = null;
185
186
  async function getIceServers() {
@@ -300,17 +301,44 @@ async function importRTC() {
300
301
  async function startShare(opts) {
301
302
  opts.url;
302
303
  const sighost = opts.sighost ?? DEFAULT_SIGHOST;
303
- const { room, token, host } = opts.url ? parseShareUrl(opts.url) : {
304
+ const initial = opts.url ? parseShareUrl(opts.url) : {
304
305
  room: "r" + randomBytes(3).toString("hex"),
305
306
  token: `${MARKER}${randomBytes(32).toString("hex")}`,
306
307
  host: sighost
307
308
  };
308
- const { s: S, v2 } = parseSecret(token);
309
+ const host = initial.host;
310
+ let room = initial.room;
311
+ let token = initial.token;
312
+ const { s: firstS, v2 } = parseSecret(token);
309
313
  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();
314
+ let S = firstS;
312
315
  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}`;
316
+ const ui = host === "s.agent-yes.com" ? "https://agent-yes.com" : "http://localhost:7778";
317
+ const suffix = host === "s.agent-yes.com" ? "" : "@" + host;
318
+ const mkLink = () => `${ui}/#${room}:${MARKER}${S}${suffix}`;
319
+ let authToken = await deriveAuthToken(S, room, host);
320
+ let link = mkLink();
321
+ const RTCPeerConnection = await importRTC();
322
+ let rotateCount = 0;
323
+ const rotate = async () => {
324
+ if (!opts.onRotate || closed || rotateCount >= 5) return false;
325
+ rotateCount++;
326
+ room = "r" + randomBytes(3).toString("hex");
327
+ token = `${MARKER}${randomBytes(32).toString("hex")}`;
328
+ S = parseSecret(token).s;
329
+ authToken = await deriveAuthToken(S, room, host);
330
+ link = mkLink();
331
+ if (closed) return false;
332
+ try {
333
+ await mkdir(path.dirname(shareRoomPath()), { recursive: true });
334
+ await writeFile(shareRoomPath(), `webrtc://${room}:${token}@${host}`, { mode: 384 });
335
+ } catch {}
336
+ await opts.onRotate({
337
+ room,
338
+ link
339
+ });
340
+ return true;
341
+ };
314
342
  const peers = /* @__PURE__ */ new Map();
315
343
  let closed = false;
316
344
  let currentWs;
@@ -321,11 +349,16 @@ async function startShare(opts) {
321
349
  let ready = false;
322
350
  let lastRecv = Date.now();
323
351
  let hb;
352
+ let refresh;
324
353
  const stopHb = () => {
325
354
  if (hb) {
326
355
  clearInterval(hb);
327
356
  hb = void 0;
328
357
  }
358
+ if (refresh) {
359
+ clearTimeout(refresh);
360
+ refresh = void 0;
361
+ }
329
362
  };
330
363
  ws.onopen = () => {
331
364
  ws.send(JSON.stringify({
@@ -349,6 +382,11 @@ async function startShare(opts) {
349
382
  ws.send(JSON.stringify({ type: "ping" }));
350
383
  } catch {}
351
384
  }, HOST_HEARTBEAT_MS);
385
+ refresh = setTimeout(() => {
386
+ try {
387
+ ws.close();
388
+ } catch {}
389
+ }, SIG_REFRESH_MS);
352
390
  onReady();
353
391
  };
354
392
  ws.onmessage = async (ev) => {
@@ -380,8 +418,16 @@ async function startShare(opts) {
380
418
  stopHb();
381
419
  if (closed) return;
382
420
  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");
421
+ rotate().then((rotated) => {
422
+ if (rotated) connectSignaling(() => {});
423
+ else {
424
+ closed = true;
425
+ process.stderr.write("[share] room rejected by signaling server — delete ~/.agent-yes/.share-room to rotate the room\n");
426
+ }
427
+ }).catch(() => {
428
+ closed = true;
429
+ process.stderr.write("[share] room rejected and rotation failed — delete ~/.agent-yes/.share-room to rotate manually\n");
430
+ });
385
431
  return;
386
432
  }
387
433
  setTimeout(() => connectSignaling(() => {}), ready ? 1e3 : 2e3);
@@ -579,4 +625,4 @@ async function startShare(opts) {
579
625
 
580
626
  //#endregion
581
627
  export { loadOrCreateShareRoom, startShare };
582
- //# sourceMappingURL=share-ClsUSd_0.js.map
628
+ //# sourceMappingURL=share-CksllWW-.js.map
@@ -1,6 +1,6 @@
1
1
  import "./logger-B9h0djqx.js";
2
2
  import "./globalPidIndex-gZuTvTBs.js";
3
3
  import "./remotes-D2fqaRU8.js";
4
- import { a as finalizedLines, c as listRecords, d as renderRawLog, f as resolveOne, g as writeToIpc, h as stopTipForCli, i as cursorAbs, l as matchKeyword, m as snapshotStatus, n as cmdHelp, o as isPidAlive, p as runSubcommand, r as controlCodeFromName, s as isSubcommand, t as GRACEFUL_EXIT_COMMANDS, u as readNotes } from "./subcommands-CmNGbbIA.js";
4
+ import { a as finalizedLines, c as listRecords, d as renderRawLog, f as resolveOne, g as writeToIpc, h as stopTipForCli, i as cursorAbs, l as matchKeyword, m as snapshotStatus, n as cmdHelp, o as isPidAlive, p as runSubcommand, r as controlCodeFromName, s as isSubcommand, t as GRACEFUL_EXIT_COMMANDS, u as readNotes } from "./subcommands-CT1z9Jl4.js";
5
5
 
6
6
  export { cmdHelp, isSubcommand, runSubcommand };
@@ -233,15 +233,15 @@ async function runSubcommand(argv) {
233
233
  case "restart": return await cmdRestart(rest);
234
234
  case "note": return await cmdNote(rest);
235
235
  case "serve": {
236
- const { cmdServe } = await import("./serve-VczpuYDk.js");
236
+ const { cmdServe } = await import("./serve-O3e2YFfp.js");
237
237
  return cmdServe(rest);
238
238
  }
239
239
  case "setup": {
240
- const { cmdSetup } = await import("./setup-DveWqmSR.js");
240
+ const { cmdSetup } = await import("./setup-yKMfadhq.js");
241
241
  return cmdSetup(rest);
242
242
  }
243
243
  case "schedule": {
244
- const { cmdSchedule } = await import("./schedule-CJaNc82S.js");
244
+ const { cmdSchedule } = await import("./schedule-OJeQo0Da.js");
245
245
  return cmdSchedule(rest);
246
246
  }
247
247
  case "remote": {
@@ -1689,4 +1689,4 @@ async function cmdStatus(rest) {
1689
1689
 
1690
1690
  //#endregion
1691
1691
  export { finalizedLines as a, listRecords as c, renderRawLog as d, resolveOne as f, writeToIpc as g, stopTipForCli as h, cursorAbs as i, matchKeyword as l, snapshotStatus as m, cmdHelp as n, isPidAlive as o, runSubcommand as p, controlCodeFromName as r, isSubcommand as s, GRACEFUL_EXIT_COMMANDS as t, readNotes as u };
1692
- //# sourceMappingURL=subcommands-CmNGbbIA.js.map
1692
+ //# sourceMappingURL=subcommands-CT1z9Jl4.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-B5vxV_hH.js";
2
+ import { r as getInstalledPackage } from "./versionChecker-DmCadDPY.js";
3
3
  import { t as agentYesHome } from "./agentYesHome-BvaUOzCV.js";
4
4
  import { i as shouldUseLock, r as releaseLock, t as acquireLock } from "./runningLock-CJxsoGdb.js";
5
5
  import { t as PidStore } from "./pidStore-B5vBu8Px.js";
@@ -1784,4 +1784,4 @@ function sleep(ms) {
1784
1784
 
1785
1785
  //#endregion
1786
1786
  export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
1787
- //# sourceMappingURL=ts-7kSDmCpQ.js.map
1787
+ //# sourceMappingURL=ts-DyDU_Dae.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.122.0";
10
+ var version = "1.122.1";
11
11
 
12
12
  //#endregion
13
13
  //#region ts/versionChecker.ts
@@ -215,4 +215,4 @@ async function displayVersion() {
215
215
 
216
216
  //#endregion
217
217
  export { versionString as i, displayVersion as n, getInstalledPackage as r, checkAndAutoUpdate as t };
218
- //# sourceMappingURL=versionChecker-B5vxV_hH.js.map
218
+ //# sourceMappingURL=versionChecker-DmCadDPY.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-yes",
3
- "version": "1.122.0",
3
+ "version": "1.122.1",
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
@@ -1219,39 +1219,47 @@ export async function cmdServe(rest: string[]): Promise<number> {
1219
1219
  typeof webrtcVal === "string" && webrtcVal.startsWith("webrtc://") ? webrtcVal : undefined;
1220
1220
  try {
1221
1221
  const { startShare, loadOrCreateShareRoom } = await import("./share.ts");
1222
+ const linkFile = path.join(
1223
+ process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes"),
1224
+ ".share-link",
1225
+ );
1226
+ // Announce the link — reused for the initial share and for any auto-rotation
1227
+ // (when the signaling server rejects a stale persisted room).
1228
+ const announce = async (room: string, link: string, rotated: boolean) => {
1229
+ const lead = rotated
1230
+ ? "the room was rejected by signaling (stale generation) — rotated to a fresh link"
1231
+ : "shared over WebRTC — open this link (the token is eaten from the URL on open)";
1232
+ if (process.stdout.isTTY) {
1233
+ const persistNote = explicitUrl
1234
+ ? "\n"
1235
+ : ` (persistent room — same link across restarts; delete ~/.agent-yes/.share-room to rotate)\n\n`;
1236
+ process.stdout.write(`${wantHttp ? "\n" : ""}${lead}:\n ${link}\n` + persistNote);
1237
+ } else {
1238
+ // Non-TTY (daemon/journal/CI): the link embeds the room secret S, so never
1239
+ // write it to a log stream. Stash it in a 0600 file and point there instead.
1240
+ try {
1241
+ await writeFile(linkFile, link + "\n", { mode: 0o600 });
1242
+ } catch {
1243
+ /* best effort */
1244
+ }
1245
+ process.stdout.write(
1246
+ `${wantHttp ? "\n" : ""}${rotated ? "rotated WebRTC room" : "shared over WebRTC"} · room ${room} — the link carries a secret, so it is NOT logged.\n` +
1247
+ ` read it from ${linkFile} (mode 0600); delete ~/.agent-yes/.share-room to rotate\n\n`,
1248
+ );
1249
+ }
1250
+ };
1222
1251
  // No explicit webrtc:// URL → reuse the persisted room (minted once and
1223
1252
  // saved like the serve token), so the link is stable across restarts.
1253
+ // Only the persisted path may auto-rotate (onRotate set); an explicit URL
1254
+ // is the operator's choice and must not be silently changed.
1224
1255
  const { room, link, close } = await startShare({
1225
1256
  url: explicitUrl ?? (await loadOrCreateShareRoom()),
1226
1257
  localFetch: apiFetch,
1227
1258
  apiToken: token,
1259
+ onRotate: explicitUrl ? undefined : (info) => announce(info.room, info.link, true),
1228
1260
  });
1229
1261
  closeShare = close;
1230
- const persistNote = explicitUrl
1231
- ? "\n"
1232
- : ` (persistent room — same link across restarts; delete ~/.agent-yes/.share-room to rotate)\n\n`;
1233
- if (process.stdout.isTTY) {
1234
- process.stdout.write(
1235
- `${wantHttp ? "\n" : ""}shared over WebRTC — open this link (the token is eaten from the URL on open):\n ${link}\n` +
1236
- persistNote,
1237
- );
1238
- } else {
1239
- // Non-TTY (daemon/journal/CI): the link embeds the room secret S, so never
1240
- // write it to a log stream. Stash it in a 0600 file and point there instead.
1241
- const linkFile = path.join(
1242
- process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes"),
1243
- ".share-link",
1244
- );
1245
- try {
1246
- await writeFile(linkFile, link + "\n", { mode: 0o600 });
1247
- } catch {
1248
- /* best effort */
1249
- }
1250
- process.stdout.write(
1251
- `${wantHttp ? "\n" : ""}shared over WebRTC · room ${room} — the link carries a secret, so it is NOT logged.\n` +
1252
- ` read it from ${linkFile} (mode 0600); delete ~/.agent-yes/.share-room to rotate\n\n`,
1253
- );
1254
- }
1262
+ await announce(room, link, false);
1255
1263
  } catch (e) {
1256
1264
  process.stderr.write(`ay serve --webrtc failed: ${(e as Error).message}\n`);
1257
1265
  if (!wantHttp) return 1; // nothing else is running
package/ts/share.ts CHANGED
@@ -28,6 +28,15 @@ const SUB = "ay-signal-1";
28
28
  // MAX_CHUNK is imported from e2e.js; ICE is replaced by STUN + getIceServers (TURN) below.
29
29
  const DEFAULT_SIGHOST = "s.agent-yes.com";
30
30
  const HOST_HEARTBEAT_MS = 20000; // keepalive ping to the rendezvous + silent-drop detection
31
+ // Proactively recycle the signaling connection on this interval. The 20s ping is
32
+ // answered at the Cloudflare edge (setWebSocketAutoResponse, see cf/worker.ts) so
33
+ // it can't wake a hibernated DO — but that also means an auto-answered ping only
34
+ // proves the *edge socket* is alive, NOT that the DO still routes peer-joins to
35
+ // us. A DO that hibernated/evicted can leave us a "zombie" host: socket
36
+ // ESTABLISHED, pings auto-ponged, yet new browsers can't reach us and the
37
+ // heartbeat never trips. Re-running the hello on a timer forces the DO to
38
+ // re-register us, self-healing that state. Cheap: one reconnect per few minutes.
39
+ const SIG_REFRESH_MS = 4 * 60_000;
31
40
 
32
41
  type IceServer = { urls: string | string[]; username?: string; credential?: string };
33
42
  const STUN: IceServer[] = [{ urls: "stun:stun.l.google.com:19302" }];
@@ -77,6 +86,12 @@ export interface ShareOpts {
77
86
  localFetch: (req: Request) => Promise<Response>;
78
87
  /** bearer token for the local ay-serve API */
79
88
  apiToken: string;
89
+ /** When set, a persisted/auto-minted room may auto-rotate: if the signaling
90
+ * server rejects the room (close 1008 — pinned to a different protocol
91
+ * generation/token), startShare mints a fresh room, persists it, and calls
92
+ * this so the caller can refresh its stored link. Leave unset for explicit
93
+ * webrtc:// URLs, which must NOT be silently rotated. */
94
+ onRotate?: (info: { room: string; link: string }) => void | Promise<void>;
80
95
  }
81
96
 
82
97
  // The room+token persist like the serve token, so the share link (and any
@@ -202,31 +217,66 @@ export async function startShare(
202
217
  ): Promise<{ room: string; link: string; close: () => void }> {
203
218
  const minted = !opts.url;
204
219
  const sighost = opts.sighost ?? DEFAULT_SIGHOST;
205
- const { room, token, host } = opts.url
220
+ const initial = opts.url
206
221
  ? parseShareUrl(opts.url)
207
222
  : {
208
223
  room: "r" + randomBytes(3).toString("hex"),
209
224
  token: `${MARKER}${randomBytes(32).toString("hex")}`,
210
225
  host: sighost,
211
226
  };
227
+ const host = initial.host;
228
+ // Mutable: auto-rotation (below) re-mints room/token/S/authToken/link in place
229
+ // when the signaling server rejects the room as pinned to another generation.
230
+ let room = initial.room;
231
+ let token = initial.token;
212
232
 
213
233
  // E2E: the URL secret S splits into authToken (the only value the server sees,
214
234
  // for room matching) and per-connection AES keys the server never sees. We
215
235
  // refuse to host a legacy plaintext room — old rooms are auto-rotated to v2 by
216
236
  // loadOrCreateShareRoom (delete ~/.agent-yes/.share-room to force a rotation).
217
- const { s: S, v2 } = parseSecret(token);
237
+ const { s: firstS, v2 } = parseSecret(token);
218
238
  if (!v2) {
219
239
  throw new Error(
220
240
  "refusing to host an unencrypted room — delete ~/.agent-yes/.share-room to rotate to an encrypted link",
221
241
  );
222
242
  }
223
- const authToken = await deriveAuthToken(S, room, host);
243
+ let S = firstS;
224
244
 
225
- const RTCPeerConnection = await importRTC();
226
245
  const wsScheme = host.startsWith("localhost") || host.startsWith("127.") ? "ws" : "wss";
227
246
  const ui = host === "s.agent-yes.com" ? "https://agent-yes.com" : "http://localhost:7778";
228
247
  const suffix = host === "s.agent-yes.com" ? "" : "@" + host;
229
- const link = `${ui}/#${room}:${MARKER}${S}${suffix}`;
248
+ const mkLink = () => `${ui}/#${room}:${MARKER}${S}${suffix}`;
249
+ let authToken = await deriveAuthToken(S, room, host);
250
+ let link = mkLink();
251
+
252
+ const RTCPeerConnection = await importRTC();
253
+
254
+ // Auto-rotate a rejected persisted room to a fresh one. A signaling 1008 means
255
+ // the room is pinned to a different generation/token (e.g. a pre-E2E room), so
256
+ // re-using it can never succeed; mint+persist a new room and let the caller
257
+ // refresh its stored link. Gated on opts.onRotate (only the persisted-room
258
+ // caller sets it) and a small cap so a persistent reject can't spin forever.
259
+ let rotateCount = 0;
260
+ const rotate = async (): Promise<boolean> => {
261
+ if (!opts.onRotate || closed || rotateCount >= 5) return false;
262
+ rotateCount++;
263
+ room = "r" + randomBytes(3).toString("hex");
264
+ token = `${MARKER}${randomBytes(32).toString("hex")}`;
265
+ S = parseSecret(token).s;
266
+ authToken = await deriveAuthToken(S, room, host);
267
+ link = mkLink();
268
+ // close() may have run during the await above — don't persist/announce or let
269
+ // the caller reconnect a room for a share that's shutting down.
270
+ if (closed) return false;
271
+ try {
272
+ await mkdir(path.dirname(shareRoomPath()), { recursive: true });
273
+ await writeFile(shareRoomPath(), `webrtc://${room}:${token}@${host}`, { mode: 0o600 });
274
+ } catch {
275
+ /* best effort — in-memory rotation still lets new browsers join */
276
+ }
277
+ await opts.onRotate({ room, link });
278
+ return true;
279
+ };
230
280
 
231
281
  type Peer = {
232
282
  pc: any;
@@ -257,11 +307,16 @@ export async function startShare(
257
307
  let ready = false;
258
308
  let lastRecv = Date.now();
259
309
  let hb: ReturnType<typeof setInterval> | undefined;
310
+ let refresh: ReturnType<typeof setTimeout> | undefined;
260
311
  const stopHb = () => {
261
312
  if (hb) {
262
313
  clearInterval(hb);
263
314
  hb = undefined;
264
315
  }
316
+ if (refresh) {
317
+ clearTimeout(refresh);
318
+ refresh = undefined;
319
+ }
265
320
  };
266
321
  ws.onopen = () => {
267
322
  ws.send(JSON.stringify({ type: "hello", role: "host", v: 2, token: authToken }));
@@ -284,6 +339,15 @@ export async function startShare(
284
339
  ws.send(JSON.stringify({ type: "ping" }));
285
340
  } catch {}
286
341
  }, HOST_HEARTBEAT_MS);
342
+ // Proactive re-registration (see SIG_REFRESH_MS): the edge-answered ping
343
+ // above can't detect a DO that hibernated/evicted our routing while the
344
+ // socket stays up, so periodically recycle the connection to force a fresh
345
+ // hello. onclose then reconnects (~1s), re-registering us as host.
346
+ refresh = setTimeout(() => {
347
+ try {
348
+ ws.close();
349
+ } catch {}
350
+ }, SIG_REFRESH_MS);
287
351
  onReady();
288
352
  };
289
353
  ws.onmessage = async (ev) => {
@@ -321,14 +385,28 @@ export async function startShare(
321
385
  ws.onclose = (ev: any) => {
322
386
  stopHb();
323
387
  if (closed) return; // shutting down — don't resurrect the rendezvous
324
- // The signaling server pins a room to its first host's authToken. A 1008
325
- // means a different generation already owns this room don't hot-loop;
326
- // tell the operator to rotate. (Secret-free message.)
388
+ // The signaling server pins a room to its first host's authToken+protocol.
389
+ // A 1008 means a different generation already owns this room, so reusing it
390
+ // can never succeed. For a persisted/auto-minted room, rotate to a fresh one
391
+ // and reconnect; otherwise (explicit URL) give up with a secret-free hint.
327
392
  if (ev?.code === 1008) {
328
- closed = true;
329
- process.stderr.write(
330
- "[share] room rejected by signaling server — delete ~/.agent-yes/.share-room to rotate the room\n",
331
- );
393
+ rotate()
394
+ .then((rotated) => {
395
+ if (rotated) {
396
+ connectSignaling(() => {});
397
+ } else {
398
+ closed = true;
399
+ process.stderr.write(
400
+ "[share] room rejected by signaling server — delete ~/.agent-yes/.share-room to rotate the room\n",
401
+ );
402
+ }
403
+ })
404
+ .catch(() => {
405
+ closed = true;
406
+ process.stderr.write(
407
+ "[share] room rejected and rotation failed — delete ~/.agent-yes/.share-room to rotate manually\n",
408
+ );
409
+ });
332
410
  return;
333
411
  }
334
412
  // Keep established WebRTC peers; just re-establish the rendezvous so new
@@ -1,8 +0,0 @@
1
- import "./ts-7kSDmCpQ.js";
2
- import "./logger-B9h0djqx.js";
3
- import "./versionChecker-B5vxV_hH.js";
4
- import "./pidStore-B5vBu8Px.js";
5
- import "./globalPidIndex-gZuTvTBs.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DFYbm2uk.js";
7
-
8
- export { SUPPORTED_CLIS };