agent-yes 1.121.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.
Files changed (39) hide show
  1. package/default.config.yaml +27 -4
  2. package/dist/SUPPORTED_CLIS-DcWAr8NI.js +8 -0
  3. package/dist/{SUPPORTED_CLIS-O57LGUEG.js → SUPPORTED_CLIS-f50t1rrA.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-OJeQo0Da.js +144 -0
  12. package/dist/{serve-D2czcYNC.js → serve-O3e2YFfp.js} +137 -36
  13. package/dist/{setup-f1FIFcZm.js → setup-yKMfadhq.js} +5 -42
  14. package/dist/{share-B6QVr5D1.js → share-CksllWW-.js} +122 -16
  15. package/dist/{subcommands-DobVXouH.js → subcommands-BkR-nSAB.js} +2 -2
  16. package/dist/{subcommands-CzpZQHO6.js → subcommands-CT1z9Jl4.js} +15 -6
  17. package/dist/{tray-B8_rx1iu.js → tray-DjCIyakK.js} +22 -10
  18. package/dist/{ts-D91dm1E0.js → ts-DyDU_Dae.js} +76 -7
  19. package/dist/{versionChecker-CAtpgnoQ.js → versionChecker-DmCadDPY.js} +13 -19
  20. package/dist/workspaceConfig-XP2NEWmV.js +56 -0
  21. package/lab/ui/index.html +63 -32
  22. package/package.json +1 -1
  23. package/ts/autoRetry.spec.ts +19 -0
  24. package/ts/autoRetry.ts +16 -0
  25. package/ts/configShared.ts +4 -0
  26. package/ts/index.ts +102 -0
  27. package/ts/oxmgrService.ts +36 -0
  28. package/ts/pty.ts +19 -1
  29. package/ts/reaper.spec.ts +45 -0
  30. package/ts/reaper.ts +77 -0
  31. package/ts/schedule.spec.ts +30 -0
  32. package/ts/schedule.ts +161 -0
  33. package/ts/serve.ts +207 -44
  34. package/ts/share.ts +171 -22
  35. package/ts/subcommands.ts +0 -0
  36. package/ts/tray.spec.ts +9 -1
  37. package/ts/tray.ts +30 -14
  38. package/ts/versionChecker.ts +24 -27
  39. package/dist/SUPPORTED_CLIS-CegJgoEf.js +0 -8
@@ -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-D2czcYNC.js");
35
+ const { cmdServe } = await import("./serve-O3e2YFfp.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-f1FIFcZm.js.map
45
+ //# sourceMappingURL=setup-yKMfadhq.js.map
@@ -178,8 +178,40 @@ function randomHex(n) {
178
178
  //#endregion
179
179
  //#region ts/share.ts
180
180
  const SUB = "ay-signal-1";
181
- const ICE = [{ urls: "stun:stun.l.google.com:19302" }];
182
181
  const DEFAULT_SIGHOST = "s.agent-yes.com";
182
+ const HOST_HEARTBEAT_MS = 2e4;
183
+ const SIG_REFRESH_MS = 4 * 6e4;
184
+ const STUN = [{ urls: "stun:stun.l.google.com:19302" }];
185
+ let iceCache = null;
186
+ async function getIceServers() {
187
+ const keyId = process.env.CF_TURN_KEY_ID;
188
+ const apiToken = process.env.CF_TURN_API_TOKEN;
189
+ if (!keyId || !apiToken) return STUN;
190
+ if (iceCache && iceCache.exp > Date.now()) return iceCache.servers;
191
+ const ttl = 3600;
192
+ try {
193
+ const r = await fetch(`https://rtc.live.cloudflare.com/v1/turn/keys/${keyId}/credentials/generate-ice-servers`, {
194
+ method: "POST",
195
+ headers: {
196
+ Authorization: `Bearer ${apiToken}`,
197
+ "Content-Type": "application/json"
198
+ },
199
+ body: JSON.stringify({ ttl }),
200
+ signal: AbortSignal.timeout(5e3)
201
+ });
202
+ if (!r.ok) throw new Error(`Cloudflare TURN ${r.status}`);
203
+ const j = await r.json();
204
+ const servers = j.iceServers?.length ? j.iceServers : STUN;
205
+ iceCache = {
206
+ servers,
207
+ exp: Date.now() + (ttl - 300) * 1e3
208
+ };
209
+ return servers;
210
+ } catch (e) {
211
+ console.error(`[share] Cloudflare TURN credential fetch failed; using STUN only: ${e}`);
212
+ return STUN;
213
+ }
214
+ }
183
215
  function shareRoomPath() {
184
216
  const home = process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
185
217
  return path.join(home, ".share-room");
@@ -269,17 +301,44 @@ async function importRTC() {
269
301
  async function startShare(opts) {
270
302
  opts.url;
271
303
  const sighost = opts.sighost ?? DEFAULT_SIGHOST;
272
- const { room, token, host } = opts.url ? parseShareUrl(opts.url) : {
304
+ const initial = opts.url ? parseShareUrl(opts.url) : {
273
305
  room: "r" + randomBytes(3).toString("hex"),
274
306
  token: `${MARKER}${randomBytes(32).toString("hex")}`,
275
307
  host: sighost
276
308
  };
277
- 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);
278
313
  if (!v2) throw new Error("refusing to host an unencrypted room — delete ~/.agent-yes/.share-room to rotate to an encrypted link");
279
- const authToken = await deriveAuthToken(S, room, host);
280
- const RTCPeerConnection = await importRTC();
314
+ let S = firstS;
281
315
  const wsScheme = host.startsWith("localhost") || host.startsWith("127.") ? "ws" : "wss";
282
- 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
+ };
283
342
  const peers = /* @__PURE__ */ new Map();
284
343
  let closed = false;
285
344
  let currentWs;
@@ -288,6 +347,19 @@ async function startShare(opts) {
288
347
  const ws = new WebSocket(`${wsScheme}://${host}/${room}`, [SUB]);
289
348
  currentWs = ws;
290
349
  let ready = false;
350
+ let lastRecv = Date.now();
351
+ let hb;
352
+ let refresh;
353
+ const stopHb = () => {
354
+ if (hb) {
355
+ clearInterval(hb);
356
+ hb = void 0;
357
+ }
358
+ if (refresh) {
359
+ clearTimeout(refresh);
360
+ refresh = void 0;
361
+ }
362
+ };
291
363
  ws.onopen = () => {
292
364
  ws.send(JSON.stringify({
293
365
  type: "hello",
@@ -296,12 +368,33 @@ async function startShare(opts) {
296
368
  token: authToken
297
369
  }));
298
370
  ready = true;
371
+ lastRecv = Date.now();
372
+ stopHb();
373
+ hb = setInterval(() => {
374
+ if (Date.now() - lastRecv > HOST_HEARTBEAT_MS * 2 + 5e3) {
375
+ stopHb();
376
+ try {
377
+ ws.close();
378
+ } catch {}
379
+ return;
380
+ }
381
+ try {
382
+ ws.send(JSON.stringify({ type: "ping" }));
383
+ } catch {}
384
+ }, HOST_HEARTBEAT_MS);
385
+ refresh = setTimeout(() => {
386
+ try {
387
+ ws.close();
388
+ } catch {}
389
+ }, SIG_REFRESH_MS);
299
390
  onReady();
300
391
  };
301
392
  ws.onmessage = async (ev) => {
302
393
  if (closed) return;
394
+ lastRecv = Date.now();
303
395
  const m = JSON.parse(ev.data);
304
- if (m.type === "peer-join") startPeer(ws, m.peer);
396
+ if (m.type === "pong") return;
397
+ if (m.type === "peer-join") startPeer(ws, m.peer).catch(() => {});
305
398
  else if (m.type === "answer") {
306
399
  const peer = peers.get(m.from);
307
400
  if (!peer) return;
@@ -322,19 +415,29 @@ async function startShare(opts) {
322
415
  else if (m.type === "peer-leave") closePeer(m.peer);
323
416
  };
324
417
  ws.onclose = (ev) => {
418
+ stopHb();
325
419
  if (closed) return;
326
420
  if (ev?.code === 1008) {
327
- closed = true;
328
- 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
+ });
329
431
  return;
330
432
  }
331
- setTimeout(() => connectSignaling(() => {}), ready ? 1500 : 4e3);
433
+ setTimeout(() => connectSignaling(() => {}), ready ? 1e3 : 2e3);
332
434
  };
333
435
  ws.onerror = () => {};
334
436
  return ws;
335
437
  };
336
- function startPeer(ws, peerId) {
337
- const pc = new RTCPeerConnection({ iceServers: ICE });
438
+ async function startPeer(ws, peerId) {
439
+ const iceServers = await getIceServers();
440
+ const pc = new RTCPeerConnection({ iceServers });
338
441
  let resolveKeys;
339
442
  const keysReady = new Promise((r) => resolveKeys = r);
340
443
  const peer = {
@@ -385,11 +488,14 @@ async function startShare(opts) {
385
488
  dc.onmessage = (e) => {
386
489
  peer.recvChain = peer.recvChain.then(() => onFrame(peerId, dc, peer, e.data)).catch(() => {});
387
490
  };
388
- pc.createOffer().then((o) => pc.setLocalDescription(o)).then(() => ws.send(JSON.stringify({
491
+ const offer = await pc.createOffer();
492
+ await pc.setLocalDescription(offer);
493
+ ws.send(JSON.stringify({
389
494
  type: "offer",
390
495
  to: peerId,
391
- sdp: pc.localDescription.sdp
392
- })));
496
+ sdp: pc.localDescription.sdp,
497
+ iceServers
498
+ }));
393
499
  }
394
500
  function closePeer(peerId) {
395
501
  const p = peers.get(peerId);
@@ -519,4 +625,4 @@ async function startShare(opts) {
519
625
 
520
626
  //#endregion
521
627
  export { loadOrCreateShareRoom, startShare };
522
- //# sourceMappingURL=share-B6QVr5D1.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
- import "./remotes-BufkGk0e.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-CzpZQHO6.js";
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-CT1z9Jl4.js";
5
5
 
6
6
  export { cmdHelp, isSubcommand, runSubcommand };
@@ -1,5 +1,5 @@
1
1
  import { i as readGlobalPids } from "./globalPidIndex-gZuTvTBs.js";
2
- import { a as resolveRemoteSpec, i as readRemotes } from "./remotes-BufkGk0e.js";
2
+ import { a as resolveRemoteSpec, i as readRemotes } from "./remotes-D2fqaRU8.js";
3
3
  import ms from "ms";
4
4
  import yargs from "yargs";
5
5
  import { appendFile, mkdir, open, readFile, stat, writeFile } from "fs/promises";
@@ -200,7 +200,9 @@ const SUBCOMMANDS = new Set([
200
200
  "note",
201
201
  "serve",
202
202
  "setup",
203
+ "schedule",
203
204
  "remote",
205
+ "reap",
204
206
  "help"
205
207
  ]);
206
208
  const IDLE_THRESHOLD_MS = 60 * 1e3;
@@ -231,17 +233,24 @@ async function runSubcommand(argv) {
231
233
  case "restart": return await cmdRestart(rest);
232
234
  case "note": return await cmdNote(rest);
233
235
  case "serve": {
234
- const { cmdServe } = await import("./serve-D2czcYNC.js");
236
+ const { cmdServe } = await import("./serve-O3e2YFfp.js");
235
237
  return cmdServe(rest);
236
238
  }
237
239
  case "setup": {
238
- const { cmdSetup } = await import("./setup-f1FIFcZm.js");
240
+ const { cmdSetup } = await import("./setup-yKMfadhq.js");
239
241
  return cmdSetup(rest);
240
242
  }
243
+ case "schedule": {
244
+ const { cmdSchedule } = await import("./schedule-OJeQo0Da.js");
245
+ return cmdSchedule(rest);
246
+ }
241
247
  case "remote": {
242
- const { cmdRemote } = await import("./remotes-DavR4Hca.js");
248
+ const { cmdRemote } = await import("./remotes-CpGcTr7A.js");
243
249
  return cmdRemote(rest);
244
250
  }
251
+ case "reap":
252
+ await (await import("./reaper-HqcUms2d.js")).sweep();
253
+ return 0;
245
254
  case "help": return cmdHelp();
246
255
  default: return null;
247
256
  }
@@ -252,7 +261,7 @@ async function runSubcommand(argv) {
252
261
  }
253
262
  }
254
263
  function cmdHelp() {
255
- process.stdout.write("ay - agent-yes CLI\n\nManagement:\n ay ls [keyword] list running agents\n ay tail [-f] <keyword> stream output (Ctrl-C to stop)\n ay cat <keyword> full log\n ay head <keyword> first N lines\n ay send <keyword> <msg> send a message\n ay attach <keyword> interactive attach (detach: Ctrl-\\)\n ay stop <keyword> graceful shutdown (/exit for claude/codex)\n ay status <keyword> agent status snapshot\n\nRemote:\n ay setup guided setup: pick a workspace, share to agent-yes.com\n ay serve [--port N] start HTTP API server (prints token)\n ay remote add <alias> http://<token>@<host>:<port>\n ay remote ls / rm <alias> manage saved remotes\n ay ls <token>@<host>:<port> connect inline (no alias needed)\n ay send <token>@<host>:<port>:<kw> <msg>\n\nRun an agent:\n ay [claude|codex|gemini|...] [options] -- [prompt]\n ay claude -- \"fix the bug in auth.ts\"\n ay claude --help full agent-runner options\n\nLabs (examples at https://github.com/snomiao/agent-yes/tree/main/lab):\n local-role-play/ designer + builder on one machine\n http-remote/ ay serve remote access demo\n p2p-pairing/ libp2p P2P (needs: cargo build --features swarm)\n");
264
+ process.stdout.write("ay - agent-yes CLI\n\nManagement:\n ay ls [keyword] list running agents\n ay tail [-f] <keyword> stream output (Ctrl-C to stop)\n ay cat <keyword> full log\n ay head <keyword> first N lines\n ay send <keyword> <msg> send a message\n ay attach <keyword> interactive attach (detach: Ctrl-\\)\n ay stop <keyword> graceful shutdown (/exit for claude/codex)\n ay status <keyword> agent status snapshot\n ay reap kill process groups leaked by dead agents\n\nRemote:\n ay setup guided setup: pick a workspace, share to agent-yes.com\n ay schedule <when> <cli> -- <msg> run an agent on a schedule (HH:MM or cron)\n ay serve [--port N] start HTTP API server (prints token)\n ay serve status show serve daemon/server status\n ay remote add <alias> http://<token>@<host>:<port>\n ay remote ls / rm <alias> manage saved remotes\n ay ls <token>@<host>:<port> connect inline (no alias needed)\n ay send <token>@<host>:<port>:<kw> <msg>\n\nRun an agent:\n ay [claude|codex|gemini|...] [options] -- [prompt]\n ay claude -- \"fix the bug in auth.ts\"\n ay claude --help full agent-runner options\n\nLabs (examples at https://github.com/snomiao/agent-yes/tree/main/lab):\n local-role-play/ designer + builder on one machine\n http-remote/ ay serve remote access demo\n p2p-pairing/ libp2p P2P (needs: cargo build --features swarm)\n");
256
265
  return 0;
257
266
  }
258
267
  function matchKeyword(record, keyword) {
@@ -1680,4 +1689,4 @@ async function cmdStatus(rest) {
1680
1689
 
1681
1690
  //#endregion
1682
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 };
1683
- //# sourceMappingURL=subcommands-CzpZQHO6.js.map
1692
+ //# sourceMappingURL=subcommands-CT1z9Jl4.js.map
@@ -61,13 +61,6 @@ function isTrayProcessRunning(pid) {
61
61
  }
62
62
  }
63
63
  /**
64
- * Write the current process PID to the tray PID file
65
- */
66
- async function writeTrayPid() {
67
- await mkdir(getTrayDir(), { recursive: true });
68
- await writeFile(getTrayPidFile(), String(process.pid), "utf8");
69
- }
70
- /**
71
64
  * Remove the tray PID file
72
65
  */
73
66
  async function removeTrayPid() {
@@ -76,6 +69,25 @@ async function removeTrayPid() {
76
69
  } catch {}
77
70
  }
78
71
  /**
72
+ * Atomically claim the tray singleton. Exclusive create (flag "wx") fails if a
73
+ * pidfile already exists, so two trays racing startup can't BOTH pass a
74
+ * check-then-act and survive (that race is how N agents ended up with N trays).
75
+ * If an existing pidfile belongs to a DEAD tray it's stale — take it over.
76
+ * Returns true if this process is now the singleton owner.
77
+ */
78
+ async function claimTraySingleton() {
79
+ const pidFile = getTrayPidFile();
80
+ await mkdir(getTrayDir(), { recursive: true });
81
+ for (let attempt = 0; attempt < 2; attempt++) try {
82
+ await writeFile(pidFile, String(process.pid), { flag: "wx" });
83
+ return true;
84
+ } catch {
85
+ if (await isTrayRunning()) return false;
86
+ await removeTrayPid();
87
+ }
88
+ return false;
89
+ }
90
+ /**
79
91
  * Check if a tray process is already running
80
92
  */
81
93
  async function isTrayRunning() {
@@ -96,6 +108,7 @@ async function isTrayRunning() {
96
108
  */
97
109
  async function ensureTray() {
98
110
  if (!isDesktopOS()) return;
111
+ if (process.env.AGENT_YES_TRAY !== "1") return;
99
112
  if (await isTrayRunning()) return;
100
113
  try {
101
114
  const cliPath = new URL("./cli.ts", import.meta.url).pathname;
@@ -116,11 +129,10 @@ async function startTray() {
116
129
  console.error("Tray icon is only supported on macOS and Windows.");
117
130
  return;
118
131
  }
119
- if (await isTrayRunning()) {
132
+ if (!await claimTraySingleton()) {
120
133
  console.error("Tray is already running.");
121
134
  return;
122
135
  }
123
- await writeTrayPid();
124
136
  let SysTray;
125
137
  try {
126
138
  SysTray = (await import("systray2")).default;
@@ -175,4 +187,4 @@ async function startTray() {
175
187
 
176
188
  //#endregion
177
189
  export { ensureTray, startTray };
178
- //# sourceMappingURL=tray-B8_rx1iu.js.map
190
+ //# sourceMappingURL=tray-DjCIyakK.js.map
@@ -1,9 +1,10 @@
1
1
  import { n as logger, t as addTransport } from "./logger-B9h0djqx.js";
2
- import { r as getInstalledPackage } from "./versionChecker-CAtpgnoQ.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";
6
6
  import { i as readGlobalPids } from "./globalPidIndex-gZuTvTBs.js";
7
+ import { n as sweep, t as register } from "./reaper-Dj8R7ltI.js";
7
8
  import { arch, platform } from "process";
8
9
  import { execSync } from "child_process";
9
10
  import { closeSync, constants, createReadStream, existsSync, mkdirSync, openSync } from "fs";
@@ -275,8 +276,16 @@ async function getPty() {
275
276
  throw error;
276
277
  });
277
278
  }
278
- const pty = await getPty();
279
+ let pty;
280
+ try {
281
+ pty = await getPty();
282
+ } catch (error) {
283
+ pty = new Proxy({}, { get() {
284
+ throw error;
285
+ } });
286
+ }
279
287
  const ptyPackage = globalThis.Bun ? "bun-pty" : "node-pty";
288
+ var pty_default = pty;
280
289
 
281
290
  //#endregion
282
291
  //#region ts/removeControlCharacters.ts
@@ -529,6 +538,17 @@ async function sendMessage(context, message, { waitForReady = true } = {}) {
529
538
  logger.debug(`sent enter`);
530
539
  }
531
540
 
541
+ //#endregion
542
+ //#region ts/autoRetry.ts
543
+ const AUTO_RETRY_BASE_SECS = 8;
544
+ const AUTO_RETRY_MAX_DELAY_SECS = 256;
545
+ const AUTO_RETRY_GIVE_UP_MS = 8 * 3600 * 1e3;
546
+ /** Backoff (ms) before the Nth consecutive auto-retry — doubles, then caps. */
547
+ function autoRetryBackoffMs(streak) {
548
+ const shift = Math.min(streak, 20);
549
+ return Math.min(AUTO_RETRY_BASE_SECS * 2 ** shift, AUTO_RETRY_MAX_DELAY_SECS) * 1e3;
550
+ }
551
+
532
552
  //#endregion
533
553
  //#region ts/core/logging.ts
534
554
  /**
@@ -782,7 +802,7 @@ function spawnAgent(options) {
782
802
  const spawn = () => {
783
803
  let [bin, ...args] = [...parseCommandString(cliConf?.binary || cli), ...cliArgs];
784
804
  logger.debug(`Spawning ${bin} with args: ${JSON.stringify(args)}`);
785
- const spawned = pty.spawn(bin, args, ptyOptions);
805
+ const spawned = pty_default.spawn(bin, args, ptyOptions);
786
806
  logger.info(`[${cli}-yes] Spawned ${bin} with PID ${spawned.pid} (agent-yes v${getInstalledPackage().version})`);
787
807
  return spawned;
788
808
  };
@@ -1040,7 +1060,7 @@ async function notifyWebhook(status, details, cwd = process.cwd()) {
1040
1060
 
1041
1061
  //#endregion
1042
1062
  //#region ts/index.ts
1043
- const config = await import("./agent-yes.config-kmtJKJHk.js").then((mod) => mod.default || mod);
1063
+ const config = await import("./agent-yes.config-z-IPzH5U.js").then((mod) => mod.default || mod);
1044
1064
  const CLIS_CONFIG = config.clis;
1045
1065
  /**
1046
1066
  * Main function to run agent-cli with automatic yes/no responses
@@ -1190,6 +1210,7 @@ async function agentYes({ cli, cliArgs = [], prompt, robust = true, cwd, env, ex
1190
1210
  ];
1191
1211
  prompt = void 0;
1192
1212
  } else logger.warn(`Unknown promptArg format: ${cliConf.promptArg}`);
1213
+ sweep().catch(() => {});
1193
1214
  const ptyEnv = { ...env ?? process.env };
1194
1215
  ptyEnv.AGENT_YES_PID = String(process.pid);
1195
1216
  const ptyOptions = {
@@ -1225,6 +1246,7 @@ async function agentYes({ cli, cliArgs = [], prompt, robust = true, cwd, env, ex
1225
1246
  } catch (error) {
1226
1247
  logger.warn(`[pidStore] Failed to register process ${shell.pid}:`, error);
1227
1248
  }
1249
+ register(process.pid, shell.pid).catch(() => {});
1228
1250
  notifyWebhook("RUNNING", prompt ?? "", workingDir).catch(() => null);
1229
1251
  const logPaths = await initializeLogPaths(pidStore, shell.pid);
1230
1252
  await setupDebugLogging(logPaths.debuggingLogsPath);
@@ -1264,6 +1286,9 @@ async function agentYes({ cli, cliArgs = [], prompt, robust = true, cwd, env, ex
1264
1286
  const pendingExitCode = Promise.withResolvers();
1265
1287
  shell.onExit(async function onExit({ exitCode }) {
1266
1288
  const exitedPid = shell.pid;
1289
+ if (process.platform !== "win32") try {
1290
+ process.kill(-exitedPid, "SIGKILL");
1291
+ } catch {}
1267
1292
  globalAgentRegistry.unregister(exitedPid);
1268
1293
  ctx.stdinReady.unready();
1269
1294
  const agentCrashed = exitCode !== 0 && !(exitCode === 130 || exitCode === 143 || userSentCtrlC);
@@ -1294,7 +1319,7 @@ async function agentYes({ cli, cliArgs = [], prompt, robust = true, cwd, env, ex
1294
1319
  cwd: cwd ?? process.cwd(),
1295
1320
  env: ptyEnv
1296
1321
  };
1297
- shell = pty.spawn(bin, args, restartPtyOptions);
1322
+ shell = pty_default.spawn(bin, args, restartPtyOptions);
1298
1323
  shellWrite = (data) => shell.write(data);
1299
1324
  try {
1300
1325
  await pidStore.registerProcess({
@@ -1307,6 +1332,7 @@ async function agentYes({ cli, cliArgs = [], prompt, robust = true, cwd, env, ex
1307
1332
  } catch (error) {
1308
1333
  logger.warn(`[pidStore] Failed to register restarted process ${shell.pid}:`, error);
1309
1334
  }
1335
+ register(process.pid, shell.pid).catch(() => {});
1310
1336
  ctx.shell = shell;
1311
1337
  try {
1312
1338
  globalAgentRegistry.register(shell.pid, {
@@ -1377,7 +1403,7 @@ async function agentYes({ cli, cliArgs = [], prompt, robust = true, cwd, env, ex
1377
1403
  cwd: cwd ?? process.cwd(),
1378
1404
  env: ptyEnv
1379
1405
  };
1380
- shell = pty.spawn(cli, restoreArgs, restorePtyOptions);
1406
+ shell = pty_default.spawn(cli, restoreArgs, restorePtyOptions);
1381
1407
  shellWrite = (data) => shell.write(data);
1382
1408
  try {
1383
1409
  await pidStore.registerProcess({
@@ -1390,6 +1416,7 @@ async function agentYes({ cli, cliArgs = [], prompt, robust = true, cwd, env, ex
1390
1416
  } catch (error) {
1391
1417
  logger.warn(`[pidStore] Failed to register restored process ${shell.pid}:`, error);
1392
1418
  }
1419
+ register(process.pid, shell.pid).catch(() => {});
1393
1420
  ctx.shell = shell;
1394
1421
  try {
1395
1422
  globalAgentRegistry.register(shell.pid, {
@@ -1443,9 +1470,33 @@ async function agentYes({ cli, cliArgs = [], prompt, robust = true, cwd, env, ex
1443
1470
  return conf.working?.some((rgx) => rgx.test(rendered));
1444
1471
  };
1445
1472
  let lastHeartbeatRendered = "";
1473
+ let retryStreak = 0;
1474
+ let retryStartedAt = null;
1475
+ let retryNextAt = null;
1476
+ let autoRetryScreen = "";
1446
1477
  const heartbeatInterval = setInterval(async () => {
1447
1478
  try {
1448
1479
  const rendered = removeControlCharacters(xtermProxy.tail(12));
1480
+ if (retryNextAt !== null) {
1481
+ const now = Date.now();
1482
+ if (retryStartedAt !== null && now - retryStartedAt >= AUTO_RETRY_GIVE_UP_MS) {
1483
+ logger.warn(`[${cli}-yes] auto-retry: giving up after 8h with no recovery`);
1484
+ retryNextAt = null;
1485
+ retryStartedAt = null;
1486
+ retryStreak = 0;
1487
+ } else if (now >= retryNextAt) {
1488
+ const working = conf.working?.some((rx) => rx.test(autoRetryScreen)) ?? false;
1489
+ const readyNow = conf.ready?.some((rx) => rx.test(autoRetryScreen)) ?? false;
1490
+ if (working || !readyNow) retryNextAt = now + 500;
1491
+ else {
1492
+ retryStreak += 1;
1493
+ logger.warn(`[${cli}-yes] auto-retry: typing 'retry' (attempt ${retryStreak})`);
1494
+ ctx.messageContext.shell.write("retry\r");
1495
+ ctx.idleWaiter.ping();
1496
+ retryNextAt = now + autoRetryBackoffMs(retryStreak);
1497
+ }
1498
+ }
1499
+ }
1449
1500
  if (rendered === lastHeartbeatRendered) return;
1450
1501
  lastHeartbeatRendered = rendered;
1451
1502
  const lines = rendered.split("\n").filter((line) => line.trim());
@@ -1642,6 +1693,24 @@ async function agentYes({ cli, cliArgs = [], prompt, robust = true, cwd, env, ex
1642
1693
  if (rendered === lastRendered) return;
1643
1694
  lastRendered = rendered;
1644
1695
  logger.debug(`stdout|${line}`);
1696
+ if (conf.autoRetry?.length) {
1697
+ autoRetryScreen = rendered;
1698
+ const errVisible = conf.autoRetry.some((rx) => rx.test(rendered));
1699
+ const readyVisible = conf.ready?.some((rx) => rx.test(rendered)) ?? false;
1700
+ if (errVisible && readyVisible) {
1701
+ if (retryNextAt === null) {
1702
+ if (retryStartedAt === null) retryStartedAt = Date.now();
1703
+ const delayMs = autoRetryBackoffMs(retryStreak);
1704
+ retryNextAt = Date.now() + delayMs;
1705
+ logger.warn(`[${cli}-yes] auto-retry armed: recoverable error detected, retrying in ${delayMs / 1e3}s (attempt ${retryStreak + 1})`);
1706
+ }
1707
+ } else if (readyVisible && !errVisible && retryStartedAt !== null) {
1708
+ logger.debug(`[${cli}-yes] auto-retry: recovered, resetting backoff`);
1709
+ retryStreak = 0;
1710
+ retryStartedAt = null;
1711
+ retryNextAt = null;
1712
+ }
1713
+ }
1645
1714
  if (conf.ready?.some((rx) => line.match(rx))) {
1646
1715
  logger.debug(`ready |${line}`);
1647
1716
  if (cli === "gemini" && lineIndex <= 80) return;
@@ -1715,4 +1784,4 @@ function sleep(ms) {
1715
1784
 
1716
1785
  //#endregion
1717
1786
  export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
1718
- //# sourceMappingURL=ts-D91dm1E0.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.121.0";
10
+ var version = "1.122.1";
11
11
 
12
12
  //#endregion
13
13
  //#region ts/versionChecker.ts
@@ -170,29 +170,23 @@ function compareVersions(v1, v2) {
170
170
  }
171
171
  /**
172
172
  * Detect how agent-yes was installed.
173
- * Returns a short label: "git", "bun link", "bun", "npm", "npx", or "unknown"
173
+ * Returns a short label: "git", "bun", "npm", "npx", "source", or "unknown".
174
+ * A bun-link of a git checkout reports "git" (it runs the working tree).
174
175
  */
175
176
  function detectInstallMethod() {
176
177
  try {
177
- const scriptDir = path.dirname(new URL(import.meta.url).pathname);
178
- if (!scriptDir.includes("node_modules")) {
179
- const repoRoot = path.resolve(scriptDir, "..");
180
- if (existsSync(path.join(repoRoot, ".git"))) return "git";
181
- return "source";
182
- }
183
- const nodeModulesEntry = scriptDir.replace(/\/dist$/, "");
178
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
179
+ const norm = scriptDir.replace(/\\/g, "/");
180
+ const hasGit = (dir) => existsSync(path.join(dir, ".git"));
181
+ if (!norm.includes("node_modules")) return hasGit(path.resolve(scriptDir, "..")) ? "git" : "source";
182
+ const nodeModulesEntry = scriptDir.replace(/[\\/]dist$/, "");
184
183
  try {
185
- if (lstatSync(nodeModulesEntry).isSymbolicLink()) {
186
- const target = readlinkSync(nodeModulesEntry);
187
- const resolvedTarget = path.resolve(path.dirname(nodeModulesEntry), target);
188
- if (existsSync(path.join(resolvedTarget, ".git"))) return "bun link (git)";
189
- return "bun link";
190
- }
184
+ if (lstatSync(nodeModulesEntry).isSymbolicLink()) return hasGit(path.resolve(path.dirname(nodeModulesEntry), readlinkSync(nodeModulesEntry))) ? "git" : "bun";
191
185
  } catch {}
192
- if (scriptDir.includes(".bun/")) return "bun";
193
- if (scriptDir.includes(".npm/")) return "npx";
194
- if (process.env.npm_execpath?.includes("bun")) return "bun";
186
+ if (norm.includes("/.bun/")) return "bun";
187
+ if (norm.includes("/.npm/")) return "npx";
195
188
  if (process.env.npm_config_user_agent?.startsWith("bun")) return "bun";
189
+ if (process.env.npm_execpath?.includes("bun")) return "bun";
196
190
  if (process.env.npm_config_user_agent?.startsWith("npm")) return "npm";
197
191
  return "npm";
198
192
  } catch {
@@ -221,4 +215,4 @@ async function displayVersion() {
221
215
 
222
216
  //#endregion
223
217
  export { versionString as i, displayVersion as n, getInstalledPackage as r, checkAndAutoUpdate as t };
224
- //# sourceMappingURL=versionChecker-CAtpgnoQ.js.map
218
+ //# sourceMappingURL=versionChecker-DmCadDPY.js.map