claude-yes 1.166.0 → 1.168.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.
- package/dist/{SUPPORTED_CLIS-Xem8nliL.js → SUPPORTED_CLIS-D0nNJGNj.js} +2 -2
- package/dist/SUPPORTED_CLIS-Dy_o9sj6.js +8 -0
- package/dist/cli.js +4 -4
- package/dist/index.js +2 -2
- package/dist/{schedule-CwAX3y8s.js → schedule-BPUyALF_.js} +4 -4
- package/dist/{serve-DeJBMba0.js → serve-DAuLdt_d.js} +5 -5
- package/dist/{setup-hWG3ZBG3.js → setup-YLHUtCC0.js} +2 -2
- package/dist/{subcommands-BnsjTNpy.js → subcommands-B2QAXBkH.js} +1 -1
- package/dist/{subcommands-BY0mIDo9.js → subcommands-VimbMSTG.js} +26 -6
- package/dist/{ts-CXxZiZKE.js → ts-DBwi5BWW.js} +38 -17
- package/dist/{versionChecker-Dv5DhRs3.js → versionChecker-DVv-WHIp.js} +2 -2
- package/package.json +1 -1
- package/ts/autoRetry.spec.ts +16 -1
- package/ts/autoRetry.ts +23 -0
- package/ts/cli.ts +1 -1
- package/ts/idleWaiter.spec.ts +12 -0
- package/ts/idleWaiter.ts +5 -0
- package/ts/index.ts +23 -5
- package/ts/removeControlCharacters.spec.ts +21 -0
- package/ts/removeControlCharacters.ts +32 -6
- package/ts/subcommands.spec.ts +127 -18
- package/ts/subcommands.ts +65 -2
- package/dist/SUPPORTED_CLIS-ClQgRKQu.js +0 -8
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { t as CLIS_CONFIG } from "./ts-
|
|
1
|
+
import { t as CLIS_CONFIG } from "./ts-DBwi5BWW.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-
|
|
8
|
+
//# sourceMappingURL=SUPPORTED_CLIS-D0nNJGNj.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import "./ts-DBwi5BWW.js";
|
|
2
|
+
import "./logger-CDIsZ-Pp.js";
|
|
3
|
+
import "./versionChecker-DVv-WHIp.js";
|
|
4
|
+
import "./pidStore-BfoBWUjc.js";
|
|
5
|
+
import "./globalPidIndex-DlmmJlO8.js";
|
|
6
|
+
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-D0nNJGNj.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-
|
|
4
|
+
import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-DVv-WHIp.js";
|
|
5
5
|
import { argv } from "process";
|
|
6
6
|
import { execFileSync, spawn } from "child_process";
|
|
7
7
|
import ms from "ms";
|
|
@@ -481,9 +481,9 @@ 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-
|
|
484
|
+
const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-B2QAXBkH.js");
|
|
485
485
|
if (isHelpFlag && process.argv.length === 3) {
|
|
486
|
-
cmdHelp(managerCommands);
|
|
486
|
+
await cmdHelp(managerCommands);
|
|
487
487
|
process.exit(0);
|
|
488
488
|
}
|
|
489
489
|
if (isSubcommand(rawArg, managerCommands)) {
|
|
@@ -523,7 +523,7 @@ if (config.useRust) {
|
|
|
523
523
|
}
|
|
524
524
|
}
|
|
525
525
|
if (rustBinary) {
|
|
526
|
-
const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-
|
|
526
|
+
const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-Dy_o9sj6.js");
|
|
527
527
|
const rustArgs = buildRustArgs(process.argv, config.cli, SUPPORTED_CLIS);
|
|
528
528
|
if (config.verbose) {
|
|
529
529
|
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-
|
|
1
|
+
import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-DBwi5BWW.js";
|
|
2
2
|
import "./logger-CDIsZ-Pp.js";
|
|
3
|
-
import "./versionChecker-
|
|
3
|
+
import "./versionChecker-DVv-WHIp.js";
|
|
4
4
|
import "./pidStore-BfoBWUjc.js";
|
|
5
5
|
import "./globalPidIndex-DlmmJlO8.js";
|
|
6
6
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import "./ts-
|
|
1
|
+
import "./ts-DBwi5BWW.js";
|
|
2
2
|
import "./logger-CDIsZ-Pp.js";
|
|
3
|
-
import "./versionChecker-
|
|
3
|
+
import "./versionChecker-DVv-WHIp.js";
|
|
4
4
|
import "./pidStore-BfoBWUjc.js";
|
|
5
5
|
import "./globalPidIndex-DlmmJlO8.js";
|
|
6
|
-
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-
|
|
6
|
+
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-D0nNJGNj.js";
|
|
7
7
|
import { l as resolveSpawnCwd } from "./workspaceConfig-D3OH7and.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-
|
|
144
|
+
//# sourceMappingURL=schedule-BPUyALF_.js.map
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import "./ts-
|
|
1
|
+
import "./ts-DBwi5BWW.js";
|
|
2
2
|
import "./logger-CDIsZ-Pp.js";
|
|
3
|
-
import { r as getInstalledPackage } from "./versionChecker-
|
|
3
|
+
import { r as getInstalledPackage } from "./versionChecker-DVv-WHIp.js";
|
|
4
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-
|
|
8
|
+
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-D0nNJGNj.js";
|
|
9
9
|
import "./e2e-Bfw7qL9O.js";
|
|
10
10
|
import "./webrtcLink-CBZkZ-LT.js";
|
|
11
11
|
import "./remotes-T6nf0t3K.js";
|
|
12
12
|
import { c as isProvisionAllowed, i as getSpawnHook, l as resolveSpawnCwd, r as getProvisionRoot, s as hasSpawnHook } from "./workspaceConfig-D3OH7and.js";
|
|
13
|
-
import { A as writeToIpc, D as snapshotStatus, S as resolveOne, b as renderRawLog, c as extractNeedsInput, h as listRecords, i as controlCodeFromName, l as extractTaskCounts, o as deriveLiveStatus, v as readNotes, y as readPtysize } from "./subcommands-
|
|
13
|
+
import { A as writeToIpc, D as snapshotStatus, S as resolveOne, b as renderRawLog, c as extractNeedsInput, h as listRecords, i as controlCodeFromName, l as extractTaskCounts, o as deriveLiveStatus, v as readNotes, y as readPtysize } from "./subcommands-VimbMSTG.js";
|
|
14
14
|
import { r as spawnRejectionReason } from "./spawnGate-B_VDMXYL.js";
|
|
15
15
|
import yargs from "yargs";
|
|
16
16
|
import { mkdir, open, readFile, stat, unlink, writeFile } from "fs/promises";
|
|
@@ -1445,4 +1445,4 @@ Options:
|
|
|
1445
1445
|
|
|
1446
1446
|
//#endregion
|
|
1447
1447
|
export { cmdServe };
|
|
1448
|
-
//# sourceMappingURL=serve-
|
|
1448
|
+
//# sourceMappingURL=serve-DAuLdt_d.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-
|
|
35
|
+
const { cmdServe } = await import("./serve-DAuLdt_d.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-
|
|
45
|
+
//# sourceMappingURL=setup-YLHUtCC0.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-T6nf0t3K.js";
|
|
7
|
-
import { A as writeToIpc, C as resolveReadWindow, D as snapshotStatus, E as runSubcommand, O as stopTipForCli, S as resolveOne, T as restartHintLines, _ as menuSelectKeys, a as cursorAbs, b as renderRawLog, c as extractNeedsInput, d as isAgentStuck, f as isExitRequest, g as matchKeyword, h as listRecords, i as controlCodeFromName, k as writeKeysPaced, l as extractTaskCounts, m as isSubcommand, n as READ_PAGE_DEFAULT, o as deriveLiveStatus, p as isPidAlive, r as cmdHelp, s as extractMenu, t as GRACEFUL_EXIT_COMMANDS, u as finalizedLines, v as readNotes, w as resolveResumeArgs, x as renderRawLogLines, y as readPtysize } from "./subcommands-
|
|
7
|
+
import { A as writeToIpc, C as resolveReadWindow, D as snapshotStatus, E as runSubcommand, O as stopTipForCli, S as resolveOne, T as restartHintLines, _ as menuSelectKeys, a as cursorAbs, b as renderRawLog, c as extractNeedsInput, d as isAgentStuck, f as isExitRequest, g as matchKeyword, h as listRecords, i as controlCodeFromName, k as writeKeysPaced, l as extractTaskCounts, m as isSubcommand, n as READ_PAGE_DEFAULT, o as deriveLiveStatus, p as isPidAlive, r as cmdHelp, s as extractMenu, t as GRACEFUL_EXIT_COMMANDS, u as finalizedLines, v as readNotes, w as resolveResumeArgs, x as renderRawLogLines, y as readPtysize } from "./subcommands-VimbMSTG.js";
|
|
8
8
|
|
|
9
9
|
export { cmdHelp, isSubcommand, runSubcommand };
|
|
@@ -597,15 +597,15 @@ async function runSubcommand(argv) {
|
|
|
597
597
|
case "restart": return await cmdRestart(rest);
|
|
598
598
|
case "note": return await cmdNote(rest);
|
|
599
599
|
case "serve": {
|
|
600
|
-
const { cmdServe } = await import("./serve-
|
|
600
|
+
const { cmdServe } = await import("./serve-DAuLdt_d.js");
|
|
601
601
|
return cmdServe(rest);
|
|
602
602
|
}
|
|
603
603
|
case "setup": {
|
|
604
|
-
const { cmdSetup } = await import("./setup-
|
|
604
|
+
const { cmdSetup } = await import("./setup-YLHUtCC0.js");
|
|
605
605
|
return cmdSetup(rest);
|
|
606
606
|
}
|
|
607
607
|
case "schedule": {
|
|
608
|
-
const { cmdSchedule } = await import("./schedule-
|
|
608
|
+
const { cmdSchedule } = await import("./schedule-BPUyALF_.js");
|
|
609
609
|
return cmdSchedule(rest);
|
|
610
610
|
}
|
|
611
611
|
case "remote": {
|
|
@@ -624,9 +624,29 @@ async function runSubcommand(argv) {
|
|
|
624
624
|
return 1;
|
|
625
625
|
}
|
|
626
626
|
}
|
|
627
|
-
|
|
627
|
+
/**
|
|
628
|
+
* The banner shown by `ay help` / `ay -h` when this process is itself running
|
|
629
|
+
* inside an agent (`AGENT_YES_PID` set — see resolveSender). Answers the three
|
|
630
|
+
* things a nested agent actually needs: who am I, who spawned me, and how do I
|
|
631
|
+
* drive sub-agents of my own — so it doesn't have to rediscover the fan-out
|
|
632
|
+
* primitives (spawn / ay ls forest / ay ls --watch) from scratch every session.
|
|
633
|
+
*/
|
|
634
|
+
async function buildAgentContextSection(self) {
|
|
635
|
+
const hasParentPid = typeof self.parent_pid === "number" && self.parent_pid > 0;
|
|
636
|
+
const parent = hasParentPid ? (await listRecords(void 0, {
|
|
637
|
+
all: true,
|
|
638
|
+
active: false,
|
|
639
|
+
json: false,
|
|
640
|
+
latest: false,
|
|
641
|
+
cwdScope: null
|
|
642
|
+
})).find((r) => r.wrapper_pid === self.parent_pid) : void 0;
|
|
643
|
+
return `You are running inside an agent:\n ${`You are agent pid ${self.pid} (${self.cli}) in ${shortenPath(self.cwd)}.`}\n ${!hasParentPid ? `Top-level agent — no parent (started from a human shell or scheduler).` : parent ? `Spawned by agent pid ${parent.pid} (${parent.cli}) in ${shortenPath(parent.cwd)}.` : `Nested under a parent (wrapper pid ${self.parent_pid}) whose record isn't in the local registry.`}\n\nAs an agent, you can:\n Spawn a sub-agent:\n ay <cli> -- "<prompt>" auto-links as your child\n ay claude --model sonnet --advisor opus -- "<prompt>" routine task\n ay claude --model opus --advisor fable -- "<prompt>" complex task\n (pick --model by task complexity so easy tasks don't cost like hard ones;\n --advisor is a claude-cli flag — only takes effect for claude/cy)\n List agents (your children nest under your own pid in the tree):\n ay ls --cwd ${shortenPath(self.cwd)}\n Watch agent state changes, scoped to your workspace:\n ay ls --watch --cwd ${shortenPath(self.cwd)}\n (NDJSON stream of state changes across every matched agent — one watcher\n for the whole fan-out instead of N \`ay status --watch\`es)\n Read one sub-agent's output:\n ay tail -f <pid> follow live output (no single command tails\n many agents' content at once yet — loop\n \`ay ls --json\` pids into per-pid \`ay tail\`)\n\n`;
|
|
644
|
+
}
|
|
645
|
+
async function cmdHelp(managerCommands = true) {
|
|
628
646
|
const setupLine = managerCommands ? ` ay setup guided setup: pick a workspace, share to agent-yes.com\n` : ``;
|
|
629
|
-
|
|
647
|
+
const self = process.env.AGENT_YES_PID ? await resolveSender() : null;
|
|
648
|
+
const agentSection = self ? await buildAgentContextSection(self) : "";
|
|
649
|
+
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 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");
|
|
630
650
|
return 0;
|
|
631
651
|
}
|
|
632
652
|
function matchKeyword(record, keyword) {
|
|
@@ -3005,4 +3025,4 @@ async function cmdResultSet(rest) {
|
|
|
3005
3025
|
|
|
3006
3026
|
//#endregion
|
|
3007
3027
|
export { writeToIpc as A, resolveReadWindow as C, snapshotStatus as D, runSubcommand as E, stopTipForCli as O, resolveOne as S, restartHintLines as T, menuSelectKeys as _, cursorAbs as a, renderRawLog as b, extractNeedsInput as c, isAgentStuck as d, isExitRequest as f, matchKeyword as g, listRecords as h, controlCodeFromName as i, writeKeysPaced as k, extractTaskCounts as l, isSubcommand as m, READ_PAGE_DEFAULT as n, deriveLiveStatus as o, isPidAlive as p, cmdHelp as r, extractMenu as s, GRACEFUL_EXIT_COMMANDS as t, finalizedLines as u, readNotes as v, resolveResumeArgs as w, renderRawLogLines as x, readPtysize as y };
|
|
3008
|
-
//# sourceMappingURL=subcommands-
|
|
3028
|
+
//# sourceMappingURL=subcommands-VimbMSTG.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-
|
|
2
|
+
import { r as getInstalledPackage } from "./versionChecker-DVv-WHIp.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
5
|
import { t as PidStore } from "./pidStore-BfoBWUjc.js";
|
|
@@ -289,8 +289,14 @@ var pty_default = pty;
|
|
|
289
289
|
|
|
290
290
|
//#endregion
|
|
291
291
|
//#region ts/removeControlCharacters.ts
|
|
292
|
+
const ESC = String.fromCharCode(27);
|
|
293
|
+
const C1 = String.fromCharCode(155);
|
|
294
|
+
const BACKSLASH = String.fromCharCode(92);
|
|
295
|
+
const ST_PATTERN_SOURCE = ESC + BACKSLASH + BACKSLASH;
|
|
296
|
+
const OSC_PATTERN = new RegExp(ESC + "][^\x07\x1B]*(?:\x07|" + ST_PATTERN_SOURCE + ")", "g");
|
|
297
|
+
const CSI_PATTERN = new RegExp("[" + ESC + C1 + "][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]", "g");
|
|
292
298
|
function removeControlCharacters(str) {
|
|
293
|
-
return str.replace(
|
|
299
|
+
return str.replace(OSC_PATTERN, "").replace(CSI_PATTERN, "");
|
|
294
300
|
}
|
|
295
301
|
|
|
296
302
|
//#endregion
|
|
@@ -474,6 +480,10 @@ var IdleWaiter = class {
|
|
|
474
480
|
this.lastActivityTime = Date.now();
|
|
475
481
|
return this;
|
|
476
482
|
}
|
|
483
|
+
/** Milliseconds since the last ping. */
|
|
484
|
+
idleTimeMs() {
|
|
485
|
+
return Date.now() - this.lastActivityTime;
|
|
486
|
+
}
|
|
477
487
|
async wait(ms) {
|
|
478
488
|
while (this.lastActivityTime >= Date.now() - ms) await new Promise((resolve) => setTimeout(resolve, this.checkInterval));
|
|
479
489
|
}
|
|
@@ -511,8 +521,12 @@ async function sendEnter(context, waitms = 1e3) {
|
|
|
511
521
|
logger.debug(`sendEnter| idleWait took ${String(Date.now() - st)}ms`);
|
|
512
522
|
context.nextStdout.unready();
|
|
513
523
|
context.shell.write("\r");
|
|
524
|
+
context.idleWaiter.ping();
|
|
514
525
|
for (const ms of [1e3, 3e3]) await Promise.race([context.nextStdout.wait(), new Promise((resolve) => setTimeout(() => {
|
|
515
|
-
if (!context.nextStdout.isReady)
|
|
526
|
+
if (!context.nextStdout.isReady) {
|
|
527
|
+
context.shell.write("\r");
|
|
528
|
+
context.idleWaiter.ping();
|
|
529
|
+
}
|
|
516
530
|
resolve();
|
|
517
531
|
}, ms))]);
|
|
518
532
|
}
|
|
@@ -543,11 +557,21 @@ async function sendMessage(context, message, { waitForReady = true } = {}) {
|
|
|
543
557
|
const AUTO_RETRY_BASE_SECS = 8;
|
|
544
558
|
const AUTO_RETRY_MAX_DELAY_SECS = 256;
|
|
545
559
|
const AUTO_RETRY_GIVE_UP_MS = 8 * 3600 * 1e3;
|
|
560
|
+
const AUTO_RETRY_MIN_IDLE_MS = 5e3;
|
|
546
561
|
/** Backoff (ms) before the Nth consecutive auto-retry — doubles, then caps. */
|
|
547
562
|
function autoRetryBackoffMs(streak) {
|
|
548
563
|
const shift = Math.min(streak, 20);
|
|
549
564
|
return Math.min(AUTO_RETRY_BASE_SECS * 2 ** shift, AUTO_RETRY_MAX_DELAY_SECS) * 1e3;
|
|
550
565
|
}
|
|
566
|
+
/**
|
|
567
|
+
* Whether a scheduled auto-retry may actually fire: the agent must be sitting
|
|
568
|
+
* idle at a ready prompt (not mid-work) AND the terminal must have been quiet
|
|
569
|
+
* for at least `minIdleMs` — see AUTO_RETRY_MIN_IDLE_MS. Mirrors Rust's
|
|
570
|
+
* should_fire_retry in rs/src/context.rs.
|
|
571
|
+
*/
|
|
572
|
+
function shouldFireRetry(working, ready, idleMs, minIdleMs) {
|
|
573
|
+
return !working && ready && idleMs >= minIdleMs;
|
|
574
|
+
}
|
|
551
575
|
|
|
552
576
|
//#endregion
|
|
553
577
|
//#region ts/core/logging.ts
|
|
@@ -1503,17 +1527,13 @@ async function agentYes({ cli, cliArgs = [], skipPermissions = false, prompt, ro
|
|
|
1503
1527
|
retryNextAt = null;
|
|
1504
1528
|
retryStartedAt = null;
|
|
1505
1529
|
retryStreak = 0;
|
|
1506
|
-
} else if (now >= retryNextAt)
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
ctx.messageContext.shell.write("retry\r");
|
|
1514
|
-
ctx.idleWaiter.ping();
|
|
1515
|
-
retryNextAt = now + autoRetryBackoffMs(retryStreak);
|
|
1516
|
-
}
|
|
1530
|
+
} else if (now >= retryNextAt) if (!shouldFireRetry(conf.working?.some((rx) => rx.test(autoRetryScreen)) ?? false, conf.ready?.some((rx) => rx.test(autoRetryScreen)) ?? false, ctx.idleWaiter.idleTimeMs(), AUTO_RETRY_MIN_IDLE_MS)) retryNextAt = now + 500;
|
|
1531
|
+
else {
|
|
1532
|
+
retryStreak += 1;
|
|
1533
|
+
logger.warn(`[${cli}-yes] auto-retry: typing 'retry' (attempt ${retryStreak})`);
|
|
1534
|
+
ctx.messageContext.shell.write("retry\r");
|
|
1535
|
+
ctx.idleWaiter.ping();
|
|
1536
|
+
retryNextAt = now + autoRetryBackoffMs(retryStreak);
|
|
1517
1537
|
}
|
|
1518
1538
|
}
|
|
1519
1539
|
if (rendered === lastHeartbeatRendered) return;
|
|
@@ -1689,10 +1709,11 @@ async function agentYes({ cli, cliArgs = [], skipPermissions = false, prompt, ro
|
|
|
1689
1709
|
writable: new WritableStream({ write: async (data) => {
|
|
1690
1710
|
await ctx.stdinReady.wait();
|
|
1691
1711
|
shell.write(data);
|
|
1712
|
+
ctx.idleWaiter.ping();
|
|
1692
1713
|
} }),
|
|
1693
1714
|
readable: xtermProxy.readable
|
|
1694
|
-
}).forEach(() => {
|
|
1695
|
-
ctx.idleWaiter.ping();
|
|
1715
|
+
}).forEach((chunk) => {
|
|
1716
|
+
if (removeControlCharacters(chunk).trim()) ctx.idleWaiter.ping();
|
|
1696
1717
|
pidStore.updateStatus(shell.pid, "active").catch(() => null);
|
|
1697
1718
|
ctx.nextStdout.ready();
|
|
1698
1719
|
}).forkTo(async function rawLogger(f) {
|
|
@@ -1803,4 +1824,4 @@ function sleep(ms) {
|
|
|
1803
1824
|
|
|
1804
1825
|
//#endregion
|
|
1805
1826
|
export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
|
|
1806
|
-
//# sourceMappingURL=ts-
|
|
1827
|
+
//# sourceMappingURL=ts-DBwi5BWW.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.
|
|
10
|
+
var version = "1.168.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-
|
|
218
|
+
//# sourceMappingURL=versionChecker-DVv-WHIp.js.map
|
package/package.json
CHANGED
package/ts/autoRetry.spec.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { AUTO_RETRY_MAX_DELAY_SECS, autoRetryBackoffMs } from "./autoRetry.ts";
|
|
2
|
+
import { AUTO_RETRY_MAX_DELAY_SECS, autoRetryBackoffMs, shouldFireRetry } from "./autoRetry.ts";
|
|
3
3
|
|
|
4
4
|
describe("autoRetryBackoffMs", () => {
|
|
5
5
|
it("doubles 8,16,32,…,256 then caps", () => {
|
|
@@ -17,3 +17,18 @@ describe("autoRetryBackoffMs", () => {
|
|
|
17
17
|
expect(Number.isFinite(autoRetryBackoffMs(1000))).toBe(true);
|
|
18
18
|
});
|
|
19
19
|
});
|
|
20
|
+
|
|
21
|
+
describe("shouldFireRetry", () => {
|
|
22
|
+
it("requires ready + not working + past the idle window", () => {
|
|
23
|
+
// Busy — never fire even if otherwise ready and quiet.
|
|
24
|
+
expect(shouldFireRetry(true, true, 10_000, 5_000)).toBe(false);
|
|
25
|
+
// Not at a recognized ready prompt — don't fire.
|
|
26
|
+
expect(shouldFireRetry(false, false, 10_000, 5_000)).toBe(false);
|
|
27
|
+
// Ready and idle, but the quiet window hasn't elapsed yet (user may still
|
|
28
|
+
// be mid-typing) — defer.
|
|
29
|
+
expect(shouldFireRetry(false, true, 4_999, 5_000)).toBe(false);
|
|
30
|
+
// Ready, idle, and past the quiet window — fire.
|
|
31
|
+
expect(shouldFireRetry(false, true, 5_000, 5_000)).toBe(true);
|
|
32
|
+
expect(shouldFireRetry(false, true, 10_000, 5_000)).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
});
|
package/ts/autoRetry.ts
CHANGED
|
@@ -8,9 +8,32 @@ export const AUTO_RETRY_BASE_SECS = 8; // first backoff; doubles each consecutiv
|
|
|
8
8
|
export const AUTO_RETRY_MAX_DELAY_SECS = 256; // cap: 8,16,32,…,256 then hold
|
|
9
9
|
export const AUTO_RETRY_GIVE_UP_MS = 8 * 3600 * 1000; // stop after 8h (claude's usage window is ~5h)
|
|
10
10
|
|
|
11
|
+
// Minimum quiet time (no CLI output, no forwarded stdin — see idleWaiter.ping()
|
|
12
|
+
// call sites) required before a scheduled auto-retry may actually fire, on top
|
|
13
|
+
// of the backoff delay above. The backoff schedule alone can elapse while the
|
|
14
|
+
// user is mid-typing into the prompt; typing "retry" + Enter over that would
|
|
15
|
+
// submit a mangled line. Deliberately short — this only debounces against
|
|
16
|
+
// active typing, not a real excuse to delay recovery.
|
|
17
|
+
export const AUTO_RETRY_MIN_IDLE_MS = 5_000;
|
|
18
|
+
|
|
11
19
|
/** Backoff (ms) before the Nth consecutive auto-retry — doubles, then caps. */
|
|
12
20
|
export function autoRetryBackoffMs(streak: number): number {
|
|
13
21
|
const shift = Math.min(streak, 20); // guard against absurd streaks blowing up 2 ** n
|
|
14
22
|
const secs = Math.min(AUTO_RETRY_BASE_SECS * 2 ** shift, AUTO_RETRY_MAX_DELAY_SECS);
|
|
15
23
|
return secs * 1000;
|
|
16
24
|
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Whether a scheduled auto-retry may actually fire: the agent must be sitting
|
|
28
|
+
* idle at a ready prompt (not mid-work) AND the terminal must have been quiet
|
|
29
|
+
* for at least `minIdleMs` — see AUTO_RETRY_MIN_IDLE_MS. Mirrors Rust's
|
|
30
|
+
* should_fire_retry in rs/src/context.rs.
|
|
31
|
+
*/
|
|
32
|
+
export function shouldFireRetry(
|
|
33
|
+
working: boolean,
|
|
34
|
+
ready: boolean,
|
|
35
|
+
idleMs: number,
|
|
36
|
+
minIdleMs: number,
|
|
37
|
+
): boolean {
|
|
38
|
+
return !working && ready && idleMs >= minIdleMs;
|
|
39
|
+
}
|
package/ts/cli.ts
CHANGED
|
@@ -30,7 +30,7 @@ import { buildRustArgs } from "./buildRustArgs.ts";
|
|
|
30
30
|
const isHelpFlag = rawArg === "-h" || rawArg === "--help";
|
|
31
31
|
const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands.ts");
|
|
32
32
|
if (isHelpFlag && process.argv.length === 3) {
|
|
33
|
-
cmdHelp(managerCommands);
|
|
33
|
+
await cmdHelp(managerCommands);
|
|
34
34
|
process.exit(0);
|
|
35
35
|
}
|
|
36
36
|
if (isSubcommand(rawArg, managerCommands)) {
|
package/ts/idleWaiter.spec.ts
CHANGED
|
@@ -59,6 +59,18 @@ describe("IdleWaiter", () => {
|
|
|
59
59
|
expect(result).toBe(waiter);
|
|
60
60
|
});
|
|
61
61
|
|
|
62
|
+
it("idleTimeMs reflects elapsed time since the last ping", async () => {
|
|
63
|
+
const waiter = new IdleWaiter();
|
|
64
|
+
waiter.ping();
|
|
65
|
+
expect(waiter.idleTimeMs()).toBeLessThan(20);
|
|
66
|
+
|
|
67
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
68
|
+
expect(waiter.idleTimeMs()).toBeGreaterThanOrEqual(50);
|
|
69
|
+
|
|
70
|
+
waiter.ping();
|
|
71
|
+
expect(waiter.idleTimeMs()).toBeLessThan(20);
|
|
72
|
+
});
|
|
73
|
+
|
|
62
74
|
it("should wait until idle period has passed", async () => {
|
|
63
75
|
const waiter = new IdleWaiter();
|
|
64
76
|
waiter.checkInterval = 10;
|
package/ts/idleWaiter.ts
CHANGED
|
@@ -24,6 +24,11 @@ export class IdleWaiter {
|
|
|
24
24
|
return this;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/** Milliseconds since the last ping. */
|
|
28
|
+
idleTimeMs() {
|
|
29
|
+
return Date.now() - this.lastActivityTime;
|
|
30
|
+
}
|
|
31
|
+
|
|
27
32
|
async wait(ms: number) {
|
|
28
33
|
while (this.lastActivityTime >= Date.now() - ms)
|
|
29
34
|
await new Promise((resolve) => setTimeout(resolve, this.checkInterval));
|
package/ts/index.ts
CHANGED
|
@@ -18,7 +18,12 @@ import { logger } from "./logger.ts";
|
|
|
18
18
|
import { createFifoStream } from "./beta/fifo.ts";
|
|
19
19
|
import { PidStore } from "./pidStore.ts";
|
|
20
20
|
import { sendEnter, sendMessage } from "./core/messaging.ts";
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
AUTO_RETRY_GIVE_UP_MS,
|
|
23
|
+
AUTO_RETRY_MIN_IDLE_MS,
|
|
24
|
+
autoRetryBackoffMs,
|
|
25
|
+
shouldFireRetry,
|
|
26
|
+
} from "./autoRetry.ts";
|
|
22
27
|
import {
|
|
23
28
|
initializeLogPaths,
|
|
24
29
|
setupDebugLogging,
|
|
@@ -805,8 +810,12 @@ export default async function agentYes({
|
|
|
805
810
|
} else if (now >= retryNextAt) {
|
|
806
811
|
const working = conf.working?.some((rx: RegExp) => rx.test(autoRetryScreen)) ?? false;
|
|
807
812
|
const readyNow = conf.ready?.some((rx: RegExp) => rx.test(autoRetryScreen)) ?? false;
|
|
808
|
-
|
|
809
|
-
|
|
813
|
+
// Also require a few quiet seconds on top of the backoff delay, so a
|
|
814
|
+
// scheduled retry doesn't collide with a line the user is actively
|
|
815
|
+
// typing into the prompt (see AUTO_RETRY_MIN_IDLE_MS).
|
|
816
|
+
const idleMs = ctx.idleWaiter.idleTimeMs();
|
|
817
|
+
if (!shouldFireRetry(working, readyNow, idleMs, AUTO_RETRY_MIN_IDLE_MS)) {
|
|
818
|
+
retryNextAt = now + 500; // busy / not at prompt / still active — re-check shortly
|
|
810
819
|
} else {
|
|
811
820
|
retryStreak += 1;
|
|
812
821
|
logger.warn(`[${cli}-yes] auto-retry: typing 'retry' (attempt ${retryStreak})`);
|
|
@@ -1096,13 +1105,22 @@ export default async function agentYes({
|
|
|
1096
1105
|
write: async (data) => {
|
|
1097
1106
|
await ctx.stdinReady.wait();
|
|
1098
1107
|
shell.write(data);
|
|
1108
|
+
// Forwarded user input counts as activity too (mirrors the Rust
|
|
1109
|
+
// runtime's ping on stdin forward) — the auto-retry idle gate below
|
|
1110
|
+
// must not fire while the user is actively typing.
|
|
1111
|
+
ctx.idleWaiter.ping();
|
|
1099
1112
|
},
|
|
1100
1113
|
}),
|
|
1101
1114
|
readable: xtermProxy.readable,
|
|
1102
1115
|
})
|
|
1103
1116
|
|
|
1104
|
-
.forEach(() => {
|
|
1105
|
-
|
|
1117
|
+
.forEach((chunk) => {
|
|
1118
|
+
// Only ping activity if there's visible content (not just ANSI/cursor
|
|
1119
|
+
// control sequences) — mirrors the Rust runtime's handle_output gate.
|
|
1120
|
+
// Without this, periodic control-only chatter (e.g. cursor position
|
|
1121
|
+
// queries) would keep resetting the auto-retry idle clock and the
|
|
1122
|
+
// scheduled retry could defer indefinitely without ever firing.
|
|
1123
|
+
if (removeControlCharacters(chunk).trim()) ctx.idleWaiter.ping();
|
|
1106
1124
|
pidStore.updateStatus(shell.pid, "active").catch(() => null);
|
|
1107
1125
|
ctx.nextStdout.ready();
|
|
1108
1126
|
})
|
|
@@ -70,4 +70,25 @@ describe("removeControlCharacters", () => {
|
|
|
70
70
|
const expected = "TextClearLine";
|
|
71
71
|
expect(removeControlCharacters(input)).toBe(expected);
|
|
72
72
|
});
|
|
73
|
+
|
|
74
|
+
it("should remove OSC sequences (e.g. window title updates)", () => {
|
|
75
|
+
const input = "Before\u001b]0;window title\u0007After";
|
|
76
|
+
const expected = "BeforeAfter";
|
|
77
|
+
expect(removeControlCharacters(input)).toBe(expected);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("does NOT strip trailing text when an OSC sequence is unterminated (no BEL)", () => {
|
|
81
|
+
const input = "Before\u001b]0;titleAfter";
|
|
82
|
+
expect(removeControlCharacters(input)).toBe(input);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should remove OSC sequences terminated by ST (ESC backslash), not just BEL", () => {
|
|
86
|
+
const input = "Before\u001b]8;;https://example.com\u001b\\After";
|
|
87
|
+
expect(removeControlCharacters(input)).toBe("BeforeAfter");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("does not let an ST-terminated OSC run on and eat real text before a later BEL", () => {
|
|
91
|
+
const input = "Before\u001b]0;t\u001b\\visible\u0007After";
|
|
92
|
+
expect(removeControlCharacters(input)).toBe("Beforevisible\u0007After");
|
|
93
|
+
});
|
|
73
94
|
});
|
|
@@ -1,8 +1,34 @@
|
|
|
1
|
+
const ESC = String.fromCharCode(0x1b);
|
|
2
|
+
const C1 = String.fromCharCode(0x9b);
|
|
3
|
+
const BEL = String.fromCharCode(0x07);
|
|
4
|
+
const BACKSLASH = String.fromCharCode(0x5c);
|
|
5
|
+
// String Terminator (ESC \) as REGEX SOURCE text: a literal backslash inside a
|
|
6
|
+
// regex pattern is itself an escape character, so matching one literal
|
|
7
|
+
// backslash requires two backslash characters in the pattern source.
|
|
8
|
+
const ST_PATTERN_SOURCE = ESC + BACKSLASH + BACKSLASH;
|
|
9
|
+
|
|
10
|
+
// OSC sequences (window/tab title updates, hyperlinks, etc.): ESC ] ...
|
|
11
|
+
// terminated by either BEL or ST (ESC \) — both are valid per-spec and used
|
|
12
|
+
// by real terminal apps. Not covered by the CSI pattern below — without this,
|
|
13
|
+
// e.g. a periodic title update would count as "visible" content to callers
|
|
14
|
+
// that gate activity on non-empty output.
|
|
15
|
+
// The terminator is required, not optional: an unterminated ESC]... (the
|
|
16
|
+
// terminator never arrives, e.g. a truncated chunk boundary) must NOT strip
|
|
17
|
+
// through to end-of-string — that would eat real trailing text as if it were
|
|
18
|
+
// part of the title sequence. The body excludes ESC too (not just BEL) so an
|
|
19
|
+
// ST-terminated sequence can't accidentally run on and swallow real text up
|
|
20
|
+
// to some unrelated, later BEL.
|
|
21
|
+
const OSC_PATTERN = new RegExp(
|
|
22
|
+
ESC + "][^" + BEL + ESC + "]*(?:" + BEL + "|" + ST_PATTERN_SOURCE + ")",
|
|
23
|
+
"g",
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
// Matches control characters in the C0 and C1 ranges, including Delete (U+007F)
|
|
27
|
+
const CSI_PATTERN = new RegExp(
|
|
28
|
+
"[" + ESC + C1 + "][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]",
|
|
29
|
+
"g",
|
|
30
|
+
);
|
|
31
|
+
|
|
1
32
|
export function removeControlCharacters(str: string): string {
|
|
2
|
-
|
|
3
|
-
return str.replace(
|
|
4
|
-
// eslint-disable-next-line no-control-regex This is a control regex
|
|
5
|
-
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
|
|
6
|
-
"",
|
|
7
|
-
);
|
|
33
|
+
return str.replace(OSC_PATTERN, "").replace(CSI_PATTERN, "");
|
|
8
34
|
}
|
package/ts/subcommands.spec.ts
CHANGED
|
@@ -113,25 +113,134 @@ describe("subcommands.isSubcommand", () => {
|
|
|
113
113
|
});
|
|
114
114
|
|
|
115
115
|
describe("subcommands.cmdHelp", () => {
|
|
116
|
-
|
|
116
|
+
const capture = async (managerCommands?: boolean) => {
|
|
117
117
|
const { cmdHelp } = await loadModule();
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
expect(capture()).toContain("ay setup"); //
|
|
133
|
-
expect(capture(
|
|
134
|
-
expect(capture(false)).toContain("ay
|
|
118
|
+
let out = "";
|
|
119
|
+
const spy = vi.spyOn(process.stdout, "write").mockImplementation((s: unknown) => {
|
|
120
|
+
out += String(s);
|
|
121
|
+
return true;
|
|
122
|
+
});
|
|
123
|
+
try {
|
|
124
|
+
await cmdHelp(managerCommands);
|
|
125
|
+
} finally {
|
|
126
|
+
spy.mockRestore();
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
it("hides the manager-only `setup` line for cli-bound aliases", async () => {
|
|
132
|
+
expect(await capture(true)).toContain("ay setup"); // manager
|
|
133
|
+
expect(await capture()).toContain("ay setup"); // default = manager
|
|
134
|
+
expect(await capture(false)).not.toContain("ay setup"); // cli-bound alias (cy)
|
|
135
|
+
expect(await capture(false)).toContain("ay ls"); // universal commands still shown
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("stays plain for a human shell (no AGENT_YES_PID)", async () => {
|
|
139
|
+
const out = await capture();
|
|
140
|
+
expect(out).not.toContain("You are running inside an agent");
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("prints self + parent identity and sub-agent guidance when nested in an agent", async () => {
|
|
144
|
+
const { appendGlobalPid } = await import("./globalPidIndex.ts");
|
|
145
|
+
const parentWrapperPid = 555001;
|
|
146
|
+
const selfWrapperPid = 555002;
|
|
147
|
+
await appendGlobalPid({
|
|
148
|
+
pid: 900001,
|
|
149
|
+
cli: "codex",
|
|
150
|
+
prompt: "orchestrate the migration",
|
|
151
|
+
cwd: "/work/parent",
|
|
152
|
+
log_file: null,
|
|
153
|
+
fifo_file: null,
|
|
154
|
+
status: "active",
|
|
155
|
+
exit_code: null,
|
|
156
|
+
exit_reason: null,
|
|
157
|
+
started_at: Date.now(),
|
|
158
|
+
wrapper_pid: parentWrapperPid,
|
|
159
|
+
});
|
|
160
|
+
await appendGlobalPid({
|
|
161
|
+
pid: process.pid,
|
|
162
|
+
cli: "claude",
|
|
163
|
+
prompt: "fix the failing test",
|
|
164
|
+
cwd: "/work/parent/child",
|
|
165
|
+
log_file: null,
|
|
166
|
+
fifo_file: null,
|
|
167
|
+
status: "active",
|
|
168
|
+
exit_code: null,
|
|
169
|
+
exit_reason: null,
|
|
170
|
+
started_at: Date.now(),
|
|
171
|
+
wrapper_pid: selfWrapperPid,
|
|
172
|
+
parent_pid: parentWrapperPid,
|
|
173
|
+
});
|
|
174
|
+
const saved = process.env.AGENT_YES_PID;
|
|
175
|
+
process.env.AGENT_YES_PID = String(selfWrapperPid);
|
|
176
|
+
try {
|
|
177
|
+
const out = await capture();
|
|
178
|
+
expect(out).toContain("You are running inside an agent");
|
|
179
|
+
expect(out).toContain(`You are agent pid ${process.pid} (claude)`);
|
|
180
|
+
expect(out).toContain(`Spawned by agent pid 900001 (codex)`);
|
|
181
|
+
expect(out).toContain("Spawn a sub-agent");
|
|
182
|
+
expect(out).toContain(`ay ls --cwd /work/parent/child`);
|
|
183
|
+
expect(out).toContain(`ay ls --watch --cwd /work/parent/child`);
|
|
184
|
+
} finally {
|
|
185
|
+
if (saved === undefined) delete process.env.AGENT_YES_PID;
|
|
186
|
+
else process.env.AGENT_YES_PID = saved;
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("reports a nested-but-unresolved parent distinctly from top-level", async () => {
|
|
191
|
+
const { appendGlobalPid } = await import("./globalPidIndex.ts");
|
|
192
|
+
const selfWrapperPid = 555004;
|
|
193
|
+
await appendGlobalPid({
|
|
194
|
+
pid: process.pid,
|
|
195
|
+
cli: "claude",
|
|
196
|
+
prompt: null,
|
|
197
|
+
cwd: process.cwd(),
|
|
198
|
+
log_file: null,
|
|
199
|
+
fifo_file: null,
|
|
200
|
+
status: "active",
|
|
201
|
+
exit_code: null,
|
|
202
|
+
exit_reason: null,
|
|
203
|
+
started_at: Date.now(),
|
|
204
|
+
wrapper_pid: selfWrapperPid,
|
|
205
|
+
parent_pid: 999999999, // no record ever registered for this wrapper pid
|
|
206
|
+
});
|
|
207
|
+
const saved = process.env.AGENT_YES_PID;
|
|
208
|
+
process.env.AGENT_YES_PID = String(selfWrapperPid);
|
|
209
|
+
try {
|
|
210
|
+
const out = await capture();
|
|
211
|
+
expect(out).not.toContain("Top-level agent");
|
|
212
|
+
expect(out).toContain("Nested under a parent (wrapper pid 999999999)");
|
|
213
|
+
} finally {
|
|
214
|
+
if (saved === undefined) delete process.env.AGENT_YES_PID;
|
|
215
|
+
else process.env.AGENT_YES_PID = saved;
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("reports top-level (no parent) when parent_pid is absent", async () => {
|
|
220
|
+
const { appendGlobalPid } = await import("./globalPidIndex.ts");
|
|
221
|
+
const selfWrapperPid = 555003;
|
|
222
|
+
await appendGlobalPid({
|
|
223
|
+
pid: process.pid,
|
|
224
|
+
cli: "claude",
|
|
225
|
+
prompt: null,
|
|
226
|
+
cwd: process.cwd(),
|
|
227
|
+
log_file: null,
|
|
228
|
+
fifo_file: null,
|
|
229
|
+
status: "active",
|
|
230
|
+
exit_code: null,
|
|
231
|
+
exit_reason: null,
|
|
232
|
+
started_at: Date.now(),
|
|
233
|
+
wrapper_pid: selfWrapperPid,
|
|
234
|
+
});
|
|
235
|
+
const saved = process.env.AGENT_YES_PID;
|
|
236
|
+
process.env.AGENT_YES_PID = String(selfWrapperPid);
|
|
237
|
+
try {
|
|
238
|
+
const out = await capture();
|
|
239
|
+
expect(out).toContain("Top-level agent");
|
|
240
|
+
} finally {
|
|
241
|
+
if (saved === undefined) delete process.env.AGENT_YES_PID;
|
|
242
|
+
else process.env.AGENT_YES_PID = saved;
|
|
243
|
+
}
|
|
135
244
|
});
|
|
136
245
|
});
|
|
137
246
|
|
package/ts/subcommands.ts
CHANGED
|
@@ -387,14 +387,77 @@ export async function runSubcommand(argv: string[]): Promise<number | null> {
|
|
|
387
387
|
// ay help
|
|
388
388
|
// ---------------------------------------------------------------------------
|
|
389
389
|
|
|
390
|
-
|
|
390
|
+
/**
|
|
391
|
+
* The banner shown by `ay help` / `ay -h` when this process is itself running
|
|
392
|
+
* inside an agent (`AGENT_YES_PID` set — see resolveSender). Answers the three
|
|
393
|
+
* things a nested agent actually needs: who am I, who spawned me, and how do I
|
|
394
|
+
* drive sub-agents of my own — so it doesn't have to rediscover the fan-out
|
|
395
|
+
* primitives (spawn / ay ls forest / ay ls --watch) from scratch every session.
|
|
396
|
+
*/
|
|
397
|
+
async function buildAgentContextSection(self: GlobalPidRecord): Promise<string> {
|
|
398
|
+
const hasParentPid = typeof self.parent_pid === "number" && self.parent_pid > 0;
|
|
399
|
+
const parent = hasParentPid
|
|
400
|
+
? (
|
|
401
|
+
await listRecords(undefined, {
|
|
402
|
+
all: true,
|
|
403
|
+
active: false,
|
|
404
|
+
json: false,
|
|
405
|
+
latest: false,
|
|
406
|
+
cwdScope: null,
|
|
407
|
+
})
|
|
408
|
+
).find((r) => r.wrapper_pid === self.parent_pid)
|
|
409
|
+
: undefined;
|
|
410
|
+
|
|
411
|
+
const whoAmI = `You are agent pid ${self.pid} (${self.cli}) in ${shortenPath(self.cwd)}.`;
|
|
412
|
+
// Three distinct states: no parent at all (top-level); a parent_pid whose
|
|
413
|
+
// record we can resolve; or a parent_pid we can't resolve (its record aged
|
|
414
|
+
// out / lives on a remote) — that last case is still nested, just unknown,
|
|
415
|
+
// so it must not collapse into the "top-level" line.
|
|
416
|
+
const parentLine = !hasParentPid
|
|
417
|
+
? `Top-level agent — no parent (started from a human shell or scheduler).`
|
|
418
|
+
: parent
|
|
419
|
+
? `Spawned by agent pid ${parent.pid} (${parent.cli}) in ${shortenPath(parent.cwd)}.`
|
|
420
|
+
: `Nested under a parent (wrapper pid ${self.parent_pid}) whose record isn't in the local registry.`;
|
|
421
|
+
|
|
422
|
+
return (
|
|
423
|
+
`You are running inside an agent:\n` +
|
|
424
|
+
` ${whoAmI}\n` +
|
|
425
|
+
` ${parentLine}\n` +
|
|
426
|
+
`\n` +
|
|
427
|
+
`As an agent, you can:\n` +
|
|
428
|
+
` Spawn a sub-agent:\n` +
|
|
429
|
+
` ay <cli> -- "<prompt>" auto-links as your child\n` +
|
|
430
|
+
` ay claude --model sonnet --advisor opus -- "<prompt>" routine task\n` +
|
|
431
|
+
` ay claude --model opus --advisor fable -- "<prompt>" complex task\n` +
|
|
432
|
+
` (pick --model by task complexity so easy tasks don't cost like hard ones;\n` +
|
|
433
|
+
` --advisor is a claude-cli flag — only takes effect for claude/cy)\n` +
|
|
434
|
+
` List agents (your children nest under your own pid in the tree):\n` +
|
|
435
|
+
` ay ls --cwd ${shortenPath(self.cwd)}\n` +
|
|
436
|
+
` Watch agent state changes, scoped to your workspace:\n` +
|
|
437
|
+
` ay ls --watch --cwd ${shortenPath(self.cwd)}\n` +
|
|
438
|
+
` (NDJSON stream of state changes across every matched agent — one watcher\n` +
|
|
439
|
+
` for the whole fan-out instead of N \`ay status --watch\`es)\n` +
|
|
440
|
+
` Read one sub-agent's output:\n` +
|
|
441
|
+
` ay tail -f <pid> follow live output (no single command tails\n` +
|
|
442
|
+
` many agents' content at once yet — loop\n` +
|
|
443
|
+
` \`ay ls --json\` pids into per-pid \`ay tail\`)\n` +
|
|
444
|
+
`\n`
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
export async function cmdHelp(managerCommands = true): Promise<number> {
|
|
391
449
|
// `setup` is manager-only — hide it when invoked through a cli-bound alias
|
|
392
450
|
// (cy/claude-yes/…), where `cy setup` runs the agent instead of managing the host.
|
|
393
451
|
const setupLine = managerCommands
|
|
394
452
|
? ` ay setup guided setup: pick a workspace, share to agent-yes.com\n`
|
|
395
453
|
: ``;
|
|
454
|
+
// Only agents carry AGENT_YES_PID — a human shell never sets it — so this
|
|
455
|
+
// section is skipped entirely (no async work at all) for interactive use.
|
|
456
|
+
const self = process.env.AGENT_YES_PID ? await resolveSender() : null;
|
|
457
|
+
const agentSection = self ? await buildAgentContextSection(self) : "";
|
|
396
458
|
process.stdout.write(
|
|
397
|
-
|
|
459
|
+
agentSection +
|
|
460
|
+
`ay - agent-yes CLI\n` +
|
|
398
461
|
`\n` +
|
|
399
462
|
`Management:\n` +
|
|
400
463
|
` ay ls [keyword] list running agents\n` +
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import "./ts-CXxZiZKE.js";
|
|
2
|
-
import "./logger-CDIsZ-Pp.js";
|
|
3
|
-
import "./versionChecker-Dv5DhRs3.js";
|
|
4
|
-
import "./pidStore-BfoBWUjc.js";
|
|
5
|
-
import "./globalPidIndex-DlmmJlO8.js";
|
|
6
|
-
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-Xem8nliL.js";
|
|
7
|
-
|
|
8
|
-
export { SUPPORTED_CLIS };
|