agent-yes 1.83.0 → 1.85.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-Dzx7za7u.js → SUPPORTED_CLIS-CawnsTw2.js} +3 -3
- package/dist/cli.js +8 -3
- package/dist/index.js +2 -2
- package/dist/{subcommands-Vt_yQiEZ.js → subcommands-BwWcA9uo.js} +257 -99
- package/dist/{ts-WFsbtrbl.js → ts-D0ddYVke.js} +17 -3
- package/dist/{versionChecker-DdnBAwJe.js → versionChecker-ftOiNICT.js} +2 -2
- package/package.json +1 -1
- package/ts/index.ts +21 -0
- package/ts/parseCliArgs.ts +7 -0
- package/ts/subcommands.spec.ts +478 -35
- package/ts/subcommands.ts +348 -129
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.spec.ts
CHANGED
|
@@ -27,41 +27,6 @@ async function loadModule() {
|
|
|
27
27
|
return await import("./subcommands.ts");
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
describe("subcommands.parseArgs", () => {
|
|
31
|
-
it("collects positional args and bare flags", async () => {
|
|
32
|
-
const { parseArgs } = await loadModule();
|
|
33
|
-
const out = parseArgs(["foo", "bar", "--all"]);
|
|
34
|
-
expect(out.positional).toEqual(["foo", "bar"]);
|
|
35
|
-
expect(out.flags.all).toBe(true);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it("parses --key=value form", async () => {
|
|
39
|
-
const { parseArgs } = await loadModule();
|
|
40
|
-
const out = parseArgs(["--code=enter"]);
|
|
41
|
-
expect(out.flags.code).toBe("enter");
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it("parses --key value form for non-boolean keys", async () => {
|
|
45
|
-
const { parseArgs } = await loadModule();
|
|
46
|
-
const out = parseArgs(["--cwd", "/tmp/foo"]);
|
|
47
|
-
expect(out.flags.cwd).toBe("/tmp/foo");
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("treats well-known boolean flags as boolean even with a following positional", async () => {
|
|
51
|
-
const { parseArgs } = await loadModule();
|
|
52
|
-
const out = parseArgs(["--all", "claude"]);
|
|
53
|
-
expect(out.flags.all).toBe(true);
|
|
54
|
-
expect(out.positional).toEqual(["claude"]);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("supports -n N short form", async () => {
|
|
58
|
-
const { parseArgs } = await loadModule();
|
|
59
|
-
const out = parseArgs(["-n", "50", "keyword"]);
|
|
60
|
-
expect(out.flags.n).toBe("50");
|
|
61
|
-
expect(out.positional).toEqual(["keyword"]);
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
30
|
describe("subcommands.controlCodeFromName", () => {
|
|
66
31
|
it("maps named codes to the right control bytes", async () => {
|
|
67
32
|
const { controlCodeFromName } = await loadModule();
|
|
@@ -525,6 +490,484 @@ describe("subcommands.cmdSend writes bytes to FIFO", () => {
|
|
|
525
490
|
});
|
|
526
491
|
});
|
|
527
492
|
|
|
493
|
+
// ---------------------------------------------------------------------------
|
|
494
|
+
// cmdLs additional arg coverage
|
|
495
|
+
// ---------------------------------------------------------------------------
|
|
496
|
+
|
|
497
|
+
describe("subcommands.cmdLs -h / --help", () => {
|
|
498
|
+
function captureStdout() {
|
|
499
|
+
const chunks: string[] = [];
|
|
500
|
+
const orig = process.stdout.write.bind(process.stdout);
|
|
501
|
+
(process.stdout as any).write = (s: any) => {
|
|
502
|
+
chunks.push(String(s));
|
|
503
|
+
return true;
|
|
504
|
+
};
|
|
505
|
+
return {
|
|
506
|
+
get text() {
|
|
507
|
+
return chunks.join("");
|
|
508
|
+
},
|
|
509
|
+
restore() {
|
|
510
|
+
process.stdout.write = orig;
|
|
511
|
+
},
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
it("ay ls -h prints usage to stdout and exits 0", async () => {
|
|
516
|
+
const { runSubcommand } = await loadModule();
|
|
517
|
+
const cap = captureStdout();
|
|
518
|
+
let code: number | null;
|
|
519
|
+
try {
|
|
520
|
+
code = await runSubcommand(["bun", "cli.js", "ls", "-h"]);
|
|
521
|
+
} finally {
|
|
522
|
+
cap.restore();
|
|
523
|
+
}
|
|
524
|
+
expect(code).toBe(0);
|
|
525
|
+
expect(cap.text).toMatch(/Usage:/);
|
|
526
|
+
expect(cap.text).toMatch(/--all/);
|
|
527
|
+
expect(cap.text).toMatch(/--json/);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it("ay ls --help prints usage to stdout and exits 0", async () => {
|
|
531
|
+
const { runSubcommand } = await loadModule();
|
|
532
|
+
const cap = captureStdout();
|
|
533
|
+
let code: number | null;
|
|
534
|
+
try {
|
|
535
|
+
code = await runSubcommand(["bun", "cli.js", "ls", "--help"]);
|
|
536
|
+
} finally {
|
|
537
|
+
cap.restore();
|
|
538
|
+
}
|
|
539
|
+
expect(code).toBe(0);
|
|
540
|
+
expect(cap.text).toMatch(/Usage:/);
|
|
541
|
+
});
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
describe("subcommands.cmdLs --all / --active / keyword filter / aliases", () => {
|
|
545
|
+
function captureOutput() {
|
|
546
|
+
const out: string[] = [];
|
|
547
|
+
const err: string[] = [];
|
|
548
|
+
const origOut = process.stdout.write.bind(process.stdout);
|
|
549
|
+
const origErr = process.stderr.write.bind(process.stderr);
|
|
550
|
+
(process.stdout as any).write = (s: any) => {
|
|
551
|
+
out.push(String(s));
|
|
552
|
+
return true;
|
|
553
|
+
};
|
|
554
|
+
(process.stderr as any).write = (s: any) => {
|
|
555
|
+
err.push(String(s));
|
|
556
|
+
return true;
|
|
557
|
+
};
|
|
558
|
+
return {
|
|
559
|
+
get stdout() {
|
|
560
|
+
return out.join("");
|
|
561
|
+
},
|
|
562
|
+
get stderr() {
|
|
563
|
+
return err.join("");
|
|
564
|
+
},
|
|
565
|
+
restore() {
|
|
566
|
+
process.stdout.write = origOut;
|
|
567
|
+
process.stderr.write = origErr;
|
|
568
|
+
},
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
it("--all shows exited agents", async () => {
|
|
573
|
+
const mod = await loadModule();
|
|
574
|
+
const { appendGlobalPid } = await import("./globalPidIndex.ts");
|
|
575
|
+
await appendGlobalPid({
|
|
576
|
+
pid: 1, // pid 1 is almost never the test process, so isPidAlive returns false
|
|
577
|
+
cli: "claude",
|
|
578
|
+
prompt: "exited agent",
|
|
579
|
+
cwd: process.cwd(),
|
|
580
|
+
log_file: null,
|
|
581
|
+
status: "exited",
|
|
582
|
+
exit_code: 0,
|
|
583
|
+
exit_reason: "done",
|
|
584
|
+
started_at: Date.now() - 10_000,
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
const cap = captureOutput();
|
|
588
|
+
let code: number | null;
|
|
589
|
+
try {
|
|
590
|
+
code = await mod.runSubcommand(["bun", "cli.js", "ls", "--all", "--json"]);
|
|
591
|
+
} finally {
|
|
592
|
+
cap.restore();
|
|
593
|
+
}
|
|
594
|
+
expect(code).toBe(0);
|
|
595
|
+
const parsed = JSON.parse(cap.stdout);
|
|
596
|
+
expect(Array.isArray(parsed)).toBe(true);
|
|
597
|
+
expect(parsed.some((r: any) => r.prompt === "exited agent")).toBe(true);
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
it("keyword filter restricts results to matching agents", async () => {
|
|
601
|
+
const mod = await loadModule();
|
|
602
|
+
const { appendGlobalPid } = await import("./globalPidIndex.ts");
|
|
603
|
+
await appendGlobalPid({
|
|
604
|
+
pid: process.pid,
|
|
605
|
+
cli: "claude",
|
|
606
|
+
prompt: "unique-xyzzy-prompt",
|
|
607
|
+
cwd: process.cwd(),
|
|
608
|
+
log_file: null,
|
|
609
|
+
status: "active",
|
|
610
|
+
exit_code: null,
|
|
611
|
+
exit_reason: null,
|
|
612
|
+
started_at: Date.now(),
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
const cap = captureOutput();
|
|
616
|
+
let code: number | null;
|
|
617
|
+
try {
|
|
618
|
+
code = await mod.runSubcommand(["bun", "cli.js", "ls", "--json", "unique-xyzzy-prompt"]);
|
|
619
|
+
} finally {
|
|
620
|
+
cap.restore();
|
|
621
|
+
}
|
|
622
|
+
expect(code).toBe(0);
|
|
623
|
+
const parsed = JSON.parse(cap.stdout);
|
|
624
|
+
expect(parsed.every((r: any) => r.prompt?.includes("unique-xyzzy-prompt"))).toBe(true);
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
it("keyword filter returns 'no running agents' when nothing matches", async () => {
|
|
628
|
+
const { runSubcommand } = await loadModule();
|
|
629
|
+
const stderr: string[] = [];
|
|
630
|
+
const orig = process.stderr.write.bind(process.stderr);
|
|
631
|
+
(process.stderr as any).write = (s: any) => {
|
|
632
|
+
stderr.push(String(s));
|
|
633
|
+
return true;
|
|
634
|
+
};
|
|
635
|
+
try {
|
|
636
|
+
const code = await runSubcommand(["bun", "cli.js", "ls", "no-match-zzzzzz"]);
|
|
637
|
+
expect(code).toBe(0);
|
|
638
|
+
expect(stderr.join("")).toMatch(/no running agents matched/);
|
|
639
|
+
} finally {
|
|
640
|
+
process.stderr.write = orig;
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
it("list alias routes to cmdLs", async () => {
|
|
645
|
+
const { runSubcommand } = await loadModule();
|
|
646
|
+
const stderr: string[] = [];
|
|
647
|
+
const orig = process.stderr.write.bind(process.stderr);
|
|
648
|
+
(process.stderr as any).write = (s: any) => {
|
|
649
|
+
stderr.push(String(s));
|
|
650
|
+
return true;
|
|
651
|
+
};
|
|
652
|
+
try {
|
|
653
|
+
const code = await runSubcommand(["bun", "cli.js", "list"]);
|
|
654
|
+
expect(code).toBe(0);
|
|
655
|
+
} finally {
|
|
656
|
+
process.stderr.write = orig;
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
it("ps alias routes to cmdLs", async () => {
|
|
661
|
+
const { runSubcommand } = await loadModule();
|
|
662
|
+
const stderr: string[] = [];
|
|
663
|
+
const orig = process.stderr.write.bind(process.stderr);
|
|
664
|
+
(process.stderr as any).write = (s: any) => {
|
|
665
|
+
stderr.push(String(s));
|
|
666
|
+
return true;
|
|
667
|
+
};
|
|
668
|
+
try {
|
|
669
|
+
const code = await runSubcommand(["bun", "cli.js", "ps"]);
|
|
670
|
+
expect(code).toBe(0);
|
|
671
|
+
} finally {
|
|
672
|
+
process.stderr.write = orig;
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
// ---------------------------------------------------------------------------
|
|
678
|
+
// cmdRead — head and cat modes
|
|
679
|
+
// ---------------------------------------------------------------------------
|
|
680
|
+
|
|
681
|
+
describe("subcommands.cmdRead head and cat modes", () => {
|
|
682
|
+
it("head emits first N lines", async () => {
|
|
683
|
+
const { runSubcommand } = await loadModule();
|
|
684
|
+
const { appendGlobalPid } = await import("./globalPidIndex.ts");
|
|
685
|
+
const tmp = await mkdtemp(path.join(tmpdir(), "ay-head-log-"));
|
|
686
|
+
try {
|
|
687
|
+
const logPath = path.join(tmp, "x.raw.log");
|
|
688
|
+
const lines: string[] = [];
|
|
689
|
+
for (let i = 0; i < 50; i++) lines.push(`line-${i}`);
|
|
690
|
+
await writeFile(logPath, lines.join("\r\n") + "\r\n");
|
|
691
|
+
|
|
692
|
+
await appendGlobalPid({
|
|
693
|
+
pid: process.pid,
|
|
694
|
+
cli: "claude",
|
|
695
|
+
prompt: null,
|
|
696
|
+
cwd: process.cwd(),
|
|
697
|
+
log_file: logPath,
|
|
698
|
+
status: "active",
|
|
699
|
+
exit_code: null,
|
|
700
|
+
exit_reason: null,
|
|
701
|
+
started_at: Date.now(),
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
const stdout: string[] = [];
|
|
705
|
+
const orig = process.stdout.write.bind(process.stdout);
|
|
706
|
+
(process.stdout as any).write = (s: any) => {
|
|
707
|
+
stdout.push(String(s));
|
|
708
|
+
return true;
|
|
709
|
+
};
|
|
710
|
+
const stderr_chunks: string[] = [];
|
|
711
|
+
const origErr = process.stderr.write.bind(process.stderr);
|
|
712
|
+
(process.stderr as any).write = (s: any) => {
|
|
713
|
+
stderr_chunks.push(String(s));
|
|
714
|
+
return true;
|
|
715
|
+
};
|
|
716
|
+
try {
|
|
717
|
+
const code = await runSubcommand(["bun", "cli.js", "head", String(process.pid), "-n", "5"]);
|
|
718
|
+
expect(code).toBe(0);
|
|
719
|
+
} finally {
|
|
720
|
+
process.stdout.write = orig;
|
|
721
|
+
process.stderr.write = origErr;
|
|
722
|
+
}
|
|
723
|
+
const text = stdout.join("");
|
|
724
|
+
expect(text).toMatch(/line-0/);
|
|
725
|
+
expect(text).toMatch(/line-4/);
|
|
726
|
+
expect(text).not.toMatch(/line-10\b/);
|
|
727
|
+
} finally {
|
|
728
|
+
await rm(tmp, { recursive: true, force: true }).catch(() => null);
|
|
729
|
+
}
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
it("cat emits all lines", async () => {
|
|
733
|
+
const { runSubcommand } = await loadModule();
|
|
734
|
+
const { appendGlobalPid } = await import("./globalPidIndex.ts");
|
|
735
|
+
const tmp = await mkdtemp(path.join(tmpdir(), "ay-cat-log-"));
|
|
736
|
+
try {
|
|
737
|
+
const logPath = path.join(tmp, "x.raw.log");
|
|
738
|
+
await writeFile(logPath, "alpha\r\nbeta\r\ngamma\r\n");
|
|
739
|
+
|
|
740
|
+
await appendGlobalPid({
|
|
741
|
+
pid: process.pid,
|
|
742
|
+
cli: "claude",
|
|
743
|
+
prompt: null,
|
|
744
|
+
cwd: process.cwd(),
|
|
745
|
+
log_file: logPath,
|
|
746
|
+
status: "active",
|
|
747
|
+
exit_code: null,
|
|
748
|
+
exit_reason: null,
|
|
749
|
+
started_at: Date.now(),
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
const stdout: string[] = [];
|
|
753
|
+
const orig = process.stdout.write.bind(process.stdout);
|
|
754
|
+
(process.stdout as any).write = (s: any) => {
|
|
755
|
+
stdout.push(String(s));
|
|
756
|
+
return true;
|
|
757
|
+
};
|
|
758
|
+
const stderr_chunks: string[] = [];
|
|
759
|
+
const origErr = process.stderr.write.bind(process.stderr);
|
|
760
|
+
(process.stderr as any).write = (s: any) => {
|
|
761
|
+
stderr_chunks.push(String(s));
|
|
762
|
+
return true;
|
|
763
|
+
};
|
|
764
|
+
try {
|
|
765
|
+
const code = await runSubcommand(["bun", "cli.js", "cat", String(process.pid)]);
|
|
766
|
+
expect(code).toBe(0);
|
|
767
|
+
} finally {
|
|
768
|
+
process.stdout.write = orig;
|
|
769
|
+
process.stderr.write = origErr;
|
|
770
|
+
}
|
|
771
|
+
const text = stdout.join("");
|
|
772
|
+
expect(text).toMatch(/alpha/);
|
|
773
|
+
expect(text).toMatch(/beta/);
|
|
774
|
+
expect(text).toMatch(/gamma/);
|
|
775
|
+
} finally {
|
|
776
|
+
await rm(tmp, { recursive: true, force: true }).catch(() => null);
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
// ---------------------------------------------------------------------------
|
|
782
|
+
// cmdNote
|
|
783
|
+
// ---------------------------------------------------------------------------
|
|
784
|
+
|
|
785
|
+
describe("subcommands.cmdNote", () => {
|
|
786
|
+
it("throws usage error when no keyword given", async () => {
|
|
787
|
+
const { runSubcommand } = await loadModule();
|
|
788
|
+
const stderr: string[] = [];
|
|
789
|
+
const orig = process.stderr.write.bind(process.stderr);
|
|
790
|
+
(process.stderr as any).write = (s: any) => {
|
|
791
|
+
stderr.push(String(s));
|
|
792
|
+
return true;
|
|
793
|
+
};
|
|
794
|
+
try {
|
|
795
|
+
const code = await runSubcommand(["bun", "cli.js", "note"]);
|
|
796
|
+
expect(code).toBe(1);
|
|
797
|
+
expect(stderr.join("")).toMatch(/usage:/i);
|
|
798
|
+
} finally {
|
|
799
|
+
process.stderr.write = orig;
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
it("sets a note on a matched agent", async () => {
|
|
804
|
+
const mod = await loadModule();
|
|
805
|
+
const { appendGlobalPid } = await import("./globalPidIndex.ts");
|
|
806
|
+
await appendGlobalPid({
|
|
807
|
+
pid: process.pid,
|
|
808
|
+
cli: "claude",
|
|
809
|
+
prompt: "note-target",
|
|
810
|
+
cwd: process.cwd(),
|
|
811
|
+
log_file: null,
|
|
812
|
+
status: "active",
|
|
813
|
+
exit_code: null,
|
|
814
|
+
exit_reason: null,
|
|
815
|
+
started_at: Date.now(),
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
const stdout: string[] = [];
|
|
819
|
+
const origOut = process.stdout.write.bind(process.stdout);
|
|
820
|
+
(process.stdout as any).write = (s: any) => {
|
|
821
|
+
stdout.push(String(s));
|
|
822
|
+
return true;
|
|
823
|
+
};
|
|
824
|
+
const origErr = process.stderr.write.bind(process.stderr);
|
|
825
|
+
(process.stderr as any).write = () => true;
|
|
826
|
+
try {
|
|
827
|
+
const code = await mod.runSubcommand([
|
|
828
|
+
"bun",
|
|
829
|
+
"cli.js",
|
|
830
|
+
"note",
|
|
831
|
+
String(process.pid),
|
|
832
|
+
"my note text",
|
|
833
|
+
]);
|
|
834
|
+
expect(code).toBe(0);
|
|
835
|
+
expect(stdout.join("")).toMatch(/note set/);
|
|
836
|
+
} finally {
|
|
837
|
+
process.stdout.write = origOut;
|
|
838
|
+
process.stderr.write = origErr;
|
|
839
|
+
}
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
it("clears a note when no text given", async () => {
|
|
843
|
+
const mod = await loadModule();
|
|
844
|
+
const { appendGlobalPid } = await import("./globalPidIndex.ts");
|
|
845
|
+
await appendGlobalPid({
|
|
846
|
+
pid: process.pid,
|
|
847
|
+
cli: "claude",
|
|
848
|
+
prompt: "note-clear-target",
|
|
849
|
+
cwd: process.cwd(),
|
|
850
|
+
log_file: null,
|
|
851
|
+
status: "active",
|
|
852
|
+
exit_code: null,
|
|
853
|
+
exit_reason: null,
|
|
854
|
+
started_at: Date.now(),
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
const stdout: string[] = [];
|
|
858
|
+
const origOut = process.stdout.write.bind(process.stdout);
|
|
859
|
+
(process.stdout as any).write = (s: any) => {
|
|
860
|
+
stdout.push(String(s));
|
|
861
|
+
return true;
|
|
862
|
+
};
|
|
863
|
+
(process.stderr as any).write = () => true;
|
|
864
|
+
try {
|
|
865
|
+
const code = await mod.runSubcommand(["bun", "cli.js", "note", String(process.pid)]);
|
|
866
|
+
expect(code).toBe(0);
|
|
867
|
+
expect(stdout.join("")).toMatch(/cleared note/);
|
|
868
|
+
} finally {
|
|
869
|
+
process.stdout.write = origOut;
|
|
870
|
+
process.stderr.write = process.stderr.write; // no-op restore (silenced above)
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
// ---------------------------------------------------------------------------
|
|
876
|
+
// cmdStatus
|
|
877
|
+
// ---------------------------------------------------------------------------
|
|
878
|
+
|
|
879
|
+
describe("subcommands.cmdStatus", () => {
|
|
880
|
+
it("throws usage error when no keyword given", async () => {
|
|
881
|
+
const { runSubcommand } = await loadModule();
|
|
882
|
+
const stderr: string[] = [];
|
|
883
|
+
const orig = process.stderr.write.bind(process.stderr);
|
|
884
|
+
(process.stderr as any).write = (s: any) => {
|
|
885
|
+
stderr.push(String(s));
|
|
886
|
+
return true;
|
|
887
|
+
};
|
|
888
|
+
try {
|
|
889
|
+
const code = await runSubcommand(["bun", "cli.js", "status"]);
|
|
890
|
+
expect(code).toBe(1);
|
|
891
|
+
expect(stderr.join("")).toMatch(/usage:/i);
|
|
892
|
+
} finally {
|
|
893
|
+
process.stderr.write = orig;
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
it("emits JSON snapshot for a matched agent", async () => {
|
|
898
|
+
const mod = await loadModule();
|
|
899
|
+
const { appendGlobalPid } = await import("./globalPidIndex.ts");
|
|
900
|
+
await appendGlobalPid({
|
|
901
|
+
pid: process.pid,
|
|
902
|
+
cli: "claude",
|
|
903
|
+
prompt: "status-test",
|
|
904
|
+
cwd: process.cwd(),
|
|
905
|
+
log_file: null,
|
|
906
|
+
status: "active",
|
|
907
|
+
exit_code: null,
|
|
908
|
+
exit_reason: null,
|
|
909
|
+
started_at: Date.now() - 1000,
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
const stdout: string[] = [];
|
|
913
|
+
const origOut = process.stdout.write.bind(process.stdout);
|
|
914
|
+
(process.stdout as any).write = (s: any) => {
|
|
915
|
+
stdout.push(String(s));
|
|
916
|
+
return true;
|
|
917
|
+
};
|
|
918
|
+
(process.stderr as any).write = () => true;
|
|
919
|
+
try {
|
|
920
|
+
const code = await mod.runSubcommand(["bun", "cli.js", "status", String(process.pid)]);
|
|
921
|
+
expect(code).toBe(0);
|
|
922
|
+
} finally {
|
|
923
|
+
process.stdout.write = origOut;
|
|
924
|
+
}
|
|
925
|
+
const snap = JSON.parse(stdout.join(""));
|
|
926
|
+
expect(snap).toMatchObject({ pid: process.pid, cli: "claude" });
|
|
927
|
+
expect(typeof snap.age_ms).toBe("number");
|
|
928
|
+
});
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
// ---------------------------------------------------------------------------
|
|
932
|
+
// cmdRestart
|
|
933
|
+
// ---------------------------------------------------------------------------
|
|
934
|
+
|
|
935
|
+
describe("subcommands.cmdRestart", () => {
|
|
936
|
+
it("returns 1 and warns when the agent is still alive", async () => {
|
|
937
|
+
const mod = await loadModule();
|
|
938
|
+
const { appendGlobalPid } = await import("./globalPidIndex.ts");
|
|
939
|
+
await appendGlobalPid({
|
|
940
|
+
pid: process.pid,
|
|
941
|
+
cli: "claude",
|
|
942
|
+
prompt: "restart-live-test",
|
|
943
|
+
cwd: process.cwd(),
|
|
944
|
+
log_file: null,
|
|
945
|
+
status: "active",
|
|
946
|
+
exit_code: null,
|
|
947
|
+
exit_reason: null,
|
|
948
|
+
started_at: Date.now(),
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
const stderr: string[] = [];
|
|
952
|
+
const origErr = process.stderr.write.bind(process.stderr);
|
|
953
|
+
(process.stderr as any).write = (s: any) => {
|
|
954
|
+
stderr.push(String(s));
|
|
955
|
+
return true;
|
|
956
|
+
};
|
|
957
|
+
try {
|
|
958
|
+
const code = await mod.runSubcommand(["bun", "cli.js", "restart", String(process.pid)]);
|
|
959
|
+
expect(code).toBe(1);
|
|
960
|
+
expect(stderr.join("")).toMatch(/still running/);
|
|
961
|
+
} finally {
|
|
962
|
+
process.stderr.write = origErr;
|
|
963
|
+
}
|
|
964
|
+
});
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
// ---------------------------------------------------------------------------
|
|
968
|
+
// listRecords merges per-cwd TS file with global
|
|
969
|
+
// ---------------------------------------------------------------------------
|
|
970
|
+
|
|
528
971
|
describe("subcommands.listRecords merges per-cwd TS file with global", () => {
|
|
529
972
|
it("includes records from <cwd>/.agent-yes/pid-records.jsonl", async () => {
|
|
530
973
|
// Write a fake per-cwd file that uses the live process pid so liveOnly
|