agent-yes 1.116.0 → 1.118.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,8 @@
1
+ import "./ts-DYzATaI_.js";
2
+ import "./logger-B9h0djqx.js";
3
+ import "./versionChecker-D-OYziVA.js";
4
+ import "./pidStore-BcGnnKQf.js";
5
+ import "./globalPidIndex-yVd3mbsV.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-CrIGjD28.js";
7
+
8
+ export { SUPPORTED_CLIS };
@@ -1,8 +1,8 @@
1
- import { t as CLIS_CONFIG } from "./ts-DvnOmOAf.js";
1
+ import { t as CLIS_CONFIG } from "./ts-DYzATaI_.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-DuCEAzP9.js.map
8
+ //# sourceMappingURL=SUPPORTED_CLIS-CrIGjD28.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-DeXuTfJ0.js";
3
+ import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-D-OYziVA.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-CxHjPXEH.js");
485
+ const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-FtwGxxtc.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-vFwB3bnK.js");
518
+ const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-BS7EiXxW.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}`);
@@ -545,7 +545,7 @@ if (config.showVersion) {
545
545
  process.exit(0);
546
546
  }
547
547
  if (config.appendPrompt) {
548
- const { PidStore } = await import("./pidStore-9b3YTuf4.js");
548
+ const { PidStore } = await import("./pidStore-BLcnCpkX.js");
549
549
  const ipcPath = await PidStore.findActiveFifo(process.cwd());
550
550
  if (!ipcPath) {
551
551
  console.error("No active agent with IPC found in current directory.");
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-DvnOmOAf.js";
1
+ import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-DYzATaI_.js";
2
2
  import "./logger-B9h0djqx.js";
3
- import "./versionChecker-DeXuTfJ0.js";
4
- import "./pidStore-DBjlqzo8.js";
3
+ import "./versionChecker-D-OYziVA.js";
4
+ import "./pidStore-BcGnnKQf.js";
5
5
  import "./globalPidIndex-yVd3mbsV.js";
6
6
 
7
7
  export { AgentContext, CLIS_CONFIG, config, agentYes as default, removeControlCharacters };
@@ -0,0 +1,5 @@
1
+ import "./logger-B9h0djqx.js";
2
+ import { t as PidStore } from "./pidStore-BcGnnKQf.js";
3
+ import "./globalPidIndex-yVd3mbsV.js";
4
+
5
+ export { PidStore };
@@ -223,7 +223,7 @@ var PidStore = class PidStore {
223
223
  logger.warn("[pidStore] Failed to initialize:", error);
224
224
  }
225
225
  }
226
- async registerProcess({ pid, cli, args, prompt, cwd }) {
226
+ async registerProcess({ pid, cli, args, prompt, cwd, wrapperPid }) {
227
227
  const now = Date.now();
228
228
  const argsJson = JSON.stringify(args);
229
229
  const logFile = this.getRawLogPath(pid);
@@ -260,7 +260,8 @@ var PidStore = class PidStore {
260
260
  status: "active",
261
261
  exit_code: null,
262
262
  exit_reason: null,
263
- started_at: now
263
+ started_at: now,
264
+ wrapper_pid: wrapperPid ?? null
264
265
  }).then(() => maybeCompactGlobalPids()).catch(() => null);
265
266
  return result;
266
267
  }
@@ -381,4 +382,4 @@ pid-db/
381
382
 
382
383
  //#endregion
383
384
  export { agentYesHome as n, PidStore as t };
384
- //# sourceMappingURL=pidStore-DBjlqzo8.js.map
385
+ //# sourceMappingURL=pidStore-BcGnnKQf.js.map
@@ -1,11 +1,11 @@
1
- import "./ts-DvnOmOAf.js";
1
+ import "./ts-DYzATaI_.js";
2
2
  import "./logger-B9h0djqx.js";
3
- import { r as getInstalledPackage } from "./versionChecker-DeXuTfJ0.js";
4
- import "./pidStore-DBjlqzo8.js";
3
+ import { r as getInstalledPackage } from "./versionChecker-D-OYziVA.js";
4
+ import "./pidStore-BcGnnKQf.js";
5
5
  import "./globalPidIndex-yVd3mbsV.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DuCEAzP9.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-CrIGjD28.js";
7
7
  import "./remotes-C3xPRtfg.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-DLJqD_Yj.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-tH8JdrBS.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";
@@ -51,6 +51,21 @@ const defaultOpts = (overrides = {}) => ({
51
51
  cwdScope: null,
52
52
  ...overrides
53
53
  });
54
+ const SESSION_PIN_ENV = new Set([
55
+ "CLAUDECODE",
56
+ "CLAUDE_CODE_SSE_PORT",
57
+ "CLAUDE_CODE_SESSION_ID",
58
+ "CLAUDE_CODE_CHILD_SESSION",
59
+ "CLAUDE_CODE_ENTRYPOINT"
60
+ ]);
61
+ function freshAgentEnv() {
62
+ const env = {};
63
+ for (const [k, v] of Object.entries(process.env)) {
64
+ if (v === void 0 || SESSION_PIN_ENV.has(k)) continue;
65
+ env[k] = v;
66
+ }
67
+ return env;
68
+ }
54
69
  const DAEMON_NAME = "agent-yes";
55
70
  async function ensureBootAutostart(oxmgrBin) {
56
71
  try {
@@ -669,6 +684,7 @@ Options:
669
684
  ...prompt ? ["--", prompt] : []
670
685
  ], {
671
686
  cwd,
687
+ env: freshAgentEnv(),
672
688
  stdin: "ignore",
673
689
  stdout: "ignore",
674
690
  stderr: "ignore"
@@ -773,4 +789,4 @@ Options:
773
789
 
774
790
  //#endregion
775
791
  export { cmdServe };
776
- //# sourceMappingURL=serve-CSupgu7Q.js.map
792
+ //# sourceMappingURL=serve-uhfwjvBd.js.map
@@ -1,6 +1,6 @@
1
1
  import "./logger-B9h0djqx.js";
2
2
  import "./globalPidIndex-yVd3mbsV.js";
3
3
  import "./remotes-C3xPRtfg.js";
4
- import { a as 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-DLJqD_Yj.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-tH8JdrBS.js";
5
5
 
6
6
  export { cmdHelp, isSubcommand, runSubcommand };
@@ -60,6 +60,74 @@ async function compactNotes() {
60
60
  })).join("\n");
61
61
  await writeFile(notesPath(), lines ? lines + "\n" : "");
62
62
  }
63
+ const READ_WINDOW_MS = 6e4;
64
+ const READS_KEY_SEP = "\0";
65
+ function readsPath() {
66
+ const dir = process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
67
+ return path.join(dir, "reads.jsonl");
68
+ }
69
+ async function readReads() {
70
+ let raw;
71
+ try {
72
+ raw = await readFile(readsPath(), "utf-8");
73
+ } catch {
74
+ return /* @__PURE__ */ new Map();
75
+ }
76
+ const map = /* @__PURE__ */ new Map();
77
+ for (const line of raw.split("\n")) {
78
+ const t = line.trim();
79
+ if (!t) continue;
80
+ try {
81
+ const { by, target, at } = JSON.parse(t);
82
+ if (typeof by === "string" && typeof target === "number" && typeof at === "number") map.set(`${by}${READS_KEY_SEP}${target}`, at);
83
+ } catch {}
84
+ }
85
+ return map;
86
+ }
87
+ async function recordRead(by, target) {
88
+ const p = readsPath();
89
+ try {
90
+ await mkdir(path.dirname(p), { recursive: true });
91
+ await appendFile(p, JSON.stringify({
92
+ by,
93
+ target,
94
+ at: Date.now()
95
+ }) + "\n");
96
+ if ((await readFile(p, "utf-8").catch(() => "")).split("\n").length > 200) {
97
+ const lines = [...(await readReads()).entries()].map(([k, at]) => {
98
+ const i = k.indexOf(READS_KEY_SEP);
99
+ return JSON.stringify({
100
+ by: k.slice(0, i),
101
+ target: Number(k.slice(i + 1)),
102
+ at
103
+ });
104
+ }).join("\n");
105
+ await writeFile(p, lines ? lines + "\n" : "");
106
+ }
107
+ } catch {}
108
+ }
109
+ async function lastReadAt(by, target) {
110
+ return (await readReads()).get(`${by}${READS_KEY_SEP}${target}`) ?? null;
111
+ }
112
+ async function resolveSender() {
113
+ const envPid = process.env.AGENT_YES_PID ? Number(process.env.AGENT_YES_PID) : null;
114
+ if (!envPid || Number.isNaN(envPid)) return null;
115
+ const recs = await listRecords(void 0, {
116
+ all: true,
117
+ active: false,
118
+ json: false,
119
+ latest: false,
120
+ cwdScope: null
121
+ });
122
+ return recs.find((r) => r.wrapper_pid === envPid) ?? recs.find((r) => r.pid === envPid) ?? null;
123
+ }
124
+ async function senderContext() {
125
+ const agent = await resolveSender();
126
+ return {
127
+ key: agent ? `agent:${agent.pid}` : "human",
128
+ agent
129
+ };
130
+ }
63
131
  /**
64
132
  * Read the per-cwd TS PidStore JSONL and convert to the global record shape,
65
133
  * so pre-existing TS agents that were spawned before the global-index mirror
@@ -163,7 +231,7 @@ async function runSubcommand(argv) {
163
231
  case "restart": return await cmdRestart(rest);
164
232
  case "note": return await cmdNote(rest);
165
233
  case "serve": {
166
- const { cmdServe } = await import("./serve-CSupgu7Q.js");
234
+ const { cmdServe } = await import("./serve-uhfwjvBd.js");
167
235
  return cmdServe(rest);
168
236
  }
169
237
  case "setup": {
@@ -762,6 +830,8 @@ async function cmdRead(rest, { mode }) {
762
830
  const record = await resolveOne(keyword, opts);
763
831
  const logPath = record.log_file;
764
832
  if (!logPath) throw new Error(`pid ${record.pid}: no log_file recorded`);
833
+ const reader = await senderContext();
834
+ await recordRead(reader.key, record.pid);
765
835
  let stats;
766
836
  try {
767
837
  stats = await stat(logPath);
@@ -779,7 +849,10 @@ async function cmdRead(rest, { mode }) {
779
849
  process.stderr.write(header + "\n");
780
850
  process.stdout.write(rendered);
781
851
  if (!rendered.endsWith("\n")) process.stdout.write("\n");
782
- if (follow) return plain ? followPlainLocal(logPath, buf) : followRawLocal(logPath, buf);
852
+ if (follow) {
853
+ setInterval(() => void recordRead(reader.key, record.pid), 3e4).unref?.();
854
+ return plain ? followPlainLocal(logPath, buf) : followRawLocal(logPath, buf);
855
+ }
783
856
  process.stderr.write(`
784
857
  ay ls # list all agents
785
858
  ay tail -f ${record.pid} # follow live output\n ay send ${record.pid} "next: ..." # send a prompt\n ay send ${record.pid} "" --code=ctrl-c # interrupt\n`);
@@ -1067,6 +1140,10 @@ async function cmdSend(rest) {
1067
1140
  }).option("cwd", {
1068
1141
  type: "string",
1069
1142
  description: "Restrict to agents under this dir"
1143
+ }).option("force", {
1144
+ type: "boolean",
1145
+ default: false,
1146
+ description: "Skip the 'tailed recently' safety check (also: AGENT_YES_FORCE_SEND=1)"
1070
1147
  }).help(false).version(false).exitProcess(false).parseAsync();
1071
1148
  const opts = {
1072
1149
  all: argv.all,
@@ -1093,9 +1170,17 @@ async function cmdSend(rest) {
1093
1170
  for await (const chunk of process.stdin) chunks.push(chunk);
1094
1171
  body = Buffer.concat(chunks).toString("utf-8").trimEnd();
1095
1172
  } else body = rawMessage;
1096
- const sourcePid = process.env.AGENT_YES_PID ? Number(process.env.AGENT_YES_PID) : null;
1097
- const talkBack = sourcePid ? `\n(from AGENT_YES_PID=${sourcePid} reply: ay send ${sourcePid} "...")` : "";
1098
- const fullBody = body + talkBack;
1173
+ const sender = await senderContext();
1174
+ const force = Boolean(argv.force) || process.env.AGENT_YES_FORCE_SEND === "1";
1175
+ if (sender.agent && sender.agent.pid === record.pid && !force) throw new Error(`refusing to send to yourself (pid ${record.pid}) — pass --force if you really mean it.`);
1176
+ const last = await lastReadAt(sender.key, record.pid);
1177
+ if (!(last !== null && Date.now() - last <= READ_WINDOW_MS) && !force) {
1178
+ const ago = last === null ? "never read" : `last read ${Math.round((Date.now() - last) / 1e3)}s ago`;
1179
+ const what = `pid ${record.pid} (${record.cli}, ${shortenPath(record.cwd)}) — ${ago}, not within ${READ_WINDOW_MS / 1e3}s`;
1180
+ if (sender.agent) throw new Error(`${what}.\n Confirm it's the right agent first: ay tail ${record.pid}\n then resend, or pass --force to override.`);
1181
+ process.stderr.write(`warning: ${what} — make sure this is the agent you meant (ay tail ${record.pid}).\n`);
1182
+ }
1183
+ const fullBody = (sender.agent ? `[from ${sender.agent.cli} #${sender.agent.pid} @ ${shortenPath(sender.agent.cwd)} — reply: ay send ${sender.agent.pid} "..."]\n` : "") + body;
1099
1184
  if (fullBody && trailing) {
1100
1185
  await writeToIpc(fifoPath, fullBody);
1101
1186
  await new Promise((r) => setTimeout(r, 200));
@@ -1103,7 +1188,7 @@ async function cmdSend(rest) {
1103
1188
  } else await writeToIpc(fifoPath, fullBody + trailing);
1104
1189
  const payload = body + trailing;
1105
1190
  process.stdout.write(`sent to pid ${record.pid} (${record.cli}): ${truncate(payload, 80)}\n`);
1106
- const replyHint = sourcePid ? ` ay send ${sourcePid} "..." # reply to sender\n` : "";
1191
+ const replyHint = sender.agent ? ` ay send ${sender.agent.pid} "..." # reply to sender\n` : "";
1107
1192
  process.stderr.write(`\n` + replyHint + ` ay tail ${record.pid} # watch output\n ay ls # list all agents\n`);
1108
1193
  if (codeName === "ctrl-c" || codeName === "ctrlc") {
1109
1194
  const tip = stopTipForCli(record.cli, record.pid);
@@ -1595,4 +1680,4 @@ async function cmdStatus(rest) {
1595
1680
 
1596
1681
  //#endregion
1597
1682
  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 };
1598
- //# sourceMappingURL=subcommands-DLJqD_Yj.js.map
1683
+ //# sourceMappingURL=subcommands-tH8JdrBS.js.map
@@ -1,6 +1,6 @@
1
1
  import { n as logger, t as addTransport } from "./logger-B9h0djqx.js";
2
- import { r as getInstalledPackage } from "./versionChecker-DeXuTfJ0.js";
3
- import { n as agentYesHome, t as PidStore } from "./pidStore-DBjlqzo8.js";
2
+ import { r as getInstalledPackage } from "./versionChecker-D-OYziVA.js";
3
+ import { n as agentYesHome, t as PidStore } from "./pidStore-BcGnnKQf.js";
4
4
  import { i as shouldUseLock, r as releaseLock, t as acquireLock } from "./runningLock-CJxsoGdb.js";
5
5
  import { i as readGlobalPids } from "./globalPidIndex-yVd3mbsV.js";
6
6
  import { arch, platform } from "process";
@@ -1218,7 +1218,8 @@ async function agentYes({ cli, cliArgs = [], prompt, robust = true, cwd, env, ex
1218
1218
  cli,
1219
1219
  args: cliArgs,
1220
1220
  prompt,
1221
- cwd: workingDir
1221
+ cwd: workingDir,
1222
+ wrapperPid: process.pid
1222
1223
  });
1223
1224
  } catch (error) {
1224
1225
  logger.warn(`[pidStore] Failed to register process ${shell.pid}:`, error);
@@ -1713,4 +1714,4 @@ function sleep(ms) {
1713
1714
 
1714
1715
  //#endregion
1715
1716
  export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
1716
- //# sourceMappingURL=ts-DvnOmOAf.js.map
1717
+ //# sourceMappingURL=ts-DYzATaI_.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.116.0";
10
+ var version = "1.118.0";
11
11
 
12
12
  //#endregion
13
13
  //#region ts/versionChecker.ts
@@ -221,4 +221,4 @@ async function displayVersion() {
221
221
 
222
222
  //#endregion
223
223
  export { versionString as i, displayVersion as n, getInstalledPackage as r, checkAndAutoUpdate as t };
224
- //# sourceMappingURL=versionChecker-DeXuTfJ0.js.map
224
+ //# sourceMappingURL=versionChecker-D-OYziVA.js.map
package/lab/ui/index.html CHANGED
@@ -1545,6 +1545,15 @@
1545
1545
  try {
1546
1546
  localStorage.setItem("ay.sel", sel);
1547
1547
  } catch {}
1548
+ // Reflect it in the URL hash as #room:pid. The fragment is never sent in
1549
+ // any request, so — unlike the old ?pid= query that landed in the server's
1550
+ // access logs — the agent id stays client-side, while still surviving a
1551
+ // refresh and being copy-pasteable as a deep link.
1552
+ try {
1553
+ const want = "#" + e._room + ":" + e.pid;
1554
+ if (location.hash !== want)
1555
+ history.replaceState(null, document.title, location.pathname + want);
1556
+ } catch {}
1548
1557
  // pid + tx are how we talk to the agent's own host; sel (composite) is
1549
1558
  // only for UI identity/highlight, since pids can collide across rooms.
1550
1559
  const pid = e.pid;
@@ -2256,7 +2265,23 @@
2256
2265
  }
2257
2266
  const full = pending ? null : /^([A-Za-z0-9_-]+):([^@]+)(?:@(.+))?$/.exec(h);
2258
2267
  const bare = /^([A-Za-z0-9_-]+)$/.exec(h);
2259
- if (full) {
2268
+ // Treat the colon form as #room:agentId only when there's no @host, the id
2269
+ // is a bare pid (digits), AND we can actually reconnect that room (it's
2270
+ // cached or local). Otherwise the second part must be the token we need to
2271
+ // connect — so a fresh browser still honours #room:token even when the token
2272
+ // is custom and happens to be all digits.
2273
+ // …and bounded to a plausible pid width (≤7 digits) so a long numeric
2274
+ // custom token can't be mistaken for an agent id. A returning user's room
2275
+ // token is persisted, so even a short numeric token reconnects fine from
2276
+ // cache; only the share-link form needs the token, which stays uneaten.
2277
+ const aidLike =
2278
+ full &&
2279
+ !full[3] &&
2280
+ /^\d{1,7}$/.test(full[2]) &&
2281
+ (full[1] === LOCAL || !!loadRooms()[full[1]]);
2282
+ if (full && !aidLike) {
2283
+ // #room:token — a share link. Connect, then eat the token so only #room
2284
+ // lingers.
2260
2285
  const [, room, token, host] = full;
2261
2286
  // SECURITY: strip the token from the URL immediately so it never lingers in
2262
2287
  // the omnibox, history, or a screenshot. Keep only the room mnemonic.
@@ -2266,6 +2291,16 @@
2266
2291
  location.pathname + location.search + "#" + room,
2267
2292
  );
2268
2293
  pending = { room, token, host };
2294
+ } else if (full) {
2295
+ // #room:agentId — a deep link to one agent in a (cached) room. The id is
2296
+ // not a secret, so it stays in the hash; reconnect the room from its
2297
+ // cached token and select the agent once it streams in. autoPid uses the
2298
+ // composite key so it picks the right host when pids collide across rooms.
2299
+ const [, room, aid] = full;
2300
+ autoPid = room + "#" + aid;
2301
+ autoPidExplicit = true;
2302
+ const r = loadRooms()[room];
2303
+ if (r) pending = { room, token: r.token, host: r.host };
2269
2304
  } else if (bare && loadRooms()[bare[1]]) {
2270
2305
  const r = loadRooms()[bare[1]];
2271
2306
  pending = { room: bare[1], token: r.token, host: r.host };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-yes",
3
- "version": "1.116.0",
3
+ "version": "1.118.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",
@@ -36,6 +36,11 @@ export interface GlobalPidRecord {
36
36
  exit_code: number | null;
37
37
  exit_reason: string | null;
38
38
  started_at: number;
39
+ // The `ay` wrapper process pid that spawned this agent. The wrapper injects
40
+ // its own pid as AGENT_YES_PID into the agent's env (the agent's OWN pid isn't
41
+ // known until after spawn), so this maps that env value back to the agent's
42
+ // canonical record — see resolveSender() in subcommands.ts.
43
+ wrapper_pid?: number | null;
39
44
  }
40
45
 
41
46
  /**
package/ts/index.ts CHANGED
@@ -374,7 +374,16 @@ export default async function agentYes({
374
374
 
375
375
  // Register process in pidStore (non-blocking - failures should not prevent agent from running)
376
376
  try {
377
- await pidStore.registerProcess({ pid: shell.pid, cli, args: cliArgs, prompt, cwd: workingDir });
377
+ await pidStore.registerProcess({
378
+ pid: shell.pid,
379
+ cli,
380
+ args: cliArgs,
381
+ prompt,
382
+ cwd: workingDir,
383
+ // We inject our own pid as AGENT_YES_PID into the agent's env above; record
384
+ // it so a child `ay send` can map that env value back to this agent.
385
+ wrapperPid: process.pid,
386
+ });
378
387
  } catch (error) {
379
388
  logger.warn(`[pidStore] Failed to register process ${shell.pid}:`, error);
380
389
  }
package/ts/pidStore.ts CHANGED
@@ -54,12 +54,14 @@ export class PidStore {
54
54
  args,
55
55
  prompt,
56
56
  cwd,
57
+ wrapperPid,
57
58
  }: {
58
59
  pid: number;
59
60
  cli: string;
60
61
  args: string[];
61
62
  prompt?: string;
62
63
  cwd: string;
64
+ wrapperPid?: number;
63
65
  }): Promise<PidRecord> {
64
66
  const now = Date.now();
65
67
  const argsJson = JSON.stringify(args);
@@ -113,6 +115,7 @@ export class PidStore {
113
115
  exit_code: null,
114
116
  exit_reason: null,
115
117
  started_at: now,
118
+ wrapper_pid: wrapperPid ?? null,
116
119
  })
117
120
  .then(() => maybeCompactGlobalPids())
118
121
  .catch(() => null);
package/ts/serve.ts CHANGED
@@ -65,6 +65,33 @@ const defaultOpts = (overrides: Partial<CommonOpts> = {}): CommonOpts => ({
65
65
  ...overrides,
66
66
  });
67
67
 
68
+ // The vars that pin a process to a PARENT Claude Code session — NOT the many
69
+ // other CLAUDE_CODE_* settings that configure provider/auth/limits (USE_BEDROCK,
70
+ // USE_VERTEX, MAX_OUTPUT_TOKENS, …), which must pass through untouched.
71
+ const SESSION_PIN_ENV = new Set([
72
+ "CLAUDECODE",
73
+ "CLAUDE_CODE_SSE_PORT",
74
+ "CLAUDE_CODE_SESSION_ID",
75
+ "CLAUDE_CODE_CHILD_SESSION",
76
+ "CLAUDE_CODE_ENTRYPOINT",
77
+ ]);
78
+
79
+ // Env for a console-spawned agent, minus only the session-pinning vars above. If
80
+ // `ay serve` was launched from inside Claude Code (or any shell carrying these),
81
+ // it would otherwise leak the parent's SSE port / session id into every spawned
82
+ // agent — so the new `claude` thinks it's a nested child and tries to attach to a
83
+ // stale port, surfacing as "fail to connect". Dropping them makes each agent a
84
+ // clean top-level session; all config/provider env (CLAUDE_EFFORT, CLAUDE_CODE_*
85
+ // settings) is preserved.
86
+ function freshAgentEnv(): Record<string, string> {
87
+ const env: Record<string, string> = {};
88
+ for (const [k, v] of Object.entries(process.env)) {
89
+ if (v === undefined || SESSION_PIN_ENV.has(k)) continue;
90
+ env[k] = v;
91
+ }
92
+ return env;
93
+ }
94
+
68
95
  // ---------------------------------------------------------------------------
69
96
  // ay serve install / uninstall / logs (oxmgr daemon management)
70
97
  // ---------------------------------------------------------------------------
@@ -860,6 +887,7 @@ export async function cmdServe(rest: string[]): Promise<number> {
860
887
  try {
861
888
  const child = Bun.spawn(["ay", cli, ...(prompt ? ["--", prompt] : [])], {
862
889
  cwd,
890
+ env: freshAgentEnv(), // don't leak our Claude Code session into the agent
863
891
  stdin: "ignore",
864
892
  stdout: "ignore",
865
893
  stderr: "ignore",
@@ -537,6 +537,13 @@ describe("subcommands.cmdSend writes bytes to FIFO", () => {
537
537
  stdout.push(String(s));
538
538
  return true;
539
539
  };
540
+ // Isolate from the send-safety guard: if the suite itself runs inside an
541
+ // ay-managed agent, AGENT_YES_PID would make cmdSend treat this as an agent
542
+ // sender (adding a "from …" prefix / blocking). Force-send to test pure
543
+ // byte delivery. --force keeps the agent prefix off only when no agent
544
+ // context resolves, so also clear AGENT_YES_PID for determinism.
545
+ const savedAyPid = process.env.AGENT_YES_PID;
546
+ delete process.env.AGENT_YES_PID;
540
547
  try {
541
548
  const code = await runSubcommand([
542
549
  "bun",
@@ -544,11 +551,13 @@ describe("subcommands.cmdSend writes bytes to FIFO", () => {
544
551
  "send",
545
552
  String(process.pid),
546
553
  "hello-fifo",
554
+ "--force",
547
555
  ]);
548
556
  expect(code).toBe(0);
549
557
  expect(stdout.join("")).toMatch(/sent to pid/);
550
558
  } finally {
551
559
  process.stdout.write = orig;
560
+ if (savedAyPid !== undefined) process.env.AGENT_YES_PID = savedAyPid;
552
561
  }
553
562
 
554
563
  // Now read the bytes back from our RDWR fd.
@@ -568,6 +577,47 @@ describe("subcommands.cmdSend writes bytes to FIFO", () => {
568
577
  });
569
578
  });
570
579
 
580
+ describe("subcommands.cmdSend safety guards", () => {
581
+ it("maps AGENT_YES_PID→wrapper_pid and blocks an agent from sending to itself", async () => {
582
+ const { runSubcommand } = await loadModule();
583
+ const { appendGlobalPid } = await import("./globalPidIndex.ts");
584
+ // We register an agent whose wrapper_pid is a known value, then run `ay send`
585
+ // with AGENT_YES_PID set to that wrapper — so resolveSender maps it back to
586
+ // this same agent, and sending to its own pid trips the self-send guard.
587
+ const wrapperPid = 424242;
588
+ await appendGlobalPid({
589
+ pid: process.pid,
590
+ cli: "claude",
591
+ prompt: null,
592
+ cwd: process.cwd(),
593
+ log_file: null,
594
+ fifo_file: "/tmp/ay-guard-test.fifo",
595
+ status: "active",
596
+ exit_code: null,
597
+ exit_reason: null,
598
+ started_at: Date.now(),
599
+ wrapper_pid: wrapperPid,
600
+ });
601
+ const stderr: string[] = [];
602
+ const orig = process.stderr.write.bind(process.stderr);
603
+ (process.stderr as any).write = (s: any) => {
604
+ stderr.push(String(s));
605
+ return true;
606
+ };
607
+ const savedAyPid = process.env.AGENT_YES_PID;
608
+ process.env.AGENT_YES_PID = String(wrapperPid);
609
+ try {
610
+ const code = await runSubcommand(["bun", "cli.js", "send", String(process.pid), "loop?"]);
611
+ expect(code).toBe(1);
612
+ expect(stderr.join("")).toMatch(/refusing to send to yourself/);
613
+ } finally {
614
+ process.stderr.write = orig;
615
+ if (savedAyPid === undefined) delete process.env.AGENT_YES_PID;
616
+ else process.env.AGENT_YES_PID = savedAyPid;
617
+ }
618
+ });
619
+ });
620
+
571
621
  // ---------------------------------------------------------------------------
572
622
  // cmdLs additional arg coverage
573
623
  // ---------------------------------------------------------------------------
package/ts/subcommands.ts CHANGED
Binary file
@@ -1,8 +0,0 @@
1
- import "./ts-DvnOmOAf.js";
2
- import "./logger-B9h0djqx.js";
3
- import "./versionChecker-DeXuTfJ0.js";
4
- import "./pidStore-DBjlqzo8.js";
5
- import "./globalPidIndex-yVd3mbsV.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DuCEAzP9.js";
7
-
8
- export { SUPPORTED_CLIS };
@@ -1,5 +0,0 @@
1
- import "./logger-B9h0djqx.js";
2
- import { t as PidStore } from "./pidStore-DBjlqzo8.js";
3
- import "./globalPidIndex-yVd3mbsV.js";
4
-
5
- export { PidStore };