agent-yes 1.70.1 → 1.72.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{SUPPORTED_CLIS-Bq5hoKRN.js → SUPPORTED_CLIS-jR_I2op4.js} +4 -248
- package/dist/{agent-yes.config-DcxG25Gv.js → agent-yes.config-CtQprJrA.js} +1 -1
- package/dist/cli.js +15 -1
- package/dist/index.js +1 -1
- package/dist/runningLock-BBI_URhR.js +263 -0
- package/dist/tray-Dyiihcrq.js +180 -0
- package/package.json +4 -1
- package/ts/cli.ts +13 -0
- package/ts/parseCliArgs.ts +6 -0
- package/ts/runningLock.ts +14 -0
- package/ts/tray.spec.ts +340 -0
- package/ts/tray.ts +205 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { t as logger } from "./logger-CX77vJDA.js";
|
|
2
|
+
import { i as shouldUseLock, r as releaseLock, t as acquireLock } from "./runningLock-BBI_URhR.js";
|
|
2
3
|
import { arch, platform } from "process";
|
|
3
4
|
import { execSync } from "child_process";
|
|
4
5
|
import { execaCommandSync, parseCommandString } from "execa";
|
|
@@ -178,251 +179,6 @@ function removeControlCharacters(str) {
|
|
|
178
179
|
return str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "");
|
|
179
180
|
}
|
|
180
181
|
|
|
181
|
-
//#endregion
|
|
182
|
-
//#region ts/runningLock.ts
|
|
183
|
-
const getLockDir = () => path.join(process.env.CLAUDE_YES_HOME || homedir(), ".claude-yes");
|
|
184
|
-
const getLockFile = () => path.join(getLockDir(), "running.lock.json");
|
|
185
|
-
const MAX_RETRIES = 5;
|
|
186
|
-
const RETRY_DELAYS = [
|
|
187
|
-
50,
|
|
188
|
-
100,
|
|
189
|
-
200,
|
|
190
|
-
400,
|
|
191
|
-
800
|
|
192
|
-
];
|
|
193
|
-
const POLL_INTERVAL = 2e3;
|
|
194
|
-
/**
|
|
195
|
-
* Check if a process is running
|
|
196
|
-
*/
|
|
197
|
-
function isProcessRunning(pid) {
|
|
198
|
-
try {
|
|
199
|
-
process.kill(pid, 0);
|
|
200
|
-
return true;
|
|
201
|
-
} catch {
|
|
202
|
-
return false;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
|
-
* Get git repository root for a directory
|
|
207
|
-
*/
|
|
208
|
-
function getGitRoot(cwd) {
|
|
209
|
-
try {
|
|
210
|
-
return execSync("git rev-parse --show-toplevel", {
|
|
211
|
-
cwd,
|
|
212
|
-
encoding: "utf8",
|
|
213
|
-
stdio: [
|
|
214
|
-
"pipe",
|
|
215
|
-
"pipe",
|
|
216
|
-
"ignore"
|
|
217
|
-
]
|
|
218
|
-
}).trim();
|
|
219
|
-
} catch {
|
|
220
|
-
return null;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* Check if directory is in a git repository
|
|
225
|
-
*/
|
|
226
|
-
function isGitRepo(cwd) {
|
|
227
|
-
try {
|
|
228
|
-
return getGitRoot(cwd) !== null;
|
|
229
|
-
} catch {
|
|
230
|
-
return false;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* Resolve path to real path (handling symlinks)
|
|
235
|
-
*/
|
|
236
|
-
function resolveRealPath(p) {
|
|
237
|
-
try {
|
|
238
|
-
return path.resolve(p);
|
|
239
|
-
} catch {
|
|
240
|
-
return p;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
/**
|
|
244
|
-
* Sleep for a given number of milliseconds
|
|
245
|
-
*/
|
|
246
|
-
function sleep$1(ms) {
|
|
247
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
248
|
-
}
|
|
249
|
-
/**
|
|
250
|
-
* Read lock file with retry logic and stale lock cleanup
|
|
251
|
-
*/
|
|
252
|
-
async function readLockFile() {
|
|
253
|
-
try {
|
|
254
|
-
const lockDir = getLockDir();
|
|
255
|
-
const lockFilePath = getLockFile();
|
|
256
|
-
await mkdir(lockDir, { recursive: true });
|
|
257
|
-
if (!existsSync(lockFilePath)) return { tasks: [] };
|
|
258
|
-
const content = await readFile(lockFilePath, "utf8");
|
|
259
|
-
const lockFile = JSON.parse(content);
|
|
260
|
-
lockFile.tasks = lockFile.tasks.filter((task) => {
|
|
261
|
-
if (isProcessRunning(task.pid)) return true;
|
|
262
|
-
return false;
|
|
263
|
-
});
|
|
264
|
-
return lockFile;
|
|
265
|
-
} catch {
|
|
266
|
-
return { tasks: [] };
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* Write lock file atomically with retry logic
|
|
271
|
-
*/
|
|
272
|
-
async function writeLockFile(lockFile, retryCount = 0) {
|
|
273
|
-
try {
|
|
274
|
-
const lockDir = getLockDir();
|
|
275
|
-
const lockFilePath = getLockFile();
|
|
276
|
-
await mkdir(lockDir, { recursive: true });
|
|
277
|
-
const tempFile = `${lockFilePath}.tmp.${process.pid}`;
|
|
278
|
-
await writeFile(tempFile, JSON.stringify(lockFile, null, 2), "utf8");
|
|
279
|
-
await rename(tempFile, lockFilePath);
|
|
280
|
-
} catch (error) {
|
|
281
|
-
if (retryCount < MAX_RETRIES) {
|
|
282
|
-
await sleep$1(RETRY_DELAYS[retryCount] || 800);
|
|
283
|
-
return writeLockFile(lockFile, retryCount + 1);
|
|
284
|
-
}
|
|
285
|
-
throw error;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
/**
|
|
289
|
-
* Check if lock exists for the current working directory
|
|
290
|
-
*/
|
|
291
|
-
async function checkLock(cwd, _prompt) {
|
|
292
|
-
const resolvedCwd = resolveRealPath(cwd);
|
|
293
|
-
const gitRoot = isGitRepo(resolvedCwd) ? getGitRoot(resolvedCwd) : null;
|
|
294
|
-
const lockKey = gitRoot || resolvedCwd;
|
|
295
|
-
const blockingTasks = (await readLockFile()).tasks.filter((task) => {
|
|
296
|
-
if (!isProcessRunning(task.pid)) return false;
|
|
297
|
-
if (task.status !== "running") return false;
|
|
298
|
-
if (gitRoot && task.gitRoot) return task.gitRoot === gitRoot;
|
|
299
|
-
else return task.cwd === lockKey;
|
|
300
|
-
});
|
|
301
|
-
return {
|
|
302
|
-
isLocked: blockingTasks.length > 0,
|
|
303
|
-
blockingTasks,
|
|
304
|
-
lockKey
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
/**
|
|
308
|
-
* Add a task to the lock file
|
|
309
|
-
*/
|
|
310
|
-
async function addTask(task) {
|
|
311
|
-
const lockFile = await readLockFile();
|
|
312
|
-
lockFile.tasks = lockFile.tasks.filter((t) => t.pid !== task.pid);
|
|
313
|
-
lockFile.tasks.push(task);
|
|
314
|
-
await writeLockFile(lockFile);
|
|
315
|
-
}
|
|
316
|
-
/**
|
|
317
|
-
* Update task status
|
|
318
|
-
*/
|
|
319
|
-
async function updateTaskStatus(pid, status) {
|
|
320
|
-
const lockFile = await readLockFile();
|
|
321
|
-
const task = lockFile.tasks.find((t) => t.pid === pid);
|
|
322
|
-
if (task) {
|
|
323
|
-
task.status = status;
|
|
324
|
-
await writeLockFile(lockFile);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
/**
|
|
328
|
-
* Remove a task from the lock file
|
|
329
|
-
*/
|
|
330
|
-
async function removeTask(pid) {
|
|
331
|
-
const lockFile = await readLockFile();
|
|
332
|
-
lockFile.tasks = lockFile.tasks.filter((t) => t.pid !== pid);
|
|
333
|
-
await writeLockFile(lockFile);
|
|
334
|
-
}
|
|
335
|
-
/**
|
|
336
|
-
* Wait for lock to be released
|
|
337
|
-
*/
|
|
338
|
-
async function waitForUnlock(blockingTasks, currentTask) {
|
|
339
|
-
const blockingTask = blockingTasks[0];
|
|
340
|
-
if (!blockingTask) return;
|
|
341
|
-
console.log(`⏳ Queueing for unlock of: ${blockingTask.task}`);
|
|
342
|
-
console.log(` Press 'b' to bypass queue, 'k' to kill previous instance`);
|
|
343
|
-
await addTask({
|
|
344
|
-
...currentTask,
|
|
345
|
-
status: "queued"
|
|
346
|
-
});
|
|
347
|
-
const stdin = process.stdin;
|
|
348
|
-
const wasRaw = stdin.isRaw;
|
|
349
|
-
stdin.setRawMode?.(true);
|
|
350
|
-
stdin.resume();
|
|
351
|
-
let bypassed = false;
|
|
352
|
-
let killed = false;
|
|
353
|
-
const keyHandler = (key) => {
|
|
354
|
-
const char = key.toString();
|
|
355
|
-
if (char === "b" || char === "B") {
|
|
356
|
-
console.log("\n⚡ Bypassing queue...");
|
|
357
|
-
bypassed = true;
|
|
358
|
-
} else if (char === "k" || char === "K") {
|
|
359
|
-
console.log("\n🔪 Killing previous instance...");
|
|
360
|
-
killed = true;
|
|
361
|
-
}
|
|
362
|
-
};
|
|
363
|
-
stdin.on("data", keyHandler);
|
|
364
|
-
let dots = 0;
|
|
365
|
-
while (true) {
|
|
366
|
-
if (bypassed) {
|
|
367
|
-
await updateTaskStatus(currentTask.pid, "running");
|
|
368
|
-
console.log("✓ Queue bypassed, starting task...");
|
|
369
|
-
break;
|
|
370
|
-
}
|
|
371
|
-
if (killed && blockingTask) {
|
|
372
|
-
try {
|
|
373
|
-
process.kill(blockingTask.pid, "SIGTERM");
|
|
374
|
-
console.log(`✓ Killed process ${blockingTask.pid}`);
|
|
375
|
-
await sleep$1(1e3);
|
|
376
|
-
} catch (err) {
|
|
377
|
-
console.log(`⚠️ Could not kill process ${blockingTask.pid}: ${err}`);
|
|
378
|
-
}
|
|
379
|
-
killed = false;
|
|
380
|
-
}
|
|
381
|
-
await sleep$1(POLL_INTERVAL);
|
|
382
|
-
if (!(await checkLock(currentTask.cwd, currentTask.task)).isLocked) {
|
|
383
|
-
await updateTaskStatus(currentTask.pid, "running");
|
|
384
|
-
console.log(`\n✓ Lock released, starting task...`);
|
|
385
|
-
break;
|
|
386
|
-
}
|
|
387
|
-
dots = (dots + 1) % 4;
|
|
388
|
-
process.stdout.write(`\r⏳ Queueing${".".repeat(dots)}${" ".repeat(3 - dots)}`);
|
|
389
|
-
}
|
|
390
|
-
stdin.off("data", keyHandler);
|
|
391
|
-
stdin.setRawMode?.(wasRaw);
|
|
392
|
-
if (!wasRaw) stdin.pause();
|
|
393
|
-
}
|
|
394
|
-
/**
|
|
395
|
-
* Acquire lock or wait if locked
|
|
396
|
-
*/
|
|
397
|
-
async function acquireLock(cwd, prompt = "no prompt provided") {
|
|
398
|
-
const resolvedCwd = resolveRealPath(cwd);
|
|
399
|
-
const task = {
|
|
400
|
-
cwd: resolvedCwd,
|
|
401
|
-
gitRoot: (isGitRepo(resolvedCwd) ? getGitRoot(resolvedCwd) : null) || void 0,
|
|
402
|
-
task: prompt.substring(0, 100),
|
|
403
|
-
pid: process.pid,
|
|
404
|
-
status: "running",
|
|
405
|
-
startedAt: Date.now(),
|
|
406
|
-
lockedAt: Date.now()
|
|
407
|
-
};
|
|
408
|
-
const lockCheck = await checkLock(resolvedCwd, prompt);
|
|
409
|
-
if (lockCheck.isLocked) await waitForUnlock(lockCheck.blockingTasks, task);
|
|
410
|
-
else await addTask(task);
|
|
411
|
-
}
|
|
412
|
-
/**
|
|
413
|
-
* Release lock for current process
|
|
414
|
-
*/
|
|
415
|
-
async function releaseLock(pid = process.pid) {
|
|
416
|
-
await removeTask(pid);
|
|
417
|
-
}
|
|
418
|
-
/**
|
|
419
|
-
* Check if we should use locking for this directory
|
|
420
|
-
* Only use locking if we're in a git repository
|
|
421
|
-
*/
|
|
422
|
-
function shouldUseLock(_cwd) {
|
|
423
|
-
return true;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
182
|
//#endregion
|
|
427
183
|
//#region ts/beta/fifo.ts
|
|
428
184
|
/**
|
|
@@ -1059,7 +815,7 @@ function tryCatch(catchFn, fn) {
|
|
|
1059
815
|
//#endregion
|
|
1060
816
|
//#region package.json
|
|
1061
817
|
var name = "agent-yes";
|
|
1062
|
-
var version = "1.
|
|
818
|
+
var version = "1.72.0";
|
|
1063
819
|
|
|
1064
820
|
//#endregion
|
|
1065
821
|
//#region ts/pty-fix.ts
|
|
@@ -1486,7 +1242,7 @@ async function notifyWebhook(status, details, cwd = process.cwd()) {
|
|
|
1486
1242
|
|
|
1487
1243
|
//#endregion
|
|
1488
1244
|
//#region ts/index.ts
|
|
1489
|
-
const config = await import("./agent-yes.config-
|
|
1245
|
+
const config = await import("./agent-yes.config-CtQprJrA.js").then((mod) => mod.default || mod);
|
|
1490
1246
|
const CLIS_CONFIG = config.clis;
|
|
1491
1247
|
/**
|
|
1492
1248
|
* Main function to run agent-cli with automatic yes/no responses
|
|
@@ -2139,4 +1895,4 @@ const SUPPORTED_CLIS = Object.keys(CLIS_CONFIG);
|
|
|
2139
1895
|
|
|
2140
1896
|
//#endregion
|
|
2141
1897
|
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 };
|
|
2142
|
-
//# sourceMappingURL=SUPPORTED_CLIS-
|
|
1898
|
+
//# sourceMappingURL=SUPPORTED_CLIS-jR_I2op4.js.map
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import { c as PidStore, o as name, s as version, t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-
|
|
2
|
+
import { c as PidStore, o as name, s as version, t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-jR_I2op4.js";
|
|
3
3
|
import { t as logger } from "./logger-CX77vJDA.js";
|
|
4
4
|
import { argv } from "process";
|
|
5
5
|
import { spawn } from "child_process";
|
|
@@ -91,6 +91,10 @@ function parseCliArgs(argv) {
|
|
|
91
91
|
description: "Pass --dangerously-skip-permissions to the CLI (claude shortcut)",
|
|
92
92
|
default: false,
|
|
93
93
|
alias: "y"
|
|
94
|
+
}).option("tray", {
|
|
95
|
+
type: "boolean",
|
|
96
|
+
description: "Show a system tray icon with running agent count (macOS/Windows only)",
|
|
97
|
+
default: false
|
|
94
98
|
}).option("rust", {
|
|
95
99
|
type: "boolean",
|
|
96
100
|
description: "Use the Rust implementation (enabled by default, use --no-rust for TypeScript)",
|
|
@@ -193,6 +197,7 @@ function parseCliArgs(argv) {
|
|
|
193
197
|
showVersion: parsedArgv.version,
|
|
194
198
|
autoYes: parsedArgv.auto !== "no",
|
|
195
199
|
idleAction: parsedArgv.idleAction,
|
|
200
|
+
tray: parsedArgv.tray,
|
|
196
201
|
useRust: parsedArgv.rust,
|
|
197
202
|
swarm: parsedArgv.swarm ?? (parsedArgv.experimentalSwarm ? parsedArgv.swarmTopic : void 0),
|
|
198
203
|
experimentalSwarm: parsedArgv.experimentalSwarm,
|
|
@@ -475,6 +480,11 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
|
|
|
475
480
|
//#region ts/cli.ts
|
|
476
481
|
const updateCheckPromise = checkAndAutoUpdate();
|
|
477
482
|
const config = parseCliArgs(process.argv);
|
|
483
|
+
if (config.tray) {
|
|
484
|
+
const { startTray } = await import("./tray-Dyiihcrq.js");
|
|
485
|
+
await startTray();
|
|
486
|
+
await new Promise(() => {});
|
|
487
|
+
}
|
|
478
488
|
if (config.useRust) {
|
|
479
489
|
let rustBinary;
|
|
480
490
|
try {
|
|
@@ -562,6 +572,10 @@ if (config.verbose) {
|
|
|
562
572
|
console.log(config);
|
|
563
573
|
console.log(argv);
|
|
564
574
|
}
|
|
575
|
+
{
|
|
576
|
+
const { ensureTray } = await import("./tray-Dyiihcrq.js");
|
|
577
|
+
ensureTray();
|
|
578
|
+
}
|
|
565
579
|
const { default: cliYes } = await import("./index.js");
|
|
566
580
|
const { exitCode } = await cliYes({
|
|
567
581
|
...config,
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as AgentContext, i as config, l as removeControlCharacters, n as CLIS_CONFIG, r as agentYes } from "./SUPPORTED_CLIS-
|
|
1
|
+
import { a as AgentContext, i as config, l as removeControlCharacters, n as CLIS_CONFIG, r as agentYes } from "./SUPPORTED_CLIS-jR_I2op4.js";
|
|
2
2
|
import "./logger-CX77vJDA.js";
|
|
3
3
|
|
|
4
4
|
export { AgentContext, CLIS_CONFIG, config, agentYes as default, removeControlCharacters };
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import { mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
import { existsSync } from "fs";
|
|
6
|
+
|
|
7
|
+
//#region ts/runningLock.ts
|
|
8
|
+
const getLockDir = () => path.join(process.env.CLAUDE_YES_HOME || homedir(), ".claude-yes");
|
|
9
|
+
const getLockFile = () => path.join(getLockDir(), "running.lock.json");
|
|
10
|
+
const MAX_RETRIES = 5;
|
|
11
|
+
const RETRY_DELAYS = [
|
|
12
|
+
50,
|
|
13
|
+
100,
|
|
14
|
+
200,
|
|
15
|
+
400,
|
|
16
|
+
800
|
|
17
|
+
];
|
|
18
|
+
const POLL_INTERVAL = 2e3;
|
|
19
|
+
/**
|
|
20
|
+
* Check if a process is running
|
|
21
|
+
*/
|
|
22
|
+
function isProcessRunning(pid) {
|
|
23
|
+
try {
|
|
24
|
+
process.kill(pid, 0);
|
|
25
|
+
return true;
|
|
26
|
+
} catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get git repository root for a directory
|
|
32
|
+
*/
|
|
33
|
+
function getGitRoot(cwd) {
|
|
34
|
+
try {
|
|
35
|
+
return execSync("git rev-parse --show-toplevel", {
|
|
36
|
+
cwd,
|
|
37
|
+
encoding: "utf8",
|
|
38
|
+
stdio: [
|
|
39
|
+
"pipe",
|
|
40
|
+
"pipe",
|
|
41
|
+
"ignore"
|
|
42
|
+
]
|
|
43
|
+
}).trim();
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Check if directory is in a git repository
|
|
50
|
+
*/
|
|
51
|
+
function isGitRepo(cwd) {
|
|
52
|
+
try {
|
|
53
|
+
return getGitRoot(cwd) !== null;
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Resolve path to real path (handling symlinks)
|
|
60
|
+
*/
|
|
61
|
+
function resolveRealPath(p) {
|
|
62
|
+
try {
|
|
63
|
+
return path.resolve(p);
|
|
64
|
+
} catch {
|
|
65
|
+
return p;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Sleep for a given number of milliseconds
|
|
70
|
+
*/
|
|
71
|
+
function sleep(ms) {
|
|
72
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Read lock file with retry logic and stale lock cleanup
|
|
76
|
+
*/
|
|
77
|
+
async function readLockFile() {
|
|
78
|
+
try {
|
|
79
|
+
const lockDir = getLockDir();
|
|
80
|
+
const lockFilePath = getLockFile();
|
|
81
|
+
await mkdir(lockDir, { recursive: true });
|
|
82
|
+
if (!existsSync(lockFilePath)) return { tasks: [] };
|
|
83
|
+
const content = await readFile(lockFilePath, "utf8");
|
|
84
|
+
const lockFile = JSON.parse(content);
|
|
85
|
+
lockFile.tasks = lockFile.tasks.filter((task) => {
|
|
86
|
+
if (isProcessRunning(task.pid)) return true;
|
|
87
|
+
return false;
|
|
88
|
+
});
|
|
89
|
+
return lockFile;
|
|
90
|
+
} catch {
|
|
91
|
+
return { tasks: [] };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Write lock file atomically with retry logic
|
|
96
|
+
*/
|
|
97
|
+
async function writeLockFile(lockFile, retryCount = 0) {
|
|
98
|
+
try {
|
|
99
|
+
const lockDir = getLockDir();
|
|
100
|
+
const lockFilePath = getLockFile();
|
|
101
|
+
await mkdir(lockDir, { recursive: true });
|
|
102
|
+
const tempFile = `${lockFilePath}.tmp.${process.pid}`;
|
|
103
|
+
await writeFile(tempFile, JSON.stringify(lockFile, null, 2), "utf8");
|
|
104
|
+
await rename(tempFile, lockFilePath);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
if (retryCount < MAX_RETRIES) {
|
|
107
|
+
await sleep(RETRY_DELAYS[retryCount] || 800);
|
|
108
|
+
return writeLockFile(lockFile, retryCount + 1);
|
|
109
|
+
}
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Check if lock exists for the current working directory
|
|
115
|
+
*/
|
|
116
|
+
async function checkLock(cwd, _prompt) {
|
|
117
|
+
const resolvedCwd = resolveRealPath(cwd);
|
|
118
|
+
const gitRoot = isGitRepo(resolvedCwd) ? getGitRoot(resolvedCwd) : null;
|
|
119
|
+
const lockKey = gitRoot || resolvedCwd;
|
|
120
|
+
const blockingTasks = (await readLockFile()).tasks.filter((task) => {
|
|
121
|
+
if (!isProcessRunning(task.pid)) return false;
|
|
122
|
+
if (task.status !== "running") return false;
|
|
123
|
+
if (gitRoot && task.gitRoot) return task.gitRoot === gitRoot;
|
|
124
|
+
else return task.cwd === lockKey;
|
|
125
|
+
});
|
|
126
|
+
return {
|
|
127
|
+
isLocked: blockingTasks.length > 0,
|
|
128
|
+
blockingTasks,
|
|
129
|
+
lockKey
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Add a task to the lock file
|
|
134
|
+
*/
|
|
135
|
+
async function addTask(task) {
|
|
136
|
+
const lockFile = await readLockFile();
|
|
137
|
+
lockFile.tasks = lockFile.tasks.filter((t) => t.pid !== task.pid);
|
|
138
|
+
lockFile.tasks.push(task);
|
|
139
|
+
await writeLockFile(lockFile);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Update task status
|
|
143
|
+
*/
|
|
144
|
+
async function updateTaskStatus(pid, status) {
|
|
145
|
+
const lockFile = await readLockFile();
|
|
146
|
+
const task = lockFile.tasks.find((t) => t.pid === pid);
|
|
147
|
+
if (task) {
|
|
148
|
+
task.status = status;
|
|
149
|
+
await writeLockFile(lockFile);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Remove a task from the lock file
|
|
154
|
+
*/
|
|
155
|
+
async function removeTask(pid) {
|
|
156
|
+
const lockFile = await readLockFile();
|
|
157
|
+
lockFile.tasks = lockFile.tasks.filter((t) => t.pid !== pid);
|
|
158
|
+
await writeLockFile(lockFile);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Wait for lock to be released
|
|
162
|
+
*/
|
|
163
|
+
async function waitForUnlock(blockingTasks, currentTask) {
|
|
164
|
+
const blockingTask = blockingTasks[0];
|
|
165
|
+
if (!blockingTask) return;
|
|
166
|
+
console.log(`⏳ Queueing for unlock of: ${blockingTask.task}`);
|
|
167
|
+
console.log(` Press 'b' to bypass queue, 'k' to kill previous instance`);
|
|
168
|
+
await addTask({
|
|
169
|
+
...currentTask,
|
|
170
|
+
status: "queued"
|
|
171
|
+
});
|
|
172
|
+
const stdin = process.stdin;
|
|
173
|
+
const wasRaw = stdin.isRaw;
|
|
174
|
+
stdin.setRawMode?.(true);
|
|
175
|
+
stdin.resume();
|
|
176
|
+
let bypassed = false;
|
|
177
|
+
let killed = false;
|
|
178
|
+
const keyHandler = (key) => {
|
|
179
|
+
const char = key.toString();
|
|
180
|
+
if (char === "b" || char === "B") {
|
|
181
|
+
console.log("\n⚡ Bypassing queue...");
|
|
182
|
+
bypassed = true;
|
|
183
|
+
} else if (char === "k" || char === "K") {
|
|
184
|
+
console.log("\n🔪 Killing previous instance...");
|
|
185
|
+
killed = true;
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
stdin.on("data", keyHandler);
|
|
189
|
+
let dots = 0;
|
|
190
|
+
while (true) {
|
|
191
|
+
if (bypassed) {
|
|
192
|
+
await updateTaskStatus(currentTask.pid, "running");
|
|
193
|
+
console.log("✓ Queue bypassed, starting task...");
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
if (killed && blockingTask) {
|
|
197
|
+
try {
|
|
198
|
+
process.kill(blockingTask.pid, "SIGTERM");
|
|
199
|
+
console.log(`✓ Killed process ${blockingTask.pid}`);
|
|
200
|
+
await sleep(1e3);
|
|
201
|
+
} catch (err) {
|
|
202
|
+
console.log(`⚠️ Could not kill process ${blockingTask.pid}: ${err}`);
|
|
203
|
+
}
|
|
204
|
+
killed = false;
|
|
205
|
+
}
|
|
206
|
+
await sleep(POLL_INTERVAL);
|
|
207
|
+
if (!(await checkLock(currentTask.cwd, currentTask.task)).isLocked) {
|
|
208
|
+
await updateTaskStatus(currentTask.pid, "running");
|
|
209
|
+
console.log(`\n✓ Lock released, starting task...`);
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
dots = (dots + 1) % 4;
|
|
213
|
+
process.stdout.write(`\r⏳ Queueing${".".repeat(dots)}${" ".repeat(3 - dots)}`);
|
|
214
|
+
}
|
|
215
|
+
stdin.off("data", keyHandler);
|
|
216
|
+
stdin.setRawMode?.(wasRaw);
|
|
217
|
+
if (!wasRaw) stdin.pause();
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Get the count of currently running agents
|
|
221
|
+
*/
|
|
222
|
+
async function getRunningAgentCount() {
|
|
223
|
+
const running = (await readLockFile()).tasks.filter((t) => t.status === "running");
|
|
224
|
+
return {
|
|
225
|
+
count: running.length,
|
|
226
|
+
tasks: running
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Acquire lock or wait if locked
|
|
231
|
+
*/
|
|
232
|
+
async function acquireLock(cwd, prompt = "no prompt provided") {
|
|
233
|
+
const resolvedCwd = resolveRealPath(cwd);
|
|
234
|
+
const task = {
|
|
235
|
+
cwd: resolvedCwd,
|
|
236
|
+
gitRoot: (isGitRepo(resolvedCwd) ? getGitRoot(resolvedCwd) : null) || void 0,
|
|
237
|
+
task: prompt.substring(0, 100),
|
|
238
|
+
pid: process.pid,
|
|
239
|
+
status: "running",
|
|
240
|
+
startedAt: Date.now(),
|
|
241
|
+
lockedAt: Date.now()
|
|
242
|
+
};
|
|
243
|
+
const lockCheck = await checkLock(resolvedCwd, prompt);
|
|
244
|
+
if (lockCheck.isLocked) await waitForUnlock(lockCheck.blockingTasks, task);
|
|
245
|
+
else await addTask(task);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Release lock for current process
|
|
249
|
+
*/
|
|
250
|
+
async function releaseLock(pid = process.pid) {
|
|
251
|
+
await removeTask(pid);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Check if we should use locking for this directory
|
|
255
|
+
* Only use locking if we're in a git repository
|
|
256
|
+
*/
|
|
257
|
+
function shouldUseLock(_cwd) {
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
//#endregion
|
|
262
|
+
export { shouldUseLock as i, getRunningAgentCount as n, releaseLock as r, acquireLock as t };
|
|
263
|
+
//# sourceMappingURL=runningLock-BBI_URhR.js.map
|