claude-yes 1.83.0 → 1.84.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-DkXclUge.js → SUPPORTED_CLIS-DM0fJTMR.js} +3 -3
- package/dist/cli.js +8 -3
- package/dist/index.js +2 -2
- package/dist/{subcommands-Vt_yQiEZ.js → subcommands-DjO8lthH.js} +123 -33
- package/dist/{ts-DbdWuoGq.js → ts-Bw6gQKyU.js} +17 -3
- package/dist/{versionChecker-Ct-4UPeG.js → versionChecker-CspuhOwO.js} +2 -2
- package/package.json +1 -1
- package/ts/index.ts +21 -0
- package/ts/parseCliArgs.ts +7 -0
- package/ts/subcommands.ts +188 -47
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as CLIS_CONFIG } from "./ts-
|
|
1
|
+
import { t as CLIS_CONFIG } from "./ts-Bw6gQKyU.js";
|
|
2
2
|
import "./logger-B9h0djqx.js";
|
|
3
|
-
import "./versionChecker-
|
|
3
|
+
import "./versionChecker-CspuhOwO.js";
|
|
4
4
|
import "./pidStore-C1JXxoPi.js";
|
|
5
5
|
import "./globalPidIndex-Cr-g75QF.js";
|
|
6
6
|
|
|
@@ -9,4 +9,4 @@ const SUPPORTED_CLIS = Object.keys(CLIS_CONFIG);
|
|
|
9
9
|
|
|
10
10
|
//#endregion
|
|
11
11
|
export { SUPPORTED_CLIS };
|
|
12
|
-
//# sourceMappingURL=SUPPORTED_CLIS-
|
|
12
|
+
//# sourceMappingURL=SUPPORTED_CLIS-DM0fJTMR.js.map
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { n as logger } from "./logger-B9h0djqx.js";
|
|
3
|
-
import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-
|
|
3
|
+
import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-CspuhOwO.js";
|
|
4
4
|
import { argv } from "process";
|
|
5
5
|
import { execFileSync, spawn } from "child_process";
|
|
6
6
|
import ms from "ms";
|
|
@@ -42,6 +42,10 @@ function parseCliArgs(argv, supportedClis) {
|
|
|
42
42
|
type: "boolean",
|
|
43
43
|
description: "Prepend SKILL.md header from current directory to the prompt (helpful for non-Claude agents)",
|
|
44
44
|
default: false
|
|
45
|
+
}).option("swarm-hint", {
|
|
46
|
+
type: "boolean",
|
|
47
|
+
description: "Inject peer discovery hint into agent system prompt when other agents are running (use --no-swarm-hint to opt out)",
|
|
48
|
+
default: true
|
|
45
49
|
}).option("timeout", {
|
|
46
50
|
type: "string",
|
|
47
51
|
description: "Exit after a period of inactivity, e.g., \"5s\" or \"1m\"",
|
|
@@ -221,6 +225,7 @@ function parseCliArgs(argv, supportedClis) {
|
|
|
221
225
|
verbose: parsedArgv.verbose,
|
|
222
226
|
resume: parsedArgv.continue,
|
|
223
227
|
useSkills: parsedArgv.useSkills,
|
|
228
|
+
swarmHint: parsedArgv.swarmHint,
|
|
224
229
|
appendPrompt: parsedArgv.appendPrompt,
|
|
225
230
|
useStdinAppend: Boolean(parsedArgv.stdpush || parsedArgv.ipc || parsedArgv.fifo),
|
|
226
231
|
showVersion: parsedArgv.version,
|
|
@@ -475,7 +480,7 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
|
|
|
475
480
|
}
|
|
476
481
|
}
|
|
477
482
|
{
|
|
478
|
-
const { isSubcommand, runSubcommand } = await import("./subcommands-
|
|
483
|
+
const { isSubcommand, runSubcommand } = await import("./subcommands-DjO8lthH.js");
|
|
479
484
|
if (isSubcommand(process.argv[2])) {
|
|
480
485
|
const code = await runSubcommand(process.argv);
|
|
481
486
|
process.exit(code ?? 0);
|
|
@@ -504,7 +509,7 @@ if (config.useRust) {
|
|
|
504
509
|
}
|
|
505
510
|
}
|
|
506
511
|
if (rustBinary) {
|
|
507
|
-
const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-
|
|
512
|
+
const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-DM0fJTMR.js");
|
|
508
513
|
const rustArgs = buildRustArgs(process.argv, config.cli, SUPPORTED_CLIS);
|
|
509
514
|
if (config.verbose) {
|
|
510
515
|
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-Bw6gQKyU.js";
|
|
2
2
|
import "./logger-B9h0djqx.js";
|
|
3
|
-
import "./versionChecker-
|
|
3
|
+
import "./versionChecker-CspuhOwO.js";
|
|
4
4
|
import "./pidStore-C1JXxoPi.js";
|
|
5
5
|
import "./globalPidIndex-Cr-g75QF.js";
|
|
6
6
|
|
|
@@ -6,7 +6,7 @@ import path from "path";
|
|
|
6
6
|
|
|
7
7
|
//#region ts/subcommands.ts
|
|
8
8
|
/**
|
|
9
|
-
* `
|
|
9
|
+
* `ay ls / read / cat / tail / head / send` subcommand implementations.
|
|
10
10
|
*
|
|
11
11
|
* Mirrors the principles of koho's `terminal-ws-lib.ts` (session list, render
|
|
12
12
|
* via @xterm/headless, keyword-keyed input) — but file-based instead of via
|
|
@@ -61,7 +61,7 @@ async function compactNotes() {
|
|
|
61
61
|
/**
|
|
62
62
|
* Read the per-cwd TS PidStore JSONL and convert to the global record shape,
|
|
63
63
|
* so pre-existing TS agents that were spawned before the global-index mirror
|
|
64
|
-
* shipped still show up in `
|
|
64
|
+
* shipped still show up in `ay ls`. Merging is done in `mergeRecords`.
|
|
65
65
|
*/
|
|
66
66
|
async function readLocalTsPids(cwd) {
|
|
67
67
|
const jsonlPath = path.join(cwd, ".agent-yes", "pid-records.jsonl");
|
|
@@ -118,6 +118,7 @@ const SUBCOMMANDS = new Set([
|
|
|
118
118
|
"ls",
|
|
119
119
|
"list",
|
|
120
120
|
"ps",
|
|
121
|
+
"status",
|
|
121
122
|
"read",
|
|
122
123
|
"cat",
|
|
123
124
|
"tail",
|
|
@@ -126,6 +127,7 @@ const SUBCOMMANDS = new Set([
|
|
|
126
127
|
"restart",
|
|
127
128
|
"note"
|
|
128
129
|
]);
|
|
130
|
+
const IDLE_THRESHOLD_MS = 60 * 1e3;
|
|
129
131
|
function isSubcommand(name) {
|
|
130
132
|
return !!name && SUBCOMMANDS.has(name);
|
|
131
133
|
}
|
|
@@ -142,6 +144,7 @@ async function runSubcommand(argv) {
|
|
|
142
144
|
case "ls":
|
|
143
145
|
case "list":
|
|
144
146
|
case "ps": return await cmdLs(rest);
|
|
147
|
+
case "status": return await cmdStatus(rest);
|
|
145
148
|
case "read":
|
|
146
149
|
case "cat": return await cmdRead(rest, { mode: "cat" });
|
|
147
150
|
case "tail": return await cmdRead(rest, { mode: "tail" });
|
|
@@ -153,7 +156,7 @@ async function runSubcommand(argv) {
|
|
|
153
156
|
}
|
|
154
157
|
} catch (err) {
|
|
155
158
|
const msg = err instanceof Error ? err.message : String(err);
|
|
156
|
-
process.stderr.write(`
|
|
159
|
+
process.stderr.write(`ay ${sub}: ${msg}\n`);
|
|
157
160
|
return 1;
|
|
158
161
|
}
|
|
159
162
|
}
|
|
@@ -173,7 +176,8 @@ function parseArgs(rest) {
|
|
|
173
176
|
"active",
|
|
174
177
|
"follow",
|
|
175
178
|
"json",
|
|
176
|
-
"latest"
|
|
179
|
+
"latest",
|
|
180
|
+
"watch"
|
|
177
181
|
].includes(key) || !next || next.startsWith("-")) flags[key] = true;
|
|
178
182
|
else {
|
|
179
183
|
flags[key] = next;
|
|
@@ -262,7 +266,6 @@ async function cmdLs(rest) {
|
|
|
262
266
|
};
|
|
263
267
|
const fixedWidth = widths.pid + widths.cli + widths.status + widths.age + widths.cwd + 10;
|
|
264
268
|
const promptBudget = Math.max(20, termWidth - fixedWidth - 1);
|
|
265
|
-
const IDLE_THRESHOLD_MS = 60 * 1e3;
|
|
266
269
|
const notes = await readNotes();
|
|
267
270
|
const rows = await Promise.all(records.map(async (r) => {
|
|
268
271
|
let displayStatus;
|
|
@@ -277,8 +280,8 @@ async function cmdLs(rest) {
|
|
|
277
280
|
if (note) {
|
|
278
281
|
label = truncate(note, promptBudget);
|
|
279
282
|
hasNote = true;
|
|
280
|
-
} else if (r.log_file && displayStatus !== "stopped") label = truncate(await extractActivity(r.log_file) ?? r.prompt
|
|
281
|
-
else label = truncate(r.prompt
|
|
283
|
+
} else if (r.log_file && displayStatus !== "stopped") label = truncate(await extractActivity(r.log_file) ?? (r.prompt ? `→ ${r.prompt}` : ""), promptBudget);
|
|
284
|
+
else label = truncate(r.prompt ? `→ ${r.prompt}` : "", promptBudget);
|
|
282
285
|
return {
|
|
283
286
|
pid: String(r.pid),
|
|
284
287
|
cli: r.cli,
|
|
@@ -312,14 +315,17 @@ async function cmdLs(rest) {
|
|
|
312
315
|
const stopped = rows.find((r) => !r._alive);
|
|
313
316
|
const hints = ["\n"];
|
|
314
317
|
if (alive) {
|
|
315
|
-
hints.push(`
|
|
316
|
-
hints.push(`
|
|
317
|
-
hints.push(`
|
|
318
|
-
hints.push(`
|
|
319
|
-
hints.push(`
|
|
318
|
+
hints.push(` ay status ${alive.pid} # JSON status snapshot\n`);
|
|
319
|
+
hints.push(` ay status ${alive.pid} --watch # stream changes as JSON\n`);
|
|
320
|
+
hints.push(` ay tail ${alive.pid} # view latest output\n`);
|
|
321
|
+
hints.push(` ay tail -f ${alive.pid} # follow live output\n`);
|
|
322
|
+
hints.push(` ay send ${alive.pid} "next: ..." # send a prompt (keyword: pid, cwd, or prompt substring)\n`);
|
|
323
|
+
hints.push(` ay send ${alive.pid} "" --code=ctrl-c # interrupt\n`);
|
|
324
|
+
hints.push(` ay note ${alive.pid} "what it's doing" # set a note\n`);
|
|
325
|
+
hints.push(` ay ls --json # machine-readable list for scripts/agents\n`);
|
|
320
326
|
}
|
|
321
|
-
if (stopped) hints.push(`
|
|
322
|
-
if (!alive && !stopped) hints.push(`
|
|
327
|
+
if (stopped) hints.push(` ay restart ${stopped.pid} # restart stopped agent\n`);
|
|
328
|
+
if (!alive && !stopped) hints.push(` ay ls --all # show exited agents\n`);
|
|
323
329
|
process.stderr.write(hints.join(""));
|
|
324
330
|
}
|
|
325
331
|
return 0;
|
|
@@ -392,8 +398,8 @@ async function cmdRead(rest, { mode }) {
|
|
|
392
398
|
return 0;
|
|
393
399
|
}
|
|
394
400
|
process.stderr.write(`
|
|
395
|
-
|
|
396
|
-
|
|
401
|
+
ay ls # list all agents
|
|
402
|
+
ay tail -f ${record.pid} # follow live output\n ay send ${record.pid} "next: ..." # send a prompt\n ay send ${record.pid} "" --code=ctrl-c # interrupt\n`);
|
|
397
403
|
return 0;
|
|
398
404
|
}
|
|
399
405
|
/**
|
|
@@ -473,15 +479,23 @@ function extractActivityFromLines(lines) {
|
|
|
473
479
|
return !s || /^─+$/.test(s) || s.startsWith("? for shortcuts") || /^esc to interrupt/i.test(s) || /\d+%\s*until auto-compact/i.test(s) || /^\/model\s+/i.test(s) || /^⧉\s+In\s+/i.test(s) || /^●\s+(high|medium|low)\s*[·•]/i.test(s) || /^[·•]\s*\d+\s+(left|request)/i.test(s);
|
|
474
480
|
};
|
|
475
481
|
const clean = lines.filter((l) => !isChrome(l));
|
|
476
|
-
const
|
|
482
|
+
const isSpinnerLine = (l) => /^[^\w\s❯>⎿✓✗]\s+[A-Z]\w+[….]/u.test(l.trim()) || /still thinking/i.test(l);
|
|
483
|
+
let lastPromptIdx = -1;
|
|
484
|
+
let lastSpinnerIdx = -1;
|
|
485
|
+
for (let i = clean.length - 1; i >= 0; i--) {
|
|
486
|
+
const l = clean[i].trim();
|
|
487
|
+
if (lastPromptIdx === -1 && l.startsWith("❯")) lastPromptIdx = i;
|
|
488
|
+
if (lastSpinnerIdx === -1 && isSpinnerLine(l)) lastSpinnerIdx = i;
|
|
489
|
+
if (lastPromptIdx !== -1 && lastSpinnerIdx !== -1) break;
|
|
490
|
+
}
|
|
491
|
+
if (lastPromptIdx > lastSpinnerIdx) {
|
|
492
|
+
const text = clean[lastPromptIdx].trim().replace(/^❯\s*/, "").trim();
|
|
493
|
+
return text ? `» ${text}` : null;
|
|
494
|
+
}
|
|
495
|
+
const thinkingLine = clean.find((l) => isSpinnerLine(l));
|
|
477
496
|
if (thinkingLine) {
|
|
478
497
|
const m = /^.\s+(\w+[^(]*)(?:\s*\(|$)/u.exec(thinkingLine.trim());
|
|
479
|
-
return m ? `✳ ${m[1].trim()}` : "thinking…";
|
|
480
|
-
}
|
|
481
|
-
const promptLines = clean.filter((l) => /^❯\s+/.test(l.trim()));
|
|
482
|
-
if (promptLines.length > 0) {
|
|
483
|
-
const text = promptLines[promptLines.length - 1].trim().replace(/^❯\s+/, "").trim();
|
|
484
|
-
if (text) return `» ${text}`;
|
|
498
|
+
return m?.[1] ? `✳ ${m[1].trim()}` : "thinking…";
|
|
485
499
|
}
|
|
486
500
|
const cookIdx = clean.findIndex((l) => /^✻\s+/.test(l.trim()));
|
|
487
501
|
if (cookIdx >= 0) {
|
|
@@ -502,7 +516,7 @@ async function cmdSend(rest) {
|
|
|
502
516
|
const opts = commonOpts(flags);
|
|
503
517
|
const keyword = positional[0];
|
|
504
518
|
const rawMessage = positional.slice(1).join(" ");
|
|
505
|
-
if (!keyword) throw new Error("usage:
|
|
519
|
+
if (!keyword) throw new Error("usage: ay send <keyword> <msg|-> [--code=enter|esc|ctrl-c|ctrl-y|tab|none]");
|
|
506
520
|
const trailing = controlCodeFromName(typeof flags.code === "string" ? flags.code.toLowerCase() : "enter");
|
|
507
521
|
const record = await resolveOne(keyword, opts);
|
|
508
522
|
const fifoPath = record.fifo_file;
|
|
@@ -513,14 +527,18 @@ async function cmdSend(rest) {
|
|
|
513
527
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
514
528
|
body = Buffer.concat(chunks).toString("utf-8").trimEnd();
|
|
515
529
|
} else body = rawMessage;
|
|
516
|
-
|
|
517
|
-
|
|
530
|
+
const sourcePid = process.env.AGENT_YES_PID ? Number(process.env.AGENT_YES_PID) : null;
|
|
531
|
+
const talkBack = sourcePid ? `\n(from AGENT_YES_PID=${sourcePid} — reply: ay send ${sourcePid} "...")` : "";
|
|
532
|
+
const fullBody = body + talkBack;
|
|
533
|
+
if (fullBody && trailing) {
|
|
534
|
+
await writeToIpc(fifoPath, fullBody);
|
|
518
535
|
await new Promise((r) => setTimeout(r, 200));
|
|
519
536
|
await writeToIpc(fifoPath, trailing);
|
|
520
|
-
} else await writeToIpc(fifoPath,
|
|
537
|
+
} else await writeToIpc(fifoPath, fullBody + trailing);
|
|
521
538
|
const payload = body + trailing;
|
|
522
539
|
process.stdout.write(`sent to pid ${record.pid} (${record.cli}): ${truncate(payload, 80)}\n`);
|
|
523
|
-
|
|
540
|
+
const replyHint = sourcePid ? ` ay send ${sourcePid} "..." # reply to sender\n` : "";
|
|
541
|
+
process.stderr.write(`\n` + replyHint + ` ay tail ${record.pid} # watch output\n ay ls # list all agents\n`);
|
|
524
542
|
return 0;
|
|
525
543
|
}
|
|
526
544
|
function controlCodeFromName(name) {
|
|
@@ -584,7 +602,7 @@ async function cmdRestart(rest) {
|
|
|
584
602
|
const keyword = positional[0];
|
|
585
603
|
const record = await resolveOne(keyword, opts);
|
|
586
604
|
if (isPidAlive(record.pid)) {
|
|
587
|
-
process.stderr.write(`pid ${record.pid} is still running — stop it first or use
|
|
605
|
+
process.stderr.write(`pid ${record.pid} is still running — stop it first or use ay send\n`);
|
|
588
606
|
return 1;
|
|
589
607
|
}
|
|
590
608
|
const args = ["--cli=" + record.cli];
|
|
@@ -599,7 +617,7 @@ async function cmdRestart(rest) {
|
|
|
599
617
|
]
|
|
600
618
|
});
|
|
601
619
|
process.stdout.write(`restarted ${record.cli} in ${shortenPath(record.cwd)} (new pid: ${proc.pid})\n`);
|
|
602
|
-
process.stderr.write(`\n
|
|
620
|
+
process.stderr.write(`\n ay tail ${proc.pid} # watch output\n ay ls # list all agents\n`);
|
|
603
621
|
return 0;
|
|
604
622
|
}
|
|
605
623
|
async function cmdNote(rest) {
|
|
@@ -607,7 +625,7 @@ async function cmdNote(rest) {
|
|
|
607
625
|
const opts = commonOpts(flags);
|
|
608
626
|
const keyword = positional[0];
|
|
609
627
|
const note = positional.slice(1).join(" ");
|
|
610
|
-
if (!keyword) throw new Error("usage:
|
|
628
|
+
if (!keyword) throw new Error("usage: ay note <keyword> [\"note text\"] (omit text to clear)");
|
|
611
629
|
const record = await resolveOne(keyword, {
|
|
612
630
|
...opts,
|
|
613
631
|
all: true
|
|
@@ -620,10 +638,82 @@ async function cmdNote(rest) {
|
|
|
620
638
|
}
|
|
621
639
|
await writeNote(record.pid, note);
|
|
622
640
|
process.stdout.write(`note set for pid ${record.pid}: ${note}\n`);
|
|
623
|
-
process.stderr.write(`\n
|
|
641
|
+
process.stderr.write(`\n ay ls # see updated note in list\n`);
|
|
642
|
+
return 0;
|
|
643
|
+
}
|
|
644
|
+
async function snapshotStatus(record) {
|
|
645
|
+
const alive = isPidAlive(record.pid);
|
|
646
|
+
let state;
|
|
647
|
+
let logMtimeMs = null;
|
|
648
|
+
if (!alive) state = "stopped";
|
|
649
|
+
else if (record.log_file) {
|
|
650
|
+
logMtimeMs = await stat(record.log_file).then((s) => s.mtimeMs).catch(() => null);
|
|
651
|
+
state = logMtimeMs !== null && Date.now() - logMtimeMs > IDLE_THRESHOLD_MS ? "idle" : "active";
|
|
652
|
+
} else state = "active";
|
|
653
|
+
const activity = state !== "stopped" && record.log_file ? await extractActivity(record.log_file) : null;
|
|
654
|
+
const note = (await readNotes()).get(record.pid) ?? null;
|
|
655
|
+
return {
|
|
656
|
+
pid: record.pid,
|
|
657
|
+
cli: record.cli,
|
|
658
|
+
cwd: record.cwd,
|
|
659
|
+
state,
|
|
660
|
+
activity,
|
|
661
|
+
note,
|
|
662
|
+
log_mtime_ms: logMtimeMs,
|
|
663
|
+
started_at: record.started_at,
|
|
664
|
+
age_ms: Date.now() - record.started_at,
|
|
665
|
+
exit_code: record.exit_code,
|
|
666
|
+
exit_reason: record.exit_reason,
|
|
667
|
+
log_file: record.log_file ?? null
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
async function cmdStatus(rest) {
|
|
671
|
+
const { flags, positional } = parseArgs(rest);
|
|
672
|
+
const opts = {
|
|
673
|
+
...commonOpts(flags),
|
|
674
|
+
all: true
|
|
675
|
+
};
|
|
676
|
+
const keyword = positional[0];
|
|
677
|
+
if (!keyword) throw new Error("usage: ay status <keyword> [--watch] [--interval=N]");
|
|
678
|
+
const watch = !!(flags.watch || flags.w);
|
|
679
|
+
const intervalFlag = typeof flags.interval === "string" ? Number(flags.interval) : 2;
|
|
680
|
+
const intervalMs = Math.max(500, (Number.isFinite(intervalFlag) ? intervalFlag : 2) * 1e3);
|
|
681
|
+
const record = await resolveOne(keyword, opts);
|
|
682
|
+
const emit = (snap, ts) => {
|
|
683
|
+
const out = ts !== void 0 ? {
|
|
684
|
+
ts,
|
|
685
|
+
...snap
|
|
686
|
+
} : snap;
|
|
687
|
+
process.stdout.write(JSON.stringify(out) + "\n");
|
|
688
|
+
};
|
|
689
|
+
if (!watch) {
|
|
690
|
+
emit(await snapshotStatus(record));
|
|
691
|
+
return 0;
|
|
692
|
+
}
|
|
693
|
+
process.stderr.write(`watching pid ${record.pid} every ${intervalMs / 1e3}s… (Ctrl-C to stop)\n`);
|
|
694
|
+
let prev = null;
|
|
695
|
+
const tick = async () => {
|
|
696
|
+
const snap = await snapshotStatus(record);
|
|
697
|
+
if (prev === null || snap.state !== prev.state || snap.activity !== prev.activity || snap.exit_code !== prev.exit_code) {
|
|
698
|
+
emit(snap, Date.now());
|
|
699
|
+
prev = {
|
|
700
|
+
state: snap.state,
|
|
701
|
+
activity: snap.activity,
|
|
702
|
+
exit_code: snap.exit_code
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
await tick();
|
|
707
|
+
await new Promise((resolve) => {
|
|
708
|
+
const timer = setInterval(tick, intervalMs);
|
|
709
|
+
process.on("SIGINT", () => {
|
|
710
|
+
clearInterval(timer);
|
|
711
|
+
resolve();
|
|
712
|
+
});
|
|
713
|
+
});
|
|
624
714
|
return 0;
|
|
625
715
|
}
|
|
626
716
|
|
|
627
717
|
//#endregion
|
|
628
718
|
export { isSubcommand, runSubcommand };
|
|
629
|
-
//# sourceMappingURL=subcommands-
|
|
719
|
+
//# sourceMappingURL=subcommands-DjO8lthH.js.map
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { n as logger, t as addTransport } from "./logger-B9h0djqx.js";
|
|
2
|
-
import { r as getInstalledPackage } from "./versionChecker-
|
|
2
|
+
import { r as getInstalledPackage } from "./versionChecker-CspuhOwO.js";
|
|
3
3
|
import { i as shouldUseLock, r as releaseLock, t as acquireLock } from "./runningLock-C22d9SRJ.js";
|
|
4
4
|
import { t as PidStore } from "./pidStore-C1JXxoPi.js";
|
|
5
|
+
import { r as readGlobalPids } from "./globalPidIndex-Cr-g75QF.js";
|
|
5
6
|
import { arch, platform } from "process";
|
|
6
7
|
import { execSync } from "child_process";
|
|
7
8
|
import { closeSync, constants, createReadStream, existsSync, mkdirSync, openSync } from "fs";
|
|
@@ -1062,7 +1063,7 @@ const CLIS_CONFIG = config.clis;
|
|
|
1062
1063
|
* });
|
|
1063
1064
|
* ```
|
|
1064
1065
|
*/
|
|
1065
|
-
async function agentYes({ cli, cliArgs = [], prompt, robust = true, cwd, env, exitOnIdle, logFile, removeControlCharactersFromStdout = false, verbose = false, queue = false, install = false, resume = false, useSkills = false, useStdinAppend = false, autoYes = true, idleAction }) {
|
|
1066
|
+
async function agentYes({ cli, cliArgs = [], prompt, robust = true, cwd, env, exitOnIdle, logFile, removeControlCharactersFromStdout = false, verbose = false, queue = false, install = false, resume = false, useSkills = false, useStdinAppend = false, autoYes = true, idleAction, swarmHint = true }) {
|
|
1066
1067
|
if (!cli) throw new Error(`cli is required`);
|
|
1067
1068
|
const conf = CLIS_CONFIG[cli] || DIE(`Unsupported cli tool: ${cli}, current process.argv: ${process.argv.join(" ")}`);
|
|
1068
1069
|
const workingDir = cwd ?? process.cwd();
|
|
@@ -1139,6 +1140,18 @@ async function agentYes({ cli, cliArgs = [], prompt, robust = true, cwd, env, ex
|
|
|
1139
1140
|
} catch (error) {
|
|
1140
1141
|
if (verbose) logger.warn("[skills] Failed to inject SKILL.md header:", { error });
|
|
1141
1142
|
}
|
|
1143
|
+
if (swarmHint) try {
|
|
1144
|
+
const peers = await readGlobalPids({ liveOnly: true });
|
|
1145
|
+
if (peers.length > 0) {
|
|
1146
|
+
const hint = `${peers.length} peer agent${peers.length > 1 ? "s are" : " is"} running. Before asking the user for input on any domain-specific topic (design, testing, architecture, etc.), check for relevant peers first: \`ay ls --json\` (see \`prompt\` field for their role). Ask one: \`ay send <pid> <question>\`. Read reply: \`ay tail <pid>\`. Do not use interactive forms or user prompts when a peer can answer.`;
|
|
1147
|
+
if (cli === "claude") cliArgs = [
|
|
1148
|
+
"--append-system-prompt",
|
|
1149
|
+
hint,
|
|
1150
|
+
...cliArgs
|
|
1151
|
+
];
|
|
1152
|
+
prompt = prompt ? `[${hint}]\n\n${prompt}` : hint;
|
|
1153
|
+
}
|
|
1154
|
+
} catch {}
|
|
1142
1155
|
if (resume) if (cli === "codex" && resume) {
|
|
1143
1156
|
const storedSessionId = await getSessionForCwd(workingDir);
|
|
1144
1157
|
if (storedSessionId) {
|
|
@@ -1171,6 +1184,7 @@ async function agentYes({ cli, cliArgs = [], prompt, robust = true, cwd, env, ex
|
|
|
1171
1184
|
prompt = void 0;
|
|
1172
1185
|
} else logger.warn(`Unknown promptArg format: ${cliConf.promptArg}`);
|
|
1173
1186
|
const ptyEnv = { ...env ?? process.env };
|
|
1187
|
+
ptyEnv.AGENT_YES_PID = String(process.pid);
|
|
1174
1188
|
const ptyOptions = {
|
|
1175
1189
|
name: "xterm-color",
|
|
1176
1190
|
...getTerminalDimensions(),
|
|
@@ -1679,4 +1693,4 @@ function sleep(ms) {
|
|
|
1679
1693
|
|
|
1680
1694
|
//#endregion
|
|
1681
1695
|
export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
|
|
1682
|
-
//# sourceMappingURL=ts-
|
|
1696
|
+
//# sourceMappingURL=ts-Bw6gQKyU.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.84.0";
|
|
11
11
|
|
|
12
12
|
//#endregion
|
|
13
13
|
//#region ts/versionChecker.ts
|
|
@@ -221,4 +221,4 @@ async function displayVersion() {
|
|
|
221
221
|
|
|
222
222
|
//#endregion
|
|
223
223
|
export { versionString as i, displayVersion as n, getInstalledPackage as r, checkAndAutoUpdate as t };
|
|
224
|
-
//# sourceMappingURL=versionChecker-
|
|
224
|
+
//# sourceMappingURL=versionChecker-CspuhOwO.js.map
|
package/package.json
CHANGED
package/ts/index.ts
CHANGED
|
@@ -28,6 +28,7 @@ import { AgentContext } from "./core/context.ts";
|
|
|
28
28
|
import { createTerminatorStream } from "./core/streamHelpers.ts";
|
|
29
29
|
import { globalAgentRegistry } from "./agentRegistry.ts";
|
|
30
30
|
import { notifyWebhook } from "./webhookNotifier.ts";
|
|
31
|
+
import { readGlobalPids } from "./globalPidIndex.ts";
|
|
31
32
|
|
|
32
33
|
export { removeControlCharacters };
|
|
33
34
|
export { AgentContext };
|
|
@@ -124,6 +125,7 @@ export default async function agentYes({
|
|
|
124
125
|
useStdinAppend = false,
|
|
125
126
|
autoYes = true,
|
|
126
127
|
idleAction,
|
|
128
|
+
swarmHint = true,
|
|
127
129
|
}: {
|
|
128
130
|
cli: keyof typeof CLIS_CONFIG;
|
|
129
131
|
cliArgs?: string[];
|
|
@@ -142,6 +144,7 @@ export default async function agentYes({
|
|
|
142
144
|
useStdinAppend?: boolean; // if true, enable FIFO input stream on Linux, for additional stdin input
|
|
143
145
|
autoYes?: boolean; // if true, auto-yes is enabled (default), toggle with Ctrl+Y during session
|
|
144
146
|
idleAction?: string; // if set, type this message when idle instead of exiting
|
|
147
|
+
swarmHint?: boolean; // if true (default), inject peer discovery hint when other agents are running; --no-swarm-hint to opt out
|
|
145
148
|
}) {
|
|
146
149
|
if (!cli) throw new Error(`cli is required`);
|
|
147
150
|
const conf =
|
|
@@ -274,6 +277,23 @@ export default async function agentYes({
|
|
|
274
277
|
if (verbose) logger.warn("[skills] Failed to inject SKILL.md header:", { error });
|
|
275
278
|
}
|
|
276
279
|
|
|
280
|
+
// Inject peer discovery hint when other agents are running
|
|
281
|
+
if (swarmHint) {
|
|
282
|
+
try {
|
|
283
|
+
const peers = await readGlobalPids({ liveOnly: true });
|
|
284
|
+
if (peers.length > 0) {
|
|
285
|
+
const hint = `${peers.length} peer agent${peers.length > 1 ? "s are" : " is"} running. Before asking the user for input on any domain-specific topic (design, testing, architecture, etc.), check for relevant peers first: \`ay ls --json\` (see \`prompt\` field for their role). Ask one: \`ay send <pid> <question>\`. Read reply: \`ay tail <pid>\`. Do not use interactive forms or user prompts when a peer can answer.`;
|
|
286
|
+
if (cli === "claude") {
|
|
287
|
+
cliArgs = ["--append-system-prompt", hint, ...cliArgs];
|
|
288
|
+
}
|
|
289
|
+
// Prepend to prompt for all CLIs (including claude) so it's read before the task
|
|
290
|
+
prompt = prompt ? `[${hint}]\n\n${prompt}` : hint;
|
|
291
|
+
}
|
|
292
|
+
} catch {
|
|
293
|
+
// Non-fatal
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
277
297
|
// Handle --continue flag for codex session restoration
|
|
278
298
|
if (resume) {
|
|
279
299
|
if (cli === "codex" && resume) {
|
|
@@ -322,6 +342,7 @@ export default async function agentYes({
|
|
|
322
342
|
|
|
323
343
|
// Spawn the agent CLI process
|
|
324
344
|
const ptyEnv = { ...(env ?? (process.env as Record<string, string>)) };
|
|
345
|
+
ptyEnv.AGENT_YES_PID = String(process.pid);
|
|
325
346
|
const ptyOptions = {
|
|
326
347
|
name: "xterm-color",
|
|
327
348
|
...getTerminalDimensions(),
|
package/ts/parseCliArgs.ts
CHANGED
|
@@ -63,6 +63,12 @@ export function parseCliArgs(argv: string[], supportedClis?: readonly string[])
|
|
|
63
63
|
"Prepend SKILL.md header from current directory to the prompt (helpful for non-Claude agents)",
|
|
64
64
|
default: false,
|
|
65
65
|
})
|
|
66
|
+
.option("swarm-hint", {
|
|
67
|
+
type: "boolean",
|
|
68
|
+
description:
|
|
69
|
+
"Inject peer discovery hint into agent system prompt when other agents are running (use --no-swarm-hint to opt out)",
|
|
70
|
+
default: true,
|
|
71
|
+
})
|
|
66
72
|
.option("timeout", {
|
|
67
73
|
type: "string",
|
|
68
74
|
description: 'Exit after a period of inactivity, e.g., "5s" or "1m"',
|
|
@@ -313,6 +319,7 @@ export function parseCliArgs(argv: string[], supportedClis?: readonly string[])
|
|
|
313
319
|
verbose: parsedArgv.verbose,
|
|
314
320
|
resume: parsedArgv.continue, // Note: intentional use resume here to avoid preserved keyword (continue)
|
|
315
321
|
useSkills: parsedArgv.useSkills,
|
|
322
|
+
swarmHint: parsedArgv.swarmHint,
|
|
316
323
|
appendPrompt: parsedArgv.appendPrompt,
|
|
317
324
|
useStdinAppend: Boolean(parsedArgv.stdpush || parsedArgv.ipc || parsedArgv.fifo), // Support --stdpush, --ipc, and --fifo (backward compatibility)
|
|
318
325
|
showVersion: parsedArgv.version,
|
package/ts/subcommands.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `
|
|
2
|
+
* `ay ls / read / cat / tail / head / send` subcommand implementations.
|
|
3
3
|
*
|
|
4
4
|
* Mirrors the principles of koho's `terminal-ws-lib.ts` (session list, render
|
|
5
5
|
* via @xterm/headless, keyword-keyed input) — but file-based instead of via
|
|
@@ -66,7 +66,7 @@ async function compactNotes(): Promise<void> {
|
|
|
66
66
|
/**
|
|
67
67
|
* Read the per-cwd TS PidStore JSONL and convert to the global record shape,
|
|
68
68
|
* so pre-existing TS agents that were spawned before the global-index mirror
|
|
69
|
-
* shipped still show up in `
|
|
69
|
+
* shipped still show up in `ay ls`. Merging is done in `mergeRecords`.
|
|
70
70
|
*/
|
|
71
71
|
async function readLocalTsPids(cwd: string): Promise<GlobalPidRecord[]> {
|
|
72
72
|
const jsonlPath = path.join(cwd, ".agent-yes", "pid-records.jsonl");
|
|
@@ -127,6 +127,7 @@ const SUBCOMMANDS = new Set([
|
|
|
127
127
|
"ls",
|
|
128
128
|
"list",
|
|
129
129
|
"ps",
|
|
130
|
+
"status",
|
|
130
131
|
"read",
|
|
131
132
|
"cat",
|
|
132
133
|
"tail",
|
|
@@ -136,6 +137,8 @@ const SUBCOMMANDS = new Set([
|
|
|
136
137
|
"note",
|
|
137
138
|
]);
|
|
138
139
|
|
|
140
|
+
const IDLE_THRESHOLD_MS = 60 * 1000;
|
|
141
|
+
|
|
139
142
|
export function isSubcommand(name: string | undefined): boolean {
|
|
140
143
|
return !!name && SUBCOMMANDS.has(name);
|
|
141
144
|
}
|
|
@@ -156,6 +159,8 @@ export async function runSubcommand(argv: string[]): Promise<number | null> {
|
|
|
156
159
|
case "list":
|
|
157
160
|
case "ps":
|
|
158
161
|
return await cmdLs(rest);
|
|
162
|
+
case "status":
|
|
163
|
+
return await cmdStatus(rest);
|
|
159
164
|
case "read":
|
|
160
165
|
case "cat":
|
|
161
166
|
return await cmdRead(rest, { mode: "cat" });
|
|
@@ -174,7 +179,7 @@ export async function runSubcommand(argv: string[]): Promise<number | null> {
|
|
|
174
179
|
}
|
|
175
180
|
} catch (err) {
|
|
176
181
|
const msg = err instanceof Error ? err.message : String(err);
|
|
177
|
-
process.stderr.write(`
|
|
182
|
+
process.stderr.write(`ay ${sub}: ${msg}\n`);
|
|
178
183
|
return 1;
|
|
179
184
|
}
|
|
180
185
|
}
|
|
@@ -210,7 +215,7 @@ export function parseArgs(rest: string[]): ParsedArgs {
|
|
|
210
215
|
const next = rest[i + 1];
|
|
211
216
|
// Boolean flags: --all, --json, --latest
|
|
212
217
|
if (
|
|
213
|
-
["all", "active", "follow", "json", "latest"].includes(key) ||
|
|
218
|
+
["all", "active", "follow", "json", "latest", "watch"].includes(key) ||
|
|
214
219
|
!next ||
|
|
215
220
|
next.startsWith("-")
|
|
216
221
|
) {
|
|
@@ -322,7 +327,7 @@ async function resolveOne(keyword: string | undefined, opts: CommonOpts): Promis
|
|
|
322
327
|
}
|
|
323
328
|
|
|
324
329
|
// ---------------------------------------------------------------------------
|
|
325
|
-
//
|
|
330
|
+
// ay ls
|
|
326
331
|
// ---------------------------------------------------------------------------
|
|
327
332
|
|
|
328
333
|
async function cmdLs(rest: string[]): Promise<number> {
|
|
@@ -359,7 +364,6 @@ async function cmdLs(rest: string[]): Promise<number> {
|
|
|
359
364
|
const fixedWidth = widths.pid + widths.cli + widths.status + widths.age + widths.cwd + 5 * 2; // 5 separators of " "
|
|
360
365
|
const promptBudget = Math.max(20, termWidth - fixedWidth - 1);
|
|
361
366
|
|
|
362
|
-
const IDLE_THRESHOLD_MS = 60 * 1000;
|
|
363
367
|
const notes = await readNotes();
|
|
364
368
|
const rows = await Promise.all(
|
|
365
369
|
records.map(async (r) => {
|
|
@@ -383,9 +387,9 @@ async function cmdLs(rest: string[]): Promise<number> {
|
|
|
383
387
|
hasNote = true;
|
|
384
388
|
} else if (r.log_file && displayStatus !== "stopped") {
|
|
385
389
|
const activity = await extractActivity(r.log_file);
|
|
386
|
-
label = truncate(activity ?? r.prompt
|
|
390
|
+
label = truncate(activity ?? (r.prompt ? `→ ${r.prompt}` : ""), promptBudget);
|
|
387
391
|
} else {
|
|
388
|
-
label = truncate(r.prompt
|
|
392
|
+
label = truncate(r.prompt ? `→ ${r.prompt}` : "", promptBudget);
|
|
389
393
|
}
|
|
390
394
|
return {
|
|
391
395
|
pid: String(r.pid),
|
|
@@ -429,17 +433,24 @@ async function cmdLs(rest: string[]): Promise<number> {
|
|
|
429
433
|
const stopped = rows.find((r) => !r._alive);
|
|
430
434
|
const hints: string[] = ["\n"];
|
|
431
435
|
if (alive) {
|
|
432
|
-
hints.push(`
|
|
433
|
-
hints.push(`
|
|
434
|
-
hints.push(`
|
|
435
|
-
hints.push(`
|
|
436
|
-
hints.push(
|
|
436
|
+
hints.push(` ay status ${alive.pid} # JSON status snapshot\n`);
|
|
437
|
+
hints.push(` ay status ${alive.pid} --watch # stream changes as JSON\n`);
|
|
438
|
+
hints.push(` ay tail ${alive.pid} # view latest output\n`);
|
|
439
|
+
hints.push(` ay tail -f ${alive.pid} # follow live output\n`);
|
|
440
|
+
hints.push(
|
|
441
|
+
` ay send ${alive.pid} "next: ..." # send a prompt (keyword: pid, cwd, or prompt substring)\n`,
|
|
442
|
+
);
|
|
443
|
+
hints.push(` ay send ${alive.pid} "" --code=ctrl-c # interrupt\n`);
|
|
444
|
+
hints.push(` ay note ${alive.pid} "what it's doing" # set a note\n`);
|
|
445
|
+
hints.push(
|
|
446
|
+
` ay ls --json # machine-readable list for scripts/agents\n`,
|
|
447
|
+
);
|
|
437
448
|
}
|
|
438
449
|
if (stopped) {
|
|
439
|
-
hints.push(`
|
|
450
|
+
hints.push(` ay restart ${stopped.pid} # restart stopped agent\n`);
|
|
440
451
|
}
|
|
441
452
|
if (!alive && !stopped)
|
|
442
|
-
hints.push(`
|
|
453
|
+
hints.push(` ay ls --all # show exited agents\n`);
|
|
443
454
|
process.stderr.write(hints.join(""));
|
|
444
455
|
}
|
|
445
456
|
|
|
@@ -469,7 +480,7 @@ function truncate(s: string, n: number): string {
|
|
|
469
480
|
}
|
|
470
481
|
|
|
471
482
|
// ---------------------------------------------------------------------------
|
|
472
|
-
//
|
|
483
|
+
// ay read / cat / tail / head
|
|
473
484
|
// ---------------------------------------------------------------------------
|
|
474
485
|
|
|
475
486
|
interface ReadOpts {
|
|
@@ -544,10 +555,10 @@ async function cmdRead(rest: string[], { mode }: ReadOpts): Promise<number> {
|
|
|
544
555
|
|
|
545
556
|
process.stderr.write(
|
|
546
557
|
`\n` +
|
|
547
|
-
`
|
|
548
|
-
`
|
|
549
|
-
`
|
|
550
|
-
`
|
|
558
|
+
` ay ls # list all agents\n` +
|
|
559
|
+
` ay tail -f ${record.pid} # follow live output\n` +
|
|
560
|
+
` ay send ${record.pid} "next: ..." # send a prompt\n` +
|
|
561
|
+
` ay send ${record.pid} "" --code=ctrl-c # interrupt\n`,
|
|
551
562
|
);
|
|
552
563
|
return 0;
|
|
553
564
|
}
|
|
@@ -656,26 +667,38 @@ function extractActivityFromLines(lines: string[]): string | null {
|
|
|
656
667
|
|
|
657
668
|
const clean = lines.filter((l) => !isChrome(l));
|
|
658
669
|
|
|
670
|
+
const isSpinnerLine = (l: string) =>
|
|
671
|
+
/^[^\w\s❯>⎿✓✗]\s+[A-Z]\w+[….]/u.test(l.trim()) || /still thinking/i.test(l);
|
|
672
|
+
|
|
673
|
+
// Find positions of the last ❯ prompt and last spinner in the rendered output.
|
|
674
|
+
// If ❯ comes after the last spinner, the agent finished and is waiting — show
|
|
675
|
+
// idle state rather than the stale spinner description.
|
|
676
|
+
let lastPromptIdx = -1;
|
|
677
|
+
let lastSpinnerIdx = -1;
|
|
678
|
+
for (let i = clean.length - 1; i >= 0; i--) {
|
|
679
|
+
const l = clean[i]!.trim();
|
|
680
|
+
if (lastPromptIdx === -1 && l.startsWith("❯")) lastPromptIdx = i;
|
|
681
|
+
if (lastSpinnerIdx === -1 && isSpinnerLine(l)) lastSpinnerIdx = i;
|
|
682
|
+
if (lastPromptIdx !== -1 && lastSpinnerIdx !== -1) break;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// ❯ appears after (or without) any spinner → agent is idle/waiting for input
|
|
686
|
+
if (lastPromptIdx > lastSpinnerIdx) {
|
|
687
|
+
const text = clean[lastPromptIdx]!.trim()
|
|
688
|
+
.replace(/^❯\s*/, "")
|
|
689
|
+
.trim();
|
|
690
|
+
return text ? `» ${text}` : null;
|
|
691
|
+
}
|
|
692
|
+
|
|
659
693
|
// Priority 1: thinking/composing spinner active
|
|
660
694
|
// Claude Code cycles through various Unicode dingbats for its spinner (✢✳✶✻✷…).
|
|
661
695
|
// The format is always: SPINNER_CHAR Verb… (timing…)
|
|
662
696
|
// Require ellipsis after the verb so we don't false-positive on normal text
|
|
663
697
|
// that happens to contain one of these chars mid-sentence.
|
|
664
|
-
const thinkingLine = clean.find(
|
|
665
|
-
(l) => /^[^\w\s❯>⎿✓✗]\s+[A-Z]\w+[….]/u.test(l.trim()) || /still thinking/i.test(l),
|
|
666
|
-
);
|
|
698
|
+
const thinkingLine = clean.find((l) => isSpinnerLine(l));
|
|
667
699
|
if (thinkingLine) {
|
|
668
700
|
const m = /^.\s+(\w+[^(]*)(?:\s*\(|$)/u.exec(thinkingLine.trim());
|
|
669
|
-
return m ? `✳ ${m[1].trim()}` : "thinking…";
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
// Priority 2: last ❯ prompt line means agent is idle, waiting for next input
|
|
673
|
-
const promptLines = clean.filter((l) => /^❯\s+/.test(l.trim()));
|
|
674
|
-
if (promptLines.length > 0) {
|
|
675
|
-
const text = promptLines[promptLines.length - 1]!.trim()
|
|
676
|
-
.replace(/^❯\s+/, "")
|
|
677
|
-
.trim();
|
|
678
|
-
if (text) return `» ${text}`;
|
|
701
|
+
return m?.[1] ? `✳ ${m[1].trim()}` : "thinking…";
|
|
679
702
|
}
|
|
680
703
|
|
|
681
704
|
// Priority 3: ✻ spinner just finished — show nearby context
|
|
@@ -705,7 +728,7 @@ function extractActivityFromLines(lines: string[]): string | null {
|
|
|
705
728
|
}
|
|
706
729
|
|
|
707
730
|
// ---------------------------------------------------------------------------
|
|
708
|
-
//
|
|
731
|
+
// ay send
|
|
709
732
|
// ---------------------------------------------------------------------------
|
|
710
733
|
|
|
711
734
|
async function cmdSend(rest: string[]): Promise<number> {
|
|
@@ -715,7 +738,7 @@ async function cmdSend(rest: string[]): Promise<number> {
|
|
|
715
738
|
const rawMessage = positional.slice(1).join(" ");
|
|
716
739
|
|
|
717
740
|
if (!keyword)
|
|
718
|
-
throw new Error("usage:
|
|
741
|
+
throw new Error("usage: ay send <keyword> <msg|-> [--code=enter|esc|ctrl-c|ctrl-y|tab|none]");
|
|
719
742
|
|
|
720
743
|
const codeName = typeof flags.code === "string" ? flags.code.toLowerCase() : "enter";
|
|
721
744
|
const trailing = controlCodeFromName(codeName);
|
|
@@ -737,20 +760,30 @@ async function cmdSend(rest: string[]): Promise<number> {
|
|
|
737
760
|
body = rawMessage;
|
|
738
761
|
}
|
|
739
762
|
|
|
740
|
-
|
|
741
|
-
|
|
763
|
+
const sourcePid = process.env.AGENT_YES_PID ? Number(process.env.AGENT_YES_PID) : null;
|
|
764
|
+
const talkBack = sourcePid
|
|
765
|
+
? `\n(from AGENT_YES_PID=${sourcePid} — reply: ay send ${sourcePid} "...")`
|
|
766
|
+
: "";
|
|
767
|
+
|
|
768
|
+
const fullBody = body + talkBack;
|
|
769
|
+
if (fullBody && trailing) {
|
|
770
|
+
await writeToIpc(fifoPath, fullBody);
|
|
742
771
|
await new Promise((r) => setTimeout(r, 200));
|
|
743
772
|
await writeToIpc(fifoPath, trailing);
|
|
744
773
|
} else {
|
|
745
|
-
await writeToIpc(fifoPath,
|
|
774
|
+
await writeToIpc(fifoPath, fullBody + trailing);
|
|
746
775
|
}
|
|
747
776
|
const payload = body + trailing;
|
|
748
777
|
process.stdout.write(`sent to pid ${record.pid} (${record.cli}): ${truncate(payload, 80)}\n`);
|
|
749
778
|
|
|
779
|
+
const replyHint = sourcePid
|
|
780
|
+
? ` ay send ${sourcePid} "..." # reply to sender\n`
|
|
781
|
+
: "";
|
|
750
782
|
process.stderr.write(
|
|
751
783
|
`\n` +
|
|
752
|
-
|
|
753
|
-
`
|
|
784
|
+
replyHint +
|
|
785
|
+
` ay tail ${record.pid} # watch output\n` +
|
|
786
|
+
` ay ls # list all agents\n`,
|
|
754
787
|
);
|
|
755
788
|
return 0;
|
|
756
789
|
}
|
|
@@ -818,7 +851,7 @@ async function writeToIpc(ipcPath: string, payload: string): Promise<void> {
|
|
|
818
851
|
}
|
|
819
852
|
|
|
820
853
|
// ---------------------------------------------------------------------------
|
|
821
|
-
//
|
|
854
|
+
// ay restart
|
|
822
855
|
// ---------------------------------------------------------------------------
|
|
823
856
|
|
|
824
857
|
async function cmdRestart(rest: string[]): Promise<number> {
|
|
@@ -828,7 +861,7 @@ async function cmdRestart(rest: string[]): Promise<number> {
|
|
|
828
861
|
const record = await resolveOne(keyword, opts);
|
|
829
862
|
|
|
830
863
|
if (isPidAlive(record.pid)) {
|
|
831
|
-
process.stderr.write(`pid ${record.pid} is still running — stop it first or use
|
|
864
|
+
process.stderr.write(`pid ${record.pid} is still running — stop it first or use ay send\n`);
|
|
832
865
|
return 1;
|
|
833
866
|
}
|
|
834
867
|
|
|
@@ -846,14 +879,14 @@ async function cmdRestart(rest: string[]): Promise<number> {
|
|
|
846
879
|
);
|
|
847
880
|
process.stderr.write(
|
|
848
881
|
`\n` +
|
|
849
|
-
`
|
|
850
|
-
`
|
|
882
|
+
` ay tail ${proc.pid} # watch output\n` +
|
|
883
|
+
` ay ls # list all agents\n`,
|
|
851
884
|
);
|
|
852
885
|
return 0;
|
|
853
886
|
}
|
|
854
887
|
|
|
855
888
|
// ---------------------------------------------------------------------------
|
|
856
|
-
//
|
|
889
|
+
// ay note
|
|
857
890
|
// ---------------------------------------------------------------------------
|
|
858
891
|
|
|
859
892
|
async function cmdNote(rest: string[]): Promise<number> {
|
|
@@ -862,7 +895,7 @@ async function cmdNote(rest: string[]): Promise<number> {
|
|
|
862
895
|
const keyword = positional[0];
|
|
863
896
|
const note = positional.slice(1).join(" ");
|
|
864
897
|
|
|
865
|
-
if (!keyword) throw new Error('usage:
|
|
898
|
+
if (!keyword) throw new Error('usage: ay note <keyword> ["note text"] (omit text to clear)');
|
|
866
899
|
|
|
867
900
|
const record = await resolveOne(keyword, { ...opts, all: true });
|
|
868
901
|
|
|
@@ -876,6 +909,114 @@ async function cmdNote(rest: string[]): Promise<number> {
|
|
|
876
909
|
|
|
877
910
|
await writeNote(record.pid, note);
|
|
878
911
|
process.stdout.write(`note set for pid ${record.pid}: ${note}\n`);
|
|
879
|
-
process.stderr.write(`\n
|
|
912
|
+
process.stderr.write(`\n ay ls # see updated note in list\n`);
|
|
913
|
+
return 0;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// ---------------------------------------------------------------------------
|
|
917
|
+
// ay status
|
|
918
|
+
// ---------------------------------------------------------------------------
|
|
919
|
+
|
|
920
|
+
interface StatusSnapshot {
|
|
921
|
+
pid: number;
|
|
922
|
+
cli: string;
|
|
923
|
+
cwd: string;
|
|
924
|
+
state: "active" | "idle" | "stopped";
|
|
925
|
+
activity: string | null;
|
|
926
|
+
note: string | null;
|
|
927
|
+
log_mtime_ms: number | null;
|
|
928
|
+
started_at: number;
|
|
929
|
+
age_ms: number;
|
|
930
|
+
exit_code: number | null;
|
|
931
|
+
exit_reason: string | null;
|
|
932
|
+
log_file: string | null;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
async function snapshotStatus(record: GlobalPidRecord): Promise<StatusSnapshot> {
|
|
936
|
+
const alive = isPidAlive(record.pid);
|
|
937
|
+
let state: "active" | "idle" | "stopped";
|
|
938
|
+
let logMtimeMs: number | null = null;
|
|
939
|
+
if (!alive) {
|
|
940
|
+
state = "stopped";
|
|
941
|
+
} else if (record.log_file) {
|
|
942
|
+
logMtimeMs = await stat(record.log_file)
|
|
943
|
+
.then((s) => s.mtimeMs)
|
|
944
|
+
.catch(() => null);
|
|
945
|
+
state = logMtimeMs !== null && Date.now() - logMtimeMs > IDLE_THRESHOLD_MS ? "idle" : "active";
|
|
946
|
+
} else {
|
|
947
|
+
state = "active";
|
|
948
|
+
}
|
|
949
|
+
const activity =
|
|
950
|
+
state !== "stopped" && record.log_file ? await extractActivity(record.log_file) : null;
|
|
951
|
+
const notes = await readNotes();
|
|
952
|
+
const note = notes.get(record.pid) ?? null;
|
|
953
|
+
return {
|
|
954
|
+
pid: record.pid,
|
|
955
|
+
cli: record.cli,
|
|
956
|
+
cwd: record.cwd,
|
|
957
|
+
state,
|
|
958
|
+
activity,
|
|
959
|
+
note,
|
|
960
|
+
log_mtime_ms: logMtimeMs,
|
|
961
|
+
started_at: record.started_at,
|
|
962
|
+
age_ms: Date.now() - record.started_at,
|
|
963
|
+
exit_code: record.exit_code,
|
|
964
|
+
exit_reason: record.exit_reason,
|
|
965
|
+
log_file: record.log_file ?? null,
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
async function cmdStatus(rest: string[]): Promise<number> {
|
|
970
|
+
const { flags, positional } = parseArgs(rest);
|
|
971
|
+
const opts = { ...commonOpts(flags), all: true };
|
|
972
|
+
const keyword = positional[0];
|
|
973
|
+
|
|
974
|
+
if (!keyword) throw new Error("usage: ay status <keyword> [--watch] [--interval=N]");
|
|
975
|
+
|
|
976
|
+
const watch = !!(flags.watch || flags.w);
|
|
977
|
+
const intervalFlag = typeof flags.interval === "string" ? Number(flags.interval) : 2;
|
|
978
|
+
const intervalMs = Math.max(500, (Number.isFinite(intervalFlag) ? intervalFlag : 2) * 1000);
|
|
979
|
+
|
|
980
|
+
const record = await resolveOne(keyword, opts);
|
|
981
|
+
|
|
982
|
+
const emit = (snap: StatusSnapshot, ts?: number): void => {
|
|
983
|
+
const out = ts !== undefined ? { ts, ...snap } : snap;
|
|
984
|
+
process.stdout.write(JSON.stringify(out) + "\n");
|
|
985
|
+
};
|
|
986
|
+
|
|
987
|
+
if (!watch) {
|
|
988
|
+
emit(await snapshotStatus(record));
|
|
989
|
+
return 0;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
process.stderr.write(
|
|
993
|
+
`watching pid ${record.pid} every ${intervalMs / 1000}s… (Ctrl-C to stop)\n`,
|
|
994
|
+
);
|
|
995
|
+
|
|
996
|
+
let prev: { state: string; activity: string | null; exit_code: number | null } | null = null;
|
|
997
|
+
|
|
998
|
+
const tick = async (): Promise<void> => {
|
|
999
|
+
const snap = await snapshotStatus(record);
|
|
1000
|
+
if (
|
|
1001
|
+
prev === null ||
|
|
1002
|
+
snap.state !== prev.state ||
|
|
1003
|
+
snap.activity !== prev.activity ||
|
|
1004
|
+
snap.exit_code !== prev.exit_code
|
|
1005
|
+
) {
|
|
1006
|
+
emit(snap, Date.now());
|
|
1007
|
+
prev = { state: snap.state, activity: snap.activity, exit_code: snap.exit_code };
|
|
1008
|
+
}
|
|
1009
|
+
};
|
|
1010
|
+
|
|
1011
|
+
await tick();
|
|
1012
|
+
|
|
1013
|
+
await new Promise<void>((resolve) => {
|
|
1014
|
+
const timer = setInterval(tick, intervalMs);
|
|
1015
|
+
process.on("SIGINT", () => {
|
|
1016
|
+
clearInterval(timer);
|
|
1017
|
+
resolve();
|
|
1018
|
+
});
|
|
1019
|
+
});
|
|
1020
|
+
|
|
880
1021
|
return 0;
|
|
881
1022
|
}
|