agent-yes 1.72.4 → 1.73.2
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/agent-yes.config.schema.json +32 -8
- package/default.config.yaml +154 -0
- package/dist/SUPPORTED_CLIS-C7sGMdKJ.js +10 -0
- package/dist/{agent-yes.config-CtQprJrA.js → agent-yes.config-CyP5iRZf.js} +75 -119
- package/dist/cli.js +147 -16
- package/dist/index.js +3 -2
- package/dist/logger-B9h0djqx.js +51 -0
- package/dist/package-DpfHTSW2.js +7 -0
- package/dist/pidStore-B4yDm3TL.js +4 -0
- package/dist/pidStore-CPrgJSJi.js +319 -0
- package/dist/{runningLock-BBI_URhR.js → runningLock-DQWJSptq.js} +3 -3
- package/dist/{tray-CPpdxTV-.js → tray-Bzb1owBN.js} +4 -4
- package/dist/{SUPPORTED_CLIS-Bqw9gxey.js → ts-CsdLrLod.js} +146 -362
- package/package.json +9 -3
- package/ts/cli.ts +16 -9
- package/ts/configLoader.spec.ts +19 -0
- package/ts/configLoader.ts +8 -2
- package/ts/configShared.spec.ts +97 -0
- package/ts/configShared.ts +158 -0
- package/ts/index.ts +93 -102
- package/ts/logger.spec.ts +27 -0
- package/ts/logger.ts +63 -19
- package/ts/parseCliArgs.spec.ts +88 -0
- package/ts/parseCliArgs.ts +48 -10
- package/ts/rustBinary.ts +68 -0
- package/ts/versionChecker.spec.ts +48 -0
- package/ts/versionChecker.ts +67 -10
- package/ts/xterm-proxy.ts +130 -0
- package/dist/logger-CX77vJDA.js +0 -16
package/dist/cli.js
CHANGED
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import {
|
|
3
|
-
import { t as
|
|
2
|
+
import { n as logger } from "./logger-B9h0djqx.js";
|
|
3
|
+
import { n as version, t as name } from "./package-DpfHTSW2.js";
|
|
4
4
|
import { argv } from "process";
|
|
5
5
|
import { execFileSync, spawn } from "child_process";
|
|
6
6
|
import ms from "ms";
|
|
7
7
|
import yargs from "yargs";
|
|
8
8
|
import { hideBin } from "yargs/helpers";
|
|
9
|
-
import {
|
|
9
|
+
import { existsSync, lstatSync, mkdirSync, readlinkSync, unlinkSync } from "fs";
|
|
10
10
|
import { chmod, copyFile, mkdir, readFile, writeFile } from "fs/promises";
|
|
11
|
-
import path from "path";
|
|
12
11
|
import { homedir } from "os";
|
|
13
|
-
import
|
|
12
|
+
import path from "path";
|
|
14
13
|
|
|
15
14
|
//#region ts/parseCliArgs.ts
|
|
16
15
|
/**
|
|
17
16
|
* Parse CLI arguments the same way cli.ts does
|
|
18
17
|
* This is a test helper that mirrors the parsing logic in cli.ts
|
|
19
18
|
*/
|
|
20
|
-
function parseCliArgs(argv) {
|
|
19
|
+
function parseCliArgs(argv, supportedClis) {
|
|
21
20
|
const scriptBaseName = argv[1]?.split(/[/\\]/).at(-1)?.replace(/(\.[jt]s)?$/, "") || "";
|
|
22
21
|
const CLI_ALIASES = { cy: "claude" };
|
|
23
22
|
const cliName = (() => {
|
|
@@ -129,7 +128,7 @@ function parseCliArgs(argv) {
|
|
|
129
128
|
}).positional("cli", {
|
|
130
129
|
describe: "The AI CLI to run, e.g., claude, codex, copilot, cursor, gemini",
|
|
131
130
|
type: "string",
|
|
132
|
-
choices:
|
|
131
|
+
choices: supportedClis,
|
|
133
132
|
demandOption: false,
|
|
134
133
|
default: cliName
|
|
135
134
|
}).help().version(false).option("version", {
|
|
@@ -156,26 +155,53 @@ function parseCliArgs(argv) {
|
|
|
156
155
|
if (key === "continue") yargsConsumed.add("-c");
|
|
157
156
|
}
|
|
158
157
|
});
|
|
158
|
+
const positionalPromptWords = [];
|
|
159
159
|
const cliArgsForSpawn = (() => {
|
|
160
|
-
if (parsedArgv._[0] && !cliName)
|
|
161
|
-
|
|
160
|
+
if (parsedArgv._[0] && !cliName) {
|
|
161
|
+
const allAfterCli = rawArgs.slice((cliArgIndex ?? 0) + 1, dashIndex ?? void 0);
|
|
162
|
+
const result = [];
|
|
163
|
+
for (let i = 0; i < allAfterCli.length; i++) {
|
|
164
|
+
const arg = allAfterCli[i];
|
|
165
|
+
if (arg.startsWith("-")) {
|
|
166
|
+
result.push(arg);
|
|
167
|
+
if (!arg.includes("=") && i + 1 < allAfterCli.length) {
|
|
168
|
+
const nextArg = allAfterCli[i + 1];
|
|
169
|
+
if (nextArg && !nextArg.startsWith("-")) {
|
|
170
|
+
result.push(nextArg);
|
|
171
|
+
i++;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
} else positionalPromptWords.push(arg);
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
} else if (cliName) {
|
|
162
178
|
const result = [];
|
|
163
179
|
const argsToCheck = rawArgs.slice(0, dashIndex ?? void 0);
|
|
164
180
|
for (let i = 0; i < argsToCheck.length; i++) {
|
|
165
181
|
const arg = argsToCheck[i];
|
|
166
182
|
if (!arg) continue;
|
|
167
183
|
const [flag] = arg.split("=");
|
|
168
|
-
if (flag && yargsConsumed.has(flag)) {
|
|
184
|
+
if (flag && yargsConsumed.has(flag) || flag?.startsWith("--no-") && yargsConsumed.has(`--${flag.slice(5)}`)) {
|
|
169
185
|
if (!arg.includes("=") && i + 1 < argsToCheck.length) {
|
|
170
186
|
const nextArg = argsToCheck[i + 1];
|
|
171
187
|
if (nextArg && !nextArg.startsWith("-")) i++;
|
|
172
188
|
}
|
|
173
|
-
} else
|
|
189
|
+
} else if (arg.startsWith("-")) {
|
|
190
|
+
result.push(arg);
|
|
191
|
+
if (!arg.includes("=") && i + 1 < argsToCheck.length) {
|
|
192
|
+
const nextArg = argsToCheck[i + 1];
|
|
193
|
+
if (nextArg && !nextArg.startsWith("-")) {
|
|
194
|
+
result.push(nextArg);
|
|
195
|
+
i++;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
} else positionalPromptWords.push(arg);
|
|
174
199
|
}
|
|
175
200
|
return result;
|
|
176
201
|
}
|
|
177
202
|
return [];
|
|
178
203
|
})();
|
|
204
|
+
const positionalPrompt = positionalPromptWords.join(" ") || void 0;
|
|
179
205
|
const dashPrompt = dashIndex === void 0 ? void 0 : rawArgs.slice(dashIndex + 1).join(" ");
|
|
180
206
|
if (parsedArgv.exitOnIdle !== void 0) console.warn("\x1B[33m⚠ Warning: --exit-on-idle and -e are deprecated. Please use --timeout instead.\x1B[0m");
|
|
181
207
|
return {
|
|
@@ -183,7 +209,11 @@ function parseCliArgs(argv) {
|
|
|
183
209
|
env: process.env,
|
|
184
210
|
cli: cliName || parsedArgv.cli || (dashIndex !== 0 ? parsedArgv._[0]?.toString()?.replace?.(/-yes$/, "") : void 0),
|
|
185
211
|
cliArgs: [...cliArgsForSpawn, ...parsedArgv.yes ? ["--dangerously-skip-permissions"] : []],
|
|
186
|
-
prompt: [
|
|
212
|
+
prompt: [
|
|
213
|
+
parsedArgv.prompt,
|
|
214
|
+
positionalPrompt,
|
|
215
|
+
dashPrompt
|
|
216
|
+
].filter(Boolean).join(" ") || void 0,
|
|
187
217
|
install: parsedArgv.install,
|
|
188
218
|
exitOnIdle: Number((parsedArgv.timeout || parsedArgv.idle || parsedArgv.exitOnIdle)?.replace(/.*/, (e) => String(ms(e))) || 0),
|
|
189
219
|
queue: parsedArgv.queue,
|
|
@@ -242,7 +272,12 @@ function detectPackageManager() {
|
|
|
242
272
|
*/
|
|
243
273
|
async function checkAndAutoUpdate() {
|
|
244
274
|
if (process.env.AGENT_YES_NO_UPDATE) return;
|
|
245
|
-
if (process.env.AGENT_YES_UPDATED
|
|
275
|
+
if (process.env.AGENT_YES_UPDATED) return;
|
|
276
|
+
if (import.meta.url.startsWith("file://") && !import.meta.url.includes("node_modules")) {
|
|
277
|
+
const scriptDir = path.dirname(new URL(import.meta.url).pathname);
|
|
278
|
+
const repoRoot = path.resolve(scriptDir, "..");
|
|
279
|
+
if (existsSync(path.join(repoRoot, ".git"))) return;
|
|
280
|
+
}
|
|
246
281
|
try {
|
|
247
282
|
let latestVersion;
|
|
248
283
|
const cache = await readUpdateCache();
|
|
@@ -265,6 +300,7 @@ async function runInstall(latestVersion) {
|
|
|
265
300
|
const installCmd = detectPackageManager() === "bun" ? `bun add -g agent-yes@${latestVersion}` : `npm install -g agent-yes@${latestVersion}`;
|
|
266
301
|
process.stderr.write(`\x1b[33m[agent-yes] Updating ${version} → ${latestVersion}…\x1b[0m\n`);
|
|
267
302
|
try {
|
|
303
|
+
const { execaCommand } = await import("execa");
|
|
268
304
|
await execaCommand(installCmd, { stdio: "inherit" });
|
|
269
305
|
process.stderr.write(`\x1b[32m[agent-yes] Updated to ${latestVersion}\x1b[0m\n`);
|
|
270
306
|
return true;
|
|
@@ -321,10 +357,47 @@ function compareVersions(v1, v2) {
|
|
|
321
357
|
return 0;
|
|
322
358
|
}
|
|
323
359
|
/**
|
|
360
|
+
* Detect how agent-yes was installed.
|
|
361
|
+
* Returns a short label: "git", "bun link", "bun", "npm", "npx", or "unknown"
|
|
362
|
+
*/
|
|
363
|
+
function detectInstallMethod() {
|
|
364
|
+
try {
|
|
365
|
+
const scriptDir = path.dirname(new URL(import.meta.url).pathname);
|
|
366
|
+
if (!scriptDir.includes("node_modules")) {
|
|
367
|
+
const repoRoot = path.resolve(scriptDir, "..");
|
|
368
|
+
if (existsSync(path.join(repoRoot, ".git"))) return "git";
|
|
369
|
+
return "source";
|
|
370
|
+
}
|
|
371
|
+
const nodeModulesEntry = scriptDir.replace(/\/dist$/, "");
|
|
372
|
+
try {
|
|
373
|
+
if (lstatSync(nodeModulesEntry).isSymbolicLink()) {
|
|
374
|
+
const target = readlinkSync(nodeModulesEntry);
|
|
375
|
+
const resolvedTarget = path.resolve(path.dirname(nodeModulesEntry), target);
|
|
376
|
+
if (existsSync(path.join(resolvedTarget, ".git"))) return "bun link (git)";
|
|
377
|
+
return "bun link";
|
|
378
|
+
}
|
|
379
|
+
} catch {}
|
|
380
|
+
if (scriptDir.includes(".bun/")) return "bun";
|
|
381
|
+
if (scriptDir.includes(".npm/")) return "npx";
|
|
382
|
+
if (process.env.npm_execpath?.includes("bun")) return "bun";
|
|
383
|
+
if (process.env.npm_config_user_agent?.startsWith("bun")) return "bun";
|
|
384
|
+
if (process.env.npm_config_user_agent?.startsWith("npm")) return "npm";
|
|
385
|
+
return "npm";
|
|
386
|
+
} catch {
|
|
387
|
+
return "unknown";
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Format version string with install method
|
|
392
|
+
*/
|
|
393
|
+
function versionString() {
|
|
394
|
+
return `agent-yes v${version} (${detectInstallMethod()})`;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
324
397
|
* Display version information with async latest version check
|
|
325
398
|
*/
|
|
326
399
|
async function displayVersion() {
|
|
327
|
-
console.log(
|
|
400
|
+
console.log(versionString());
|
|
328
401
|
const latestVersion = await fetchLatestVersion();
|
|
329
402
|
if (latestVersion) {
|
|
330
403
|
const comparison = compareVersions(version, latestVersion);
|
|
@@ -466,6 +539,53 @@ async function downloadBinary(verbose = false) {
|
|
|
466
539
|
return binaryPath;
|
|
467
540
|
}
|
|
468
541
|
/**
|
|
542
|
+
* Get the version of a Rust binary by running it with --version
|
|
543
|
+
*/
|
|
544
|
+
function getRustBinaryVersion(binaryPath) {
|
|
545
|
+
try {
|
|
546
|
+
const match = execFileSync(binaryPath, ["--version"], {
|
|
547
|
+
timeout: 5e3,
|
|
548
|
+
encoding: "utf8",
|
|
549
|
+
stdio: [
|
|
550
|
+
"ignore",
|
|
551
|
+
"pipe",
|
|
552
|
+
"ignore"
|
|
553
|
+
]
|
|
554
|
+
}).match(/(\d+\.\d+\.\d+)/);
|
|
555
|
+
return match ? match[1] : null;
|
|
556
|
+
} catch {
|
|
557
|
+
return null;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Check if a binary path is inside a git repo (dev build), and rebuild if outdated.
|
|
562
|
+
* Returns the same path if up-to-date or rebuilt, undefined if rebuild failed.
|
|
563
|
+
*/
|
|
564
|
+
function autoRebuildIfOutdated(binaryPath, verbose) {
|
|
565
|
+
if (!binaryPath.includes("/target/release") && !binaryPath.includes("/target/debug")) return true;
|
|
566
|
+
const binaryVersion = getRustBinaryVersion(binaryPath);
|
|
567
|
+
if (verbose) console.log(`[rust] Binary version: ${binaryVersion}, package version: ${version}`);
|
|
568
|
+
if (binaryVersion === version) return true;
|
|
569
|
+
const rsDir = binaryPath.replace(/\/target\/(release|debug)\/agent-yes.*$/, "");
|
|
570
|
+
if (!existsSync(path.join(rsDir, "Cargo.toml"))) {
|
|
571
|
+
if (verbose) console.log(`[rust] Cannot find Cargo.toml at ${rsDir}, skipping rebuild`);
|
|
572
|
+
return true;
|
|
573
|
+
}
|
|
574
|
+
process.stderr.write(`\x1b[33m[rust] Binary outdated (${binaryVersion ?? "unknown"} → ${version}), rebuilding…\x1b[0m\n`);
|
|
575
|
+
try {
|
|
576
|
+
execFileSync("cargo", ["build", ...binaryPath.includes("/target/release") ? ["--release"] : []], {
|
|
577
|
+
cwd: rsDir,
|
|
578
|
+
stdio: "inherit",
|
|
579
|
+
timeout: 3e5
|
|
580
|
+
});
|
|
581
|
+
process.stderr.write(`\x1b[32m[rust] Rebuild complete\x1b[0m\n`);
|
|
582
|
+
return true;
|
|
583
|
+
} catch {
|
|
584
|
+
process.stderr.write(`\x1b[31m[rust] Auto-rebuild failed, using outdated binary\x1b[0m\n`);
|
|
585
|
+
return true;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
469
589
|
* Get or download the Rust binary
|
|
470
590
|
*/
|
|
471
591
|
async function getRustBinary(options = {}) {
|
|
@@ -474,6 +594,7 @@ async function getRustBinary(options = {}) {
|
|
|
474
594
|
const existing = findRustBinary(verbose);
|
|
475
595
|
if (existing) {
|
|
476
596
|
if (verbose) console.log(`[rust] Using existing binary: ${existing}`);
|
|
597
|
+
autoRebuildIfOutdated(existing, verbose);
|
|
477
598
|
return existing;
|
|
478
599
|
}
|
|
479
600
|
}
|
|
@@ -506,15 +627,23 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
|
|
|
506
627
|
|
|
507
628
|
//#endregion
|
|
508
629
|
//#region ts/cli.ts
|
|
630
|
+
{
|
|
631
|
+
const rawArgs = process.argv.slice(2);
|
|
632
|
+
if (rawArgs[0] === "-v" || rawArgs.includes("--version")) {
|
|
633
|
+
console.log(versionString());
|
|
634
|
+
process.exit(0);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
509
637
|
await checkAndAutoUpdate();
|
|
638
|
+
logger.info(versionString());
|
|
510
639
|
const config = parseCliArgs(process.argv);
|
|
511
640
|
if (config.tray) {
|
|
512
|
-
const { startTray } = await import("./tray-
|
|
641
|
+
const { startTray } = await import("./tray-Bzb1owBN.js");
|
|
513
642
|
await startTray();
|
|
514
643
|
await new Promise(() => {});
|
|
515
644
|
}
|
|
516
645
|
{
|
|
517
|
-
const { ensureTray } = await import("./tray-
|
|
646
|
+
const { ensureTray } = await import("./tray-Bzb1owBN.js");
|
|
518
647
|
ensureTray();
|
|
519
648
|
}
|
|
520
649
|
if (config.useRust) {
|
|
@@ -528,6 +657,7 @@ if (config.useRust) {
|
|
|
528
657
|
}
|
|
529
658
|
}
|
|
530
659
|
if (rustBinary) {
|
|
660
|
+
const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-C7sGMdKJ.js");
|
|
531
661
|
const rustArgs = buildRustArgs(process.argv, config.cli, SUPPORTED_CLIS);
|
|
532
662
|
if (config.verbose) {
|
|
533
663
|
console.log(`[rust] Using binary: ${rustBinary}`);
|
|
@@ -557,6 +687,7 @@ if (config.showVersion) {
|
|
|
557
687
|
process.exit(0);
|
|
558
688
|
}
|
|
559
689
|
if (config.appendPrompt) {
|
|
690
|
+
const { PidStore } = await import("./pidStore-B4yDm3TL.js");
|
|
560
691
|
const ipcPath = await PidStore.findActiveFifo(process.cwd());
|
|
561
692
|
if (!ipcPath) {
|
|
562
693
|
console.error("No active agent with IPC found in current directory.");
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import "./logger-
|
|
1
|
+
import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-CsdLrLod.js";
|
|
2
|
+
import "./logger-B9h0djqx.js";
|
|
3
|
+
import "./pidStore-CPrgJSJi.js";
|
|
3
4
|
|
|
4
5
|
export { AgentContext, CLIS_CONFIG, config, agentYes as default, removeControlCharacters };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
//#region ts/logger.ts
|
|
2
|
+
let _inner = null;
|
|
3
|
+
let _initPromise = null;
|
|
4
|
+
const _queue = [];
|
|
5
|
+
function init() {
|
|
6
|
+
if (_initPromise) return _initPromise;
|
|
7
|
+
_initPromise = import("winston").then(({ default: winston }) => {
|
|
8
|
+
const logFormat = winston.format.combine(winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), winston.format.printf(({ timestamp, level, message, ...meta }) => {
|
|
9
|
+
return `${timestamp} [${level}]: ${message}${Object.keys(meta).length ? ` ${JSON.stringify(meta)}` : ""}`;
|
|
10
|
+
}));
|
|
11
|
+
_inner = winston.createLogger({
|
|
12
|
+
level: process.env.VERBOSE ? "debug" : "info",
|
|
13
|
+
format: logFormat,
|
|
14
|
+
transports: [new winston.transports.Console({ format: winston.format.combine(winston.format.colorize(), logFormat) })],
|
|
15
|
+
silent: false
|
|
16
|
+
});
|
|
17
|
+
for (const { level, msg, meta } of _queue.splice(0)) _inner[level](msg, ...meta);
|
|
18
|
+
});
|
|
19
|
+
return _initPromise;
|
|
20
|
+
}
|
|
21
|
+
function makeMethod(level) {
|
|
22
|
+
return (msg, ...meta) => {
|
|
23
|
+
if (_inner) _inner[level](msg, ...meta);
|
|
24
|
+
else {
|
|
25
|
+
_queue.push({
|
|
26
|
+
level,
|
|
27
|
+
msg,
|
|
28
|
+
meta
|
|
29
|
+
});
|
|
30
|
+
init().catch((e) => console.error("[logger] Failed to load winston:", e));
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** Add a winston transport. Awaits logger initialization first. */
|
|
35
|
+
async function addTransport(transport) {
|
|
36
|
+
await init();
|
|
37
|
+
_inner.add(transport);
|
|
38
|
+
}
|
|
39
|
+
const logger = {
|
|
40
|
+
error: makeMethod("error"),
|
|
41
|
+
warn: makeMethod("warn"),
|
|
42
|
+
info: makeMethod("info"),
|
|
43
|
+
http: makeMethod("http"),
|
|
44
|
+
verbose: makeMethod("verbose"),
|
|
45
|
+
debug: makeMethod("debug"),
|
|
46
|
+
silly: makeMethod("silly")
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
//#endregion
|
|
50
|
+
export { logger as n, addTransport as t };
|
|
51
|
+
//# sourceMappingURL=logger-B9h0djqx.js.map
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { n as logger } from "./logger-B9h0djqx.js";
|
|
2
|
+
import { closeSync, existsSync, fsyncSync, openSync } from "fs";
|
|
3
|
+
import { appendFile, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { lock } from "proper-lockfile";
|
|
6
|
+
|
|
7
|
+
//#region ts/JsonlStore.ts
|
|
8
|
+
/**
|
|
9
|
+
* A lightweight NeDB-style JSONL persistence layer.
|
|
10
|
+
*
|
|
11
|
+
* - Append-only writes (one JSON object per line)
|
|
12
|
+
* - Same `_id` → last line wins (fields merged)
|
|
13
|
+
* - `$$deleted` lines act as tombstones
|
|
14
|
+
* - Crash recovery: skip partial last line, recover from temp file
|
|
15
|
+
* - Multi-process safe via proper-lockfile (reads don't need lock)
|
|
16
|
+
* - Compact on close: deduplicates into clean file via atomic rename
|
|
17
|
+
*/
|
|
18
|
+
var JsonlStore = class {
|
|
19
|
+
filePath;
|
|
20
|
+
tempPath;
|
|
21
|
+
docs = /* @__PURE__ */ new Map();
|
|
22
|
+
constructor(filePath) {
|
|
23
|
+
this.filePath = filePath;
|
|
24
|
+
this.tempPath = filePath + "~";
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Load all records from the JSONL file. No lock needed.
|
|
28
|
+
* Handles crash recovery: partial last line skipped, temp file recovery.
|
|
29
|
+
*/
|
|
30
|
+
async load() {
|
|
31
|
+
await mkdir(path.dirname(this.filePath), { recursive: true });
|
|
32
|
+
if (!existsSync(this.filePath) && existsSync(this.tempPath)) {
|
|
33
|
+
logger.debug("[JsonlStore] Recovering from temp file");
|
|
34
|
+
await rename(this.tempPath, this.filePath);
|
|
35
|
+
}
|
|
36
|
+
this.docs = /* @__PURE__ */ new Map();
|
|
37
|
+
let raw = "";
|
|
38
|
+
try {
|
|
39
|
+
raw = await readFile(this.filePath, "utf-8");
|
|
40
|
+
} catch (err) {
|
|
41
|
+
if (err.code === "ENOENT") return this.docs;
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
44
|
+
const lines = raw.split("\n");
|
|
45
|
+
let corruptCount = 0;
|
|
46
|
+
for (const line of lines) {
|
|
47
|
+
const trimmed = line.trim();
|
|
48
|
+
if (!trimmed) continue;
|
|
49
|
+
try {
|
|
50
|
+
const doc = JSON.parse(trimmed);
|
|
51
|
+
if (!doc._id) continue;
|
|
52
|
+
if (doc.$$deleted) this.docs.delete(doc._id);
|
|
53
|
+
else {
|
|
54
|
+
const existing = this.docs.get(doc._id);
|
|
55
|
+
if (existing) this.docs.set(doc._id, {
|
|
56
|
+
...existing,
|
|
57
|
+
...doc
|
|
58
|
+
});
|
|
59
|
+
else this.docs.set(doc._id, doc);
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
corruptCount++;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (corruptCount > 0) logger.debug(`[JsonlStore] Skipped ${corruptCount} corrupt line(s) in ${this.filePath}`);
|
|
66
|
+
return this.docs;
|
|
67
|
+
}
|
|
68
|
+
/** Get all live documents. */
|
|
69
|
+
getAll() {
|
|
70
|
+
return Array.from(this.docs.values());
|
|
71
|
+
}
|
|
72
|
+
/** Find a document by _id. */
|
|
73
|
+
getById(id) {
|
|
74
|
+
return this.docs.get(id);
|
|
75
|
+
}
|
|
76
|
+
/** Find documents matching a predicate. */
|
|
77
|
+
find(predicate) {
|
|
78
|
+
return this.getAll().filter(predicate);
|
|
79
|
+
}
|
|
80
|
+
/** Find first document matching a predicate. */
|
|
81
|
+
findOne(predicate) {
|
|
82
|
+
for (const doc of this.docs.values()) if (predicate(doc)) return doc;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Append a new document. Acquires lock.
|
|
86
|
+
* If no _id is provided, one is generated.
|
|
87
|
+
*/
|
|
88
|
+
async append(doc) {
|
|
89
|
+
const id = doc._id || generateId();
|
|
90
|
+
const { _id: _, ...rest } = doc;
|
|
91
|
+
const fullDoc = {
|
|
92
|
+
_id: id,
|
|
93
|
+
...rest
|
|
94
|
+
};
|
|
95
|
+
return await this.withLock(async () => {
|
|
96
|
+
await appendFile(this.filePath, JSON.stringify(fullDoc) + "\n");
|
|
97
|
+
const existing = this.docs.get(fullDoc._id);
|
|
98
|
+
if (existing) this.docs.set(fullDoc._id, {
|
|
99
|
+
...existing,
|
|
100
|
+
...fullDoc
|
|
101
|
+
});
|
|
102
|
+
else this.docs.set(fullDoc._id, fullDoc);
|
|
103
|
+
return fullDoc;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Update a document by _id. Appends a merge line. Acquires lock.
|
|
108
|
+
*/
|
|
109
|
+
async updateById(id, patch) {
|
|
110
|
+
await this.withLock(async () => {
|
|
111
|
+
const line = {
|
|
112
|
+
_id: id,
|
|
113
|
+
...patch
|
|
114
|
+
};
|
|
115
|
+
await appendFile(this.filePath, JSON.stringify(line) + "\n");
|
|
116
|
+
const existing = this.docs.get(id);
|
|
117
|
+
if (existing) this.docs.set(id, {
|
|
118
|
+
...existing,
|
|
119
|
+
...patch
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Delete a document by _id. Appends a tombstone. Acquires lock.
|
|
125
|
+
*/
|
|
126
|
+
async deleteById(id) {
|
|
127
|
+
await this.withLock(async () => {
|
|
128
|
+
const tombstone = {
|
|
129
|
+
_id: id,
|
|
130
|
+
$$deleted: true
|
|
131
|
+
};
|
|
132
|
+
await appendFile(this.filePath, JSON.stringify(tombstone) + "\n");
|
|
133
|
+
this.docs.delete(id);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Compact the file: deduplicate entries, remove tombstones.
|
|
138
|
+
* Writes to temp file, fsyncs, then atomic renames.
|
|
139
|
+
* Acquires lock.
|
|
140
|
+
*/
|
|
141
|
+
async compact() {
|
|
142
|
+
const lines = Array.from(this.docs.values()).map((doc) => {
|
|
143
|
+
const { _id, $$deleted: _$$deleted, ...rest } = doc;
|
|
144
|
+
return JSON.stringify({
|
|
145
|
+
_id,
|
|
146
|
+
...rest
|
|
147
|
+
});
|
|
148
|
+
}).join("\n");
|
|
149
|
+
const content = lines ? lines + "\n" : "";
|
|
150
|
+
try {
|
|
151
|
+
await this.withLock(async () => {
|
|
152
|
+
await writeFile(this.tempPath, content);
|
|
153
|
+
const fd = openSync(this.tempPath, "r");
|
|
154
|
+
fsyncSync(fd);
|
|
155
|
+
closeSync(fd);
|
|
156
|
+
await rename(this.tempPath, this.filePath);
|
|
157
|
+
});
|
|
158
|
+
} catch {
|
|
159
|
+
await writeFile(this.filePath, content);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async withLock(fn) {
|
|
163
|
+
const dir = path.dirname(this.filePath);
|
|
164
|
+
let release;
|
|
165
|
+
try {
|
|
166
|
+
release = await lock(dir, {
|
|
167
|
+
lockfilePath: this.filePath + ".lock",
|
|
168
|
+
retries: {
|
|
169
|
+
retries: 5,
|
|
170
|
+
minTimeout: 50,
|
|
171
|
+
maxTimeout: 500
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
return await fn();
|
|
175
|
+
} finally {
|
|
176
|
+
if (release) await release();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
let idCounter = 0;
|
|
181
|
+
function generateId() {
|
|
182
|
+
return Date.now().toString(36) + (idCounter++).toString(36) + Math.random().toString(36).slice(2, 6);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
//#endregion
|
|
186
|
+
//#region ts/pidStore.ts
|
|
187
|
+
var PidStore = class PidStore {
|
|
188
|
+
storeDir;
|
|
189
|
+
store;
|
|
190
|
+
constructor(workingDir) {
|
|
191
|
+
this.storeDir = path.resolve(workingDir, ".agent-yes");
|
|
192
|
+
this.store = new JsonlStore(path.join(this.storeDir, "pid-records.jsonl"));
|
|
193
|
+
}
|
|
194
|
+
async init() {
|
|
195
|
+
try {
|
|
196
|
+
await this.ensureGitignore();
|
|
197
|
+
await this.store.load();
|
|
198
|
+
await this.cleanStaleRecords();
|
|
199
|
+
} catch (error) {
|
|
200
|
+
logger.warn("[pidStore] Failed to initialize:", error);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
async registerProcess({ pid, cli, args, prompt, cwd }) {
|
|
204
|
+
const now = Date.now();
|
|
205
|
+
const record = {
|
|
206
|
+
pid,
|
|
207
|
+
cli,
|
|
208
|
+
args: JSON.stringify(args),
|
|
209
|
+
prompt,
|
|
210
|
+
cwd,
|
|
211
|
+
logFile: path.resolve(this.getLogDir(), `${pid}.log`),
|
|
212
|
+
fifoFile: this.getFifoPath(pid),
|
|
213
|
+
status: "active",
|
|
214
|
+
exitReason: "",
|
|
215
|
+
startedAt: now
|
|
216
|
+
};
|
|
217
|
+
const existing = this.store.findOne((doc) => doc.pid === pid);
|
|
218
|
+
if (existing) await this.store.updateById(existing._id, record);
|
|
219
|
+
else await this.store.append(record);
|
|
220
|
+
const result = this.store.findOne((doc) => doc.pid === pid);
|
|
221
|
+
if (!result) {
|
|
222
|
+
const allRecords = this.store.getAll();
|
|
223
|
+
logger.error(`[pidStore] Failed to find record for PID ${pid}. All records:`, allRecords);
|
|
224
|
+
throw new Error(`Failed to register process ${pid}`);
|
|
225
|
+
}
|
|
226
|
+
logger.debug(`[pidStore] Registered process ${pid}`);
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
async updateStatus(pid, status, extra) {
|
|
230
|
+
const existing = this.store.findOne((doc) => doc.pid === pid);
|
|
231
|
+
if (!existing) return;
|
|
232
|
+
const patch = { status };
|
|
233
|
+
if (extra?.exitReason !== void 0) patch.exitReason = extra.exitReason;
|
|
234
|
+
if (extra?.exitCode !== void 0) patch.exitCode = extra.exitCode;
|
|
235
|
+
await this.store.updateById(existing._id, patch);
|
|
236
|
+
logger.debug(`[pidStore] Updated process ${pid} status=${status}`);
|
|
237
|
+
}
|
|
238
|
+
getAllRecords() {
|
|
239
|
+
return this.store.getAll();
|
|
240
|
+
}
|
|
241
|
+
getLogDir() {
|
|
242
|
+
return path.resolve(this.storeDir, "logs");
|
|
243
|
+
}
|
|
244
|
+
getFifoPath(pid) {
|
|
245
|
+
if (process.platform === "win32") return `\\\\.\\pipe\\agent-yes-${pid}`;
|
|
246
|
+
else return path.resolve(this.storeDir, "fifo", `${pid}.stdin`);
|
|
247
|
+
}
|
|
248
|
+
async cleanStaleRecords() {
|
|
249
|
+
const activeRecords = this.store.find((r) => r.status !== "exited");
|
|
250
|
+
for (const record of activeRecords) if (!this.isProcessAlive(record.pid)) {
|
|
251
|
+
await this.store.updateById(record._id, {
|
|
252
|
+
status: "exited",
|
|
253
|
+
exitReason: "stale-cleanup"
|
|
254
|
+
});
|
|
255
|
+
logger.debug(`[pidStore] Cleaned stale record for PID ${record.pid}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async close() {
|
|
259
|
+
try {
|
|
260
|
+
await this.store.compact();
|
|
261
|
+
} catch (error) {
|
|
262
|
+
logger.debug("[pidStore] Compact on close failed:", error);
|
|
263
|
+
}
|
|
264
|
+
logger.debug("[pidStore] Database compacted and closed");
|
|
265
|
+
}
|
|
266
|
+
isProcessAlive(pid) {
|
|
267
|
+
try {
|
|
268
|
+
process.kill(pid, 0);
|
|
269
|
+
return true;
|
|
270
|
+
} catch {
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
async ensureGitignore() {
|
|
275
|
+
const gitignorePath = path.join(this.storeDir, ".gitignore");
|
|
276
|
+
const gitignoreContent = `# Auto-generated .gitignore for agent-yes
|
|
277
|
+
# Ignore all log files and runtime data
|
|
278
|
+
logs/
|
|
279
|
+
fifo/
|
|
280
|
+
pid-db/
|
|
281
|
+
*.jsonl
|
|
282
|
+
*.jsonl~
|
|
283
|
+
*.jsonl.lock
|
|
284
|
+
*.sqlite
|
|
285
|
+
*.sqlite-*
|
|
286
|
+
*.log
|
|
287
|
+
*.raw.log
|
|
288
|
+
*.lines.log
|
|
289
|
+
*.debug.log
|
|
290
|
+
|
|
291
|
+
# Ignore .gitignore itself
|
|
292
|
+
.gitignore
|
|
293
|
+
|
|
294
|
+
`;
|
|
295
|
+
try {
|
|
296
|
+
await mkdir(this.storeDir, { recursive: true });
|
|
297
|
+
await writeFile(gitignorePath, gitignoreContent, { flag: "wx" });
|
|
298
|
+
logger.debug(`[pidStore] Created .gitignore in ${this.storeDir}`);
|
|
299
|
+
} catch (error) {
|
|
300
|
+
if (error.code !== "EEXIST") logger.warn(`[pidStore] Failed to create .gitignore:`, error);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
static async findActiveFifo(workingDir) {
|
|
304
|
+
try {
|
|
305
|
+
const store = new PidStore(workingDir);
|
|
306
|
+
await store.init();
|
|
307
|
+
const records = store.store.find((r) => r.status !== "exited").sort((a, b) => b.startedAt - a.startedAt);
|
|
308
|
+
await store.close();
|
|
309
|
+
return records[0]?.fifoFile ?? null;
|
|
310
|
+
} catch (error) {
|
|
311
|
+
logger.warn("[pidStore] findActiveFifo failed:", error);
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
//#endregion
|
|
318
|
+
export { PidStore as t };
|
|
319
|
+
//# sourceMappingURL=pidStore-CPrgJSJi.js.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { execSync } from "child_process";
|
|
2
|
+
import { existsSync } from "fs";
|
|
2
3
|
import { mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
3
|
-
import path from "path";
|
|
4
4
|
import { homedir } from "os";
|
|
5
|
-
import
|
|
5
|
+
import path from "path";
|
|
6
6
|
|
|
7
7
|
//#region ts/runningLock.ts
|
|
8
8
|
const getLockDir = () => path.join(process.env.CLAUDE_YES_HOME || homedir(), ".claude-yes");
|
|
@@ -260,4 +260,4 @@ function shouldUseLock(_cwd) {
|
|
|
260
260
|
|
|
261
261
|
//#endregion
|
|
262
262
|
export { shouldUseLock as i, getRunningAgentCount as n, releaseLock as r, acquireLock as t };
|
|
263
|
-
//# sourceMappingURL=runningLock-
|
|
263
|
+
//# sourceMappingURL=runningLock-DQWJSptq.js.map
|