claude-yes 1.165.0 → 1.167.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/{SUPPORTED_CLIS-H7IIw10E.js → SUPPORTED_CLIS-DFBATC85.js} +2 -2
  2. package/dist/SUPPORTED_CLIS-DLFqRE8W.js +8 -0
  3. package/dist/cli.js +15 -6
  4. package/dist/index.js +2 -2
  5. package/dist/{remotes-DkNuq417.js → remotes-ClT1bq16.js} +1 -1
  6. package/dist/{remotes-DBCvpp3B.js → remotes-T6nf0t3K.js} +2 -2
  7. package/dist/{schedule-BobQwIBS.js → schedule-C2RMA3qm.js} +5 -5
  8. package/dist/{serve-DzOWMI_8.js → serve-mLqsRwfR.js} +12 -9
  9. package/dist/{setup-tVfp7djj.js → setup-BtqKZx3q.js} +3 -3
  10. package/dist/{share-Bq_tDXQU.js → share-BjqQBWM-.js} +1 -1
  11. package/dist/spawnGate-B_VDMXYL.js +107 -0
  12. package/dist/spawnGate-UH73I2le.js +5 -0
  13. package/dist/{subcommands-CIFWi9vq.js → subcommands-B-WoBVhk.js} +2 -2
  14. package/dist/{subcommands-DtwxPMYe.js → subcommands-DWeo7ZLc.js} +29 -9
  15. package/dist/{tray-BMzpUSfa.js → tray-CZarCA2Q.js} +1 -1
  16. package/dist/{ts-jDEwTsVt.js → ts-s3wYccKf.js} +2 -2
  17. package/dist/{versionChecker-C0UJyFcN.js → versionChecker-BvR6tV3u.js} +2 -2
  18. package/dist/{webrtcRemote-Ccdzmuc-.js → webrtcRemote-GAgF5K45.js} +1 -1
  19. package/dist/{workspaceConfig-B0Q9-q2B.js → workspaceConfig-D3OH7and.js} +41 -2
  20. package/package.json +1 -1
  21. package/ts/cli.ts +22 -1
  22. package/ts/serve.ts +8 -0
  23. package/ts/spawnGate.spec.ts +155 -0
  24. package/ts/spawnGate.ts +123 -0
  25. package/ts/subcommands.spec.ts +127 -18
  26. package/ts/subcommands.ts +65 -2
  27. package/ts/workspaceConfig.spec.ts +85 -0
  28. package/ts/workspaceConfig.ts +64 -0
  29. package/dist/SUPPORTED_CLIS-dBgLp3mi.js +0 -8
@@ -1,8 +1,8 @@
1
- import { t as CLIS_CONFIG } from "./ts-jDEwTsVt.js";
1
+ import { t as CLIS_CONFIG } from "./ts-s3wYccKf.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-H7IIw10E.js.map
8
+ //# sourceMappingURL=SUPPORTED_CLIS-DFBATC85.js.map
@@ -0,0 +1,8 @@
1
+ import "./ts-s3wYccKf.js";
2
+ import "./logger-CDIsZ-Pp.js";
3
+ import "./versionChecker-BvR6tV3u.js";
4
+ import "./pidStore-BfoBWUjc.js";
5
+ import "./globalPidIndex-DlmmJlO8.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DFBATC85.js";
7
+
8
+ export { SUPPORTED_CLIS };
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
  import { t as invokedCliName } from "./invokedCli-uqM2YYA7.js";
3
3
  import { n as logger } from "./logger-CDIsZ-Pp.js";
4
- import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-C0UJyFcN.js";
4
+ import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-BvR6tV3u.js";
5
5
  import { argv } from "process";
6
6
  import { execFileSync, spawn } from "child_process";
7
7
  import ms from "ms";
@@ -481,9 +481,9 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
481
481
  const rawArg = process.argv[2];
482
482
  const managerCommands = !invokedCliName(process.argv);
483
483
  const isHelpFlag = rawArg === "-h" || rawArg === "--help";
484
- const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-CIFWi9vq.js");
484
+ const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-B-WoBVhk.js");
485
485
  if (isHelpFlag && process.argv.length === 3) {
486
- cmdHelp(managerCommands);
486
+ await cmdHelp(managerCommands);
487
487
  process.exit(0);
488
488
  }
489
489
  if (isSubcommand(rawArg, managerCommands)) {
@@ -495,14 +495,23 @@ await checkAndAutoUpdate();
495
495
  logger.info(versionString());
496
496
  const config = parseCliArgs(process.argv);
497
497
  if (config.tray) {
498
- const { startTray } = await import("./tray-BMzpUSfa.js");
498
+ const { startTray } = await import("./tray-CZarCA2Q.js");
499
499
  await startTray();
500
500
  await new Promise(() => {});
501
501
  }
502
502
  {
503
- const { ensureTray } = await import("./tray-BMzpUSfa.js");
503
+ const { ensureTray } = await import("./tray-CZarCA2Q.js");
504
504
  ensureTray();
505
505
  }
506
+ if (!config.showVersion && !config.appendPrompt && !config.tray) {
507
+ const { waitForSpawnCapacity } = await import("./spawnGate-UH73I2le.js");
508
+ await waitForSpawnCapacity({
509
+ onWait: (reason, waitedMs) => {
510
+ if (config.verbose || waitedMs === 0) console.error(`[agent-yes] spawn gate: ${reason} — waiting…`);
511
+ },
512
+ onProceedAnyway: (reason, waitedMs) => console.error(`[agent-yes] spawn gate: ${reason} — waited ${Math.round(waitedMs / 1e3)}s, proceeding anyway`)
513
+ });
514
+ }
506
515
  if (config.useRust) {
507
516
  let rustBinary;
508
517
  try {
@@ -514,7 +523,7 @@ if (config.useRust) {
514
523
  }
515
524
  }
516
525
  if (rustBinary) {
517
- const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-dBgLp3mi.js");
526
+ const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-DLFqRE8W.js");
518
527
  const rustArgs = buildRustArgs(process.argv, config.cli, SUPPORTED_CLIS);
519
528
  if (config.verbose) {
520
529
  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-jDEwTsVt.js";
1
+ import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-s3wYccKf.js";
2
2
  import "./logger-CDIsZ-Pp.js";
3
- import "./versionChecker-C0UJyFcN.js";
3
+ import "./versionChecker-BvR6tV3u.js";
4
4
  import "./pidStore-BfoBWUjc.js";
5
5
  import "./globalPidIndex-DlmmJlO8.js";
6
6
 
@@ -1,5 +1,5 @@
1
1
  import "./e2e-Bfw7qL9O.js";
2
2
  import "./webrtcLink-CBZkZ-LT.js";
3
- import { a as resolveRemoteSpec, i as readRemotes, n as deleteRemoteAlias, o as writeRemoteAlias, r as parseDirectRemoteSpec, t as cmdRemote } from "./remotes-DBCvpp3B.js";
3
+ import { a as resolveRemoteSpec, i as readRemotes, n as deleteRemoteAlias, o as writeRemoteAlias, r as parseDirectRemoteSpec, t as cmdRemote } from "./remotes-T6nf0t3K.js";
4
4
 
5
5
  export { cmdRemote };
@@ -86,7 +86,7 @@ async function resolveRemoteSpec(spec) {
86
86
  * The bridge lives for the rest of the process (torn down on `process.exit`).
87
87
  */
88
88
  async function resolveWebrtc(link, keyword) {
89
- const { startWebrtcBridge } = await import("./webrtcRemote-Ccdzmuc-.js");
89
+ const { startWebrtcBridge } = await import("./webrtcRemote-GAgF5K45.js");
90
90
  const bridge = await startWebrtcBridge(link);
91
91
  return {
92
92
  url: bridge.baseUrl,
@@ -173,4 +173,4 @@ async function cmdRemote(rest) {
173
173
 
174
174
  //#endregion
175
175
  export { resolveRemoteSpec as a, readRemotes as i, deleteRemoteAlias as n, writeRemoteAlias as o, parseDirectRemoteSpec as r, cmdRemote as t };
176
- //# sourceMappingURL=remotes-DBCvpp3B.js.map
176
+ //# sourceMappingURL=remotes-T6nf0t3K.js.map
@@ -1,10 +1,10 @@
1
- import "./ts-jDEwTsVt.js";
1
+ import "./ts-s3wYccKf.js";
2
2
  import "./logger-CDIsZ-Pp.js";
3
- import "./versionChecker-C0UJyFcN.js";
3
+ import "./versionChecker-BvR6tV3u.js";
4
4
  import "./pidStore-BfoBWUjc.js";
5
5
  import "./globalPidIndex-DlmmJlO8.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-H7IIw10E.js";
7
- import { o as resolveSpawnCwd } from "./workspaceConfig-B0Q9-q2B.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DFBATC85.js";
7
+ import { l as resolveSpawnCwd } from "./workspaceConfig-D3OH7and.js";
8
8
  import { createHash } from "node:crypto";
9
9
 
10
10
  //#region ts/oxmgrService.ts
@@ -141,4 +141,4 @@ async function cmdSchedule(rest) {
141
141
 
142
142
  //#endregion
143
143
  export { cmdSchedule };
144
- //# sourceMappingURL=schedule-BobQwIBS.js.map
144
+ //# sourceMappingURL=schedule-C2RMA3qm.js.map
@@ -1,16 +1,17 @@
1
- import "./ts-jDEwTsVt.js";
1
+ import "./ts-s3wYccKf.js";
2
2
  import "./logger-CDIsZ-Pp.js";
3
- import { r as getInstalledPackage } from "./versionChecker-C0UJyFcN.js";
3
+ import { r as getInstalledPackage } from "./versionChecker-BvR6tV3u.js";
4
4
  import "./pidStore-BfoBWUjc.js";
5
5
  import { a as updateGlobalPidStatus } from "./globalPidIndex-DlmmJlO8.js";
6
6
  import { t as pgidForWrapper } from "./reaper-C-eWAxIj.js";
7
7
  import "./configShared-CEyhl0WH.js";
8
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-H7IIw10E.js";
8
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DFBATC85.js";
9
9
  import "./e2e-Bfw7qL9O.js";
10
10
  import "./webrtcLink-CBZkZ-LT.js";
11
- import "./remotes-DBCvpp3B.js";
12
- import { a as isProvisionAllowed, i as hasSpawnHook, n as getSpawnHook, o as resolveSpawnCwd, t as getProvisionRoot } from "./workspaceConfig-B0Q9-q2B.js";
13
- import { A as writeToIpc, D as snapshotStatus, S as resolveOne, b as renderRawLog, c as extractNeedsInput, h as listRecords, i as controlCodeFromName, l as extractTaskCounts, o as deriveLiveStatus, v as readNotes, y as readPtysize } from "./subcommands-DtwxPMYe.js";
11
+ import "./remotes-T6nf0t3K.js";
12
+ import { c as isProvisionAllowed, i as getSpawnHook, l as resolveSpawnCwd, r as getProvisionRoot, s as hasSpawnHook } from "./workspaceConfig-D3OH7and.js";
13
+ import { A as writeToIpc, D as snapshotStatus, S as resolveOne, b as renderRawLog, c as extractNeedsInput, h as listRecords, i as controlCodeFromName, l as extractTaskCounts, o as deriveLiveStatus, v as readNotes, y as readPtysize } from "./subcommands-DWeo7ZLc.js";
14
+ import { r as spawnRejectionReason } from "./spawnGate-B_VDMXYL.js";
14
15
  import yargs from "yargs";
15
16
  import { mkdir, open, readFile, stat, unlink, writeFile } from "fs/promises";
16
17
  import { homedir, hostname, userInfo } from "os";
@@ -307,7 +308,7 @@ async function cmdServeDaemon(sub, args) {
307
308
  let shareLink = null;
308
309
  let shareLinkMinted = false;
309
310
  if (webrtcDaemon) try {
310
- const { loadOrCreateShareRoom, shareLinkFromRoomUrl } = await import("./share-Bq_tDXQU.js");
311
+ const { loadOrCreateShareRoom, shareLinkFromRoomUrl } = await import("./share-BjqQBWM-.js");
311
312
  const explicit = explicitWebrtcUrl(effArgs);
312
313
  shareLink = shareLinkFromRoomUrl(explicit ?? await loadOrCreateShareRoom());
313
314
  shareLinkMinted = !explicit;
@@ -1169,6 +1170,8 @@ Options:
1169
1170
  const cli = String(body.cli ?? "claude");
1170
1171
  if (!SUPPORTED_CLIS.includes(cli)) return new Response(`unsupported cli: ${cli}`, { status: 400 });
1171
1172
  const prompt = String(body.prompt ?? "");
1173
+ const reject = await spawnRejectionReason();
1174
+ if (reject) return new Response(reject, { status: 429 });
1172
1175
  let cwd;
1173
1176
  let provisioned = null;
1174
1177
  const fork = body.fork && typeof body.fork.fromCwd === "string" && body.fork.fromCwd.trim() && typeof body.fork.branch === "string" && body.fork.branch.trim() ? {
@@ -1383,7 +1386,7 @@ Options:
1383
1386
  const webrtcVal = argv.webrtc ?? argv.share;
1384
1387
  const explicitUrl = typeof webrtcVal === "string" && webrtcVal.startsWith("webrtc://") ? webrtcVal : void 0;
1385
1388
  try {
1386
- const { startShare, loadOrCreateShareRoom } = await import("./share-Bq_tDXQU.js");
1389
+ const { startShare, loadOrCreateShareRoom } = await import("./share-BjqQBWM-.js");
1387
1390
  const linkFile = path.join(process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes"), ".share-link");
1388
1391
  const announce = async (room, link, rotated) => {
1389
1392
  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)";
@@ -1442,4 +1445,4 @@ Options:
1442
1445
 
1443
1446
  //#endregion
1444
1447
  export { cmdServe };
1445
- //# sourceMappingURL=serve-DzOWMI_8.js.map
1448
+ //# sourceMappingURL=serve-mLqsRwfR.js.map
@@ -1,4 +1,4 @@
1
- import { r as getWorkspaceRoot, s as setWorkspaceRoot } from "./workspaceConfig-B0Q9-q2B.js";
1
+ import { o as getWorkspaceRoot, u as setWorkspaceRoot } from "./workspaceConfig-D3OH7and.js";
2
2
  import { existsSync } from "node:fs";
3
3
  import { stdin, stdout } from "node:process";
4
4
  import { createInterface } from "node:readline/promises";
@@ -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-DzOWMI_8.js");
35
+ const { cmdServe } = await import("./serve-mLqsRwfR.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-tVfp7djj.js.map
45
+ //# sourceMappingURL=setup-BtqKZx3q.js.map
@@ -534,4 +534,4 @@ async function startShare(opts) {
534
534
 
535
535
  //#endregion
536
536
  export { loadOrCreateShareRoom, shareLinkFromRoomUrl, startShare };
537
- //# sourceMappingURL=share-Bq_tDXQU.js.map
537
+ //# sourceMappingURL=share-BjqQBWM-.js.map
@@ -0,0 +1,107 @@
1
+ import { i as readGlobalPids } from "./globalPidIndex-DlmmJlO8.js";
2
+ import { a as getSpawnWaitMs, n as getMinFreeMb, t as getMaxAgents } from "./workspaceConfig-D3OH7and.js";
3
+ import { readFile } from "fs/promises";
4
+
5
+ //#region ts/spawnGate.ts
6
+ /**
7
+ * Spawn admission control — keep an unbounded fan-out of agents from exhausting
8
+ * host RAM and tripping the kernel OOM-killer (which, on a box with no cgroup
9
+ * memory limit, hangs the whole machine). Two opt-in limits:
10
+ * - {@link getMaxAgents} — max concurrently-live agents
11
+ * - {@link getMinFreeMb} — minimum free memory before admitting a spawn
12
+ *
13
+ * Two entry points share one instantaneous check ({@link spawnRejectionReason}):
14
+ * - the `ay serve` daemon HARD-REJECTS (`/api/spawn` → 429) so the caller retries;
15
+ * - the CLI path BLOCKS-AND-WAITS ({@link waitForSpawnCapacity}) with φ-backoff,
16
+ * failing open after a timeout so recursive `ay <cli>` spawns get spaced out
17
+ * (the actual cause of the burst storms) without ever deadlocking a workflow.
18
+ */
19
+ /**
20
+ * System available memory in MB. Prefers Linux `/proc/meminfo` `MemAvailable`
21
+ * (the kernel's own estimate of what's reclaimable without swapping — far more
22
+ * accurate than free RAM), falling back to `os.freemem()` on other platforms or
23
+ * if the file can't be parsed. Returns null when nothing usable is available.
24
+ */
25
+ async function memAvailableMb() {
26
+ try {
27
+ const txt = await readFile("/proc/meminfo", "utf-8");
28
+ const m = /^MemAvailable:\s+(\d+)\s*kB/m.exec(txt);
29
+ if (m) return Math.floor(Number(m[1]) / 1024);
30
+ } catch {}
31
+ try {
32
+ const { freemem } = await import("os");
33
+ return Math.floor(freemem() / (1024 * 1024));
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
38
+ /** True when at least one spawn limit is configured (else the gate is a no-op). */
39
+ function spawnGateEnabled() {
40
+ return getMaxAgents() !== void 0 || getMinFreeMb() !== void 0;
41
+ }
42
+ /**
43
+ * Instantaneous capacity check. Returns a human-readable reason string when a
44
+ * new spawn should be held back (cap reached / memory too low), or null when
45
+ * there's capacity. Both limits are opt-in — unset means no check.
46
+ *
47
+ * NOTE — the `maxAgents` count is BEST-EFFORT, not a hard barrier: the check is
48
+ * non-atomic (check here → register in `pids.jsonl` later, in another process),
49
+ * so a simultaneous burst can briefly overshoot the cap before the new wrappers
50
+ * appear in the live count. That's acceptable because (a) the CLI path's
51
+ * φ-backoff desynchronizes retries, spreading a burst out, and (b) `minFreeMb`
52
+ * is the HARD OOM guard — it's re-evaluated against live `/proc/meminfo` on every
53
+ * attempt, so once RAM actually drops, further spawns are held regardless of the
54
+ * count. Exact admission would need a cross-process reservation+lock (a TTL'd
55
+ * reservation file); deliberately deferred to avoid stale reservations wedging
56
+ * spawns, which would be worse than a transient overshoot.
57
+ */
58
+ async function spawnRejectionReason() {
59
+ const maxAgents = getMaxAgents();
60
+ if (maxAgents !== void 0) {
61
+ const live = (await readGlobalPids({ liveOnly: true })).length;
62
+ if (live >= maxAgents) return `too many agents running (${live}/${maxAgents}) — this agent wasn't started. Please wait for one to finish and try again. (host: raise "maxAgents" in ~/.agent-yes/config.json or AGENT_YES_MAX_AGENTS)`;
63
+ }
64
+ const minFreeMb = getMinFreeMb();
65
+ if (minFreeMb !== void 0) {
66
+ const avail = await memAvailableMb();
67
+ if (avail !== null && avail < minFreeMb) return `the host is low on memory (${avail}MB free, needs ${minFreeMb}MB) — this agent wasn't started, to avoid crashing the machine. Please try again in a moment. (host: adjust "minFreeMb" in ~/.agent-yes/config.json or AGENT_YES_MIN_FREE_MB)`;
68
+ }
69
+ return null;
70
+ }
71
+ const PHI = 1.618;
72
+ const BACKOFF_CAP_MS = 6e4;
73
+ const BACKOFF_BASE_MS = 1e3;
74
+ /**
75
+ * Block until there's spawn capacity, polling with φ-backoff (1s × φⁿ, capped at
76
+ * 60s — there is no OS event for "memory freed", so polling is unavoidable here).
77
+ * Fails open after {@link getSpawnWaitMs} ms (default 10 min): on timeout it logs
78
+ * and proceeds, so a set of mutually-waiting recursive spawns can never deadlock
79
+ * permanently. A no-op (returns immediately) when no limit is configured, so it
80
+ * adds ~zero overhead to the spawn hot path for users who haven't opted in.
81
+ *
82
+ * `sleep`/`now` are injectable for tests. `onWait` fires once per backoff cycle.
83
+ */
84
+ async function waitForSpawnCapacity(opts) {
85
+ if (!spawnGateEnabled()) return;
86
+ const maxWaitMs = opts?.maxWaitMs ?? getSpawnWaitMs();
87
+ const now = opts?.now ?? (() => performance.now());
88
+ const sleep = opts?.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
89
+ const start = now();
90
+ let delay = BACKOFF_BASE_MS;
91
+ for (;;) {
92
+ const reason = await spawnRejectionReason();
93
+ if (!reason) return;
94
+ const waited = now() - start;
95
+ if (waited >= maxWaitMs) {
96
+ opts?.onProceedAnyway?.(reason, waited);
97
+ return;
98
+ }
99
+ opts?.onWait?.(reason, waited);
100
+ await sleep(Math.min(delay, BACKOFF_CAP_MS, maxWaitMs - waited));
101
+ delay = Math.min(delay * PHI, BACKOFF_CAP_MS);
102
+ }
103
+ }
104
+
105
+ //#endregion
106
+ export { waitForSpawnCapacity as i, spawnGateEnabled as n, spawnRejectionReason as r, memAvailableMb as t };
107
+ //# sourceMappingURL=spawnGate-B_VDMXYL.js.map
@@ -0,0 +1,5 @@
1
+ import "./logger-CDIsZ-Pp.js";
2
+ import "./globalPidIndex-DlmmJlO8.js";
3
+ import { i as waitForSpawnCapacity, n as spawnGateEnabled, r as spawnRejectionReason, t as memAvailableMb } from "./spawnGate-B_VDMXYL.js";
4
+
5
+ export { waitForSpawnCapacity };
@@ -3,7 +3,7 @@ import "./globalPidIndex-DlmmJlO8.js";
3
3
  import "./configShared-CEyhl0WH.js";
4
4
  import "./e2e-Bfw7qL9O.js";
5
5
  import "./webrtcLink-CBZkZ-LT.js";
6
- import "./remotes-DBCvpp3B.js";
7
- import { A as writeToIpc, C as resolveReadWindow, D as snapshotStatus, E as runSubcommand, O as stopTipForCli, S as resolveOne, T as restartHintLines, _ as menuSelectKeys, a as cursorAbs, b as renderRawLog, c as extractNeedsInput, d as isAgentStuck, f as isExitRequest, g as matchKeyword, h as listRecords, i as controlCodeFromName, k as writeKeysPaced, l as extractTaskCounts, m as isSubcommand, n as READ_PAGE_DEFAULT, o as deriveLiveStatus, p as isPidAlive, r as cmdHelp, s as extractMenu, t as GRACEFUL_EXIT_COMMANDS, u as finalizedLines, v as readNotes, w as resolveResumeArgs, x as renderRawLogLines, y as readPtysize } from "./subcommands-DtwxPMYe.js";
6
+ import "./remotes-T6nf0t3K.js";
7
+ import { A as writeToIpc, C as resolveReadWindow, D as snapshotStatus, E as runSubcommand, O as stopTipForCli, S as resolveOne, T as restartHintLines, _ as menuSelectKeys, a as cursorAbs, b as renderRawLog, c as extractNeedsInput, d as isAgentStuck, f as isExitRequest, g as matchKeyword, h as listRecords, i as controlCodeFromName, k as writeKeysPaced, l as extractTaskCounts, m as isSubcommand, n as READ_PAGE_DEFAULT, o as deriveLiveStatus, p as isPidAlive, r as cmdHelp, s as extractMenu, t as GRACEFUL_EXIT_COMMANDS, u as finalizedLines, v as readNotes, w as resolveResumeArgs, x as renderRawLogLines, y as readPtysize } from "./subcommands-DWeo7ZLc.js";
8
8
 
9
9
  export { cmdHelp, isSubcommand, runSubcommand };
@@ -3,7 +3,7 @@ import { t as agentYesHome } from "./agentYesHome-_eJa3DaX.js";
3
3
  import { a as updateGlobalPidStatus, i as readGlobalPids } from "./globalPidIndex-DlmmJlO8.js";
4
4
  import { t as loadSharedCliDefaults } from "./configShared-CEyhl0WH.js";
5
5
  import { n as isWebrtcSpec } from "./webrtcLink-CBZkZ-LT.js";
6
- import { a as resolveRemoteSpec, i as readRemotes } from "./remotes-DBCvpp3B.js";
6
+ import { a as resolveRemoteSpec, i as readRemotes } from "./remotes-T6nf0t3K.js";
7
7
  import ms from "ms";
8
8
  import yargs from "yargs";
9
9
  import { appendFile, mkdir, open, readFile, stat, writeFile } from "fs/promises";
@@ -597,19 +597,19 @@ async function runSubcommand(argv) {
597
597
  case "restart": return await cmdRestart(rest);
598
598
  case "note": return await cmdNote(rest);
599
599
  case "serve": {
600
- const { cmdServe } = await import("./serve-DzOWMI_8.js");
600
+ const { cmdServe } = await import("./serve-mLqsRwfR.js");
601
601
  return cmdServe(rest);
602
602
  }
603
603
  case "setup": {
604
- const { cmdSetup } = await import("./setup-tVfp7djj.js");
604
+ const { cmdSetup } = await import("./setup-BtqKZx3q.js");
605
605
  return cmdSetup(rest);
606
606
  }
607
607
  case "schedule": {
608
- const { cmdSchedule } = await import("./schedule-BobQwIBS.js");
608
+ const { cmdSchedule } = await import("./schedule-C2RMA3qm.js");
609
609
  return cmdSchedule(rest);
610
610
  }
611
611
  case "remote": {
612
- const { cmdRemote } = await import("./remotes-DkNuq417.js");
612
+ const { cmdRemote } = await import("./remotes-ClT1bq16.js");
613
613
  return cmdRemote(rest);
614
614
  }
615
615
  case "reap":
@@ -624,9 +624,29 @@ async function runSubcommand(argv) {
624
624
  return 1;
625
625
  }
626
626
  }
627
- function cmdHelp(managerCommands = true) {
627
+ /**
628
+ * The banner shown by `ay help` / `ay -h` when this process is itself running
629
+ * inside an agent (`AGENT_YES_PID` set — see resolveSender). Answers the three
630
+ * things a nested agent actually needs: who am I, who spawned me, and how do I
631
+ * drive sub-agents of my own — so it doesn't have to rediscover the fan-out
632
+ * primitives (spawn / ay ls forest / ay ls --watch) from scratch every session.
633
+ */
634
+ async function buildAgentContextSection(self) {
635
+ const hasParentPid = typeof self.parent_pid === "number" && self.parent_pid > 0;
636
+ const parent = hasParentPid ? (await listRecords(void 0, {
637
+ all: true,
638
+ active: false,
639
+ json: false,
640
+ latest: false,
641
+ cwdScope: null
642
+ })).find((r) => r.wrapper_pid === self.parent_pid) : void 0;
643
+ return `You are running inside an agent:\n ${`You are agent pid ${self.pid} (${self.cli}) in ${shortenPath(self.cwd)}.`}\n ${!hasParentPid ? `Top-level agent — no parent (started from a human shell or scheduler).` : parent ? `Spawned by agent pid ${parent.pid} (${parent.cli}) in ${shortenPath(parent.cwd)}.` : `Nested under a parent (wrapper pid ${self.parent_pid}) whose record isn't in the local registry.`}\n\nAs an agent, you can:\n Spawn a sub-agent:\n ay <cli> -- "<prompt>" auto-links as your child\n ay claude --model sonnet --advisor opus -- "<prompt>" routine task\n ay claude --model opus --advisor fable -- "<prompt>" complex task\n (pick --model by task complexity so easy tasks don't cost like hard ones;\n --advisor is a claude-cli flag — only takes effect for claude/cy)\n List agents (your children nest under your own pid in the tree):\n ay ls --cwd ${shortenPath(self.cwd)}\n Watch agent state changes, scoped to your workspace:\n ay ls --watch --cwd ${shortenPath(self.cwd)}\n (NDJSON stream of state changes across every matched agent — one watcher\n for the whole fan-out instead of N \`ay status --watch\`es)\n Read one sub-agent's output:\n ay tail -f <pid> follow live output (no single command tails\n many agents' content at once yet — loop\n \`ay ls --json\` pids into per-pid \`ay tail\`)\n\n`;
644
+ }
645
+ async function cmdHelp(managerCommands = true) {
628
646
  const setupLine = managerCommands ? ` ay setup guided setup: pick a workspace, share to agent-yes.com\n` : ``;
629
- process.stdout.write("ay - agent-yes CLI\n\nManagement:\n ay ls [keyword] list running agents\n ay tail [-f] [-n N] <keyword> last N lines (96), -f to follow\n ay read <keyword> [page opts] paginate: --last/--head N, --range A:B,\n --before-line L [--limit N]\n ay cat <keyword> full log\n ay head <keyword> first N lines\n ay send <keyword> <msg> send a message\n ay key <keyword> <key...> send raw keystrokes (down/up/enter/esc/…) — drives menus\n ay select <keyword> <N> pick option N of a needs_input selection menu\n ay attach <keyword> interactive attach (detach: Ctrl-\\)\n ay stop <keyword> graceful shutdown (/exit for claude/codex)\n ay exit <keyword> [reason] graceful shutdown, recording who/why (= 'ay send <kw> exit')\n ay restart <keyword> [--fresh] stop (if live) + relaunch resuming the session; --fresh replays the prompt\n ay status <keyword> agent status snapshot\n ay result <keyword> [--wait] pull an agent's structured result envelope\n ay result set '<json>' (inside an agent) deposit your result envelope\n ay reap kill process groups leaked by dead agents\n\nRemote:\n" + setupLine + " 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");
647
+ const self = process.env.AGENT_YES_PID ? await resolveSender() : null;
648
+ const agentSection = self ? await buildAgentContextSection(self) : "";
649
+ process.stdout.write(agentSection + "ay - agent-yes CLI\n\nManagement:\n ay ls [keyword] list running agents\n ay tail [-f] [-n N] <keyword> last N lines (96), -f to follow\n ay read <keyword> [page opts] paginate: --last/--head N, --range A:B,\n --before-line L [--limit N]\n ay cat <keyword> full log\n ay head <keyword> first N lines\n ay send <keyword> <msg> send a message\n ay key <keyword> <key...> send raw keystrokes (down/up/enter/esc/…) — drives menus\n ay select <keyword> <N> pick option N of a needs_input selection menu\n ay attach <keyword> interactive attach (detach: Ctrl-\\)\n ay stop <keyword> graceful shutdown (/exit for claude/codex)\n ay exit <keyword> [reason] graceful shutdown, recording who/why (= 'ay send <kw> exit')\n ay restart <keyword> [--fresh] stop (if live) + relaunch resuming the session; --fresh replays the prompt\n ay status <keyword> agent status snapshot\n ay result <keyword> [--wait] pull an agent's structured result envelope\n ay result set '<json>' (inside an agent) deposit your result envelope\n ay reap kill process groups leaked by dead agents\n\nRemote:\n" + setupLine + " 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");
630
650
  return 0;
631
651
  }
632
652
  function matchKeyword(record, keyword) {
@@ -982,7 +1002,7 @@ async function fetchRemoteRecordsRaw(url, token, opts) {
982
1002
  let base = url;
983
1003
  let bearer = token;
984
1004
  if (isWebrtcSpec(url)) {
985
- const { startWebrtcBridge } = await import("./webrtcRemote-Ccdzmuc-.js");
1005
+ const { startWebrtcBridge } = await import("./webrtcRemote-GAgF5K45.js");
986
1006
  bridge = await startWebrtcBridge(url);
987
1007
  base = bridge.baseUrl;
988
1008
  bearer = bridge.token;
@@ -3005,4 +3025,4 @@ async function cmdResultSet(rest) {
3005
3025
 
3006
3026
  //#endregion
3007
3027
  export { writeToIpc as A, resolveReadWindow as C, snapshotStatus as D, runSubcommand as E, stopTipForCli as O, resolveOne as S, restartHintLines as T, menuSelectKeys as _, cursorAbs as a, renderRawLog as b, extractNeedsInput as c, isAgentStuck as d, isExitRequest as f, matchKeyword as g, listRecords as h, controlCodeFromName as i, writeKeysPaced as k, extractTaskCounts as l, isSubcommand as m, READ_PAGE_DEFAULT as n, deriveLiveStatus as o, isPidAlive as p, cmdHelp as r, extractMenu as s, GRACEFUL_EXIT_COMMANDS as t, finalizedLines as u, readNotes as v, resolveResumeArgs as w, renderRawLogLines as x, readPtysize as y };
3008
- //# sourceMappingURL=subcommands-DtwxPMYe.js.map
3028
+ //# sourceMappingURL=subcommands-DWeo7ZLc.js.map
@@ -187,4 +187,4 @@ async function startTray() {
187
187
 
188
188
  //#endregion
189
189
  export { ensureTray, startTray };
190
- //# sourceMappingURL=tray-BMzpUSfa.js.map
190
+ //# sourceMappingURL=tray-CZarCA2Q.js.map
@@ -1,5 +1,5 @@
1
1
  import { n as logger, t as addTransport } from "./logger-CDIsZ-Pp.js";
2
- import { r as getInstalledPackage } from "./versionChecker-C0UJyFcN.js";
2
+ import { r as getInstalledPackage } from "./versionChecker-BvR6tV3u.js";
3
3
  import { t as agentYesHome } from "./agentYesHome-_eJa3DaX.js";
4
4
  import { i as shouldUseLock, r as releaseLock, t as acquireLock } from "./runningLock-V4qvXgAw.js";
5
5
  import { t as PidStore } from "./pidStore-BfoBWUjc.js";
@@ -1803,4 +1803,4 @@ function sleep(ms) {
1803
1803
 
1804
1804
  //#endregion
1805
1805
  export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
1806
- //# sourceMappingURL=ts-jDEwTsVt.js.map
1806
+ //# sourceMappingURL=ts-s3wYccKf.js.map
@@ -7,7 +7,7 @@ import { fileURLToPath } from "url";
7
7
 
8
8
  //#region package.json
9
9
  var name = "claude-yes";
10
- var version = "1.165.0";
10
+ var version = "1.167.0";
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-C0UJyFcN.js.map
218
+ //# sourceMappingURL=versionChecker-BvR6tV3u.js.map
@@ -256,4 +256,4 @@ async function startWebrtcBridge(link) {
256
256
 
257
257
  //#endregion
258
258
  export { startWebrtcBridge };
259
- //# sourceMappingURL=webrtcRemote-Ccdzmuc-.js.map
259
+ //# sourceMappingURL=webrtcRemote-GAgF5K45.js.map
@@ -96,6 +96,45 @@ function getSpawnHook() {
96
96
  function hasSpawnHook() {
97
97
  return getSpawnHook() !== null;
98
98
  }
99
+ /**
100
+ * Cap on concurrently-live agents admitted via `/api/spawn`. Env
101
+ * `AGENT_YES_MAX_AGENTS` overrides the config `maxAgents`. A non-positive,
102
+ * missing, or unparseable value means **unlimited** (returns undefined), which
103
+ * preserves the historical no-cap behavior. Exists to stop an unbounded fan-out
104
+ * of agents from exhausting host RAM and tripping the OOM-killer.
105
+ */
106
+ function getMaxAgents() {
107
+ const raw = process.env.AGENT_YES_MAX_AGENTS?.trim();
108
+ const n = raw !== void 0 && raw !== "" ? Number(raw) : readConfig().maxAgents;
109
+ const v = typeof n === "number" && Number.isFinite(n) ? Math.floor(n) : NaN;
110
+ return v > 0 ? v : void 0;
111
+ }
112
+ /**
113
+ * Minimum system MemAvailable (MB) required to admit a new spawn. Env
114
+ * `AGENT_YES_MIN_FREE_MB` overrides config `minFreeMb`. Non-positive/unset =
115
+ * no floor (undefined). Complements {@link getMaxAgents}: a count cap alone
116
+ * can't stop OOM when individual agents are large, so we also refuse to spawn
117
+ * when free memory is already low.
118
+ */
119
+ function getMinFreeMb() {
120
+ const raw = process.env.AGENT_YES_MIN_FREE_MB?.trim();
121
+ const n = raw !== void 0 && raw !== "" ? Number(raw) : readConfig().minFreeMb;
122
+ const v = typeof n === "number" && Number.isFinite(n) ? Math.floor(n) : NaN;
123
+ return v > 0 ? v : void 0;
124
+ }
125
+ /**
126
+ * Max time (ms) a CLI spawn blocks waiting for capacity before failing open.
127
+ * Env `AGENT_YES_SPAWN_WAIT_MS` overrides config `spawnWaitMs`. A non-negative
128
+ * finite value is used as-is (0 = don't wait, check once then proceed); anything
129
+ * missing/garbage falls back to the 10-minute default. Bounded fail-open is
130
+ * deliberate: recursive spawns must never deadlock permanently on each other.
131
+ */
132
+ function getSpawnWaitMs() {
133
+ const DEFAULT = 6e5;
134
+ const raw = process.env.AGENT_YES_SPAWN_WAIT_MS?.trim();
135
+ const n = raw !== void 0 && raw !== "" ? Number(raw) : readConfig().spawnWaitMs;
136
+ return typeof n === "number" && Number.isFinite(n) && n >= 0 ? Math.floor(n) : DEFAULT;
137
+ }
99
138
  /** Persist the workspace root, tilde-expanded and resolved to an absolute path. */
100
139
  function setWorkspaceRoot(dir) {
101
140
  const abs = path.resolve(expandTilde(dir));
@@ -122,5 +161,5 @@ function resolveSpawnCwd(input) {
122
161
  }
123
162
 
124
163
  //#endregion
125
- export { isProvisionAllowed as a, hasSpawnHook as i, getSpawnHook as n, resolveSpawnCwd as o, getWorkspaceRoot as r, setWorkspaceRoot as s, getProvisionRoot as t };
126
- //# sourceMappingURL=workspaceConfig-B0Q9-q2B.js.map
164
+ export { getSpawnWaitMs as a, isProvisionAllowed as c, getSpawnHook as i, resolveSpawnCwd as l, getMinFreeMb as n, getWorkspaceRoot as o, getProvisionRoot as r, hasSpawnHook as s, getMaxAgents as t, setWorkspaceRoot as u };
165
+ //# sourceMappingURL=workspaceConfig-D3OH7and.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-yes",
3
- "version": "1.165.0",
3
+ "version": "1.167.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/cli.ts CHANGED
@@ -30,7 +30,7 @@ import { buildRustArgs } from "./buildRustArgs.ts";
30
30
  const isHelpFlag = rawArg === "-h" || rawArg === "--help";
31
31
  const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands.ts");
32
32
  if (isHelpFlag && process.argv.length === 3) {
33
- cmdHelp(managerCommands);
33
+ await cmdHelp(managerCommands);
34
34
  process.exit(0);
35
35
  }
36
36
  if (isSubcommand(rawArg, managerCommands)) {
@@ -62,6 +62,27 @@ if (config.tray) {
62
62
  ensureTray(); // fire-and-forget, don't await
63
63
  }
64
64
 
65
+ // Spawn admission control — applies to a REAL agent launch only (subcommands,
66
+ // --version, --tray already handled/exited above; --append-prompt / --version
67
+ // below are not launches). Sits BEFORE the --rust dispatch so it gates BOTH the
68
+ // Rust and the TypeScript runtimes. Blocks with φ-backoff until there's capacity
69
+ // (or fails open after a timeout) so a burst of recursive `ay <cli>` spawns gets
70
+ // spaced out instead of storming the host into the OOM-killer. No-op (instant)
71
+ // unless maxAgents/minFreeMb is configured — see ts/spawnGate.ts.
72
+ if (!config.showVersion && !config.appendPrompt && !config.tray) {
73
+ const { waitForSpawnCapacity } = await import("./spawnGate.ts");
74
+ await waitForSpawnCapacity({
75
+ onWait: (reason, waitedMs) => {
76
+ if (config.verbose || waitedMs === 0)
77
+ console.error(`[agent-yes] spawn gate: ${reason} — waiting…`);
78
+ },
79
+ onProceedAnyway: (reason, waitedMs) =>
80
+ console.error(
81
+ `[agent-yes] spawn gate: ${reason} — waited ${Math.round(waitedMs / 1000)}s, proceeding anyway`,
82
+ ),
83
+ });
84
+ }
85
+
65
86
  // Handle --rust: spawn the Rust binary instead, fall back to TypeScript if unavailable
66
87
  if (config.useRust) {
67
88
  let rustBinary: string | undefined;
package/ts/serve.ts CHANGED
@@ -21,6 +21,7 @@ import {
21
21
  type CommonOpts,
22
22
  } from "./subcommands.ts";
23
23
  import { updateGlobalPidStatus } from "./globalPidIndex.ts";
24
+ import { spawnRejectionReason } from "./spawnGate.ts";
24
25
  import { pgidForWrapper } from "./reaper.ts";
25
26
  import { SUPPORTED_CLIS } from "./SUPPORTED_CLIS.ts";
26
27
  import { getInstalledPackage } from "./versionChecker.ts";
@@ -1705,6 +1706,13 @@ export async function cmdServe(rest: string[]): Promise<number> {
1705
1706
  return new Response(`unsupported cli: ${cli}`, { status: 400 });
1706
1707
  const prompt = String(body.prompt ?? "");
1707
1708
 
1709
+ // Admission control BEFORE any provisioning (clone/worktree) so a capped
1710
+ // request fails fast without doing expensive work. Enforces the optional
1711
+ // concurrency cap + memory floor that keep a fan-out of agents from
1712
+ // driving the host into the OOM-killer. 429 = back-pressure, retry later.
1713
+ const reject = await spawnRejectionReason();
1714
+ if (reject) return new Response(reject, { status: 429 });
1715
+
1708
1716
  // Resolve the working directory. A `from` source is provisioned (clone /
1709
1717
  // worktree) through codehost/provision; a plain `cwd` is resolved to the
1710
1718
  // workspace root and mkdir-p'd so a missing dir no longer ENOENTs.