agent-yes 1.73.0 → 1.74.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.
@@ -0,0 +1,51 @@
1
+ //#region ts/logger.ts
2
+ let _inner = null;
3
+ let _initPromise = null;
4
+ const _queue = [];
5
+ function init() {
6
+ if (_initPromise) return _initPromise;
7
+ _initPromise = import("winston").then(({ default: winston }) => {
8
+ const logFormat = winston.format.combine(winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), winston.format.printf(({ timestamp, level, message, ...meta }) => {
9
+ return `${timestamp} [${level}]: ${message}${Object.keys(meta).length ? ` ${JSON.stringify(meta)}` : ""}`;
10
+ }));
11
+ _inner = winston.createLogger({
12
+ level: process.env.VERBOSE ? "debug" : "info",
13
+ format: logFormat,
14
+ transports: [new winston.transports.Console({ format: winston.format.combine(winston.format.colorize(), logFormat) })],
15
+ silent: false
16
+ });
17
+ for (const { level, msg, meta } of _queue.splice(0)) _inner[level](msg, ...meta);
18
+ });
19
+ return _initPromise;
20
+ }
21
+ function makeMethod(level) {
22
+ return (msg, ...meta) => {
23
+ if (_inner) _inner[level](msg, ...meta);
24
+ else {
25
+ _queue.push({
26
+ level,
27
+ msg,
28
+ meta
29
+ });
30
+ init().catch((e) => console.error("[logger] Failed to load winston:", e));
31
+ }
32
+ };
33
+ }
34
+ /** Add a winston transport. Awaits logger initialization first. */
35
+ async function addTransport(transport) {
36
+ await init();
37
+ _inner.add(transport);
38
+ }
39
+ const logger = {
40
+ error: makeMethod("error"),
41
+ warn: makeMethod("warn"),
42
+ info: makeMethod("info"),
43
+ http: makeMethod("http"),
44
+ verbose: makeMethod("verbose"),
45
+ debug: makeMethod("debug"),
46
+ silly: makeMethod("silly")
47
+ };
48
+
49
+ //#endregion
50
+ export { logger as n, addTransport as t };
51
+ //# sourceMappingURL=logger-B9h0djqx.js.map
@@ -0,0 +1,7 @@
1
+ //#region package.json
2
+ var name = "agent-yes";
3
+ var version = "1.73.2";
4
+
5
+ //#endregion
6
+ export { version as n, name as t };
7
+ //# sourceMappingURL=package-Bn0B_jWZ.js.map
@@ -0,0 +1,340 @@
1
+ import { n as logger } from "./logger-B9h0djqx.js";
2
+ import { r as updateGlobalPidStatus, t as appendGlobalPid } from "./globalPidIndex-DNEh8a_O.js";
3
+ import { closeSync, existsSync, fsyncSync, openSync } from "fs";
4
+ import { appendFile, mkdir, readFile, rename, writeFile } from "fs/promises";
5
+ import path from "path";
6
+ import { lock } from "proper-lockfile";
7
+
8
+ //#region ts/JsonlStore.ts
9
+ /**
10
+ * A lightweight NeDB-style JSONL persistence layer.
11
+ *
12
+ * - Append-only writes (one JSON object per line)
13
+ * - Same `_id` → last line wins (fields merged)
14
+ * - `$$deleted` lines act as tombstones
15
+ * - Crash recovery: skip partial last line, recover from temp file
16
+ * - Multi-process safe via proper-lockfile (reads don't need lock)
17
+ * - Compact on close: deduplicates into clean file via atomic rename
18
+ */
19
+ var JsonlStore = class {
20
+ filePath;
21
+ tempPath;
22
+ docs = /* @__PURE__ */ new Map();
23
+ constructor(filePath) {
24
+ this.filePath = filePath;
25
+ this.tempPath = filePath + "~";
26
+ }
27
+ /**
28
+ * Load all records from the JSONL file. No lock needed.
29
+ * Handles crash recovery: partial last line skipped, temp file recovery.
30
+ */
31
+ async load() {
32
+ await mkdir(path.dirname(this.filePath), { recursive: true });
33
+ if (!existsSync(this.filePath) && existsSync(this.tempPath)) {
34
+ logger.debug("[JsonlStore] Recovering from temp file");
35
+ await rename(this.tempPath, this.filePath);
36
+ }
37
+ this.docs = /* @__PURE__ */ new Map();
38
+ let raw = "";
39
+ try {
40
+ raw = await readFile(this.filePath, "utf-8");
41
+ } catch (err) {
42
+ if (err.code === "ENOENT") return this.docs;
43
+ throw err;
44
+ }
45
+ const lines = raw.split("\n");
46
+ let corruptCount = 0;
47
+ for (const line of lines) {
48
+ const trimmed = line.trim();
49
+ if (!trimmed) continue;
50
+ try {
51
+ const doc = JSON.parse(trimmed);
52
+ if (!doc._id) continue;
53
+ if (doc.$$deleted) this.docs.delete(doc._id);
54
+ else {
55
+ const existing = this.docs.get(doc._id);
56
+ if (existing) this.docs.set(doc._id, {
57
+ ...existing,
58
+ ...doc
59
+ });
60
+ else this.docs.set(doc._id, doc);
61
+ }
62
+ } catch {
63
+ corruptCount++;
64
+ }
65
+ }
66
+ if (corruptCount > 0) logger.debug(`[JsonlStore] Skipped ${corruptCount} corrupt line(s) in ${this.filePath}`);
67
+ return this.docs;
68
+ }
69
+ /** Get all live documents. */
70
+ getAll() {
71
+ return Array.from(this.docs.values());
72
+ }
73
+ /** Find a document by _id. */
74
+ getById(id) {
75
+ return this.docs.get(id);
76
+ }
77
+ /** Find documents matching a predicate. */
78
+ find(predicate) {
79
+ return this.getAll().filter(predicate);
80
+ }
81
+ /** Find first document matching a predicate. */
82
+ findOne(predicate) {
83
+ for (const doc of this.docs.values()) if (predicate(doc)) return doc;
84
+ }
85
+ /**
86
+ * Append a new document. Acquires lock.
87
+ * If no _id is provided, one is generated.
88
+ */
89
+ async append(doc) {
90
+ const id = doc._id || generateId();
91
+ const { _id: _, ...rest } = doc;
92
+ const fullDoc = {
93
+ _id: id,
94
+ ...rest
95
+ };
96
+ return await this.withLock(async () => {
97
+ await appendFile(this.filePath, JSON.stringify(fullDoc) + "\n");
98
+ const existing = this.docs.get(fullDoc._id);
99
+ if (existing) this.docs.set(fullDoc._id, {
100
+ ...existing,
101
+ ...fullDoc
102
+ });
103
+ else this.docs.set(fullDoc._id, fullDoc);
104
+ return fullDoc;
105
+ });
106
+ }
107
+ /**
108
+ * Update a document by _id. Appends a merge line. Acquires lock.
109
+ */
110
+ async updateById(id, patch) {
111
+ await this.withLock(async () => {
112
+ const line = {
113
+ _id: id,
114
+ ...patch
115
+ };
116
+ await appendFile(this.filePath, JSON.stringify(line) + "\n");
117
+ const existing = this.docs.get(id);
118
+ if (existing) this.docs.set(id, {
119
+ ...existing,
120
+ ...patch
121
+ });
122
+ });
123
+ }
124
+ /**
125
+ * Delete a document by _id. Appends a tombstone. Acquires lock.
126
+ */
127
+ async deleteById(id) {
128
+ await this.withLock(async () => {
129
+ const tombstone = {
130
+ _id: id,
131
+ $$deleted: true
132
+ };
133
+ await appendFile(this.filePath, JSON.stringify(tombstone) + "\n");
134
+ this.docs.delete(id);
135
+ });
136
+ }
137
+ /**
138
+ * Compact the file: deduplicate entries, remove tombstones.
139
+ * Writes to temp file, fsyncs, then atomic renames.
140
+ * Acquires lock.
141
+ */
142
+ async compact() {
143
+ const lines = Array.from(this.docs.values()).map((doc) => {
144
+ const { _id, $$deleted: _$$deleted, ...rest } = doc;
145
+ return JSON.stringify({
146
+ _id,
147
+ ...rest
148
+ });
149
+ }).join("\n");
150
+ const content = lines ? lines + "\n" : "";
151
+ try {
152
+ await this.withLock(async () => {
153
+ await writeFile(this.tempPath, content);
154
+ const fd = openSync(this.tempPath, "r");
155
+ fsyncSync(fd);
156
+ closeSync(fd);
157
+ await rename(this.tempPath, this.filePath);
158
+ });
159
+ } catch {
160
+ await writeFile(this.filePath, content);
161
+ }
162
+ }
163
+ async withLock(fn) {
164
+ const dir = path.dirname(this.filePath);
165
+ let release;
166
+ try {
167
+ release = await lock(dir, {
168
+ lockfilePath: this.filePath + ".lock",
169
+ retries: {
170
+ retries: 5,
171
+ minTimeout: 50,
172
+ maxTimeout: 500
173
+ }
174
+ });
175
+ return await fn();
176
+ } finally {
177
+ if (release) await release();
178
+ }
179
+ }
180
+ };
181
+ let idCounter = 0;
182
+ function generateId() {
183
+ return Date.now().toString(36) + (idCounter++).toString(36) + Math.random().toString(36).slice(2, 6);
184
+ }
185
+
186
+ //#endregion
187
+ //#region ts/pidStore.ts
188
+ var PidStore = class PidStore {
189
+ storeDir;
190
+ store;
191
+ constructor(workingDir) {
192
+ this.storeDir = path.resolve(workingDir, ".agent-yes");
193
+ this.store = new JsonlStore(path.join(this.storeDir, "pid-records.jsonl"));
194
+ }
195
+ async init() {
196
+ try {
197
+ await this.ensureGitignore();
198
+ await this.store.load();
199
+ await this.cleanStaleRecords();
200
+ } catch (error) {
201
+ logger.warn("[pidStore] Failed to initialize:", error);
202
+ }
203
+ }
204
+ async registerProcess({ pid, cli, args, prompt, cwd }) {
205
+ const now = Date.now();
206
+ const argsJson = JSON.stringify(args);
207
+ const logFile = path.resolve(this.getLogDir(), `${pid}.log`);
208
+ const fifoFile = this.getFifoPath(pid);
209
+ const record = {
210
+ pid,
211
+ cli,
212
+ args: argsJson,
213
+ prompt,
214
+ cwd,
215
+ logFile,
216
+ fifoFile,
217
+ status: "active",
218
+ exitReason: "",
219
+ startedAt: now
220
+ };
221
+ const existing = this.store.findOne((doc) => doc.pid === pid);
222
+ if (existing) await this.store.updateById(existing._id, record);
223
+ else await this.store.append(record);
224
+ const result = this.store.findOne((doc) => doc.pid === pid);
225
+ if (!result) {
226
+ const allRecords = this.store.getAll();
227
+ logger.error(`[pidStore] Failed to find record for PID ${pid}. All records:`, allRecords);
228
+ throw new Error(`Failed to register process ${pid}`);
229
+ }
230
+ logger.debug(`[pidStore] Registered process ${pid}`);
231
+ appendGlobalPid({
232
+ pid,
233
+ cli,
234
+ prompt: prompt ?? null,
235
+ cwd,
236
+ log_file: logFile,
237
+ fifo_file: fifoFile,
238
+ status: "active",
239
+ exit_code: null,
240
+ exit_reason: null,
241
+ started_at: now
242
+ }).catch(() => null);
243
+ return result;
244
+ }
245
+ async updateStatus(pid, status, extra) {
246
+ const existing = this.store.findOne((doc) => doc.pid === pid);
247
+ if (!existing) return;
248
+ const patch = { status };
249
+ if (extra?.exitReason !== void 0) patch.exitReason = extra.exitReason;
250
+ if (extra?.exitCode !== void 0) patch.exitCode = extra.exitCode;
251
+ await this.store.updateById(existing._id, patch);
252
+ logger.debug(`[pidStore] Updated process ${pid} status=${status}`);
253
+ updateGlobalPidStatus(pid, {
254
+ status,
255
+ exit_code: extra?.exitCode ?? null,
256
+ exit_reason: extra?.exitReason ?? null
257
+ }).catch(() => null);
258
+ }
259
+ getAllRecords() {
260
+ return this.store.getAll();
261
+ }
262
+ getLogDir() {
263
+ return path.resolve(this.storeDir, "logs");
264
+ }
265
+ getFifoPath(pid) {
266
+ if (process.platform === "win32") return `\\\\.\\pipe\\agent-yes-${pid}`;
267
+ else return path.resolve(this.storeDir, "fifo", `${pid}.stdin`);
268
+ }
269
+ async cleanStaleRecords() {
270
+ const activeRecords = this.store.find((r) => r.status !== "exited");
271
+ for (const record of activeRecords) if (!this.isProcessAlive(record.pid)) {
272
+ await this.store.updateById(record._id, {
273
+ status: "exited",
274
+ exitReason: "stale-cleanup"
275
+ });
276
+ logger.debug(`[pidStore] Cleaned stale record for PID ${record.pid}`);
277
+ }
278
+ }
279
+ async close() {
280
+ try {
281
+ await this.store.compact();
282
+ } catch (error) {
283
+ logger.debug("[pidStore] Compact on close failed:", error);
284
+ }
285
+ logger.debug("[pidStore] Database compacted and closed");
286
+ }
287
+ isProcessAlive(pid) {
288
+ try {
289
+ process.kill(pid, 0);
290
+ return true;
291
+ } catch {
292
+ return false;
293
+ }
294
+ }
295
+ async ensureGitignore() {
296
+ const gitignorePath = path.join(this.storeDir, ".gitignore");
297
+ const gitignoreContent = `# Auto-generated .gitignore for agent-yes
298
+ # Ignore all log files and runtime data
299
+ logs/
300
+ fifo/
301
+ pid-db/
302
+ *.jsonl
303
+ *.jsonl~
304
+ *.jsonl.lock
305
+ *.sqlite
306
+ *.sqlite-*
307
+ *.log
308
+ *.raw.log
309
+ *.lines.log
310
+ *.debug.log
311
+
312
+ # Ignore .gitignore itself
313
+ .gitignore
314
+
315
+ `;
316
+ try {
317
+ await mkdir(this.storeDir, { recursive: true });
318
+ await writeFile(gitignorePath, gitignoreContent, { flag: "wx" });
319
+ logger.debug(`[pidStore] Created .gitignore in ${this.storeDir}`);
320
+ } catch (error) {
321
+ if (error.code !== "EEXIST") logger.warn(`[pidStore] Failed to create .gitignore:`, error);
322
+ }
323
+ }
324
+ static async findActiveFifo(workingDir) {
325
+ try {
326
+ const store = new PidStore(workingDir);
327
+ await store.init();
328
+ const records = store.store.find((r) => r.status !== "exited").sort((a, b) => b.startedAt - a.startedAt);
329
+ await store.close();
330
+ return records[0]?.fifoFile ?? null;
331
+ } catch (error) {
332
+ logger.warn("[pidStore] findActiveFifo failed:", error);
333
+ return null;
334
+ }
335
+ }
336
+ };
337
+
338
+ //#endregion
339
+ export { PidStore as t };
340
+ //# sourceMappingURL=pidStore-CHLHMBEM.js.map
@@ -0,0 +1,5 @@
1
+ import "./logger-B9h0djqx.js";
2
+ import { t as PidStore } from "./pidStore-CHLHMBEM.js";
3
+ import "./globalPidIndex-DNEh8a_O.js";
4
+
5
+ export { PidStore };
@@ -1,8 +1,8 @@
1
1
  import { execSync } from "child_process";
2
+ import { existsSync } from "fs";
2
3
  import { mkdir, readFile, rename, writeFile } from "fs/promises";
3
- import path from "path";
4
4
  import { homedir } from "os";
5
- import { existsSync } from "fs";
5
+ import path from "path";
6
6
 
7
7
  //#region ts/runningLock.ts
8
8
  const getLockDir = () => path.join(process.env.CLAUDE_YES_HOME || homedir(), ".claude-yes");
@@ -260,4 +260,4 @@ function shouldUseLock(_cwd) {
260
260
 
261
261
  //#endregion
262
262
  export { shouldUseLock as i, getRunningAgentCount as n, releaseLock as r, acquireLock as t };
263
- //# sourceMappingURL=runningLock-BBI_URhR.js.map
263
+ //# sourceMappingURL=runningLock-DQWJSptq.js.map