agent-yes 1.90.2 → 1.92.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-nqOr59QU.js +12 -0
- package/dist/cli.js +7 -7
- package/dist/{globalPidIndex-Cr-g75QF.js → globalPidIndex-yVd3mbsV.js} +52 -4
- package/dist/index.js +4 -4
- package/dist/{pidStore-C1JXxoPi.js → pidStore-DTzl6zeh.js} +48 -4
- package/dist/pidStore-iJY3JFTn.js +5 -0
- package/dist/{serve-BYPX6VXm.js → serve-CixOwENN.js} +3 -3
- package/dist/{subcommands-LRmJ4_iJ.js → subcommands-D9wmaZ3U.js} +3 -3
- package/dist/{subcommands-D5WYWmh-.js → subcommands-DjrOWqD9.js} +2 -2
- package/dist/{ts-CdqzM4tm.js → ts-CIgbGM8b.js} +27 -15
- package/dist/{versionChecker-_0XCEwWt.js → versionChecker-B1TNIVBt.js} +2 -2
- package/package.json +1 -1
- package/ts/agentYesHome.spec.ts +25 -0
- package/ts/agentYesHome.ts +20 -0
- package/ts/globalPidIndex.spec.ts +140 -0
- package/ts/globalPidIndex.ts +55 -3
- package/ts/index.ts +24 -2
- package/ts/parseCliArgs.spec.ts +18 -0
- package/ts/parseCliArgs.ts +4 -3
- package/ts/pidStore.spec.ts +27 -2
- package/ts/pidStore.ts +42 -2
- package/dist/SUPPORTED_CLIS-C4NNZH6T.js +0 -12
- package/dist/pidStore-BCsY5BW3.js +0 -5
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { t as CLIS_CONFIG } from "./ts-CIgbGM8b.js";
|
|
2
|
+
import "./logger-B9h0djqx.js";
|
|
3
|
+
import "./versionChecker-B1TNIVBt.js";
|
|
4
|
+
import "./pidStore-DTzl6zeh.js";
|
|
5
|
+
import "./globalPidIndex-yVd3mbsV.js";
|
|
6
|
+
|
|
7
|
+
//#region ts/SUPPORTED_CLIS.ts
|
|
8
|
+
const SUPPORTED_CLIS = Object.keys(CLIS_CONFIG);
|
|
9
|
+
|
|
10
|
+
//#endregion
|
|
11
|
+
export { SUPPORTED_CLIS };
|
|
12
|
+
//# sourceMappingURL=SUPPORTED_CLIS-nqOr59QU.js.map
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { n as logger } from "./logger-B9h0djqx.js";
|
|
3
|
-
import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-
|
|
3
|
+
import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-B1TNIVBt.js";
|
|
4
4
|
import { argv } from "process";
|
|
5
5
|
import { execFileSync, spawn } from "child_process";
|
|
6
6
|
import ms from "ms";
|
|
@@ -80,8 +80,8 @@ function parseCliArgs(argv, supportedClis) {
|
|
|
80
80
|
description: "Send a prompt to the active agent's stdin in current directory"
|
|
81
81
|
}).option("stdpush", {
|
|
82
82
|
type: "boolean",
|
|
83
|
-
description: "Enable external input stream to push additional data to stdin",
|
|
84
|
-
default:
|
|
83
|
+
description: "Enable external input stream to push additional data to stdin (default: true; pass --no-stdpush to disable). Required for `ay send` to deliver messages to this agent.",
|
|
84
|
+
default: true,
|
|
85
85
|
alias: ["ipc", "fifo"]
|
|
86
86
|
}).option("auto", {
|
|
87
87
|
type: "string",
|
|
@@ -227,7 +227,7 @@ function parseCliArgs(argv, supportedClis) {
|
|
|
227
227
|
useSkills: parsedArgv.useSkills,
|
|
228
228
|
swarmHint: parsedArgv.swarmHint,
|
|
229
229
|
appendPrompt: parsedArgv.appendPrompt,
|
|
230
|
-
useStdinAppend: Boolean(parsedArgv.stdpush
|
|
230
|
+
useStdinAppend: Boolean(parsedArgv.stdpush),
|
|
231
231
|
showVersion: parsedArgv.version,
|
|
232
232
|
autoYes: parsedArgv.auto !== "no",
|
|
233
233
|
idleAction: parsedArgv.idleAction,
|
|
@@ -482,7 +482,7 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
|
|
|
482
482
|
{
|
|
483
483
|
const rawArg = process.argv[2];
|
|
484
484
|
const isHelpFlag = rawArg === "-h" || rawArg === "--help";
|
|
485
|
-
const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-
|
|
485
|
+
const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-DjrOWqD9.js");
|
|
486
486
|
if (isHelpFlag && process.argv.length === 3) {
|
|
487
487
|
cmdHelp();
|
|
488
488
|
process.exit(0);
|
|
@@ -515,7 +515,7 @@ if (config.useRust) {
|
|
|
515
515
|
}
|
|
516
516
|
}
|
|
517
517
|
if (rustBinary) {
|
|
518
|
-
const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-
|
|
518
|
+
const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-nqOr59QU.js");
|
|
519
519
|
const rustArgs = buildRustArgs(process.argv, config.cli, SUPPORTED_CLIS);
|
|
520
520
|
if (config.verbose) {
|
|
521
521
|
console.log(`[rust] Using binary: ${rustBinary}`);
|
|
@@ -545,7 +545,7 @@ if (config.showVersion) {
|
|
|
545
545
|
process.exit(0);
|
|
546
546
|
}
|
|
547
547
|
if (config.appendPrompt) {
|
|
548
|
-
const { PidStore } = await import("./pidStore-
|
|
548
|
+
const { PidStore } = await import("./pidStore-iJY3JFTn.js");
|
|
549
549
|
const ipcPath = await PidStore.findActiveFifo(process.cwd());
|
|
550
550
|
if (!ipcPath) {
|
|
551
551
|
console.error("No active agent with IPC found in current directory.");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as logger } from "./logger-B9h0djqx.js";
|
|
2
|
-
import { appendFile, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
2
|
+
import { appendFile, mkdir, readFile, rename, unlink, writeFile } from "fs/promises";
|
|
3
3
|
import { homedir } from "os";
|
|
4
4
|
import path from "path";
|
|
5
5
|
import { lock } from "proper-lockfile";
|
|
@@ -48,7 +48,7 @@ async function appendGlobalPid(record) {
|
|
|
48
48
|
logger.debug("[globalPidIndex] append failed:", error);
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
-
/** Append a partial
|
|
51
|
+
/** Append a partial update by pid (status, exit_code, exit_reason, log_file). */
|
|
52
52
|
async function updateGlobalPidStatus(pid, patch) {
|
|
53
53
|
try {
|
|
54
54
|
await withLock(async () => {
|
|
@@ -138,7 +138,55 @@ async function maybeCompactGlobalPids() {
|
|
|
138
138
|
logger.debug("[globalPidIndex] compact failed:", error);
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
|
+
/** Default log retention: sessions older than this whose process is gone. */
|
|
142
|
+
const DEFAULT_RETENTION_DAYS = 7;
|
|
143
|
+
function retentionMs() {
|
|
144
|
+
const days = Number(process.env.AGENT_YES_LOG_RETENTION_DAYS);
|
|
145
|
+
return (Number.isFinite(days) && days > 0 ? days : DEFAULT_RETENTION_DAYS) * 24 * 60 * 60 * 1e3;
|
|
146
|
+
}
|
|
147
|
+
/** All on-disk log files associated with a record (raw + rendered + sidecars). */
|
|
148
|
+
function logSiblings(logFile) {
|
|
149
|
+
if (!logFile) return [];
|
|
150
|
+
const base = logFile.replace(/\.raw\.log$|\.log$/, "");
|
|
151
|
+
return [
|
|
152
|
+
`${base}.raw.log`,
|
|
153
|
+
`${base}.log`,
|
|
154
|
+
`${base}.lines.log`,
|
|
155
|
+
`${base}.debug.log`
|
|
156
|
+
];
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Index-driven retention sweep: delete the log files of sessions whose process
|
|
160
|
+
* is gone (exited or dead pid) and that started longer ago than the retention
|
|
161
|
+
* window. Because the index records absolute `log_file` paths, this reclaims
|
|
162
|
+
* logs scattered across many project `.agent-yes/` dirs from one call.
|
|
163
|
+
* Best-effort; never throws. Returns the number of files removed.
|
|
164
|
+
*/
|
|
165
|
+
async function pruneOldLogs(maxAgeMs = retentionMs()) {
|
|
166
|
+
let records;
|
|
167
|
+
try {
|
|
168
|
+
records = await readGlobalPidsRaw();
|
|
169
|
+
} catch {
|
|
170
|
+
return 0;
|
|
171
|
+
}
|
|
172
|
+
const now = Date.now();
|
|
173
|
+
let removed = 0;
|
|
174
|
+
for (const r of records) {
|
|
175
|
+
const dead = r.status === "exited" || !isProcessAlive(r.pid);
|
|
176
|
+
const old = now - (r.started_at ?? now) > maxAgeMs;
|
|
177
|
+
if (!dead || !old) continue;
|
|
178
|
+
for (const f of logSiblings(r.log_file)) try {
|
|
179
|
+
await unlink(f);
|
|
180
|
+
removed++;
|
|
181
|
+
} catch {}
|
|
182
|
+
}
|
|
183
|
+
if (removed > 0) {
|
|
184
|
+
logger.debug(`[globalPidIndex] pruned ${removed} stale log file(s)`);
|
|
185
|
+
await maybeCompactGlobalPids();
|
|
186
|
+
}
|
|
187
|
+
return removed;
|
|
188
|
+
}
|
|
141
189
|
|
|
142
190
|
//#endregion
|
|
143
|
-
export { updateGlobalPidStatus as i, maybeCompactGlobalPids as n,
|
|
144
|
-
//# sourceMappingURL=globalPidIndex-
|
|
191
|
+
export { updateGlobalPidStatus as a, readGlobalPids as i, maybeCompactGlobalPids as n, pruneOldLogs as r, appendGlobalPid as t };
|
|
192
|
+
//# sourceMappingURL=globalPidIndex-yVd3mbsV.js.map
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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-CIgbGM8b.js";
|
|
2
2
|
import "./logger-B9h0djqx.js";
|
|
3
|
-
import "./versionChecker-
|
|
4
|
-
import "./pidStore-
|
|
5
|
-
import "./globalPidIndex-
|
|
3
|
+
import "./versionChecker-B1TNIVBt.js";
|
|
4
|
+
import "./pidStore-DTzl6zeh.js";
|
|
5
|
+
import "./globalPidIndex-yVd3mbsV.js";
|
|
6
6
|
|
|
7
7
|
export { AgentContext, CLIS_CONFIG, config, agentYes as default, removeControlCharacters };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { n as logger } from "./logger-B9h0djqx.js";
|
|
2
|
-
import {
|
|
2
|
+
import { a as updateGlobalPidStatus, n as maybeCompactGlobalPids, r as pruneOldLogs, t as appendGlobalPid } from "./globalPidIndex-yVd3mbsV.js";
|
|
3
3
|
import { closeSync, existsSync, fsyncSync, openSync } from "fs";
|
|
4
4
|
import { appendFile, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
5
|
+
import { homedir } from "os";
|
|
5
6
|
import path from "path";
|
|
6
7
|
import { lock } from "proper-lockfile";
|
|
7
8
|
|
|
@@ -183,6 +184,26 @@ function generateId() {
|
|
|
183
184
|
return Date.now().toString(36) + (idCounter++).toString(36) + Math.random().toString(36).slice(2, 6);
|
|
184
185
|
}
|
|
185
186
|
|
|
187
|
+
//#endregion
|
|
188
|
+
//#region ts/agentYesHome.ts
|
|
189
|
+
/**
|
|
190
|
+
* Root directory for cross-runtime, machine-global agent-yes state:
|
|
191
|
+
* the pid index (`pids.jsonl`), FIFO/named-pipe IPC endpoints (`fifo/`),
|
|
192
|
+
* winsize signals, notes, and the serve token.
|
|
193
|
+
*
|
|
194
|
+
* Durable per-session *logs* deliberately do NOT live here — they go under
|
|
195
|
+
* `<cwd>/.agent-yes/` so they stay colocated with the project that produced
|
|
196
|
+
* them (see `PidStore`). Only ephemeral IPC + the discovery index are global,
|
|
197
|
+
* which keeps FIFOs on the local home filesystem (reliable `mkfifo`) and lets
|
|
198
|
+
* `ay ls`/`ay attach` find every agent regardless of the caller's cwd.
|
|
199
|
+
*
|
|
200
|
+
* Resolved at call time (not module load) so tests and callers can override
|
|
201
|
+
* via `$AGENT_YES_HOME` without juggling the module cache.
|
|
202
|
+
*/
|
|
203
|
+
function agentYesHome() {
|
|
204
|
+
return process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
|
|
205
|
+
}
|
|
206
|
+
|
|
186
207
|
//#endregion
|
|
187
208
|
//#region ts/pidStore.ts
|
|
188
209
|
var PidStore = class PidStore {
|
|
@@ -197,6 +218,7 @@ var PidStore = class PidStore {
|
|
|
197
218
|
await this.ensureGitignore();
|
|
198
219
|
await this.store.load();
|
|
199
220
|
await this.cleanStaleRecords();
|
|
221
|
+
pruneOldLogs().catch(() => null);
|
|
200
222
|
} catch (error) {
|
|
201
223
|
logger.warn("[pidStore] Failed to initialize:", error);
|
|
202
224
|
}
|
|
@@ -204,7 +226,7 @@ var PidStore = class PidStore {
|
|
|
204
226
|
async registerProcess({ pid, cli, args, prompt, cwd }) {
|
|
205
227
|
const now = Date.now();
|
|
206
228
|
const argsJson = JSON.stringify(args);
|
|
207
|
-
const logFile =
|
|
229
|
+
const logFile = this.getRawLogPath(pid);
|
|
208
230
|
const fifoFile = this.getFifoPath(pid);
|
|
209
231
|
const record = {
|
|
210
232
|
pid,
|
|
@@ -259,12 +281,34 @@ var PidStore = class PidStore {
|
|
|
259
281
|
getAllRecords() {
|
|
260
282
|
return this.store.getAll();
|
|
261
283
|
}
|
|
284
|
+
/** Project-local store dir: `<cwd>/.agent-yes`. Durable logs live here. */
|
|
285
|
+
getStoreDir() {
|
|
286
|
+
return this.storeDir;
|
|
287
|
+
}
|
|
262
288
|
getLogDir() {
|
|
263
289
|
return path.resolve(this.storeDir, "logs");
|
|
264
290
|
}
|
|
291
|
+
/** Raw PTY byte log (runtime), `<cwd>/.agent-yes/<pid>.raw.log`. */
|
|
292
|
+
getRawLogPath(pid) {
|
|
293
|
+
return path.resolve(this.storeDir, `${pid}.raw.log`);
|
|
294
|
+
}
|
|
295
|
+
/** Rendered plain-text log (final), `<cwd>/.agent-yes/<pid>.log`. */
|
|
296
|
+
getRenderedLogPath(pid) {
|
|
297
|
+
return path.resolve(this.storeDir, `${pid}.log`);
|
|
298
|
+
}
|
|
265
299
|
getFifoPath(pid) {
|
|
266
300
|
if (process.platform === "win32") return `\\\\.\\pipe\\agent-yes-${pid}`;
|
|
267
|
-
else return path.resolve(
|
|
301
|
+
else return path.resolve(agentYesHome(), "fifo", `${pid}.stdin`);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Repoint a session's log from the raw byte stream to its rendered text log
|
|
305
|
+
* (called on clean exit once the rendered log is durably written and the raw
|
|
306
|
+
* log has been reclaimed). Updates both the local record and global index.
|
|
307
|
+
*/
|
|
308
|
+
async markRendered(pid, renderedPath) {
|
|
309
|
+
const existing = this.store.findOne((doc) => doc.pid === pid);
|
|
310
|
+
if (existing) await this.store.updateById(existing._id, { logFile: renderedPath });
|
|
311
|
+
updateGlobalPidStatus(pid, { log_file: renderedPath }).catch(() => null);
|
|
268
312
|
}
|
|
269
313
|
async cleanStaleRecords() {
|
|
270
314
|
const activeRecords = this.store.find((r) => r.status !== "exited");
|
|
@@ -337,4 +381,4 @@ pid-db/
|
|
|
337
381
|
|
|
338
382
|
//#endregion
|
|
339
383
|
export { PidStore as t };
|
|
340
|
-
//# sourceMappingURL=pidStore-
|
|
384
|
+
//# sourceMappingURL=pidStore-DTzl6zeh.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "./logger-B9h0djqx.js";
|
|
2
|
-
import "./globalPidIndex-
|
|
2
|
+
import "./globalPidIndex-yVd3mbsV.js";
|
|
3
3
|
import "./remotes-Bjp2GYPz.js";
|
|
4
|
-
import { c as readNotes, f as snapshotStatus, l as renderRawLog, m as writeToIpc, o as listRecords, r as controlCodeFromName, u as resolveOne } from "./subcommands-
|
|
4
|
+
import { c as readNotes, f as snapshotStatus, l as renderRawLog, m as writeToIpc, o as listRecords, r as controlCodeFromName, u as resolveOne } from "./subcommands-D9wmaZ3U.js";
|
|
5
5
|
import yargs from "yargs";
|
|
6
6
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
7
7
|
import { homedir } from "os";
|
|
@@ -312,4 +312,4 @@ Options:
|
|
|
312
312
|
|
|
313
313
|
//#endregion
|
|
314
314
|
export { cmdServe };
|
|
315
|
-
//# sourceMappingURL=serve-
|
|
315
|
+
//# sourceMappingURL=serve-CixOwENN.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as readGlobalPids } from "./globalPidIndex-yVd3mbsV.js";
|
|
2
2
|
import { a as resolveRemoteSpec, i as readRemotes } from "./remotes-Bjp2GYPz.js";
|
|
3
3
|
import ms from "ms";
|
|
4
4
|
import yargs from "yargs";
|
|
@@ -162,7 +162,7 @@ async function runSubcommand(argv) {
|
|
|
162
162
|
case "restart": return await cmdRestart(rest);
|
|
163
163
|
case "note": return await cmdNote(rest);
|
|
164
164
|
case "serve": {
|
|
165
|
-
const { cmdServe } = await import("./serve-
|
|
165
|
+
const { cmdServe } = await import("./serve-CixOwENN.js");
|
|
166
166
|
return cmdServe(rest);
|
|
167
167
|
}
|
|
168
168
|
case "remote": {
|
|
@@ -1447,4 +1447,4 @@ async function cmdStatus(rest) {
|
|
|
1447
1447
|
|
|
1448
1448
|
//#endregion
|
|
1449
1449
|
export { isSubcommand as a, readNotes as c, runSubcommand as d, snapshotStatus as f, isPidAlive as i, renderRawLog as l, writeToIpc as m, cmdHelp as n, listRecords as o, stopTipForCli as p, controlCodeFromName as r, matchKeyword as s, GRACEFUL_EXIT_COMMANDS as t, resolveOne as u };
|
|
1450
|
-
//# sourceMappingURL=subcommands-
|
|
1450
|
+
//# sourceMappingURL=subcommands-D9wmaZ3U.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "./logger-B9h0djqx.js";
|
|
2
|
-
import "./globalPidIndex-
|
|
2
|
+
import "./globalPidIndex-yVd3mbsV.js";
|
|
3
3
|
import "./remotes-Bjp2GYPz.js";
|
|
4
|
-
import { a as isSubcommand, c as readNotes, d as runSubcommand, f as snapshotStatus, i as isPidAlive, l as renderRawLog, m as writeToIpc, n as cmdHelp, o as listRecords, p as stopTipForCli, r as controlCodeFromName, s as matchKeyword, t as GRACEFUL_EXIT_COMMANDS, u as resolveOne } from "./subcommands-
|
|
4
|
+
import { a as isSubcommand, c as readNotes, d as runSubcommand, f as snapshotStatus, i as isPidAlive, l as renderRawLog, m as writeToIpc, n as cmdHelp, o as listRecords, p as stopTipForCli, r as controlCodeFromName, s as matchKeyword, t as GRACEFUL_EXIT_COMMANDS, u as resolveOne } from "./subcommands-D9wmaZ3U.js";
|
|
5
5
|
|
|
6
6
|
export { cmdHelp, isSubcommand, runSubcommand };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { n as logger, t as addTransport } from "./logger-B9h0djqx.js";
|
|
2
|
-
import { r as getInstalledPackage } from "./versionChecker-
|
|
2
|
+
import { r as getInstalledPackage } from "./versionChecker-B1TNIVBt.js";
|
|
3
3
|
import { i as shouldUseLock, r as releaseLock, t as acquireLock } from "./runningLock-C22d9SRJ.js";
|
|
4
|
-
import { t as PidStore } from "./pidStore-
|
|
5
|
-
import {
|
|
4
|
+
import { t as PidStore } from "./pidStore-DTzl6zeh.js";
|
|
5
|
+
import { i as readGlobalPids } from "./globalPidIndex-yVd3mbsV.js";
|
|
6
6
|
import { arch, platform } from "process";
|
|
7
7
|
import { execSync } from "child_process";
|
|
8
8
|
import { closeSync, constants, createReadStream, existsSync, mkdirSync, openSync } from "fs";
|
|
@@ -538,13 +538,13 @@ async function sendMessage(context, message, { waitForReady = true } = {}) {
|
|
|
538
538
|
* @returns Object containing all log paths
|
|
539
539
|
*/
|
|
540
540
|
async function initializeLogPaths(pidStore, pid) {
|
|
541
|
-
const
|
|
542
|
-
await mkdir(
|
|
541
|
+
const storeDir = pidStore.getStoreDir();
|
|
542
|
+
await mkdir(storeDir, { recursive: true });
|
|
543
543
|
return {
|
|
544
|
-
logPath:
|
|
545
|
-
rawLogPath:
|
|
546
|
-
rawLinesLogPath: path.resolve(
|
|
547
|
-
debuggingLogsPath: path.resolve(
|
|
544
|
+
logPath: pidStore.getRenderedLogPath(pid),
|
|
545
|
+
rawLogPath: pidStore.getRawLogPath(pid),
|
|
546
|
+
rawLinesLogPath: path.resolve(storeDir, `${pid}.lines.log`),
|
|
547
|
+
debuggingLogsPath: path.resolve(storeDir, `${pid}.debug.log`)
|
|
548
548
|
};
|
|
549
549
|
}
|
|
550
550
|
/**
|
|
@@ -566,10 +566,17 @@ async function setupDebugLogging(debuggingLogsPath) {
|
|
|
566
566
|
* @param content Rendered content to save
|
|
567
567
|
*/
|
|
568
568
|
async function saveLogFile(logPath, content) {
|
|
569
|
-
if (!logPath) return;
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
569
|
+
if (!logPath) return false;
|
|
570
|
+
if (!content.trim()) return false;
|
|
571
|
+
try {
|
|
572
|
+
await mkdir(path.dirname(logPath), { recursive: true });
|
|
573
|
+
await writeFile(logPath, content);
|
|
574
|
+
logger.info(`Full logs saved to ${logPath}`);
|
|
575
|
+
return true;
|
|
576
|
+
} catch (error) {
|
|
577
|
+
logger.warn(`Failed to save rendered log to ${logPath}:`, error);
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
573
580
|
}
|
|
574
581
|
/**
|
|
575
582
|
* Save logs to deprecated logFile option (for backward compatibility)
|
|
@@ -1220,6 +1227,7 @@ async function agentYes({ cli, cliArgs = [], prompt, robust = true, cwd, env, ex
|
|
|
1220
1227
|
notifyWebhook("RUNNING", prompt ?? "", workingDir).catch(() => null);
|
|
1221
1228
|
const logPaths = await initializeLogPaths(pidStore, shell.pid);
|
|
1222
1229
|
await setupDebugLogging(logPaths.debuggingLogsPath);
|
|
1230
|
+
let usedAltScreen = false;
|
|
1223
1231
|
const ctx = new AgentContext({
|
|
1224
1232
|
shell,
|
|
1225
1233
|
pidStore,
|
|
@@ -1613,6 +1621,7 @@ async function agentYes({ cli, cliArgs = [], prompt, robust = true, cwd, env, ex
|
|
|
1613
1621
|
return await mkdir(path.dirname(rawLogPath), { recursive: true }).then(() => {
|
|
1614
1622
|
logger.debug(`[${cli}-yes] raw logs streaming to ${rawLogPath}`);
|
|
1615
1623
|
return f.forEach(async (chars) => {
|
|
1624
|
+
if (!usedAltScreen && /\[\?(?:1049|1047|47)h/.test(chars)) usedAltScreen = true;
|
|
1616
1625
|
await writeFile(rawLogPath, chars, { flag: "a" }).catch(() => null);
|
|
1617
1626
|
}).run();
|
|
1618
1627
|
}).catch(() => f.run());
|
|
@@ -1655,7 +1664,10 @@ async function agentYes({ cli, cliArgs = [], prompt, robust = true, cwd, env, ex
|
|
|
1655
1664
|
}
|
|
1656
1665
|
});
|
|
1657
1666
|
}).by((s) => removeControlCharactersFromStdout ? s.map((e) => removeControlCharacters(e)) : s).by(createTerminatorStream(pendingExitCode.promise)).to(fromWritable(process.stdout));
|
|
1658
|
-
await saveLogFile(ctx.logPaths.logPath, xtermProxy.render())
|
|
1667
|
+
if (await saveLogFile(ctx.logPaths.logPath, xtermProxy.render()) && !usedAltScreen && ctx.logPaths.rawLogPath && ctx.logPaths.logPath) {
|
|
1668
|
+
await unlink(ctx.logPaths.rawLogPath).catch(() => null);
|
|
1669
|
+
await pidStore.markRendered(shell.pid, ctx.logPaths.logPath).catch(() => null);
|
|
1670
|
+
}
|
|
1659
1671
|
const exitCode = await pendingExitCode.promise;
|
|
1660
1672
|
logger.info(`[${cli}-yes] ${cli} exited with code ${exitCode}`);
|
|
1661
1673
|
await pidStore.close();
|
|
@@ -1693,4 +1705,4 @@ function sleep(ms) {
|
|
|
1693
1705
|
|
|
1694
1706
|
//#endregion
|
|
1695
1707
|
export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
|
|
1696
|
-
//# sourceMappingURL=ts-
|
|
1708
|
+
//# sourceMappingURL=ts-CIgbGM8b.js.map
|
|
@@ -7,7 +7,7 @@ import { fileURLToPath } from "url";
|
|
|
7
7
|
|
|
8
8
|
//#region package.json
|
|
9
9
|
var name = "agent-yes";
|
|
10
|
-
var version = "1.
|
|
10
|
+
var version = "1.92.0";
|
|
11
11
|
|
|
12
12
|
//#endregion
|
|
13
13
|
//#region ts/versionChecker.ts
|
|
@@ -221,4 +221,4 @@ async function displayVersion() {
|
|
|
221
221
|
|
|
222
222
|
//#endregion
|
|
223
223
|
export { versionString as i, displayVersion as n, getInstalledPackage as r, checkAndAutoUpdate as t };
|
|
224
|
-
//# sourceMappingURL=versionChecker-
|
|
224
|
+
//# sourceMappingURL=versionChecker-B1TNIVBt.js.map
|
package/package.json
CHANGED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { agentYesHome } from "./agentYesHome.ts";
|
|
5
|
+
|
|
6
|
+
describe("agentYesHome", () => {
|
|
7
|
+
let original: string | undefined;
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
original = process.env.AGENT_YES_HOME;
|
|
10
|
+
});
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
if (original === undefined) delete process.env.AGENT_YES_HOME;
|
|
13
|
+
else process.env.AGENT_YES_HOME = original;
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("uses $AGENT_YES_HOME when set", () => {
|
|
17
|
+
process.env.AGENT_YES_HOME = "/custom/ay-home";
|
|
18
|
+
expect(agentYesHome()).toBe("/custom/ay-home");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("falls back to ~/.agent-yes when unset", () => {
|
|
22
|
+
delete process.env.AGENT_YES_HOME;
|
|
23
|
+
expect(agentYesHome()).toBe(path.join(homedir(), ".agent-yes"));
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { homedir } from "os";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Root directory for cross-runtime, machine-global agent-yes state:
|
|
6
|
+
* the pid index (`pids.jsonl`), FIFO/named-pipe IPC endpoints (`fifo/`),
|
|
7
|
+
* winsize signals, notes, and the serve token.
|
|
8
|
+
*
|
|
9
|
+
* Durable per-session *logs* deliberately do NOT live here — they go under
|
|
10
|
+
* `<cwd>/.agent-yes/` so they stay colocated with the project that produced
|
|
11
|
+
* them (see `PidStore`). Only ephemeral IPC + the discovery index are global,
|
|
12
|
+
* which keeps FIFOs on the local home filesystem (reliable `mkfifo`) and lets
|
|
13
|
+
* `ay ls`/`ay attach` find every agent regardless of the caller's cwd.
|
|
14
|
+
*
|
|
15
|
+
* Resolved at call time (not module load) so tests and callers can override
|
|
16
|
+
* via `$AGENT_YES_HOME` without juggling the module cache.
|
|
17
|
+
*/
|
|
18
|
+
export function agentYesHome(): string {
|
|
19
|
+
return process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
|
|
20
|
+
}
|
|
@@ -205,6 +205,146 @@ describe("globalPidIndex", () => {
|
|
|
205
205
|
await mod.maybeCompactGlobalPids(); // no throw, no error
|
|
206
206
|
});
|
|
207
207
|
|
|
208
|
+
it("updateGlobalPidStatus can repoint log_file (raw -> rendered)", async () => {
|
|
209
|
+
const mod = await loadModule();
|
|
210
|
+
await mod.appendGlobalPid({
|
|
211
|
+
pid: 4242,
|
|
212
|
+
cli: "claude",
|
|
213
|
+
prompt: null,
|
|
214
|
+
cwd: "/a",
|
|
215
|
+
log_file: "/a/.agent-yes/4242.raw.log",
|
|
216
|
+
status: "active",
|
|
217
|
+
exit_code: null,
|
|
218
|
+
exit_reason: null,
|
|
219
|
+
started_at: 1,
|
|
220
|
+
});
|
|
221
|
+
await mod.updateGlobalPidStatus(4242, { log_file: "/a/.agent-yes/4242.log" });
|
|
222
|
+
const records = await mod.readGlobalPids();
|
|
223
|
+
expect(records[0]?.log_file).toBe("/a/.agent-yes/4242.log");
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe("pruneOldLogs", () => {
|
|
227
|
+
it("deletes log siblings of old, dead sessions but keeps live/recent ones", async () => {
|
|
228
|
+
const mod = await loadModule();
|
|
229
|
+
const { mkdir, writeFile } = await import("fs/promises");
|
|
230
|
+
const { existsSync } = await import("fs");
|
|
231
|
+
const dir = path.join(testHome, "logs");
|
|
232
|
+
await mkdir(dir, { recursive: true });
|
|
233
|
+
|
|
234
|
+
// Old + dead pid: should be pruned (raw + rendered + sidecars).
|
|
235
|
+
const deadRaw = path.join(dir, "999999.raw.log");
|
|
236
|
+
const deadRendered = path.join(dir, "999999.log");
|
|
237
|
+
const deadLines = path.join(dir, "999999.lines.log");
|
|
238
|
+
for (const f of [deadRaw, deadRendered, deadLines]) await writeFile(f, "x");
|
|
239
|
+
|
|
240
|
+
// Live pid (this process), old timestamp: must be kept (still running).
|
|
241
|
+
const liveRaw = path.join(dir, `${process.pid}.raw.log`);
|
|
242
|
+
await writeFile(liveRaw, "x");
|
|
243
|
+
|
|
244
|
+
// Dead pid but recent: must be kept (inside retention window).
|
|
245
|
+
const recentRaw = path.join(dir, "999998.raw.log");
|
|
246
|
+
await writeFile(recentRaw, "x");
|
|
247
|
+
|
|
248
|
+
const oldTs = Date.now() - 30 * 24 * 60 * 60 * 1000; // 30 days ago
|
|
249
|
+
await mod.appendGlobalPid({
|
|
250
|
+
pid: 999999,
|
|
251
|
+
cli: "claude",
|
|
252
|
+
prompt: null,
|
|
253
|
+
cwd: "/a",
|
|
254
|
+
log_file: deadRaw,
|
|
255
|
+
status: "exited",
|
|
256
|
+
exit_code: 0,
|
|
257
|
+
exit_reason: null,
|
|
258
|
+
started_at: oldTs,
|
|
259
|
+
});
|
|
260
|
+
await mod.appendGlobalPid({
|
|
261
|
+
pid: process.pid,
|
|
262
|
+
cli: "claude",
|
|
263
|
+
prompt: null,
|
|
264
|
+
cwd: "/a",
|
|
265
|
+
log_file: liveRaw,
|
|
266
|
+
status: "active",
|
|
267
|
+
exit_code: null,
|
|
268
|
+
exit_reason: null,
|
|
269
|
+
started_at: oldTs,
|
|
270
|
+
});
|
|
271
|
+
await mod.appendGlobalPid({
|
|
272
|
+
pid: 999998,
|
|
273
|
+
cli: "claude",
|
|
274
|
+
prompt: null,
|
|
275
|
+
cwd: "/a",
|
|
276
|
+
log_file: recentRaw,
|
|
277
|
+
status: "exited",
|
|
278
|
+
exit_code: 0,
|
|
279
|
+
exit_reason: null,
|
|
280
|
+
started_at: Date.now(),
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const removed = await mod.pruneOldLogs();
|
|
284
|
+
|
|
285
|
+
expect(removed).toBe(3); // deadRaw + deadRendered + deadLines
|
|
286
|
+
expect(existsSync(deadRaw)).toBe(false);
|
|
287
|
+
expect(existsSync(deadRendered)).toBe(false);
|
|
288
|
+
expect(existsSync(deadLines)).toBe(false);
|
|
289
|
+
expect(existsSync(liveRaw)).toBe(true);
|
|
290
|
+
expect(existsSync(recentRaw)).toBe(true);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("returns 0 and does not throw when the index is empty", async () => {
|
|
294
|
+
const mod = await loadModule();
|
|
295
|
+
expect(await mod.pruneOldLogs()).toBe(0);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("skips records with no log_file and honors an explicit maxAge", async () => {
|
|
299
|
+
const mod = await loadModule();
|
|
300
|
+
await mod.appendGlobalPid({
|
|
301
|
+
pid: 999999,
|
|
302
|
+
cli: "claude",
|
|
303
|
+
prompt: null,
|
|
304
|
+
cwd: "/a",
|
|
305
|
+
log_file: null,
|
|
306
|
+
status: "exited",
|
|
307
|
+
exit_code: 0,
|
|
308
|
+
exit_reason: null,
|
|
309
|
+
started_at: 1,
|
|
310
|
+
});
|
|
311
|
+
// Old + dead but no log_file to delete → nothing removed, no throw.
|
|
312
|
+
expect(await mod.pruneOldLogs(1)).toBe(0);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it("respects $AGENT_YES_LOG_RETENTION_DAYS for the default window", async () => {
|
|
316
|
+
const mod = await loadModule();
|
|
317
|
+
const { mkdir, writeFile } = await import("fs/promises");
|
|
318
|
+
const { existsSync } = await import("fs");
|
|
319
|
+
const dir = path.join(testHome, "logs");
|
|
320
|
+
await mkdir(dir, { recursive: true });
|
|
321
|
+
const raw = path.join(dir, "999999.raw.log");
|
|
322
|
+
await writeFile(raw, "x");
|
|
323
|
+
const twoDaysAgo = Date.now() - 2 * 24 * 60 * 60 * 1000;
|
|
324
|
+
await mod.appendGlobalPid({
|
|
325
|
+
pid: 999999,
|
|
326
|
+
cli: "claude",
|
|
327
|
+
prompt: null,
|
|
328
|
+
cwd: "/a",
|
|
329
|
+
log_file: raw,
|
|
330
|
+
status: "exited",
|
|
331
|
+
exit_code: 0,
|
|
332
|
+
exit_reason: null,
|
|
333
|
+
started_at: twoDaysAgo,
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
const original = process.env.AGENT_YES_LOG_RETENTION_DAYS;
|
|
337
|
+
try {
|
|
338
|
+
process.env.AGENT_YES_LOG_RETENTION_DAYS = "1"; // 1-day window → 2-day-old log is stale
|
|
339
|
+
expect(await mod.pruneOldLogs()).toBe(1);
|
|
340
|
+
expect(existsSync(raw)).toBe(false);
|
|
341
|
+
} finally {
|
|
342
|
+
if (original === undefined) delete process.env.AGENT_YES_LOG_RETENTION_DAYS;
|
|
343
|
+
else process.env.AGENT_YES_LOG_RETENTION_DAYS = original;
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
|
|
208
348
|
it("skips corrupt lines without throwing", async () => {
|
|
209
349
|
const mod = await loadModule();
|
|
210
350
|
await mod.appendGlobalPid({
|
package/ts/globalPidIndex.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { appendFile, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
1
|
+
import { appendFile, mkdir, readFile, rename, unlink, writeFile } from "fs/promises";
|
|
2
2
|
import { homedir } from "os";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { lock } from "proper-lockfile";
|
|
@@ -86,10 +86,10 @@ export async function appendGlobalPid(record: GlobalPidRecord): Promise<void> {
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
/** Append a partial
|
|
89
|
+
/** Append a partial update by pid (status, exit_code, exit_reason, log_file). */
|
|
90
90
|
export async function updateGlobalPidStatus(
|
|
91
91
|
pid: number,
|
|
92
|
-
patch: Partial<Pick<GlobalPidRecord, "status" | "exit_code" | "exit_reason">>,
|
|
92
|
+
patch: Partial<Pick<GlobalPidRecord, "status" | "exit_code" | "exit_reason" | "log_file">>,
|
|
93
93
|
): Promise<void> {
|
|
94
94
|
try {
|
|
95
95
|
await withLock(async () => {
|
|
@@ -190,3 +190,55 @@ export async function maybeCompactGlobalPids(): Promise<void> {
|
|
|
190
190
|
logger.debug("[globalPidIndex] compact failed:", error);
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
|
+
|
|
194
|
+
/** Default log retention: sessions older than this whose process is gone. */
|
|
195
|
+
const DEFAULT_RETENTION_DAYS = 7;
|
|
196
|
+
|
|
197
|
+
function retentionMs(): number {
|
|
198
|
+
const days = Number(process.env.AGENT_YES_LOG_RETENTION_DAYS);
|
|
199
|
+
return (Number.isFinite(days) && days > 0 ? days : DEFAULT_RETENTION_DAYS) * 24 * 60 * 60 * 1000;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** All on-disk log files associated with a record (raw + rendered + sidecars). */
|
|
203
|
+
function logSiblings(logFile: string | null): string[] {
|
|
204
|
+
if (!logFile) return [];
|
|
205
|
+
// logFile may point at either `<pid>.raw.log` or `<pid>.log`; derive the rest.
|
|
206
|
+
const base = logFile.replace(/\.raw\.log$|\.log$/, "");
|
|
207
|
+
return [`${base}.raw.log`, `${base}.log`, `${base}.lines.log`, `${base}.debug.log`];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Index-driven retention sweep: delete the log files of sessions whose process
|
|
212
|
+
* is gone (exited or dead pid) and that started longer ago than the retention
|
|
213
|
+
* window. Because the index records absolute `log_file` paths, this reclaims
|
|
214
|
+
* logs scattered across many project `.agent-yes/` dirs from one call.
|
|
215
|
+
* Best-effort; never throws. Returns the number of files removed.
|
|
216
|
+
*/
|
|
217
|
+
export async function pruneOldLogs(maxAgeMs: number = retentionMs()): Promise<number> {
|
|
218
|
+
let records: GlobalPidRecord[];
|
|
219
|
+
try {
|
|
220
|
+
records = await readGlobalPidsRaw();
|
|
221
|
+
} catch {
|
|
222
|
+
return 0;
|
|
223
|
+
}
|
|
224
|
+
const now = Date.now();
|
|
225
|
+
let removed = 0;
|
|
226
|
+
for (const r of records) {
|
|
227
|
+
const dead = r.status === "exited" || !isProcessAlive(r.pid);
|
|
228
|
+
const old = now - (r.started_at ?? now) > maxAgeMs;
|
|
229
|
+
if (!dead || !old) continue;
|
|
230
|
+
for (const f of logSiblings(r.log_file)) {
|
|
231
|
+
try {
|
|
232
|
+
await unlink(f);
|
|
233
|
+
removed++;
|
|
234
|
+
} catch {
|
|
235
|
+
// missing / already gone — ignore
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (removed > 0) {
|
|
240
|
+
logger.debug(`[globalPidIndex] pruned ${removed} stale log file(s)`);
|
|
241
|
+
await maybeCompactGlobalPids();
|
|
242
|
+
}
|
|
243
|
+
return removed;
|
|
244
|
+
}
|
package/ts/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execaCommandSync, parseCommandString } from "execa";
|
|
2
2
|
import { fromWritable } from "from-node-stream";
|
|
3
|
-
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
3
|
+
import { mkdir, readFile, unlink, writeFile } from "fs/promises";
|
|
4
4
|
import path from "path";
|
|
5
5
|
import DIE from "phpdie";
|
|
6
6
|
import sflow from "sflow";
|
|
@@ -383,6 +383,13 @@ export default async function agentYes({
|
|
|
383
383
|
const logPaths = await initializeLogPaths(pidStore, shell.pid);
|
|
384
384
|
await setupDebugLogging(logPaths.debuggingLogsPath);
|
|
385
385
|
|
|
386
|
+
// Track whether the session ever switched to the alternate screen buffer.
|
|
387
|
+
// render() reconstructs scrollback of the normal buffer only; the alt buffer
|
|
388
|
+
// keeps no scrollback, so for alt-screen apps the rendered log would be just
|
|
389
|
+
// the final frame and must NOT replace the raw byte log. Current CLIs (ink-
|
|
390
|
+
// based claude/codex) stay on the normal buffer, but guard for the future.
|
|
391
|
+
let usedAltScreen = false;
|
|
392
|
+
|
|
386
393
|
// Create agent context
|
|
387
394
|
const ctx = new AgentContext({
|
|
388
395
|
shell,
|
|
@@ -949,6 +956,11 @@ export default async function agentYes({
|
|
|
949
956
|
logger.debug(`[${cli}-yes] raw logs streaming to ${rawLogPath}`);
|
|
950
957
|
return f
|
|
951
958
|
.forEach(async (chars) => {
|
|
959
|
+
// Detect alt-screen enter (DECSET 1049/1047/47) so we know whether
|
|
960
|
+
// the rendered log can safely stand in for this raw log on exit.
|
|
961
|
+
if (!usedAltScreen && /\[\?(?:1049|1047|47)h/.test(chars)) {
|
|
962
|
+
usedAltScreen = true;
|
|
963
|
+
}
|
|
952
964
|
await writeFile(rawLogPath, chars, { flag: "a" }).catch(() => null);
|
|
953
965
|
})
|
|
954
966
|
.run();
|
|
@@ -1054,7 +1066,17 @@ export default async function agentYes({
|
|
|
1054
1066
|
.by(createTerminatorStream(pendingExitCode.promise))
|
|
1055
1067
|
.to(fromWritable(process.stdout));
|
|
1056
1068
|
|
|
1057
|
-
await saveLogFile(ctx.logPaths.logPath, xtermProxy.render());
|
|
1069
|
+
const renderedSaved = await saveLogFile(ctx.logPaths.logPath, xtermProxy.render());
|
|
1070
|
+
|
|
1071
|
+
// The raw byte log exists for live tailing during the run. Once a non-empty
|
|
1072
|
+
// rendered log is durably written — and the session stayed on the normal
|
|
1073
|
+
// screen buffer, so the render holds the full scrollback — the raw log is
|
|
1074
|
+
// redundant: drop it and repoint the index at the rendered log. On crash /
|
|
1075
|
+
// empty render / alt-screen we keep the raw log; the startup sweep prunes it.
|
|
1076
|
+
if (renderedSaved && !usedAltScreen && ctx.logPaths.rawLogPath && ctx.logPaths.logPath) {
|
|
1077
|
+
await unlink(ctx.logPaths.rawLogPath).catch(() => null);
|
|
1078
|
+
await pidStore.markRendered(shell.pid, ctx.logPaths.logPath).catch(() => null);
|
|
1079
|
+
}
|
|
1058
1080
|
|
|
1059
1081
|
// and then get its exitcode
|
|
1060
1082
|
const exitCode = await pendingExitCode.promise;
|
package/ts/parseCliArgs.spec.ts
CHANGED
|
@@ -421,4 +421,22 @@ describe("CLI argument parsing", () => {
|
|
|
421
421
|
expect(result.queue).toBe(false);
|
|
422
422
|
expect(result.cliArgs).not.toContain("--no-queue");
|
|
423
423
|
});
|
|
424
|
+
|
|
425
|
+
it("enables stdpush IPC by default", () => {
|
|
426
|
+
// --stdpush defaults to true so `ay send` works out of the box.
|
|
427
|
+
// Without this, fifo_file ends up null in pids.jsonl and `ay send` returns 409.
|
|
428
|
+
const result = parseCliArgs(["node", "/path/to/ay", "claude"]);
|
|
429
|
+
|
|
430
|
+
expect(result.useStdinAppend).toBe(true);
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it("respects --no-stdpush opt-out only when placed before the CLI positional", () => {
|
|
434
|
+
// halt-at-non-option means agent-yes flags must precede the CLI positional.
|
|
435
|
+
const beforeCli = parseCliArgs(["node", "/path/to/ay", "--no-stdpush", "claude"]);
|
|
436
|
+
const afterCli = parseCliArgs(["node", "/path/to/ay", "claude", "--no-stdpush"]);
|
|
437
|
+
|
|
438
|
+
expect(beforeCli.useStdinAppend).toBe(false);
|
|
439
|
+
expect(afterCli.useStdinAppend).toBe(true);
|
|
440
|
+
expect(afterCli.cliArgs).toContain("--no-stdpush");
|
|
441
|
+
});
|
|
424
442
|
});
|
package/ts/parseCliArgs.ts
CHANGED
|
@@ -114,8 +114,9 @@ export function parseCliArgs(argv: string[], supportedClis?: readonly string[])
|
|
|
114
114
|
})
|
|
115
115
|
.option("stdpush", {
|
|
116
116
|
type: "boolean",
|
|
117
|
-
description:
|
|
118
|
-
|
|
117
|
+
description:
|
|
118
|
+
"Enable external input stream to push additional data to stdin (default: true; pass --no-stdpush to disable). Required for `ay send` to deliver messages to this agent.",
|
|
119
|
+
default: true,
|
|
119
120
|
alias: ["ipc", "fifo"], // backward compatibility
|
|
120
121
|
})
|
|
121
122
|
.option("auto", {
|
|
@@ -321,7 +322,7 @@ export function parseCliArgs(argv: string[], supportedClis?: readonly string[])
|
|
|
321
322
|
useSkills: parsedArgv.useSkills,
|
|
322
323
|
swarmHint: parsedArgv.swarmHint,
|
|
323
324
|
appendPrompt: parsedArgv.appendPrompt,
|
|
324
|
-
useStdinAppend: Boolean(parsedArgv.stdpush
|
|
325
|
+
useStdinAppend: Boolean(parsedArgv.stdpush), // --ipc and --fifo are yargs aliases of --stdpush; reading the canonical key ensures --no-stdpush wins over alias defaults
|
|
325
326
|
showVersion: parsedArgv.version,
|
|
326
327
|
autoYes: parsedArgv.auto !== "no", // auto-yes enabled by default, disabled with --auto=no
|
|
327
328
|
idleAction: parsedArgv.idleAction as string | undefined,
|
package/ts/pidStore.spec.ts
CHANGED
|
@@ -106,7 +106,9 @@ describe("PidStore", () => {
|
|
|
106
106
|
cwd: "/tmp",
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
// The index points at the raw log during the run (repointed to the
|
|
110
|
+
// rendered <pid>.log on clean exit via markRendered).
|
|
111
|
+
expect(rec.logFile).toContain("42.raw.log");
|
|
110
112
|
if (isWindows) {
|
|
111
113
|
expect(rec.fifoFile).toContain("agent-yes-42");
|
|
112
114
|
} else {
|
|
@@ -245,9 +247,32 @@ describe("PidStore", () => {
|
|
|
245
247
|
if (isWindows) {
|
|
246
248
|
expect(fifo).toBe(`\\\\.\\pipe\\agent-yes-42`);
|
|
247
249
|
} else {
|
|
248
|
-
|
|
250
|
+
// FIFO lives under the global home root (AGENT_YES_HOME), not the
|
|
251
|
+
// project dir — ephemeral IPC is intentionally not colocated with logs.
|
|
252
|
+
expect(fifo).toBe(path.resolve(GLOBAL_TEST_DIR, "fifo", "42.stdin"));
|
|
249
253
|
}
|
|
250
254
|
});
|
|
255
|
+
|
|
256
|
+
it("raw/rendered/store paths resolve under the project store dir", () => {
|
|
257
|
+
expect(store.getStoreDir()).toBe(path.resolve(TEST_DIR, ".agent-yes"));
|
|
258
|
+
expect(store.getRawLogPath(42)).toBe(path.resolve(TEST_DIR, ".agent-yes", "42.raw.log"));
|
|
259
|
+
expect(store.getRenderedLogPath(42)).toBe(path.resolve(TEST_DIR, ".agent-yes", "42.log"));
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
describe("markRendered", () => {
|
|
264
|
+
it("repoints the record's logFile to the rendered path", async () => {
|
|
265
|
+
await store.registerProcess({ pid: 51, cli: "claude", args: [], cwd: "/tmp" });
|
|
266
|
+
const rendered = store.getRenderedLogPath(51);
|
|
267
|
+
await store.markRendered(51, rendered);
|
|
268
|
+
|
|
269
|
+
const rec = store.getAllRecords().find((r) => r.pid === 51);
|
|
270
|
+
expect(rec?.logFile).toBe(rendered);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("is a no-op for an unknown pid", async () => {
|
|
274
|
+
await expect(store.markRendered(999123, "/whatever/999123.log")).resolves.toBeUndefined();
|
|
275
|
+
});
|
|
251
276
|
});
|
|
252
277
|
|
|
253
278
|
describe("gitignore", () => {
|
package/ts/pidStore.ts
CHANGED
|
@@ -2,9 +2,11 @@ import { mkdir, writeFile } from "fs/promises";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { logger } from "./logger.ts";
|
|
4
4
|
import { JsonlStore } from "./JsonlStore.ts";
|
|
5
|
+
import { agentYesHome } from "./agentYesHome.ts";
|
|
5
6
|
import {
|
|
6
7
|
appendGlobalPid,
|
|
7
8
|
maybeCompactGlobalPids,
|
|
9
|
+
pruneOldLogs,
|
|
8
10
|
updateGlobalPidStatus,
|
|
9
11
|
} from "./globalPidIndex.ts";
|
|
10
12
|
|
|
@@ -37,6 +39,10 @@ export class PidStore {
|
|
|
37
39
|
await this.ensureGitignore();
|
|
38
40
|
await this.store.load();
|
|
39
41
|
await this.cleanStaleRecords();
|
|
42
|
+
// Best-effort, fire-and-forget: reclaim raw/rendered logs of long-dead
|
|
43
|
+
// sessions across all projects. Index-driven so scattered pwd logs are
|
|
44
|
+
// still swept. Never block startup on it.
|
|
45
|
+
pruneOldLogs().catch(() => null);
|
|
40
46
|
} catch (error) {
|
|
41
47
|
logger.warn("[pidStore] Failed to initialize:", error);
|
|
42
48
|
}
|
|
@@ -57,7 +63,10 @@ export class PidStore {
|
|
|
57
63
|
}): Promise<PidRecord> {
|
|
58
64
|
const now = Date.now();
|
|
59
65
|
const argsJson = JSON.stringify(args);
|
|
60
|
-
|
|
66
|
+
// The index points at the file readers should tail. During the run that is
|
|
67
|
+
// the raw byte log (`ay logs`/`attach` stream it live); on clean exit it is
|
|
68
|
+
// repointed to the rendered log via `markRendered`.
|
|
69
|
+
const logFile = this.getRawLogPath(pid);
|
|
61
70
|
const fifoFile = this.getFifoPath(pid);
|
|
62
71
|
|
|
63
72
|
const record: Omit<PidRecord, "_id"> = {
|
|
@@ -138,16 +147,47 @@ export class PidStore {
|
|
|
138
147
|
return this.store.getAll();
|
|
139
148
|
}
|
|
140
149
|
|
|
150
|
+
/** Project-local store dir: `<cwd>/.agent-yes`. Durable logs live here. */
|
|
151
|
+
getStoreDir() {
|
|
152
|
+
return this.storeDir;
|
|
153
|
+
}
|
|
154
|
+
|
|
141
155
|
getLogDir() {
|
|
142
156
|
return path.resolve(this.storeDir, "logs");
|
|
143
157
|
}
|
|
144
158
|
|
|
159
|
+
/** Raw PTY byte log (runtime), `<cwd>/.agent-yes/<pid>.raw.log`. */
|
|
160
|
+
getRawLogPath(pid: number) {
|
|
161
|
+
return path.resolve(this.storeDir, `${pid}.raw.log`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Rendered plain-text log (final), `<cwd>/.agent-yes/<pid>.log`. */
|
|
165
|
+
getRenderedLogPath(pid: number) {
|
|
166
|
+
return path.resolve(this.storeDir, `${pid}.log`);
|
|
167
|
+
}
|
|
168
|
+
|
|
145
169
|
getFifoPath(pid: number) {
|
|
146
170
|
if (process.platform === "win32") {
|
|
147
171
|
return `\\\\.\\pipe\\agent-yes-${pid}`;
|
|
148
172
|
} else {
|
|
149
|
-
|
|
173
|
+
// Ephemeral IPC lives under the global home root, not the project dir:
|
|
174
|
+
// keeps the FIFO on a local filesystem (reliable mkfifo) and stable even
|
|
175
|
+
// if the project dir is moved/removed while the agent runs.
|
|
176
|
+
return path.resolve(agentYesHome(), "fifo", `${pid}.stdin`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Repoint a session's log from the raw byte stream to its rendered text log
|
|
182
|
+
* (called on clean exit once the rendered log is durably written and the raw
|
|
183
|
+
* log has been reclaimed). Updates both the local record and global index.
|
|
184
|
+
*/
|
|
185
|
+
async markRendered(pid: number, renderedPath: string): Promise<void> {
|
|
186
|
+
const existing = this.store.findOne((doc) => doc.pid === pid);
|
|
187
|
+
if (existing) {
|
|
188
|
+
await this.store.updateById(existing._id!, { logFile: renderedPath });
|
|
150
189
|
}
|
|
190
|
+
updateGlobalPidStatus(pid, { log_file: renderedPath }).catch(() => null);
|
|
151
191
|
}
|
|
152
192
|
|
|
153
193
|
async cleanStaleRecords(): Promise<void> {
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { t as CLIS_CONFIG } from "./ts-CdqzM4tm.js";
|
|
2
|
-
import "./logger-B9h0djqx.js";
|
|
3
|
-
import "./versionChecker-_0XCEwWt.js";
|
|
4
|
-
import "./pidStore-C1JXxoPi.js";
|
|
5
|
-
import "./globalPidIndex-Cr-g75QF.js";
|
|
6
|
-
|
|
7
|
-
//#region ts/SUPPORTED_CLIS.ts
|
|
8
|
-
const SUPPORTED_CLIS = Object.keys(CLIS_CONFIG);
|
|
9
|
-
|
|
10
|
-
//#endregion
|
|
11
|
-
export { SUPPORTED_CLIS };
|
|
12
|
-
//# sourceMappingURL=SUPPORTED_CLIS-C4NNZH6T.js.map
|