agent-yes 1.194.0 → 1.196.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.
@@ -161,6 +161,94 @@ function parseTaskCounts(lines) {
161
161
  return best;
162
162
  }
163
163
 
164
+ //#endregion
165
+ //#region ts/messageLog.ts
166
+ /**
167
+ * Durable inter-agent message log.
168
+ *
169
+ * Every `ay send` that carries a real body is recorded twice — from the two
170
+ * ends' points of view — as append-only JSONL colocated with each agent's
171
+ * project dir (the same `<cwd>/.agent-yes/` convention the session logs use):
172
+ *
173
+ * - the SENDER's `<from.cwd>/.agent-yes/outbox.jsonl`
174
+ * - the RECIPIENT's `<to.cwd>/.agent-yes/inbox.jsonl`
175
+ *
176
+ * A single cwd may host several agents, so records carry the stable `agent_id`
177
+ * (falling back to `pid`) of each end; `ay msgs` filters a mailbox down to one
178
+ * agent by that key. Reading needs no lock (last line wins isn't relevant — a
179
+ * message log keeps every entry); writing is best-effort and never blocks or
180
+ * fails a send.
181
+ */
182
+ /** Keep at most this many lines per mailbox; older entries are compacted away. */
183
+ const MAILBOX_MAX_LINES = 2e3;
184
+ /** Path to a cwd's mailbox file (`<cwd>/.agent-yes/{inbox,outbox}.jsonl`). */
185
+ function mailboxPath(cwd, box) {
186
+ return path.join(cwd, ".agent-yes", `${box}.jsonl`);
187
+ }
188
+ /** Whether a mail party is the agent identified by (agentId, pid). Prefers the
189
+ * stable agent_id (survives restart); falls back to pid for legacy records. */
190
+ function partyMatches(party, agentId, pid) {
191
+ if (!party) return false;
192
+ if (agentId && party.agent_id && party.agent_id === agentId) return true;
193
+ if (typeof pid === "number" && party.pid === pid) return true;
194
+ return false;
195
+ }
196
+ async function appendCapped(filePath, record) {
197
+ await mkdir(path.dirname(filePath), { recursive: true });
198
+ await appendFile(filePath, JSON.stringify(record) + "\n");
199
+ const lines = (await readFile(filePath, "utf-8").catch(() => "")).split("\n").filter((l) => l.trim());
200
+ if (lines.length > MAILBOX_MAX_LINES) await writeFile(filePath, lines.slice(lines.length - MAILBOX_MAX_LINES).join("\n") + "\n");
201
+ }
202
+ /**
203
+ * Record the SENDER's view in its outbox. Best-effort — a filesystem error is
204
+ * logged and swallowed so persistence never breaks a send. The outbox lives
205
+ * under `from.cwd`; a human sender (from === null) writes under `process.cwd()`.
206
+ */
207
+ async function recordOutbox(record) {
208
+ const outCwd = record.from?.cwd ?? process.cwd();
209
+ try {
210
+ await appendCapped(mailboxPath(outCwd, "outbox"), record);
211
+ } catch (err) {
212
+ logger.debug(`[messageLog] outbox append failed: ${err}`);
213
+ }
214
+ }
215
+ /** Record the RECIPIENT's view in its inbox (under `to.cwd`). Best-effort. */
216
+ async function recordInbox(record) {
217
+ try {
218
+ await appendCapped(mailboxPath(record.to.cwd, "inbox"), record);
219
+ } catch (err) {
220
+ logger.debug(`[messageLog] inbox append failed: ${err}`);
221
+ }
222
+ }
223
+ /**
224
+ * Record a same-host message in both mailboxes — the sender's outbox and the
225
+ * recipient's inbox both live on this machine. For a message that crossed the
226
+ * wire, each end calls `recordOutbox`/`recordInbox` on its own host instead.
227
+ */
228
+ async function recordMessage(record) {
229
+ await recordOutbox(record);
230
+ await recordInbox(record);
231
+ }
232
+ /** Read and parse a cwd's mailbox, oldest first. Missing/corrupt lines skipped. */
233
+ async function readMailbox(cwd, box) {
234
+ let raw;
235
+ try {
236
+ raw = await readFile(mailboxPath(cwd, box), "utf-8");
237
+ } catch {
238
+ return [];
239
+ }
240
+ const out = [];
241
+ for (const line of raw.split("\n")) {
242
+ const t = line.trim();
243
+ if (!t) continue;
244
+ try {
245
+ const rec = JSON.parse(t);
246
+ if (rec && typeof rec.at === "number" && rec.to) out.push(rec);
247
+ } catch {}
248
+ }
249
+ return out;
250
+ }
251
+
164
252
  //#endregion
165
253
  //#region ts/badges.ts
166
254
  const BADGE_DEFS = [
@@ -988,6 +1076,40 @@ async function recentReadEdges(windowMs = READ_WINDOW_MS) {
988
1076
  }
989
1077
  return out;
990
1078
  }
1079
+ /**
1080
+ * Scan every live agent's outbox for sends within `windowMs` and return them as
1081
+ * directional edges (newest `at` per by→target pair wins). Bounded by the number
1082
+ * of distinct agent cwds — the outbox is per-cwd, so each dir is read once.
1083
+ */
1084
+ async function recentMessageEdges(windowMs = READ_WINDOW_MS) {
1085
+ const now = Date.now();
1086
+ const records = await listRecords(void 0, {
1087
+ all: true,
1088
+ active: false,
1089
+ json: false,
1090
+ latest: false,
1091
+ cwdScope: null
1092
+ });
1093
+ const cwds = [...new Set(records.map((r) => r.cwd).filter(Boolean))];
1094
+ const best = /* @__PURE__ */ new Map();
1095
+ await Promise.all(cwds.map(async (cwd) => {
1096
+ for (const rec of await readMailbox(cwd, "outbox")) {
1097
+ if (now - rec.at > windowMs) continue;
1098
+ const by = rec.from?.pid;
1099
+ const target = rec.to?.pid;
1100
+ if (!by || !target || by === target) continue;
1101
+ const key = `${by}\0${target}`;
1102
+ const prev = best.get(key);
1103
+ if (!prev || rec.at > prev.at) best.set(key, {
1104
+ by,
1105
+ target,
1106
+ at: rec.at,
1107
+ kind: rec.kind
1108
+ });
1109
+ }
1110
+ }));
1111
+ return [...best.values()];
1112
+ }
991
1113
  async function resolveSender() {
992
1114
  const envPid = process.env.AGENT_YES_PID ? Number(process.env.AGENT_YES_PID) : null;
993
1115
  if (!envPid || Number.isNaN(envPid)) return null;
@@ -1076,6 +1198,7 @@ const SUBCOMMANDS = new Set([
1076
1198
  "tail",
1077
1199
  "head",
1078
1200
  "send",
1201
+ "msgs",
1079
1202
  "key",
1080
1203
  "select",
1081
1204
  "spawn",
@@ -1138,6 +1261,7 @@ async function runSubcommand(argv) {
1138
1261
  case "tail": return await cmdRead(rest, { mode: "tail" });
1139
1262
  case "head": return await cmdRead(rest, { mode: "head" });
1140
1263
  case "send": return await cmdSend(rest);
1264
+ case "msgs": return await cmdMsgs(rest);
1141
1265
  case "key": return await cmdKey(rest);
1142
1266
  case "select": return await cmdSelect(rest);
1143
1267
  case "spawn": return await cmdSpawn(rest);
@@ -1147,15 +1271,15 @@ async function runSubcommand(argv) {
1147
1271
  case "restart": return await cmdRestart(rest);
1148
1272
  case "note": return await cmdNote(rest);
1149
1273
  case "serve": {
1150
- const { cmdServe } = await import("./serve-hA23xPpg.js");
1274
+ const { cmdServe } = await import("./serve-D0-LoTfw.js");
1151
1275
  return cmdServe(rest);
1152
1276
  }
1153
1277
  case "setup": {
1154
- const { cmdSetup } = await import("./setup-C6grPf1R.js");
1278
+ const { cmdSetup } = await import("./setup-BC1VjTOf.js");
1155
1279
  return cmdSetup(rest);
1156
1280
  }
1157
1281
  case "schedule": {
1158
- const { cmdSchedule } = await import("./schedule-MQZjDaY6.js");
1282
+ const { cmdSchedule } = await import("./schedule-rvMgc0tA.js");
1159
1283
  return cmdSchedule(rest);
1160
1284
  }
1161
1285
  case "remote": {
@@ -1200,7 +1324,7 @@ async function cmdHelp(managerCommands = true) {
1200
1324
  const setupLine = managerCommands ? ` ay setup guided setup: pick a workspace, share to agent-yes.com\n` : ``;
1201
1325
  const self = process.env.AGENT_YES_PID ? await resolveSender() : null;
1202
1326
  const agentSection = self ? await buildAgentContextSection(self) : "";
1203
- process.stdout.write(agentSection + "ay - agent-yes CLI\n\nManagement:\n ay ls [keyword] list running agents\n ay tail [-f] [-n N] <keyword> last N lines (96), -f to follow\n ay read <keyword> [page opts] paginate: --last/--head N, --range A:B,\n --before-line L [--limit N]\n ay cat <keyword> full log\n ay head <keyword> first N lines\n ay send <keyword> <msg> send a message\n ay key <keyword> <key...> send raw keystrokes (down/up/enter/esc/…) — drives menus\n ay select <keyword> <N> pick option N of a needs_input selection menu\n ay attach <keyword> interactive attach (detach: Ctrl-\\)\n ay stop <keyword> graceful shutdown (/exit for claude/codex)\n ay exit <keyword> [reason] graceful shutdown, recording who/why (= 'ay send <kw> exit')\n ay restart <keyword> [--fresh] stop (if live) + relaunch resuming the session; --fresh replays the prompt\n ay status <keyword> agent status snapshot\n ay result <keyword> [--wait] pull an agent's structured result envelope\n ay result set '<json>' (inside an agent) deposit your result envelope\n ay reap kill process groups leaked by dead agents\n\nRemote:\n" + setupLine + " ay schedule <when> <cli> -- <msg> run an agent on a schedule (HH:MM or cron)\n ay serve [--port N] start HTTP API server (prints token)\n ay serve status show serve daemon/server status\n ay remote add <alias> http://<token>@<host>:<port>\n ay remote ls / rm <alias> manage saved remotes\n ay expose <port> share localhost:<port> at https://<id>.agent-yes.com (private link)\n ay ls <token>@<host>:<port> connect inline (no alias needed)\n ay send <token>@<host>:<port>:<kw> <msg>\n\nRun an agent:\n ay [claude|codex|gemini|...] [options] -- [prompt]\n ay claude -- \"fix the bug in auth.ts\"\n ay claude --help full agent-runner options\n\nLabs (examples at https://github.com/snomiao/agent-yes/tree/main/lab):\n local-role-play/ designer + builder on one machine\n http-remote/ ay serve remote access demo\n p2p-pairing/ libp2p P2P (needs: cargo build --features swarm)\n");
1327
+ process.stdout.write(agentSection + "ay - agent-yes CLI\n\nManagement:\n ay ls [keyword] list running agents\n ay tail [-f] [-n N] <keyword> last N lines (96), -f to follow\n ay read <keyword> [page opts] paginate: --last/--head N, --range A:B,\n --before-line L [--limit N]\n ay cat <keyword> full log\n ay head <keyword> first N lines\n ay send <keyword> <msg> send a message\n ay msgs [keyword] [--in|--out] inter-agent message log (sent + received)\n ay key <keyword> <key...> send raw keystrokes (down/up/enter/esc/…) — drives menus\n ay select <keyword> <N> pick option N of a needs_input selection menu\n ay attach <keyword> interactive attach (detach: Ctrl-\\)\n ay stop <keyword> graceful shutdown (/exit for claude/codex)\n ay exit <keyword> [reason] graceful shutdown, recording who/why (= 'ay send <kw> exit')\n ay restart <keyword> [--fresh] stop (if live) + relaunch resuming the session; --fresh replays the prompt\n ay status <keyword> agent status snapshot\n ay result <keyword> [--wait] pull an agent's structured result envelope\n ay result set '<json>' (inside an agent) deposit your result envelope\n ay reap kill process groups leaked by dead agents\n\nRemote:\n" + setupLine + " ay schedule <when> <cli> -- <msg> run an agent on a schedule (HH:MM or cron)\n ay serve [--port N] start HTTP API server (prints token)\n ay serve status show serve daemon/server status\n ay remote add <alias> http://<token>@<host>:<port>\n ay remote ls / rm <alias> manage saved remotes\n ay expose <port> share localhost:<port> at https://<id>.agent-yes.com (private link)\n ay ls <token>@<host>:<port> connect inline (no alias needed)\n ay send <token>@<host>:<port>:<kw> <msg>\n\nRun an agent:\n ay [claude|codex|gemini|...] [options] -- [prompt]\n ay claude -- \"fix the bug in auth.ts\"\n ay claude --help full agent-runner options\n\nLabs (examples at https://github.com/snomiao/agent-yes/tree/main/lab):\n local-role-play/ designer + builder on one machine\n http-remote/ ay serve remote access demo\n p2p-pairing/ libp2p P2P (needs: cargo build --features swarm)\n");
1204
1328
  return 0;
1205
1329
  }
1206
1330
  function matchKeyword(record, keyword) {
@@ -1480,10 +1604,18 @@ async function runRemoteSend(remote, msg, code) {
1480
1604
  process.stderr.write("remote send requires a keyword (e.g. token@host:port:keyword)\n");
1481
1605
  return 1;
1482
1606
  }
1607
+ const sender = await senderContext();
1608
+ const from = sender.agent ? {
1609
+ pid: sender.agent.pid,
1610
+ cli: sender.agent.cli,
1611
+ cwd: sender.agent.cwd,
1612
+ agent_id: sender.agent.agent_id
1613
+ } : null;
1483
1614
  const res = await remotePost(remote, "/api/send", {
1484
1615
  keyword,
1485
1616
  msg,
1486
- code
1617
+ code,
1618
+ from
1487
1619
  });
1488
1620
  if (!res.ok) {
1489
1621
  process.stderr.write(`remote error ${res.status}: ${await res.text()}\n`);
@@ -1491,6 +1623,21 @@ async function runRemoteSend(remote, msg, code) {
1491
1623
  }
1492
1624
  const data = await res.json();
1493
1625
  process.stdout.write(`sent to remote pid ${data.pid} (${remote.url} ${keyword})\n`);
1626
+ if (msg && msg !== "-") await recordOutbox({
1627
+ at: Date.now(),
1628
+ from,
1629
+ to: {
1630
+ pid: data.pid,
1631
+ cli: data.cli ?? keyword,
1632
+ cwd: data.cwd ?? "",
1633
+ agent_id: data.agentId
1634
+ },
1635
+ body: msg,
1636
+ code: code.toLowerCase() === "enter" ? void 0 : code.toLowerCase(),
1637
+ confirmed: true,
1638
+ wrapped: false,
1639
+ remote: remote.url
1640
+ });
1494
1641
  return 0;
1495
1642
  }
1496
1643
  /**
@@ -2929,8 +3076,9 @@ async function cmdSend(rest) {
2929
3076
  const replyTarget = sender.agent?.agent_id || sender.agent?.pid;
2930
3077
  let prefix = "";
2931
3078
  let suffix = "";
3079
+ let nonce;
2932
3080
  if (sender.agent && !isSlashCommand(body)) {
2933
- const nonce = randomBytes$1(4).toString("hex");
3081
+ nonce = randomBytes$1(4).toString("hex");
2934
3082
  prefix = `<ay-msg ${nonce} from ${sender.agent.cli} #${sender.agent.pid} @ ${shortenPath(sender.agent.cwd)} — reply: ay send ${replyTarget} "...">\n`;
2935
3083
  suffix = `\n</ay-msg ${nonce}>`;
2936
3084
  }
@@ -2957,6 +3105,26 @@ async function cmdSend(rest) {
2957
3105
  const payload = body + trailing;
2958
3106
  const status = confirmed ? "sent" : "sent but NOT confirmed submitted";
2959
3107
  process.stdout.write(`${status} to pid ${record.pid} (${record.cli}): ${truncate(payload, 80)}\n`);
3108
+ if (body) await recordMessage({
3109
+ at: Date.now(),
3110
+ nonce,
3111
+ from: sender.agent ? {
3112
+ pid: sender.agent.pid,
3113
+ cli: sender.agent.cli,
3114
+ cwd: sender.agent.cwd,
3115
+ agent_id: sender.agent.agent_id
3116
+ } : null,
3117
+ to: {
3118
+ pid: record.pid,
3119
+ cli: record.cli,
3120
+ cwd: record.cwd,
3121
+ agent_id: record.agent_id
3122
+ },
3123
+ body,
3124
+ code: trailing === "\r" ? void 0 : codeName,
3125
+ confirmed,
3126
+ wrapped: Boolean(nonce)
3127
+ });
2960
3128
  if (!confirmed) process.stderr.write(`\nwarning: couldn't confirm the CLI acted on it after ${SEND_SUBMIT_MAX_RETRIES + 1} attempt(s) — it may still be sitting unsubmitted in the prompt. Last screen:\n` + lastScreen.slice(-8).map((l) => ` ${l}`).join("\n") + "\n");
2961
3129
  const replyHint = sender.agent ? ` ay send ${replyTarget} "..." # reply to sender\n` : "";
2962
3130
  process.stderr.write(`\n` + replyHint + ` ay tail ${record.pid} # watch output\n ay ls # list all agents\n`);
@@ -2966,11 +3134,134 @@ async function cmdSend(rest) {
2966
3134
  }
2967
3135
  return confirmed ? 0 : 1;
2968
3136
  }
3137
+ /**
3138
+ * `ay msgs [keyword]` — show the inter-agent messages an agent sent and
3139
+ * received. With no keyword it uses THIS caller's context (the agent running
3140
+ * `ay msgs`, or the human shell's cwd); with a keyword it resolves one agent and
3141
+ * reads that agent's mailboxes. Newest last, like a chat log.
3142
+ */
3143
+ async function cmdMsgs(rest) {
3144
+ const argv = await yargs(rest).usage("Usage: ay msgs [keyword] [--in|--out] [-n N] [--json]").option("in", {
3145
+ type: "boolean",
3146
+ default: false,
3147
+ description: "Only received messages"
3148
+ }).option("out", {
3149
+ type: "boolean",
3150
+ default: false,
3151
+ description: "Only sent messages"
3152
+ }).option("n", {
3153
+ type: "number",
3154
+ description: "Show the last N messages (default 50)"
3155
+ }).option("json", {
3156
+ type: "boolean",
3157
+ default: false,
3158
+ description: "Emit raw JSONL records"
3159
+ }).option("all", {
3160
+ type: "boolean",
3161
+ default: false,
3162
+ description: "Include exited agents"
3163
+ }).option("latest", {
3164
+ type: "boolean",
3165
+ default: false,
3166
+ description: "Use most recent match when multiple match"
3167
+ }).option("cwd", {
3168
+ type: "string",
3169
+ description: "Restrict to agents under this dir"
3170
+ }).help(false).version(false).exitProcess(false).parseAsync();
3171
+ const keyword = argv._[0] !== void 0 ? String(argv._[0]) : void 0;
3172
+ let ownerCwd;
3173
+ let ownerAgentId;
3174
+ let ownerPid;
3175
+ let ownerLabel;
3176
+ if (keyword) {
3177
+ const record = await resolveOne(keyword, {
3178
+ all: argv.all,
3179
+ active: false,
3180
+ json: false,
3181
+ latest: argv.latest,
3182
+ cwdScope: typeof argv.cwd === "string" ? path.resolve(argv.cwd) : null
3183
+ });
3184
+ ownerCwd = record.cwd;
3185
+ ownerAgentId = record.agent_id;
3186
+ ownerPid = record.pid;
3187
+ ownerLabel = `pid ${record.pid} (${record.cli})`;
3188
+ } else {
3189
+ const sender = await senderContext();
3190
+ ownerCwd = sender.agent?.cwd ?? process.cwd();
3191
+ ownerAgentId = sender.agent?.agent_id;
3192
+ ownerPid = sender.agent?.pid;
3193
+ ownerLabel = sender.agent ? `pid ${sender.agent.pid} (${sender.agent.cli})` : "this shell";
3194
+ }
3195
+ const isOwner = (party) => ownerAgentId || ownerPid ? partyMatches(party, ownerAgentId, ownerPid) : party === null;
3196
+ const messages = [];
3197
+ if (!argv.in) {
3198
+ for (const rec of await readMailbox(ownerCwd, "outbox")) if (isOwner(rec.from)) messages.push({
3199
+ dir: "out",
3200
+ rec
3201
+ });
3202
+ }
3203
+ if (!argv.out) {
3204
+ for (const rec of await readMailbox(ownerCwd, "inbox")) if (isOwner(rec.to)) messages.push({
3205
+ dir: "in",
3206
+ rec
3207
+ });
3208
+ }
3209
+ messages.sort((a, b) => a.rec.at - b.rec.at);
3210
+ const limit = argv.n !== void 0 && Number.isFinite(argv.n) && argv.n > 0 ? Math.floor(argv.n) : 50;
3211
+ const shown = messages.slice(-limit);
3212
+ if (argv.json) {
3213
+ for (const { dir, rec } of shown) process.stdout.write(JSON.stringify({
3214
+ dir,
3215
+ ...rec
3216
+ }) + "\n");
3217
+ return 0;
3218
+ }
3219
+ if (shown.length === 0) {
3220
+ process.stderr.write(`no messages for ${ownerLabel}.\n`);
3221
+ return 0;
3222
+ }
3223
+ for (const { dir, rec } of shown) {
3224
+ const when = new Date(rec.at).toLocaleTimeString();
3225
+ const peer = dir === "out" ? `→ ${rec.to.cli} #${rec.to.pid}` : `← ${rec.from ? `${rec.from.cli} #${rec.from.pid}` : "human"}`;
3226
+ const via = rec.remote ? ` (via ${rec.remote})` : "";
3227
+ const flag = rec.confirmed === false ? " (unconfirmed)" : "";
3228
+ const line = (rec.kind ? `[${rec.kind}] ` : "") + truncate(rec.body.replace(/\s+/g, " "), 100);
3229
+ process.stdout.write(`${when} ${peer.padEnd(20)}${via}${flag} ${line}\n`);
3230
+ }
3231
+ return 0;
3232
+ }
2969
3233
  async function resolveWritableAgent(keyword, opts) {
2970
3234
  const record = await resolveOne(keyword, opts);
2971
3235
  if (!record.fifo_file) 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.`);
2972
3236
  return record;
2973
3237
  }
3238
+ /**
3239
+ * Record an `ay key` / `ay select` stdin write in the message log, same as a
3240
+ * text send but tagged with its `kind` (and `body` = the keystroke names /
3241
+ * chosen option). Key/select are local-only (no remote wire), so both mailboxes
3242
+ * are on this host — recordMessage writes both. Best-effort.
3243
+ */
3244
+ async function recordKeyEvent(sender, record, kind, body) {
3245
+ await recordMessage({
3246
+ at: Date.now(),
3247
+ from: sender.agent ? {
3248
+ pid: sender.agent.pid,
3249
+ cli: sender.agent.cli,
3250
+ cwd: sender.agent.cwd,
3251
+ agent_id: sender.agent.agent_id
3252
+ } : null,
3253
+ to: {
3254
+ pid: record.pid,
3255
+ cli: record.cli,
3256
+ cwd: record.cwd,
3257
+ agent_id: record.agent_id
3258
+ },
3259
+ kind,
3260
+ body,
3261
+ confirmed: true,
3262
+ wrapped: false
3263
+ });
3264
+ }
2974
3265
  async function cmdKey(rest) {
2975
3266
  const argv = await yargs(rest).usage("Usage: ay key <keyword> <key...> [options]\n\nSend raw named keystrokes to a live agent's TUI — no message framing, no\nauto-Enter. Drives selection menus and other interactive prompts that a\nplain `ay send` (text + Enter) can't. Keys are paced so the CLI registers\neach as a discrete event, not a paste.\n\nKeys: up down left right enter esc tab space backspace delete home end\n pageup pagedown ctrl-c ctrl-d ctrl-y raw:0xNN\n\nExamples:\n ay key 1234 down down enter # move the menu cursor down twice, confirm\n ay key 1234 esc # dismiss a menu\n ay key 1234 raw:0x1b # a literal ESC byte").option("pace", {
2976
3267
  type: "number",
@@ -3003,9 +3294,10 @@ async function cmdKey(rest) {
3003
3294
  latest: argv.latest,
3004
3295
  cwdScope: typeof argv.cwd === "string" ? path.resolve(argv.cwd) : null
3005
3296
  });
3006
- await enforceSendGuards(record, Boolean(argv.force) || process.env.AGENT_YES_FORCE_SEND === "1");
3297
+ const sender = await enforceSendGuards(record, Boolean(argv.force) || process.env.AGENT_YES_FORCE_SEND === "1");
3007
3298
  await writeKeysPaced(record.fifo_file, byteSeqs, Math.max(0, argv.pace));
3008
3299
  process.stdout.write(`sent to pid ${record.pid} (${record.cli}): ${keyNames.join(" ")}\n`);
3300
+ await recordKeyEvent(sender, record, "key", keyNames.join(" "));
3009
3301
  return 0;
3010
3302
  }
3011
3303
  async function cmdSelect(rest) {
@@ -3048,7 +3340,7 @@ async function cmdSelect(rest) {
3048
3340
  cwdScope: typeof argv.cwd === "string" ? path.resolve(argv.cwd) : null
3049
3341
  });
3050
3342
  if (!record.log_file) throw new Error(`pid ${record.pid}: no log_file recorded — can't read the menu to select from.`);
3051
- await enforceSendGuards(record, Boolean(argv.force) || process.env.AGENT_YES_FORCE_SEND === "1");
3343
+ const sender = await enforceSendGuards(record, Boolean(argv.force) || process.env.AGENT_YES_FORCE_SEND === "1");
3052
3344
  const menu = await extractMenu(record.log_file, record.cli);
3053
3345
  if (!menu) throw new Error(`pid ${record.pid} (${record.cli}) is not parked on a selection menu (not needs_input).\n Check with: ay status ${record.pid}`);
3054
3346
  if (menu.options.length > 0 && !menu.options.includes(n)) throw new Error(`option ${n} is out of range — this menu offers ${menu.options.join(", ")}.`);
@@ -3057,6 +3349,7 @@ async function cmdSelect(rest) {
3057
3349
  const delta = n - menu.cursor;
3058
3350
  const moved = delta === 0 ? "cursor already there" : `${Math.abs(delta)}× ${delta > 0 ? "down" : "up"}`;
3059
3351
  process.stdout.write(`pid ${record.pid} (${record.cli}): selected option ${n} (${moved} + enter)\n`);
3352
+ await recordKeyEvent(sender, record, "select", `option ${n}`);
3060
3353
  if (argv.wait) {
3061
3354
  const ok = await waitForNeedsInputClear(record, Math.max(1, argv.timeout) * 1e3);
3062
3355
  process.stdout.write(ok ? ` menu cleared — selection accepted.\n` : ` still needs_input after ${argv.timeout}s — re-check with 'ay status ${record.pid}'.\n`);
@@ -4004,7 +4297,7 @@ async function cmdNotify(rest) {
4004
4297
  }
4005
4298
  const ensure = async () => {
4006
4299
  if (!argv["ensure-daemon"]) return;
4007
- const { ensureDaemon } = await import("./notifyDaemon-Deisyrbn.js");
4300
+ const { ensureDaemon } = await import("./notifyDaemon-BdOeFIVl.js");
4008
4301
  await ensureDaemon().catch(() => null);
4009
4302
  };
4010
4303
  await heartbeatWatcher(parent, selfStartedAt);
@@ -4078,7 +4371,7 @@ async function cmdNotifyCursor(args) {
4078
4371
  }
4079
4372
  async function cmdNotifyd(rest) {
4080
4373
  const sub = rest[0] ?? "status";
4081
- const daemon = await import("./notifyDaemon-Deisyrbn.js");
4374
+ const daemon = await import("./notifyDaemon-BdOeFIVl.js");
4082
4375
  switch (sub) {
4083
4376
  case "run": return daemon.runDaemon();
4084
4377
  case "once": return daemon.runDaemon({ once: true });
@@ -4104,5 +4397,5 @@ async function cmdNotifyd(rest) {
4104
4397
  }
4105
4398
 
4106
4399
  //#endregion
4107
- export { TYPING_BADGE as $, renderRawLogLines as A, waitForLogQuiet as B, matchKeyword as C, recentReadEdges as D, readPtysize as E, runSubcommand as F, hostId as G, writeToIpc as H, snapshotStatus as I, readInbox as J, listInboxParents as K, stdinActivityPath as L, resolveReadWindow as M, resolveResumeArgs as N, renderLogTailLines as O, restartHintLines as P, notifyDir as Q, stopTipForCli as R, listRecords as S, readNotes as T, appendEvent as U, writeKeysPaced 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, resolveOne as j, renderRawLog 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, submitAndConfirm as z };
4108
- //# sourceMappingURL=subcommands-Ba0V9EEH.js.map
4400
+ export { notifyDir as $, renderRawLog as A, submitAndConfirm as B, matchKeyword as C, recentMessageEdges as D, readPtysize as E, restartHintLines as F, gcInboxes as G, writeKeysPaced as H, runSubcommand as I, liveWatchers as J, hostId as K, snapshotStatus as L, resolveOne as M, resolveReadWindow as N, recentReadEdges as O, resolveResumeArgs as P, daemonLockOwnerPath as Q, stdinActivityPath as R, listRecords as S, readNotes as T, writeToIpc as U, waitForLogQuiet as V, appendEvent as W, shouldStealLock as X, readInbox as Y, daemonLockDir as Z, isPidAlive as _, cmdHelp as a, isUserTyping as b, deriveLiveState as c, extractMenu as d, TYPING_BADGE as et, extractNeedsInput as f, isExitRequest as g, isAgentStuck as h, backoffWhileTyping as i, renderRawLogLines as j, renderLogTailLines as k, deriveLiveStatus as l, finalizedLines as m, READ_PAGE_DEFAULT as n, controlCodeFromName as o, extractTaskCounts as p, listInboxParents as q, TYPING_WINDOW_MS as r, cursorAbs as s, GRACEFUL_EXIT_COMMANDS as t, recordInbox as tt, extractBadges as u, isSlashCommand as v, menuSelectKeys as w, lastStdinAt as x, isSubcommand as y, stopTipForCli as z };
4401
+ //# sourceMappingURL=subcommands-s74ViJcI.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-DDNKcQRx.js";
2
+ import { r as getInstalledPackage } from "./versionChecker-DTssk7cU.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-6JcbtJh1.js.map
1827
+ //# sourceMappingURL=ts-CP17kUI1.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.194.0";
10
+ var version = "1.196.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-DDNKcQRx.js.map
218
+ //# sourceMappingURL=versionChecker-DTssk7cU.js.map