claude-yes 1.149.1 → 1.150.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-BsjC_rd9.js +8 -0
- package/dist/{SUPPORTED_CLIS-DyQjQODe.js → SUPPORTED_CLIS-D8mK1P78.js} +2 -2
- package/dist/cli.js +3 -3
- package/dist/index.js +2 -2
- package/dist/{schedule-BJk96cOy.js → schedule-3FpLAXCx.js} +4 -4
- package/dist/{serve-CaIDZfhy.js → serve-plR3NUPH.js} +45 -21
- package/dist/{setup-B15dHEd1.js → setup-BWGYyij8.js} +2 -2
- package/dist/subcommands-BDftiFrp.js +9 -0
- package/dist/{subcommands-OyK7Y447.js → subcommands-BtUKGF4n.js} +38 -12
- package/dist/{ts-C21nFgXQ.js → ts-Cng1o5fC.js} +7 -5
- package/dist/{versionChecker-C-JJk14E.js → versionChecker-CeVZzVQB.js} +2 -2
- package/lab/ui/index.html +62 -2
- package/package.json +1 -1
- package/ts/index.ts +12 -3
- package/ts/serve.ts +71 -18
- package/ts/subcommands.spec.ts +77 -0
- package/ts/subcommands.ts +39 -9
- package/dist/SUPPORTED_CLIS-DhVolHXQ.js +0 -8
- package/dist/subcommands-Bv7xJq9E.js +0 -9
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import "./ts-Cng1o5fC.js";
|
|
2
|
+
import "./logger-CDIsZ-Pp.js";
|
|
3
|
+
import "./versionChecker-CeVZzVQB.js";
|
|
4
|
+
import "./pidStore-fqXqTKkh.js";
|
|
5
|
+
import "./globalPidIndex-DlmmJlO8.js";
|
|
6
|
+
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-D8mK1P78.js";
|
|
7
|
+
|
|
8
|
+
export { SUPPORTED_CLIS };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { t as CLIS_CONFIG } from "./ts-
|
|
1
|
+
import { t as CLIS_CONFIG } from "./ts-Cng1o5fC.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-D8mK1P78.js.map
|
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-CeVZzVQB.js";
|
|
5
5
|
import { argv } from "process";
|
|
6
6
|
import { execFileSync, spawn } from "child_process";
|
|
7
7
|
import ms from "ms";
|
|
@@ -480,7 +480,7 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
|
|
|
480
480
|
const rawArg = process.argv[2];
|
|
481
481
|
const managerCommands = !invokedCliName(process.argv);
|
|
482
482
|
const isHelpFlag = rawArg === "-h" || rawArg === "--help";
|
|
483
|
-
const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-
|
|
483
|
+
const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-BDftiFrp.js");
|
|
484
484
|
if (isHelpFlag && process.argv.length === 3) {
|
|
485
485
|
cmdHelp(managerCommands);
|
|
486
486
|
process.exit(0);
|
|
@@ -513,7 +513,7 @@ if (config.useRust) {
|
|
|
513
513
|
}
|
|
514
514
|
}
|
|
515
515
|
if (rustBinary) {
|
|
516
|
-
const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-
|
|
516
|
+
const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-BsjC_rd9.js");
|
|
517
517
|
const rustArgs = buildRustArgs(process.argv, config.cli, SUPPORTED_CLIS);
|
|
518
518
|
if (config.verbose) {
|
|
519
519
|
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-Cng1o5fC.js";
|
|
2
2
|
import "./logger-CDIsZ-Pp.js";
|
|
3
|
-
import "./versionChecker-
|
|
3
|
+
import "./versionChecker-CeVZzVQB.js";
|
|
4
4
|
import "./pidStore-fqXqTKkh.js";
|
|
5
5
|
import "./globalPidIndex-DlmmJlO8.js";
|
|
6
6
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import "./ts-
|
|
1
|
+
import "./ts-Cng1o5fC.js";
|
|
2
2
|
import "./logger-CDIsZ-Pp.js";
|
|
3
|
-
import "./versionChecker-
|
|
3
|
+
import "./versionChecker-CeVZzVQB.js";
|
|
4
4
|
import "./pidStore-fqXqTKkh.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-D8mK1P78.js";
|
|
7
7
|
import { i as resolveSpawnCwd } from "./workspaceConfig-B9VX8tVO.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-3FpLAXCx.js.map
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import "./ts-
|
|
1
|
+
import "./ts-Cng1o5fC.js";
|
|
2
2
|
import "./logger-CDIsZ-Pp.js";
|
|
3
|
-
import { r as getInstalledPackage } from "./versionChecker-
|
|
3
|
+
import { r as getInstalledPackage } from "./versionChecker-CeVZzVQB.js";
|
|
4
4
|
import "./pidStore-fqXqTKkh.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-C1C04bbq.js";
|
|
8
|
-
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-
|
|
8
|
+
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-D8mK1P78.js";
|
|
9
9
|
import "./e2e-ClOI_aqV.js";
|
|
10
10
|
import "./webrtcLink-BWhuA74k.js";
|
|
11
11
|
import "./remotes-qK6uozO4.js";
|
|
12
12
|
import { i as resolveSpawnCwd, r as isProvisionAllowed, t as getProvisionRoot } from "./workspaceConfig-B9VX8tVO.js";
|
|
13
|
-
import {
|
|
13
|
+
import { C as snapshotStatus, T as writeToIpc, _ as readPtysize, b as resolveOne, c as extractTaskCounts, g as readNotes, i as controlCodeFromName, m as listRecords, o as deriveLiveStatus, v as renderRawLog } from "./subcommands-BtUKGF4n.js";
|
|
14
14
|
import yargs from "yargs";
|
|
15
15
|
import { mkdir, open, readFile, stat, writeFile } from "fs/promises";
|
|
16
16
|
import { homedir, hostname, userInfo } from "os";
|
|
@@ -802,9 +802,13 @@ Options:
|
|
|
802
802
|
try {
|
|
803
803
|
const record = await resolveOne(keyword, defaultOpts());
|
|
804
804
|
if (!record.log_file) return new Response(`pid ${record.pid}: no log_file`, { status: 404 });
|
|
805
|
-
const
|
|
805
|
+
const buf = await readFile(record.log_file);
|
|
806
|
+
const size = await readPtysize(record.pid);
|
|
807
|
+
const text = await renderRawLog(buf, {
|
|
806
808
|
mode,
|
|
807
|
-
n
|
|
809
|
+
n,
|
|
810
|
+
cols: size?.cols,
|
|
811
|
+
rows: size?.rows
|
|
808
812
|
});
|
|
809
813
|
return new Response(text, { headers: { "Content-Type": "text/plain; charset=utf-8" } });
|
|
810
814
|
} catch (e) {
|
|
@@ -816,20 +820,11 @@ Options:
|
|
|
816
820
|
const keyword = decodeURIComponent(sizeM[1]);
|
|
817
821
|
try {
|
|
818
822
|
const record = await resolveOne(keyword, defaultOpts());
|
|
819
|
-
const
|
|
820
|
-
let cols = null;
|
|
821
|
-
let rows = null;
|
|
822
|
-
try {
|
|
823
|
-
const [c = 0, r = 0] = (await readFile(path.join(ayHome, "ptysize", String(record.pid)), "utf-8")).trim().split(/\s+/).map(Number);
|
|
824
|
-
if (c > 0 && r > 0) {
|
|
825
|
-
cols = c;
|
|
826
|
-
rows = r;
|
|
827
|
-
}
|
|
828
|
-
} catch {}
|
|
823
|
+
const size = await readPtysize(record.pid);
|
|
829
824
|
return Response.json({
|
|
830
825
|
pid: record.pid,
|
|
831
|
-
cols,
|
|
832
|
-
rows
|
|
826
|
+
cols: size?.cols ?? null,
|
|
827
|
+
rows: size?.rows ?? null
|
|
833
828
|
});
|
|
834
829
|
} catch (e) {
|
|
835
830
|
return new Response(e.message, { status: 404 });
|
|
@@ -1051,8 +1046,37 @@ Options:
|
|
|
1051
1046
|
const prompt = String(body.prompt ?? "");
|
|
1052
1047
|
let cwd;
|
|
1053
1048
|
let provisioned = null;
|
|
1049
|
+
const fork = body.fork && typeof body.fork.fromCwd === "string" && typeof body.fork.branch === "string" ? {
|
|
1050
|
+
fromCwd: body.fork.fromCwd,
|
|
1051
|
+
branch: body.fork.branch
|
|
1052
|
+
} : null;
|
|
1054
1053
|
const from = typeof body.from === "string" ? body.from.trim() : "";
|
|
1055
|
-
if (
|
|
1054
|
+
if (fork) {
|
|
1055
|
+
let prov;
|
|
1056
|
+
try {
|
|
1057
|
+
prov = await import("codehost/provision");
|
|
1058
|
+
} catch (e) {
|
|
1059
|
+
return new Response(`fork needs the 'codehost' package (codehost/provision) — install it (npm i -g codehost) or 'bun link' it for local dev: ${e.message}`, { status: 501 });
|
|
1060
|
+
}
|
|
1061
|
+
let result;
|
|
1062
|
+
try {
|
|
1063
|
+
const wsRoot = getProvisionRoot();
|
|
1064
|
+
result = await prov.forkWorktree({
|
|
1065
|
+
fromCwd: fork.fromCwd,
|
|
1066
|
+
branch: fork.branch,
|
|
1067
|
+
...wsRoot ? { wsRoot } : {}
|
|
1068
|
+
});
|
|
1069
|
+
} catch (e) {
|
|
1070
|
+
return new Response(`fork failed: ${e.message}`, { status: 502 });
|
|
1071
|
+
}
|
|
1072
|
+
if (!result?.ok) return new Response(result?.error ?? "fork failed", { status: 502 });
|
|
1073
|
+
if (result.spec && !isProvisionAllowed(result.spec.owner, result.spec.repo)) return new Response(`forking '${result.spec.owner}/${result.spec.repo}' is not allowed — add the owner to provisionAllowlist in ~/.agent-yes/config.json (or "*" to allow all)`, { status: 403 });
|
|
1074
|
+
cwd = result.folder;
|
|
1075
|
+
provisioned = {
|
|
1076
|
+
action: result.action,
|
|
1077
|
+
folder: result.folder
|
|
1078
|
+
};
|
|
1079
|
+
} else if (from) {
|
|
1056
1080
|
let prov;
|
|
1057
1081
|
try {
|
|
1058
1082
|
prov = await import("codehost/provision");
|
|
@@ -1088,7 +1112,7 @@ Options:
|
|
|
1088
1112
|
return new Response(`cannot create cwd ${cwd}: ${e.message}`, { status: 500 });
|
|
1089
1113
|
}
|
|
1090
1114
|
}
|
|
1091
|
-
process.stderr.write(`→ console spawned: ay ${cli}${prompt ? ` -- "${prompt.slice(0, 60)}"` : ""} (cwd: ${cwd}${provisioned ? `, ${provisioned.action}
|
|
1115
|
+
process.stderr.write(`→ console spawned: ay ${cli}${prompt ? ` -- "${prompt.slice(0, 60)}"` : ""} (cwd: ${cwd}${provisioned ? `, ${provisioned.action}` : ""})\n`);
|
|
1092
1116
|
const ayBin = Bun.which("ay") ?? process.argv[1];
|
|
1093
1117
|
const ayCmd = process.platform === "win32" && ayBin.toLowerCase().endsWith(".exe") ? [ayBin] : [process.execPath, ayBin];
|
|
1094
1118
|
try {
|
|
@@ -1235,4 +1259,4 @@ Options:
|
|
|
1235
1259
|
|
|
1236
1260
|
//#endregion
|
|
1237
1261
|
export { cmdServe };
|
|
1238
|
-
//# sourceMappingURL=serve-
|
|
1262
|
+
//# sourceMappingURL=serve-plR3NUPH.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-plR3NUPH.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-BWGYyij8.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import "./logger-CDIsZ-Pp.js";
|
|
2
|
+
import "./globalPidIndex-DlmmJlO8.js";
|
|
3
|
+
import "./configShared-C1C04bbq.js";
|
|
4
|
+
import "./e2e-ClOI_aqV.js";
|
|
5
|
+
import "./webrtcLink-BWhuA74k.js";
|
|
6
|
+
import "./remotes-qK6uozO4.js";
|
|
7
|
+
import { C as snapshotStatus, S as runSubcommand, T as writeToIpc, _ as readPtysize, a as cursorAbs, b as resolveOne, c as extractTaskCounts, d as isExitRequest, f as isPidAlive, g as readNotes, h as matchKeyword, i as controlCodeFromName, l as finalizedLines, m as listRecords, n as READ_PAGE_DEFAULT, o as deriveLiveStatus, p as isSubcommand, r as cmdHelp, s as extractNeedsInput, t as GRACEFUL_EXIT_COMMANDS, u as isAgentStuck, v as renderRawLog, w as stopTipForCli, x as resolveReadWindow, y as renderRawLogLines } from "./subcommands-BtUKGF4n.js";
|
|
8
|
+
|
|
9
|
+
export { cmdHelp, isSubcommand, runSubcommand };
|
|
@@ -554,15 +554,15 @@ async function runSubcommand(argv) {
|
|
|
554
554
|
case "restart": return await cmdRestart(rest);
|
|
555
555
|
case "note": return await cmdNote(rest);
|
|
556
556
|
case "serve": {
|
|
557
|
-
const { cmdServe } = await import("./serve-
|
|
557
|
+
const { cmdServe } = await import("./serve-plR3NUPH.js");
|
|
558
558
|
return cmdServe(rest);
|
|
559
559
|
}
|
|
560
560
|
case "setup": {
|
|
561
|
-
const { cmdSetup } = await import("./setup-
|
|
561
|
+
const { cmdSetup } = await import("./setup-BWGYyij8.js");
|
|
562
562
|
return cmdSetup(rest);
|
|
563
563
|
}
|
|
564
564
|
case "schedule": {
|
|
565
|
-
const { cmdSchedule } = await import("./schedule-
|
|
565
|
+
const { cmdSchedule } = await import("./schedule-3FpLAXCx.js");
|
|
566
566
|
return cmdSchedule(rest);
|
|
567
567
|
}
|
|
568
568
|
case "remote": {
|
|
@@ -1298,12 +1298,15 @@ async function cmdRead(rest, { mode }) {
|
|
|
1298
1298
|
}
|
|
1299
1299
|
if (!stats.isFile()) throw new Error(`pid ${record.pid}: log path is not a file: ${logPath}`);
|
|
1300
1300
|
const buf = await readFile(logPath);
|
|
1301
|
+
const size = await readPtysize(record.pid);
|
|
1301
1302
|
const noteLabel = (await readNotes()).get(record.pid);
|
|
1302
1303
|
const header = noteLabel ? `[pid ${record.pid} ${shortenPath(record.cwd)} * ${noteLabel}]` : `[pid ${record.pid} ${shortenPath(record.cwd)}]`;
|
|
1303
1304
|
if (follow) {
|
|
1304
1305
|
const rendered = await renderRawLog(buf, {
|
|
1305
1306
|
mode,
|
|
1306
|
-
n
|
|
1307
|
+
n,
|
|
1308
|
+
cols: size?.cols,
|
|
1309
|
+
rows: size?.rows
|
|
1307
1310
|
});
|
|
1308
1311
|
process.stderr.write(header + "\n");
|
|
1309
1312
|
process.stdout.write(rendered);
|
|
@@ -1311,7 +1314,10 @@ async function cmdRead(rest, { mode }) {
|
|
|
1311
1314
|
setInterval(() => void recordRead(reader.key, record.pid), 3e4).unref?.();
|
|
1312
1315
|
return plain ? followPlainLocal(logPath, buf) : followRawLocal(logPath, buf);
|
|
1313
1316
|
}
|
|
1314
|
-
const allLines = await renderRawLogLines(buf
|
|
1317
|
+
const allLines = await renderRawLogLines(buf, {
|
|
1318
|
+
cols: size?.cols,
|
|
1319
|
+
rows: size?.rows
|
|
1320
|
+
});
|
|
1315
1321
|
const total = allLines.length;
|
|
1316
1322
|
const win = resolveReadWindow({
|
|
1317
1323
|
total,
|
|
@@ -1493,11 +1499,31 @@ async function followPlainLocal(logPath, buf) {
|
|
|
1493
1499
|
return 0;
|
|
1494
1500
|
}
|
|
1495
1501
|
/**
|
|
1502
|
+
* Read an agent's last-known PTY geometry from `~/.agent-yes/ptysize/<pid>`
|
|
1503
|
+
* (written by both runtimes — ts/index.ts and rs/src/pty_spawner.rs — as
|
|
1504
|
+
* "<cols> <rows>\n"). Returns null when there's no sidecar (older agent, or not
|
|
1505
|
+
* yet written).
|
|
1506
|
+
*/
|
|
1507
|
+
async function readPtysize(pid) {
|
|
1508
|
+
const dir = process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
|
|
1509
|
+
try {
|
|
1510
|
+
const [c = 0, r = 0] = (await readFile(path.join(dir, "ptysize", String(pid)), "utf-8")).trim().split(/\s+/).map(Number);
|
|
1511
|
+
if (c > 0 && r > 0) return {
|
|
1512
|
+
cols: c,
|
|
1513
|
+
rows: r
|
|
1514
|
+
};
|
|
1515
|
+
} catch {}
|
|
1516
|
+
return null;
|
|
1517
|
+
}
|
|
1518
|
+
/**
|
|
1496
1519
|
* Feed the raw PTY bytes through @xterm/headless and emit plain text.
|
|
1497
1520
|
* Same approach as koho's renderTerminalBuffer + agent-yes's XtermProxy.
|
|
1498
1521
|
*/
|
|
1499
|
-
async function renderRawLog(buf, { mode, n }) {
|
|
1500
|
-
const lines = await renderRawLogLines(buf
|
|
1522
|
+
async function renderRawLog(buf, { mode, n, cols, rows }) {
|
|
1523
|
+
const lines = await renderRawLogLines(buf, {
|
|
1524
|
+
cols,
|
|
1525
|
+
rows
|
|
1526
|
+
});
|
|
1501
1527
|
if (mode === "cat") return lines.join("\n");
|
|
1502
1528
|
if (mode === "tail") return lines.slice(Math.max(0, lines.length - n)).join("\n");
|
|
1503
1529
|
return lines.slice(0, n).join("\n");
|
|
@@ -1510,9 +1536,9 @@ async function renderRawLog(buf, { mode, n }) {
|
|
|
1510
1536
|
* cursor moves / clears / wraps), so we always render the whole buffer once and
|
|
1511
1537
|
* window the resulting lines.
|
|
1512
1538
|
*/
|
|
1513
|
-
async function renderRawLogLines(buf) {
|
|
1514
|
-
const cols = 200;
|
|
1515
|
-
const rows = 50;
|
|
1539
|
+
async function renderRawLogLines(buf, geom) {
|
|
1540
|
+
const cols = geom?.cols && geom.cols > 0 ? geom.cols : 200;
|
|
1541
|
+
const rows = geom?.rows && geom.rows > 0 ? geom.rows : 50;
|
|
1516
1542
|
const scrollback = 5e4;
|
|
1517
1543
|
try {
|
|
1518
1544
|
const { Terminal } = await import("@xterm/headless");
|
|
@@ -2592,5 +2618,5 @@ async function cmdResultSet(rest) {
|
|
|
2592
2618
|
}
|
|
2593
2619
|
|
|
2594
2620
|
//#endregion
|
|
2595
|
-
export {
|
|
2596
|
-
//# sourceMappingURL=subcommands-
|
|
2621
|
+
export { snapshotStatus as C, runSubcommand as S, writeToIpc as T, readPtysize as _, cursorAbs as a, resolveOne as b, extractTaskCounts as c, isExitRequest as d, isPidAlive as f, readNotes as g, matchKeyword as h, controlCodeFromName as i, finalizedLines as l, listRecords as m, READ_PAGE_DEFAULT as n, deriveLiveStatus as o, isSubcommand as p, cmdHelp as r, extractNeedsInput as s, GRACEFUL_EXIT_COMMANDS as t, isAgentStuck as u, renderRawLog as v, stopTipForCli as w, resolveReadWindow as x, renderRawLogLines as y };
|
|
2622
|
+
//# sourceMappingURL=subcommands-BtUKGF4n.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-CeVZzVQB.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-fqXqTKkh.js";
|
|
@@ -1228,8 +1228,10 @@ async function agentYes({ cli, cliArgs = [], skipPermissions = false, prompt, ro
|
|
|
1228
1228
|
if (unresolved) continue;
|
|
1229
1229
|
ptyEnv[key] = value;
|
|
1230
1230
|
}
|
|
1231
|
+
if (!ptyEnv.TERM) ptyEnv.TERM = "xterm-256color";
|
|
1232
|
+
if (!ptyEnv.COLORTERM) ptyEnv.COLORTERM = "truecolor";
|
|
1231
1233
|
const ptyOptions = {
|
|
1232
|
-
name:
|
|
1234
|
+
name: ptyEnv.TERM,
|
|
1233
1235
|
...getTerminalDimensions(),
|
|
1234
1236
|
cwd: cwd ?? process.cwd(),
|
|
1235
1237
|
env: ptyEnv
|
|
@@ -1330,7 +1332,7 @@ async function agentYes({ cli, cliArgs = [], skipPermissions = false, prompt, ro
|
|
|
1330
1332
|
let [bin, ...args] = [...parseCommandString(cliConf?.binary || cli), ...cliArgs.filter((arg) => !["--continue", "--resume"].includes(arg))];
|
|
1331
1333
|
logger.info(`Restarting ${cli} ${JSON.stringify([bin, ...args])}`);
|
|
1332
1334
|
const restartPtyOptions = {
|
|
1333
|
-
name:
|
|
1335
|
+
name: ptyEnv.TERM,
|
|
1334
1336
|
...getTerminalDimensions(),
|
|
1335
1337
|
cwd: cwd ?? process.cwd(),
|
|
1336
1338
|
env: ptyEnv
|
|
@@ -1414,7 +1416,7 @@ async function agentYes({ cli, cliArgs = [], skipPermissions = false, prompt, ro
|
|
|
1414
1416
|
} else logger.debug(`restore|no stored session, using default restore args`);
|
|
1415
1417
|
}
|
|
1416
1418
|
const restorePtyOptions = {
|
|
1417
|
-
name:
|
|
1419
|
+
name: ptyEnv.TERM,
|
|
1418
1420
|
...getTerminalDimensions(),
|
|
1419
1421
|
cwd: cwd ?? process.cwd(),
|
|
1420
1422
|
env: ptyEnv
|
|
@@ -1800,4 +1802,4 @@ function sleep(ms) {
|
|
|
1800
1802
|
|
|
1801
1803
|
//#endregion
|
|
1802
1804
|
export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
|
|
1803
|
-
//# sourceMappingURL=ts-
|
|
1805
|
+
//# sourceMappingURL=ts-Cng1o5fC.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.150.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-CeVZzVQB.js.map
|
package/lab/ui/index.html
CHANGED
|
@@ -3794,6 +3794,18 @@
|
|
|
3794
3794
|
}, 120);
|
|
3795
3795
|
}
|
|
3796
3796
|
|
|
3797
|
+
// Auto-derive a branch name from the typed prompt for the Cmd+K fork action
|
|
3798
|
+
// (editable at launch). e.g. "fix the auth bug" → "fork/fix-the-auth-bug".
|
|
3799
|
+
function forkSlug(q) {
|
|
3800
|
+
const s = q
|
|
3801
|
+
.toLowerCase()
|
|
3802
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
3803
|
+
.replace(/^-+|-+$/g, "")
|
|
3804
|
+
.slice(0, 40)
|
|
3805
|
+
.replace(/-+$/, "");
|
|
3806
|
+
return "fork/" + (s || "wip");
|
|
3807
|
+
}
|
|
3808
|
+
|
|
3797
3809
|
function renderOmni() {
|
|
3798
3810
|
const q = $("omni-input").value.trim();
|
|
3799
3811
|
if (omniIdx >= omniRows.length) omniIdx = Math.max(0, omniRows.length - 1);
|
|
@@ -3809,6 +3821,22 @@
|
|
|
3809
3821
|
<div class="omni-sub">${esc(anchor?.cwd || "(host default)")} · prompt: ${esc(q || "(empty)")}</div>
|
|
3810
3822
|
</div></div>`;
|
|
3811
3823
|
}
|
|
3824
|
+
if (r.kind === "fork") {
|
|
3825
|
+
return `<div class="omni-row omni-spawn${sc}" data-i="${i}">
|
|
3826
|
+
<span class="dot active"></span>
|
|
3827
|
+
<div class="omni-main">
|
|
3828
|
+
<div class="omni-title">⑂ Fork current branch & run <span style="opacity:.55">(carries uncommitted work)</span></div>
|
|
3829
|
+
<div class="omni-sub">${esc(anchor?.cwd || "?")} → new branch <b>${esc(r.branch)}</b> · ⏎ to edit & launch</div>
|
|
3830
|
+
</div></div>`;
|
|
3831
|
+
}
|
|
3832
|
+
if (r.kind === "spawncwd") {
|
|
3833
|
+
return `<div class="omni-row omni-spawn${sc}" data-i="${i}">
|
|
3834
|
+
<span class="dot active"></span>
|
|
3835
|
+
<div class="omni-main">
|
|
3836
|
+
<div class="omni-title">⊕ Spawn new ${esc(anchor?.cli || "claude")} agent in a directory…</div>
|
|
3837
|
+
<div class="omni-sub">⏎ to choose a working dir · prompt: ${esc(q || "(empty)")}</div>
|
|
3838
|
+
</div></div>`;
|
|
3839
|
+
}
|
|
3812
3840
|
const e = r.entry;
|
|
3813
3841
|
const snip = r.snippet ? `<div class="omni-snip">…${r.snippet}…</div>` : "";
|
|
3814
3842
|
return `<div class="omni-row${sc}" data-i="${i}">
|
|
@@ -3837,7 +3865,12 @@
|
|
|
3837
3865
|
)
|
|
3838
3866
|
.slice(0, 30);
|
|
3839
3867
|
omniRows = hits.map((entry) => ({ kind: "agent", entry }));
|
|
3840
|
-
if (q)
|
|
3868
|
+
if (q) {
|
|
3869
|
+
omniRows.push({ kind: "spawn" });
|
|
3870
|
+
const anchor = omniAnchor();
|
|
3871
|
+
if (anchor?.cwd) omniRows.push({ kind: "fork", branch: forkSlug(q) });
|
|
3872
|
+
omniRows.push({ kind: "spawncwd" });
|
|
3873
|
+
}
|
|
3841
3874
|
omniIdx = 0;
|
|
3842
3875
|
renderOmni();
|
|
3843
3876
|
// Tier 2 — debounced output (tail) search for alive agents not already shown.
|
|
@@ -3902,8 +3935,9 @@
|
|
|
3902
3935
|
async function omniActivate(forceSpawn) {
|
|
3903
3936
|
const q = $("omni-input").value.trim();
|
|
3904
3937
|
const row = omniRows[omniIdx];
|
|
3938
|
+
const anchor = omniAnchor();
|
|
3939
|
+
// ⌘⏎ always = spawn-here (the fast default), regardless of the selected row.
|
|
3905
3940
|
if (forceSpawn || (row && row.kind === "spawn")) {
|
|
3906
|
-
const anchor = omniAnchor();
|
|
3907
3941
|
closeOmni(false); // committing — keep the bg as-is, spawnAndSelect takes over
|
|
3908
3942
|
await spawnAndSelect(
|
|
3909
3943
|
{ cli: anchor?.cli || "claude", cwd: anchor?.cwd || undefined, prompt: q || undefined },
|
|
@@ -3911,6 +3945,31 @@
|
|
|
3911
3945
|
);
|
|
3912
3946
|
return;
|
|
3913
3947
|
}
|
|
3948
|
+
if (row && row.kind === "fork") {
|
|
3949
|
+
// Editable at launch (auto-slug is the default); carries the anchor's WIP.
|
|
3950
|
+
const branch = prompt("Fork to a new branch (carries your uncommitted work):", row.branch);
|
|
3951
|
+
if (!branch || !branch.trim()) return; // cancelled — leave the omnibox open
|
|
3952
|
+
closeOmni(false);
|
|
3953
|
+
await spawnAndSelect(
|
|
3954
|
+
{
|
|
3955
|
+
cli: anchor?.cli || "claude",
|
|
3956
|
+
prompt: q || undefined,
|
|
3957
|
+
fork: { fromCwd: anchor?.cwd, branch: branch.trim() },
|
|
3958
|
+
},
|
|
3959
|
+
anchor?._room,
|
|
3960
|
+
);
|
|
3961
|
+
return;
|
|
3962
|
+
}
|
|
3963
|
+
if (row && row.kind === "spawncwd") {
|
|
3964
|
+
const cwd = prompt("Spawn in which working directory?", anchor?.cwd || "");
|
|
3965
|
+
if (cwd == null) return; // cancelled
|
|
3966
|
+
closeOmni(false);
|
|
3967
|
+
await spawnAndSelect(
|
|
3968
|
+
{ cli: anchor?.cli || "claude", cwd: cwd.trim() || undefined, prompt: q || undefined },
|
|
3969
|
+
anchor?._room,
|
|
3970
|
+
);
|
|
3971
|
+
return;
|
|
3972
|
+
}
|
|
3914
3973
|
if (row && row.kind === "agent") {
|
|
3915
3974
|
closeOmni(false); // committing the previewed agent — don't restore the original
|
|
3916
3975
|
select(row.entry._key);
|
|
@@ -4335,6 +4394,7 @@
|
|
|
4335
4394
|
const res = await target.tx.post("/api/spawn", {
|
|
4336
4395
|
cli: spec.cli || "claude",
|
|
4337
4396
|
from: spec.from || undefined,
|
|
4397
|
+
fork: spec.fork || undefined,
|
|
4338
4398
|
cwd: spec.cwd || undefined,
|
|
4339
4399
|
prompt: spec.prompt || undefined,
|
|
4340
4400
|
});
|
package/package.json
CHANGED
package/ts/index.ts
CHANGED
|
@@ -395,8 +395,17 @@ export default async function agentYes({
|
|
|
395
395
|
ptyEnv[key] = value;
|
|
396
396
|
}
|
|
397
397
|
}
|
|
398
|
+
// The agent runs in a PTY (a real terminal), so advertise terminal
|
|
399
|
+
// capabilities. A console/daemon-spawned agent inherits an env with no TERM/
|
|
400
|
+
// COLORTERM: neither the daemon (no controlling terminal) nor the recovered
|
|
401
|
+
// login-shell env (captured without a tty) carries them — those vars are set
|
|
402
|
+
// by the terminal emulator, not by the shell. Without them the wrapped CLI
|
|
403
|
+
// renders colorless in the web console. Fill only when absent so a
|
|
404
|
+
// terminal-launched agent keeps its real values (e.g. xterm-256color, tmux).
|
|
405
|
+
if (!ptyEnv.TERM) ptyEnv.TERM = "xterm-256color";
|
|
406
|
+
if (!ptyEnv.COLORTERM) ptyEnv.COLORTERM = "truecolor";
|
|
398
407
|
const ptyOptions = {
|
|
399
|
-
name:
|
|
408
|
+
name: ptyEnv.TERM,
|
|
400
409
|
...getTerminalDimensions(),
|
|
401
410
|
cwd: cwd ?? process.cwd(),
|
|
402
411
|
env: ptyEnv,
|
|
@@ -561,7 +570,7 @@ export default async function agentYes({
|
|
|
561
570
|
logger.info(`Restarting ${cli} ${JSON.stringify([bin, ...args])}`);
|
|
562
571
|
|
|
563
572
|
const restartPtyOptions = {
|
|
564
|
-
name:
|
|
573
|
+
name: ptyEnv.TERM,
|
|
565
574
|
...getTerminalDimensions(),
|
|
566
575
|
cwd: cwd ?? process.cwd(),
|
|
567
576
|
env: ptyEnv,
|
|
@@ -667,7 +676,7 @@ export default async function agentYes({
|
|
|
667
676
|
}
|
|
668
677
|
|
|
669
678
|
const restorePtyOptions = {
|
|
670
|
-
name:
|
|
679
|
+
name: ptyEnv.TERM,
|
|
671
680
|
...getTerminalDimensions(),
|
|
672
681
|
cwd: cwd ?? process.cwd(),
|
|
673
682
|
env: ptyEnv,
|
package/ts/serve.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
extractTaskCounts,
|
|
12
12
|
listRecords,
|
|
13
13
|
readNotes,
|
|
14
|
+
readPtysize,
|
|
14
15
|
renderRawLog,
|
|
15
16
|
resolveOne,
|
|
16
17
|
snapshotStatus,
|
|
@@ -1163,7 +1164,8 @@ export async function cmdServe(rest: string[]): Promise<number> {
|
|
|
1163
1164
|
if (!record.log_file)
|
|
1164
1165
|
return new Response(`pid ${record.pid}: no log_file`, { status: 404 });
|
|
1165
1166
|
const buf = await readFile(record.log_file);
|
|
1166
|
-
const
|
|
1167
|
+
const size = await readPtysize(record.pid);
|
|
1168
|
+
const text = await renderRawLog(buf, { mode, n, cols: size?.cols, rows: size?.rows });
|
|
1167
1169
|
return new Response(text, { headers: { "Content-Type": "text/plain; charset=utf-8" } });
|
|
1168
1170
|
} catch (e) {
|
|
1169
1171
|
return new Response((e as Error).message, { status: 404 });
|
|
@@ -1177,20 +1179,12 @@ export async function cmdServe(rest: string[]): Promise<number> {
|
|
|
1177
1179
|
const keyword = decodeURIComponent(sizeM[1]!);
|
|
1178
1180
|
try {
|
|
1179
1181
|
const record = await resolveOne(keyword, defaultOpts());
|
|
1180
|
-
const
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
if (c > 0 && r > 0) {
|
|
1187
|
-
cols = c;
|
|
1188
|
-
rows = r;
|
|
1189
|
-
}
|
|
1190
|
-
} catch {
|
|
1191
|
-
/* no ptysize sidecar (older agent or not yet written) */
|
|
1192
|
-
}
|
|
1193
|
-
return Response.json({ pid: record.pid, cols, rows });
|
|
1182
|
+
const size = await readPtysize(record.pid);
|
|
1183
|
+
return Response.json({
|
|
1184
|
+
pid: record.pid,
|
|
1185
|
+
cols: size?.cols ?? null,
|
|
1186
|
+
rows: size?.rows ?? null,
|
|
1187
|
+
});
|
|
1194
1188
|
} catch (e) {
|
|
1195
1189
|
return new Response((e as Error).message, { status: 404 });
|
|
1196
1190
|
}
|
|
@@ -1478,7 +1472,13 @@ export async function cmdServe(rest: string[]): Promise<number> {
|
|
|
1478
1472
|
// standard); otherwise `cwd` is resolved against the workspace root and
|
|
1479
1473
|
// created if missing (Layer-0 plain-dir provisioning — no more ENOENT 500).
|
|
1480
1474
|
if (req.method === "POST" && p === "/api/spawn") {
|
|
1481
|
-
let body: {
|
|
1475
|
+
let body: {
|
|
1476
|
+
cli?: string;
|
|
1477
|
+
cwd?: string;
|
|
1478
|
+
from?: string;
|
|
1479
|
+
prompt?: string;
|
|
1480
|
+
fork?: { fromCwd?: string; branch?: string };
|
|
1481
|
+
};
|
|
1482
1482
|
try {
|
|
1483
1483
|
body = (await req.json()) as typeof body;
|
|
1484
1484
|
} catch {
|
|
@@ -1494,8 +1494,61 @@ export async function cmdServe(rest: string[]): Promise<number> {
|
|
|
1494
1494
|
// workspace root and mkdir-p'd so a missing dir no longer ENOENTs.
|
|
1495
1495
|
let cwd: string;
|
|
1496
1496
|
let provisioned: { action: string; folder: string } | null = null;
|
|
1497
|
+
const fork =
|
|
1498
|
+
body.fork && typeof body.fork.fromCwd === "string" && typeof body.fork.branch === "string"
|
|
1499
|
+
? { fromCwd: body.fork.fromCwd, branch: body.fork.branch }
|
|
1500
|
+
: null;
|
|
1497
1501
|
const from = typeof body.from === "string" ? body.from.trim() : "";
|
|
1498
|
-
if (
|
|
1502
|
+
if (fork) {
|
|
1503
|
+
// Fork the anchor agent's branch (carrying its WIP) into a new sibling
|
|
1504
|
+
// worktree via codehost/provision (git worktree off HEAD, no clone), then
|
|
1505
|
+
// spawn the agent there.
|
|
1506
|
+
let prov: {
|
|
1507
|
+
forkWorktree: (o: { fromCwd: string; branch: string; wsRoot?: string }) => Promise<{
|
|
1508
|
+
ok: boolean;
|
|
1509
|
+
folder: string;
|
|
1510
|
+
action: string;
|
|
1511
|
+
error?: string;
|
|
1512
|
+
spec?: { owner: string; repo: string };
|
|
1513
|
+
}>;
|
|
1514
|
+
};
|
|
1515
|
+
try {
|
|
1516
|
+
prov = (await import("codehost/provision")) as typeof prov;
|
|
1517
|
+
} catch (e) {
|
|
1518
|
+
return new Response(
|
|
1519
|
+
`fork needs the 'codehost' package (codehost/provision) — install it ` +
|
|
1520
|
+
`(npm i -g codehost) or 'bun link' it for local dev: ${(e as Error).message}`,
|
|
1521
|
+
{ status: 501 },
|
|
1522
|
+
);
|
|
1523
|
+
}
|
|
1524
|
+
let result: {
|
|
1525
|
+
ok: boolean;
|
|
1526
|
+
folder: string;
|
|
1527
|
+
action: string;
|
|
1528
|
+
error?: string;
|
|
1529
|
+
spec?: { owner: string; repo: string };
|
|
1530
|
+
};
|
|
1531
|
+
try {
|
|
1532
|
+
const wsRoot = getProvisionRoot();
|
|
1533
|
+
result = await prov.forkWorktree({
|
|
1534
|
+
fromCwd: fork.fromCwd,
|
|
1535
|
+
branch: fork.branch,
|
|
1536
|
+
...(wsRoot ? { wsRoot } : {}),
|
|
1537
|
+
});
|
|
1538
|
+
} catch (e) {
|
|
1539
|
+
return new Response(`fork failed: ${(e as Error).message}`, { status: 502 });
|
|
1540
|
+
}
|
|
1541
|
+
if (!result?.ok) return new Response(result?.error ?? "fork failed", { status: 502 });
|
|
1542
|
+
// Same allowlist gate as `from` — the fork runs setup-repo.sh (code exec).
|
|
1543
|
+
if (result.spec && !isProvisionAllowed(result.spec.owner, result.spec.repo))
|
|
1544
|
+
return new Response(
|
|
1545
|
+
`forking '${result.spec.owner}/${result.spec.repo}' is not allowed — add the owner ` +
|
|
1546
|
+
`to provisionAllowlist in ~/.agent-yes/config.json (or "*" to allow all)`,
|
|
1547
|
+
{ status: 403 },
|
|
1548
|
+
);
|
|
1549
|
+
cwd = result.folder;
|
|
1550
|
+
provisioned = { action: result.action, folder: result.folder };
|
|
1551
|
+
} else if (from) {
|
|
1499
1552
|
type Spec = { owner: string; repo: string; branch: string };
|
|
1500
1553
|
let prov: {
|
|
1501
1554
|
parseSource?: (s: string) => Spec | null;
|
|
@@ -1553,7 +1606,7 @@ export async function cmdServe(rest: string[]): Promise<number> {
|
|
|
1553
1606
|
}
|
|
1554
1607
|
}
|
|
1555
1608
|
process.stderr.write(
|
|
1556
|
-
`→ console spawned: ay ${cli}${prompt ? ` -- "${prompt.slice(0, 60)}"` : ""} (cwd: ${cwd}${provisioned ? `, ${provisioned.action}
|
|
1609
|
+
`→ console spawned: ay ${cli}${prompt ? ` -- "${prompt.slice(0, 60)}"` : ""} (cwd: ${cwd}${provisioned ? `, ${provisioned.action}` : ""})\n`,
|
|
1557
1610
|
);
|
|
1558
1611
|
// Resolve `ay` to an absolute command. The detached daemon (oxmgr/launchd/
|
|
1559
1612
|
// pm2) usually has a PATH WITHOUT ~/.bun/bin, so a bare "ay" fails with
|
package/ts/subcommands.spec.ts
CHANGED
|
@@ -1718,3 +1718,80 @@ describe("subcommands.isAgentStuck / stuck state", () => {
|
|
|
1718
1718
|
}
|
|
1719
1719
|
});
|
|
1720
1720
|
});
|
|
1721
|
+
|
|
1722
|
+
// A CLI like Claude Code repaints by moving the cursor UP over the previous
|
|
1723
|
+
// frame and rewriting it. The up-count is the frame's height AT THE AGENT'S REAL
|
|
1724
|
+
// WIDTH. Replayed narrower, the body line wraps to an extra row, the up-count
|
|
1725
|
+
// undershoots, and every old frame strands below as a duplicate — the `ay tail`
|
|
1726
|
+
// stutter. `bodyLen` chars stay one row at the real width but wrap below it.
|
|
1727
|
+
function buildRedrawLog(frames: number, bodyLen: number): Buffer {
|
|
1728
|
+
const body = "BODY-" + "z".repeat(Math.max(0, bodyLen - 5));
|
|
1729
|
+
const frame = (i: number) => `HEADER-${i}\r\n${body}`;
|
|
1730
|
+
let bytes = frame(0) + "\r\n";
|
|
1731
|
+
for (let i = 1; i < frames; i++) bytes += `\x1b[2A\r` + frame(i) + "\r\n"; // up 2 = header+body at real width
|
|
1732
|
+
return Buffer.from(bytes);
|
|
1733
|
+
}
|
|
1734
|
+
const countHeaders = (s: string) => (s.match(/^HEADER-\d+/gm) ?? []).length;
|
|
1735
|
+
|
|
1736
|
+
describe("renderRawLog honors the agent's recorded PTY geometry", () => {
|
|
1737
|
+
it("collapses redraw frames at the recorded width but duplicates at a mismatched width", async () => {
|
|
1738
|
+
const { renderRawLog } = await loadModule();
|
|
1739
|
+
const buf = buildRedrawLog(6, 220); // one row at >=220 cols, two rows below that
|
|
1740
|
+
|
|
1741
|
+
// Replayed at the real width, each repaint lands on the prior frame: one header.
|
|
1742
|
+
const correct = await renderRawLog(buf, { mode: "cat", n: 0, cols: 240, rows: 50 });
|
|
1743
|
+
expect(countHeaders(correct)).toBe(1);
|
|
1744
|
+
|
|
1745
|
+
// Replayed narrower (the body wraps), repaints undershoot and pile up.
|
|
1746
|
+
const wrong = await renderRawLog(buf, { mode: "cat", n: 0, cols: 120, rows: 50 });
|
|
1747
|
+
expect(countHeaders(wrong)).toBeGreaterThan(1);
|
|
1748
|
+
});
|
|
1749
|
+
});
|
|
1750
|
+
|
|
1751
|
+
describe("subcommands.cmdRead replays at the ptysize sidecar geometry", () => {
|
|
1752
|
+
it("renders at the recorded geometry, not the 200-col fallback", async () => {
|
|
1753
|
+
const { runSubcommand } = await loadModule();
|
|
1754
|
+
const { appendGlobalPid } = await import("./globalPidIndex.ts");
|
|
1755
|
+
const tmp = await mkdtemp(path.join(tmpdir(), "ay-raw-log-"));
|
|
1756
|
+
try {
|
|
1757
|
+
const logPath = path.join(tmp, "wide.raw.log");
|
|
1758
|
+
// Authored for a 240-col terminal; at the 200-col fallback the body wraps
|
|
1759
|
+
// and the redraw duplicates (see buildRedrawLog / renderRawLogLines).
|
|
1760
|
+
await writeFile(logPath, buildRedrawLog(6, 220));
|
|
1761
|
+
|
|
1762
|
+
// ptysize sidecar lives under the (mocked) home: ~/.agent-yes/ptysize/<pid>.
|
|
1763
|
+
const ptDir = path.join(testHome, ".agent-yes", "ptysize");
|
|
1764
|
+
await mkdir(ptDir, { recursive: true });
|
|
1765
|
+
await writeFile(path.join(ptDir, String(process.pid)), "240 50\n");
|
|
1766
|
+
|
|
1767
|
+
await appendGlobalPid({
|
|
1768
|
+
pid: process.pid,
|
|
1769
|
+
cli: "claude",
|
|
1770
|
+
prompt: null,
|
|
1771
|
+
cwd: process.cwd(),
|
|
1772
|
+
log_file: logPath,
|
|
1773
|
+
status: "active" as const,
|
|
1774
|
+
exit_code: null,
|
|
1775
|
+
exit_reason: null,
|
|
1776
|
+
started_at: Date.now(),
|
|
1777
|
+
});
|
|
1778
|
+
|
|
1779
|
+
const stdout: string[] = [];
|
|
1780
|
+
const orig = process.stdout.write.bind(process.stdout);
|
|
1781
|
+
(process.stdout as any).write = (s: any) => {
|
|
1782
|
+
stdout.push(String(s));
|
|
1783
|
+
return true;
|
|
1784
|
+
};
|
|
1785
|
+
try {
|
|
1786
|
+
const code = await runSubcommand(["bun", "cli.js", "cat", String(process.pid)]);
|
|
1787
|
+
expect(code).toBe(0);
|
|
1788
|
+
} finally {
|
|
1789
|
+
process.stdout.write = orig;
|
|
1790
|
+
}
|
|
1791
|
+
// With the sidecar honored, the six repaints collapse to a single frame.
|
|
1792
|
+
expect(countHeaders(stdout.join(""))).toBe(1);
|
|
1793
|
+
} finally {
|
|
1794
|
+
await rm(tmp, { recursive: true, force: true }).catch(() => null);
|
|
1795
|
+
}
|
|
1796
|
+
});
|
|
1797
|
+
});
|
package/ts/subcommands.ts
CHANGED
|
@@ -1406,6 +1406,7 @@ async function cmdRead(rest: string[], { mode }: ReadOpts): Promise<number> {
|
|
|
1406
1406
|
}
|
|
1407
1407
|
|
|
1408
1408
|
const buf = await readFile(logPath);
|
|
1409
|
+
const size = await readPtysize(record.pid);
|
|
1409
1410
|
const notes = await readNotes();
|
|
1410
1411
|
const noteLabel = notes.get(record.pid);
|
|
1411
1412
|
const header = noteLabel
|
|
@@ -1414,7 +1415,7 @@ async function cmdRead(rest: string[], { mode }: ReadOpts): Promise<number> {
|
|
|
1414
1415
|
|
|
1415
1416
|
if (follow) {
|
|
1416
1417
|
// Follow mode ignores pagination: print the initial context, then stream deltas.
|
|
1417
|
-
const rendered = await renderRawLog(buf, { mode, n });
|
|
1418
|
+
const rendered = await renderRawLog(buf, { mode, n, cols: size?.cols, rows: size?.rows });
|
|
1418
1419
|
process.stderr.write(header + "\n");
|
|
1419
1420
|
process.stdout.write(rendered);
|
|
1420
1421
|
if (!rendered.endsWith("\n")) process.stdout.write("\n");
|
|
@@ -1427,7 +1428,7 @@ async function cmdRead(rest: string[], { mode }: ReadOpts): Promise<number> {
|
|
|
1427
1428
|
|
|
1428
1429
|
// Static read: render the full log once, then window into the rendered lines
|
|
1429
1430
|
// so line numbers (and the pagination cursor in the footer) are exact.
|
|
1430
|
-
const allLines = await renderRawLogLines(buf);
|
|
1431
|
+
const allLines = await renderRawLogLines(buf, { cols: size?.cols, rows: size?.rows });
|
|
1431
1432
|
const total = allLines.length;
|
|
1432
1433
|
const win = resolveReadWindow({
|
|
1433
1434
|
total,
|
|
@@ -1663,15 +1664,42 @@ async function followPlainLocal(logPath: string, buf: Uint8Array): Promise<numbe
|
|
|
1663
1664
|
return 0;
|
|
1664
1665
|
}
|
|
1665
1666
|
|
|
1667
|
+
/**
|
|
1668
|
+
* The agent's real PTY geometry (from readPtysize), passed to the renderers so
|
|
1669
|
+
* the raw log replays at the size it was authored for. Omitted → a wide 200x50
|
|
1670
|
+
* default; if the agent ran wider/taller than that its cursor-addressed redraw
|
|
1671
|
+
* frames undershoot on replay and strand into scrollback as duplicates (the
|
|
1672
|
+
* `ay tail` stutter this guards).
|
|
1673
|
+
*/
|
|
1674
|
+
type RenderGeom = { cols?: number; rows?: number };
|
|
1675
|
+
|
|
1676
|
+
/**
|
|
1677
|
+
* Read an agent's last-known PTY geometry from `~/.agent-yes/ptysize/<pid>`
|
|
1678
|
+
* (written by both runtimes — ts/index.ts and rs/src/pty_spawner.rs — as
|
|
1679
|
+
* "<cols> <rows>\n"). Returns null when there's no sidecar (older agent, or not
|
|
1680
|
+
* yet written).
|
|
1681
|
+
*/
|
|
1682
|
+
export async function readPtysize(pid: number): Promise<{ cols: number; rows: number } | null> {
|
|
1683
|
+
const dir = process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
|
|
1684
|
+
try {
|
|
1685
|
+
const txt = await readFile(path.join(dir, "ptysize", String(pid)), "utf-8");
|
|
1686
|
+
const [c = 0, r = 0] = txt.trim().split(/\s+/).map(Number);
|
|
1687
|
+
if (c > 0 && r > 0) return { cols: c, rows: r };
|
|
1688
|
+
} catch {
|
|
1689
|
+
/* no ptysize sidecar */
|
|
1690
|
+
}
|
|
1691
|
+
return null;
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1666
1694
|
/**
|
|
1667
1695
|
* Feed the raw PTY bytes through @xterm/headless and emit plain text.
|
|
1668
1696
|
* Same approach as koho's renderTerminalBuffer + agent-yes's XtermProxy.
|
|
1669
1697
|
*/
|
|
1670
1698
|
export async function renderRawLog(
|
|
1671
1699
|
buf: Uint8Array,
|
|
1672
|
-
{ mode, n }: { mode: "cat" | "tail" | "head"; n: number },
|
|
1700
|
+
{ mode, n, cols, rows }: { mode: "cat" | "tail" | "head"; n: number } & RenderGeom,
|
|
1673
1701
|
): Promise<string> {
|
|
1674
|
-
const lines = await renderRawLogLines(buf);
|
|
1702
|
+
const lines = await renderRawLogLines(buf, { cols, rows });
|
|
1675
1703
|
if (mode === "cat") return lines.join("\n");
|
|
1676
1704
|
if (mode === "tail") return lines.slice(Math.max(0, lines.length - n)).join("\n");
|
|
1677
1705
|
return lines.slice(0, n).join("\n");
|
|
@@ -1685,11 +1713,13 @@ export async function renderRawLog(
|
|
|
1685
1713
|
* cursor moves / clears / wraps), so we always render the whole buffer once and
|
|
1686
1714
|
* window the resulting lines.
|
|
1687
1715
|
*/
|
|
1688
|
-
export async function renderRawLogLines(buf: Uint8Array): Promise<string[]> {
|
|
1689
|
-
//
|
|
1690
|
-
// 200x50
|
|
1691
|
-
|
|
1692
|
-
|
|
1716
|
+
export async function renderRawLogLines(buf: Uint8Array, geom?: RenderGeom): Promise<string[]> {
|
|
1717
|
+
// Replay at the agent's real geometry when known (see RenderGeom / readPtysize);
|
|
1718
|
+
// otherwise fall back to a wide 200x50 — a reasonable upper bound that won't
|
|
1719
|
+
// truncate normal output, though an agent wider/taller than it can still
|
|
1720
|
+
// duplicate on replay (which is why callers pass the recorded size).
|
|
1721
|
+
const cols = geom?.cols && geom.cols > 0 ? geom.cols : 200;
|
|
1722
|
+
const rows = geom?.rows && geom.rows > 0 ? geom.rows : 50;
|
|
1693
1723
|
// Scrollback caps how far back pagination can reach; older lines are evicted.
|
|
1694
1724
|
const scrollback = 50000;
|
|
1695
1725
|
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import "./ts-C21nFgXQ.js";
|
|
2
|
-
import "./logger-CDIsZ-Pp.js";
|
|
3
|
-
import "./versionChecker-C-JJk14E.js";
|
|
4
|
-
import "./pidStore-fqXqTKkh.js";
|
|
5
|
-
import "./globalPidIndex-DlmmJlO8.js";
|
|
6
|
-
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DyQjQODe.js";
|
|
7
|
-
|
|
8
|
-
export { SUPPORTED_CLIS };
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import "./logger-CDIsZ-Pp.js";
|
|
2
|
-
import "./globalPidIndex-DlmmJlO8.js";
|
|
3
|
-
import "./configShared-C1C04bbq.js";
|
|
4
|
-
import "./e2e-ClOI_aqV.js";
|
|
5
|
-
import "./webrtcLink-BWhuA74k.js";
|
|
6
|
-
import "./remotes-qK6uozO4.js";
|
|
7
|
-
import { C as stopTipForCli, S as snapshotStatus, _ as renderRawLog, a as cursorAbs, b as resolveReadWindow, c as extractTaskCounts, d as isExitRequest, f as isPidAlive, g as readNotes, h as matchKeyword, i as controlCodeFromName, l as finalizedLines, m as listRecords, n as READ_PAGE_DEFAULT, o as deriveLiveStatus, p as isSubcommand, r as cmdHelp, s as extractNeedsInput, t as GRACEFUL_EXIT_COMMANDS, u as isAgentStuck, v as renderRawLogLines, w as writeToIpc, x as runSubcommand, y as resolveOne } from "./subcommands-OyK7Y447.js";
|
|
8
|
-
|
|
9
|
-
export { cmdHelp, isSubcommand, runSubcommand };
|