claude-yes 1.161.0 → 1.164.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.
@@ -1,8 +1,8 @@
1
- import { t as CLIS_CONFIG } from "./ts-CRo3qQWb.js";
1
+ import { t as CLIS_CONFIG } from "./ts-BzyVSzo1.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-DYTnLX8C.js.map
8
+ //# sourceMappingURL=SUPPORTED_CLIS-C_J_okdw.js.map
@@ -0,0 +1,8 @@
1
+ import "./ts-BzyVSzo1.js";
2
+ import "./logger-CDIsZ-Pp.js";
3
+ import "./versionChecker-CCHuozmQ.js";
4
+ import "./pidStore-BfoBWUjc.js";
5
+ import "./globalPidIndex-DlmmJlO8.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-C_J_okdw.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-DrjR4P0K.js";
4
+ import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-CCHuozmQ.js";
5
5
  import { argv } from "process";
6
6
  import { execFileSync, spawn } from "child_process";
7
7
  import ms from "ms";
@@ -481,7 +481,7 @@ 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-5AXvxB0S.js");
484
+ const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-C-aCmtre.js");
485
485
  if (isHelpFlag && process.argv.length === 3) {
486
486
  cmdHelp(managerCommands);
487
487
  process.exit(0);
@@ -514,7 +514,7 @@ if (config.useRust) {
514
514
  }
515
515
  }
516
516
  if (rustBinary) {
517
- const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-CHmQfHCh.js");
517
+ const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-CmJam_wA.js");
518
518
  const rustArgs = buildRustArgs(process.argv, config.cli, SUPPORTED_CLIS);
519
519
  if (config.verbose) {
520
520
  console.log(`[rust] Using binary: ${rustBinary}`);
@@ -544,7 +544,7 @@ if (config.showVersion) {
544
544
  process.exit(0);
545
545
  }
546
546
  if (config.appendPrompt) {
547
- const { PidStore } = await import("./pidStore-DfHh3Yt0.js");
547
+ const { PidStore } = await import("./pidStore-5qyLX3Fh.js");
548
548
  const ipcPath = await PidStore.findActiveFifo(process.cwd());
549
549
  if (!ipcPath) {
550
550
  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-CRo3qQWb.js";
1
+ import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-BzyVSzo1.js";
2
2
  import "./logger-CDIsZ-Pp.js";
3
- import "./versionChecker-DrjR4P0K.js";
4
- import "./pidStore-fqXqTKkh.js";
3
+ import "./versionChecker-CCHuozmQ.js";
4
+ import "./pidStore-BfoBWUjc.js";
5
5
  import "./globalPidIndex-DlmmJlO8.js";
6
6
 
7
7
  export { AgentContext, CLIS_CONFIG, config, agentYes as default, removeControlCharacters };
@@ -0,0 +1,5 @@
1
+ import "./logger-CDIsZ-Pp.js";
2
+ import { t as PidStore } from "./pidStore-BfoBWUjc.js";
3
+ import "./globalPidIndex-DlmmJlO8.js";
4
+
5
+ export { PidStore };
@@ -209,8 +209,9 @@ var PidStore = class PidStore {
209
209
  const argsJson = JSON.stringify(args);
210
210
  const logFile = this.getRawLogPath(pid);
211
211
  const fifoFile = this.getFifoPath(pid);
212
+ const injected = process.env.AGENT_YES_AGENT_ID;
212
213
  const existing = this.store.findOne((doc) => doc.pid === pid);
213
- const agentId = existing?.agentId ?? randomBytes(6).toString("hex");
214
+ const agentId = existing?.agentId ?? (injected && /^[0-9a-f]{6,32}$/i.test(injected) ? injected : randomBytes(6).toString("hex"));
214
215
  const record = {
215
216
  pid,
216
217
  cli,
@@ -367,4 +368,4 @@ pid-db/
367
368
 
368
369
  //#endregion
369
370
  export { PidStore as t };
370
- //# sourceMappingURL=pidStore-fqXqTKkh.js.map
371
+ //# sourceMappingURL=pidStore-BfoBWUjc.js.map
@@ -1,9 +1,9 @@
1
- import "./ts-CRo3qQWb.js";
1
+ import "./ts-BzyVSzo1.js";
2
2
  import "./logger-CDIsZ-Pp.js";
3
- import "./versionChecker-DrjR4P0K.js";
4
- import "./pidStore-fqXqTKkh.js";
3
+ import "./versionChecker-CCHuozmQ.js";
4
+ import "./pidStore-BfoBWUjc.js";
5
5
  import "./globalPidIndex-DlmmJlO8.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DYTnLX8C.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-C_J_okdw.js";
7
7
  import { o as resolveSpawnCwd } from "./workspaceConfig-B0Q9-q2B.js";
8
8
  import { createHash } from "node:crypto";
9
9
 
@@ -141,4 +141,4 @@ async function cmdSchedule(rest) {
141
141
 
142
142
  //#endregion
143
143
  export { cmdSchedule };
144
- //# sourceMappingURL=schedule-CCvB7SCT.js.map
144
+ //# sourceMappingURL=schedule-B-Ncgklt.js.map
@@ -1,16 +1,16 @@
1
- import "./ts-CRo3qQWb.js";
1
+ import "./ts-BzyVSzo1.js";
2
2
  import "./logger-CDIsZ-Pp.js";
3
- import { r as getInstalledPackage } from "./versionChecker-DrjR4P0K.js";
4
- import "./pidStore-fqXqTKkh.js";
3
+ import { r as getInstalledPackage } from "./versionChecker-CCHuozmQ.js";
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-DYTnLX8C.js";
8
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-C_J_okdw.js";
9
9
  import "./e2e-Bfw7qL9O.js";
10
10
  import "./webrtcLink-CBZkZ-LT.js";
11
11
  import "./remotes-DBCvpp3B.js";
12
12
  import { a as isProvisionAllowed, i as hasSpawnHook, n as getSpawnHook, o as resolveSpawnCwd, t as getProvisionRoot } from "./workspaceConfig-B0Q9-q2B.js";
13
- import { D as writeToIpc, T as snapshotStatus, _ as readPtysize, b as resolveOne, c as extractTaskCounts, g as readNotes, i as controlCodeFromName, m as listRecords, o as deriveLiveStatus, v as renderRawLog } from "./subcommands-DGkBxWS5.js";
13
+ import { D as writeToIpc, T as snapshotStatus, _ as readPtysize, b as resolveOne, c as extractTaskCounts, g as readNotes, i as controlCodeFromName, m as listRecords, o as deriveLiveStatus, s as extractNeedsInput, v as renderRawLog } from "./subcommands-BbKngO7F.js";
14
14
  import yargs from "yargs";
15
15
  import { mkdir, open, readFile, stat, unlink, writeFile } from "fs/promises";
16
16
  import { homedir, hostname, userInfo } from "os";
@@ -625,6 +625,24 @@ Options:
625
625
  return null;
626
626
  }
627
627
  };
628
+ const niCache = /* @__PURE__ */ new Map();
629
+ const logNeedsInput = async (logFile, cli) => {
630
+ if (!logFile) return null;
631
+ try {
632
+ const { size, mtimeMs } = await stat(logFile);
633
+ const hit = niCache.get(logFile);
634
+ if (hit && hit.size === size && hit.mtimeMs === mtimeMs) return hit.question;
635
+ const question = (await extractNeedsInput(logFile, cli))?.question ?? null;
636
+ niCache.set(logFile, {
637
+ size,
638
+ mtimeMs,
639
+ question
640
+ });
641
+ return question;
642
+ } catch {
643
+ return null;
644
+ }
645
+ };
628
646
  const GIT_DEBOUNCE_MS = 800;
629
647
  const GIT_SAFETY_MS = 6e4;
630
648
  const runGit = async (args, cwd) => {
@@ -643,17 +661,43 @@ Options:
643
661
  }
644
662
  };
645
663
  const parseGitStatus = (out) => {
646
- const lines = out.split("\n");
647
- const h = /^## (.+)$/.exec(lines[0] ?? "")?.[1] ?? "";
648
- const unborn = /^No commits yet on (.+)$/.exec(h);
649
- const branch = unborn ? unborn[1] : /^(.+?)(?:\.\.\.|\s|$)/.exec(h)?.[1] || null;
650
- const ahead = Number(/\bahead (\d+)/.exec(h)?.[1] ?? 0);
651
- const behind = Number(/\bbehind (\d+)/.exec(h)?.[1] ?? 0);
652
- const changed = lines.slice(1).filter((l) => l.trim().length > 0).length;
664
+ let branch = null;
665
+ let ahead = 0;
666
+ let behind = 0;
667
+ let changed = 0;
668
+ let pins = 0;
669
+ let subDirty = 0;
670
+ for (const line of out.split("\n")) {
671
+ if (line.length === 0) continue;
672
+ if (line[0] === "#") {
673
+ const head = /^# branch\.head (.+)$/.exec(line);
674
+ if (head) {
675
+ branch = head[1] === "(detached)" ? null : head[1];
676
+ continue;
677
+ }
678
+ const ab = /^# branch\.ab \+(\d+) -(\d+)/.exec(line);
679
+ if (ab) {
680
+ ahead = Number(ab[1]);
681
+ behind = Number(ab[2]);
682
+ }
683
+ continue;
684
+ }
685
+ const type = line[0];
686
+ if (type === "?") changed++;
687
+ else if (type === "u") changed++;
688
+ else if (type === "1" || type === "2") {
689
+ const sub = line.split(" ")[2] ?? "N...";
690
+ if (sub[0] === "S") if (sub[1] === "C") pins++;
691
+ else subDirty++;
692
+ else changed++;
693
+ }
694
+ }
653
695
  return {
654
696
  branch,
655
697
  dirty: changed > 0,
656
698
  changed,
699
+ pins,
700
+ subDirty,
657
701
  ahead,
658
702
  behind
659
703
  };
@@ -676,7 +720,7 @@ Options:
676
720
  try {
677
721
  const out = await runGit([
678
722
  "status",
679
- "--porcelain",
723
+ "--porcelain=v2",
680
724
  "--branch"
681
725
  ], root);
682
726
  if (out != null) rw.val = parseGitStatus(out);
@@ -717,9 +761,11 @@ Options:
717
761
  };
718
762
  const withMeta = async (r) => {
719
763
  const status = await deriveLiveStatus(r);
764
+ const question = status !== "exited" && !r.unresponsive ? await logNeedsInput(r.log_file, r.cli) : null;
720
765
  return {
721
766
  ...r,
722
- status: status !== "exited" && r.unresponsive ? "stuck" : status,
767
+ status: status === "exited" ? status : r.unresponsive ? "stuck" : question ? "needs_input" : status,
768
+ question,
723
769
  title: await logTitle(r.log_file),
724
770
  git: status === "exited" ? null : await gitStatus(r.cwd),
725
771
  tasks: status === "exited" ? null : await logTasks(r.log_file)
@@ -1200,6 +1246,7 @@ Options:
1200
1246
  ...prompt ? ["--", prompt] : []
1201
1247
  ];
1202
1248
  const agentEnv = freshAgentEnv();
1249
+ const agentId = randomBytes(6).toString("hex");
1203
1250
  try {
1204
1251
  const hook = getSpawnHook();
1205
1252
  if (hook) {
@@ -1218,7 +1265,8 @@ Options:
1218
1265
  env: {
1219
1266
  ...agentEnv,
1220
1267
  AGENT_YES_CWD: cwd,
1221
- AGENT_YES_CLI: cli
1268
+ AGENT_YES_CLI: cli,
1269
+ AGENT_YES_AGENT_ID: agentId
1222
1270
  },
1223
1271
  stdin: "ignore",
1224
1272
  stdout: "ignore",
@@ -1239,6 +1287,7 @@ Options:
1239
1287
  return Response.json({
1240
1288
  ok: true,
1241
1289
  pid: child.pid,
1290
+ agentId,
1242
1291
  cli,
1243
1292
  cwd,
1244
1293
  hook: true,
@@ -1248,7 +1297,10 @@ Options:
1248
1297
  const child = Bun.spawn(agentArgv, {
1249
1298
  cwd,
1250
1299
  detached: true,
1251
- env: agentEnv,
1300
+ env: {
1301
+ ...agentEnv,
1302
+ AGENT_YES_AGENT_ID: agentId
1303
+ },
1252
1304
  stdin: "ignore",
1253
1305
  stdout: "ignore",
1254
1306
  stderr: "ignore"
@@ -1257,6 +1309,7 @@ Options:
1257
1309
  return Response.json({
1258
1310
  ok: true,
1259
1311
  pid: child.pid,
1312
+ agentId,
1260
1313
  cli,
1261
1314
  cwd,
1262
1315
  ...provisioned ? { provisioned } : {}
@@ -1389,4 +1442,4 @@ Options:
1389
1442
 
1390
1443
  //#endregion
1391
1444
  export { cmdServe };
1392
- //# sourceMappingURL=serve-BRARQjsC.js.map
1445
+ //# sourceMappingURL=serve-yVh9Y6ot.js.map
@@ -32,7 +32,7 @@ async function cmdSetup(rest) {
32
32
  if (!existsSync(abs)) process.stderr.write(` note: that directory doesn't exist yet — create it, or agents spawned there will fail\n`);
33
33
  if (noShare) return 0;
34
34
  process.stdout.write(`\nsharing this machine to agent-yes.com…\n`);
35
- const { cmdServe } = await import("./serve-BRARQjsC.js");
35
+ const { cmdServe } = await import("./serve-yVh9Y6ot.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-Cx7jDKtC.js.map
45
+ //# sourceMappingURL=setup-BmYrvUsz.js.map
@@ -556,15 +556,15 @@ async function runSubcommand(argv) {
556
556
  case "restart": return await cmdRestart(rest);
557
557
  case "note": return await cmdNote(rest);
558
558
  case "serve": {
559
- const { cmdServe } = await import("./serve-BRARQjsC.js");
559
+ const { cmdServe } = await import("./serve-yVh9Y6ot.js");
560
560
  return cmdServe(rest);
561
561
  }
562
562
  case "setup": {
563
- const { cmdSetup } = await import("./setup-Cx7jDKtC.js");
563
+ const { cmdSetup } = await import("./setup-BmYrvUsz.js");
564
564
  return cmdSetup(rest);
565
565
  }
566
566
  case "schedule": {
567
- const { cmdSchedule } = await import("./schedule-CCvB7SCT.js");
567
+ const { cmdSchedule } = await import("./schedule-B-Ncgklt.js");
568
568
  return cmdSchedule(rest);
569
569
  }
570
570
  case "remote": {
@@ -913,7 +913,8 @@ async function runRemoteSpawn(remote, hint, spec) {
913
913
  }
914
914
  const r = await res.json();
915
915
  process.stdout.write(`spawned ${r.cli} on ${remote.url} in ${r.cwd}${r.hook ? " (via spawn hook)" : ""}${r.provisioned ? ` (${r.provisioned.action})` : ""}\n`);
916
- process.stderr.write(`\n ay ls ${hint} # the new ${r.cli} agent appears here\n`);
916
+ if (r.agentId && !hint.includes("://")) process.stderr.write(`\n ay tail ${hint}:${r.agentId} # watch its output\n ay status ${hint}:${r.agentId} --wait # block until it needs you\n`);
917
+ else process.stderr.write(`\n ay ls ${hint} # the new ${r.cli} agent appears here\n`);
917
918
  return 0;
918
919
  }
919
920
  async function runRemoteStatus(remote) {
@@ -1933,7 +1934,7 @@ async function cmdSend(rest) {
1933
1934
  const trailing = controlCodeFromName(codeName);
1934
1935
  const record = await resolveOne(keyword, opts);
1935
1936
  const fifoPath = record.fifo_file;
1936
- if (!fifoPath) throw new Error(`pid ${record.pid}: no fifo_file recorded — agent was not started with --stdpush (or was spawned by Rust which doesn't yet support FIFO IPC; see ROADMAP item 10)`);
1937
+ if (!fifoPath) throw new Error(`pid ${record.pid}: no fifo_file recorded — this agent didn't register a stdin FIFO (an older agent, or one not started with --stdpush). Restarting it (ay restart ${record.pid}) re-registers one.`);
1937
1938
  let body;
1938
1939
  if (rawMessage === "-") {
1939
1940
  const chunks = [];
@@ -2794,4 +2795,4 @@ async function cmdResultSet(rest) {
2794
2795
 
2795
2796
  //#endregion
2796
2797
  export { restartHintLines as C, writeToIpc as D, stopTipForCli as E, resolveResumeArgs as S, snapshotStatus as T, readPtysize as _, cursorAbs as a, resolveOne as b, extractTaskCounts as c, isExitRequest as d, isPidAlive as f, readNotes as g, matchKeyword as h, controlCodeFromName as i, finalizedLines as l, listRecords as m, READ_PAGE_DEFAULT as n, deriveLiveStatus as o, isSubcommand as p, cmdHelp as r, extractNeedsInput as s, GRACEFUL_EXIT_COMMANDS as t, isAgentStuck as u, renderRawLog as v, runSubcommand as w, resolveReadWindow as x, renderRawLogLines as y };
2797
- //# sourceMappingURL=subcommands-DGkBxWS5.js.map
2798
+ //# sourceMappingURL=subcommands-BbKngO7F.js.map
@@ -4,6 +4,6 @@ import "./configShared-CEyhl0WH.js";
4
4
  import "./e2e-Bfw7qL9O.js";
5
5
  import "./webrtcLink-CBZkZ-LT.js";
6
6
  import "./remotes-DBCvpp3B.js";
7
- import { C as restartHintLines, D as writeToIpc, E as stopTipForCli, S as resolveResumeArgs, T as snapshotStatus, _ as readPtysize, a as cursorAbs, b as resolveOne, c as extractTaskCounts, d as isExitRequest, f as isPidAlive, g as readNotes, h as matchKeyword, i as controlCodeFromName, l as finalizedLines, m as listRecords, n as READ_PAGE_DEFAULT, o as deriveLiveStatus, p as isSubcommand, r as cmdHelp, s as extractNeedsInput, t as GRACEFUL_EXIT_COMMANDS, u as isAgentStuck, v as renderRawLog, w as runSubcommand, x as resolveReadWindow, y as renderRawLogLines } from "./subcommands-DGkBxWS5.js";
7
+ import { C as restartHintLines, D as writeToIpc, E as stopTipForCli, S as resolveResumeArgs, T as snapshotStatus, _ as readPtysize, a as cursorAbs, b as resolveOne, c as extractTaskCounts, d as isExitRequest, f as isPidAlive, g as readNotes, h as matchKeyword, i as controlCodeFromName, l as finalizedLines, m as listRecords, n as READ_PAGE_DEFAULT, o as deriveLiveStatus, p as isSubcommand, r as cmdHelp, s as extractNeedsInput, t as GRACEFUL_EXIT_COMMANDS, u as isAgentStuck, v as renderRawLog, w as runSubcommand, x as resolveReadWindow, y as renderRawLogLines } from "./subcommands-BbKngO7F.js";
8
8
 
9
9
  export { cmdHelp, isSubcommand, runSubcommand };
@@ -1,8 +1,8 @@
1
1
  import { n as logger, t as addTransport } from "./logger-CDIsZ-Pp.js";
2
- import { r as getInstalledPackage } from "./versionChecker-DrjR4P0K.js";
2
+ import { r as getInstalledPackage } from "./versionChecker-CCHuozmQ.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
- import { t as PidStore } from "./pidStore-fqXqTKkh.js";
5
+ import { t as PidStore } from "./pidStore-BfoBWUjc.js";
6
6
  import { i as readGlobalPids } from "./globalPidIndex-DlmmJlO8.js";
7
7
  import { n as register, r as sweep } from "./reaper-C-eWAxIj.js";
8
8
  import { arch, platform } from "process";
@@ -1216,6 +1216,7 @@ async function agentYes({ cli, cliArgs = [], skipPermissions = false, prompt, ro
1216
1216
  const inheritedAyPid = Number(ptyEnv.AGENT_YES_PID);
1217
1217
  const parentPid = Number.isInteger(inheritedAyPid) && inheritedAyPid > 0 ? inheritedAyPid : void 0;
1218
1218
  ptyEnv.AGENT_YES_PID = String(process.pid);
1219
+ delete ptyEnv.AGENT_YES_AGENT_ID;
1219
1220
  if (cliConf?.env) for (const [key, raw] of Object.entries(cliConf.env)) {
1220
1221
  let unresolved = false;
1221
1222
  const value = raw.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g, (_, name, fallback) => {
@@ -1802,4 +1803,4 @@ function sleep(ms) {
1802
1803
 
1803
1804
  //#endregion
1804
1805
  export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
1805
- //# sourceMappingURL=ts-CRo3qQWb.js.map
1806
+ //# sourceMappingURL=ts-BzyVSzo1.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.161.0";
10
+ var version = "1.164.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-DrjR4P0K.js.map
218
+ //# sourceMappingURL=versionChecker-CCHuozmQ.js.map
@@ -153,14 +153,19 @@ export function tagsFor(e) {
153
153
  }
154
154
 
155
155
  // Compact git indicator from the record's `git` snapshot (server-side
156
- // `git status --porcelain --branch`): "±3" changed files, "↑1" ahead, "↓2"
157
- // behind. Returns "" when there's no git info or the tree is clean and in sync,
158
- // so a tidy repo adds no noise. Branch itself is shown via the path identity.
156
+ // `git status --porcelain=v2 --branch`): "±3" real changed files, "2"
157
+ // submodule pin-bumps, "⊙1" submodule internal dirt, "↑1" ahead, "↓2" behind.
158
+ // Pins/sub-dirt are split out of "±" so submodule drift (constant in a repo with
159
+ // many submodules) never buries the real file edits. Returns "" when there's no
160
+ // git info or the tree is clean and in sync, so a tidy repo adds no noise. Branch
161
+ // itself is shown via the path identity.
159
162
  export function gitLabel(e) {
160
163
  const g = e.git;
161
164
  if (!g) return "";
162
165
  const parts = [];
163
166
  if (g.changed > 0) parts.push("±" + g.changed);
167
+ if (g.pins > 0) parts.push("⑂" + g.pins);
168
+ if (g.subDirty > 0) parts.push("⊙" + g.subDirty);
164
169
  if (g.ahead > 0) parts.push("↑" + g.ahead);
165
170
  if (g.behind > 0) parts.push("↓" + g.behind);
166
171
  return parts.join(" ");
@@ -521,10 +526,21 @@ export function fitTransform(gridW, gridH, paneW, paneH) {
521
526
 
522
527
  // Browser-tab title: "<glyph> <selected agent title> - agent-yes", or the bare
523
528
  // console title when nothing is selected (blank/whitespace name). The leading
524
- // glyph mirrors the agent's status dot — active, idle, exited — so the tab
525
- // shows liveness at a glance; an unknown status adds no glyph.
529
+ // glyph mirrors the agent's status dot — needs_input ("your turn"), stuck,
530
+ // ● active, ○ idle, ✗ exited — so the tab shows liveness at a glance even in a
531
+ // background tab; an unknown status adds no glyph.
526
532
  export function statusGlyph(status) {
527
- return status === "active" ? "●" : status === "idle" ? "○" : status === "exited" ? "✗" : "";
533
+ return status === "needs_input"
534
+ ? "⌨"
535
+ : status === "stuck"
536
+ ? "⚠"
537
+ : status === "active"
538
+ ? "●"
539
+ : status === "idle"
540
+ ? "○"
541
+ : status === "exited"
542
+ ? "✗"
543
+ : "";
528
544
  }
529
545
  export function docTitle(name, status) {
530
546
  const n = name && String(name).trim();
package/lab/ui/index.html CHANGED
@@ -891,6 +891,11 @@
891
891
  color: var(--green);
892
892
  border-color: #2a3a2a;
893
893
  }
894
+ /* The pending question when an agent needs_input — blue accent to match the
895
+ needs_input dot, reading as "your turn / here's what it's asking". */
896
+ .detail.ask {
897
+ color: var(--accent);
898
+ }
894
899
  .detail {
895
900
  color: var(--muted);
896
901
  font-size: 12.5px;
@@ -3187,13 +3192,19 @@
3187
3192
  );
3188
3193
  }
3189
3194
 
3190
- // A small git chip (±changed ↑ahead ↓behind) for a row, amber when the tree
3191
- // is dirty. Empty for clean/in-sync repos and non-git cwds no noise.
3195
+ // A small git chip (±changed ⑂pins ⊙sub-dirty ↑ahead ↓behind) for a row,
3196
+ // amber when the tree has real file changes. Submodule pin-drift shows as a
3197
+ // muted "⑂" that never turns the chip amber, so it can't bury real edits.
3198
+ // Empty for clean/in-sync repos and non-git cwds — no noise.
3192
3199
  function gitChipHtml(e) {
3193
3200
  const label = gitLabel(e);
3194
3201
  if (!label) return "";
3195
3202
  const g = e.git || {};
3196
- const tip = `git: ${g.changed || 0} changed · ${g.ahead || 0} ahead · ${g.behind || 0} behind`;
3203
+ const tip =
3204
+ `git: ${g.changed || 0} changed file${(g.changed || 0) === 1 ? "" : "s"}` +
3205
+ (g.pins ? ` · ${g.pins} submodule pin-bump${g.pins === 1 ? "" : "s"}` : "") +
3206
+ (g.subDirty ? ` · ${g.subDirty} submodule dirty` : "") +
3207
+ ` · ${g.ahead || 0} ahead · ${g.behind || 0} behind`;
3197
3208
  return `<span class="git${g.dirty ? " dirty" : ""}" title="${esc(tip)}">${esc(label)}</span>`;
3198
3209
  }
3199
3210
 
@@ -3285,7 +3296,13 @@
3285
3296
  ${gitChipHtml(e)}
3286
3297
  <span class="age">${age(e)}</span></div>
3287
3298
  ${e.title ? `<div class="rowtitle" title="${esc(e.title)}">${esc(e.title)}</div>` : ""}
3288
- ${e.prompt ? `<div class="detail" title="${esc(e.prompt)}">${esc(e.prompt)}</div>` : ""}
3299
+ ${
3300
+ e.status === "needs_input" && e.question
3301
+ ? `<div class="detail ask" title="${esc(e.question)}">⌨ ${esc(e.question)}</div>`
3302
+ : e.prompt
3303
+ ? `<div class="detail" title="${esc(e.prompt)}">${esc(e.prompt)}</div>`
3304
+ : ""
3305
+ }
3289
3306
  <div class="rowtags">${tags}</div>
3290
3307
  </div>`;
3291
3308
  })
@@ -3918,7 +3935,7 @@
3918
3935
  return `<div class="omni-row omni-spawn${sc}" data-i="${i}">
3919
3936
  <span class="dot active"></span>
3920
3937
  <div class="omni-main">
3921
- <div class="omni-title">⑂ Fork <b>${esc(r.srcBranch)}</b> &amp; run <span style="opacity:.55">(carries uncommitted work)</span></div>
3938
+ <div class="omni-title">⑂ Fork <b>${esc(r.srcBranch)}</b> &amp; run <span style="opacity:.55">(clean committed work only)</span></div>
3922
3939
  <div class="omni-sub">${esc(r.fromCwd)} &nbsp;→&nbsp; new branch <b>${esc(r.branch)}</b> &nbsp;·&nbsp; ⏎ to edit &amp; launch</div>
3923
3940
  </div></div>`;
3924
3941
  }
@@ -4050,14 +4067,12 @@
4050
4067
  return;
4051
4068
  }
4052
4069
  if (row && row.kind === "fork") {
4053
- // Editable at launch (auto-slug is the default); carries the source's WIP.
4070
+ // Editable at launch (auto-slug is the default). The fork is clean —
4071
+ // committed work only, the source's uncommitted changes stay put.
4054
4072
  // Fork from the source PINNED on the row (row.fromCwd), not a freshly
4055
4073
  // re-resolved anchor — the anchor can drift as the list refreshes, but the
4056
4074
  // branch we fork from must be the one the row was built for.
4057
- const branch = prompt(
4058
- "Fork to a new branch (carries your uncommitted work):",
4059
- row.branch,
4060
- );
4075
+ const branch = prompt("Fork to a new branch (clean — committed work only):", row.branch);
4061
4076
  if (!branch || !branch.trim()) return; // cancelled — leave the omnibox open
4062
4077
  closeOmni(false);
4063
4078
  await spawnAndSelect(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-yes",
3
- "version": "1.161.0",
3
+ "version": "1.164.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/index.ts CHANGED
@@ -374,6 +374,12 @@ export default async function agentYes({
374
374
  const parentPid =
375
375
  Number.isInteger(inheritedAyPid) && inheritedAyPid > 0 ? inheritedAyPid : undefined;
376
376
  ptyEnv.AGENT_YES_PID = String(process.pid);
377
+ // A caller-injected AGENT_YES_AGENT_ID (from `ay serve`'s /api/spawn) is meant
378
+ // for THIS agent's record only — pidStore.registerProcess reads it from our own
379
+ // env. Strip it from the wrapped CLI's env so the agent's subagents (a nested
380
+ // `ay`) don't inherit it and register under the same id (which would make that
381
+ // id ambiguous). The wrapper's own process.env still carries it for pidStore.
382
+ delete ptyEnv.AGENT_YES_AGENT_ID;
377
383
  // Inject per-CLI env (e.g. glm → Z.AI endpoint). Expand ${VAR} against the
378
384
  // launching env; skip entries whose vars are unset so we never blank out an
379
385
  // inherited value (e.g. ANTHROPIC_AUTH_TOKEN when ZAI_API_KEY isn't exported).
package/ts/pidStore.ts CHANGED
@@ -77,9 +77,15 @@ export class PidStore {
77
77
  const fifoFile = this.getFifoPath(pid);
78
78
 
79
79
  // Upsert by pid. Reuse an existing record's agent id so re-registration
80
- // (e.g. status churn) keeps the id stable; mint a fresh 12-hex id otherwise.
80
+ // (e.g. status churn) keeps the id stable; else adopt a caller-injected
81
+ // AGENT_YES_AGENT_ID (so `ay serve`'s /api/spawn can hand back an id that
82
+ // addresses this exact agent — index.ts strips it from the wrapped CLI's env
83
+ // so subagents don't inherit it); else mint a fresh 12-hex id.
84
+ const injected = process.env.AGENT_YES_AGENT_ID;
81
85
  const existing = this.store.findOne((doc) => doc.pid === pid);
82
- const agentId = existing?.agentId ?? randomBytes(6).toString("hex");
86
+ const agentId =
87
+ existing?.agentId ??
88
+ (injected && /^[0-9a-f]{6,32}$/i.test(injected) ? injected : randomBytes(6).toString("hex"));
83
89
 
84
90
  const record: Omit<PidRecord, "_id"> = {
85
91
  pid,
package/ts/serve.ts CHANGED
@@ -9,6 +9,7 @@ import yargs from "yargs";
9
9
  import {
10
10
  controlCodeFromName,
11
11
  deriveLiveStatus,
12
+ extractNeedsInput,
12
13
  extractTaskCounts,
13
14
  listRecords,
14
15
  readNotes,
@@ -172,12 +173,15 @@ function loginShellEnv(): Record<string, string> | null {
172
173
  // Delimiters fence off the env dump from any banner/prompt noise the rc files
173
174
  // print to stdout; `env -0` is NUL-separated so values with newlines survive.
174
175
  const delim = "_AY_SHELL_ENV_DELIM_";
175
- const res = Bun.spawnSync([shell, "-ilc", `printf %s "${delim}"; env -0; printf %s "${delim}"`], {
176
- stdin: "ignore",
177
- stderr: "ignore",
178
- timeout: 5_000,
179
- env: process.env as Record<string, string>,
180
- });
176
+ const res = Bun.spawnSync(
177
+ [shell, "-ilc", `printf %s "${delim}"; env -0; printf %s "${delim}"`],
178
+ {
179
+ stdin: "ignore",
180
+ stderr: "ignore",
181
+ timeout: 5_000,
182
+ env: process.env as Record<string, string>,
183
+ },
184
+ );
181
185
  const out = res.stdout?.toString() ?? "";
182
186
  const start = out.indexOf(delim);
183
187
  const end = out.lastIndexOf(delim);
@@ -930,8 +934,37 @@ export async function cmdServe(rest: string[]): Promise<number> {
930
934
  }
931
935
  };
932
936
 
937
+ // Per-agent "waiting on you" detection: the agent is parked on an interactive
938
+ // menu it did NOT auto-resolve (config `needsInput` patterns). Same source and
939
+ // classifier as `ay ls` / `ay status`, so the console's dot matches the CLI's
940
+ // needs_input. Cached per (size, mtime) exactly like logTitle/logTasks — each
941
+ // miss renders a log window through xterm, so the 1s list tick stays cheap on
942
+ // an idle fleet. Returns the pending question text (surfaced in the UI), or
943
+ // null when the agent isn't blocked.
944
+ const niCache = new Map<string, { size: number; mtimeMs: number; question: string | null }>();
945
+ const logNeedsInput = async (
946
+ logFile: string | null | undefined,
947
+ cli: string,
948
+ ): Promise<string | null> => {
949
+ if (!logFile) return null;
950
+ try {
951
+ const { size, mtimeMs } = await stat(logFile);
952
+ const hit = niCache.get(logFile);
953
+ if (hit && hit.size === size && hit.mtimeMs === mtimeMs) return hit.question;
954
+ const ni = await extractNeedsInput(logFile, cli);
955
+ const question = ni?.question ?? null;
956
+ niCache.set(logFile, { size, mtimeMs, question });
957
+ return question;
958
+ } catch {
959
+ return null;
960
+ }
961
+ };
962
+
933
963
  // Per-repo git snapshot for the list (branch + dirty/changed + ahead/behind, from
934
- // one `git status --porcelain --branch`). WATCHER-INVALIDATED, not polled: a read
964
+ // one `git status --porcelain=v2 --branch`). v2 (not v1) so the submodule field
965
+ // lets us split real file changes from submodule pin-drift, which would otherwise
966
+ // inflate `changed` — in a superproject with many submodules the constant gitlink
967
+ // drift buries the real edits. WATCHER-INVALIDATED, not polled: a read
935
968
  // returns the cached snapshot instantly and NEVER spawns `git status` on the
936
969
  // request path. A per-repo-root fs watcher recomputes (debounced) only when the
937
970
  // repo actually changes, so an idle fleet costs ~0 git processes. The old design
@@ -942,7 +975,9 @@ export async function cmdServe(rest: string[]): Promise<number> {
942
975
  interface GitInfo {
943
976
  branch: string | null;
944
977
  dirty: boolean;
945
- changed: number;
978
+ changed: number; // real file changes (excludes submodule pin-bumps & internal dirt)
979
+ pins: number; // submodule gitlinks pointing at new commit(s) — pin-bump/drift
980
+ subDirty: number; // submodule has internal changes but its recorded pin is unchanged
946
981
  ahead: number;
947
982
  behind: number;
948
983
  }
@@ -964,16 +999,55 @@ export async function cmdServe(rest: string[]): Promise<number> {
964
999
  }
965
1000
  };
966
1001
  const parseGitStatus = (out: string): GitInfo => {
967
- const lines = out.split("\n");
968
- // Branch header, e.g. "## main...origin/main [ahead 1, behind 2]", "## main"
969
- // (no upstream), "## HEAD (no branch)", or "## No commits yet on x".
970
- const h = /^## (.+)$/.exec(lines[0] ?? "")?.[1] ?? "";
971
- const unborn = /^No commits yet on (.+)$/.exec(h);
972
- const branch = unborn ? unborn[1]! : /^(.+?)(?:\.\.\.|\s|$)/.exec(h)?.[1] || null;
973
- const ahead = Number(/\bahead (\d+)/.exec(h)?.[1] ?? 0);
974
- const behind = Number(/\bbehind (\d+)/.exec(h)?.[1] ?? 0);
975
- const changed = lines.slice(1).filter((l) => l.trim().length > 0).length;
976
- return { branch, dirty: changed > 0, changed, ahead, behind };
1002
+ // porcelain=v2 --branch: "# branch.*" headers then one line per changed entry.
1003
+ let branch: string | null = null;
1004
+ let ahead = 0;
1005
+ let behind = 0;
1006
+ let changed = 0; // real file changes
1007
+ let pins = 0; // submodule pin-bumps (gitlink new commit)
1008
+ let subDirty = 0; // submodule internal dirt, recorded pin unchanged
1009
+ for (const line of out.split("\n")) {
1010
+ if (line.length === 0) continue;
1011
+ if (line[0] === "#") {
1012
+ // "# branch.head <name|(detached)>" and "# branch.ab +A -B" (ab absent
1013
+ // when there's no upstream). branch.head carries the name even on an
1014
+ // unborn branch, so no special-casing needed.
1015
+ const head = /^# branch\.head (.+)$/.exec(line);
1016
+ if (head) {
1017
+ branch = head[1] === "(detached)" ? null : head[1]!;
1018
+ continue;
1019
+ }
1020
+ const ab = /^# branch\.ab \+(\d+) -(\d+)/.exec(line);
1021
+ if (ab) {
1022
+ ahead = Number(ab[1]);
1023
+ behind = Number(ab[2]);
1024
+ }
1025
+ continue;
1026
+ }
1027
+ // Changed-entry lines. Untracked ("?") and unmerged ("u") are always real
1028
+ // work; ignored ("!") never appears (we don't pass --ignored). For ordinary
1029
+ // ("1") / renamed ("2") entries the 3rd space-delimited token is the
1030
+ // submodule field <sub>: "N..." for a normal path, or "S<c><m><u>" for a
1031
+ // submodule where c=C means its gitlink moved to new commit(s) (pin-bump),
1032
+ // and m/u flag internal dirty/untracked with the recorded pin unchanged.
1033
+ // Paths (which may contain spaces) come after token 2, so counting by token
1034
+ // index is robust without -z.
1035
+ const type = line[0];
1036
+ if (type === "?") {
1037
+ changed++;
1038
+ } else if (type === "u") {
1039
+ changed++;
1040
+ } else if (type === "1" || type === "2") {
1041
+ const sub = line.split(" ")[2] ?? "N...";
1042
+ if (sub[0] === "S") {
1043
+ if (sub[1] === "C") pins++;
1044
+ else subDirty++;
1045
+ } else {
1046
+ changed++;
1047
+ }
1048
+ }
1049
+ }
1050
+ return { branch, dirty: changed > 0, changed, pins, subDirty, ahead, behind };
977
1051
  };
978
1052
  // cwd -> repo root ("" = resolved, not a repo). Resolved once per cwd via a cheap
979
1053
  // `git rev-parse --show-toplevel` (no tree scan) and cached, so many agents in the
@@ -999,7 +1073,7 @@ export async function cmdServe(rest: string[]): Promise<number> {
999
1073
  if (rw.busy) return void recompute(root, rw); // re-arm if one is in flight
1000
1074
  rw.busy = true;
1001
1075
  try {
1002
- const out = await runGit(["status", "--porcelain", "--branch"], root);
1076
+ const out = await runGit(["status", "--porcelain=v2", "--branch"], root);
1003
1077
  if (out != null) rw.val = parseGitStatus(out);
1004
1078
  } finally {
1005
1079
  rw.busy = false;
@@ -1051,12 +1125,23 @@ export async function cmdServe(rest: string[]): Promise<number> {
1051
1125
  // the LIVE status here — same liveness+log-mtime basis as `ay ls` — so the
1052
1126
  // console's dot (and the browser tab glyph) flips to idle in step with `ay ls`.
1053
1127
  const status = await deriveLiveStatus(r);
1128
+ // "Waiting on you": alive, quiet, but parked on an unanswered menu. Checked
1129
+ // only for live agents, and skipped when unresponsive (the Rust wedge signal
1130
+ // wins) — mirroring deriveLiveState's precedence in `ay ls` so the console's
1131
+ // dot and the CLI agree.
1132
+ const question =
1133
+ status !== "exited" && !r.unresponsive ? await logNeedsInput(r.log_file, r.cli) : null;
1054
1134
  return {
1055
1135
  ...r,
1056
- // The Rust supervisor's unresponsive flag is an authoritative wedge signal —
1057
- // surface it as `stuck` so the console's dot matches `ay ls`. (A dead agent
1058
- // is never unresponsiveRust clears the flag on exit.)
1059
- status: status !== "exited" && r.unresponsive ? "stuck" : status,
1136
+ // Precedence: exited stays exited; the Rust supervisor's unresponsive flag is
1137
+ // an authoritative wedge signal (`stuck`); then a blocked menu (`needs_input`);
1138
+ // else the base live status so the console's dot matches `ay ls`. (A dead
1139
+ // agent is never unresponsive Rust clears the flag on exit.)
1140
+ status:
1141
+ status === "exited" ? status : r.unresponsive ? "stuck" : question ? "needs_input" : status,
1142
+ // The pending menu/question text when needs_input, for the console to show
1143
+ // WHAT the agent is waiting on. Null otherwise.
1144
+ question,
1060
1145
  title: await logTitle(r.log_file),
1061
1146
  git: status === "exited" ? null : await gitStatus(r.cwd),
1062
1147
  // Task progress from the rendered todo block (null when none detected → no
@@ -1642,9 +1727,11 @@ export async function cmdServe(rest: string[]): Promise<number> {
1642
1727
  return new Response("fork requires a non-empty fromCwd and branch", { status: 400 });
1643
1728
  const from = typeof body.from === "string" ? body.from.trim() : "";
1644
1729
  if (fork) {
1645
- // Fork the anchor agent's branch (carrying its WIP) into a new sibling
1646
- // worktree via codehost/provision (git worktree off HEAD, no clone), then
1647
- // spawn the agent there.
1730
+ // Fork the anchor agent's branch into a new sibling worktree via
1731
+ // codehost/provision (git worktree off HEAD, no clone), then spawn the
1732
+ // agent there. The fork is clean — committed work only; the source's
1733
+ // uncommitted changes stay put (codehost forkWorktree defaults wip:false,
1734
+ // and we don't opt in).
1648
1735
  let prov: {
1649
1736
  forkWorktree: (o: { fromCwd: string; branch: string; wsRoot?: string }) => Promise<{
1650
1737
  ok: boolean;
@@ -1763,6 +1850,14 @@ export async function cmdServe(rest: string[]): Promise<number> {
1763
1850
  const agentArgv = [...ayCmd, cli, ...(prompt ? ["--", prompt] : [])];
1764
1851
  // don't leak our Claude Code session into the agent
1765
1852
  const agentEnv = freshAgentEnv();
1853
+ // Correlation id: the spawn response returns the `ay` LAUNCHER pid, which is
1854
+ // NOT the agent's registered pid — so the caller can't find the agent it just
1855
+ // spawned. Mint a 12-hex id (the agent_id format), inject it as
1856
+ // AGENT_YES_AGENT_ID, and return it. Both runtimes adopt it as their agent_id
1857
+ // (instead of minting a random one) and strip it from the wrapped CLI's env so
1858
+ // subagents don't inherit and collide. The caller can then address the agent
1859
+ // immediately: `ay <verb> <remote>:<agentId>`.
1860
+ const agentId = randomBytes(6).toString("hex");
1766
1861
  // Detach the agent into its OWN session (setsid). When `ay serve` runs WITH a
1767
1862
  // controlling terminal (started in a shell rather than as a headless daemon),
1768
1863
  // an undetached child inherits the daemon's session + controlling tty and lands
@@ -1792,7 +1887,12 @@ export async function cmdServe(rest: string[]): Promise<number> {
1792
1887
  const child = Bun.spawn([shell, "-c", script, "ay-spawn", ...agentArgv], {
1793
1888
  cwd,
1794
1889
  detached: true,
1795
- env: { ...agentEnv, AGENT_YES_CWD: cwd, AGENT_YES_CLI: cli },
1890
+ env: {
1891
+ ...agentEnv,
1892
+ AGENT_YES_CWD: cwd,
1893
+ AGENT_YES_CLI: cli,
1894
+ AGENT_YES_AGENT_ID: agentId,
1895
+ },
1796
1896
  stdin: "ignore",
1797
1897
  stdout: "ignore",
1798
1898
  stderr: Bun.file(errPath),
@@ -1829,6 +1929,7 @@ export async function cmdServe(rest: string[]): Promise<number> {
1829
1929
  return Response.json({
1830
1930
  ok: true,
1831
1931
  pid: child.pid,
1932
+ agentId,
1832
1933
  cli,
1833
1934
  cwd,
1834
1935
  hook: true,
@@ -1838,7 +1939,7 @@ export async function cmdServe(rest: string[]): Promise<number> {
1838
1939
  const child = Bun.spawn(agentArgv, {
1839
1940
  cwd,
1840
1941
  detached: true,
1841
- env: agentEnv,
1942
+ env: { ...agentEnv, AGENT_YES_AGENT_ID: agentId },
1842
1943
  stdin: "ignore",
1843
1944
  stdout: "ignore",
1844
1945
  stderr: "ignore",
@@ -1847,6 +1948,7 @@ export async function cmdServe(rest: string[]): Promise<number> {
1847
1948
  return Response.json({
1848
1949
  ok: true,
1849
1950
  pid: child.pid,
1951
+ agentId,
1850
1952
  cli,
1851
1953
  cwd,
1852
1954
  ...(provisioned ? { provisioned } : {}),
package/ts/subcommands.ts CHANGED
@@ -897,6 +897,7 @@ async function runRemoteSpawn(
897
897
  pid: number;
898
898
  cli: string;
899
899
  cwd: string;
900
+ agentId?: string;
900
901
  hook?: boolean;
901
902
  provisioned?: { action: string };
902
903
  };
@@ -905,15 +906,19 @@ async function runRemoteSpawn(
905
906
  `${r.hook ? " (via spawn hook)" : ""}` +
906
907
  `${r.provisioned ? ` (${r.provisioned.action})` : ""}\n`,
907
908
  );
908
- // We deliberately do NOT print the agent's own pid. `/api/spawn` returns the
909
- // `ay` LAUNCHER pid, which is not the agent's registered pid (the record's
910
- // pid/wrapper_pid are the runtime process, not the launcher) — and picking the
911
- // freshly-registered agent by "newest" would, under a concurrent spawn,
912
- // surface a sibling's (or another user's) pid. Point at the remote's list,
913
- // where the new agent reliably appears, instead of guessing.
914
- // (A one-step `ay tail <remote>:<pid>` would need /api/spawn to return a
915
- // correlatable agent id a server change left for a follow-up.)
916
- process.stderr.write(`\n ay ls ${hint} # the new ${r.cli} agent appears here\n`);
909
+ // `/api/spawn` returns a correlation `agentId` that the agent adopts as its
910
+ // agent_id so we address the EXACT agent by it (no pid guessing, no race). A
911
+ // webrtc:// / share-link target isn't `:keyword`-addressable, so the keyword
912
+ // hint is only for an alias / token@host:port. Older remotes omit agentId →
913
+ // fall back to pointing at `ay ls`.
914
+ if (r.agentId && !hint.includes("://")) {
915
+ process.stderr.write(
916
+ `\n ay tail ${hint}:${r.agentId} # watch its output\n` +
917
+ ` ay status ${hint}:${r.agentId} --wait # block until it needs you\n`,
918
+ );
919
+ } else {
920
+ process.stderr.write(`\n ay ls ${hint} # the new ${r.cli} agent appears here\n`);
921
+ }
917
922
  return 0;
918
923
  }
919
924
 
@@ -2258,7 +2263,7 @@ async function cmdSend(rest: string[]): Promise<number> {
2258
2263
  const fifoPath = record.fifo_file;
2259
2264
  if (!fifoPath) {
2260
2265
  throw new Error(
2261
- `pid ${record.pid}: no fifo_file recorded — agent was not started with --stdpush (or was spawned by Rust which doesn't yet support FIFO IPC; see ROADMAP item 10)`,
2266
+ `pid ${record.pid}: no fifo_file recorded — this agent didn't register a stdin FIFO (an older agent, or one not started with --stdpush). Restarting it (ay restart ${record.pid}) re-registers one.`,
2262
2267
  );
2263
2268
  }
2264
2269
 
@@ -1,8 +0,0 @@
1
- import "./ts-CRo3qQWb.js";
2
- import "./logger-CDIsZ-Pp.js";
3
- import "./versionChecker-DrjR4P0K.js";
4
- import "./pidStore-fqXqTKkh.js";
5
- import "./globalPidIndex-DlmmJlO8.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DYTnLX8C.js";
7
-
8
- export { SUPPORTED_CLIS };
@@ -1,5 +0,0 @@
1
- import "./logger-CDIsZ-Pp.js";
2
- import { t as PidStore } from "./pidStore-fqXqTKkh.js";
3
- import "./globalPidIndex-DlmmJlO8.js";
4
-
5
- export { PidStore };