agent-yes 1.117.0 → 1.118.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,8 @@
1
+ import "./ts-DSvAXKGk.js";
2
+ import "./logger-B9h0djqx.js";
3
+ import "./versionChecker-C40LF9-T.js";
4
+ import "./pidStore-B5vBu8Px.js";
5
+ import "./globalPidIndex-gZuTvTBs.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-FpD7Ea6s.js";
7
+
8
+ export { SUPPORTED_CLIS };
@@ -1,8 +1,8 @@
1
- import { t as CLIS_CONFIG } from "./ts-B4lhxCQx.js";
1
+ import { t as CLIS_CONFIG } from "./ts-DSvAXKGk.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-DQYx5cvl.js.map
8
+ //# sourceMappingURL=SUPPORTED_CLIS-FpD7Ea6s.js.map
@@ -288,4 +288,4 @@ async function getDefaultConfig() {
288
288
 
289
289
  //#endregion
290
290
  export { agent_yes_config_default as default };
291
- //# sourceMappingURL=agent-yes.config-1LMoK18R.js.map
291
+ //# sourceMappingURL=agent-yes.config-kmtJKJHk.js.map
@@ -0,0 +1,25 @@
1
+ import { homedir } from "os";
2
+ import path from "path";
3
+
4
+ //#region ts/agentYesHome.ts
5
+ /**
6
+ * Root directory for cross-runtime, machine-global agent-yes state:
7
+ * the pid index (`pids.jsonl`), FIFO/named-pipe IPC endpoints (`fifo/`),
8
+ * winsize signals, notes, and the serve token.
9
+ *
10
+ * Durable per-session *logs* deliberately do NOT live here — they go under
11
+ * `<cwd>/.agent-yes/` so they stay colocated with the project that produced
12
+ * them (see `PidStore`). Only ephemeral IPC + the discovery index are global,
13
+ * which keeps FIFOs on the local home filesystem (reliable `mkfifo`) and lets
14
+ * `ay ls`/`ay attach` find every agent regardless of the caller's cwd.
15
+ *
16
+ * Resolved at call time (not module load) so tests and callers can override
17
+ * via `$AGENT_YES_HOME` without juggling the module cache.
18
+ */
19
+ function agentYesHome() {
20
+ return process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
21
+ }
22
+
23
+ //#endregion
24
+ export { agentYesHome as t };
25
+ //# sourceMappingURL=agentYesHome-BvaUOzCV.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-CS7qsffQ.js";
3
+ import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-C40LF9-T.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-BuLieGot.js");
485
+ const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-Ca1GYIB2.js");
486
486
  if (isHelpFlag && process.argv.length === 3) {
487
487
  cmdHelp();
488
488
  process.exit(0);
@@ -496,12 +496,12 @@ await checkAndAutoUpdate();
496
496
  logger.info(versionString());
497
497
  const config = parseCliArgs(process.argv);
498
498
  if (config.tray) {
499
- const { startTray } = await import("./tray-CWQe9DMY.js");
499
+ const { startTray } = await import("./tray-B8_rx1iu.js");
500
500
  await startTray();
501
501
  await new Promise(() => {});
502
502
  }
503
503
  {
504
- const { ensureTray } = await import("./tray-CWQe9DMY.js");
504
+ const { ensureTray } = await import("./tray-B8_rx1iu.js");
505
505
  ensureTray();
506
506
  }
507
507
  if (config.useRust) {
@@ -515,7 +515,7 @@ if (config.useRust) {
515
515
  }
516
516
  }
517
517
  if (rustBinary) {
518
- const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-DK9PO6Y6.js");
518
+ const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-Bx3xCmVR.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-7y1cTcAE.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.");
@@ -189,4 +189,4 @@ async function pruneOldLogs(maxAgeMs = retentionMs()) {
189
189
 
190
190
  //#endregion
191
191
  export { updateGlobalPidStatus as a, readGlobalPids as i, maybeCompactGlobalPids as n, pruneOldLogs as r, appendGlobalPid as t };
192
- //# sourceMappingURL=globalPidIndex-yVd3mbsV.js.map
192
+ //# sourceMappingURL=globalPidIndex-gZuTvTBs.js.map
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-B4lhxCQx.js";
1
+ import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-DSvAXKGk.js";
2
2
  import "./logger-B9h0djqx.js";
3
- import "./versionChecker-CS7qsffQ.js";
4
- import "./pidStore-DBjlqzo8.js";
5
- import "./globalPidIndex-yVd3mbsV.js";
3
+ import "./versionChecker-C40LF9-T.js";
4
+ import "./pidStore-B5vBu8Px.js";
5
+ import "./globalPidIndex-gZuTvTBs.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-B5vBu8Px.js";
3
+ import "./globalPidIndex-gZuTvTBs.js";
4
+
5
+ export { PidStore };
@@ -1,31 +1,11 @@
1
1
  import { n as logger } from "./logger-B9h0djqx.js";
2
- import { a as updateGlobalPidStatus, n as maybeCompactGlobalPids, r as pruneOldLogs, t as appendGlobalPid } from "./globalPidIndex-yVd3mbsV.js";
2
+ import { t as agentYesHome } from "./agentYesHome-BvaUOzCV.js";
3
+ import { a as updateGlobalPidStatus, n as maybeCompactGlobalPids, r as pruneOldLogs, t as appendGlobalPid } from "./globalPidIndex-gZuTvTBs.js";
3
4
  import { closeSync, existsSync, fsyncSync, openSync } from "fs";
4
5
  import { appendFile, mkdir, readFile, rename, writeFile } from "fs/promises";
5
- import { homedir } from "os";
6
6
  import path from "path";
7
7
  import { lock } from "proper-lockfile";
8
8
 
9
- //#region ts/agentYesHome.ts
10
- /**
11
- * Root directory for cross-runtime, machine-global agent-yes state:
12
- * the pid index (`pids.jsonl`), FIFO/named-pipe IPC endpoints (`fifo/`),
13
- * winsize signals, notes, and the serve token.
14
- *
15
- * Durable per-session *logs* deliberately do NOT live here — they go under
16
- * `<cwd>/.agent-yes/` so they stay colocated with the project that produced
17
- * them (see `PidStore`). Only ephemeral IPC + the discovery index are global,
18
- * which keeps FIFOs on the local home filesystem (reliable `mkfifo`) and lets
19
- * `ay ls`/`ay attach` find every agent regardless of the caller's cwd.
20
- *
21
- * Resolved at call time (not module load) so tests and callers can override
22
- * via `$AGENT_YES_HOME` without juggling the module cache.
23
- */
24
- function agentYesHome() {
25
- return process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
26
- }
27
-
28
- //#endregion
29
9
  //#region ts/JsonlStore.ts
30
10
  /**
31
11
  * A lightweight NeDB-style JSONL persistence layer.
@@ -223,7 +203,7 @@ var PidStore = class PidStore {
223
203
  logger.warn("[pidStore] Failed to initialize:", error);
224
204
  }
225
205
  }
226
- async registerProcess({ pid, cli, args, prompt, cwd }) {
206
+ async registerProcess({ pid, cli, args, prompt, cwd, wrapperPid }) {
227
207
  const now = Date.now();
228
208
  const argsJson = JSON.stringify(args);
229
209
  const logFile = this.getRawLogPath(pid);
@@ -260,7 +240,8 @@ var PidStore = class PidStore {
260
240
  status: "active",
261
241
  exit_code: null,
262
242
  exit_reason: null,
263
- started_at: now
243
+ started_at: now,
244
+ wrapper_pid: wrapperPid ?? null
264
245
  }).then(() => maybeCompactGlobalPids()).catch(() => null);
265
246
  return result;
266
247
  }
@@ -380,5 +361,5 @@ pid-db/
380
361
  };
381
362
 
382
363
  //#endregion
383
- export { agentYesHome as n, PidStore as t };
384
- //# sourceMappingURL=pidStore-DBjlqzo8.js.map
364
+ export { PidStore as t };
365
+ //# sourceMappingURL=pidStore-B5vBu8Px.js.map
@@ -147,4 +147,4 @@ async function cmdRemote(rest) {
147
147
 
148
148
  //#endregion
149
149
  export { resolveRemoteSpec as a, readRemotes as i, deleteRemoteAlias as n, writeRemoteAlias as o, parseDirectRemoteSpec as r, cmdRemote as t };
150
- //# sourceMappingURL=remotes-C3xPRtfg.js.map
150
+ //# sourceMappingURL=remotes-BufkGk0e.js.map
@@ -1,3 +1,3 @@
1
- import { a as resolveRemoteSpec, i as readRemotes, n as deleteRemoteAlias, o as writeRemoteAlias, r as parseDirectRemoteSpec, t as cmdRemote } from "./remotes-C3xPRtfg.js";
1
+ import { a as resolveRemoteSpec, i as readRemotes, n as deleteRemoteAlias, o as writeRemoteAlias, r as parseDirectRemoteSpec, t as cmdRemote } from "./remotes-BufkGk0e.js";
2
2
 
3
3
  export { cmdRemote };
@@ -1,11 +1,11 @@
1
- import "./ts-B4lhxCQx.js";
1
+ import "./ts-DSvAXKGk.js";
2
2
  import "./logger-B9h0djqx.js";
3
- import { r as getInstalledPackage } from "./versionChecker-CS7qsffQ.js";
4
- import "./pidStore-DBjlqzo8.js";
5
- import "./globalPidIndex-yVd3mbsV.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DQYx5cvl.js";
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-B_JJRHkV.js";
3
+ import { r as getInstalledPackage } from "./versionChecker-C40LF9-T.js";
4
+ import "./pidStore-B5vBu8Px.js";
5
+ import "./globalPidIndex-gZuTvTBs.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-FpD7Ea6s.js";
7
+ import "./remotes-BufkGk0e.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-3oALzYZW.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";
@@ -758,7 +758,7 @@ Options:
758
758
  const webrtcVal = argv.webrtc ?? argv.share;
759
759
  const explicitUrl = typeof webrtcVal === "string" && webrtcVal.startsWith("webrtc://") ? webrtcVal : void 0;
760
760
  try {
761
- const { startShare, loadOrCreateShareRoom } = await import("./share-DwzKXEsJ.js");
761
+ const { startShare, loadOrCreateShareRoom } = await import("./share-BM-H85FE.js");
762
762
  const { link, close } = await startShare({
763
763
  url: explicitUrl ?? await loadOrCreateShareRoom(),
764
764
  localFetch: apiFetch,
@@ -789,4 +789,4 @@ Options:
789
789
 
790
790
  //#endregion
791
791
  export { cmdServe };
792
- //# sourceMappingURL=serve-DJthZQAN.js.map
792
+ //# sourceMappingURL=serve-CBvx2LeV.js.map
@@ -0,0 +1,82 @@
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";
6
+ import { stdin, stdout } from "node:process";
7
+ import { createInterface } from "node:readline/promises";
8
+
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
+ //#region ts/setup.ts
44
+ async function cmdSetup(rest) {
45
+ if (rest.includes("-h") || rest.includes("--help")) {
46
+ process.stdout.write("Usage: ay setup [workspace-dir] [--no-share] [--port N]\n\nGuided setup:\n 1. pick the workspace root new agents spawn into (default: your home dir)\n 2. share this machine to the agent-yes.com console (a restart-on-boot daemon)\n\nOptions:\n workspace-dir default directory for new agents (skips the prompt)\n --no-share set the workspace only; don't install the share daemon\n --port N HTTP API port for the share daemon (default: 7432)\n");
47
+ return 0;
48
+ }
49
+ const noShare = rest.includes("--no-share");
50
+ const portIdx = rest.indexOf("--port");
51
+ const port = portIdx >= 0 ? rest[portIdx + 1] : void 0;
52
+ let ws = rest.filter((a, i) => !a.startsWith("-") && i !== portIdx + 1)[0];
53
+ if (!ws) {
54
+ const current = getWorkspaceRoot();
55
+ if (stdin.isTTY && stdout.isTTY) {
56
+ const rl = createInterface({
57
+ input: stdin,
58
+ output: stdout
59
+ });
60
+ try {
61
+ ws = (await rl.question(`Workspace root for new agents [${current}]: `)).trim() || current;
62
+ } finally {
63
+ rl.close();
64
+ }
65
+ } else ws = current;
66
+ }
67
+ const abs = setWorkspaceRoot(ws);
68
+ 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`);
70
+ if (noShare) return 0;
71
+ process.stdout.write(`\nsharing this machine to agent-yes.com…\n`);
72
+ const { cmdServe } = await import("./serve-CBvx2LeV.js");
73
+ return cmdServe([
74
+ "install",
75
+ "--share",
76
+ ...port ? ["--port", port] : []
77
+ ]);
78
+ }
79
+
80
+ //#endregion
81
+ export { cmdSetup };
82
+ //# sourceMappingURL=setup-C7BNSEaG.js.map
@@ -215,4 +215,4 @@ async function startShare(opts) {
215
215
 
216
216
  //#endregion
217
217
  export { loadOrCreateShareRoom, startShare };
218
- //# sourceMappingURL=share-DwzKXEsJ.js.map
218
+ //# sourceMappingURL=share-BM-H85FE.js.map
@@ -1,5 +1,5 @@
1
- import { i as readGlobalPids } from "./globalPidIndex-yVd3mbsV.js";
2
- import { a as resolveRemoteSpec, i as readRemotes } from "./remotes-C3xPRtfg.js";
1
+ import { i as readGlobalPids } from "./globalPidIndex-gZuTvTBs.js";
2
+ import { a as resolveRemoteSpec, i as readRemotes } from "./remotes-BufkGk0e.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";
@@ -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,15 +231,15 @@ 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-DJthZQAN.js");
234
+ const { cmdServe } = await import("./serve-CBvx2LeV.js");
167
235
  return cmdServe(rest);
168
236
  }
169
237
  case "setup": {
170
- const { cmdSetup } = await import("./setup.ts");
238
+ const { cmdSetup } = await import("./setup-C7BNSEaG.js");
171
239
  return cmdSetup(rest);
172
240
  }
173
241
  case "remote": {
174
- const { cmdRemote } = await import("./remotes-C9WMt5PY.js");
242
+ const { cmdRemote } = await import("./remotes-DavR4Hca.js");
175
243
  return cmdRemote(rest);
176
244
  }
177
245
  case "help": return cmdHelp();
@@ -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-B_JJRHkV.js.map
1683
+ //# sourceMappingURL=subcommands-3oALzYZW.js.map
@@ -1,6 +1,6 @@
1
1
  import "./logger-B9h0djqx.js";
2
- import "./globalPidIndex-yVd3mbsV.js";
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-B_JJRHkV.js";
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-3oALzYZW.js";
5
5
 
6
6
  export { cmdHelp, isSubcommand, runSubcommand };
@@ -175,4 +175,4 @@ async function startTray() {
175
175
 
176
176
  //#endregion
177
177
  export { ensureTray, startTray };
178
- //# sourceMappingURL=tray-CWQe9DMY.js.map
178
+ //# sourceMappingURL=tray-B8_rx1iu.js.map
@@ -1,8 +1,9 @@
1
1
  import { n as logger, t as addTransport } from "./logger-B9h0djqx.js";
2
- import { r as getInstalledPackage } from "./versionChecker-CS7qsffQ.js";
3
- import { n as agentYesHome, t as PidStore } from "./pidStore-DBjlqzo8.js";
2
+ import { r as getInstalledPackage } from "./versionChecker-C40LF9-T.js";
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
- import { i as readGlobalPids } from "./globalPidIndex-yVd3mbsV.js";
5
+ import { t as PidStore } from "./pidStore-B5vBu8Px.js";
6
+ import { i as readGlobalPids } from "./globalPidIndex-gZuTvTBs.js";
6
7
  import { arch, platform } from "process";
7
8
  import { execSync } from "child_process";
8
9
  import { closeSync, constants, createReadStream, existsSync, mkdirSync, openSync } from "fs";
@@ -1039,7 +1040,7 @@ async function notifyWebhook(status, details, cwd = process.cwd()) {
1039
1040
 
1040
1041
  //#endregion
1041
1042
  //#region ts/index.ts
1042
- const config = await import("./agent-yes.config-1LMoK18R.js").then((mod) => mod.default || mod);
1043
+ const config = await import("./agent-yes.config-kmtJKJHk.js").then((mod) => mod.default || mod);
1043
1044
  const CLIS_CONFIG = config.clis;
1044
1045
  /**
1045
1046
  * Main function to run agent-cli with automatic yes/no responses
@@ -1218,7 +1219,8 @@ async function agentYes({ cli, cliArgs = [], prompt, robust = true, cwd, env, ex
1218
1219
  cli,
1219
1220
  args: cliArgs,
1220
1221
  prompt,
1221
- cwd: workingDir
1222
+ cwd: workingDir,
1223
+ wrapperPid: process.pid
1222
1224
  });
1223
1225
  } catch (error) {
1224
1226
  logger.warn(`[pidStore] Failed to register process ${shell.pid}:`, error);
@@ -1713,4 +1715,4 @@ function sleep(ms) {
1713
1715
 
1714
1716
  //#endregion
1715
1717
  export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
1716
- //# sourceMappingURL=ts-B4lhxCQx.js.map
1718
+ //# sourceMappingURL=ts-DSvAXKGk.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.117.0";
10
+ var version = "1.118.1";
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-CS7qsffQ.js.map
224
+ //# sourceMappingURL=versionChecker-C40LF9-T.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-yes",
3
- "version": "1.117.0",
3
+ "version": "1.118.1",
4
4
  "description": "A wrapper tool that automates interactions with various AI CLI tools by automatically handling common prompts and responses.",
5
5
  "keywords": [
6
6
  "ai",
@@ -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);
@@ -0,0 +1,42 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
+ import { mkdtempSync, rmSync } from "fs";
3
+ import { tmpdir } from "os";
4
+ import path from "path";
5
+ import { cmdSetup } from "./setup.ts";
6
+ import { getWorkspaceRoot } from "./workspaceConfig.ts";
7
+
8
+ // Guards against the regression where `ay setup` was registered + documented but
9
+ // its module was missing ("Cannot find module './setup.ts'"). Exercises the
10
+ // --no-share path only, so no daemon is installed.
11
+ describe("cmdSetup", () => {
12
+ let original: string | undefined;
13
+ let tmp: string;
14
+ beforeEach(() => {
15
+ original = process.env.AGENT_YES_HOME;
16
+ tmp = mkdtempSync(path.join(tmpdir(), "ay-setup-"));
17
+ process.env.AGENT_YES_HOME = tmp;
18
+ });
19
+ afterEach(() => {
20
+ if (original === undefined) delete process.env.AGENT_YES_HOME;
21
+ else process.env.AGENT_YES_HOME = original;
22
+ rmSync(tmp, { recursive: true, force: true });
23
+ });
24
+
25
+ it("--help returns 0 without touching config", async () => {
26
+ expect(await cmdSetup(["--help"])).toBe(0);
27
+ });
28
+
29
+ it("--no-share sets the workspace root and skips the daemon", async () => {
30
+ const dir = path.join(tmp, "myspace");
31
+ const code = await cmdSetup(["--no-share", dir]);
32
+ expect(code).toBe(0);
33
+ expect(getWorkspaceRoot()).toBe(path.resolve(dir));
34
+ });
35
+
36
+ it("--no-share with a --port flag still treats the path as the workspace", async () => {
37
+ const dir = path.join(tmp, "ws");
38
+ const code = await cmdSetup(["--no-share", "--port", "7440", dir]);
39
+ expect(code).toBe(0);
40
+ expect(getWorkspaceRoot()).toBe(path.resolve(dir));
41
+ });
42
+ });
package/ts/setup.ts ADDED
@@ -0,0 +1,67 @@
1
+ import { existsSync } from "node:fs";
2
+ import { stdin, stdout } from "node:process";
3
+ import { createInterface } from "node:readline/promises";
4
+ import { getWorkspaceRoot, setWorkspaceRoot } from "./workspaceConfig.ts";
5
+
6
+ // `ay setup` — guided onboarding. Two steps:
7
+ // 1. choose the workspace root (the default cwd new agents spawn into when the
8
+ // console doesn't pass one — see workspaceConfig), and
9
+ // 2. share this machine to the agent-yes.com console over WebRTC, installed as a
10
+ // restart-on-boot daemon (delegates to `ay serve install --share`).
11
+ //
12
+ // Designed to degrade gracefully: with a TTY it prompts (defaulting to the
13
+ // current/home workspace); piped or in a script it takes the first positional as
14
+ // the workspace and otherwise keeps the current one — never blocking on input.
15
+ export async function cmdSetup(rest: string[]): Promise<number> {
16
+ if (rest.includes("-h") || rest.includes("--help")) {
17
+ process.stdout.write(
18
+ `Usage: ay setup [workspace-dir] [--no-share] [--port N]\n\n` +
19
+ `Guided setup:\n` +
20
+ ` 1. pick the workspace root new agents spawn into (default: your home dir)\n` +
21
+ ` 2. share this machine to the agent-yes.com console (a restart-on-boot daemon)\n\n` +
22
+ `Options:\n` +
23
+ ` workspace-dir default directory for new agents (skips the prompt)\n` +
24
+ ` --no-share set the workspace only; don't install the share daemon\n` +
25
+ ` --port N HTTP API port for the share daemon (default: 7432)\n`,
26
+ );
27
+ return 0;
28
+ }
29
+
30
+ const noShare = rest.includes("--no-share");
31
+ const portIdx = rest.indexOf("--port");
32
+ const port = portIdx >= 0 ? rest[portIdx + 1] : undefined;
33
+ // The workspace is the first non-flag token (and not the value of --port).
34
+ const positional = rest.filter((a, i) => !a.startsWith("-") && i !== portIdx + 1);
35
+
36
+ // 1. Workspace root.
37
+ let ws = positional[0];
38
+ if (!ws) {
39
+ const current = getWorkspaceRoot();
40
+ if (stdin.isTTY && stdout.isTTY) {
41
+ const rl = createInterface({ input: stdin, output: stdout });
42
+ try {
43
+ const ans = (await rl.question(`Workspace root for new agents [${current}]: `)).trim();
44
+ ws = ans || current;
45
+ } finally {
46
+ rl.close();
47
+ }
48
+ } else {
49
+ ws = current; // non-interactive: keep whatever's configured (home by default)
50
+ }
51
+ }
52
+ const abs = setWorkspaceRoot(ws);
53
+ process.stdout.write(`workspace root: ${abs}\n`);
54
+ if (!existsSync(abs)) {
55
+ process.stderr.write(
56
+ ` note: that directory doesn't exist yet — create it, or agents spawned there will fail\n`,
57
+ );
58
+ }
59
+
60
+ if (noShare) return 0;
61
+
62
+ // 2. Share to agent-yes.com as a boot-persistent daemon. `ay serve install`
63
+ // handles oxmgr registration, version roll-forward, and printing the link.
64
+ process.stdout.write(`\nsharing this machine to agent-yes.com…\n`);
65
+ const { cmdServe } = await import("./serve.ts");
66
+ return cmdServe(["install", "--share", ...(port ? ["--port", port] : [])]);
67
+ }
@@ -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-B4lhxCQx.js";
2
- import "./logger-B9h0djqx.js";
3
- import "./versionChecker-CS7qsffQ.js";
4
- import "./pidStore-DBjlqzo8.js";
5
- import "./globalPidIndex-yVd3mbsV.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DQYx5cvl.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 };