agent-yes 1.75.0 → 1.75.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/README.md +19 -0
- package/dist/SUPPORTED_CLIS-C2w9JqbM.js +11 -0
- package/dist/cli.js +4 -4
- package/dist/globalPidIndex-Cr-g75QF.js +144 -0
- package/dist/index.js +3 -3
- package/dist/{package-CE0J-uFT.js → package-8zpT1iww.js} +2 -2
- package/dist/pidStore-BCsY5BW3.js +5 -0
- package/dist/{pidStore-CHLHMBEM.js → pidStore-C1JXxoPi.js} +3 -3
- package/dist/{subcommands-DpOqSs-b.js → subcommands-DSm9WL-6.js} +2 -2
- package/dist/{ts-D_iRstH9.js → ts-Cuys5-PF.js} +3 -3
- package/package.json +1 -1
- package/ts/globalPidIndex.spec.ts +63 -0
- package/ts/globalPidIndex.ts +59 -10
- package/ts/pidStore.spec.ts +12 -0
- package/ts/pidStore.ts +8 -2
- package/dist/SUPPORTED_CLIS-Dzf60ENT.js +0 -11
- package/dist/globalPidIndex-DNEh8a_O.js +0 -103
- package/dist/pidStore-DR1yPY3t.js +0 -5
package/README.md
CHANGED
|
@@ -130,6 +130,25 @@ claude-yes --exit-on-idle=60s "run all tests and commit current changes"
|
|
|
130
130
|
claude-code-execute claude-yes "your task here"
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
+
### Inspect and message running agents (`cy ls / read / send`)
|
|
134
|
+
|
|
135
|
+
From any terminal you can list and interact with agents that are already
|
|
136
|
+
running on the machine — both TS- and Rust-spawned ones:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
cy ls # list all running agents
|
|
140
|
+
cy ls codex # filter (matches pid, cwd, cli, or prompt)
|
|
141
|
+
cy tail <keyword> # render last 96 lines via @xterm/headless
|
|
142
|
+
cy read <keyword> # full rendered log
|
|
143
|
+
cy send <keyword> "next: run tests" # append a prompt to that agent's stdin
|
|
144
|
+
cy send <keyword> "" --code=ctrl-c # send a Ctrl+C
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`cy` (and `ay` / `agent-yes`) writes to a shared registry at
|
|
148
|
+
`~/.agent-yes/pids.jsonl` and a per-pid FIFO at `~/.agent-yes/fifo/<pid>.stdin`,
|
|
149
|
+
so subcommands work whether the target agent is the TS or Rust runtime.
|
|
150
|
+
Detailed reference (Japanese): [`docs/cy-subcommands.md`](./docs/cy-subcommands.md).
|
|
151
|
+
|
|
133
152
|
### Docker Usage
|
|
134
153
|
|
|
135
154
|
You can run `agent-yes` in a Docker container with all AI CLI tools pre-installed.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { t as CLIS_CONFIG } from "./ts-Cuys5-PF.js";
|
|
2
|
+
import "./logger-B9h0djqx.js";
|
|
3
|
+
import "./pidStore-C1JXxoPi.js";
|
|
4
|
+
import "./globalPidIndex-Cr-g75QF.js";
|
|
5
|
+
|
|
6
|
+
//#region ts/SUPPORTED_CLIS.ts
|
|
7
|
+
const SUPPORTED_CLIS = Object.keys(CLIS_CONFIG);
|
|
8
|
+
|
|
9
|
+
//#endregion
|
|
10
|
+
export { SUPPORTED_CLIS };
|
|
11
|
+
//# sourceMappingURL=SUPPORTED_CLIS-C2w9JqbM.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 { n as version, t as name } from "./package-
|
|
3
|
+
import { n as version, t as name } from "./package-8zpT1iww.js";
|
|
4
4
|
import { argv } from "process";
|
|
5
5
|
import { execFileSync, spawn } from "child_process";
|
|
6
6
|
import ms from "ms";
|
|
@@ -635,7 +635,7 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
|
|
|
635
635
|
}
|
|
636
636
|
}
|
|
637
637
|
{
|
|
638
|
-
const { isSubcommand, runSubcommand } = await import("./subcommands-
|
|
638
|
+
const { isSubcommand, runSubcommand } = await import("./subcommands-DSm9WL-6.js");
|
|
639
639
|
if (isSubcommand(process.argv[2])) {
|
|
640
640
|
const code = await runSubcommand(process.argv);
|
|
641
641
|
process.exit(code ?? 0);
|
|
@@ -664,7 +664,7 @@ if (config.useRust) {
|
|
|
664
664
|
}
|
|
665
665
|
}
|
|
666
666
|
if (rustBinary) {
|
|
667
|
-
const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-
|
|
667
|
+
const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-C2w9JqbM.js");
|
|
668
668
|
const rustArgs = buildRustArgs(process.argv, config.cli, SUPPORTED_CLIS);
|
|
669
669
|
if (config.verbose) {
|
|
670
670
|
console.log(`[rust] Using binary: ${rustBinary}`);
|
|
@@ -694,7 +694,7 @@ if (config.showVersion) {
|
|
|
694
694
|
process.exit(0);
|
|
695
695
|
}
|
|
696
696
|
if (config.appendPrompt) {
|
|
697
|
-
const { PidStore } = await import("./pidStore-
|
|
697
|
+
const { PidStore } = await import("./pidStore-BCsY5BW3.js");
|
|
698
698
|
const ipcPath = await PidStore.findActiveFifo(process.cwd());
|
|
699
699
|
if (!ipcPath) {
|
|
700
700
|
console.error("No active agent with IPC found in current directory.");
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { n as logger } from "./logger-B9h0djqx.js";
|
|
2
|
+
import { appendFile, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { lock } from "proper-lockfile";
|
|
6
|
+
|
|
7
|
+
//#region ts/globalPidIndex.ts
|
|
8
|
+
/**
|
|
9
|
+
* Resolved at call time (not module load time) so tests and other callers
|
|
10
|
+
* can override via $AGENT_YES_HOME without juggling module-cache resets.
|
|
11
|
+
* Falls back to `~/.agent-yes` for normal user runs.
|
|
12
|
+
*/
|
|
13
|
+
function resolveGlobalDir() {
|
|
14
|
+
return process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
|
|
15
|
+
}
|
|
16
|
+
function resolveGlobalFile() {
|
|
17
|
+
return path.join(resolveGlobalDir(), "pids.jsonl");
|
|
18
|
+
}
|
|
19
|
+
async function ensureDir() {
|
|
20
|
+
await mkdir(resolveGlobalDir(), { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
async function withLock(fn) {
|
|
23
|
+
await ensureDir();
|
|
24
|
+
const file = resolveGlobalFile();
|
|
25
|
+
const dir = resolveGlobalDir();
|
|
26
|
+
let release;
|
|
27
|
+
try {
|
|
28
|
+
release = await lock(dir, {
|
|
29
|
+
lockfilePath: file + ".lock",
|
|
30
|
+
retries: {
|
|
31
|
+
retries: 5,
|
|
32
|
+
minTimeout: 50,
|
|
33
|
+
maxTimeout: 500
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
return await fn();
|
|
37
|
+
} finally {
|
|
38
|
+
await release?.();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Append one full record line. Caller must provide all required fields. */
|
|
42
|
+
async function appendGlobalPid(record) {
|
|
43
|
+
try {
|
|
44
|
+
await withLock(async () => {
|
|
45
|
+
await appendFile(resolveGlobalFile(), JSON.stringify(record) + "\n");
|
|
46
|
+
});
|
|
47
|
+
} catch (error) {
|
|
48
|
+
logger.debug("[globalPidIndex] append failed:", error);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** Append a partial status update by pid (status, exit_code, exit_reason). */
|
|
52
|
+
async function updateGlobalPidStatus(pid, patch) {
|
|
53
|
+
try {
|
|
54
|
+
await withLock(async () => {
|
|
55
|
+
const existing = (await readGlobalPidsRaw()).find((r) => r.pid === pid);
|
|
56
|
+
if (!existing) return;
|
|
57
|
+
const merged = {
|
|
58
|
+
...existing,
|
|
59
|
+
...patch
|
|
60
|
+
};
|
|
61
|
+
await appendFile(resolveGlobalFile(), JSON.stringify(merged) + "\n");
|
|
62
|
+
});
|
|
63
|
+
} catch (error) {
|
|
64
|
+
logger.debug("[globalPidIndex] updateStatus failed:", error);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Read the file once without merge logic — internal helper for status updates.
|
|
69
|
+
*/
|
|
70
|
+
async function readGlobalPidsRaw() {
|
|
71
|
+
let raw;
|
|
72
|
+
try {
|
|
73
|
+
raw = await readFile(resolveGlobalFile(), "utf-8");
|
|
74
|
+
} catch (err) {
|
|
75
|
+
if (err.code === "ENOENT") return [];
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
const merged = /* @__PURE__ */ new Map();
|
|
79
|
+
for (const line of raw.split("\n")) {
|
|
80
|
+
const trimmed = line.trim();
|
|
81
|
+
if (!trimmed) continue;
|
|
82
|
+
try {
|
|
83
|
+
const doc = JSON.parse(trimmed);
|
|
84
|
+
if (typeof doc.pid !== "number") continue;
|
|
85
|
+
const prev = merged.get(doc.pid);
|
|
86
|
+
merged.set(doc.pid, prev ? {
|
|
87
|
+
...prev,
|
|
88
|
+
...doc
|
|
89
|
+
} : doc);
|
|
90
|
+
} catch {}
|
|
91
|
+
}
|
|
92
|
+
return Array.from(merged.values());
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Read all records, last-line-per-pid wins (events get merged).
|
|
96
|
+
* Optionally filter to live processes only.
|
|
97
|
+
*/
|
|
98
|
+
async function readGlobalPids(opts = {}) {
|
|
99
|
+
const records = await readGlobalPidsRaw();
|
|
100
|
+
if (!opts.liveOnly) return records;
|
|
101
|
+
return records.filter((r) => r.status !== "exited" && isProcessAlive(r.pid));
|
|
102
|
+
}
|
|
103
|
+
function isProcessAlive(pid) {
|
|
104
|
+
try {
|
|
105
|
+
process.kill(pid, 0);
|
|
106
|
+
return true;
|
|
107
|
+
} catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const COMPACT_THRESHOLD_LINES = 500;
|
|
112
|
+
/**
|
|
113
|
+
* Best-effort compaction: rewrite the JSONL file with one line per known pid,
|
|
114
|
+
* dropping records whose pid is dead AND status is exited (those won't be
|
|
115
|
+
* referenced by `cy ls` anyway). Triggered opportunistically when the raw
|
|
116
|
+
* file grows past `COMPACT_THRESHOLD_LINES`. Safe to call unconditionally;
|
|
117
|
+
* it no-ops when the file is already small enough.
|
|
118
|
+
*/
|
|
119
|
+
async function maybeCompactGlobalPids() {
|
|
120
|
+
let raw;
|
|
121
|
+
try {
|
|
122
|
+
raw = await readFile(resolveGlobalFile(), "utf-8");
|
|
123
|
+
} catch (err) {
|
|
124
|
+
if (err.code === "ENOENT") return;
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const lineCount = raw.split("\n").filter((l) => l.trim()).length;
|
|
128
|
+
if (lineCount < COMPACT_THRESHOLD_LINES) return;
|
|
129
|
+
try {
|
|
130
|
+
await withLock(async () => {
|
|
131
|
+
const keep = (await readGlobalPidsRaw()).filter((r) => r.status !== "exited" || isProcessAlive(r.pid));
|
|
132
|
+
const tmpFile = resolveGlobalFile() + ".compact";
|
|
133
|
+
await writeFile(tmpFile, keep.map((r) => JSON.stringify(r)).join("\n") + (keep.length ? "\n" : ""));
|
|
134
|
+
await rename(tmpFile, resolveGlobalFile());
|
|
135
|
+
logger.debug(`[globalPidIndex] compacted ${lineCount} → ${keep.length} lines`);
|
|
136
|
+
});
|
|
137
|
+
} catch (error) {
|
|
138
|
+
logger.debug("[globalPidIndex] compact failed:", error);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
//#endregion
|
|
143
|
+
export { updateGlobalPidStatus as i, maybeCompactGlobalPids as n, readGlobalPids as r, appendGlobalPid as t };
|
|
144
|
+
//# sourceMappingURL=globalPidIndex-Cr-g75QF.js.map
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-
|
|
1
|
+
import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-Cuys5-PF.js";
|
|
2
2
|
import "./logger-B9h0djqx.js";
|
|
3
|
-
import "./pidStore-
|
|
4
|
-
import "./globalPidIndex-
|
|
3
|
+
import "./pidStore-C1JXxoPi.js";
|
|
4
|
+
import "./globalPidIndex-Cr-g75QF.js";
|
|
5
5
|
|
|
6
6
|
export { AgentContext, CLIS_CONFIG, config, agentYes as default, removeControlCharacters };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as logger } from "./logger-B9h0djqx.js";
|
|
2
|
-
import {
|
|
2
|
+
import { i as updateGlobalPidStatus, n as maybeCompactGlobalPids, t as appendGlobalPid } from "./globalPidIndex-Cr-g75QF.js";
|
|
3
3
|
import { closeSync, existsSync, fsyncSync, openSync } from "fs";
|
|
4
4
|
import { appendFile, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
5
5
|
import path from "path";
|
|
@@ -239,7 +239,7 @@ var PidStore = class PidStore {
|
|
|
239
239
|
exit_code: null,
|
|
240
240
|
exit_reason: null,
|
|
241
241
|
started_at: now
|
|
242
|
-
}).catch(() => null);
|
|
242
|
+
}).then(() => maybeCompactGlobalPids()).catch(() => null);
|
|
243
243
|
return result;
|
|
244
244
|
}
|
|
245
245
|
async updateStatus(pid, status, extra) {
|
|
@@ -337,4 +337,4 @@ pid-db/
|
|
|
337
337
|
|
|
338
338
|
//#endregion
|
|
339
339
|
export { PidStore as t };
|
|
340
|
-
//# sourceMappingURL=pidStore-
|
|
340
|
+
//# sourceMappingURL=pidStore-C1JXxoPi.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "./logger-B9h0djqx.js";
|
|
2
|
-
import {
|
|
2
|
+
import { r as readGlobalPids } from "./globalPidIndex-Cr-g75QF.js";
|
|
3
3
|
import { readFile, stat } from "fs/promises";
|
|
4
4
|
import { homedir } from "os";
|
|
5
5
|
import path from "path";
|
|
@@ -388,4 +388,4 @@ async function writeToIpc(ipcPath, payload) {
|
|
|
388
388
|
|
|
389
389
|
//#endregion
|
|
390
390
|
export { isSubcommand, runSubcommand };
|
|
391
|
-
//# sourceMappingURL=subcommands-
|
|
391
|
+
//# sourceMappingURL=subcommands-DSm9WL-6.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { n as logger, t as addTransport } from "./logger-B9h0djqx.js";
|
|
2
|
-
import { n as version } from "./package-
|
|
2
|
+
import { n as version } from "./package-8zpT1iww.js";
|
|
3
3
|
import { i as shouldUseLock, r as releaseLock, t as acquireLock } from "./runningLock-DQWJSptq.js";
|
|
4
|
-
import { t as PidStore } from "./pidStore-
|
|
4
|
+
import { t as PidStore } from "./pidStore-C1JXxoPi.js";
|
|
5
5
|
import { arch, platform } from "process";
|
|
6
6
|
import { execSync } from "child_process";
|
|
7
7
|
import { closeSync, constants, createReadStream, existsSync, mkdirSync, openSync } from "fs";
|
|
@@ -1679,4 +1679,4 @@ function sleep(ms) {
|
|
|
1679
1679
|
|
|
1680
1680
|
//#endregion
|
|
1681
1681
|
export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
|
|
1682
|
-
//# sourceMappingURL=ts-
|
|
1682
|
+
//# sourceMappingURL=ts-Cuys5-PF.js.map
|
package/package.json
CHANGED
|
@@ -142,6 +142,69 @@ describe("globalPidIndex", () => {
|
|
|
142
142
|
expect(records).toEqual([]);
|
|
143
143
|
});
|
|
144
144
|
|
|
145
|
+
it("maybeCompactGlobalPids no-ops when below threshold", async () => {
|
|
146
|
+
const mod = await loadModule();
|
|
147
|
+
await mod.appendGlobalPid({
|
|
148
|
+
pid: 1234,
|
|
149
|
+
cli: "claude",
|
|
150
|
+
prompt: null,
|
|
151
|
+
cwd: "/a",
|
|
152
|
+
log_file: null,
|
|
153
|
+
status: "active",
|
|
154
|
+
exit_code: null,
|
|
155
|
+
exit_reason: null,
|
|
156
|
+
started_at: 1,
|
|
157
|
+
});
|
|
158
|
+
const before = (await import("fs/promises")).readFile;
|
|
159
|
+
const beforeContent = await before(mod.getGlobalPidIndexPath(), "utf-8");
|
|
160
|
+
await mod.maybeCompactGlobalPids();
|
|
161
|
+
const afterContent = await before(mod.getGlobalPidIndexPath(), "utf-8");
|
|
162
|
+
expect(afterContent).toBe(beforeContent);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("maybeCompactGlobalPids collapses event spam to one line per pid", async () => {
|
|
166
|
+
const mod = await loadModule();
|
|
167
|
+
// Emit > 500 status events across two pids (one alive, one will be exited+dead)
|
|
168
|
+
for (let i = 0; i < 260; i++) {
|
|
169
|
+
await mod.appendGlobalPid({
|
|
170
|
+
pid: process.pid,
|
|
171
|
+
cli: "claude",
|
|
172
|
+
prompt: null,
|
|
173
|
+
cwd: "/a",
|
|
174
|
+
log_file: null,
|
|
175
|
+
status: "active",
|
|
176
|
+
exit_code: null,
|
|
177
|
+
exit_reason: null,
|
|
178
|
+
started_at: 1,
|
|
179
|
+
});
|
|
180
|
+
await mod.appendGlobalPid({
|
|
181
|
+
pid: 999999, // dead
|
|
182
|
+
cli: "codex",
|
|
183
|
+
prompt: null,
|
|
184
|
+
cwd: "/b",
|
|
185
|
+
log_file: null,
|
|
186
|
+
status: "exited",
|
|
187
|
+
exit_code: 0,
|
|
188
|
+
exit_reason: "done",
|
|
189
|
+
started_at: 1,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
const fs = await import("fs/promises");
|
|
193
|
+
const before = (await fs.readFile(mod.getGlobalPidIndexPath(), "utf-8")).split("\n").length;
|
|
194
|
+
await mod.maybeCompactGlobalPids();
|
|
195
|
+
const after = (await fs.readFile(mod.getGlobalPidIndexPath(), "utf-8")).split("\n").length;
|
|
196
|
+
// Compaction must have shrunk the file dramatically and dropped the
|
|
197
|
+
// dead-and-exited pid 999999 entirely.
|
|
198
|
+
expect(after).toBeLessThan(before / 10);
|
|
199
|
+
const records = await mod.readGlobalPids();
|
|
200
|
+
expect(records.map((r) => r.pid)).toEqual([process.pid]);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("maybeCompactGlobalPids on missing file is a noop", async () => {
|
|
204
|
+
const mod = await loadModule();
|
|
205
|
+
await mod.maybeCompactGlobalPids(); // no throw, no error
|
|
206
|
+
});
|
|
207
|
+
|
|
145
208
|
it("skips corrupt lines without throwing", async () => {
|
|
146
209
|
const mod = await loadModule();
|
|
147
210
|
await mod.appendGlobalPid({
|
package/ts/globalPidIndex.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { appendFile, mkdir, readFile } from "fs/promises";
|
|
1
|
+
import { appendFile, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
2
2
|
import { homedir } from "os";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { lock } from "proper-lockfile";
|
|
@@ -38,23 +38,35 @@ export interface GlobalPidRecord {
|
|
|
38
38
|
started_at: number;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Resolved at call time (not module load time) so tests and other callers
|
|
43
|
+
* can override via $AGENT_YES_HOME without juggling module-cache resets.
|
|
44
|
+
* Falls back to `~/.agent-yes` for normal user runs.
|
|
45
|
+
*/
|
|
46
|
+
function resolveGlobalDir(): string {
|
|
47
|
+
return process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function resolveGlobalFile(): string {
|
|
51
|
+
return path.join(resolveGlobalDir(), "pids.jsonl");
|
|
52
|
+
}
|
|
43
53
|
|
|
44
54
|
export function getGlobalPidIndexPath(): string {
|
|
45
|
-
return
|
|
55
|
+
return resolveGlobalFile();
|
|
46
56
|
}
|
|
47
57
|
|
|
48
58
|
async function ensureDir() {
|
|
49
|
-
await mkdir(
|
|
59
|
+
await mkdir(resolveGlobalDir(), { recursive: true });
|
|
50
60
|
}
|
|
51
61
|
|
|
52
62
|
async function withLock<R>(fn: () => Promise<R>): Promise<R> {
|
|
53
63
|
await ensureDir();
|
|
64
|
+
const file = resolveGlobalFile();
|
|
65
|
+
const dir = resolveGlobalDir();
|
|
54
66
|
let release: (() => Promise<void>) | undefined;
|
|
55
67
|
try {
|
|
56
|
-
release = await lock(
|
|
57
|
-
lockfilePath:
|
|
68
|
+
release = await lock(dir, {
|
|
69
|
+
lockfilePath: file + ".lock",
|
|
58
70
|
retries: { retries: 5, minTimeout: 50, maxTimeout: 500 },
|
|
59
71
|
});
|
|
60
72
|
return await fn();
|
|
@@ -67,7 +79,7 @@ async function withLock<R>(fn: () => Promise<R>): Promise<R> {
|
|
|
67
79
|
export async function appendGlobalPid(record: GlobalPidRecord): Promise<void> {
|
|
68
80
|
try {
|
|
69
81
|
await withLock(async () => {
|
|
70
|
-
await appendFile(
|
|
82
|
+
await appendFile(resolveGlobalFile(), JSON.stringify(record) + "\n");
|
|
71
83
|
});
|
|
72
84
|
} catch (error) {
|
|
73
85
|
logger.debug("[globalPidIndex] append failed:", error);
|
|
@@ -85,7 +97,7 @@ export async function updateGlobalPidStatus(
|
|
|
85
97
|
const existing = current.find((r) => r.pid === pid);
|
|
86
98
|
if (!existing) return; // unknown pid — nothing to update
|
|
87
99
|
const merged: GlobalPidRecord = { ...existing, ...patch };
|
|
88
|
-
await appendFile(
|
|
100
|
+
await appendFile(resolveGlobalFile(), JSON.stringify(merged) + "\n");
|
|
89
101
|
});
|
|
90
102
|
} catch (error) {
|
|
91
103
|
logger.debug("[globalPidIndex] updateStatus failed:", error);
|
|
@@ -98,7 +110,7 @@ export async function updateGlobalPidStatus(
|
|
|
98
110
|
async function readGlobalPidsRaw(): Promise<GlobalPidRecord[]> {
|
|
99
111
|
let raw: string;
|
|
100
112
|
try {
|
|
101
|
-
raw = await readFile(
|
|
113
|
+
raw = await readFile(resolveGlobalFile(), "utf-8");
|
|
102
114
|
} catch (err: any) {
|
|
103
115
|
if (err.code === "ENOENT") return [];
|
|
104
116
|
throw err;
|
|
@@ -141,3 +153,40 @@ function isProcessAlive(pid: number): boolean {
|
|
|
141
153
|
return false;
|
|
142
154
|
}
|
|
143
155
|
}
|
|
156
|
+
|
|
157
|
+
const COMPACT_THRESHOLD_LINES = 500; // raw events; one merged record per pid
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Best-effort compaction: rewrite the JSONL file with one line per known pid,
|
|
161
|
+
* dropping records whose pid is dead AND status is exited (those won't be
|
|
162
|
+
* referenced by `cy ls` anyway). Triggered opportunistically when the raw
|
|
163
|
+
* file grows past `COMPACT_THRESHOLD_LINES`. Safe to call unconditionally;
|
|
164
|
+
* it no-ops when the file is already small enough.
|
|
165
|
+
*/
|
|
166
|
+
export async function maybeCompactGlobalPids(): Promise<void> {
|
|
167
|
+
let raw: string;
|
|
168
|
+
try {
|
|
169
|
+
raw = await readFile(resolveGlobalFile(), "utf-8");
|
|
170
|
+
} catch (err: any) {
|
|
171
|
+
if (err.code === "ENOENT") return;
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const lineCount = raw.split("\n").filter((l) => l.trim()).length;
|
|
175
|
+
if (lineCount < COMPACT_THRESHOLD_LINES) return;
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
await withLock(async () => {
|
|
179
|
+
const merged = await readGlobalPidsRaw();
|
|
180
|
+
// Drop dead-and-exited entries; keep dead-but-not-yet-exited so a later
|
|
181
|
+
// status-update from elsewhere can still be matched against them.
|
|
182
|
+
const keep = merged.filter((r) => r.status !== "exited" || isProcessAlive(r.pid));
|
|
183
|
+
const tmpFile = resolveGlobalFile() + ".compact";
|
|
184
|
+
const content = keep.map((r) => JSON.stringify(r)).join("\n") + (keep.length ? "\n" : "");
|
|
185
|
+
await writeFile(tmpFile, content);
|
|
186
|
+
await rename(tmpFile, resolveGlobalFile());
|
|
187
|
+
logger.debug(`[globalPidIndex] compacted ${lineCount} → ${keep.length} lines`);
|
|
188
|
+
});
|
|
189
|
+
} catch (error) {
|
|
190
|
+
logger.debug("[globalPidIndex] compact failed:", error);
|
|
191
|
+
}
|
|
192
|
+
}
|
package/ts/pidStore.spec.ts
CHANGED
|
@@ -10,6 +10,11 @@ const TEST_DIR = isWindows
|
|
|
10
10
|
|
|
11
11
|
describe("PidStore", () => {
|
|
12
12
|
let store: PidStore;
|
|
13
|
+
// Isolate the cross-runtime global pid index too — without this, the
|
|
14
|
+
// synthetic pid=12345 records leak into the user's real
|
|
15
|
+
// ~/.agent-yes/pids.jsonl via the mirror writer wired into PidStore.
|
|
16
|
+
const GLOBAL_TEST_DIR = path.join(TEST_DIR, "global");
|
|
17
|
+
let originalAgentYesHome: string | undefined;
|
|
13
18
|
|
|
14
19
|
beforeEach(async () => {
|
|
15
20
|
try {
|
|
@@ -17,12 +22,19 @@ describe("PidStore", () => {
|
|
|
17
22
|
} catch {
|
|
18
23
|
// ignore cleanup failures (e.g. Windows lock files from previous test)
|
|
19
24
|
}
|
|
25
|
+
originalAgentYesHome = process.env.AGENT_YES_HOME;
|
|
26
|
+
process.env.AGENT_YES_HOME = GLOBAL_TEST_DIR;
|
|
20
27
|
store = new PidStore(TEST_DIR);
|
|
21
28
|
await store.init();
|
|
22
29
|
});
|
|
23
30
|
|
|
24
31
|
afterEach(async () => {
|
|
25
32
|
await store.close();
|
|
33
|
+
if (originalAgentYesHome === undefined) {
|
|
34
|
+
delete process.env.AGENT_YES_HOME;
|
|
35
|
+
} else {
|
|
36
|
+
process.env.AGENT_YES_HOME = originalAgentYesHome;
|
|
37
|
+
}
|
|
26
38
|
try {
|
|
27
39
|
await rm(TEST_DIR, { recursive: true, force: true });
|
|
28
40
|
} catch {
|
package/ts/pidStore.ts
CHANGED
|
@@ -2,7 +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 {
|
|
5
|
+
import {
|
|
6
|
+
appendGlobalPid,
|
|
7
|
+
maybeCompactGlobalPids,
|
|
8
|
+
updateGlobalPidStatus,
|
|
9
|
+
} from "./globalPidIndex.ts";
|
|
6
10
|
|
|
7
11
|
export interface PidRecord {
|
|
8
12
|
_id?: string;
|
|
@@ -100,7 +104,9 @@ export class PidStore {
|
|
|
100
104
|
exit_code: null,
|
|
101
105
|
exit_reason: null,
|
|
102
106
|
started_at: now,
|
|
103
|
-
})
|
|
107
|
+
})
|
|
108
|
+
.then(() => maybeCompactGlobalPids())
|
|
109
|
+
.catch(() => null);
|
|
104
110
|
|
|
105
111
|
return result;
|
|
106
112
|
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { t as CLIS_CONFIG } from "./ts-D_iRstH9.js";
|
|
2
|
-
import "./logger-B9h0djqx.js";
|
|
3
|
-
import "./pidStore-CHLHMBEM.js";
|
|
4
|
-
import "./globalPidIndex-DNEh8a_O.js";
|
|
5
|
-
|
|
6
|
-
//#region ts/SUPPORTED_CLIS.ts
|
|
7
|
-
const SUPPORTED_CLIS = Object.keys(CLIS_CONFIG);
|
|
8
|
-
|
|
9
|
-
//#endregion
|
|
10
|
-
export { SUPPORTED_CLIS };
|
|
11
|
-
//# sourceMappingURL=SUPPORTED_CLIS-Dzf60ENT.js.map
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { n as logger } from "./logger-B9h0djqx.js";
|
|
2
|
-
import { appendFile, mkdir, readFile } from "fs/promises";
|
|
3
|
-
import { homedir } from "os";
|
|
4
|
-
import path from "path";
|
|
5
|
-
import { lock } from "proper-lockfile";
|
|
6
|
-
|
|
7
|
-
//#region ts/globalPidIndex.ts
|
|
8
|
-
const GLOBAL_DIR = path.join(homedir(), ".agent-yes");
|
|
9
|
-
const GLOBAL_FILE = path.join(GLOBAL_DIR, "pids.jsonl");
|
|
10
|
-
async function ensureDir() {
|
|
11
|
-
await mkdir(GLOBAL_DIR, { recursive: true });
|
|
12
|
-
}
|
|
13
|
-
async function withLock(fn) {
|
|
14
|
-
await ensureDir();
|
|
15
|
-
let release;
|
|
16
|
-
try {
|
|
17
|
-
release = await lock(GLOBAL_DIR, {
|
|
18
|
-
lockfilePath: GLOBAL_FILE + ".lock",
|
|
19
|
-
retries: {
|
|
20
|
-
retries: 5,
|
|
21
|
-
minTimeout: 50,
|
|
22
|
-
maxTimeout: 500
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
return await fn();
|
|
26
|
-
} finally {
|
|
27
|
-
await release?.();
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
/** Append one full record line. Caller must provide all required fields. */
|
|
31
|
-
async function appendGlobalPid(record) {
|
|
32
|
-
try {
|
|
33
|
-
await withLock(async () => {
|
|
34
|
-
await appendFile(GLOBAL_FILE, JSON.stringify(record) + "\n");
|
|
35
|
-
});
|
|
36
|
-
} catch (error) {
|
|
37
|
-
logger.debug("[globalPidIndex] append failed:", error);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
/** Append a partial status update by pid (status, exit_code, exit_reason). */
|
|
41
|
-
async function updateGlobalPidStatus(pid, patch) {
|
|
42
|
-
try {
|
|
43
|
-
await withLock(async () => {
|
|
44
|
-
const existing = (await readGlobalPidsRaw()).find((r) => r.pid === pid);
|
|
45
|
-
if (!existing) return;
|
|
46
|
-
const merged = {
|
|
47
|
-
...existing,
|
|
48
|
-
...patch
|
|
49
|
-
};
|
|
50
|
-
await appendFile(GLOBAL_FILE, JSON.stringify(merged) + "\n");
|
|
51
|
-
});
|
|
52
|
-
} catch (error) {
|
|
53
|
-
logger.debug("[globalPidIndex] updateStatus failed:", error);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Read the file once without merge logic — internal helper for status updates.
|
|
58
|
-
*/
|
|
59
|
-
async function readGlobalPidsRaw() {
|
|
60
|
-
let raw;
|
|
61
|
-
try {
|
|
62
|
-
raw = await readFile(GLOBAL_FILE, "utf-8");
|
|
63
|
-
} catch (err) {
|
|
64
|
-
if (err.code === "ENOENT") return [];
|
|
65
|
-
throw err;
|
|
66
|
-
}
|
|
67
|
-
const merged = /* @__PURE__ */ new Map();
|
|
68
|
-
for (const line of raw.split("\n")) {
|
|
69
|
-
const trimmed = line.trim();
|
|
70
|
-
if (!trimmed) continue;
|
|
71
|
-
try {
|
|
72
|
-
const doc = JSON.parse(trimmed);
|
|
73
|
-
if (typeof doc.pid !== "number") continue;
|
|
74
|
-
const prev = merged.get(doc.pid);
|
|
75
|
-
merged.set(doc.pid, prev ? {
|
|
76
|
-
...prev,
|
|
77
|
-
...doc
|
|
78
|
-
} : doc);
|
|
79
|
-
} catch {}
|
|
80
|
-
}
|
|
81
|
-
return Array.from(merged.values());
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Read all records, last-line-per-pid wins (events get merged).
|
|
85
|
-
* Optionally filter to live processes only.
|
|
86
|
-
*/
|
|
87
|
-
async function readGlobalPids(opts = {}) {
|
|
88
|
-
const records = await readGlobalPidsRaw();
|
|
89
|
-
if (!opts.liveOnly) return records;
|
|
90
|
-
return records.filter((r) => r.status !== "exited" && isProcessAlive(r.pid));
|
|
91
|
-
}
|
|
92
|
-
function isProcessAlive(pid) {
|
|
93
|
-
try {
|
|
94
|
-
process.kill(pid, 0);
|
|
95
|
-
return true;
|
|
96
|
-
} catch {
|
|
97
|
-
return false;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
//#endregion
|
|
102
|
-
export { readGlobalPids as n, updateGlobalPidStatus as r, appendGlobalPid as t };
|
|
103
|
-
//# sourceMappingURL=globalPidIndex-DNEh8a_O.js.map
|