claude-yes 1.155.1 → 1.157.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-DLSsJ845.js +8 -0
- package/dist/{SUPPORTED_CLIS-Nwu058MP.js → SUPPORTED_CLIS-tIYMeICi.js} +2 -2
- package/dist/cli.js +3 -3
- package/dist/index.js +2 -2
- package/dist/{schedule-CuPyOtGt.js → schedule-43wkfctj.js} +5 -5
- package/dist/{serve-KupsUr6A.js → serve-MQwHVctu.js} +59 -14
- package/dist/{setup-DkN1MjXp.js → setup-DcYBxFku.js} +3 -3
- package/dist/{subcommands-DcJsS7Uu.js → subcommands-B1EoS0xQ.js} +1 -1
- package/dist/{subcommands-DSTQRbub.js → subcommands-Bkf8qkP8.js} +4 -4
- package/dist/{ts-SEpCdKU5.js → ts-CKvvDjEK.js} +2 -2
- package/dist/{versionChecker-B9y2MQF2.js → versionChecker-IAATi04_.js} +2 -2
- package/dist/{workspaceConfig-DT3cCBZK.js → workspaceConfig-B0Q9-q2B.js} +38 -3
- package/lab/ui/index.html +11 -0
- package/package.json +1 -1
- package/ts/serve.ts +84 -4
- package/ts/workspaceConfig.spec.ts +42 -1
- package/ts/workspaceConfig.ts +46 -1
- package/dist/SUPPORTED_CLIS-BJkbB_vi.js +0 -8
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import "./ts-CKvvDjEK.js";
|
|
2
|
+
import "./logger-CDIsZ-Pp.js";
|
|
3
|
+
import "./versionChecker-IAATi04_.js";
|
|
4
|
+
import "./pidStore-fqXqTKkh.js";
|
|
5
|
+
import "./globalPidIndex-DlmmJlO8.js";
|
|
6
|
+
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-tIYMeICi.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-CKvvDjEK.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-tIYMeICi.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-IAATi04_.js";
|
|
5
5
|
import { argv } from "process";
|
|
6
6
|
import { execFileSync, spawn } from "child_process";
|
|
7
7
|
import ms from "ms";
|
|
@@ -481,7 +481,7 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
|
|
|
481
481
|
const rawArg = process.argv[2];
|
|
482
482
|
const managerCommands = !invokedCliName(process.argv);
|
|
483
483
|
const isHelpFlag = rawArg === "-h" || rawArg === "--help";
|
|
484
|
-
const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-
|
|
484
|
+
const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-B1EoS0xQ.js");
|
|
485
485
|
if (isHelpFlag && process.argv.length === 3) {
|
|
486
486
|
cmdHelp(managerCommands);
|
|
487
487
|
process.exit(0);
|
|
@@ -514,7 +514,7 @@ if (config.useRust) {
|
|
|
514
514
|
}
|
|
515
515
|
}
|
|
516
516
|
if (rustBinary) {
|
|
517
|
-
const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-
|
|
517
|
+
const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-DLSsJ845.js");
|
|
518
518
|
const rustArgs = buildRustArgs(process.argv, config.cli, SUPPORTED_CLIS);
|
|
519
519
|
if (config.verbose) {
|
|
520
520
|
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-CKvvDjEK.js";
|
|
2
2
|
import "./logger-CDIsZ-Pp.js";
|
|
3
|
-
import "./versionChecker-
|
|
3
|
+
import "./versionChecker-IAATi04_.js";
|
|
4
4
|
import "./pidStore-fqXqTKkh.js";
|
|
5
5
|
import "./globalPidIndex-DlmmJlO8.js";
|
|
6
6
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import "./ts-
|
|
1
|
+
import "./ts-CKvvDjEK.js";
|
|
2
2
|
import "./logger-CDIsZ-Pp.js";
|
|
3
|
-
import "./versionChecker-
|
|
3
|
+
import "./versionChecker-IAATi04_.js";
|
|
4
4
|
import "./pidStore-fqXqTKkh.js";
|
|
5
5
|
import "./globalPidIndex-DlmmJlO8.js";
|
|
6
|
-
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-
|
|
7
|
-
import {
|
|
6
|
+
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-tIYMeICi.js";
|
|
7
|
+
import { o as resolveSpawnCwd } from "./workspaceConfig-B0Q9-q2B.js";
|
|
8
8
|
import { createHash } from "node:crypto";
|
|
9
9
|
|
|
10
10
|
//#region ts/oxmgrService.ts
|
|
@@ -141,4 +141,4 @@ async function cmdSchedule(rest) {
|
|
|
141
141
|
|
|
142
142
|
//#endregion
|
|
143
143
|
export { cmdSchedule };
|
|
144
|
-
//# sourceMappingURL=schedule-
|
|
144
|
+
//# sourceMappingURL=schedule-43wkfctj.js.map
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import "./ts-
|
|
1
|
+
import "./ts-CKvvDjEK.js";
|
|
2
2
|
import "./logger-CDIsZ-Pp.js";
|
|
3
|
-
import { r as getInstalledPackage } from "./versionChecker-
|
|
3
|
+
import { r as getInstalledPackage } from "./versionChecker-IAATi04_.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-CEyhl0WH.js";
|
|
8
|
-
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-
|
|
8
|
+
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-tIYMeICi.js";
|
|
9
9
|
import "./e2e-Bfw7qL9O.js";
|
|
10
10
|
import "./webrtcLink-CBZkZ-LT.js";
|
|
11
11
|
import "./remotes-DBCvpp3B.js";
|
|
12
|
-
import { i as
|
|
13
|
-
import { E 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, w as snapshotStatus } from "./subcommands-
|
|
12
|
+
import { a as isProvisionAllowed, i as hasSpawnHook, n as getSpawnHook, o as resolveSpawnCwd, t as getProvisionRoot } from "./workspaceConfig-B0Q9-q2B.js";
|
|
13
|
+
import { E 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, w as snapshotStatus } from "./subcommands-Bkf8qkP8.js";
|
|
14
14
|
import yargs from "yargs";
|
|
15
|
-
import { mkdir, open, readFile, stat, writeFile } from "fs/promises";
|
|
15
|
+
import { mkdir, open, readFile, stat, unlink, writeFile } from "fs/promises";
|
|
16
16
|
import { homedir, hostname, userInfo } from "os";
|
|
17
17
|
import path from "path";
|
|
18
18
|
import { execFileSync } from "node:child_process";
|
|
@@ -789,6 +789,7 @@ Options:
|
|
|
789
789
|
return Response.json({ host: user ? `${user}@${host}` : host });
|
|
790
790
|
}
|
|
791
791
|
if (req.method === "GET" && p === "/api/version") return Response.json({ version: getInstalledPackage().version });
|
|
792
|
+
if (req.method === "GET" && p === "/api/spawn-config") return Response.json({ hasSpawnHook: hasSpawnHook() });
|
|
792
793
|
if (req.method === "GET" && p === "/api/notes") {
|
|
793
794
|
const notes = await readNotes();
|
|
794
795
|
return Response.json(Object.fromEntries(notes));
|
|
@@ -1154,15 +1155,59 @@ Options:
|
|
|
1154
1155
|
}
|
|
1155
1156
|
process.stderr.write(`→ console spawned: ay ${cli}${prompt ? ` -- "${prompt.slice(0, 60)}"` : ""} (cwd: ${cwd}${provisioned ? `, ${provisioned.action}` : ""})\n`);
|
|
1156
1157
|
const ayBin = Bun.which("ay") ?? process.argv[1];
|
|
1157
|
-
const
|
|
1158
|
+
const agentArgv = [
|
|
1159
|
+
...process.platform === "win32" && ayBin.toLowerCase().endsWith(".exe") ? [ayBin] : [process.execPath, ayBin],
|
|
1160
|
+
cli,
|
|
1161
|
+
...prompt ? ["--", prompt] : []
|
|
1162
|
+
];
|
|
1163
|
+
const agentEnv = freshAgentEnv();
|
|
1158
1164
|
try {
|
|
1159
|
-
const
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1165
|
+
const hook = getSpawnHook();
|
|
1166
|
+
if (hook) {
|
|
1167
|
+
const shell = process.env.AGENT_YES_SPAWN_SHELL?.trim() || "/bin/sh";
|
|
1168
|
+
const script = `set -e\n${hook}\nexec "$@"`;
|
|
1169
|
+
const errPath = path.join(agentYesHome(), `spawn-hook-${process.pid}-${performance.now().toString(36).replace(".", "")}.err`);
|
|
1170
|
+
const child = Bun.spawn([
|
|
1171
|
+
shell,
|
|
1172
|
+
"-c",
|
|
1173
|
+
script,
|
|
1174
|
+
"ay-spawn",
|
|
1175
|
+
...agentArgv
|
|
1176
|
+
], {
|
|
1177
|
+
cwd,
|
|
1178
|
+
env: {
|
|
1179
|
+
...agentEnv,
|
|
1180
|
+
AGENT_YES_CWD: cwd,
|
|
1181
|
+
AGENT_YES_CLI: cli
|
|
1182
|
+
},
|
|
1183
|
+
stdin: "ignore",
|
|
1184
|
+
stdout: "ignore",
|
|
1185
|
+
stderr: Bun.file(errPath)
|
|
1186
|
+
});
|
|
1187
|
+
const windowMs = Number(process.env.AGENT_YES_SPAWN_HOOK_TIMEOUT_MS) || 5e3;
|
|
1188
|
+
const exitCode = await Promise.race([child.exited, new Promise((r) => setTimeout(() => r(null), windowMs))]);
|
|
1189
|
+
if (exitCode !== null && exitCode !== 0) {
|
|
1190
|
+
let detail = "";
|
|
1191
|
+
try {
|
|
1192
|
+
detail = (await Bun.file(errPath).text()).slice(0, 4096);
|
|
1193
|
+
} catch {}
|
|
1194
|
+
await unlink(errPath).catch(() => {});
|
|
1195
|
+
return new Response(`spawn hook failed (exit ${exitCode})${detail ? `:\n${detail.trimEnd()}` : ""}`, { status: 502 });
|
|
1196
|
+
}
|
|
1197
|
+
child.unref();
|
|
1198
|
+
setTimeout(() => void unlink(errPath).catch(() => {}), 6e4).unref?.();
|
|
1199
|
+
return Response.json({
|
|
1200
|
+
ok: true,
|
|
1201
|
+
pid: child.pid,
|
|
1202
|
+
cli,
|
|
1203
|
+
cwd,
|
|
1204
|
+
hook: true,
|
|
1205
|
+
...provisioned ? { provisioned } : {}
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
const child = Bun.spawn(agentArgv, {
|
|
1164
1209
|
cwd,
|
|
1165
|
-
env:
|
|
1210
|
+
env: agentEnv,
|
|
1166
1211
|
stdin: "ignore",
|
|
1167
1212
|
stdout: "ignore",
|
|
1168
1213
|
stderr: "ignore"
|
|
@@ -1303,4 +1348,4 @@ Options:
|
|
|
1303
1348
|
|
|
1304
1349
|
//#endregion
|
|
1305
1350
|
export { cmdServe };
|
|
1306
|
-
//# sourceMappingURL=serve-
|
|
1351
|
+
//# sourceMappingURL=serve-MQwHVctu.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { r as getWorkspaceRoot, s as setWorkspaceRoot } from "./workspaceConfig-B0Q9-q2B.js";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { stdin, stdout } from "node:process";
|
|
4
4
|
import { createInterface } from "node:readline/promises";
|
|
@@ -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-MQwHVctu.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-DcYBxFku.js.map
|
|
@@ -4,6 +4,6 @@ import "./configShared-CEyhl0WH.js";
|
|
|
4
4
|
import "./e2e-Bfw7qL9O.js";
|
|
5
5
|
import "./webrtcLink-CBZkZ-LT.js";
|
|
6
6
|
import "./remotes-DBCvpp3B.js";
|
|
7
|
-
import { C as runSubcommand, E as writeToIpc, S as resolveResumeArgs, T as stopTipForCli, _ 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 snapshotStatus, x as resolveReadWindow, y as renderRawLogLines } from "./subcommands-
|
|
7
|
+
import { C as runSubcommand, E as writeToIpc, S as resolveResumeArgs, T as stopTipForCli, _ 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 snapshotStatus, x as resolveReadWindow, y as renderRawLogLines } from "./subcommands-Bkf8qkP8.js";
|
|
8
8
|
|
|
9
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-MQwHVctu.js");
|
|
558
558
|
return cmdServe(rest);
|
|
559
559
|
}
|
|
560
560
|
case "setup": {
|
|
561
|
-
const { cmdSetup } = await import("./setup-
|
|
561
|
+
const { cmdSetup } = await import("./setup-DcYBxFku.js");
|
|
562
562
|
return cmdSetup(rest);
|
|
563
563
|
}
|
|
564
564
|
case "schedule": {
|
|
565
|
-
const { cmdSchedule } = await import("./schedule-
|
|
565
|
+
const { cmdSchedule } = await import("./schedule-43wkfctj.js");
|
|
566
566
|
return cmdSchedule(rest);
|
|
567
567
|
}
|
|
568
568
|
case "remote": {
|
|
@@ -2710,4 +2710,4 @@ async function cmdResultSet(rest) {
|
|
|
2710
2710
|
|
|
2711
2711
|
//#endregion
|
|
2712
2712
|
export { runSubcommand as C, writeToIpc as E, resolveResumeArgs as S, stopTipForCli 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, snapshotStatus as w, resolveReadWindow as x, renderRawLogLines as y };
|
|
2713
|
-
//# sourceMappingURL=subcommands-
|
|
2713
|
+
//# sourceMappingURL=subcommands-Bkf8qkP8.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-IAATi04_.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";
|
|
@@ -1802,4 +1802,4 @@ function sleep(ms) {
|
|
|
1802
1802
|
|
|
1803
1803
|
//#endregion
|
|
1804
1804
|
export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
|
|
1805
|
-
//# sourceMappingURL=ts-
|
|
1805
|
+
//# sourceMappingURL=ts-CKvvDjEK.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.157.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-IAATi04_.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as agentYesHome } from "./agentYesHome-_eJa3DaX.js";
|
|
2
|
-
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
+
import { lstatSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
|
|
3
3
|
import { homedir } from "os";
|
|
4
4
|
import path from "path";
|
|
5
5
|
|
|
@@ -61,6 +61,41 @@ function isProvisionAllowed(owner, repo) {
|
|
|
61
61
|
const full = `${owner}/${repo}`.toLowerCase();
|
|
62
62
|
return list.some((e) => e.replace(/\/\*$/, "") === o || e === full);
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* A trusted, HOST-LOCAL shell hook run before each agent spawn. It is a POSIX
|
|
66
|
+
* `sh -c` script that prepares the environment (provisioning, env, cd) and that
|
|
67
|
+
* agent-yes runs as `sh -c "set -e\n<hook>\nexec \"$@\"" ay-spawn <agent argv…>`
|
|
68
|
+
* — so the real agent argv is passed as positional params and the prompt is
|
|
69
|
+
* never shell-parsed. `set -e` aborts the spawn if any hook step fails.
|
|
70
|
+
*
|
|
71
|
+
* This is arbitrary local code that runs on EVERY spawn, so it is deliberately
|
|
72
|
+
* NOT writable over the network. Set it on the machine by editing
|
|
73
|
+
* `~/.agent-yes/config.json` (`"spawnHook"`), or via env `AGENT_YES_SPAWN_HOOK`.
|
|
74
|
+
*
|
|
75
|
+
* Tampering guard (POSIX): a file-backed hook is ignored when the config file is
|
|
76
|
+
* a symlink, is not owned by us, or is group/world-writable — we refuse to run a
|
|
77
|
+
* hook from a file other users can swap or rewrite. The env override is trusted
|
|
78
|
+
* (it comes from the daemon's own environment). Returns null when unset/guarded.
|
|
79
|
+
*/
|
|
80
|
+
function getSpawnHook() {
|
|
81
|
+
const env = process.env.AGENT_YES_SPAWN_HOOK;
|
|
82
|
+
if (env && env.trim()) return env;
|
|
83
|
+
const h = readConfig().spawnHook;
|
|
84
|
+
if (!h || !h.trim()) return null;
|
|
85
|
+
if (process.platform !== "win32") try {
|
|
86
|
+
if (lstatSync(configPath()).isSymbolicLink()) return null;
|
|
87
|
+
const st = statSync(configPath());
|
|
88
|
+
if ((st.mode & 18) !== 0) return null;
|
|
89
|
+
if (typeof process.getuid === "function" && st.uid !== process.getuid()) return null;
|
|
90
|
+
} catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
return h;
|
|
94
|
+
}
|
|
95
|
+
/** Whether a usable {@link getSpawnHook} is configured (for read-only disclosure). */
|
|
96
|
+
function hasSpawnHook() {
|
|
97
|
+
return getSpawnHook() !== null;
|
|
98
|
+
}
|
|
64
99
|
/** Persist the workspace root, tilde-expanded and resolved to an absolute path. */
|
|
65
100
|
function setWorkspaceRoot(dir) {
|
|
66
101
|
const abs = path.resolve(expandTilde(dir));
|
|
@@ -87,5 +122,5 @@ function resolveSpawnCwd(input) {
|
|
|
87
122
|
}
|
|
88
123
|
|
|
89
124
|
//#endregion
|
|
90
|
-
export {
|
|
91
|
-
//# sourceMappingURL=workspaceConfig-
|
|
125
|
+
export { isProvisionAllowed as a, hasSpawnHook as i, getSpawnHook as n, resolveSpawnCwd as o, getWorkspaceRoot as r, setWorkspaceRoot as s, getProvisionRoot as t };
|
|
126
|
+
//# sourceMappingURL=workspaceConfig-B0Q9-q2B.js.map
|
package/lab/ui/index.html
CHANGED
|
@@ -832,6 +832,17 @@
|
|
|
832
832
|
.dot.idle {
|
|
833
833
|
background: var(--amber);
|
|
834
834
|
}
|
|
835
|
+
/* Wedged: the Rust supervisor flagged the agent unresponsive (no PTY output
|
|
836
|
+
after a poke / frozen spinner) — surfaced as `stuck` by /api/ls. Red so a
|
|
837
|
+
stuck agent stands out from a healthy green / quiet-amber one. */
|
|
838
|
+
.dot.stuck {
|
|
839
|
+
background: var(--red);
|
|
840
|
+
}
|
|
841
|
+
/* Waiting on you: alive and quiet, but parked on an unanswered prompt/menu.
|
|
842
|
+
The UI accent (blue) reads as "your turn" — distinct from idle-amber. */
|
|
843
|
+
.dot.needs_input {
|
|
844
|
+
background: var(--accent);
|
|
845
|
+
}
|
|
835
846
|
.dot.stopped,
|
|
836
847
|
.dot.exited {
|
|
837
848
|
background: var(--muted);
|
package/package.json
CHANGED
package/ts/serve.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdir, open, readFile, stat, writeFile } from "fs/promises";
|
|
1
|
+
import { mkdir, open, readFile, stat, unlink, writeFile } from "fs/promises";
|
|
2
2
|
import { renameSync, watch, writeFileSync } from "node:fs";
|
|
3
3
|
import { execFileSync } from "node:child_process";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
@@ -23,7 +23,13 @@ import { updateGlobalPidStatus } from "./globalPidIndex.ts";
|
|
|
23
23
|
import { pgidForWrapper } from "./reaper.ts";
|
|
24
24
|
import { SUPPORTED_CLIS } from "./SUPPORTED_CLIS.ts";
|
|
25
25
|
import { getInstalledPackage } from "./versionChecker.ts";
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
getProvisionRoot,
|
|
28
|
+
getSpawnHook,
|
|
29
|
+
hasSpawnHook,
|
|
30
|
+
isProvisionAllowed,
|
|
31
|
+
resolveSpawnCwd,
|
|
32
|
+
} from "./workspaceConfig.ts";
|
|
27
33
|
|
|
28
34
|
const DEFAULT_PORT = 7432;
|
|
29
35
|
|
|
@@ -1157,6 +1163,15 @@ export async function cmdServe(rest: string[]): Promise<number> {
|
|
|
1157
1163
|
return Response.json({ version: getInstalledPackage().version });
|
|
1158
1164
|
}
|
|
1159
1165
|
|
|
1166
|
+
// GET /api/spawn-config — read-only disclosure of WHETHER a host-local spawn
|
|
1167
|
+
// hook is configured. Deliberately returns only a boolean, never the hook
|
|
1168
|
+
// body: the hook is arbitrary local code and console/room clients holding the
|
|
1169
|
+
// share token are not necessarily host admins. Setting it stays host-local
|
|
1170
|
+
// (edit ~/.agent-yes/config.json) — there is no PUT.
|
|
1171
|
+
if (req.method === "GET" && p === "/api/spawn-config") {
|
|
1172
|
+
return Response.json({ hasSpawnHook: hasSpawnHook() });
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1160
1175
|
// GET /api/notes
|
|
1161
1176
|
if (req.method === "GET" && p === "/api/notes") {
|
|
1162
1177
|
const notes = await readNotes();
|
|
@@ -1685,10 +1700,75 @@ export async function cmdServe(rest: string[]): Promise<number> {
|
|
|
1685
1700
|
process.platform === "win32" && ayBin.toLowerCase().endsWith(".exe")
|
|
1686
1701
|
? [ayBin]
|
|
1687
1702
|
: [process.execPath, ayBin];
|
|
1703
|
+
const agentArgv = [...ayCmd, cli, ...(prompt ? ["--", prompt] : [])];
|
|
1704
|
+
// don't leak our Claude Code session into the agent
|
|
1705
|
+
const agentEnv = freshAgentEnv();
|
|
1688
1706
|
try {
|
|
1689
|
-
const
|
|
1707
|
+
const hook = getSpawnHook();
|
|
1708
|
+
if (hook) {
|
|
1709
|
+
// Host-local spawn hook (trusted local code, never network-writable). We
|
|
1710
|
+
// run it via POSIX `sh -c` then `exec "$@"` the real agent — the agent
|
|
1711
|
+
// argv is passed as positional params ($1…), so the prompt is NEVER
|
|
1712
|
+
// shell-parsed (no quoting/injection surface). `set -e` aborts the spawn
|
|
1713
|
+
// if any hook step fails. No `-l`: we don't re-source rc files here (the
|
|
1714
|
+
// env is already the recovered login-shell env via freshAgentEnv). cwd
|
|
1715
|
+
// and cli are exposed as env for the hook to consume.
|
|
1716
|
+
const shell = process.env.AGENT_YES_SPAWN_SHELL?.trim() || "/bin/sh";
|
|
1717
|
+
const script = `set -e\n${hook}\nexec "$@"`;
|
|
1718
|
+
const errPath = path.join(
|
|
1719
|
+
agentYesHome(),
|
|
1720
|
+
`spawn-hook-${process.pid}-${performance.now().toString(36).replace(".", "")}.err`,
|
|
1721
|
+
);
|
|
1722
|
+
// File-backed stderr never blocks the child (a pipe we stop draining
|
|
1723
|
+
// after exec would), and bounds our read to the first few KB.
|
|
1724
|
+
const child = Bun.spawn([shell, "-c", script, "ay-spawn", ...agentArgv], {
|
|
1725
|
+
cwd,
|
|
1726
|
+
env: { ...agentEnv, AGENT_YES_CWD: cwd, AGENT_YES_CLI: cli },
|
|
1727
|
+
stdin: "ignore",
|
|
1728
|
+
stdout: "ignore",
|
|
1729
|
+
stderr: Bun.file(errPath),
|
|
1730
|
+
});
|
|
1731
|
+
// Handshake: surface an EARLY hook/provision failure synchronously. If
|
|
1732
|
+
// the child exits non-zero within the window, the hook failed before the
|
|
1733
|
+
// agent settled — return the captured stderr. If it's still alive after
|
|
1734
|
+
// the window, `exec` succeeded (or the agent is running) → detach and
|
|
1735
|
+
// report success. We can't always attribute a failure to the hook vs the
|
|
1736
|
+
// agent, so we just surface the captured output.
|
|
1737
|
+
const windowMs = Number(process.env.AGENT_YES_SPAWN_HOOK_TIMEOUT_MS) || 5000;
|
|
1738
|
+
const exitCode = await Promise.race([
|
|
1739
|
+
child.exited,
|
|
1740
|
+
new Promise<null>((r) => setTimeout(() => r(null), windowMs)),
|
|
1741
|
+
]);
|
|
1742
|
+
if (exitCode !== null && exitCode !== 0) {
|
|
1743
|
+
let detail = "";
|
|
1744
|
+
try {
|
|
1745
|
+
detail = (await Bun.file(errPath).text()).slice(0, 4096);
|
|
1746
|
+
} catch {
|
|
1747
|
+
/* no stderr captured */
|
|
1748
|
+
}
|
|
1749
|
+
await unlink(errPath).catch(() => {});
|
|
1750
|
+
return new Response(
|
|
1751
|
+
`spawn hook failed (exit ${exitCode})${detail ? `:\n${detail.trimEnd()}` : ""}`,
|
|
1752
|
+
{ status: 502 },
|
|
1753
|
+
);
|
|
1754
|
+
}
|
|
1755
|
+
child.unref();
|
|
1756
|
+
// Clean up the stderr sidecar. On POSIX the running agent may still hold
|
|
1757
|
+
// the fd; unlinking an open file is harmless (the inode lives until
|
|
1758
|
+
// close), so a brief delay is enough to keep early-failure reads valid.
|
|
1759
|
+
setTimeout(() => void unlink(errPath).catch(() => {}), 60_000).unref?.();
|
|
1760
|
+
return Response.json({
|
|
1761
|
+
ok: true,
|
|
1762
|
+
pid: child.pid,
|
|
1763
|
+
cli,
|
|
1764
|
+
cwd,
|
|
1765
|
+
hook: true,
|
|
1766
|
+
...(provisioned ? { provisioned } : {}),
|
|
1767
|
+
});
|
|
1768
|
+
}
|
|
1769
|
+
const child = Bun.spawn(agentArgv, {
|
|
1690
1770
|
cwd,
|
|
1691
|
-
env:
|
|
1771
|
+
env: agentEnv,
|
|
1692
1772
|
stdin: "ignore",
|
|
1693
1773
|
stdout: "ignore",
|
|
1694
1774
|
stderr: "ignore",
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
-
import { mkdtempSync, rmSync, writeFileSync } from "fs";
|
|
2
|
+
import { chmodSync, mkdtempSync, rmSync, writeFileSync } from "fs";
|
|
3
3
|
import { homedir, tmpdir } from "os";
|
|
4
4
|
import path from "path";
|
|
5
5
|
import {
|
|
6
6
|
expandTilde,
|
|
7
7
|
getProvisionAllowlist,
|
|
8
8
|
getProvisionRoot,
|
|
9
|
+
getSpawnHook,
|
|
9
10
|
getWorkspaceRoot,
|
|
11
|
+
hasSpawnHook,
|
|
10
12
|
isProvisionAllowed,
|
|
11
13
|
resolveSpawnCwd,
|
|
12
14
|
setWorkspaceRoot,
|
|
@@ -25,6 +27,7 @@ describe("workspaceConfig", () => {
|
|
|
25
27
|
else process.env.AGENT_YES_HOME = original;
|
|
26
28
|
delete process.env.CODEHOST_WS_ROOT;
|
|
27
29
|
delete process.env.CODEHOST_PROVISION_ALLOWLIST;
|
|
30
|
+
delete process.env.AGENT_YES_SPAWN_HOOK;
|
|
28
31
|
rmSync(tmp, { recursive: true, force: true });
|
|
29
32
|
});
|
|
30
33
|
|
|
@@ -142,4 +145,42 @@ describe("workspaceConfig", () => {
|
|
|
142
145
|
expect(isProvisionAllowed("org", "anything")).toBe(true);
|
|
143
146
|
});
|
|
144
147
|
});
|
|
148
|
+
|
|
149
|
+
describe("getSpawnHook / hasSpawnHook", () => {
|
|
150
|
+
const isPosix = process.platform !== "win32";
|
|
151
|
+
|
|
152
|
+
it("is null/false when unset", () => {
|
|
153
|
+
expect(getSpawnHook()).toBeNull();
|
|
154
|
+
expect(hasSpawnHook()).toBe(false);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("returns the configured hook from a private (0600) config", () => {
|
|
158
|
+
writeConfig({ spawnHook: 'echo hi >&2\nexec "$@"' });
|
|
159
|
+
if (isPosix) chmodSync(path.join(tmp, "config.json"), 0o600);
|
|
160
|
+
expect(getSpawnHook()).toBe('echo hi >&2\nexec "$@"');
|
|
161
|
+
expect(hasSpawnHook()).toBe(true);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("ignores a blank hook", () => {
|
|
165
|
+
writeConfig({ spawnHook: " " });
|
|
166
|
+
expect(getSpawnHook()).toBeNull();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("env AGENT_YES_SPAWN_HOOK overrides the config", () => {
|
|
170
|
+
writeConfig({ spawnHook: "from-file" });
|
|
171
|
+
process.env.AGENT_YES_SPAWN_HOOK = "from-env";
|
|
172
|
+
expect(getSpawnHook()).toBe("from-env");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it.skipIf(!isPosix)(
|
|
176
|
+
"refuses a file-backed hook when the config is group/world-writable (tampering guard)",
|
|
177
|
+
() => {
|
|
178
|
+
writeConfig({ spawnHook: "echo pwned" });
|
|
179
|
+
chmodSync(path.join(tmp, "config.json"), 0o666);
|
|
180
|
+
expect(getSpawnHook()).toBeNull();
|
|
181
|
+
chmodSync(path.join(tmp, "config.json"), 0o600);
|
|
182
|
+
expect(getSpawnHook()).toBe("echo pwned");
|
|
183
|
+
},
|
|
184
|
+
);
|
|
185
|
+
});
|
|
145
186
|
});
|
package/ts/workspaceConfig.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
1
|
+
import { lstatSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
|
|
2
2
|
import { homedir } from "os";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { agentYesHome } from "./agentYesHome.ts";
|
|
@@ -19,6 +19,12 @@ interface Config {
|
|
|
19
19
|
provisionRoot?: string;
|
|
20
20
|
/** Owners/repos permitted for `from`-provisioning; empty = deny all. */
|
|
21
21
|
provisionAllowlist?: string[];
|
|
22
|
+
/**
|
|
23
|
+
* A trusted, HOST-LOCAL shell hook run before each console/CLI spawn. See
|
|
24
|
+
* {@link getSpawnHook}. Intentionally NOT settable over the network — it is
|
|
25
|
+
* arbitrary local code that runs on every spawn.
|
|
26
|
+
*/
|
|
27
|
+
spawnHook?: string;
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
function configPath(): string {
|
|
@@ -86,6 +92,45 @@ export function isProvisionAllowed(owner: string, repo: string): boolean {
|
|
|
86
92
|
return list.some((e) => e.replace(/\/\*$/, "") === o || e === full);
|
|
87
93
|
}
|
|
88
94
|
|
|
95
|
+
/**
|
|
96
|
+
* A trusted, HOST-LOCAL shell hook run before each agent spawn. It is a POSIX
|
|
97
|
+
* `sh -c` script that prepares the environment (provisioning, env, cd) and that
|
|
98
|
+
* agent-yes runs as `sh -c "set -e\n<hook>\nexec \"$@\"" ay-spawn <agent argv…>`
|
|
99
|
+
* — so the real agent argv is passed as positional params and the prompt is
|
|
100
|
+
* never shell-parsed. `set -e` aborts the spawn if any hook step fails.
|
|
101
|
+
*
|
|
102
|
+
* This is arbitrary local code that runs on EVERY spawn, so it is deliberately
|
|
103
|
+
* NOT writable over the network. Set it on the machine by editing
|
|
104
|
+
* `~/.agent-yes/config.json` (`"spawnHook"`), or via env `AGENT_YES_SPAWN_HOOK`.
|
|
105
|
+
*
|
|
106
|
+
* Tampering guard (POSIX): a file-backed hook is ignored when the config file is
|
|
107
|
+
* a symlink, is not owned by us, or is group/world-writable — we refuse to run a
|
|
108
|
+
* hook from a file other users can swap or rewrite. The env override is trusted
|
|
109
|
+
* (it comes from the daemon's own environment). Returns null when unset/guarded.
|
|
110
|
+
*/
|
|
111
|
+
export function getSpawnHook(): string | null {
|
|
112
|
+
const env = process.env.AGENT_YES_SPAWN_HOOK;
|
|
113
|
+
if (env && env.trim()) return env;
|
|
114
|
+
const h = readConfig().spawnHook;
|
|
115
|
+
if (!h || !h.trim()) return null;
|
|
116
|
+
if (process.platform !== "win32") {
|
|
117
|
+
try {
|
|
118
|
+
if (lstatSync(configPath()).isSymbolicLink()) return null;
|
|
119
|
+
const st = statSync(configPath());
|
|
120
|
+
if ((st.mode & 0o022) !== 0) return null; // group/world-writable
|
|
121
|
+
if (typeof process.getuid === "function" && st.uid !== process.getuid()) return null;
|
|
122
|
+
} catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return h;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Whether a usable {@link getSpawnHook} is configured (for read-only disclosure). */
|
|
130
|
+
export function hasSpawnHook(): boolean {
|
|
131
|
+
return getSpawnHook() !== null;
|
|
132
|
+
}
|
|
133
|
+
|
|
89
134
|
/** Persist the workspace root, tilde-expanded and resolved to an absolute path. */
|
|
90
135
|
export function setWorkspaceRoot(dir: string): string {
|
|
91
136
|
const abs = path.resolve(expandTilde(dir));
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import "./ts-SEpCdKU5.js";
|
|
2
|
-
import "./logger-CDIsZ-Pp.js";
|
|
3
|
-
import "./versionChecker-B9y2MQF2.js";
|
|
4
|
-
import "./pidStore-fqXqTKkh.js";
|
|
5
|
-
import "./globalPidIndex-DlmmJlO8.js";
|
|
6
|
-
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-Nwu058MP.js";
|
|
7
|
-
|
|
8
|
-
export { SUPPORTED_CLIS };
|