agent-yes 1.60.6 → 1.61.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.
@@ -1,22 +1,22 @@
1
- import { t as logger } from "./logger-CY9ormLF.js";
1
+ import { t as logger } from "./logger-CX77vJDA.js";
2
2
  import { arch, platform } from "process";
3
3
  import { execSync } from "child_process";
4
- import { closeSync, existsSync, fsyncSync, mkdirSync, openSync } from "fs";
5
- import path, { dirname, join } from "path";
6
- import { readFile } from "node:fs/promises";
7
- import os from "node:os";
8
- import path$1 from "node:path";
9
- import winston from "winston";
10
4
  import { execaCommandSync, parseCommandString } from "execa";
11
5
  import { fromWritable } from "from-node-stream";
12
- import { appendFile, mkdir as mkdir$1, readFile as readFile$1, readdir, rename, writeFile as writeFile$1 } from "fs/promises";
6
+ import { appendFile, mkdir, readFile, readdir, rename, writeFile } from "fs/promises";
7
+ import path, { dirname, join } from "path";
13
8
  import DIE from "phpdie";
14
9
  import sflow from "sflow";
15
10
  import { TerminalRenderStream } from "terminal-render";
16
11
  import { homedir } from "os";
12
+ import winston from "winston";
13
+ import { closeSync, existsSync, fsyncSync, openSync } from "fs";
17
14
  import { lock } from "proper-lockfile";
18
15
  import { execSync as execSync$1 } from "node:child_process";
19
16
  import { fileURLToPath } from "url";
17
+ import os from "node:os";
18
+ import { readFile as readFile$1 } from "node:fs/promises";
19
+ import path$1 from "node:path";
20
20
 
21
21
  //#region \0rolldown/runtime.js
22
22
  var __defProp = Object.defineProperty;
@@ -44,7 +44,7 @@ const getCodexSessionsDir = () => process.env.CLI_YES_TEST_HOME ? path.join(proc
44
44
  */
45
45
  async function loadSessionMap() {
46
46
  try {
47
- const content = await readFile$1(getSessionsFile(), "utf-8");
47
+ const content = await readFile(getSessionsFile(), "utf-8");
48
48
  return JSON.parse(content);
49
49
  } catch {
50
50
  return {};
@@ -56,8 +56,8 @@ async function loadSessionMap() {
56
56
  async function saveSessionMap(sessionMap) {
57
57
  try {
58
58
  const sessionsFile = getSessionsFile();
59
- await mkdir$1(path.dirname(sessionsFile), { recursive: true });
60
- await writeFile$1(sessionsFile, JSON.stringify(sessionMap, null, 2));
59
+ await mkdir(path.dirname(sessionsFile), { recursive: true });
60
+ await writeFile(sessionsFile, JSON.stringify(sessionMap, null, 2));
61
61
  } catch (error) {
62
62
  console.warn("Failed to save codex session map:", error);
63
63
  }
@@ -78,7 +78,7 @@ async function storeSessionForCwd(cwd, sessionId) {
78
78
  */
79
79
  async function parseCodexSessionFile(filePath) {
80
80
  try {
81
- const lines = (await readFile$1(filePath, "utf-8")).trim().split("\n");
81
+ const lines = (await readFile(filePath, "utf-8")).trim().split("\n");
82
82
  for (const line of lines) {
83
83
  if (!line.trim()) continue;
84
84
  const data = JSON.parse(line);
@@ -252,9 +252,9 @@ async function readLockFile() {
252
252
  try {
253
253
  const lockDir = getLockDir();
254
254
  const lockFilePath = getLockFile();
255
- await mkdir$1(lockDir, { recursive: true });
255
+ await mkdir(lockDir, { recursive: true });
256
256
  if (!existsSync(lockFilePath)) return { tasks: [] };
257
- const content = await readFile$1(lockFilePath, "utf8");
257
+ const content = await readFile(lockFilePath, "utf8");
258
258
  const lockFile = JSON.parse(content);
259
259
  lockFile.tasks = lockFile.tasks.filter((task) => {
260
260
  if (isProcessRunning(task.pid)) return true;
@@ -272,9 +272,9 @@ async function writeLockFile(lockFile, retryCount = 0) {
272
272
  try {
273
273
  const lockDir = getLockDir();
274
274
  const lockFilePath = getLockFile();
275
- await mkdir$1(lockDir, { recursive: true });
275
+ await mkdir(lockDir, { recursive: true });
276
276
  const tempFile = `${lockFilePath}.tmp.${process.pid}`;
277
- await writeFile$1(tempFile, JSON.stringify(lockFile, null, 2), "utf8");
277
+ await writeFile(tempFile, JSON.stringify(lockFile, null, 2), "utf8");
278
278
  await rename(tempFile, lockFilePath);
279
279
  } catch (error) {
280
280
  if (retryCount < MAX_RETRIES) {
@@ -437,19 +437,17 @@ function shouldUseLock(_cwd) {
437
437
  var JsonlStore = class {
438
438
  filePath;
439
439
  tempPath;
440
- lockPath;
441
440
  docs = /* @__PURE__ */ new Map();
442
441
  constructor(filePath) {
443
442
  this.filePath = filePath;
444
443
  this.tempPath = filePath + "~";
445
- this.lockPath = path.dirname(filePath);
446
444
  }
447
445
  /**
448
446
  * Load all records from the JSONL file. No lock needed.
449
447
  * Handles crash recovery: partial last line skipped, temp file recovery.
450
448
  */
451
449
  async load() {
452
- await mkdir$1(path.dirname(this.filePath), { recursive: true });
450
+ await mkdir(path.dirname(this.filePath), { recursive: true });
453
451
  if (!existsSync(this.filePath) && existsSync(this.tempPath)) {
454
452
  logger.debug("[JsonlStore] Recovering from temp file");
455
453
  await rename(this.tempPath, this.filePath);
@@ -457,7 +455,7 @@ var JsonlStore = class {
457
455
  this.docs = /* @__PURE__ */ new Map();
458
456
  let raw = "";
459
457
  try {
460
- raw = await readFile$1(this.filePath, "utf-8");
458
+ raw = await readFile(this.filePath, "utf-8");
461
459
  } catch (err) {
462
460
  if (err.code === "ENOENT") return this.docs;
463
461
  throw err;
@@ -561,7 +559,7 @@ var JsonlStore = class {
561
559
  */
562
560
  async compact() {
563
561
  const lines = Array.from(this.docs.values()).map((doc) => {
564
- const { _id, $$deleted, ...rest } = doc;
562
+ const { _id, $$deleted: _$$deleted, ...rest } = doc;
565
563
  return JSON.stringify({
566
564
  _id,
567
565
  ...rest
@@ -570,14 +568,14 @@ var JsonlStore = class {
570
568
  const content = lines ? lines + "\n" : "";
571
569
  try {
572
570
  await this.withLock(async () => {
573
- await writeFile$1(this.tempPath, content);
571
+ await writeFile(this.tempPath, content);
574
572
  const fd = openSync(this.tempPath, "r");
575
573
  fsyncSync(fd);
576
574
  closeSync(fd);
577
575
  await rename(this.tempPath, this.filePath);
578
576
  });
579
577
  } catch {
580
- await writeFile$1(this.filePath, content);
578
+ await writeFile(this.filePath, content);
581
579
  }
582
580
  }
583
581
  async withLock(fn) {
@@ -714,8 +712,8 @@ pid-db/
714
712
 
715
713
  `;
716
714
  try {
717
- await mkdir$1(this.storeDir, { recursive: true });
718
- await writeFile$1(gitignorePath, gitignoreContent, { flag: "wx" });
715
+ await mkdir(this.storeDir, { recursive: true });
716
+ await writeFile(gitignorePath, gitignoreContent, { flag: "wx" });
719
717
  logger.debug(`[pidStore] Created .gitignore in ${this.storeDir}`);
720
718
  } catch (error) {
721
719
  if (error.code !== "EEXIST") logger.warn(`[pidStore] Failed to create .gitignore:`, error);
@@ -837,7 +835,7 @@ async function sendMessage(context, message, { waitForReady = true } = {}) {
837
835
  */
838
836
  async function initializeLogPaths(pidStore, pid) {
839
837
  const logDir = pidStore.getLogDir();
840
- await mkdir$1(logDir, { recursive: true });
838
+ await mkdir(logDir, { recursive: true });
841
839
  return {
842
840
  logPath: logDir,
843
841
  rawLogPath: path.resolve(path.dirname(logDir), `${pid}.raw.log`),
@@ -862,8 +860,8 @@ function setupDebugLogging(debuggingLogsPath) {
862
860
  */
863
861
  async function saveLogFile(logPath, content) {
864
862
  if (!logPath) return;
865
- await mkdir$1(path.dirname(logPath), { recursive: true }).catch(() => null);
866
- await writeFile$1(logPath, content).catch(() => null);
863
+ await mkdir(path.dirname(logPath), { recursive: true }).catch(() => null);
864
+ await writeFile(logPath, content).catch(() => null);
867
865
  logger.info(`Full logs saved to ${logPath}`);
868
866
  }
869
867
  /**
@@ -876,8 +874,8 @@ async function saveDeprecatedLogFile(logFile, content, verbose) {
876
874
  if (!logFile) return;
877
875
  if (verbose) logger.info(`Writing rendered logs to ${logFile}`);
878
876
  const logFilePath = path.resolve(logFile);
879
- await mkdir$1(path.dirname(logFilePath), { recursive: true }).catch(() => null);
880
- await writeFile$1(logFilePath, content);
877
+ await mkdir(path.dirname(logFilePath), { recursive: true }).catch(() => null);
878
+ await writeFile(logFilePath, content);
881
879
  }
882
880
 
883
881
  //#endregion
@@ -906,7 +904,7 @@ function tryCatch(catchFn, fn) {
906
904
  //#endregion
907
905
  //#region package.json
908
906
  var name = "agent-yes";
909
- var version = "1.60.6";
907
+ var version = "1.61.0";
910
908
 
911
909
  //#endregion
912
910
  //#region ts/pty-fix.ts
@@ -1071,7 +1069,7 @@ function isCommandNotFoundError(e) {
1071
1069
  * ```
1072
1070
  */
1073
1071
  function spawnAgent(options) {
1074
- const { cli, cliConf, cliArgs, verbose, install, ptyOptions } = options;
1072
+ const { cli, cliConf, cliArgs, verbose: _verbose, install, ptyOptions } = options;
1075
1073
  const spawn = () => {
1076
1074
  let [bin, ...args] = [...parseCommandString(cliConf?.binary || cli), ...cliArgs];
1077
1075
  logger.debug(`Spawning ${bin} with args: ${JSON.stringify(args)}`);
@@ -1079,7 +1077,7 @@ function spawnAgent(options) {
1079
1077
  logger.info(`[${cli}-yes] Spawned ${bin} with PID ${spawned.pid} (agent-yes v${version})`);
1080
1078
  return spawned;
1081
1079
  };
1082
- return tryCatch((error, attempts, spawn, ...args) => {
1080
+ return tryCatch((error, _attempts, spawn, ...args) => {
1083
1081
  logger.error(`Fatal: Failed to start ${cli}.`);
1084
1082
  const isNotFound = isCommandNotFoundError(error);
1085
1083
  if (cliConf?.install && isNotFound) {
@@ -1293,7 +1291,7 @@ async function loadInstallEnv() {
1293
1291
  if (_installEnv) return _installEnv;
1294
1292
  const envPath = path$1.join(installDir, ".env");
1295
1293
  try {
1296
- _installEnv = parseEnvContent(await readFile(envPath, "utf-8"));
1294
+ _installEnv = parseEnvContent(await readFile$1(envPath, "utf-8"));
1297
1295
  } catch {
1298
1296
  _installEnv = {};
1299
1297
  }
@@ -1332,7 +1330,7 @@ async function notifyWebhook(status, details, cwd = process.cwd()) {
1332
1330
 
1333
1331
  //#endregion
1334
1332
  //#region ts/index.ts
1335
- const config = await import("./agent-yes.config-XmUcKFde.js").then((mod) => mod.default || mod);
1333
+ const config = await import("./agent-yes.config-DcxG25Gv.js").then((mod) => mod.default || mod);
1336
1334
  const CLIS_CONFIG = config.clis;
1337
1335
  /**
1338
1336
  * Main function to run agent-cli with automatic yes/no responses
@@ -1362,7 +1360,7 @@ const CLIS_CONFIG = config.clis;
1362
1360
  * });
1363
1361
  * ```
1364
1362
  */
1365
- async function agentYes({ cli, cliArgs = [], prompt, robust = true, cwd, env, exitOnIdle, logFile, removeControlCharactersFromStdout = false, verbose = false, queue = false, install = false, resume = false, useSkills = false, useStdinAppend = false, autoYes = true }) {
1363
+ async function agentYes({ cli, cliArgs = [], prompt, robust = true, cwd, env, exitOnIdle, logFile, removeControlCharactersFromStdout = false, verbose = false, queue = false, install = false, resume = false, useSkills = false, useStdinAppend: _useStdinAppend = false, autoYes = true }) {
1366
1364
  if (!cli) throw new Error(`cli is required`);
1367
1365
  const conf = CLIS_CONFIG[cli] || DIE(`Unsupported cli tool: ${cli}, current process.argv: ${process.argv.join(" ")}`);
1368
1366
  const workingDir = cwd ?? process.cwd();
@@ -1411,7 +1409,7 @@ async function agentYes({ cli, cliArgs = [], prompt, robust = true, cwd, env, ex
1411
1409
  let currentDir = workingDir;
1412
1410
  const searchLimit = gitRoot || path.parse(currentDir).root;
1413
1411
  while (true) {
1414
- const md = await readFile$1(path.resolve(currentDir, "SKILL.md"), "utf8").catch(() => null);
1412
+ const md = await readFile(path.resolve(currentDir, "SKILL.md"), "utf8").catch(() => null);
1415
1413
  if (md) {
1416
1414
  const headerMatch = md.match(/^[\s\S]*?(?=\n##\s)/);
1417
1415
  const headerRaw = (headerMatch ? headerMatch[0] : md).trim();
@@ -1776,7 +1774,7 @@ async function agentYes({ cli, cliArgs = [], prompt, robust = true, cwd, env, ex
1776
1774
  process.stdin.on("close", endHandler);
1777
1775
  process.stdin.on("error", errorHandler);
1778
1776
  },
1779
- cancel(reason) {
1777
+ cancel(_reason) {
1780
1778
  process.stdin.pause();
1781
1779
  }
1782
1780
  });
@@ -1857,10 +1855,10 @@ async function agentYes({ cli, cliArgs = [], prompt, robust = true, cwd, env, ex
1857
1855
  }).forkTo(async function rawLogger(f) {
1858
1856
  const rawLogPath = ctx.logPaths.rawLogPath;
1859
1857
  if (!rawLogPath) return f.run();
1860
- return await mkdir$1(path.dirname(rawLogPath), { recursive: true }).then(() => {
1858
+ return await mkdir(path.dirname(rawLogPath), { recursive: true }).then(() => {
1861
1859
  logger.debug(`[${cli}-yes] raw logs streaming to ${rawLogPath}`);
1862
1860
  return f.forEach(async (chars) => {
1863
- await writeFile$1(rawLogPath, chars, { flag: "a" }).catch(() => null);
1861
+ await writeFile(rawLogPath, chars, { flag: "a" }).catch(() => null);
1864
1862
  }).run();
1865
1863
  }).catch(() => f.run());
1866
1864
  }).by(function consoleResponder(e) {
@@ -1953,4 +1951,4 @@ const SUPPORTED_CLIS = Object.keys(CLIS_CONFIG);
1953
1951
 
1954
1952
  //#endregion
1955
1953
  export { AgentContext as a, PidStore as c, config as i, removeControlCharacters as l, CLIS_CONFIG as n, name as o, agentYes as r, version as s, SUPPORTED_CLIS as t };
1956
- //# sourceMappingURL=SUPPORTED_CLIS--w_SPLyp.js.map
1954
+ //# sourceMappingURL=SUPPORTED_CLIS-C0l1NfFH.js.map
@@ -1,6 +1,6 @@
1
- import { t as logger } from "./logger-CY9ormLF.js";
2
- import { access, mkdir, readFile, writeFile } from "node:fs/promises";
1
+ import { t as logger } from "./logger-CX77vJDA.js";
3
2
  import os from "node:os";
3
+ import { access, mkdir, readFile, writeFile } from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import { parse } from "yaml";
6
6
 
@@ -331,5 +331,5 @@ function getDefaultConfig() {
331
331
  }
332
332
 
333
333
  //#endregion
334
- export { agent_yes_config_default as t };
335
- //# sourceMappingURL=agent-yes.config-B-sre0vp.js.map
334
+ export { agent_yes_config_default as default };
335
+ //# sourceMappingURL=agent-yes.config-DcxG25Gv.js.map
package/dist/cli.js CHANGED
@@ -1,15 +1,14 @@
1
1
  #!/usr/bin/env bun
2
- import { c as PidStore, o as name, s as version, t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS--w_SPLyp.js";
3
- import "./agent-yes.config-B-sre0vp.js";
4
- import { t as logger } from "./logger-CY9ormLF.js";
2
+ import { c as PidStore, o as name, s as version, t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-C0l1NfFH.js";
3
+ import { t as logger } from "./logger-CX77vJDA.js";
5
4
  import { argv } from "process";
6
5
  import { spawn } from "child_process";
7
- import { existsSync, mkdirSync, unlinkSync } from "fs";
8
- import path from "path";
9
6
  import ms from "ms";
10
7
  import yargs from "yargs";
11
8
  import { hideBin } from "yargs/helpers";
12
- import { chmod, copyFile, rename } from "fs/promises";
9
+ import { chmod, copyFile } from "fs/promises";
10
+ import path from "path";
11
+ import { existsSync, mkdirSync, unlinkSync } from "fs";
13
12
 
14
13
  //#region ts/parseCliArgs.ts
15
14
  /**
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as AgentContext, i as config, l as removeControlCharacters, n as CLIS_CONFIG, r as agentYes } from "./SUPPORTED_CLIS--w_SPLyp.js";
2
- import "./logger-CY9ormLF.js";
1
+ import { a as AgentContext, i as config, l as removeControlCharacters, n as CLIS_CONFIG, r as agentYes } from "./SUPPORTED_CLIS-C0l1NfFH.js";
2
+ import "./logger-CX77vJDA.js";
3
3
 
4
4
  export { AgentContext, CLIS_CONFIG, config, agentYes as default, removeControlCharacters };
@@ -13,4 +13,4 @@ const logger = winston.createLogger({
13
13
 
14
14
  //#endregion
15
15
  export { logger as t };
16
- //# sourceMappingURL=logger-CY9ormLF.js.map
16
+ //# sourceMappingURL=logger-CX77vJDA.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-yes",
3
- "version": "1.60.6",
3
+ "version": "1.61.0",
4
4
  "description": "A wrapper tool that automates interactions with various AI CLI tools by automatically handling common prompts and responses.",
5
5
  "keywords": [
6
6
  "ai",
@@ -80,7 +80,8 @@
80
80
  "prepare": "husky",
81
81
  "release": "standard-version && npm publish",
82
82
  "release:beta": "standard-version && npm publish --tag beta",
83
- "test": "bun test --coverage"
83
+ "test": "vitest run",
84
+ "test:coverage": "vitest run --coverage"
84
85
  },
85
86
  "dependencies": {
86
87
  "@snomiao/bun-pty": "^0.3.4",
@@ -91,7 +92,7 @@
91
92
  "phpdie": "^1.7.0",
92
93
  "proper-lockfile": "^4.1.2",
93
94
  "sflow": "^1.27.0",
94
- "terminal-render": "^1.5.0",
95
+ "terminal-render": "^1.5.1",
95
96
  "winston": "^3.19.0",
96
97
  "yaml": "^2.8.2",
97
98
  "yargs": "^18.0.0"
@@ -106,6 +107,7 @@
106
107
  "@types/proper-lockfile": "^4.1.4",
107
108
  "@types/yargs": "^17.0.35",
108
109
  "@typescript/native-preview": "^7.0.0-dev.20260124.1",
110
+ "@vitest/coverage-v8": "^4.1.0",
109
111
  "husky": "^9.1.7",
110
112
  "lint-staged": "^16.2.7",
111
113
  "node-pty": "^1.1.0",
@@ -116,7 +118,7 @@
116
118
  "semantic-release": "^25.0.2",
117
119
  "standard-version": "^9.5.0",
118
120
  "tsdown": "^0.20.3",
119
- "vitest": "^4.0.17",
121
+ "vitest": "4.1.0",
120
122
  "zod": "^3.23.0"
121
123
  },
122
124
  "peerDependencies": {
@@ -0,0 +1,195 @@
1
+ import { describe, expect, it, beforeEach, afterEach } from "vitest";
2
+ import { JsonlStore } from "./JsonlStore";
3
+ import { rm, readFile, writeFile, mkdir } from "fs/promises";
4
+ import path from "path";
5
+
6
+ const TEST_DIR = "/tmp/jsonlstore-test-" + process.pid;
7
+ const TEST_FILE = path.join(TEST_DIR, "test.jsonl");
8
+
9
+ describe("JsonlStore", () => {
10
+ let store: JsonlStore;
11
+
12
+ beforeEach(async () => {
13
+ await rm(TEST_DIR, { recursive: true, force: true });
14
+ await mkdir(TEST_DIR, { recursive: true });
15
+ store = new JsonlStore(TEST_FILE);
16
+ });
17
+
18
+ afterEach(async () => {
19
+ await rm(TEST_DIR, { recursive: true, force: true });
20
+ });
21
+
22
+ describe("load", () => {
23
+ it("should return empty map for new file", async () => {
24
+ const docs = await store.load();
25
+ expect(docs.size).toBe(0);
26
+ });
27
+
28
+ it("should load existing JSONL data", async () => {
29
+ await writeFile(TEST_FILE, '{"_id":"1","name":"Alice"}\n{"_id":"2","name":"Bob"}\n');
30
+ const docs = await store.load();
31
+ expect(docs.size).toBe(2);
32
+ expect(docs.get("1")).toEqual({ _id: "1", name: "Alice" });
33
+ });
34
+
35
+ it("should merge duplicate IDs (last wins)", async () => {
36
+ await writeFile(
37
+ TEST_FILE,
38
+ '{"_id":"1","name":"Alice","age":30}\n{"_id":"1","name":"Alice Updated"}\n',
39
+ );
40
+ const docs = await store.load();
41
+ expect(docs.size).toBe(1);
42
+ expect(docs.get("1")).toEqual({ _id: "1", name: "Alice Updated", age: 30 });
43
+ });
44
+
45
+ it("should handle $$deleted tombstones", async () => {
46
+ await writeFile(TEST_FILE, '{"_id":"1","name":"Alice"}\n{"_id":"1","$$deleted":true}\n');
47
+ const docs = await store.load();
48
+ expect(docs.size).toBe(0);
49
+ });
50
+
51
+ it("should skip lines without _id", async () => {
52
+ await writeFile(TEST_FILE, '{"name":"no id"}\n{"_id":"1","name":"valid"}\n');
53
+ const docs = await store.load();
54
+ expect(docs.size).toBe(1);
55
+ });
56
+
57
+ it("should skip corrupt lines gracefully", async () => {
58
+ await writeFile(
59
+ TEST_FILE,
60
+ '{"_id":"1","name":"ok"}\n{corrupt json\n{"_id":"2","name":"also ok"}\n',
61
+ );
62
+ const docs = await store.load();
63
+ expect(docs.size).toBe(2);
64
+ });
65
+
66
+ it("should recover from temp file when main file missing", async () => {
67
+ const tempFile = TEST_FILE + "~";
68
+ await writeFile(tempFile, '{"_id":"1","name":"recovered"}\n');
69
+ const docs = await store.load();
70
+ expect(docs.size).toBe(1);
71
+ expect(docs.get("1")!.name).toBe("recovered");
72
+ });
73
+ });
74
+
75
+ describe("getAll / getById / find / findOne", () => {
76
+ beforeEach(async () => {
77
+ await writeFile(
78
+ TEST_FILE,
79
+ '{"_id":"1","name":"Alice","age":30}\n{"_id":"2","name":"Bob","age":25}\n{"_id":"3","name":"Charlie","age":35}\n',
80
+ );
81
+ await store.load();
82
+ });
83
+
84
+ it("getAll returns all documents", () => {
85
+ expect(store.getAll()).toHaveLength(3);
86
+ });
87
+
88
+ it("getById returns correct doc", () => {
89
+ expect(store.getById("2")).toEqual({ _id: "2", name: "Bob", age: 25 });
90
+ });
91
+
92
+ it("getById returns undefined for missing id", () => {
93
+ expect(store.getById("999")).toBeUndefined();
94
+ });
95
+
96
+ it("find returns matching docs", () => {
97
+ const result = store.find((d) => d.age > 28);
98
+ expect(result).toHaveLength(2);
99
+ });
100
+
101
+ it("findOne returns first match", () => {
102
+ const result = store.findOne((d) => d.age > 28);
103
+ expect(result).toBeDefined();
104
+ expect(result!.age).toBeGreaterThan(28);
105
+ });
106
+
107
+ it("findOne returns undefined when no match", () => {
108
+ expect(store.findOne((d) => d.age > 100)).toBeUndefined();
109
+ });
110
+ });
111
+
112
+ describe("append", () => {
113
+ it("should append and return doc with generated id", async () => {
114
+ await store.load();
115
+ const doc = await store.append({ name: "test" } as any);
116
+ expect(doc._id).toBeDefined();
117
+ expect(doc.name).toBe("test");
118
+ expect(store.getAll()).toHaveLength(1);
119
+ });
120
+
121
+ it("should append with provided _id", async () => {
122
+ await store.load();
123
+ const doc = await store.append({ _id: "custom-id", name: "test" } as any);
124
+ expect(doc._id).toBe("custom-id");
125
+ });
126
+
127
+ it("should merge with existing doc of same _id", async () => {
128
+ await store.load();
129
+ await store.append({ _id: "x", name: "first", value: 1 } as any);
130
+ await store.append({ _id: "x", name: "second" } as any);
131
+ const doc = store.getById("x")!;
132
+ expect(doc.name).toBe("second");
133
+ expect(doc.value).toBe(1);
134
+ });
135
+ });
136
+
137
+ describe("updateById", () => {
138
+ it("should update existing doc", async () => {
139
+ await store.load();
140
+ await store.append({ _id: "u1", name: "original", status: "active" } as any);
141
+ await store.updateById("u1", { status: "done" });
142
+ expect(store.getById("u1")!.status).toBe("done");
143
+ expect(store.getById("u1")!.name).toBe("original");
144
+ });
145
+
146
+ it("should no-op for non-existent id", async () => {
147
+ await store.load();
148
+ await store.updateById("nonexistent", { status: "done" });
149
+ expect(store.getAll()).toHaveLength(0);
150
+ });
151
+ });
152
+
153
+ describe("deleteById", () => {
154
+ it("should remove doc from memory and write tombstone", async () => {
155
+ await store.load();
156
+ await store.append({ _id: "d1", name: "to delete" } as any);
157
+ expect(store.getAll()).toHaveLength(1);
158
+
159
+ await store.deleteById("d1");
160
+ expect(store.getAll()).toHaveLength(0);
161
+ expect(store.getById("d1")).toBeUndefined();
162
+
163
+ // Tombstone should be written to file
164
+ const content = await readFile(TEST_FILE, "utf-8");
165
+ expect(content).toContain('"$$deleted":true');
166
+ });
167
+ });
168
+
169
+ describe("compact", () => {
170
+ it("should deduplicate entries", async () => {
171
+ await store.load();
172
+ await store.append({ _id: "c1", name: "v1" } as any);
173
+ await store.updateById("c1", { name: "v2" });
174
+ await store.updateById("c1", { name: "v3" });
175
+
176
+ // Before compact: 3 lines
177
+ const before = (await readFile(TEST_FILE, "utf-8")).trim().split("\n");
178
+ expect(before).toHaveLength(3);
179
+
180
+ await store.compact();
181
+
182
+ // After compact: 1 line
183
+ const after = (await readFile(TEST_FILE, "utf-8")).trim().split("\n");
184
+ expect(after).toHaveLength(1);
185
+ expect(JSON.parse(after[0]!).name).toBe("v3");
186
+ });
187
+
188
+ it("should handle empty store", async () => {
189
+ await store.load();
190
+ await store.compact();
191
+ const content = await readFile(TEST_FILE, "utf-8");
192
+ expect(content).toBe("");
193
+ });
194
+ });
195
+ });
package/ts/JsonlStore.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { appendFile, mkdir, readFile, rename, unlink, writeFile } from "fs/promises";
1
+ import { appendFile, mkdir, readFile, rename, writeFile } from "fs/promises";
2
2
  import { existsSync } from "fs";
3
3
  import { fsyncSync, openSync, closeSync } from "fs";
4
4
  import path from "path";
5
- import { lock, unlock } from "proper-lockfile";
5
+ import { lock } from "proper-lockfile";
6
6
  import { logger } from "./logger.ts";
7
7
 
8
8
  export interface JsonlDoc {
@@ -24,14 +24,11 @@ export interface JsonlDoc {
24
24
  export class JsonlStore<T extends Record<string, any> = Record<string, any>> {
25
25
  private filePath: string;
26
26
  private tempPath: string;
27
- private lockPath: string;
28
27
  private docs = new Map<string, T & JsonlDoc>();
29
28
 
30
29
  constructor(filePath: string) {
31
30
  this.filePath = filePath;
32
31
  this.tempPath = filePath + "~";
33
- // Lock on the directory (proper-lockfile needs an existing path)
34
- this.lockPath = path.dirname(filePath);
35
32
  }
36
33
 
37
34
  /**
@@ -169,7 +166,7 @@ export class JsonlStore<T extends Record<string, any> = Record<string, any>> {
169
166
  async compact(): Promise<void> {
170
167
  const lines = Array.from(this.docs.values())
171
168
  .map((doc) => {
172
- const { _id, $$deleted, ...rest } = doc;
169
+ const { _id, $$deleted: _$$deleted, ...rest } = doc;
173
170
  return JSON.stringify({ _id, ...rest });
174
171
  })
175
172
  .join("\n");
package/ts/cli.ts CHANGED
@@ -1,9 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
  import { argv } from "process";
3
3
  import { spawn } from "child_process";
4
- import { existsSync } from "fs";
5
- import path from "path";
6
- import cliYesConfig from "../agent-yes.config.ts";
7
4
  import { parseCliArgs } from "./parseCliArgs.ts";
8
5
  import { SUPPORTED_CLIS } from "./SUPPORTED_CLIS.ts";
9
6
  import { logger } from "./logger.ts";
@@ -1,4 +1,4 @@
1
- import { describe, it, expect, beforeEach, afterEach } from "bun:test";
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
2
  import { loadCascadingConfig, getConfigPaths, ensureSchemaInConfigFiles } from "./configLoader.ts";
3
3
  import { mkdir, writeFile, readFile, rm } from "node:fs/promises";
4
4
  import path from "node:path";
@@ -187,4 +187,68 @@ configDir: /test
187
187
  expect(result.skipped).toContain(configPath);
188
188
  expect(result.modified).not.toContain(configPath);
189
189
  });
190
+
191
+ it("should handle invalid JSON config gracefully", async () => {
192
+ const configPath = path.join(testDir, ".agent-yes.config.json");
193
+ await writeFile(configPath, "not valid json {{{");
194
+
195
+ const config = await loadCascadingConfig({ projectDir: testDir, homeDir: testDir });
196
+ // Should not throw, returns empty
197
+ expect(config).toEqual({});
198
+ });
199
+
200
+ it("should handle empty YAML config returning null", async () => {
201
+ const configPath = path.join(testDir, ".agent-yes.config.yaml");
202
+ await writeFile(configPath, "");
203
+
204
+ const config = await loadCascadingConfig({ projectDir: testDir, homeDir: testDir });
205
+ expect(config).toEqual({});
206
+ });
207
+
208
+ it("should add schema to YAML with leading comments", async () => {
209
+ const configPath = path.join(testDir, ".agent-yes.config.yaml");
210
+ await writeFile(
211
+ configPath,
212
+ `# My config comment
213
+ # Another comment
214
+ configDir: /test
215
+ `,
216
+ );
217
+
218
+ const result = await ensureSchemaInConfigFiles({ projectDir: testDir, homeDir: testDir });
219
+ expect(result.modified).toContain(configPath);
220
+ const content = await readFile(configPath, "utf-8");
221
+ expect(content).toContain("yaml-language-server:");
222
+ });
223
+
224
+ it("should handle ensureSchema when no config files exist", async () => {
225
+ const emptyDir = path.join(testDir, "empty");
226
+ await mkdir(emptyDir, { recursive: true });
227
+
228
+ const result = await ensureSchemaInConfigFiles({ projectDir: emptyDir, homeDir: emptyDir });
229
+ expect(result.modified).toHaveLength(0);
230
+ expect(result.skipped).toHaveLength(0);
231
+ });
232
+
233
+ it("should handle addSchemaReference for JSON that already has schema", async () => {
234
+ const configPath = path.join(testDir, ".agent-yes.config.json");
235
+ const content = JSON.stringify({ $schema: "existing", configDir: "/test" }, null, 2);
236
+ await writeFile(configPath, content);
237
+
238
+ const result = await ensureSchemaInConfigFiles({ projectDir: testDir, homeDir: testDir });
239
+ expect(result.skipped).toContain(configPath);
240
+ });
241
+
242
+ it("should use defaults for getConfigPaths without options", () => {
243
+ const paths = getConfigPaths();
244
+ expect(paths.length).toBeGreaterThan(0);
245
+ expect(paths.some((p) => p.includes(".agent-yes.config.json"))).toBe(true);
246
+ });
247
+
248
+ it("should use defaults for loadCascadingConfig without options", async () => {
249
+ // This tests the default path (process.cwd() and os.homedir())
250
+ const config = await loadCascadingConfig();
251
+ // Should not throw, may or may not find configs
252
+ expect(config).toBeDefined();
253
+ });
190
254
  });
@@ -0,0 +1,29 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { defineCliYesConfig } from "./defineConfig";
3
+
4
+ describe("defineCliYesConfig", () => {
5
+ it("should return a plain config object", async () => {
6
+ const cfg = await defineCliYesConfig({ clis: {} });
7
+ expect(cfg).toEqual({ clis: {} });
8
+ });
9
+
10
+ it("should accept a function that receives the default config", async () => {
11
+ const cfg = await defineCliYesConfig((original) => {
12
+ expect(original).toEqual({ clis: {} });
13
+ return { ...original, clis: { claude: { bin: "claude" } } };
14
+ });
15
+ expect(cfg.clis).toHaveProperty("claude");
16
+ });
17
+
18
+ it("should accept an async function", async () => {
19
+ const cfg = await defineCliYesConfig(async () => {
20
+ return { clis: { test: { bin: "test-cli" } } };
21
+ });
22
+ expect(cfg.clis.test).toEqual({ bin: "test-cli" });
23
+ });
24
+
25
+ it("should accept a promise", async () => {
26
+ const cfg = await defineCliYesConfig(Promise.resolve({ clis: {} }));
27
+ expect(cfg).toEqual({ clis: {} });
28
+ });
29
+ });
@@ -52,4 +52,20 @@ describe("IdleWaiter", () => {
52
52
  const result = waiter.ping().ping().ping();
53
53
  expect(result).toBe(waiter);
54
54
  });
55
+
56
+ it("should wait until idle period has passed", async () => {
57
+ const waiter = new IdleWaiter();
58
+ waiter.checkInterval = 10;
59
+
60
+ // Ping right now so it's not idle
61
+ waiter.ping();
62
+
63
+ const start = Date.now();
64
+ // Wait for 50ms idle period — the wait loop should actually iterate
65
+ await waiter.wait(50);
66
+ const elapsed = Date.now() - start;
67
+
68
+ // Should have waited at least ~50ms
69
+ expect(elapsed).toBeGreaterThanOrEqual(40);
70
+ });
55
71
  });
package/ts/index.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { execaCommandSync, parseCommandString } from "execa";
2
- import { fromReadable, fromWritable } from "from-node-stream";
2
+ import { fromWritable } from "from-node-stream";
3
3
  import { mkdir, readFile, writeFile } from "fs/promises";
4
4
  import path from "path";
5
5
  import DIE from "phpdie";
6
6
  import sflow from "sflow";
7
- import { TerminalRenderStream, TerminalTextRender } from "terminal-render";
7
+ import { TerminalRenderStream } from "terminal-render";
8
8
  import {
9
9
  extractSessionId,
10
10
  getSessionForCwd,
@@ -14,7 +14,7 @@ import pty, { ptyPackage } from "./pty.ts";
14
14
  import { removeControlCharacters } from "./removeControlCharacters.ts";
15
15
  import { acquireLock, releaseLock, shouldUseLock } from "./runningLock.ts";
16
16
  import { logger } from "./logger.ts";
17
- import { createFifoStream } from "./beta/fifo.ts";
17
+ // import { createFifoStream } from "./beta/fifo.ts";
18
18
  import { PidStore } from "./pidStore.ts";
19
19
  import { SUPPORTED_CLIS } from "./SUPPORTED_CLIS.ts";
20
20
  import { sendEnter, sendMessage } from "./core/messaging.ts";
@@ -26,10 +26,10 @@ import {
26
26
  } from "./core/logging.ts";
27
27
  import { spawnAgent } from "./core/spawner.ts";
28
28
  import { AgentContext } from "./core/context.ts";
29
- import { createAutoResponseHandler } from "./core/responders.ts";
29
+ // import { createAutoResponseHandler } from "./core/responders.ts";
30
30
  import { createTerminatorStream } from "./core/streamHelpers.ts";
31
31
  import { globalAgentRegistry } from "./agentRegistry.ts";
32
- import { ReadyManager } from "./ReadyManager.ts";
32
+ // import { ReadyManager } from "./ReadyManager.ts";
33
33
  import { notifyWebhook } from "./webhookNotifier.ts";
34
34
 
35
35
  export { removeControlCharacters };
@@ -119,7 +119,7 @@ export default async function agentYes({
119
119
  install = false,
120
120
  resume = false,
121
121
  useSkills = false,
122
- useStdinAppend = false,
122
+ useStdinAppend: _useStdinAppend = false,
123
123
  autoYes = true,
124
124
  }: {
125
125
  cli: SUPPORTED_CLIS;
@@ -730,7 +730,7 @@ export default async function agentYes({
730
730
  process.stdin.on("close", endHandler);
731
731
  process.stdin.on("error", errorHandler);
732
732
  },
733
- cancel(reason) {
733
+ cancel(_reason) {
734
734
  process.stdin.pause();
735
735
  },
736
736
  });
@@ -802,6 +802,7 @@ export default async function agentYes({
802
802
  // Only check for /auto if line is short enough
803
803
  if (line.length <= 20) {
804
804
  const cleanLine = line
805
+ // oxlint-disable-next-line no-control-regex
805
806
  .replace(/[\x00-\x1f]|\x1b\[[0-9;]*[A-Za-z]|\[[A-Z]/g, "")
806
807
  .trim();
807
808
  if (cleanLine === "/auto") {
@@ -2,7 +2,6 @@ import ms from "ms";
2
2
  import yargs from "yargs";
3
3
  import { hideBin } from "yargs/helpers";
4
4
  import { SUPPORTED_CLIS } from "./SUPPORTED_CLIS.ts";
5
- import pkg from "../package.json" with { type: "json" };
6
5
 
7
6
  // const pkg = await JSON.parse(await readFile(path.resolve((import.meta.dir) + "/../package.json"), 'utf8'))
8
7
  /**
@@ -115,6 +115,20 @@ describe("PidStore", () => {
115
115
  expect(rec?.exitReason).toBe("crash");
116
116
  expect(rec?.exitCode).toBe(1);
117
117
  });
118
+
119
+ it("should no-op when updating non-existent pid", async () => {
120
+ await store.updateStatus(999888, "exited");
121
+ // Should not throw and records should be unchanged
122
+ expect(store.getAllRecords()).toHaveLength(0);
123
+ });
124
+
125
+ it("should update status without extra params", async () => {
126
+ await store.registerProcess({ pid: 333, cli: "test", args: [], cwd: "/tmp" });
127
+ await store.updateStatus(333, "idle");
128
+ const rec = store.getAllRecords().find((r) => r.pid === 333);
129
+ expect(rec?.status).toBe("idle");
130
+ expect(rec?.exitReason).toBe("");
131
+ });
118
132
  });
119
133
 
120
134
  describe("getAllRecords", () => {
package/ts/rustBinary.ts CHANGED
@@ -2,8 +2,8 @@
2
2
  * Rust binary helper - finds or downloads the appropriate prebuilt binary
3
3
  */
4
4
 
5
- import { existsSync, mkdirSync, unlinkSync, renameSync, copyFileSync } from "fs";
6
- import { chmod, unlink, rename, copyFile } from "fs/promises";
5
+ import { existsSync, mkdirSync, unlinkSync } from "fs";
6
+ import { chmod, copyFile } from "fs/promises";
7
7
  import path from "path";
8
8
 
9
9
  // Platform/arch to binary name mapping
@@ -60,7 +60,7 @@ export function getBinDir(): string {
60
60
  */
61
61
  export function findRustBinary(verbose = false): string | undefined {
62
62
  const binaryName = getBinaryName();
63
- const baseName = binaryName.replace(/\.exe$/, "");
63
+ const _baseName = binaryName.replace(/\.exe$/, "");
64
64
 
65
65
  const searchPaths = [
66
66
  // 1. Check in npm package bin directory
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from "bun:test";
1
+ import { describe, expect, it } from "vitest";
2
2
  import { extractSessionId, extractSessionIdFromSessionMeta } from "./resume/codexSessionManager";
3
3
 
4
4
  describe("Session Extraction Test", () => {
@@ -0,0 +1,119 @@
1
+ import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
2
+ import { compareVersions, fetchLatestVersion, displayVersion } from "./versionChecker";
3
+
4
+ describe("versionChecker", () => {
5
+ describe("compareVersions", () => {
6
+ it("should return 0 for equal versions", () => {
7
+ expect(compareVersions("1.0.0", "1.0.0")).toBe(0);
8
+ expect(compareVersions("2.3.4", "2.3.4")).toBe(0);
9
+ });
10
+
11
+ it("should return 1 when v1 > v2", () => {
12
+ expect(compareVersions("2.0.0", "1.0.0")).toBe(1);
13
+ expect(compareVersions("1.1.0", "1.0.0")).toBe(1);
14
+ expect(compareVersions("1.0.1", "1.0.0")).toBe(1);
15
+ });
16
+
17
+ it("should return -1 when v1 < v2", () => {
18
+ expect(compareVersions("1.0.0", "2.0.0")).toBe(-1);
19
+ expect(compareVersions("1.0.0", "1.1.0")).toBe(-1);
20
+ expect(compareVersions("1.0.0", "1.0.1")).toBe(-1);
21
+ });
22
+
23
+ it("should handle versions with different segment counts", () => {
24
+ expect(compareVersions("1.0", "1.0.0")).toBe(0);
25
+ expect(compareVersions("1.0.0", "1.0")).toBe(0);
26
+ expect(compareVersions("1.0.1", "1.0")).toBe(1);
27
+ expect(compareVersions("1.0", "1.0.1")).toBe(-1);
28
+ });
29
+ });
30
+
31
+ describe("fetchLatestVersion", () => {
32
+ beforeEach(() => {
33
+ vi.stubGlobal("fetch", vi.fn());
34
+ });
35
+
36
+ afterEach(() => {
37
+ vi.restoreAllMocks();
38
+ });
39
+
40
+ it("should return version from npm registry", async () => {
41
+ vi.mocked(fetch).mockResolvedValue({
42
+ ok: true,
43
+ json: async () => ({ version: "1.2.3" }),
44
+ } as Response);
45
+
46
+ const version = await fetchLatestVersion();
47
+ expect(version).toBe("1.2.3");
48
+ });
49
+
50
+ it("should return null on non-ok response", async () => {
51
+ vi.mocked(fetch).mockResolvedValue({
52
+ ok: false,
53
+ } as Response);
54
+
55
+ const version = await fetchLatestVersion();
56
+ expect(version).toBeNull();
57
+ });
58
+
59
+ it("should return null on network error", async () => {
60
+ vi.mocked(fetch).mockRejectedValue(new Error("network error"));
61
+
62
+ const version = await fetchLatestVersion();
63
+ expect(version).toBeNull();
64
+ });
65
+ });
66
+
67
+ describe("displayVersion", () => {
68
+ beforeEach(() => {
69
+ vi.stubGlobal("fetch", vi.fn());
70
+ vi.spyOn(console, "log").mockImplementation(() => {});
71
+ });
72
+
73
+ afterEach(() => {
74
+ vi.restoreAllMocks();
75
+ });
76
+
77
+ it("should log update available when behind", async () => {
78
+ vi.mocked(fetch).mockResolvedValue({
79
+ ok: true,
80
+ json: async () => ({ version: "999.0.0" }),
81
+ } as Response);
82
+
83
+ await displayVersion();
84
+
85
+ expect(console.log).toHaveBeenCalledWith(expect.stringContaining("update available"));
86
+ });
87
+
88
+ it("should log latest when versions match", async () => {
89
+ const pkg = await import("../package.json");
90
+ vi.mocked(fetch).mockResolvedValue({
91
+ ok: true,
92
+ json: async () => ({ version: pkg.default.version }),
93
+ } as Response);
94
+
95
+ await displayVersion();
96
+
97
+ expect(console.log).toHaveBeenCalledWith(expect.stringContaining("latest"));
98
+ });
99
+
100
+ it("should log latest published when ahead", async () => {
101
+ vi.mocked(fetch).mockResolvedValue({
102
+ ok: true,
103
+ json: async () => ({ version: "0.0.1" }),
104
+ } as Response);
105
+
106
+ await displayVersion();
107
+
108
+ expect(console.log).toHaveBeenCalledWith(expect.stringContaining("latest published"));
109
+ });
110
+
111
+ it("should handle fetch failure gracefully", async () => {
112
+ vi.mocked(fetch).mockRejectedValue(new Error("fail"));
113
+
114
+ await displayVersion();
115
+
116
+ expect(console.log).toHaveBeenCalledWith(expect.stringContaining("unable to check"));
117
+ });
118
+ });
119
+ });
@@ -1,4 +0,0 @@
1
- import { t as agent_yes_config_default } from "./agent-yes.config-B-sre0vp.js";
2
- import "./logger-CY9ormLF.js";
3
-
4
- export { agent_yes_config_default as default };