agent-yes 1.60.0 → 1.60.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.
@@ -0,0 +1,1952 @@
1
+ import { t as logger } from "./logger-CY9ormLF.js";
2
+ import { arch, platform } from "process";
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
+ import { execaCommandSync, parseCommandString } from "execa";
11
+ 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";
13
+ import DIE from "phpdie";
14
+ import sflow from "sflow";
15
+ import { TerminalRenderStream } from "terminal-render";
16
+ import { homedir } from "os";
17
+ import { lock } from "proper-lockfile";
18
+ import { execSync as execSync$1 } from "node:child_process";
19
+ import { fileURLToPath } from "url";
20
+
21
+ //#region \0rolldown/runtime.js
22
+ var __defProp = Object.defineProperty;
23
+ var __esmMin = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
24
+ var __exportAll = (all, no_symbols) => {
25
+ let target = {};
26
+ for (var name in all) {
27
+ __defProp(target, name, {
28
+ get: all[name],
29
+ enumerable: true
30
+ });
31
+ }
32
+ if (!no_symbols) {
33
+ __defProp(target, Symbol.toStringTag, { value: "Module" });
34
+ }
35
+ return target;
36
+ };
37
+
38
+ //#endregion
39
+ //#region ts/resume/codexSessionManager.ts
40
+ const getSessionsFile = () => process.env.CLI_YES_TEST_HOME ? path.join(process.env.CLI_YES_TEST_HOME, ".config", "agent-yes", "codex-sessions.json") : path.join(homedir(), ".config", "agent-yes", "codex-sessions.json");
41
+ const getCodexSessionsDir = () => process.env.CLI_YES_TEST_HOME ? path.join(process.env.CLI_YES_TEST_HOME, ".codex", "sessions") : path.join(homedir(), ".codex", "sessions");
42
+ /**
43
+ * Load the session map from the config file
44
+ */
45
+ async function loadSessionMap() {
46
+ try {
47
+ const content = await readFile$1(getSessionsFile(), "utf-8");
48
+ return JSON.parse(content);
49
+ } catch {
50
+ return {};
51
+ }
52
+ }
53
+ /**
54
+ * Save the session map to the config file
55
+ */
56
+ async function saveSessionMap(sessionMap) {
57
+ try {
58
+ const sessionsFile = getSessionsFile();
59
+ await mkdir$1(path.dirname(sessionsFile), { recursive: true });
60
+ await writeFile$1(sessionsFile, JSON.stringify(sessionMap, null, 2));
61
+ } catch (error) {
62
+ console.warn("Failed to save codex session map:", error);
63
+ }
64
+ }
65
+ /**
66
+ * Store a session ID for a specific working directory
67
+ */
68
+ async function storeSessionForCwd(cwd, sessionId) {
69
+ const sessionMap = await loadSessionMap();
70
+ sessionMap[cwd] = {
71
+ sessionId,
72
+ lastUsed: (/* @__PURE__ */ new Date()).toISOString()
73
+ };
74
+ await saveSessionMap(sessionMap);
75
+ }
76
+ /**
77
+ * Parse a codex session file to extract session metadata
78
+ */
79
+ async function parseCodexSessionFile(filePath) {
80
+ try {
81
+ const lines = (await readFile$1(filePath, "utf-8")).trim().split("\n");
82
+ for (const line of lines) {
83
+ if (!line.trim()) continue;
84
+ const data = JSON.parse(line);
85
+ if (data.type === "session_meta" && data.payload) {
86
+ const payload = data.payload;
87
+ return {
88
+ id: payload.id,
89
+ timestamp: payload.timestamp || data.timestamp,
90
+ cwd: payload.cwd,
91
+ filePath,
92
+ git: payload.git
93
+ };
94
+ }
95
+ }
96
+ return null;
97
+ } catch {
98
+ return null;
99
+ }
100
+ }
101
+ /**
102
+ * Get all codex sessions from the .codex/sessions directory
103
+ */
104
+ async function getAllCodexSessions() {
105
+ const sessions = [];
106
+ const codexSessionsDir = getCodexSessionsDir();
107
+ try {
108
+ const years = await readdir(codexSessionsDir);
109
+ for (const year of years) {
110
+ const yearPath = path.join(codexSessionsDir, year);
111
+ try {
112
+ const months = await readdir(yearPath);
113
+ for (const month of months) {
114
+ const monthPath = path.join(yearPath, month);
115
+ try {
116
+ const days = await readdir(monthPath);
117
+ for (const day of days) {
118
+ const dayPath = path.join(monthPath, day);
119
+ try {
120
+ const files = await readdir(dayPath);
121
+ for (const file of files) if (file.endsWith(".jsonl")) {
122
+ const session = await parseCodexSessionFile(path.join(dayPath, file));
123
+ if (session) sessions.push(session);
124
+ }
125
+ } catch {}
126
+ }
127
+ } catch {}
128
+ }
129
+ } catch {}
130
+ }
131
+ } catch {
132
+ return [];
133
+ }
134
+ return sessions.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
135
+ }
136
+ /**
137
+ * Get the most recent session for a specific working directory from actual codex files
138
+ */
139
+ async function getMostRecentCodexSessionForCwd(targetCwd) {
140
+ return (await getAllCodexSessions()).filter((session) => session.cwd === targetCwd)[0] || null;
141
+ }
142
+ /**
143
+ * Get the last session ID for a specific working directory
144
+ * Now checks actual codex session files first, falls back to stored mapping
145
+ */
146
+ async function getSessionForCwd(cwd) {
147
+ const recentSession = await getMostRecentCodexSessionForCwd(cwd);
148
+ if (recentSession) return recentSession.id;
149
+ return (await loadSessionMap())[cwd]?.sessionId || null;
150
+ }
151
+ /**
152
+ * Extract session ID from codex output
153
+ * Session IDs are UUIDs in the format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
154
+ */
155
+ function extractSessionId(output) {
156
+ const match = output.match(/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i);
157
+ return match ? match[0] : null;
158
+ }
159
+
160
+ //#endregion
161
+ //#region ts/pty.ts
162
+ async function getPty() {
163
+ return globalThis.Bun ? await import("bun-pty").catch((error) => {
164
+ logger.error("Failed to load bun-pty:", error);
165
+ throw error;
166
+ }) : await import("node-pty").catch((error) => {
167
+ logger.error("Failed to load node-pty:", error);
168
+ throw error;
169
+ });
170
+ }
171
+ const pty = await getPty();
172
+ const ptyPackage = globalThis.Bun ? "bun-pty" : "node-pty";
173
+
174
+ //#endregion
175
+ //#region ts/removeControlCharacters.ts
176
+ function removeControlCharacters(str) {
177
+ return str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "");
178
+ }
179
+
180
+ //#endregion
181
+ //#region ts/runningLock.ts
182
+ const getLockDir = () => path.join(process.env.CLAUDE_YES_HOME || homedir(), ".claude-yes");
183
+ const getLockFile = () => path.join(getLockDir(), "running.lock.json");
184
+ const MAX_RETRIES = 5;
185
+ const RETRY_DELAYS = [
186
+ 50,
187
+ 100,
188
+ 200,
189
+ 400,
190
+ 800
191
+ ];
192
+ const POLL_INTERVAL = 2e3;
193
+ /**
194
+ * Check if a process is running
195
+ */
196
+ function isProcessRunning(pid) {
197
+ try {
198
+ process.kill(pid, 0);
199
+ return true;
200
+ } catch {
201
+ return false;
202
+ }
203
+ }
204
+ /**
205
+ * Get git repository root for a directory
206
+ */
207
+ function getGitRoot(cwd) {
208
+ try {
209
+ return execSync("git rev-parse --show-toplevel", {
210
+ cwd,
211
+ encoding: "utf8",
212
+ stdio: [
213
+ "pipe",
214
+ "pipe",
215
+ "ignore"
216
+ ]
217
+ }).trim();
218
+ } catch {
219
+ return null;
220
+ }
221
+ }
222
+ /**
223
+ * Check if directory is in a git repository
224
+ */
225
+ function isGitRepo(cwd) {
226
+ try {
227
+ return getGitRoot(cwd) !== null;
228
+ } catch {
229
+ return false;
230
+ }
231
+ }
232
+ /**
233
+ * Resolve path to real path (handling symlinks)
234
+ */
235
+ function resolveRealPath(p) {
236
+ try {
237
+ return path.resolve(p);
238
+ } catch {
239
+ return p;
240
+ }
241
+ }
242
+ /**
243
+ * Sleep for a given number of milliseconds
244
+ */
245
+ function sleep$1(ms) {
246
+ return new Promise((resolve) => setTimeout(resolve, ms));
247
+ }
248
+ /**
249
+ * Read lock file with retry logic and stale lock cleanup
250
+ */
251
+ async function readLockFile() {
252
+ try {
253
+ const lockDir = getLockDir();
254
+ const lockFilePath = getLockFile();
255
+ await mkdir$1(lockDir, { recursive: true });
256
+ if (!existsSync(lockFilePath)) return { tasks: [] };
257
+ const content = await readFile$1(lockFilePath, "utf8");
258
+ const lockFile = JSON.parse(content);
259
+ lockFile.tasks = lockFile.tasks.filter((task) => {
260
+ if (isProcessRunning(task.pid)) return true;
261
+ return false;
262
+ });
263
+ return lockFile;
264
+ } catch {
265
+ return { tasks: [] };
266
+ }
267
+ }
268
+ /**
269
+ * Write lock file atomically with retry logic
270
+ */
271
+ async function writeLockFile(lockFile, retryCount = 0) {
272
+ try {
273
+ const lockDir = getLockDir();
274
+ const lockFilePath = getLockFile();
275
+ await mkdir$1(lockDir, { recursive: true });
276
+ const tempFile = `${lockFilePath}.tmp.${process.pid}`;
277
+ await writeFile$1(tempFile, JSON.stringify(lockFile, null, 2), "utf8");
278
+ await rename(tempFile, lockFilePath);
279
+ } catch (error) {
280
+ if (retryCount < MAX_RETRIES) {
281
+ await sleep$1(RETRY_DELAYS[retryCount] || 800);
282
+ return writeLockFile(lockFile, retryCount + 1);
283
+ }
284
+ throw error;
285
+ }
286
+ }
287
+ /**
288
+ * Check if lock exists for the current working directory
289
+ */
290
+ async function checkLock(cwd, _prompt) {
291
+ const resolvedCwd = resolveRealPath(cwd);
292
+ const gitRoot = isGitRepo(resolvedCwd) ? getGitRoot(resolvedCwd) : null;
293
+ const lockKey = gitRoot || resolvedCwd;
294
+ const blockingTasks = (await readLockFile()).tasks.filter((task) => {
295
+ if (!isProcessRunning(task.pid)) return false;
296
+ if (task.status !== "running") return false;
297
+ if (gitRoot && task.gitRoot) return task.gitRoot === gitRoot;
298
+ else return task.cwd === lockKey;
299
+ });
300
+ return {
301
+ isLocked: blockingTasks.length > 0,
302
+ blockingTasks,
303
+ lockKey
304
+ };
305
+ }
306
+ /**
307
+ * Add a task to the lock file
308
+ */
309
+ async function addTask(task) {
310
+ const lockFile = await readLockFile();
311
+ lockFile.tasks = lockFile.tasks.filter((t) => t.pid !== task.pid);
312
+ lockFile.tasks.push(task);
313
+ await writeLockFile(lockFile);
314
+ }
315
+ /**
316
+ * Update task status
317
+ */
318
+ async function updateTaskStatus(pid, status) {
319
+ const lockFile = await readLockFile();
320
+ const task = lockFile.tasks.find((t) => t.pid === pid);
321
+ if (task) {
322
+ task.status = status;
323
+ await writeLockFile(lockFile);
324
+ }
325
+ }
326
+ /**
327
+ * Remove a task from the lock file
328
+ */
329
+ async function removeTask(pid) {
330
+ const lockFile = await readLockFile();
331
+ lockFile.tasks = lockFile.tasks.filter((t) => t.pid !== pid);
332
+ await writeLockFile(lockFile);
333
+ }
334
+ /**
335
+ * Wait for lock to be released
336
+ */
337
+ async function waitForUnlock(blockingTasks, currentTask) {
338
+ const blockingTask = blockingTasks[0];
339
+ if (!blockingTask) return;
340
+ console.log(`⏳ Queueing for unlock of: ${blockingTask.task}`);
341
+ console.log(` Press 'b' to bypass queue, 'k' to kill previous instance`);
342
+ await addTask({
343
+ ...currentTask,
344
+ status: "queued"
345
+ });
346
+ const stdin = process.stdin;
347
+ const wasRaw = stdin.isRaw;
348
+ stdin.setRawMode?.(true);
349
+ stdin.resume();
350
+ let bypassed = false;
351
+ let killed = false;
352
+ const keyHandler = (key) => {
353
+ const char = key.toString();
354
+ if (char === "b" || char === "B") {
355
+ console.log("\n⚡ Bypassing queue...");
356
+ bypassed = true;
357
+ } else if (char === "k" || char === "K") {
358
+ console.log("\n🔪 Killing previous instance...");
359
+ killed = true;
360
+ }
361
+ };
362
+ stdin.on("data", keyHandler);
363
+ let dots = 0;
364
+ while (true) {
365
+ if (bypassed) {
366
+ await updateTaskStatus(currentTask.pid, "running");
367
+ console.log("✓ Queue bypassed, starting task...");
368
+ break;
369
+ }
370
+ if (killed && blockingTask) {
371
+ try {
372
+ process.kill(blockingTask.pid, "SIGTERM");
373
+ console.log(`✓ Killed process ${blockingTask.pid}`);
374
+ await sleep$1(1e3);
375
+ } catch (err) {
376
+ console.log(`⚠️ Could not kill process ${blockingTask.pid}: ${err}`);
377
+ }
378
+ killed = false;
379
+ }
380
+ await sleep$1(POLL_INTERVAL);
381
+ if (!(await checkLock(currentTask.cwd, currentTask.task)).isLocked) {
382
+ await updateTaskStatus(currentTask.pid, "running");
383
+ console.log(`\n✓ Lock released, starting task...`);
384
+ break;
385
+ }
386
+ dots = (dots + 1) % 4;
387
+ process.stdout.write(`\r⏳ Queueing${".".repeat(dots)}${" ".repeat(3 - dots)}`);
388
+ }
389
+ stdin.off("data", keyHandler);
390
+ stdin.setRawMode?.(wasRaw);
391
+ if (!wasRaw) stdin.pause();
392
+ }
393
+ /**
394
+ * Acquire lock or wait if locked
395
+ */
396
+ async function acquireLock(cwd, prompt = "no prompt provided") {
397
+ const resolvedCwd = resolveRealPath(cwd);
398
+ const task = {
399
+ cwd: resolvedCwd,
400
+ gitRoot: (isGitRepo(resolvedCwd) ? getGitRoot(resolvedCwd) : null) || void 0,
401
+ task: prompt.substring(0, 100),
402
+ pid: process.pid,
403
+ status: "running",
404
+ startedAt: Date.now(),
405
+ lockedAt: Date.now()
406
+ };
407
+ const lockCheck = await checkLock(resolvedCwd, prompt);
408
+ if (lockCheck.isLocked) await waitForUnlock(lockCheck.blockingTasks, task);
409
+ else await addTask(task);
410
+ }
411
+ /**
412
+ * Release lock for current process
413
+ */
414
+ async function releaseLock(pid = process.pid) {
415
+ await removeTask(pid);
416
+ }
417
+ /**
418
+ * Check if we should use locking for this directory
419
+ * Only use locking if we're in a git repository
420
+ */
421
+ function shouldUseLock(_cwd) {
422
+ return true;
423
+ }
424
+
425
+ //#endregion
426
+ //#region ts/JsonlStore.ts
427
+ /**
428
+ * A lightweight NeDB-style JSONL persistence layer.
429
+ *
430
+ * - Append-only writes (one JSON object per line)
431
+ * - Same `_id` → last line wins (fields merged)
432
+ * - `$$deleted` lines act as tombstones
433
+ * - Crash recovery: skip partial last line, recover from temp file
434
+ * - Multi-process safe via proper-lockfile (reads don't need lock)
435
+ * - Compact on close: deduplicates into clean file via atomic rename
436
+ */
437
+ var JsonlStore = class {
438
+ filePath;
439
+ tempPath;
440
+ lockPath;
441
+ docs = /* @__PURE__ */ new Map();
442
+ constructor(filePath) {
443
+ this.filePath = filePath;
444
+ this.tempPath = filePath + "~";
445
+ this.lockPath = path.dirname(filePath);
446
+ }
447
+ /**
448
+ * Load all records from the JSONL file. No lock needed.
449
+ * Handles crash recovery: partial last line skipped, temp file recovery.
450
+ */
451
+ async load() {
452
+ await mkdir$1(path.dirname(this.filePath), { recursive: true });
453
+ if (!existsSync(this.filePath) && existsSync(this.tempPath)) {
454
+ logger.debug("[JsonlStore] Recovering from temp file");
455
+ await rename(this.tempPath, this.filePath);
456
+ }
457
+ this.docs = /* @__PURE__ */ new Map();
458
+ let raw = "";
459
+ try {
460
+ raw = await readFile$1(this.filePath, "utf-8");
461
+ } catch (err) {
462
+ if (err.code === "ENOENT") return this.docs;
463
+ throw err;
464
+ }
465
+ const lines = raw.split("\n");
466
+ let corruptCount = 0;
467
+ for (const line of lines) {
468
+ const trimmed = line.trim();
469
+ if (!trimmed) continue;
470
+ try {
471
+ const doc = JSON.parse(trimmed);
472
+ if (!doc._id) continue;
473
+ if (doc.$$deleted) this.docs.delete(doc._id);
474
+ else {
475
+ const existing = this.docs.get(doc._id);
476
+ if (existing) this.docs.set(doc._id, {
477
+ ...existing,
478
+ ...doc
479
+ });
480
+ else this.docs.set(doc._id, doc);
481
+ }
482
+ } catch {
483
+ corruptCount++;
484
+ }
485
+ }
486
+ if (corruptCount > 0) logger.debug(`[JsonlStore] Skipped ${corruptCount} corrupt line(s) in ${this.filePath}`);
487
+ return this.docs;
488
+ }
489
+ /** Get all live documents. */
490
+ getAll() {
491
+ return Array.from(this.docs.values());
492
+ }
493
+ /** Find a document by _id. */
494
+ getById(id) {
495
+ return this.docs.get(id);
496
+ }
497
+ /** Find documents matching a predicate. */
498
+ find(predicate) {
499
+ return this.getAll().filter(predicate);
500
+ }
501
+ /** Find first document matching a predicate. */
502
+ findOne(predicate) {
503
+ for (const doc of this.docs.values()) if (predicate(doc)) return doc;
504
+ }
505
+ /**
506
+ * Append a new document. Acquires lock.
507
+ * If no _id is provided, one is generated.
508
+ */
509
+ async append(doc) {
510
+ const id = doc._id || generateId();
511
+ const { _id: _, ...rest } = doc;
512
+ const fullDoc = {
513
+ _id: id,
514
+ ...rest
515
+ };
516
+ return await this.withLock(async () => {
517
+ await appendFile(this.filePath, JSON.stringify(fullDoc) + "\n");
518
+ const existing = this.docs.get(fullDoc._id);
519
+ if (existing) this.docs.set(fullDoc._id, {
520
+ ...existing,
521
+ ...fullDoc
522
+ });
523
+ else this.docs.set(fullDoc._id, fullDoc);
524
+ return fullDoc;
525
+ });
526
+ }
527
+ /**
528
+ * Update a document by _id. Appends a merge line. Acquires lock.
529
+ */
530
+ async updateById(id, patch) {
531
+ await this.withLock(async () => {
532
+ const line = {
533
+ _id: id,
534
+ ...patch
535
+ };
536
+ await appendFile(this.filePath, JSON.stringify(line) + "\n");
537
+ const existing = this.docs.get(id);
538
+ if (existing) this.docs.set(id, {
539
+ ...existing,
540
+ ...patch
541
+ });
542
+ });
543
+ }
544
+ /**
545
+ * Delete a document by _id. Appends a tombstone. Acquires lock.
546
+ */
547
+ async deleteById(id) {
548
+ await this.withLock(async () => {
549
+ const tombstone = {
550
+ _id: id,
551
+ $$deleted: true
552
+ };
553
+ await appendFile(this.filePath, JSON.stringify(tombstone) + "\n");
554
+ this.docs.delete(id);
555
+ });
556
+ }
557
+ /**
558
+ * Compact the file: deduplicate entries, remove tombstones.
559
+ * Writes to temp file, fsyncs, then atomic renames.
560
+ * Acquires lock.
561
+ */
562
+ async compact() {
563
+ await this.withLock(async () => {
564
+ const lines = Array.from(this.docs.values()).map((doc) => {
565
+ const { _id, $$deleted, ...rest } = doc;
566
+ return JSON.stringify({
567
+ _id,
568
+ ...rest
569
+ });
570
+ }).join("\n");
571
+ const content = lines ? lines + "\n" : "";
572
+ await writeFile$1(this.tempPath, content);
573
+ const fd = openSync(this.tempPath, "r");
574
+ fsyncSync(fd);
575
+ closeSync(fd);
576
+ await rename(this.tempPath, this.filePath);
577
+ });
578
+ }
579
+ async withLock(fn) {
580
+ const dir = path.dirname(this.filePath);
581
+ let release;
582
+ try {
583
+ release = await lock(dir, {
584
+ lockfilePath: this.filePath + ".lock",
585
+ retries: {
586
+ retries: 5,
587
+ minTimeout: 50,
588
+ maxTimeout: 500
589
+ }
590
+ });
591
+ return await fn();
592
+ } finally {
593
+ if (release) await release();
594
+ }
595
+ }
596
+ };
597
+ let idCounter = 0;
598
+ function generateId() {
599
+ return Date.now().toString(36) + (idCounter++).toString(36) + Math.random().toString(36).slice(2, 6);
600
+ }
601
+
602
+ //#endregion
603
+ //#region ts/pidStore.ts
604
+ var PidStore = class PidStore {
605
+ storeDir;
606
+ store;
607
+ constructor(workingDir) {
608
+ this.storeDir = path.resolve(workingDir, ".agent-yes");
609
+ this.store = new JsonlStore(path.join(this.storeDir, "pid-records.jsonl"));
610
+ }
611
+ async init() {
612
+ try {
613
+ await this.ensureGitignore();
614
+ await this.store.load();
615
+ await this.cleanStaleRecords();
616
+ } catch (error) {
617
+ logger.warn("[pidStore] Failed to initialize:", error);
618
+ }
619
+ }
620
+ async registerProcess({ pid, cli, args, prompt, cwd }) {
621
+ const now = Date.now();
622
+ const record = {
623
+ pid,
624
+ cli,
625
+ args: JSON.stringify(args),
626
+ prompt,
627
+ cwd,
628
+ logFile: path.resolve(this.getLogDir(), `${pid}.log`),
629
+ fifoFile: this.getFifoPath(pid),
630
+ status: "active",
631
+ exitReason: "",
632
+ startedAt: now
633
+ };
634
+ const existing = this.store.findOne((doc) => doc.pid === pid);
635
+ if (existing) await this.store.updateById(existing._id, record);
636
+ else await this.store.append(record);
637
+ const result = this.store.findOne((doc) => doc.pid === pid);
638
+ if (!result) {
639
+ const allRecords = this.store.getAll();
640
+ logger.error(`[pidStore] Failed to find record for PID ${pid}. All records:`, allRecords);
641
+ throw new Error(`Failed to register process ${pid}`);
642
+ }
643
+ logger.debug(`[pidStore] Registered process ${pid}`);
644
+ return result;
645
+ }
646
+ async updateStatus(pid, status, extra) {
647
+ const existing = this.store.findOne((doc) => doc.pid === pid);
648
+ if (!existing) return;
649
+ const patch = { status };
650
+ if (extra?.exitReason !== void 0) patch.exitReason = extra.exitReason;
651
+ if (extra?.exitCode !== void 0) patch.exitCode = extra.exitCode;
652
+ await this.store.updateById(existing._id, patch);
653
+ logger.debug(`[pidStore] Updated process ${pid} status=${status}`);
654
+ }
655
+ getAllRecords() {
656
+ return this.store.getAll();
657
+ }
658
+ getLogDir() {
659
+ return path.resolve(this.storeDir, "logs");
660
+ }
661
+ getFifoPath(pid) {
662
+ if (process.platform === "win32") return `\\\\.\\pipe\\agent-yes-${pid}`;
663
+ else return path.resolve(this.storeDir, "fifo", `${pid}.stdin`);
664
+ }
665
+ async cleanStaleRecords() {
666
+ const activeRecords = this.store.find((r) => r.status !== "exited");
667
+ for (const record of activeRecords) if (!this.isProcessAlive(record.pid)) {
668
+ await this.store.updateById(record._id, {
669
+ status: "exited",
670
+ exitReason: "stale-cleanup"
671
+ });
672
+ logger.debug(`[pidStore] Cleaned stale record for PID ${record.pid}`);
673
+ }
674
+ }
675
+ async close() {
676
+ try {
677
+ await this.store.compact();
678
+ } catch (error) {
679
+ logger.debug("[pidStore] Compact on close failed:", error);
680
+ }
681
+ logger.debug("[pidStore] Database compacted and closed");
682
+ }
683
+ isProcessAlive(pid) {
684
+ try {
685
+ process.kill(pid, 0);
686
+ return true;
687
+ } catch {
688
+ return false;
689
+ }
690
+ }
691
+ async ensureGitignore() {
692
+ const gitignorePath = path.join(this.storeDir, ".gitignore");
693
+ const gitignoreContent = `# Auto-generated .gitignore for agent-yes
694
+ # Ignore all log files and runtime data
695
+ logs/
696
+ fifo/
697
+ pid-db/
698
+ *.jsonl
699
+ *.jsonl~
700
+ *.jsonl.lock
701
+ *.sqlite
702
+ *.sqlite-*
703
+ *.log
704
+ *.raw.log
705
+ *.lines.log
706
+ *.debug.log
707
+
708
+ # Ignore .gitignore itself
709
+ .gitignore
710
+
711
+ `;
712
+ try {
713
+ await mkdir$1(this.storeDir, { recursive: true });
714
+ await writeFile$1(gitignorePath, gitignoreContent, { flag: "wx" });
715
+ logger.debug(`[pidStore] Created .gitignore in ${this.storeDir}`);
716
+ } catch (error) {
717
+ if (error.code !== "EEXIST") logger.warn(`[pidStore] Failed to create .gitignore:`, error);
718
+ }
719
+ }
720
+ static async findActiveFifo(workingDir) {
721
+ try {
722
+ const store = new PidStore(workingDir);
723
+ await store.init();
724
+ const records = store.store.find((r) => r.status !== "exited").sort((a, b) => b.startedAt - a.startedAt);
725
+ await store.close();
726
+ return records[0]?.fifoFile ?? null;
727
+ } catch (error) {
728
+ logger.warn("[pidStore] findActiveFifo failed:", error);
729
+ return null;
730
+ }
731
+ }
732
+ };
733
+
734
+ //#endregion
735
+ //#region ts/idleWaiter.ts
736
+ /**
737
+ * A utility class to wait for idle periods based on activity pings.
738
+ *
739
+ * @example
740
+ * const idleWaiter = new IdleWaiter();
741
+ *
742
+ * // Somewhere in your code, when activity occurs:
743
+ * idleWaiter.ping();
744
+ *
745
+ * // To wait for an idle period of 5 seconds:
746
+ * await idleWaiter.wait(5000);
747
+ * console.log('System has been idle for 5 seconds');
748
+ */
749
+ var IdleWaiter = class {
750
+ lastActivityTime = Date.now();
751
+ checkInterval = 100;
752
+ constructor() {
753
+ this.ping();
754
+ }
755
+ ping() {
756
+ this.lastActivityTime = Date.now();
757
+ return this;
758
+ }
759
+ async wait(ms) {
760
+ while (this.lastActivityTime >= Date.now() - ms) await new Promise((resolve) => setTimeout(resolve, this.checkInterval));
761
+ }
762
+ };
763
+
764
+ //#endregion
765
+ //#region ts/ReadyManager.ts
766
+ var ReadyManager = class {
767
+ isReady = false;
768
+ readyQueue = [];
769
+ wait() {
770
+ if (this.isReady) return;
771
+ return new Promise((resolve) => this.readyQueue.push(resolve));
772
+ }
773
+ unready() {
774
+ this.isReady = false;
775
+ }
776
+ ready() {
777
+ this.isReady = true;
778
+ if (!this.readyQueue.length) return;
779
+ this.readyQueue.splice(0).map((resolve) => resolve());
780
+ }
781
+ };
782
+
783
+ //#endregion
784
+ //#region ts/core/messaging.ts
785
+ /**
786
+ * Send Enter key to the shell after waiting for idle state
787
+ * @param context Message context with shell and state managers
788
+ * @param waitms Milliseconds to wait for idle before sending Enter (default: 1000)
789
+ */
790
+ async function sendEnter(context, waitms = 1e3) {
791
+ const st = Date.now();
792
+ await context.idleWaiter.wait(waitms);
793
+ const et = Date.now();
794
+ logger.debug(`sendingEnter| idleWaiter.wait(${String(waitms)}) took ${String(et - st)}ms`);
795
+ context.nextStdout.unready();
796
+ context.shell.write("\r");
797
+ logger.debug(`enterSent| idleWaiter.wait(${String(waitms)}) took ${String(et - st)}ms`);
798
+ await Promise.race([context.nextStdout.wait(), new Promise((resolve) => setTimeout(() => {
799
+ if (!context.nextStdout.ready) context.shell.write("\r");
800
+ resolve();
801
+ }, 1e3))]);
802
+ await Promise.race([context.nextStdout.wait(), new Promise((resolve) => setTimeout(() => {
803
+ if (!context.nextStdout.ready) context.shell.write("\r");
804
+ resolve();
805
+ }, 3e3))]);
806
+ }
807
+ /**
808
+ * Send a message to the shell
809
+ * @param context Message context with shell and state managers
810
+ * @param message Message string to send
811
+ * @param options Options for message sending
812
+ */
813
+ async function sendMessage(context, message, { waitForReady = true } = {}) {
814
+ if (waitForReady) await context.stdinReady.wait();
815
+ logger.debug(`send |${message}`);
816
+ context.nextStdout.unready();
817
+ context.shell.write(message);
818
+ context.idleWaiter.ping();
819
+ logger.debug(`waiting next stdout|${message}`);
820
+ await context.nextStdout.wait();
821
+ logger.debug(`sending enter`);
822
+ await sendEnter(context, 1e3);
823
+ logger.debug(`sent enter`);
824
+ }
825
+
826
+ //#endregion
827
+ //#region ts/core/logging.ts
828
+ /**
829
+ * Initialize log paths based on PID
830
+ * @param pidStore PID store instance
831
+ * @param pid Process ID
832
+ * @returns Object containing all log paths
833
+ */
834
+ async function initializeLogPaths(pidStore, pid) {
835
+ const logDir = pidStore.getLogDir();
836
+ await mkdir$1(logDir, { recursive: true });
837
+ return {
838
+ logPath: logDir,
839
+ rawLogPath: path.resolve(path.dirname(logDir), `${pid}.raw.log`),
840
+ rawLinesLogPath: path.resolve(path.dirname(logDir), `${pid}.lines.log`),
841
+ debuggingLogsPath: path.resolve(path.dirname(logDir), `${pid}.debug.log`)
842
+ };
843
+ }
844
+ /**
845
+ * Setup debug logging to file
846
+ * @param debuggingLogsPath Path to debug log file
847
+ */
848
+ function setupDebugLogging(debuggingLogsPath) {
849
+ if (debuggingLogsPath) logger.add(new winston.transports.File({
850
+ filename: debuggingLogsPath,
851
+ level: "debug"
852
+ }));
853
+ }
854
+ /**
855
+ * Save rendered terminal output to log file
856
+ * @param logPath Path to log file
857
+ * @param content Rendered content to save
858
+ */
859
+ async function saveLogFile(logPath, content) {
860
+ if (!logPath) return;
861
+ await mkdir$1(path.dirname(logPath), { recursive: true }).catch(() => null);
862
+ await writeFile$1(logPath, content).catch(() => null);
863
+ logger.info(`Full logs saved to ${logPath}`);
864
+ }
865
+ /**
866
+ * Save logs to deprecated logFile option (for backward compatibility)
867
+ * @param logFile User-specified log file path
868
+ * @param content Rendered content to save
869
+ * @param verbose Whether to log verbose messages
870
+ */
871
+ async function saveDeprecatedLogFile(logFile, content, verbose) {
872
+ if (!logFile) return;
873
+ if (verbose) logger.info(`Writing rendered logs to ${logFile}`);
874
+ const logFilePath = path.resolve(logFile);
875
+ await mkdir$1(path.dirname(logFilePath), { recursive: true }).catch(() => null);
876
+ await writeFile$1(logFilePath, content);
877
+ }
878
+
879
+ //#endregion
880
+ //#region ts/tryCatch.ts
881
+ /**
882
+ * A utility function to wrap another function with a try-catch block.
883
+ * If an error occurs during the execution of the function, the provided
884
+ * catchFn is called with the error, the original function, and its arguments.
885
+ *
886
+ * @param catchFn - The function to call when an error occurs.
887
+ * @param fn - The function to wrap.
888
+ * @returns A new function that wraps the original function with error handling.
889
+ */
890
+ function tryCatch(catchFn, fn) {
891
+ let attempts = 0;
892
+ return function robustFn(...args) {
893
+ try {
894
+ attempts++;
895
+ return fn(...args);
896
+ } catch (error) {
897
+ return catchFn(error, attempts, robustFn, ...args);
898
+ }
899
+ };
900
+ }
901
+
902
+ //#endregion
903
+ //#region package.json
904
+ var name = "agent-yes";
905
+ var version = "1.60.2";
906
+
907
+ //#endregion
908
+ //#region ts/pty-fix.ts
909
+ var pty_fix_exports = /* @__PURE__ */ __exportAll({});
910
+ function getLibraryName() {
911
+ switch (platform) {
912
+ case "win32": return "rust_pty.dll";
913
+ case "darwin": return arch === "arm64" ? "librust_pty_arm64.dylib" : "librust_pty.dylib";
914
+ case "linux": return arch === "arm64" ? "librust_pty_arm64.so" : "librust_pty.so";
915
+ default: return "librust_pty.so";
916
+ }
917
+ }
918
+ function rebuildBunPty() {
919
+ try {
920
+ const cargoCmd = platform === "win32" ? "cargo.exe" : "cargo";
921
+ try {
922
+ execSync(`${cargoCmd} --version`, { stdio: "ignore" });
923
+ } catch {
924
+ console.warn("Warning: Rust/Cargo not found. bun-pty native module may not work.");
925
+ console.warn("To fix this, install Rust: https://rustup.rs/");
926
+ return;
927
+ }
928
+ const rustPtyDir = join(bunPtyPath, "rust-pty");
929
+ const isWindows = platform === "win32";
930
+ const tempBase = isWindows ? process.env.TEMP || "C:\\Temp" : "/tmp";
931
+ if (!existsSync(join(rustPtyDir, "Cargo.toml"))) {
932
+ console.log("Source code not found in npm package, cloning from repository...");
933
+ const tmpDir = join(tempBase, `bun-pty-build-${Date.now()}`);
934
+ try {
935
+ execSync(`git clone https://github.com/snomiao/bun-pty.git "${tmpDir}"`, { stdio: "inherit" });
936
+ if (isWindows) execSync(`cd /d "${tmpDir}" && cargo build --release --manifest-path rust-pty\\Cargo.toml`, { stdio: "inherit" });
937
+ else execSync(`cd "${tmpDir}" && cargo build --release --manifest-path rust-pty/Cargo.toml`, { stdio: "inherit" });
938
+ const builtLib = join(tmpDir, "rust-pty", "target", "release", libName);
939
+ if (existsSync(builtLib)) {
940
+ const targetDir = join(rustPtyDir, "target", "release");
941
+ if (isWindows) {
942
+ execSync(`if not exist "${targetDir}" mkdir "${targetDir}"`, {});
943
+ execSync(`copy /Y "${builtLib}" "${libPath}"`, {});
944
+ } else {
945
+ execSync(`mkdir -p "${targetDir}"`, { stdio: "inherit" });
946
+ execSync(`cp "${builtLib}" "${libPath}"`, { stdio: "inherit" });
947
+ }
948
+ console.log("Successfully rebuilt bun-pty native module");
949
+ }
950
+ if (isWindows) execSync(`rmdir /s /q "${tmpDir}"`, { stdio: "ignore" });
951
+ else execSync(`rm -rf "${tmpDir}"`, { stdio: "ignore" });
952
+ } catch (buildError) {
953
+ console.error("Failed to build bun-pty:", buildError instanceof Error ? buildError.message : buildError);
954
+ console.warn("The application may not work correctly without bun-pty");
955
+ }
956
+ } else {
957
+ console.log("Building bun-pty from source...");
958
+ if (isWindows) execSync(`cd /d "${rustPtyDir}" && cargo build --release`, { stdio: "inherit" });
959
+ else execSync(`cd "${rustPtyDir}" && cargo build --release`, { stdio: "inherit" });
960
+ console.log("Successfully rebuilt bun-pty native module");
961
+ }
962
+ } catch (error) {
963
+ console.error("Failed to rebuild bun-pty:", error instanceof Error ? error.message : error);
964
+ console.warn("The application may not work correctly without bun-pty");
965
+ }
966
+ }
967
+ var bunPtyPath, libName, libPath;
968
+ var init_pty_fix = __esmMin((() => {
969
+ bunPtyPath = dirname(fileURLToPath(import.meta.resolve("@snomiao/bun-pty"))) + "/..";
970
+ libName = getLibraryName();
971
+ libPath = join(bunPtyPath, "rust-pty", "target", "release", libName);
972
+ if (!existsSync(bunPtyPath)) {
973
+ console.log({ bunPtyPath });
974
+ console.log("bun-pty not found, skipping pty-fix in ");
975
+ process.exit(0);
976
+ }
977
+ if (platform === "linux") try {
978
+ const lddOutput = execSync(`ldd "${libPath}" 2>&1`, { encoding: "utf8" });
979
+ if (lddOutput.includes("GLIBC") && lddOutput.includes("not found")) {
980
+ console.log("GLIBC compatibility issue detected, rebuilding bun-pty...");
981
+ rebuildBunPty();
982
+ } else console.log("bun-pty binary is compatible");
983
+ } catch {
984
+ console.log("Checking bun-pty compatibility...");
985
+ rebuildBunPty();
986
+ }
987
+ else if (platform === "win32") if (!existsSync(libPath)) {
988
+ console.log("Windows DLL not found, attempting to rebuild...");
989
+ rebuildBunPty();
990
+ } else console.log("bun-pty Windows DLL found");
991
+ else if (platform === "darwin") if (!existsSync(libPath)) {
992
+ console.log("macOS dylib not found, attempting to rebuild...");
993
+ rebuildBunPty();
994
+ } else console.log("bun-pty macOS dylib found");
995
+ else console.log(`Platform ${platform} may require manual configuration`);
996
+ }));
997
+
998
+ //#endregion
999
+ //#region ts/core/spawner.ts
1000
+ /**
1001
+ * Get install command based on platform and configuration
1002
+ *
1003
+ * Selects the appropriate install command from the configuration
1004
+ * based on the current platform (Windows/Unix) and available shells.
1005
+ * Falls back to npm if platform-specific commands aren't available.
1006
+ *
1007
+ * @param installConfig - Install command configuration (string or platform-specific object)
1008
+ * @returns Install command string or null if no suitable command found
1009
+ *
1010
+ * @example
1011
+ * ```typescript
1012
+ * // Simple string config
1013
+ * getInstallCommand('npm install -g claude-cli')
1014
+ *
1015
+ * // Platform-specific config
1016
+ * getInstallCommand({
1017
+ * windows: 'npm install -g claude-cli',
1018
+ * unix: 'curl -fsSL install.sh | sh',
1019
+ * npm: 'npm install -g claude-cli'
1020
+ * })
1021
+ * ```
1022
+ */
1023
+ function getInstallCommand(installConfig) {
1024
+ if (typeof installConfig === "string") return installConfig;
1025
+ const isWindows = process.platform === "win32";
1026
+ const platform = isWindows ? "windows" : "unix";
1027
+ if (installConfig[platform]) return installConfig[platform];
1028
+ if (isWindows && installConfig.powershell) return installConfig.powershell;
1029
+ if (!isWindows && installConfig.bash) return installConfig.bash;
1030
+ if (installConfig.npm) return installConfig.npm;
1031
+ return null;
1032
+ }
1033
+ /**
1034
+ * Check if error is a command not found error
1035
+ */
1036
+ function isCommandNotFoundError(e) {
1037
+ if (e instanceof Error) return e.message.includes("command not found") || e.message.includes("ENOENT") || e.message.includes("spawn");
1038
+ return false;
1039
+ }
1040
+ /**
1041
+ * Spawn agent CLI process with error handling and auto-install
1042
+ *
1043
+ * Creates a new PTY process for the specified CLI with comprehensive error
1044
+ * handling. If the CLI is not found and auto-install is enabled, attempts
1045
+ * to install it automatically. Includes special handling for bun-pty issues.
1046
+ *
1047
+ * @param options - Spawn configuration options
1048
+ * @returns IPty process instance
1049
+ * @throws Error if CLI not found and installation fails or is disabled
1050
+ *
1051
+ * @example
1052
+ * ```typescript
1053
+ * const shell = spawnAgent({
1054
+ * cli: 'claude',
1055
+ * cliConf: config.clis.claude,
1056
+ * cliArgs: ['--verbose'],
1057
+ * verbose: true,
1058
+ * install: false,
1059
+ * ptyOptions: {
1060
+ * name: 'xterm-color',
1061
+ * cols: 80,
1062
+ * rows: 30,
1063
+ * cwd: '/path/to/project',
1064
+ * env: process.env
1065
+ * }
1066
+ * });
1067
+ * ```
1068
+ */
1069
+ function spawnAgent(options) {
1070
+ const { cli, cliConf, cliArgs, verbose, install, ptyOptions } = options;
1071
+ const spawn = () => {
1072
+ let [bin, ...args] = [...parseCommandString(cliConf?.binary || cli), ...cliArgs];
1073
+ logger.debug(`Spawning ${bin} with args: ${JSON.stringify(args)}`);
1074
+ const spawned = pty.spawn(bin, args, ptyOptions);
1075
+ logger.info(`[${cli}-yes] Spawned ${bin} with PID ${spawned.pid} (agent-yes v${version})`);
1076
+ return spawned;
1077
+ };
1078
+ return tryCatch((error, attempts, spawn, ...args) => {
1079
+ logger.error(`Fatal: Failed to start ${cli}.`);
1080
+ const isNotFound = isCommandNotFoundError(error);
1081
+ if (cliConf?.install && isNotFound) {
1082
+ const installCmd = getInstallCommand(cliConf.install);
1083
+ if (!installCmd) {
1084
+ logger.error(`No suitable install command found for ${cli} on this platform`);
1085
+ throw error;
1086
+ }
1087
+ logger.info(`Please install the cli by run ${installCmd}`);
1088
+ if (install) {
1089
+ logger.debug(`Attempting to install ${cli}...`);
1090
+ execSync$1(installCmd, { stdio: "inherit" });
1091
+ logger.info(`${cli} installed successfully. Please rerun the command.`);
1092
+ return spawn(...args);
1093
+ } else {
1094
+ logger.error(`If you did not installed it yet, Please install it first: ${installCmd}`);
1095
+ throw error;
1096
+ }
1097
+ }
1098
+ if (globalThis.Bun && error instanceof Error && error.stack?.includes("bun-pty")) {
1099
+ logger.error(`Detected bun-pty issue, attempted to fix it. Please try again.`);
1100
+ init_pty_fix();
1101
+ }
1102
+ throw error;
1103
+ }, spawn)();
1104
+ }
1105
+
1106
+ //#endregion
1107
+ //#region ts/core/context.ts
1108
+ /**
1109
+ * Shared context for agent session
1110
+ *
1111
+ * Groups related state and dependencies for easier passing between modules.
1112
+ * This class encapsulates all stateful components needed during an agent session,
1113
+ * including the PTY shell, configuration, state managers, and flags.
1114
+ *
1115
+ * @example
1116
+ * ```typescript
1117
+ * const ctx = new AgentContext({
1118
+ * shell,
1119
+ * pidStore,
1120
+ * logPaths,
1121
+ * cli: 'claude',
1122
+ * cliConf,
1123
+ * verbose: true,
1124
+ * robust: true
1125
+ * });
1126
+ *
1127
+ * // Access message context for sending messages
1128
+ * await sendMessage(ctx.messageContext, 'Hello');
1129
+ *
1130
+ * // Check and update state
1131
+ * if (ctx.isFatal) {
1132
+ * await exitAgent();
1133
+ * }
1134
+ * ```
1135
+ */
1136
+ var AgentContext = class {
1137
+ shell;
1138
+ pidStore;
1139
+ logPaths;
1140
+ cli;
1141
+ cliConf;
1142
+ verbose;
1143
+ robust;
1144
+ stdinReady = new ReadyManager();
1145
+ stdinFirstReady = new ReadyManager();
1146
+ nextStdout = new ReadyManager();
1147
+ idleWaiter = new IdleWaiter();
1148
+ isFatal = false;
1149
+ shouldRestartWithoutContinue = false;
1150
+ autoYesEnabled = true;
1151
+ constructor(params) {
1152
+ this.shell = params.shell;
1153
+ this.pidStore = params.pidStore;
1154
+ this.logPaths = params.logPaths;
1155
+ this.cli = params.cli;
1156
+ this.cliConf = params.cliConf;
1157
+ this.verbose = params.verbose;
1158
+ this.robust = params.robust;
1159
+ this.autoYesEnabled = params.autoYes ?? true;
1160
+ }
1161
+ /**
1162
+ * Get message context for sendMessage/sendEnter helpers
1163
+ *
1164
+ * Provides a lightweight object with only the dependencies needed
1165
+ * for message sending operations, avoiding circular references.
1166
+ *
1167
+ * @returns MessageContext object for use with sendMessage/sendEnter
1168
+ */
1169
+ get messageContext() {
1170
+ return {
1171
+ shell: this.shell,
1172
+ idleWaiter: this.idleWaiter,
1173
+ stdinReady: this.stdinReady,
1174
+ nextStdout: this.nextStdout
1175
+ };
1176
+ }
1177
+ };
1178
+
1179
+ //#endregion
1180
+ //#region ts/core/streamHelpers.ts
1181
+ /**
1182
+ * Stream processing utilities for terminal I/O
1183
+ *
1184
+ * Provides helper functions for stream lifecycle management.
1185
+ */
1186
+ /**
1187
+ * Create a terminator transform stream that ends when promise resolves
1188
+ *
1189
+ * Creates a TransformStream that automatically terminates when the provided
1190
+ * promise resolves. Used to stop output processing when the agent exits.
1191
+ *
1192
+ * @param exitPromise - Promise that resolves when stream should terminate
1193
+ * @returns TransformStream that terminates on promise resolution
1194
+ *
1195
+ * @example
1196
+ * ```typescript
1197
+ * const exitPromise = Promise.withResolvers<number>();
1198
+ * stream.by(createTerminatorStream(exitPromise.promise));
1199
+ *
1200
+ * // Later, when agent exits:
1201
+ * exitPromise.resolve(0);
1202
+ * ```
1203
+ */
1204
+ function createTerminatorStream(exitPromise) {
1205
+ return new TransformStream({
1206
+ start: function terminator(ctrl) {
1207
+ exitPromise.then(() => ctrl.terminate());
1208
+ },
1209
+ transform: (e, ctrl) => ctrl.enqueue(e),
1210
+ flush: (ctrl) => ctrl.terminate()
1211
+ });
1212
+ }
1213
+
1214
+ //#endregion
1215
+ //#region ts/agentRegistry.ts
1216
+ const MAX_BUFFER_SIZE = 1e3;
1217
+ var AgentRegistry = class {
1218
+ agents = /* @__PURE__ */ new Map();
1219
+ /**
1220
+ * Register a new agent instance
1221
+ */
1222
+ register(pid, instance) {
1223
+ this.agents.set(pid, instance);
1224
+ }
1225
+ /**
1226
+ * Unregister an agent instance
1227
+ */
1228
+ unregister(pid) {
1229
+ this.agents.delete(pid);
1230
+ }
1231
+ /**
1232
+ * Get an agent instance by PID
1233
+ */
1234
+ get(pid) {
1235
+ return this.agents.get(pid);
1236
+ }
1237
+ /**
1238
+ * List all registered agents
1239
+ */
1240
+ list() {
1241
+ return Array.from(this.agents.values());
1242
+ }
1243
+ /**
1244
+ * Append stdout data to an agent's buffer (circular buffer)
1245
+ */
1246
+ appendStdout(pid, data) {
1247
+ const instance = this.agents.get(pid);
1248
+ if (!instance) return;
1249
+ const lines = data.split("\n");
1250
+ instance.stdoutBuffer.push(...lines);
1251
+ if (instance.stdoutBuffer.length > MAX_BUFFER_SIZE) instance.stdoutBuffer = instance.stdoutBuffer.slice(-MAX_BUFFER_SIZE);
1252
+ }
1253
+ /**
1254
+ * Get stdout from an agent's buffer
1255
+ */
1256
+ getStdout(pid, tail) {
1257
+ const instance = this.agents.get(pid);
1258
+ if (!instance) return [];
1259
+ if (tail !== void 0) return instance.stdoutBuffer.slice(-tail);
1260
+ return instance.stdoutBuffer;
1261
+ }
1262
+ };
1263
+ const globalAgentRegistry = new AgentRegistry();
1264
+
1265
+ //#endregion
1266
+ //#region ts/installEnv.ts
1267
+ const installDir = path$1.join(import.meta.dir, "..");
1268
+ function parseEnvContent(content) {
1269
+ const result = {};
1270
+ for (const line of content.split("\n")) {
1271
+ const trimmed = line.trim();
1272
+ if (!trimmed || trimmed.startsWith("#")) continue;
1273
+ const eqIndex = trimmed.indexOf("=");
1274
+ if (eqIndex < 0) continue;
1275
+ const key = trimmed.slice(0, eqIndex).trim();
1276
+ let value = trimmed.slice(eqIndex + 1).trim();
1277
+ if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
1278
+ result[key] = value;
1279
+ }
1280
+ return result;
1281
+ }
1282
+ let _installEnv = null;
1283
+ /**
1284
+ * Load .env from the agent-yes install directory (not the working dir).
1285
+ * Install dir is ${import.meta.dir}/.. relative to this file.
1286
+ * Cached after first load.
1287
+ */
1288
+ async function loadInstallEnv() {
1289
+ if (_installEnv) return _installEnv;
1290
+ const envPath = path$1.join(installDir, ".env");
1291
+ try {
1292
+ _installEnv = parseEnvContent(await readFile(envPath, "utf-8"));
1293
+ } catch {
1294
+ _installEnv = {};
1295
+ }
1296
+ return _installEnv;
1297
+ }
1298
+ /**
1299
+ * Get a value from the install .env, falling back to process.env.
1300
+ */
1301
+ async function getInstallEnv(key) {
1302
+ return (await loadInstallEnv())[key] ?? process.env[key];
1303
+ }
1304
+
1305
+ //#endregion
1306
+ //#region ts/webhookNotifier.ts
1307
+ /**
1308
+ * Notify the AGENT_YES_MESSAGE_WEBHOOK URL with a status message.
1309
+ *
1310
+ * AGENT_YES_MESSAGE_WEBHOOK should be set in the agent-yes install dir .env, e.g.:
1311
+ * AGENT_YES_MESSAGE_WEBHOOK=https://example.com/hook?q=%s
1312
+ *
1313
+ * The %s placeholder is replaced with the URL-encoded message:
1314
+ * [STATUS] hostname:cwd details
1315
+ */
1316
+ async function notifyWebhook(status, details, cwd = process.cwd()) {
1317
+ const webhookTemplate = await getInstallEnv("AGENT_YES_MESSAGE_WEBHOOK");
1318
+ if (!webhookTemplate) return;
1319
+ const message = `[${status}] ${os.hostname()}:${cwd}${details ? " " + details : ""}`;
1320
+ const url = webhookTemplate.replace("%s", encodeURIComponent(message));
1321
+ try {
1322
+ const res = await fetch(url);
1323
+ logger.debug(`[webhook] ${status} notified (${res.status}): ${url}`);
1324
+ } catch (error) {
1325
+ logger.warn(`[webhook] Failed to notify ${status}: ${error}`);
1326
+ }
1327
+ }
1328
+
1329
+ //#endregion
1330
+ //#region ts/index.ts
1331
+ const config = await import("./agent-yes.config-XmUcKFde.js").then((mod) => mod.default || mod);
1332
+ const CLIS_CONFIG = config.clis;
1333
+ /**
1334
+ * Main function to run agent-cli with automatic yes/no responses
1335
+ * @param options Configuration options
1336
+ * @param options.continueOnCrash - If true, automatically restart agent-cli when it crashes:
1337
+ * 1. Shows message 'agent-cli crashed, restarting..'
1338
+ * 2. Spawns a new 'agent-cli --continue' process
1339
+ * 3. Re-attaches the new process to the shell stdio (pipes new process stdin/stdout)
1340
+ * 4. If it crashes with "No conversation found to continue", exits the process
1341
+ * @param options.exitOnIdle - Exit when agent-cli is idle. Boolean or timeout in milliseconds, recommended 5000 - 60000, default is false
1342
+ * @param options.cliArgs - Additional arguments to pass to the agent-cli CLI
1343
+ * @param options.removeControlCharactersFromStdout - Remove ANSI control characters from stdout. Defaults to !process.stdout.isTTY
1344
+ * @param options.disableLock - Disable the running lock feature that prevents concurrent agents in the same directory/repo
1345
+ *
1346
+ * @example
1347
+ * ```typescript
1348
+ * import agentYes from 'agent-yes';
1349
+ * await agentYes({
1350
+ * prompt: 'help me solve all todos in my codebase',
1351
+ *
1352
+ * // optional
1353
+ * cliArgs: ['--verbose'], // additional args to pass to agent-cli
1354
+ * exitOnIdle: 30000, // exit after 30 seconds of idle
1355
+ * robust: true, // auto restart with --continue if claude crashes, default is true
1356
+ * logFile: 'claude-output.log', // save logs to file
1357
+ * disableLock: false, // disable running lock (default is false)
1358
+ * });
1359
+ * ```
1360
+ */
1361
+ 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 }) {
1362
+ if (!cli) throw new Error(`cli is required`);
1363
+ const conf = CLIS_CONFIG[cli] || DIE(`Unsupported cli tool: ${cli}, current process.argv: ${process.argv.join(" ")}`);
1364
+ const workingDir = cwd ?? process.cwd();
1365
+ if (queue) {
1366
+ if (queue && shouldUseLock(workingDir)) await acquireLock(workingDir, prompt ?? "Interactive session");
1367
+ const cleanupLock = async () => {
1368
+ if (queue && shouldUseLock(workingDir)) await releaseLock().catch(() => null);
1369
+ };
1370
+ process.on("exit", () => {
1371
+ if (queue) releaseLock().catch(() => null);
1372
+ });
1373
+ process.on("SIGINT", async (code) => {
1374
+ await cleanupLock();
1375
+ process.exit(code);
1376
+ });
1377
+ process.on("SIGTERM", async (code) => {
1378
+ await cleanupLock();
1379
+ process.exit(code);
1380
+ });
1381
+ }
1382
+ const pidStore = new PidStore(workingDir);
1383
+ await pidStore.init();
1384
+ let userSentCtrlC = false;
1385
+ if (verbose) logger.debug(`[stdin] isTTY: ${process.stdin.isTTY}, setRawMode available: ${!!process.stdin.setRawMode}`);
1386
+ process.stdin.setRawMode?.(true);
1387
+ if (verbose) logger.debug(`[stdin] Raw mode set, isRaw: ${process.stdin.isRaw}`);
1388
+ const terminalStream = new TerminalRenderStream({ mode: "raw" });
1389
+ const terminalRender = terminalStream.getRenderer();
1390
+ const outputWriter = terminalStream.writable.getWriter();
1391
+ logger.debug(`Using ${ptyPackage} for pseudo terminal management.`);
1392
+ if (!!process.env.CLAUDE_PPID) logger.info(`[${cli}-yes] Running as sub-agent (CLAUDE_PPID=${process.env.CLAUDE_PPID})`);
1393
+ const cliConf = CLIS_CONFIG[cli] || {};
1394
+ cliArgs = cliConf.defaultArgs ? [...cliConf.defaultArgs, ...cliArgs] : cliArgs;
1395
+ try {
1396
+ const workingDir = cwd ?? process.cwd();
1397
+ if (useSkills && cli !== "claude") {
1398
+ let gitRoot = null;
1399
+ try {
1400
+ const result = execaCommandSync("git rev-parse --show-toplevel", {
1401
+ cwd: workingDir,
1402
+ reject: false
1403
+ });
1404
+ if (result.exitCode === 0) gitRoot = result.stdout.trim();
1405
+ } catch {}
1406
+ const skillHeaders = [];
1407
+ let currentDir = workingDir;
1408
+ const searchLimit = gitRoot || path.parse(currentDir).root;
1409
+ while (true) {
1410
+ const md = await readFile$1(path.resolve(currentDir, "SKILL.md"), "utf8").catch(() => null);
1411
+ if (md) {
1412
+ const headerMatch = md.match(/^[\s\S]*?(?=\n##\s)/);
1413
+ const headerRaw = (headerMatch ? headerMatch[0] : md).trim();
1414
+ if (headerRaw) {
1415
+ skillHeaders.push(headerRaw);
1416
+ if (verbose) logger.info(`[skills] Found SKILL.md in ${currentDir} (${headerRaw.length} chars)`);
1417
+ }
1418
+ }
1419
+ if (currentDir === searchLimit) break;
1420
+ const parentDir = path.dirname(currentDir);
1421
+ if (parentDir === currentDir) break;
1422
+ currentDir = parentDir;
1423
+ }
1424
+ if (skillHeaders.length > 0) {
1425
+ const combined = skillHeaders.join("\n\n---\n\n");
1426
+ const MAX = 2e3;
1427
+ const header = combined.length > MAX ? combined.slice(0, MAX) + "…" : combined;
1428
+ const prefix = `Use this repository skill as context:\n\n${header}`;
1429
+ prompt = prompt ? `${prefix}\n\n${prompt}` : prefix;
1430
+ if (verbose) logger.info(`[skills] Injected ${skillHeaders.length} SKILL.md header(s) (${header.length} chars total)`);
1431
+ } else if (verbose) logger.info("[skills] No SKILL.md found in directory hierarchy");
1432
+ }
1433
+ } catch (error) {
1434
+ if (verbose) logger.warn("[skills] Failed to inject SKILL.md header:", { error });
1435
+ }
1436
+ if (resume) if (cli === "codex" && resume) {
1437
+ const storedSessionId = await getSessionForCwd(workingDir);
1438
+ if (storedSessionId) {
1439
+ cliArgs = [
1440
+ "resume",
1441
+ storedSessionId,
1442
+ ...cliArgs
1443
+ ];
1444
+ await logger.debug(`resume|using stored session ID: ${storedSessionId}`);
1445
+ } else throw new Error(`No stored session found for codex in directory: ${workingDir}, please try without resume option.`);
1446
+ } else if (cli === "claude") {
1447
+ cliArgs = ["--continue", ...cliArgs];
1448
+ await logger.debug(`resume|adding --continue flag for claude`);
1449
+ } else if (cli === "gemini") {
1450
+ cliArgs = ["--resume", ...cliArgs];
1451
+ await logger.debug(`resume|adding --resume flag for gemini`);
1452
+ } else throw new Error(`Resume option is not supported for cli: ${cli}, make a feature request if you want it. https://github.com/snomiao/agent-yes/issues`);
1453
+ if (prompt && cliConf.promptArg) if (cliConf.promptArg === "first-arg") {
1454
+ cliArgs = [prompt, ...cliArgs];
1455
+ prompt = void 0;
1456
+ } else if (cliConf.promptArg === "last-arg") {
1457
+ cliArgs = [...cliArgs, prompt];
1458
+ prompt = void 0;
1459
+ } else if (cliConf.promptArg.startsWith("--")) {
1460
+ cliArgs = [
1461
+ cliConf.promptArg,
1462
+ prompt,
1463
+ ...cliArgs
1464
+ ];
1465
+ prompt = void 0;
1466
+ } else logger.warn(`Unknown promptArg format: ${cliConf.promptArg}`);
1467
+ const ptyEnv = { ...env ?? process.env };
1468
+ const ptyOptions = {
1469
+ name: "xterm-color",
1470
+ ...getTerminalDimensions(),
1471
+ cwd: cwd ?? process.cwd(),
1472
+ env: ptyEnv
1473
+ };
1474
+ let shell = spawnAgent({
1475
+ cli,
1476
+ cliConf,
1477
+ cliArgs,
1478
+ verbose,
1479
+ install,
1480
+ ptyOptions
1481
+ });
1482
+ try {
1483
+ await pidStore.registerProcess({
1484
+ pid: shell.pid,
1485
+ cli,
1486
+ args: cliArgs,
1487
+ prompt,
1488
+ cwd: workingDir
1489
+ });
1490
+ } catch (error) {
1491
+ logger.warn(`[pidStore] Failed to register process ${shell.pid}:`, error);
1492
+ }
1493
+ notifyWebhook("RUNNING", prompt ?? "", workingDir).catch(() => null);
1494
+ const logPaths = await initializeLogPaths(pidStore, shell.pid);
1495
+ setupDebugLogging(logPaths.debuggingLogsPath);
1496
+ const ctx = new AgentContext({
1497
+ shell,
1498
+ pidStore,
1499
+ logPaths,
1500
+ cli,
1501
+ cliConf,
1502
+ verbose,
1503
+ robust,
1504
+ autoYes
1505
+ });
1506
+ try {
1507
+ globalAgentRegistry.register(shell.pid, {
1508
+ pid: shell.pid,
1509
+ context: ctx,
1510
+ cwd: workingDir,
1511
+ cli,
1512
+ prompt,
1513
+ startTime: Date.now(),
1514
+ stdoutBuffer: []
1515
+ });
1516
+ } catch (error) {
1517
+ logger.warn(`[agentRegistry] Failed to register agent ${shell.pid}:`, error);
1518
+ }
1519
+ if (!ctx.autoYesEnabled) process.stderr.write("\x1B[33m[auto-yes: OFF]\x1B[0m Press Ctrl+Y to toggle\n");
1520
+ if (cliConf.ready && cliConf.ready.length === 0 || !ctx.autoYesEnabled) {
1521
+ ctx.stdinReady.ready();
1522
+ ctx.stdinFirstReady.ready();
1523
+ }
1524
+ sleep(1e4).then(() => {
1525
+ if (!ctx.stdinReady.isReady) ctx.stdinReady.ready();
1526
+ if (!ctx.stdinFirstReady.isReady) ctx.stdinFirstReady.ready();
1527
+ });
1528
+ const pendingExitCode = Promise.withResolvers();
1529
+ function onData(data) {
1530
+ const currentPid = shell.pid;
1531
+ outputWriter.write(data);
1532
+ globalAgentRegistry.appendStdout(currentPid, data);
1533
+ }
1534
+ shell.onData(onData);
1535
+ shell.onExit(async function onExit({ exitCode }) {
1536
+ const exitedPid = shell.pid;
1537
+ globalAgentRegistry.unregister(exitedPid);
1538
+ ctx.stdinReady.unready();
1539
+ const agentCrashed = exitCode !== 0 && !(exitCode === 130 || exitCode === 143 || userSentCtrlC);
1540
+ if (ctx.shouldRestartWithoutContinue) {
1541
+ try {
1542
+ await pidStore.updateStatus(exitedPid, "exited", {
1543
+ exitReason: "restarted",
1544
+ exitCode: exitCode ?? void 0
1545
+ });
1546
+ } catch (error) {
1547
+ logger.warn(`[pidStore] Failed to update status for PID ${exitedPid}:`, error);
1548
+ }
1549
+ ctx.shouldRestartWithoutContinue = false;
1550
+ ctx.isFatal = false;
1551
+ let [bin, ...args] = [...parseCommandString(cliConf?.binary || cli), ...cliArgs.filter((arg) => !["--continue", "--resume"].includes(arg))];
1552
+ logger.info(`Restarting ${cli} ${JSON.stringify([bin, ...args])}`);
1553
+ const restartPtyOptions = {
1554
+ name: "xterm-color",
1555
+ ...getTerminalDimensions(),
1556
+ cwd: cwd ?? process.cwd(),
1557
+ env: ptyEnv
1558
+ };
1559
+ shell = pty.spawn(bin, args, restartPtyOptions);
1560
+ try {
1561
+ await pidStore.registerProcess({
1562
+ pid: shell.pid,
1563
+ cli,
1564
+ args,
1565
+ prompt,
1566
+ cwd: workingDir
1567
+ });
1568
+ } catch (error) {
1569
+ logger.warn(`[pidStore] Failed to register restarted process ${shell.pid}:`, error);
1570
+ }
1571
+ ctx.shell = shell;
1572
+ try {
1573
+ globalAgentRegistry.register(shell.pid, {
1574
+ pid: shell.pid,
1575
+ context: ctx,
1576
+ cwd: workingDir,
1577
+ cli,
1578
+ prompt,
1579
+ startTime: Date.now(),
1580
+ stdoutBuffer: []
1581
+ });
1582
+ } catch (error) {
1583
+ logger.warn(`[agentRegistry] Failed to register restarted agent ${shell.pid}:`, error);
1584
+ }
1585
+ shell.onData(onData);
1586
+ shell.onExit(onExit);
1587
+ if (cliConf.ready && cliConf.ready.length === 0 || !ctx.autoYesEnabled) {
1588
+ ctx.stdinReady.ready();
1589
+ ctx.stdinFirstReady.ready();
1590
+ }
1591
+ return;
1592
+ }
1593
+ if (agentCrashed && robust && conf?.restoreArgs) {
1594
+ if (!conf.restoreArgs) {
1595
+ logger.warn(`robust is only supported for ${Object.entries(CLIS_CONFIG).filter(([_, v]) => v.restoreArgs).map(([k]) => k).join(", ")} currently, not ${cli}`);
1596
+ return;
1597
+ }
1598
+ if (ctx.isFatal) {
1599
+ try {
1600
+ await pidStore.updateStatus(exitedPid, "exited", {
1601
+ exitReason: "fatal",
1602
+ exitCode: exitCode ?? void 0
1603
+ });
1604
+ } catch (error) {
1605
+ logger.warn(`[pidStore] Failed to update status for PID ${exitedPid}:`, error);
1606
+ }
1607
+ notifyWebhook("EXIT", `fatal exitCode=${exitCode ?? "?"}`, workingDir).catch(() => null);
1608
+ return pendingExitCode.resolve(exitCode);
1609
+ }
1610
+ try {
1611
+ await pidStore.updateStatus(exitedPid, "exited", {
1612
+ exitReason: "restarted",
1613
+ exitCode: exitCode ?? void 0
1614
+ });
1615
+ } catch (error) {
1616
+ logger.warn(`[pidStore] Failed to update status for PID ${exitedPid}:`, error);
1617
+ }
1618
+ logger.info(`${cli} crashed (exit code: ${exitCode}), restarting...`);
1619
+ let restoreArgs = conf.restoreArgs;
1620
+ if (cli === "codex") {
1621
+ const storedSessionId = await getSessionForCwd(workingDir);
1622
+ if (storedSessionId) {
1623
+ restoreArgs = ["resume", storedSessionId];
1624
+ logger.debug(`restore|using stored session ID: ${storedSessionId}`);
1625
+ } else logger.debug(`restore|no stored session, using default restore args`);
1626
+ }
1627
+ const restorePtyOptions = {
1628
+ name: "xterm-color",
1629
+ ...getTerminalDimensions(),
1630
+ cwd: cwd ?? process.cwd(),
1631
+ env: ptyEnv
1632
+ };
1633
+ shell = pty.spawn(cli, restoreArgs, restorePtyOptions);
1634
+ try {
1635
+ await pidStore.registerProcess({
1636
+ pid: shell.pid,
1637
+ cli,
1638
+ args: restoreArgs,
1639
+ prompt,
1640
+ cwd: workingDir
1641
+ });
1642
+ } catch (error) {
1643
+ logger.warn(`[pidStore] Failed to register restored process ${shell.pid}:`, error);
1644
+ }
1645
+ ctx.shell = shell;
1646
+ try {
1647
+ globalAgentRegistry.register(shell.pid, {
1648
+ pid: shell.pid,
1649
+ context: ctx,
1650
+ cwd: workingDir,
1651
+ cli,
1652
+ prompt,
1653
+ startTime: Date.now(),
1654
+ stdoutBuffer: []
1655
+ });
1656
+ } catch (error) {
1657
+ logger.warn(`[agentRegistry] Failed to register restored agent ${shell.pid}:`, error);
1658
+ }
1659
+ shell.onData(onData);
1660
+ shell.onExit(onExit);
1661
+ if (cliConf.ready && cliConf.ready.length === 0 || !ctx.autoYesEnabled) {
1662
+ ctx.stdinReady.ready();
1663
+ ctx.stdinFirstReady.ready();
1664
+ }
1665
+ return;
1666
+ }
1667
+ const exitReason = agentCrashed ? "crash" : "normal";
1668
+ try {
1669
+ await pidStore.updateStatus(exitedPid, "exited", {
1670
+ exitReason,
1671
+ exitCode: exitCode ?? void 0
1672
+ });
1673
+ } catch (error) {
1674
+ logger.warn(`[pidStore] Failed to update status for PID ${exitedPid}:`, error);
1675
+ }
1676
+ notifyWebhook("EXIT", `${exitReason} exitCode=${exitCode ?? "?"}`, workingDir).catch(() => null);
1677
+ return pendingExitCode.resolve(exitCode);
1678
+ });
1679
+ process.stdout.on("resize", () => {
1680
+ const { cols, rows } = getTerminalDimensions();
1681
+ shell.resize(cols, rows);
1682
+ });
1683
+ const isStillWorkingQ = () => {
1684
+ const rendered = terminalRender.tail(24).replace(/\s+/g, " ");
1685
+ return conf.working?.some((rgx) => rgx.test(rendered));
1686
+ };
1687
+ let lastHeartbeatRendered = "";
1688
+ const heartbeatInterval = setInterval(async () => {
1689
+ try {
1690
+ const rendered = removeControlCharacters(terminalRender.tail(12));
1691
+ if (rendered === lastHeartbeatRendered) return;
1692
+ lastHeartbeatRendered = rendered;
1693
+ const lines = rendered.split("\n").filter((line) => line.trim());
1694
+ for (const line of lines) {
1695
+ if (conf.ready?.some((rx) => rx.test(line))) {
1696
+ logger.debug(`heartbeat|ready |${line}`);
1697
+ ctx.stdinReady.ready();
1698
+ ctx.stdinFirstReady.ready();
1699
+ }
1700
+ if (conf.enter?.some((rx) => rx.test(line))) {
1701
+ logger.debug(`heartbeat|sendEnter matched|${line}`);
1702
+ await sendEnter(ctx.messageContext, 400);
1703
+ continue;
1704
+ }
1705
+ const typeingRespondMatched = Object.entries(conf.typingRespond ?? {}).filter(([_sendString, onThePatterns]) => onThePatterns.some((rx) => rx.test(line)));
1706
+ if (typeingRespondMatched.length) {
1707
+ await sflow(typeingRespondMatched).map(async ([sendString]) => await sendMessage(ctx.messageContext, sendString, { waitForReady: false })).toCount();
1708
+ continue;
1709
+ }
1710
+ if (conf.fatal?.some((rx) => rx.test(line))) {
1711
+ logger.debug(`heartbeat|fatal |${line}`);
1712
+ ctx.isFatal = true;
1713
+ await exitAgent();
1714
+ break;
1715
+ }
1716
+ if (conf.restartWithoutContinueArg?.some((rx) => rx.test(line))) {
1717
+ logger.debug(`heartbeat|restart-without-continue|${line}`);
1718
+ ctx.shouldRestartWithoutContinue = true;
1719
+ ctx.isFatal = true;
1720
+ await exitAgent();
1721
+ break;
1722
+ }
1723
+ if (cli === "codex") {
1724
+ const sessionId = extractSessionId(line);
1725
+ if (sessionId) {
1726
+ logger.debug(`heartbeat|session|captured session ID: ${sessionId}`);
1727
+ await storeSessionForCwd(workingDir, sessionId);
1728
+ }
1729
+ }
1730
+ }
1731
+ } catch (error) {
1732
+ logger.debug(`heartbeat|error: ${error}`);
1733
+ }
1734
+ }, 800);
1735
+ const cleanupHeartbeat = () => clearInterval(heartbeatInterval);
1736
+ shell.onExit(cleanupHeartbeat);
1737
+ if (exitOnIdle) ctx.idleWaiter.wait(exitOnIdle).then(async () => {
1738
+ await pidStore.updateStatus(shell.pid, "idle").catch(() => null);
1739
+ if (isStillWorkingQ()) {
1740
+ logger.warn(`[${cli}-yes] ${cli} is idle, but seems still working, not exiting yet`);
1741
+ return;
1742
+ }
1743
+ logger.info(`[${cli}-yes] ${cli} is idle, exiting...`);
1744
+ notifyWebhook("IDLE", "", workingDir).catch(() => null);
1745
+ await exitAgent();
1746
+ });
1747
+ const stdinStream = new ReadableStream({
1748
+ start(controller) {
1749
+ process.stdin.resume();
1750
+ let closed = false;
1751
+ const dataHandler = (chunk) => {
1752
+ try {
1753
+ controller.enqueue(chunk);
1754
+ } catch {}
1755
+ };
1756
+ const endHandler = () => {
1757
+ if (closed) return;
1758
+ closed = true;
1759
+ try {
1760
+ controller.close();
1761
+ } catch {}
1762
+ };
1763
+ const errorHandler = (err) => {
1764
+ if (closed) return;
1765
+ closed = true;
1766
+ try {
1767
+ controller.error(err);
1768
+ } catch {}
1769
+ };
1770
+ process.stdin.on("data", dataHandler);
1771
+ process.stdin.on("end", endHandler);
1772
+ process.stdin.on("close", endHandler);
1773
+ process.stdin.on("error", errorHandler);
1774
+ },
1775
+ cancel(reason) {
1776
+ process.stdin.pause();
1777
+ }
1778
+ });
1779
+ let aborted = false;
1780
+ await sflow(stdinStream).map((buffer) => {
1781
+ const str = buffer.toString();
1782
+ const CTRL_Z = "";
1783
+ const CTRL_C = "";
1784
+ if (!aborted && str === CTRL_Z) return "";
1785
+ if (!aborted && !ctx.stdinReady.isReady && str === CTRL_C) {
1786
+ logger.error("User aborted: SIGINT");
1787
+ shell.kill("SIGINT");
1788
+ pendingExitCode.resolve(130);
1789
+ aborted = true;
1790
+ return str;
1791
+ }
1792
+ if (str === CTRL_C) {
1793
+ userSentCtrlC = true;
1794
+ setTimeout(() => {
1795
+ userSentCtrlC = false;
1796
+ }, 2e3);
1797
+ }
1798
+ return str;
1799
+ }).map((() => {
1800
+ let line = "";
1801
+ const toggleAutoYes = () => {
1802
+ ctx.autoYesEnabled = !ctx.autoYesEnabled;
1803
+ if (!ctx.autoYesEnabled) {
1804
+ ctx.stdinReady.ready();
1805
+ ctx.stdinFirstReady.ready();
1806
+ }
1807
+ const status = ctx.autoYesEnabled ? "\x1B[32m[auto-yes: ON]\x1B[0m" : "\x1B[33m[auto-yes: OFF]\x1B[0m";
1808
+ process.stderr.write(`\r${status} (Ctrl+Y to toggle)\n`);
1809
+ };
1810
+ return (data) => {
1811
+ let out = "";
1812
+ for (const ch of data) {
1813
+ if (ch === "") {
1814
+ toggleAutoYes();
1815
+ continue;
1816
+ }
1817
+ if (ch === "\r" || ch === "\n") {
1818
+ if (line.length <= 20) {
1819
+ if (line.replace(/[\x00-\x1f]|\x1b\[[0-9;]*[A-Za-z]|\[[A-Z]/g, "").trim() === "/auto") {
1820
+ out += "";
1821
+ toggleAutoYes();
1822
+ line = "";
1823
+ continue;
1824
+ }
1825
+ }
1826
+ line = "";
1827
+ out += ch;
1828
+ continue;
1829
+ }
1830
+ if (ch === "" || ch === "\b") {
1831
+ if (line.length > 0) line = line.slice(0, -1);
1832
+ out += ch;
1833
+ continue;
1834
+ }
1835
+ if (ch >= " " && ch <= "~" && line.length < 50) line += ch;
1836
+ out += ch;
1837
+ }
1838
+ return out;
1839
+ };
1840
+ })()).onStart(async function promptOnStart() {
1841
+ logger.debug("Sending prompt message: " + JSON.stringify(prompt));
1842
+ if (prompt) await sendMessage(ctx.messageContext, prompt);
1843
+ }).by({
1844
+ writable: new WritableStream({ write: async (data) => {
1845
+ await ctx.stdinReady.wait();
1846
+ shell.write(data);
1847
+ } }),
1848
+ readable: terminalStream.readable
1849
+ }).forEach(() => {
1850
+ ctx.idleWaiter.ping();
1851
+ pidStore.updateStatus(shell.pid, "active").catch(() => null);
1852
+ ctx.nextStdout.ready();
1853
+ }).forkTo(async function rawLogger(f) {
1854
+ const rawLogPath = ctx.logPaths.rawLogPath;
1855
+ if (!rawLogPath) return f.run();
1856
+ return await mkdir$1(path.dirname(rawLogPath), { recursive: true }).then(() => {
1857
+ logger.debug(`[${cli}-yes] raw logs streaming to ${rawLogPath}`);
1858
+ return f.forEach(async (chars) => {
1859
+ await writeFile$1(rawLogPath, chars, { flag: "a" }).catch(() => null);
1860
+ }).run();
1861
+ }).catch(() => f.run());
1862
+ }).by(function consoleResponder(e) {
1863
+ let lastRendered = "";
1864
+ return e.forEach((chunk) => {
1865
+ terminalRender.write(chunk);
1866
+ if (chunk.includes("\x1B[c") || chunk.includes("\x1B[0c")) {
1867
+ shell.write("\x1B[?1;2c");
1868
+ if (verbose) logger.debug("device|respond DA: VT100 with Advanced Video Option");
1869
+ return;
1870
+ }
1871
+ if (process.stdin.isTTY) return;
1872
+ if (!chunk.includes("\x1B[6n")) return;
1873
+ const { col, row } = terminalRender.getCursorPosition();
1874
+ shell.write(`\u001b[${row};${col}R`);
1875
+ logger.debug(`cursor|respond position: row=${String(row)}, col=${String(col)}`);
1876
+ }).forEach(async (line, lineIndex) => {
1877
+ if (terminalRender.tail(24) === lastRendered) return;
1878
+ logger.debug(`stdout|${line}`);
1879
+ if (conf.ready?.some((rx) => line.match(rx))) {
1880
+ logger.debug(`ready |${line}`);
1881
+ if (cli === "gemini" && lineIndex <= 80) return;
1882
+ ctx.stdinReady.ready();
1883
+ ctx.stdinFirstReady.ready();
1884
+ }
1885
+ if (conf.enter?.some((rx) => line.match(rx))) {
1886
+ logger.debug(`sendEnter matched|${line}`);
1887
+ return await sendEnter(ctx.messageContext, 400);
1888
+ }
1889
+ const typeingRespondMatched = Object.entries(conf.typingRespond ?? {}).filter(([_sendString, onThePatterns]) => onThePatterns.some((rx) => line.match(rx)));
1890
+ if (typeingRespondMatched.length && await sflow(typeingRespondMatched).map(async ([sendString]) => await sendMessage(ctx.messageContext, sendString, { waitForReady: false })).toCount()) return;
1891
+ if (conf.fatal?.some((rx) => line.match(rx))) {
1892
+ logger.debug(`fatal |${line}`);
1893
+ ctx.isFatal = true;
1894
+ await exitAgent();
1895
+ }
1896
+ if (conf.restartWithoutContinueArg?.some((rx) => line.match(rx))) {
1897
+ logger.debug(`restart-without-continue|${line}`);
1898
+ ctx.shouldRestartWithoutContinue = true;
1899
+ ctx.isFatal = true;
1900
+ await exitAgent();
1901
+ }
1902
+ if (cli === "codex") {
1903
+ const sessionId = extractSessionId(line);
1904
+ if (sessionId) {
1905
+ logger.debug(`session|captured session ID: ${sessionId}`);
1906
+ await storeSessionForCwd(workingDir, sessionId);
1907
+ }
1908
+ }
1909
+ });
1910
+ }).by((s) => removeControlCharactersFromStdout ? s.map((e) => removeControlCharacters(e)) : s).by(createTerminatorStream(pendingExitCode.promise)).to(fromWritable(process.stdout));
1911
+ await saveLogFile(ctx.logPaths.logPath, terminalRender.render());
1912
+ const exitCode = await pendingExitCode.promise;
1913
+ logger.info(`[${cli}-yes] ${cli} exited with code ${exitCode}`);
1914
+ await pidStore.close();
1915
+ await outputWriter.close();
1916
+ await saveDeprecatedLogFile(logFile, terminalRender.render(), verbose);
1917
+ return {
1918
+ exitCode,
1919
+ logs: terminalRender.render()
1920
+ };
1921
+ async function exitAgent() {
1922
+ ctx.robust = false;
1923
+ for (const cmd of cliConf.exitCommands ?? ["/exit"]) await sendMessage(ctx.messageContext, cmd);
1924
+ let exited = false;
1925
+ await Promise.race([pendingExitCode.promise.then(() => exited = true), new Promise((resolve) => setTimeout(() => {
1926
+ if (exited) return;
1927
+ shell.kill();
1928
+ resolve();
1929
+ }, 5e3))]);
1930
+ }
1931
+ function getTerminalDimensions() {
1932
+ if (!process.stdout.isTTY) return {
1933
+ cols: 80,
1934
+ rows: 24
1935
+ };
1936
+ return {
1937
+ cols: Math.max(20, process.stdout.columns),
1938
+ rows: process.stdout.rows
1939
+ };
1940
+ }
1941
+ }
1942
+ function sleep(ms) {
1943
+ return new Promise((resolve) => setTimeout(resolve, ms));
1944
+ }
1945
+
1946
+ //#endregion
1947
+ //#region ts/SUPPORTED_CLIS.ts
1948
+ const SUPPORTED_CLIS = Object.keys(CLIS_CONFIG);
1949
+
1950
+ //#endregion
1951
+ 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 };
1952
+ //# sourceMappingURL=SUPPORTED_CLIS-TKiMwx-X.js.map