claude-yes 1.170.1 → 1.172.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-kJRKPdFi.js";
1
+ import { t as CLIS_CONFIG } from "./ts-DiMpa4ri.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-Bz-oXKG8.js.map
8
+ //# sourceMappingURL=SUPPORTED_CLIS-KMkjT-Ny.js.map
@@ -0,0 +1,8 @@
1
+ import "./ts-DiMpa4ri.js";
2
+ import "./logger-CDIsZ-Pp.js";
3
+ import "./versionChecker-D6Qc6O3F.js";
4
+ import "./pidStore-BIvsBQ8X.js";
5
+ import "./globalPidIndex-CoNr7tS8.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-KMkjT-Ny.js";
7
+
8
+ export { SUPPORTED_CLIS };
package/dist/cli.js CHANGED
@@ -1,8 +1,8 @@
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, t as checkAndAutoUpdate } from "./versionChecker-eVbxzEL3.js";
5
- import { n as getRustBinary } from "./rustBinary-CYS7148m.js";
4
+ import { i as versionString, n as displayVersion, t as checkAndAutoUpdate } from "./versionChecker-D6Qc6O3F.js";
5
+ import { n as getRustBinary } from "./rustBinary-Bne6M0ik.js";
6
6
  import { argv } from "process";
7
7
  import { spawn } from "child_process";
8
8
  import ms from "ms";
@@ -285,7 +285,7 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
285
285
  const rawArg = process.argv[2];
286
286
  const managerCommands = !invokedCliName(process.argv);
287
287
  const isHelpFlag = rawArg === "-h" || rawArg === "--help";
288
- const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-DOnqoHyP.js");
288
+ const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-DZtrr8ps.js");
289
289
  if (isHelpFlag && process.argv.length === 3) {
290
290
  await cmdHelp(managerCommands);
291
291
  process.exit(0);
@@ -327,7 +327,7 @@ if (config.useRust) {
327
327
  }
328
328
  }
329
329
  if (rustBinary) {
330
- const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-Dby-8X2S.js");
330
+ const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-oB5KxI57.js");
331
331
  const rustArgs = buildRustArgs(process.argv, config.cli, SUPPORTED_CLIS);
332
332
  if (config.verbose) {
333
333
  console.log(`[rust] Using binary: ${rustBinary}`);
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-kJRKPdFi.js";
1
+ import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-DiMpa4ri.js";
2
2
  import "./logger-CDIsZ-Pp.js";
3
- import "./versionChecker-eVbxzEL3.js";
3
+ import "./versionChecker-D6Qc6O3F.js";
4
4
  import "./pidStore-BIvsBQ8X.js";
5
5
  import "./globalPidIndex-CoNr7tS8.js";
6
6
 
@@ -1,7 +1,7 @@
1
1
  import { n as logger } from "./logger-CDIsZ-Pp.js";
2
2
  import "./globalPidIndex-CoNr7tS8.js";
3
3
  import "./configShared-0MnIQ652.js";
4
- import { D as renderLogTailLines, G as hostId, J as readInbox, K as listInboxParents, Q as notifyDir, S as listRecords, U as appendEvent, W as gcInboxes, X as daemonLockDir, Y as shouldStealLock, Z as daemonLockOwnerPath, _ as isPidAlive, c as deriveLiveState, q as liveWatchers } from "./subcommands-CbcBfInM.js";
4
+ import { D as renderLogTailLines, G as hostId, J as readInbox, K as listInboxParents, Q as notifyDir, S as listRecords, U as appendEvent, W as gcInboxes, X as daemonLockDir, Y as shouldStealLock, Z as daemonLockOwnerPath, _ as isPidAlive, c as deriveLiveState, q as liveWatchers } from "./subcommands-ZjESAcio.js";
5
5
  import "./e2e-jb0Hp43q.js";
6
6
  import "./webrtcLink-B7REGtK2.js";
7
7
  import "./remotes-CC-4GuJb.js";
@@ -588,4 +588,4 @@ async function ensureDaemon() {
588
588
 
589
589
  //#endregion
590
590
  export { daemonStatus, ensureDaemon, requestDaemonStop, runDaemon };
591
- //# sourceMappingURL=notifyDaemon-BB08sE7n.js.map
591
+ //# sourceMappingURL=notifyDaemon-tmytSfnS.js.map
@@ -1,4 +1,4 @@
1
- import { r as getInstalledPackage } from "./versionChecker-eVbxzEL3.js";
1
+ import { r as getInstalledPackage } from "./versionChecker-D6Qc6O3F.js";
2
2
  import { execFileSync } from "child_process";
3
3
  import { existsSync, mkdirSync, unlinkSync } from "fs";
4
4
  import { chmod, copyFile } from "fs/promises";
@@ -225,4 +225,4 @@ async function getRustBinary(options = {}) {
225
225
 
226
226
  //#endregion
227
227
  export { getRustBinary as n, findSpawnHiddenLauncher as t };
228
- //# sourceMappingURL=rustBinary-CYS7148m.js.map
228
+ //# sourceMappingURL=rustBinary-Bne6M0ik.js.map
@@ -1,9 +1,9 @@
1
- import "./ts-kJRKPdFi.js";
1
+ import "./ts-DiMpa4ri.js";
2
2
  import "./logger-CDIsZ-Pp.js";
3
- import "./versionChecker-eVbxzEL3.js";
3
+ import "./versionChecker-D6Qc6O3F.js";
4
4
  import "./pidStore-BIvsBQ8X.js";
5
5
  import "./globalPidIndex-CoNr7tS8.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-Bz-oXKG8.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-KMkjT-Ny.js";
7
7
  import { d as resolveSpawnCwd } from "./workspaceConfig-BgqK-31W.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-D4KvBh5w.js.map
144
+ //# sourceMappingURL=schedule-BrpCaCuz.js.map
@@ -1,13 +1,13 @@
1
- import "./ts-kJRKPdFi.js";
1
+ import "./ts-DiMpa4ri.js";
2
2
  import "./logger-CDIsZ-Pp.js";
3
- import { r as getInstalledPackage } from "./versionChecker-eVbxzEL3.js";
4
- import { t as findSpawnHiddenLauncher } from "./rustBinary-CYS7148m.js";
3
+ import { r as getInstalledPackage } from "./versionChecker-D6Qc6O3F.js";
4
+ import { t as findSpawnHiddenLauncher } from "./rustBinary-Bne6M0ik.js";
5
5
  import "./pidStore-BIvsBQ8X.js";
6
6
  import { a as updateGlobalPidStatus } from "./globalPidIndex-CoNr7tS8.js";
7
7
  import { t as pgidForWrapper } from "./reaper-CWF2_ATd.js";
8
8
  import "./configShared-0MnIQ652.js";
9
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-Bz-oXKG8.js";
10
- import { A as resolveOne, E as readPtysize, F as snapshotStatus, H as TYPING_BADGE, O as renderRawLog, S as listRecords, T as readNotes, V as writeToIpc, b as isUserTyping, f as extractNeedsInput, l as deriveLiveStatus, o as controlCodeFromName, p as extractTaskCounts, u as extractBadges } from "./subcommands-CbcBfInM.js";
9
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-KMkjT-Ny.js";
10
+ import { A as resolveOne, E as readPtysize, F as snapshotStatus, H as TYPING_BADGE, O as renderRawLog, S as listRecords, T as readNotes, V as writeToIpc, b as isUserTyping, f as extractNeedsInput, l as deriveLiveStatus, o as controlCodeFromName, p as extractTaskCounts, u as extractBadges } from "./subcommands-ZjESAcio.js";
11
11
  import "./e2e-jb0Hp43q.js";
12
12
  import "./webrtcLink-B7REGtK2.js";
13
13
  import "./remotes-CC-4GuJb.js";
@@ -845,13 +845,31 @@ Options:
845
845
  if (!root) return null;
846
846
  return ensureRepoWatch(root).val;
847
847
  };
848
+ const meaningfulStdinAt = /* @__PURE__ */ new Map();
849
+ const anyDaemonWriteAt = /* @__PURE__ */ new Map();
850
+ const isTerminalReply = (s) => /^\x1b\[(\d+;\d+R|\?[\d;]*c|>[\d;]*c|\d*n)$/.test(s);
851
+ const noteStdinWrite = async (pid, fifo, meaningful) => {
852
+ const mt = await stat(fifo).then((s) => s.mtimeMs).catch(() => null);
853
+ if (mt == null) return;
854
+ anyDaemonWriteAt.set(pid, mt);
855
+ if (meaningful) meaningfulStdinAt.set(pid, mt);
856
+ };
857
+ const resolveLastStdinAt = async (r) => {
858
+ const meaningful = meaningfulStdinAt.get(r.pid) ?? null;
859
+ if (!r.fifo_file) return meaningful;
860
+ const mtime = await stat(r.fifo_file).then((s) => s.mtimeMs).catch(() => null);
861
+ if (mtime == null) return meaningful;
862
+ return mtime > (anyDaemonWriteAt.get(r.pid) ?? 0) ? mtime : meaningful;
863
+ };
848
864
  const withMeta = async (r) => {
849
865
  const status = await deriveLiveStatus(r);
850
866
  const question = status !== "exited" && !r.unresponsive ? await logNeedsInput(r.log_file, r.cli) : null;
851
867
  const lastActiveAt = r.log_file ? await stat(r.log_file).then((s) => s.mtimeMs).catch(() => r.started_at) : r.started_at;
868
+ const lastStdinAt = status !== "exited" ? await resolveLastStdinAt(r) : null;
852
869
  return {
853
870
  ...r,
854
871
  last_active_at: lastActiveAt,
872
+ last_stdin_at: lastStdinAt,
855
873
  status: status === "exited" ? status : r.unresponsive ? "stuck" : question ? "needs_input" : status,
856
874
  question,
857
875
  title: await logTitle(r.log_file),
@@ -1115,6 +1133,7 @@ Options:
1115
1133
  await new Promise((r) => setTimeout(r, 200));
1116
1134
  await writeToIpc(record.fifo_file, trailing);
1117
1135
  } else await writeToIpc(record.fifo_file, msg + trailing);
1136
+ await noteStdinWrite(record.pid, record.fifo_file, !isTerminalReply(msg));
1118
1137
  return Response.json({
1119
1138
  ok: true,
1120
1139
  pid: record.pid
@@ -1558,4 +1577,4 @@ Options:
1558
1577
 
1559
1578
  //#endregion
1560
1579
  export { cmdServe };
1561
- //# sourceMappingURL=serve-BN0mUK9m.js.map
1580
+ //# sourceMappingURL=serve-DvO4pj5p.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-BN0mUK9m.js");
35
+ const { cmdServe } = await import("./serve-DvO4pj5p.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-CUrjwXNE.js.map
45
+ //# sourceMappingURL=setup-DB2gMtqk.js.map
@@ -1,7 +1,7 @@
1
1
  import "./logger-CDIsZ-Pp.js";
2
2
  import "./globalPidIndex-CoNr7tS8.js";
3
3
  import "./configShared-0MnIQ652.js";
4
- import { A as resolveOne, B as writeKeysPaced, C as matchKeyword, D as renderLogTailLines, E as readPtysize, F as snapshotStatus, I as stdinActivityPath, L as stopTipForCli, M as resolveResumeArgs, N as restartHintLines, O as renderRawLog, P as runSubcommand, R as submitAndConfirm, S as listRecords, T as readNotes, V as writeToIpc, _ as isPidAlive, a as cmdHelp, b as isUserTyping, c as deriveLiveState, d as extractMenu, f as extractNeedsInput, g as isExitRequest, h as isAgentStuck, i as backoffWhileTyping, j as resolveReadWindow, k as renderRawLogLines, l as deriveLiveStatus, m as finalizedLines, n as READ_PAGE_DEFAULT, o as controlCodeFromName, p as extractTaskCounts, r as TYPING_WINDOW_MS, s as cursorAbs, t as GRACEFUL_EXIT_COMMANDS, u as extractBadges, v as isSlashCommand, w as menuSelectKeys, x as lastStdinAt, y as isSubcommand, z as waitForLogQuiet } from "./subcommands-CbcBfInM.js";
4
+ import { A as resolveOne, B as writeKeysPaced, C as matchKeyword, D as renderLogTailLines, E as readPtysize, F as snapshotStatus, I as stdinActivityPath, L as stopTipForCli, M as resolveResumeArgs, N as restartHintLines, O as renderRawLog, P as runSubcommand, R as submitAndConfirm, S as listRecords, T as readNotes, V as writeToIpc, _ as isPidAlive, a as cmdHelp, b as isUserTyping, c as deriveLiveState, d as extractMenu, f as extractNeedsInput, g as isExitRequest, h as isAgentStuck, i as backoffWhileTyping, j as resolveReadWindow, k as renderRawLogLines, l as deriveLiveStatus, m as finalizedLines, n as READ_PAGE_DEFAULT, o as controlCodeFromName, p as extractTaskCounts, r as TYPING_WINDOW_MS, s as cursorAbs, t as GRACEFUL_EXIT_COMMANDS, u as extractBadges, v as isSlashCommand, w as menuSelectKeys, x as lastStdinAt, y as isSubcommand, z as waitForLogQuiet } from "./subcommands-ZjESAcio.js";
5
5
  import "./e2e-jb0Hp43q.js";
6
6
  import "./webrtcLink-B7REGtK2.js";
7
7
  import "./remotes-CC-4GuJb.js";
@@ -1126,15 +1126,15 @@ async function runSubcommand(argv) {
1126
1126
  case "restart": return await cmdRestart(rest);
1127
1127
  case "note": return await cmdNote(rest);
1128
1128
  case "serve": {
1129
- const { cmdServe } = await import("./serve-BN0mUK9m.js");
1129
+ const { cmdServe } = await import("./serve-DvO4pj5p.js");
1130
1130
  return cmdServe(rest);
1131
1131
  }
1132
1132
  case "setup": {
1133
- const { cmdSetup } = await import("./setup-CUrjwXNE.js");
1133
+ const { cmdSetup } = await import("./setup-DB2gMtqk.js");
1134
1134
  return cmdSetup(rest);
1135
1135
  }
1136
1136
  case "schedule": {
1137
- const { cmdSchedule } = await import("./schedule-D4KvBh5w.js");
1137
+ const { cmdSchedule } = await import("./schedule-BrpCaCuz.js");
1138
1138
  return cmdSchedule(rest);
1139
1139
  }
1140
1140
  case "remote": {
@@ -3963,7 +3963,7 @@ async function cmdNotify(rest) {
3963
3963
  }
3964
3964
  const ensure = async () => {
3965
3965
  if (!argv["ensure-daemon"]) return;
3966
- const { ensureDaemon } = await import("./notifyDaemon-BB08sE7n.js");
3966
+ const { ensureDaemon } = await import("./notifyDaemon-tmytSfnS.js");
3967
3967
  await ensureDaemon().catch(() => null);
3968
3968
  };
3969
3969
  await heartbeatWatcher(parent, selfStartedAt);
@@ -4037,7 +4037,7 @@ async function cmdNotifyCursor(args) {
4037
4037
  }
4038
4038
  async function cmdNotifyd(rest) {
4039
4039
  const sub = rest[0] ?? "status";
4040
- const daemon = await import("./notifyDaemon-BB08sE7n.js");
4040
+ const daemon = await import("./notifyDaemon-tmytSfnS.js");
4041
4041
  switch (sub) {
4042
4042
  case "run": return daemon.runDaemon();
4043
4043
  case "once": return daemon.runDaemon({ once: true });
@@ -4064,4 +4064,4 @@ async function cmdNotifyd(rest) {
4064
4064
 
4065
4065
  //#endregion
4066
4066
  export { resolveOne as A, writeKeysPaced as B, matchKeyword as C, renderLogTailLines as D, readPtysize as E, snapshotStatus as F, hostId as G, TYPING_BADGE as H, stdinActivityPath as I, readInbox as J, listInboxParents as K, stopTipForCli as L, resolveResumeArgs as M, restartHintLines as N, renderRawLog as O, runSubcommand as P, notifyDir as Q, submitAndConfirm as R, listRecords as S, readNotes as T, appendEvent as U, writeToIpc as V, gcInboxes as W, daemonLockDir as X, shouldStealLock as Y, daemonLockOwnerPath as Z, isPidAlive as _, cmdHelp as a, isUserTyping as b, deriveLiveState as c, extractMenu as d, extractNeedsInput as f, isExitRequest as g, isAgentStuck as h, backoffWhileTyping as i, resolveReadWindow as j, renderRawLogLines as k, deriveLiveStatus as l, finalizedLines as m, READ_PAGE_DEFAULT as n, controlCodeFromName as o, extractTaskCounts as p, liveWatchers as q, TYPING_WINDOW_MS as r, cursorAbs as s, GRACEFUL_EXIT_COMMANDS as t, extractBadges as u, isSlashCommand as v, menuSelectKeys as w, lastStdinAt as x, isSubcommand as y, waitForLogQuiet as z };
4067
- //# sourceMappingURL=subcommands-CbcBfInM.js.map
4067
+ //# sourceMappingURL=subcommands-ZjESAcio.js.map
@@ -1,5 +1,5 @@
1
1
  import { n as logger, t as addTransport } from "./logger-CDIsZ-Pp.js";
2
- import { r as getInstalledPackage } from "./versionChecker-eVbxzEL3.js";
2
+ import { r as getInstalledPackage } from "./versionChecker-D6Qc6O3F.js";
3
3
  import { t as agentYesHome } from "./agentYesHome-CtHb5b71.js";
4
4
  import { i as shouldUseLock, r as releaseLock, t as acquireLock } from "./runningLock-CNMl13dC.js";
5
5
  import { t as PidStore } from "./pidStore-BIvsBQ8X.js";
@@ -1824,4 +1824,4 @@ function sleep(ms) {
1824
1824
 
1825
1825
  //#endregion
1826
1826
  export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
1827
- //# sourceMappingURL=ts-kJRKPdFi.js.map
1827
+ //# sourceMappingURL=ts-DiMpa4ri.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.170.1";
10
+ var version = "1.172.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-eVbxzEL3.js.map
218
+ //# sourceMappingURL=versionChecker-D6Qc6O3F.js.map
@@ -348,7 +348,7 @@ function agentForestNodes(list) {
348
348
  // flat entry list BEFORE layeredRows builds the room/peer/agent tree (which keeps
349
349
  // the given order for siblings and first-seen order for room/peer groups). So
350
350
  // sorting reorders roots and siblings without breaking the nesting.
351
- export const SORT_MODES = ["state", "active", "created", "identity"];
351
+ export const SORT_MODES = ["state", "active", "stdin", "created", "identity"];
352
352
 
353
353
  // Attention-first state ranking: someone scanning the fleet wants the agents that
354
354
  // need them (needs_input) up top, then the wedged ones (stuck), then live work,
@@ -383,21 +383,26 @@ function lastActive(e) {
383
383
 
384
384
  // Return a NEW array sorted for display per `mode` (default "state"):
385
385
  // - "state": attention-first state, then git busyness, then newest.
386
- // - "active": most recently active first (last_active_at desc).
386
+ // - "active": most recently active first (last_active_at, i.e. stdout, desc).
387
+ // - "stdin": most recently FED first (last_stdin_at desc) — which agent was
388
+ // just driven/typed-into/`ay send`-ed. Agents never fed sort last.
387
389
  // - "created": newest first (started_at desc).
388
390
  // - "identity": user@host:owner/repo/branch (alphabetical).
389
391
  // Every comparator falls back to newest-first so order is total & deterministic.
390
392
  export function sortEntries(entries, mode = "state") {
391
393
  const byNewest = (a, b) => (b.started_at || 0) - (a.started_at || 0);
392
394
  const byActive = (a, b) => lastActive(b) - lastActive(a) || byNewest(a, b);
395
+ const byStdin = (a, b) => (b.last_stdin_at ?? 0) - (a.last_stdin_at ?? 0) || byNewest(a, b);
393
396
  const cmp =
394
397
  mode === "active"
395
398
  ? byActive
396
- : mode === "created"
397
- ? byNewest
398
- : mode === "identity"
399
- ? (a, b) => fullIdent(a).localeCompare(fullIdent(b)) || byNewest(a, b)
400
- : (a, b) => stateRank(a) - stateRank(b) || gitWeight(b) - gitWeight(a) || byNewest(a, b);
399
+ : mode === "stdin"
400
+ ? byStdin
401
+ : mode === "created"
402
+ ? byNewest
403
+ : mode === "identity"
404
+ ? (a, b) => fullIdent(a).localeCompare(fullIdent(b)) || byNewest(a, b)
405
+ : (a, b) => stateRank(a) - stateRank(b) || gitWeight(b) - gitWeight(a) || byNewest(a, b);
401
406
  return entries.slice().sort(cmp);
402
407
  }
403
408
 
@@ -549,6 +554,52 @@ export function nextIndex(len, i, dir) {
549
554
  // coordinate math lives here so it's unit-testable without a browser.
550
555
  // ---------------------------------------------------------------------------
551
556
 
557
+ // ---- collaborative presence: peer focus + stdin flash ----------------------
558
+ // The left panel colour-codes who's on which agent: your own selection is blue
559
+ // (the .sel bar), every OTHER human peer's focus is yellow (.peerfocus), and a
560
+ // row pulses when its stdin was just written — independent of focus, since an
561
+ // `ay send` feeds an agent no one is looking at. These pure helpers own the
562
+ // bookkeeping so index.html only measures the DOM.
563
+
564
+ // Detect agents whose stdin just advanced. `seen` maps _key -> the last
565
+ // last_stdin_at we observed; given the current entries, return the _keys whose
566
+ // last_stdin_at is NEWER than what we'd seen (someone typed / `ay send` pushed),
567
+ // and fold the new values into `seen`. A key seen for the FIRST time never
568
+ // flashes (else every agent would pulse on initial load) — only a later bump
569
+ // does. Keys no longer present are pruned so `seen` can't grow without bound.
570
+ export function advanceStdinFlashes(entries, seen) {
571
+ const fresh = [];
572
+ const alive = new Set();
573
+ for (const e of entries) {
574
+ const key = e._key;
575
+ if (!key) continue;
576
+ alive.add(key);
577
+ const at = e.last_stdin_at;
578
+ if (typeof at !== "number") continue;
579
+ const prev = seen.get(key);
580
+ if (prev !== undefined && at > prev) fresh.push(key);
581
+ if (prev === undefined || at > prev) seen.set(key, at);
582
+ }
583
+ for (const key of [...seen.keys()]) if (!alive.has(key)) seen.delete(key);
584
+ return fresh;
585
+ }
586
+
587
+ // Summarize fleet presence for the peers badge + per-row chips. `records` is one
588
+ // entry per OTHER viewer currently watching an agent: { key, viewer } (key = the
589
+ // composite _key of the agent they're on). Returns { total, byKey }: `total`
590
+ // distinct viewers (the "N peers" badge), `byKey` a Map of _key -> how many
591
+ // peers watch that agent (the yellow per-row chip + .peerfocus trigger).
592
+ export function focusSummary(records) {
593
+ const viewers = new Set();
594
+ const byKey = new Map();
595
+ for (const r of records) {
596
+ if (!r || !r.key || !r.viewer) continue;
597
+ viewers.add(r.viewer);
598
+ byKey.set(r.key, (byKey.get(r.key) || 0) + 1);
599
+ }
600
+ return { total: viewers.size, byKey };
601
+ }
602
+
552
603
  // Stable per-viewer hue (0..359) for colour-coding peers' selections.
553
604
  export function hashHue(s) {
554
605
  let h = 0;
package/lab/ui/index.html CHANGED
@@ -37,6 +37,9 @@
37
37
  --purple: #bc8cff;
38
38
  --pink: #f778ba;
39
39
  --red: #f85149;
40
+ /* Peer-focus yellow: a human collaborator (not you) is on this agent.
41
+ Distinct from your own blue (--accent) and the idle-dot amber. */
42
+ --peer: #e3b341;
40
43
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
41
44
  /* Safe-area insets — non-zero only in a standalone PWA on a notched /
42
45
  gesture-bar device (else 0, so these are harmless on desktop). Consumed
@@ -70,6 +73,7 @@
70
73
  --purple: #8250df;
71
74
  --pink: #bf3989;
72
75
  --red: #cf222e;
76
+ --peer: #bf8700;
73
77
  }
74
78
  }
75
79
  * {
@@ -420,6 +424,21 @@
420
424
  font-size: 11.5px;
421
425
  margin-top: 8px;
422
426
  }
427
+ .metaleft {
428
+ display: inline-flex;
429
+ align-items: center;
430
+ gap: 8px;
431
+ }
432
+ /* Peers badge beside the agent counter — how many other humans are in the
433
+ fleet right now. Yellow ("here") when a peer shares the agent you have
434
+ open, muted otherwise. Hidden entirely when you're alone. */
435
+ .peers {
436
+ color: var(--muted);
437
+ cursor: default;
438
+ }
439
+ .peers.here {
440
+ color: var(--peer);
441
+ }
423
442
  .connbadge {
424
443
  cursor: pointer;
425
444
  user-select: none;
@@ -818,6 +837,39 @@
818
837
  background: var(--panel);
819
838
  box-shadow: inset 3px 0 0 var(--accent);
820
839
  }
840
+ /* A human peer (not you) is watching this agent: yellow, mirrored on the
841
+ RIGHT edge so it can coexist with your own blue selection bar on the
842
+ left — a row you BOTH have open shows blue-left + yellow-right. */
843
+ .row.peerfocus {
844
+ box-shadow: inset -3px 0 0 var(--peer);
845
+ background: color-mix(in srgb, var(--peer) 7%, transparent);
846
+ }
847
+ .row.sel.peerfocus {
848
+ background: var(--panel);
849
+ box-shadow: inset 3px 0 0 var(--accent), inset -3px 0 0 var(--peer);
850
+ }
851
+ /* stdin just landed here (someone typed, or `ay send` pushed a command) —
852
+ pulse the row regardless of who (if anyone) is focused on it. Runs while
853
+ the .stdinflash class is present; index.html removes it after the window. */
854
+ .row.stdinflash {
855
+ animation: stdinpulse 0.6s ease-in-out infinite;
856
+ }
857
+ @keyframes stdinpulse {
858
+ 0%,
859
+ 100% {
860
+ background-color: transparent;
861
+ }
862
+ 50% {
863
+ background-color: color-mix(in srgb, var(--peer) 24%, transparent);
864
+ }
865
+ }
866
+ /* Per-row chip: how many peers are watching THIS agent (yellow, mono). */
867
+ .peerchip {
868
+ font-family: var(--mono);
869
+ font-size: 11px;
870
+ color: var(--peer);
871
+ flex: none;
872
+ }
821
873
  .r1 {
822
874
  display: flex;
823
875
  align-items: center;
@@ -1392,7 +1444,10 @@
1392
1444
  />
1393
1445
  </div>
1394
1446
  <div class="meta">
1395
- <span id="count"></span>
1447
+ <span class="metaleft">
1448
+ <span id="count"></span>
1449
+ <span id="peers" class="peers" hidden></span>
1450
+ </span>
1396
1451
  <span class="metaright">
1397
1452
  <button id="foldbtn" class="viewbtn" title="fold subagent trees">⊞ subs</button>
1398
1453
  <button id="sortbtn" class="viewbtn" title="cycle sort order">⇅ state</button>
@@ -1584,6 +1639,8 @@
1584
1639
  SORT_MODES,
1585
1640
  taskLabel,
1586
1641
  badgesFor,
1642
+ advanceStdinFlashes,
1643
+ focusSummary,
1587
1644
  hashHue,
1588
1645
  selFromBottom,
1589
1646
  parseSel,
@@ -1610,6 +1667,17 @@
1610
1667
 
1611
1668
  let entries = [];
1612
1669
  let sel = null; // selected agent's composite key (room#pid)
1670
+ // Collaborative presence, fleet-wide: focusByKey maps an agent's _key ->
1671
+ // how many OTHER human peers are watching it (yellow bars + per-row chip);
1672
+ // peerTotal is the distinct-peer count for the header badge. stdinSeen +
1673
+ // flashUntil drive the stdin pulse: stdinSeen remembers each agent's last
1674
+ // last_stdin_at so we can spot a bump, flashUntil holds the deadline the
1675
+ // row keeps pulsing to. See advanceStdinFlashes/focusSummary (console-logic).
1676
+ let focusByKey = new Map();
1677
+ let peerTotal = 0;
1678
+ const stdinSeen = new Map();
1679
+ const flashUntil = new Map();
1680
+ const STDIN_FLASH_MS = 1200;
1613
1681
  let es = null; // live-tail subscription closer
1614
1682
  let term = null; // xterm.js Terminal rendering the raw PTY stream
1615
1683
  let fit = null;
@@ -2713,10 +2781,65 @@
2713
2781
  }
2714
2782
  renderPeerSelections();
2715
2783
  }
2784
+ // Fleet-wide presence: unlike pollPresence (only the SELECTED agent's host,
2785
+ // for the terminal overlay), this reads EVERY live source's blackboard so
2786
+ // the left panel can colour any agent a peer is watching. Each viewer
2787
+ // self-reports the pid it's on; we map that back to the source's composite
2788
+ // _key. Best-effort — a source that 404s (older host) contributes nothing.
2789
+ async function pollFleetPresence() {
2790
+ const recs = [];
2791
+ await Promise.all(
2792
+ [...sources.values()].map(async (s) => {
2793
+ if (!s.tx || !s.live) return;
2794
+ let all;
2795
+ try {
2796
+ all = await s.tx.fetchJSON("/api/presence");
2797
+ } catch {
2798
+ return;
2799
+ }
2800
+ if (!Array.isArray(all)) return;
2801
+ const keyOfPid = new Map((s.agents || []).map((e) => [String(e.pid), e._key]));
2802
+ for (const v of all) {
2803
+ if (!v || v.viewer === myViewerId) continue;
2804
+ const key = keyOfPid.get(String(v.agent));
2805
+ // Prefix the viewer id with its source so the same id on two hosts
2806
+ // still counts as two distinct people in the fleet total.
2807
+ if (key) recs.push({ key, viewer: s.id + ":" + v.viewer });
2808
+ }
2809
+ }),
2810
+ );
2811
+ const sum = focusSummary(recs);
2812
+ focusByKey = sum.byKey;
2813
+ peerTotal = sum.total;
2814
+ paintPeersBadge();
2815
+ renderList();
2816
+ }
2817
+ // "N peers" badge beside the agent counter. Yellow when a peer shares the
2818
+ // agent you have open ("same agent"); its tooltip breaks the count down by
2819
+ // agent so you can see who's where. Hidden when you're the only one here.
2820
+ function paintPeersBadge() {
2821
+ const el = $("peers");
2822
+ if (!el) return;
2823
+ if (!peerTotal) {
2824
+ el.hidden = true;
2825
+ el.removeAttribute("title");
2826
+ return;
2827
+ }
2828
+ el.hidden = false;
2829
+ el.textContent = `👤 ${peerTotal} peer${peerTotal === 1 ? "" : "s"}`;
2830
+ el.classList.toggle("here", !!(sel && focusByKey.get(sel)));
2831
+ const lines = [...focusByKey.entries()].map(([k, n]) => {
2832
+ const e = entries.find((x) => x._key === k);
2833
+ const name = e ? e.title || cliLabel(e) || ident(e) || `pid ${e.pid}` : k;
2834
+ return ` ${n} on ${name}`;
2835
+ });
2836
+ el.title = "peers watching agents in this fleet:\n" + lines.join("\n");
2837
+ }
2716
2838
  // 3s heartbeat (< the host's 12s TTL): refresh our presence + read others',
2717
2839
  // and follow the canonical grid size (resize+scale, never reflow) so all
2718
2840
  // viewers stay on one shared canvas and selections line up.
2719
2841
  setInterval(() => {
2842
+ pollFleetPresence(); // fleet-wide peer focus (independent of what's selected)
2720
2843
  if (!sel) return;
2721
2844
  sendPresence();
2722
2845
  pollPresence();
@@ -2727,6 +2850,23 @@
2727
2850
  .then(followAgentSize)
2728
2851
  .catch(() => {});
2729
2852
  }, 3000);
2853
+ // Fast pulse loop: drive the stdin flash on the STABLE list DOM (toggle a
2854
+ // class, no re-render) so a pulse begins/ends promptly regardless of the
2855
+ // list's own render cadence. Cheap — a no-op until something is flashing.
2856
+ setInterval(() => {
2857
+ if (!flashUntil.size) return;
2858
+ const now = Date.now();
2859
+ for (const [k, until] of [...flashUntil.entries()]) if (until <= now) flashUntil.delete(k);
2860
+ const listEl = $("list");
2861
+ if (!listEl) return;
2862
+ for (const el of listEl.querySelectorAll(".row[data-key]")) {
2863
+ const key = el.getAttribute("data-key");
2864
+ // Mirror rowFlags: never pulse the agent you have open (you see its input
2865
+ // live; also stops your own terminal's protocol replies blinking the row).
2866
+ const on = key !== sel && (flashUntil.get(key) || 0) > now;
2867
+ el.classList.toggle("stdinflash", on);
2868
+ }
2869
+ }, 200);
2730
2870
 
2731
2871
  // Peer-selection overlay. The coordinate math (parseSel / selSegments /
2732
2872
  // hashHue) lives in console-logic.js (unit-tested); here we just measure the
@@ -3311,6 +3451,26 @@
3311
3451
  return `<div class="ghead g-${r.kind}">${r.branch ? `<span class="tbranch">${esc(r.branch)}</span>` : ""}<span class="gicon">${icon}</span><span class="glabel">${esc(r.label || "")}</span>${ctag}<span class="gkind">${r.kind}</span></div>`;
3312
3452
  }
3313
3453
 
3454
+ // Extra row classes for collaborative presence: a yellow bar when a human
3455
+ // peer is watching this agent, plus the stdin pulse while its input just
3456
+ // advanced. Empty in the solo case, so a single-user list is unchanged.
3457
+ // The pulse is suppressed on the agent YOU have open (e._key === sel): you
3458
+ // already see its input land live in the terminal, so a flash there is
3459
+ // redundant — and it's the only row your own terminal's protocol replies
3460
+ // could ever nudge, so this also keeps a resize from blinking your own row.
3461
+ function rowFlags(e) {
3462
+ const peers = focusByKey.get(e._key) || 0;
3463
+ const flash = e._key !== sel && (flashUntil.get(e._key) || 0) > Date.now();
3464
+ return (peers ? " peerfocus" : "") + (flash ? " stdinflash" : "");
3465
+ }
3466
+ // A yellow chip showing how many peers are watching this agent ("" if none).
3467
+ function peerChipHtml(e) {
3468
+ const n = focusByKey.get(e._key) || 0;
3469
+ return n
3470
+ ? `<span class="peerchip" title="${n} peer${n === 1 ? "" : "s"} watching this agent">👤${n}</span>`
3471
+ : "";
3472
+ }
3473
+
3314
3474
  function renderList() {
3315
3475
  const toks = $("q").value.trim().split(/\s+/).filter(Boolean);
3316
3476
  // Build the full signalling-server > rooms > peers > agents > subagents
@@ -3325,6 +3485,7 @@
3325
3485
  ? fullRows.filter((r) => !(r.kind === "agent" && r.parentEntry))
3326
3486
  : fullRows;
3327
3487
  $("count").textContent = `${agentRows.length} / ${entries.length} agents`;
3488
+ paintPeersBadge(); // keep the peers badge in step with sel / the entry list
3328
3489
  $("foldbtn").textContent = foldSubs ? "⊞ subs" : "⊟ subs";
3329
3490
  $("foldbtn").classList.toggle("on", !foldSubs);
3330
3491
  $("viewbtn").classList.toggle("on", compactList);
@@ -3347,7 +3508,7 @@
3347
3508
  const t = e.title || e.prompt || "";
3348
3509
  const id = compactIdent(e, ctx, 3, r.parentEntry);
3349
3510
  const cli = cliLabel(e);
3350
- return `<div class="row crow ${e._key === sel ? "sel" : ""}" data-key="${esc(e._key)}">
3511
+ return `<div class="row crow ${e._key === sel ? "sel" : ""}${rowFlags(e)}" data-key="${esc(e._key)}">
3351
3512
  ${r.branch ? `<span class="tbranch">${esc(r.branch)}</span>` : ""}
3352
3513
  <span class="dot ${esc(e.status)}"></span>
3353
3514
  ${hasIdent(id) ? `<span class="cident" title="${esc(fullIdent(e))}">${esc(id)}</span>` : ""}
@@ -3357,6 +3518,7 @@
3357
3518
  ${taskChipHtml(e)}
3358
3519
  ${badgeChipsHtml(e)}
3359
3520
  ${gitChipHtml(e)}
3521
+ ${peerChipHtml(e)}
3360
3522
  <span class="age">${age(e)}</span></div>`;
3361
3523
  })
3362
3524
  .join("") || `<div class="empty">no match</div>`;
@@ -3376,7 +3538,7 @@
3376
3538
  `<span class="rtag" data-k="${k}"><span style="opacity:.55">${k}:</span>${esc(v)}</span>`,
3377
3539
  )
3378
3540
  .join("");
3379
- return `<div class="row ${e._key === sel ? "sel" : ""}" data-key="${esc(e._key)}">
3541
+ return `<div class="row ${e._key === sel ? "sel" : ""}${rowFlags(e)}" data-key="${esc(e._key)}">
3380
3542
  <div class="r1">${r.branch ? `<span class="tbranch">${esc(r.branch)}</span>` : ""}<span class="dot ${esc(e.status)}"></span>
3381
3543
  <span class="name">${esc(cliLabel(e) || ident(e) || "agent")}</span>
3382
3544
  <span class="badge">pid ${e.pid}</span>
@@ -3384,6 +3546,7 @@
3384
3546
  ${taskChipHtml(e)}
3385
3547
  ${badgeChipsHtml(e)}
3386
3548
  ${gitChipHtml(e)}
3549
+ ${peerChipHtml(e)}
3387
3550
  <span class="age">${age(e)}</span></div>
3388
3551
  ${e.title ? `<div class="rowtitle" title="${esc(e.title)}">${esc(e.title)}</div>` : ""}
3389
3552
  ${
@@ -3445,6 +3608,13 @@
3445
3608
  function mergeRender() {
3446
3609
  const srcs = [...sources.values()];
3447
3610
  entries = srcs.flatMap((s) => s.agents || []);
3611
+ // Spot agents whose stdin just advanced (last_stdin_at bumped since we
3612
+ // last saw them — someone typed, or `ay send` pushed input) and arm the
3613
+ // pulse. Runs before renderList so the flashing rows render pulsing; the
3614
+ // fast loop above stops them when the window lapses. Streamed deltas carry
3615
+ // the new mtime, so a silent `ay send` still trips the flash within ~1s.
3616
+ const until = Date.now() + STDIN_FLASH_MS;
3617
+ for (const k of advanceStdinFlashes(entries, stdinSeen)) flashUntil.set(k, until);
3448
3618
  // Badge: total agents + how many rooms are live. Red only when nothing
3449
3619
  // at all is reachable (no source answered).
3450
3620
  const roomSrcs = srcs.filter((s) => s.id !== LOCAL);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-yes",
3
- "version": "1.170.1",
3
+ "version": "1.172.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/serve.ts CHANGED
@@ -1232,6 +1232,47 @@ export async function cmdServe(rest: string[]): Promise<number> {
1232
1232
  return ensureRepoWatch(root).val; // cached — the request path never spawns `git status`
1233
1233
  };
1234
1234
 
1235
+ // Denoised "last meaningful stdin" tracking — drives the console's stdin flash
1236
+ // and the `stdin` sort order. The FIFO's mtime alone is too noisy: xterm forwards
1237
+ // the agent TUI's terminal-protocol auto-replies (cursor-position / device-
1238
+ // attributes reports) to stdin, so a mere resize/redraw would bump it and the row
1239
+ // would false-flash. So we stamp what WE write, split two ways: `meaningfulStdinAt`
1240
+ // skips those auto-replies; `anyDaemonWriteAt` records every write we make. A FIFO
1241
+ // mtime NEWER than any write we made can only be a local `ay send` (which writes
1242
+ // the FIFO directly, bypassing us) — always meaningful — so it still counts. See
1243
+ // resolveLastStdinAt below and the /api/send handler that stamps these.
1244
+ const meaningfulStdinAt = new Map<number, number>();
1245
+ const anyDaemonWriteAt = new Map<number, number>();
1246
+ // A payload that is PURELY a terminal auto-reply (Cursor Position Report, Device
1247
+ // Attributes, Device Status Report) — protocol chatter a TUI emits on redraw/resize,
1248
+ // not a keystroke. Anchored so a chunk that also carries real input never matches;
1249
+ // real typing (incl. arrow keys like `ESC[A`) never looks like one of these.
1250
+ const isTerminalReply = (s: string) => /^\x1b\[(\d+;\d+R|\?[\d;]*c|>[\d;]*c|\d*n)$/.test(s);
1251
+ // Stamp both maps after a daemon FIFO write, keyed off the FIFO's post-write mtime
1252
+ // (so `anyDaemonWriteAt` is exactly our write's mtime — an external write bumps it
1253
+ // strictly higher, which is how resolveLastStdinAt tells them apart with no clock skew).
1254
+ const noteStdinWrite = async (pid: number, fifo: string, meaningful: boolean) => {
1255
+ const mt = await stat(fifo)
1256
+ .then((s) => s.mtimeMs)
1257
+ .catch(() => null);
1258
+ if (mt == null) return;
1259
+ anyDaemonWriteAt.set(pid, mt);
1260
+ if (meaningful) meaningfulStdinAt.set(pid, mt);
1261
+ };
1262
+ // last_stdin_at for a record: newest of (a) the last meaningful write we made and
1263
+ // (b) a FIFO write we did NOT make (a local `ay send`, always meaningful). A mtime
1264
+ // at/below our last write means the newest write was ours → use the meaningful stamp
1265
+ // (which excludes auto-replies); a strictly newer mtime is an external real write.
1266
+ const resolveLastStdinAt = async (r: { pid: number; fifo_file?: string | null }) => {
1267
+ const meaningful = meaningfulStdinAt.get(r.pid) ?? null;
1268
+ if (!r.fifo_file) return meaningful;
1269
+ const mtime = await stat(r.fifo_file)
1270
+ .then((s) => s.mtimeMs)
1271
+ .catch(() => null);
1272
+ if (mtime == null) return meaningful;
1273
+ return mtime > (anyDaemonWriteAt.get(r.pid) ?? 0) ? mtime : meaningful;
1274
+ };
1275
+
1235
1276
  // One agent record decorated for the console: the latest OSC title + a git
1236
1277
  // snapshot (skipped for exited agents — their repo state is no longer live).
1237
1278
  const withMeta = async (r: Awaited<ReturnType<typeof listRecords>>[number]) => {
@@ -1255,9 +1296,17 @@ export async function cmdServe(rest: string[]): Promise<number> {
1255
1296
  .then((s) => s.mtimeMs)
1256
1297
  .catch(() => r.started_at)
1257
1298
  : r.started_at;
1299
+ // Last-stdin time: when this agent was last fed MEANINGFUL input — a real
1300
+ // keystroke, the console composer, or an `ay send` (local or remote) — but NOT
1301
+ // the TUI's terminal-protocol auto-replies, which would otherwise make a resize
1302
+ // or redraw look like input. The console flashes a row when this advances (so a
1303
+ // collaborator sees input land even when they weren't the one sending it) and can
1304
+ // sort by it. See resolveLastStdinAt / noteStdinWrite. Null for exited agents.
1305
+ const lastStdinAt = status !== "exited" ? await resolveLastStdinAt(r) : null;
1258
1306
  return {
1259
1307
  ...r,
1260
1308
  last_active_at: lastActiveAt,
1309
+ last_stdin_at: lastStdinAt,
1261
1310
  // Precedence: exited stays exited; the Rust supervisor's unresponsive flag is
1262
1311
  // an authoritative wedge signal (`stuck`); then a blocked menu (`needs_input`);
1263
1312
  // else the base live status — so the console's dot matches `ay ls`. (A dead
@@ -1653,6 +1702,11 @@ export async function cmdServe(rest: string[]): Promise<number> {
1653
1702
  } else {
1654
1703
  await writeToIpc(record.fifo_file, msg + trailing);
1655
1704
  }
1705
+ // Record this write for the stdin flash / sort. A payload that is purely a
1706
+ // terminal auto-reply (xterm answering the TUI's cursor/DA query, forwarded
1707
+ // over this same wire) is protocol noise, not input — stamp anyDaemonWriteAt
1708
+ // but not the "meaningful" time, so a resize/redraw can't trip the flash.
1709
+ await noteStdinWrite(record.pid, record.fifo_file, !isTerminalReply(msg));
1656
1710
  return Response.json({ ok: true, pid: record.pid });
1657
1711
  } catch (e) {
1658
1712
  return new Response((e as Error).message, { status: 404 });
@@ -1,8 +0,0 @@
1
- import "./ts-kJRKPdFi.js";
2
- import "./logger-CDIsZ-Pp.js";
3
- import "./versionChecker-eVbxzEL3.js";
4
- import "./pidStore-BIvsBQ8X.js";
5
- import "./globalPidIndex-CoNr7tS8.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-Bz-oXKG8.js";
7
-
8
- export { SUPPORTED_CLIS };