@annals/agent-mesh 0.16.3 → 0.16.4
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.
|
@@ -8,6 +8,11 @@ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
|
8
8
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
9
9
|
import { join } from "path";
|
|
10
10
|
import { homedir } from "os";
|
|
11
|
+
var DEFAULT_RUNTIME_CONFIG = {
|
|
12
|
+
max_active_requests: 100,
|
|
13
|
+
queue_wait_timeout_ms: 10 * 6e4,
|
|
14
|
+
queue_max_length: 1e3
|
|
15
|
+
};
|
|
11
16
|
var CONFIG_DIR = join(homedir(), ".agent-mesh");
|
|
12
17
|
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
13
18
|
var PIDS_DIR = join(CONFIG_DIR, "pids");
|
|
@@ -35,6 +40,34 @@ function updateConfig(partial) {
|
|
|
35
40
|
const existing = loadConfig();
|
|
36
41
|
saveConfig({ ...existing, ...partial });
|
|
37
42
|
}
|
|
43
|
+
function parsePositiveInt(raw) {
|
|
44
|
+
if (typeof raw !== "number" || !Number.isFinite(raw)) return void 0;
|
|
45
|
+
const n = Math.floor(raw);
|
|
46
|
+
return n > 0 ? n : void 0;
|
|
47
|
+
}
|
|
48
|
+
function resolveRuntimeConfig(config) {
|
|
49
|
+
const runtime = (config || loadConfig()).runtime || {};
|
|
50
|
+
return {
|
|
51
|
+
max_active_requests: parsePositiveInt(runtime.max_active_requests) ?? DEFAULT_RUNTIME_CONFIG.max_active_requests,
|
|
52
|
+
queue_wait_timeout_ms: parsePositiveInt(runtime.queue_wait_timeout_ms) ?? DEFAULT_RUNTIME_CONFIG.queue_wait_timeout_ms,
|
|
53
|
+
queue_max_length: parsePositiveInt(runtime.queue_max_length) ?? DEFAULT_RUNTIME_CONFIG.queue_max_length
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function getRuntimeConfig() {
|
|
57
|
+
return resolveRuntimeConfig(loadConfig());
|
|
58
|
+
}
|
|
59
|
+
function updateRuntimeConfig(partial) {
|
|
60
|
+
const config = loadConfig();
|
|
61
|
+
config.runtime = { ...config.runtime || {}, ...partial };
|
|
62
|
+
saveConfig(config);
|
|
63
|
+
return resolveRuntimeConfig(config);
|
|
64
|
+
}
|
|
65
|
+
function resetRuntimeConfig() {
|
|
66
|
+
const config = loadConfig();
|
|
67
|
+
config.runtime = { ...DEFAULT_RUNTIME_CONFIG };
|
|
68
|
+
saveConfig(config);
|
|
69
|
+
return resolveRuntimeConfig(config);
|
|
70
|
+
}
|
|
38
71
|
function getConfigPath() {
|
|
39
72
|
return CONFIG_FILE;
|
|
40
73
|
}
|
|
@@ -908,8 +941,13 @@ function registerListCommand(program) {
|
|
|
908
941
|
}
|
|
909
942
|
|
|
910
943
|
export {
|
|
944
|
+
DEFAULT_RUNTIME_CONFIG,
|
|
911
945
|
loadConfig,
|
|
912
946
|
updateConfig,
|
|
947
|
+
resolveRuntimeConfig,
|
|
948
|
+
getRuntimeConfig,
|
|
949
|
+
updateRuntimeConfig,
|
|
950
|
+
resetRuntimeConfig,
|
|
913
951
|
getConfigPath,
|
|
914
952
|
getAgent,
|
|
915
953
|
addAgent,
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from "./chunk-W24WCWEC.js";
|
|
6
6
|
import {
|
|
7
7
|
BOLD,
|
|
8
|
+
DEFAULT_RUNTIME_CONFIG,
|
|
8
9
|
GRAY,
|
|
9
10
|
GREEN,
|
|
10
11
|
RESET,
|
|
@@ -15,6 +16,7 @@ import {
|
|
|
15
16
|
getAgentWorkspaceDir,
|
|
16
17
|
getConfigPath,
|
|
17
18
|
getLogPath,
|
|
19
|
+
getRuntimeConfig,
|
|
18
20
|
isProcessAlive,
|
|
19
21
|
listAgents,
|
|
20
22
|
loadConfig,
|
|
@@ -23,12 +25,15 @@ import {
|
|
|
23
25
|
removeAgent,
|
|
24
26
|
removePid,
|
|
25
27
|
renderTable,
|
|
28
|
+
resetRuntimeConfig,
|
|
29
|
+
resolveRuntimeConfig,
|
|
26
30
|
spawnBackground,
|
|
27
31
|
stopProcess,
|
|
28
32
|
uniqueSlug,
|
|
29
33
|
updateConfig,
|
|
34
|
+
updateRuntimeConfig,
|
|
30
35
|
writePid
|
|
31
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-U32JDKSN.js";
|
|
32
37
|
|
|
33
38
|
// src/index.ts
|
|
34
39
|
import { createRequire } from "module";
|
|
@@ -269,6 +274,351 @@ var SessionPool = class {
|
|
|
269
274
|
}
|
|
270
275
|
};
|
|
271
276
|
|
|
277
|
+
// src/utils/local-runtime-queue.ts
|
|
278
|
+
import {
|
|
279
|
+
existsSync,
|
|
280
|
+
mkdirSync,
|
|
281
|
+
readFileSync,
|
|
282
|
+
renameSync,
|
|
283
|
+
rmSync,
|
|
284
|
+
statSync,
|
|
285
|
+
writeFileSync
|
|
286
|
+
} from "fs";
|
|
287
|
+
import { join } from "path";
|
|
288
|
+
import { homedir } from "os";
|
|
289
|
+
var DEFAULT_LOCK_STALE_MS = 3e4;
|
|
290
|
+
var DEFAULT_LOCK_WAIT_MS = 1e4;
|
|
291
|
+
var DEFAULT_LOCK_RETRY_MS = 25;
|
|
292
|
+
var DEFAULT_POLL_INTERVAL_MS = 100;
|
|
293
|
+
var DEFAULT_LEASE_TTL_MS = 15e3;
|
|
294
|
+
var DEFAULT_LEASE_HEARTBEAT_MS = 5e3;
|
|
295
|
+
var LocalRuntimeQueueError = class extends Error {
|
|
296
|
+
code;
|
|
297
|
+
constructor(code, message) {
|
|
298
|
+
super(message);
|
|
299
|
+
this.code = code;
|
|
300
|
+
this.name = "LocalRuntimeQueueError";
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
function sleep(ms) {
|
|
304
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
305
|
+
}
|
|
306
|
+
function isProcessAlive2(pid) {
|
|
307
|
+
try {
|
|
308
|
+
process.kill(pid, 0);
|
|
309
|
+
return true;
|
|
310
|
+
} catch {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function toRequestKey(input) {
|
|
315
|
+
return `${input.agentId}:${input.sessionId}:${input.requestId}`;
|
|
316
|
+
}
|
|
317
|
+
function toRuntimeQueueConfig(config) {
|
|
318
|
+
return {
|
|
319
|
+
maxActiveRequests: config.max_active_requests,
|
|
320
|
+
queueWaitTimeoutMs: config.queue_wait_timeout_ms,
|
|
321
|
+
queueMaxLength: config.queue_max_length
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
function fromRuntimeQueueConfig(config) {
|
|
325
|
+
return {
|
|
326
|
+
max_active_requests: config.maxActiveRequests,
|
|
327
|
+
queue_wait_timeout_ms: config.queueWaitTimeoutMs,
|
|
328
|
+
queue_max_length: config.queueMaxLength
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
var LocalRuntimeQueue = class {
|
|
332
|
+
runtimeRoot;
|
|
333
|
+
statePath;
|
|
334
|
+
lockPath;
|
|
335
|
+
config;
|
|
336
|
+
lockStaleMs;
|
|
337
|
+
lockWaitMs;
|
|
338
|
+
lockRetryMs;
|
|
339
|
+
pollIntervalMs;
|
|
340
|
+
leaseTtlMs;
|
|
341
|
+
leaseHeartbeatMs;
|
|
342
|
+
constructor(config, opts = {}) {
|
|
343
|
+
this.config = config;
|
|
344
|
+
const baseDir = opts.baseDir || join(homedir(), ".agent-mesh");
|
|
345
|
+
this.runtimeRoot = join(baseDir, "runtime");
|
|
346
|
+
this.statePath = join(this.runtimeRoot, "queue-state.json");
|
|
347
|
+
this.lockPath = join(this.runtimeRoot, "queue.lock");
|
|
348
|
+
this.lockStaleMs = opts.lockStaleMs ?? DEFAULT_LOCK_STALE_MS;
|
|
349
|
+
this.lockWaitMs = opts.lockWaitMs ?? DEFAULT_LOCK_WAIT_MS;
|
|
350
|
+
this.lockRetryMs = opts.lockRetryMs ?? DEFAULT_LOCK_RETRY_MS;
|
|
351
|
+
this.pollIntervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
352
|
+
this.leaseTtlMs = opts.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS;
|
|
353
|
+
this.leaseHeartbeatMs = opts.leaseHeartbeatMs ?? DEFAULT_LEASE_HEARTBEAT_MS;
|
|
354
|
+
this.ensureRuntimeDir();
|
|
355
|
+
}
|
|
356
|
+
async acquire(input, opts = {}) {
|
|
357
|
+
const requestKey = toRequestKey(input);
|
|
358
|
+
const queueId = crypto.randomUUID();
|
|
359
|
+
const deadlineAt = Date.now() + this.config.queueWaitTimeoutMs;
|
|
360
|
+
const signal = opts.signal;
|
|
361
|
+
if (signal?.aborted) {
|
|
362
|
+
throw new LocalRuntimeQueueError("queue_aborted", "Queue wait aborted");
|
|
363
|
+
}
|
|
364
|
+
await this.withLock(async (state) => {
|
|
365
|
+
const now = Date.now();
|
|
366
|
+
this.cleanupLockedState(state, now);
|
|
367
|
+
if (state.active[requestKey]) {
|
|
368
|
+
throw new LocalRuntimeQueueError("queue_cancelled", "Request is already active");
|
|
369
|
+
}
|
|
370
|
+
if (state.queue.some((entry) => entry.request_key === requestKey)) {
|
|
371
|
+
throw new LocalRuntimeQueueError("queue_cancelled", "Request is already queued");
|
|
372
|
+
}
|
|
373
|
+
if (state.queue.length >= this.config.queueMaxLength) {
|
|
374
|
+
throw new LocalRuntimeQueueError("queue_full", `Local queue full (${this.config.queueMaxLength})`);
|
|
375
|
+
}
|
|
376
|
+
state.queue.push({
|
|
377
|
+
queue_id: queueId,
|
|
378
|
+
request_key: requestKey,
|
|
379
|
+
agent_id: input.agentId,
|
|
380
|
+
session_id: input.sessionId,
|
|
381
|
+
request_id: input.requestId,
|
|
382
|
+
pid: input.pid,
|
|
383
|
+
enqueued_at: now,
|
|
384
|
+
deadline_at: deadlineAt
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
while (true) {
|
|
388
|
+
if (signal?.aborted) {
|
|
389
|
+
await this.removeQueuedByKey(requestKey);
|
|
390
|
+
throw new LocalRuntimeQueueError("queue_aborted", "Queue wait aborted");
|
|
391
|
+
}
|
|
392
|
+
let promotedLease = null;
|
|
393
|
+
let queuePosition = -1;
|
|
394
|
+
let activeCount = 0;
|
|
395
|
+
await this.withLock(async (state) => {
|
|
396
|
+
const now = Date.now();
|
|
397
|
+
this.cleanupLockedState(state, now);
|
|
398
|
+
activeCount = Object.keys(state.active).length;
|
|
399
|
+
queuePosition = state.queue.findIndex((entry2) => entry2.request_key === requestKey);
|
|
400
|
+
if (queuePosition === -1) {
|
|
401
|
+
if (state.active[requestKey]) {
|
|
402
|
+
promotedLease = state.active[requestKey];
|
|
403
|
+
}
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const entry = state.queue[queuePosition];
|
|
407
|
+
if (entry.deadline_at <= now) {
|
|
408
|
+
state.queue.splice(queuePosition, 1);
|
|
409
|
+
throw new LocalRuntimeQueueError("queue_timeout", `Local queue wait timeout (${Math.floor(this.config.queueWaitTimeoutMs / 1e3)}s)`);
|
|
410
|
+
}
|
|
411
|
+
if (queuePosition === 0 && activeCount < this.config.maxActiveRequests) {
|
|
412
|
+
state.queue.shift();
|
|
413
|
+
const lease = {
|
|
414
|
+
lease_id: crypto.randomUUID(),
|
|
415
|
+
request_key: requestKey,
|
|
416
|
+
agent_id: input.agentId,
|
|
417
|
+
session_id: input.sessionId,
|
|
418
|
+
request_id: input.requestId,
|
|
419
|
+
pid: input.pid,
|
|
420
|
+
acquired_at: now,
|
|
421
|
+
lease_expires_at: now + this.leaseTtlMs
|
|
422
|
+
};
|
|
423
|
+
state.active[requestKey] = lease;
|
|
424
|
+
promotedLease = lease;
|
|
425
|
+
activeCount = Object.keys(state.active).length;
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
if (promotedLease) {
|
|
429
|
+
if (queuePosition > 0) {
|
|
430
|
+
log.debug(`Queue promoted after race: request=${requestKey} active=${activeCount}/${this.config.maxActiveRequests}`);
|
|
431
|
+
}
|
|
432
|
+
return this.createLease(promotedLease);
|
|
433
|
+
}
|
|
434
|
+
if (queuePosition === -1) {
|
|
435
|
+
if (Date.now() >= deadlineAt) {
|
|
436
|
+
throw new LocalRuntimeQueueError("queue_timeout", `Local queue wait timeout (${Math.floor(this.config.queueWaitTimeoutMs / 1e3)}s)`);
|
|
437
|
+
}
|
|
438
|
+
throw new LocalRuntimeQueueError("queue_cancelled", "Request was removed from local queue");
|
|
439
|
+
}
|
|
440
|
+
await sleep(this.pollIntervalMs);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
async cancelQueued(input) {
|
|
444
|
+
return this.removeQueuedByKey(toRequestKey(input));
|
|
445
|
+
}
|
|
446
|
+
async snapshot() {
|
|
447
|
+
let active = 0;
|
|
448
|
+
let queued = 0;
|
|
449
|
+
await this.withLock(async (state) => {
|
|
450
|
+
this.cleanupLockedState(state, Date.now());
|
|
451
|
+
active = Object.keys(state.active).length;
|
|
452
|
+
queued = state.queue.length;
|
|
453
|
+
});
|
|
454
|
+
return {
|
|
455
|
+
active,
|
|
456
|
+
queued,
|
|
457
|
+
config: { ...this.config }
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
async removeQueuedByKey(requestKey) {
|
|
461
|
+
let removed = false;
|
|
462
|
+
await this.withLock(async (state) => {
|
|
463
|
+
const idx = state.queue.findIndex((entry) => entry.request_key === requestKey);
|
|
464
|
+
if (idx >= 0) {
|
|
465
|
+
state.queue.splice(idx, 1);
|
|
466
|
+
removed = true;
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
return removed;
|
|
470
|
+
}
|
|
471
|
+
createLease(activeLease) {
|
|
472
|
+
let released = false;
|
|
473
|
+
let heartbeatTimer = null;
|
|
474
|
+
const stopHeartbeat = () => {
|
|
475
|
+
if (heartbeatTimer) {
|
|
476
|
+
clearInterval(heartbeatTimer);
|
|
477
|
+
heartbeatTimer = null;
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
return {
|
|
481
|
+
leaseId: activeLease.lease_id,
|
|
482
|
+
requestKey: activeLease.request_key,
|
|
483
|
+
release: async (_reason) => {
|
|
484
|
+
if (released) return;
|
|
485
|
+
released = true;
|
|
486
|
+
stopHeartbeat();
|
|
487
|
+
await this.withLock(async (state) => {
|
|
488
|
+
const current = state.active[activeLease.request_key];
|
|
489
|
+
if (current?.lease_id === activeLease.lease_id) {
|
|
490
|
+
delete state.active[activeLease.request_key];
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
},
|
|
494
|
+
startHeartbeat: () => {
|
|
495
|
+
if (released || heartbeatTimer) {
|
|
496
|
+
return stopHeartbeat;
|
|
497
|
+
}
|
|
498
|
+
heartbeatTimer = setInterval(() => {
|
|
499
|
+
void this.extendLease(activeLease.request_key, activeLease.lease_id);
|
|
500
|
+
}, this.leaseHeartbeatMs);
|
|
501
|
+
heartbeatTimer.unref?.();
|
|
502
|
+
return stopHeartbeat;
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
async extendLease(requestKey, leaseId) {
|
|
507
|
+
try {
|
|
508
|
+
await this.withLock(async (state) => {
|
|
509
|
+
const lease = state.active[requestKey];
|
|
510
|
+
if (lease && lease.lease_id === leaseId) {
|
|
511
|
+
lease.lease_expires_at = Date.now() + this.leaseTtlMs;
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
} catch (err) {
|
|
515
|
+
log.debug(`Failed to extend local queue lease: ${err}`);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
ensureRuntimeDir() {
|
|
519
|
+
if (!existsSync(this.runtimeRoot)) {
|
|
520
|
+
mkdirSync(this.runtimeRoot, { recursive: true, mode: 448 });
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
defaultState() {
|
|
524
|
+
return {
|
|
525
|
+
version: 1,
|
|
526
|
+
config: fromRuntimeQueueConfig(this.config),
|
|
527
|
+
active: {},
|
|
528
|
+
queue: [],
|
|
529
|
+
updated_at: Date.now()
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
readState() {
|
|
533
|
+
try {
|
|
534
|
+
const raw = readFileSync(this.statePath, "utf-8");
|
|
535
|
+
const parsed = JSON.parse(raw);
|
|
536
|
+
if (parsed && parsed.version === 1 && parsed.active && Array.isArray(parsed.queue)) {
|
|
537
|
+
return {
|
|
538
|
+
version: 1,
|
|
539
|
+
config: fromRuntimeQueueConfig(this.config),
|
|
540
|
+
active: parsed.active,
|
|
541
|
+
queue: parsed.queue,
|
|
542
|
+
updated_at: typeof parsed.updated_at === "number" ? parsed.updated_at : Date.now()
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
} catch {
|
|
546
|
+
}
|
|
547
|
+
return this.defaultState();
|
|
548
|
+
}
|
|
549
|
+
writeState(state) {
|
|
550
|
+
state.config = fromRuntimeQueueConfig(this.config);
|
|
551
|
+
state.updated_at = Date.now();
|
|
552
|
+
const tmpPath = `${this.statePath}.${process.pid}.tmp`;
|
|
553
|
+
writeFileSync(tmpPath, JSON.stringify(state, null, 2) + "\n", { mode: 384 });
|
|
554
|
+
renameSync(tmpPath, this.statePath);
|
|
555
|
+
}
|
|
556
|
+
cleanupLockedState(state, now) {
|
|
557
|
+
for (const [key, lease] of Object.entries(state.active)) {
|
|
558
|
+
const expired = lease.lease_expires_at <= now;
|
|
559
|
+
const deadPid = !isProcessAlive2(lease.pid);
|
|
560
|
+
if (expired || deadPid) {
|
|
561
|
+
delete state.active[key];
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
state.queue = state.queue.filter((entry) => {
|
|
565
|
+
if (entry.deadline_at <= now) return false;
|
|
566
|
+
if (!isProcessAlive2(entry.pid)) return false;
|
|
567
|
+
return true;
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
async withLock(fn) {
|
|
571
|
+
this.ensureRuntimeDir();
|
|
572
|
+
const acquiredAt = Date.now();
|
|
573
|
+
while (true) {
|
|
574
|
+
try {
|
|
575
|
+
mkdirSync(this.lockPath, { mode: 448 });
|
|
576
|
+
break;
|
|
577
|
+
} catch (err) {
|
|
578
|
+
const e = err;
|
|
579
|
+
if (e.code !== "EEXIST") {
|
|
580
|
+
throw err;
|
|
581
|
+
}
|
|
582
|
+
try {
|
|
583
|
+
const st = statSync(this.lockPath);
|
|
584
|
+
if (Date.now() - st.mtimeMs > this.lockStaleMs) {
|
|
585
|
+
rmSync(this.lockPath, { recursive: true, force: true });
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
} catch {
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
if (Date.now() - acquiredAt >= this.lockWaitMs) {
|
|
592
|
+
throw new Error("Timed out waiting for local runtime queue lock");
|
|
593
|
+
}
|
|
594
|
+
await sleep(this.lockRetryMs);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
try {
|
|
598
|
+
const state = this.readState();
|
|
599
|
+
try {
|
|
600
|
+
const result = await fn(state);
|
|
601
|
+
this.writeState(state);
|
|
602
|
+
return result;
|
|
603
|
+
} catch (err) {
|
|
604
|
+
try {
|
|
605
|
+
this.writeState(state);
|
|
606
|
+
} catch {
|
|
607
|
+
}
|
|
608
|
+
throw err;
|
|
609
|
+
}
|
|
610
|
+
} finally {
|
|
611
|
+
try {
|
|
612
|
+
rmSync(this.lockPath, { recursive: true, force: true });
|
|
613
|
+
} catch {
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
function createLocalRuntimeQueue(config) {
|
|
619
|
+
return new LocalRuntimeQueue(toRuntimeQueueConfig(config));
|
|
620
|
+
}
|
|
621
|
+
|
|
272
622
|
// src/bridge/manager.ts
|
|
273
623
|
var DUPLICATE_REQUEST_TTL_MS = 10 * 6e4;
|
|
274
624
|
var SESSION_SWEEP_INTERVAL_MS = 6e4;
|
|
@@ -299,11 +649,15 @@ var BridgeManager = class {
|
|
|
299
649
|
requestTracker = /* @__PURE__ */ new Map();
|
|
300
650
|
/** Last activity timestamp per session for idle cleanup */
|
|
301
651
|
sessionLastSeenAt = /* @__PURE__ */ new Map();
|
|
652
|
+
/** Request lifecycle state for local queueing (queued/running) */
|
|
653
|
+
requestDispatches = /* @__PURE__ */ new Map();
|
|
302
654
|
cleanupTimer = null;
|
|
655
|
+
runtimeQueue;
|
|
303
656
|
constructor(opts) {
|
|
304
657
|
this.wsClient = opts.wsClient;
|
|
305
658
|
this.adapter = opts.adapter;
|
|
306
659
|
this.adapterConfig = opts.adapterConfig;
|
|
660
|
+
this.runtimeQueue = opts.runtimeQueue || createLocalRuntimeQueue(resolveRuntimeConfig(loadConfig()));
|
|
307
661
|
}
|
|
308
662
|
start() {
|
|
309
663
|
this.wsClient.onMessage((msg) => this.handleWorkerMessage(msg));
|
|
@@ -325,6 +679,7 @@ var BridgeManager = class {
|
|
|
325
679
|
this.activeRequests.clear();
|
|
326
680
|
this.wiredSessions.clear();
|
|
327
681
|
this.sessionLastSeenAt.clear();
|
|
682
|
+
this.cleanupRequestDispatches("shutdown");
|
|
328
683
|
log.info("Bridge manager stopped");
|
|
329
684
|
}
|
|
330
685
|
/**
|
|
@@ -340,6 +695,7 @@ var BridgeManager = class {
|
|
|
340
695
|
this.activeRequests.clear();
|
|
341
696
|
this.wiredSessions.clear();
|
|
342
697
|
this.sessionLastSeenAt.clear();
|
|
698
|
+
this.cleanupRequestDispatches("shutdown");
|
|
343
699
|
log.info("Bridge manager reconnected");
|
|
344
700
|
}
|
|
345
701
|
get sessionCount() {
|
|
@@ -360,7 +716,7 @@ var BridgeManager = class {
|
|
|
360
716
|
}
|
|
361
717
|
}
|
|
362
718
|
handleMessage(msg) {
|
|
363
|
-
const { session_id, request_id
|
|
719
|
+
const { session_id, request_id } = msg;
|
|
364
720
|
const now = Date.now();
|
|
365
721
|
this.pruneExpiredRequests(now);
|
|
366
722
|
this.pruneIdleSessions(now);
|
|
@@ -399,14 +755,78 @@ var BridgeManager = class {
|
|
|
399
755
|
this.wireSession(handle, session_id, requestRef);
|
|
400
756
|
this.wiredSessions.add(session_id);
|
|
401
757
|
}
|
|
402
|
-
const
|
|
758
|
+
const requestKey = this.requestKey(session_id, request_id);
|
|
759
|
+
const queueInput = {
|
|
760
|
+
agentId: this.adapterConfig.agentId || "unknown-agent",
|
|
761
|
+
sessionId: session_id,
|
|
762
|
+
requestId: request_id,
|
|
763
|
+
pid: process.pid
|
|
764
|
+
};
|
|
765
|
+
this.requestDispatches.set(requestKey, {
|
|
766
|
+
queueInput,
|
|
767
|
+
abortController: new AbortController(),
|
|
768
|
+
cancelled: false,
|
|
769
|
+
cleaned: false
|
|
770
|
+
});
|
|
771
|
+
void this.dispatchWithLocalQueue({
|
|
772
|
+
msg,
|
|
773
|
+
handle,
|
|
774
|
+
requestKey
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
async dispatchWithLocalQueue(opts) {
|
|
778
|
+
const { msg, handle, requestKey } = opts;
|
|
779
|
+
const { session_id, request_id, content, attachments, upload_url, upload_token, client_id } = msg;
|
|
780
|
+
const state = this.requestDispatches.get(requestKey);
|
|
781
|
+
if (!state) return;
|
|
403
782
|
try {
|
|
404
|
-
|
|
405
|
-
|
|
783
|
+
const lease = await this.runtimeQueue.acquire(state.queueInput, {
|
|
784
|
+
signal: state.abortController.signal
|
|
785
|
+
});
|
|
786
|
+
if (state.cleaned) {
|
|
787
|
+
await lease.release("shutdown");
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
state.lease = lease;
|
|
791
|
+
state.stopLeaseHeartbeat = lease.startHeartbeat();
|
|
792
|
+
if (state.cancelled) {
|
|
793
|
+
this.trackRequest(session_id, request_id, "cancelled");
|
|
794
|
+
this.sendError(session_id, request_id, BridgeErrorCode.SESSION_NOT_FOUND, "Request cancelled before execution");
|
|
795
|
+
await this.releaseRequestLease(session_id, request_id, "cancel");
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
const uploadCredentials = upload_url && upload_token ? { uploadUrl: upload_url, uploadToken: upload_token } : void 0;
|
|
799
|
+
try {
|
|
800
|
+
handle.send(content, attachments, uploadCredentials, client_id);
|
|
801
|
+
this.sessionLastSeenAt.set(session_id, Date.now());
|
|
802
|
+
} catch (err) {
|
|
803
|
+
log.error(`Failed to send to adapter: ${err}`);
|
|
804
|
+
this.trackRequest(session_id, request_id, "error");
|
|
805
|
+
this.sendError(session_id, request_id, BridgeErrorCode.ADAPTER_CRASH, `Adapter send failed: ${err}`);
|
|
806
|
+
await this.releaseRequestLease(session_id, request_id, "error");
|
|
807
|
+
}
|
|
406
808
|
} catch (err) {
|
|
407
|
-
|
|
809
|
+
if (state.cleaned) return;
|
|
810
|
+
if (state.cancelled && err instanceof LocalRuntimeQueueError && (err.code === "queue_aborted" || err.code === "queue_cancelled")) {
|
|
811
|
+
this.sendError(session_id, request_id, BridgeErrorCode.SESSION_NOT_FOUND, "Request cancelled before execution");
|
|
812
|
+
this.cleanupRequestDispatchState(requestKey);
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
if (err instanceof LocalRuntimeQueueError && (err.code === "queue_full" || err.code === "queue_timeout")) {
|
|
816
|
+
log.warn(`Local queue rejected request: ${err.message}`);
|
|
817
|
+
this.trackRequest(session_id, request_id, "error");
|
|
818
|
+
this.sendError(session_id, request_id, BridgeErrorCode.AGENT_BUSY, err.message);
|
|
819
|
+
this.cleanupRequestDispatchState(requestKey);
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
if (err instanceof LocalRuntimeQueueError && (err.code === "queue_aborted" || err.code === "queue_cancelled")) {
|
|
823
|
+
this.cleanupRequestDispatchState(requestKey);
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
log.error(`Local queue error: ${err}`);
|
|
408
827
|
this.trackRequest(session_id, request_id, "error");
|
|
409
|
-
this.sendError(session_id, request_id, BridgeErrorCode.
|
|
828
|
+
this.sendError(session_id, request_id, BridgeErrorCode.INTERNAL_ERROR, "Local runtime queue failure");
|
|
829
|
+
this.cleanupRequestDispatchState(requestKey);
|
|
410
830
|
}
|
|
411
831
|
}
|
|
412
832
|
wireSession(handle, sessionId, requestRef) {
|
|
@@ -436,6 +856,7 @@ var BridgeManager = class {
|
|
|
436
856
|
this.sessionLastSeenAt.set(sessionId, Date.now());
|
|
437
857
|
});
|
|
438
858
|
handle.onDone((attachments) => {
|
|
859
|
+
void this.releaseRequestLease(sessionId, requestRef.requestId, "done");
|
|
439
860
|
const done = {
|
|
440
861
|
type: "done",
|
|
441
862
|
session_id: sessionId,
|
|
@@ -452,6 +873,7 @@ var BridgeManager = class {
|
|
|
452
873
|
});
|
|
453
874
|
handle.onError((err) => {
|
|
454
875
|
log.error(`Adapter error (session=${sessionId.slice(0, 8)}...): ${err.message}`);
|
|
876
|
+
void this.releaseRequestLease(sessionId, requestRef.requestId, "error");
|
|
455
877
|
this.trackRequest(sessionId, requestRef.requestId, "error");
|
|
456
878
|
this.sendError(sessionId, requestRef.requestId, BridgeErrorCode.ADAPTER_CRASH, err.message);
|
|
457
879
|
this.sessionLastSeenAt.set(sessionId, Date.now());
|
|
@@ -461,9 +883,23 @@ var BridgeManager = class {
|
|
|
461
883
|
const { session_id, request_id } = msg;
|
|
462
884
|
log.info(`Cancel received: session=${session_id.slice(0, 8)}...`);
|
|
463
885
|
this.trackRequest(session_id, request_id, "cancelled");
|
|
886
|
+
const requestKey = this.requestKey(session_id, request_id);
|
|
887
|
+
const state = this.requestDispatches.get(requestKey);
|
|
888
|
+
if (state && !state.lease) {
|
|
889
|
+
state.cancelled = true;
|
|
890
|
+
state.abortController.abort();
|
|
891
|
+
void this.runtimeQueue.cancelQueued(state.queueInput).catch((err) => {
|
|
892
|
+
log.warn(`Failed to cancel local queue entry: ${err}`);
|
|
893
|
+
});
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
464
896
|
this.destroySession(session_id, "cancel_signal");
|
|
465
897
|
}
|
|
466
898
|
destroySession(sessionId, reason) {
|
|
899
|
+
const requestRef = this.activeRequests.get(sessionId);
|
|
900
|
+
if (requestRef) {
|
|
901
|
+
void this.releaseRequestLease(sessionId, requestRef.requestId, reason === "cancel_signal" ? "cancel" : "shutdown");
|
|
902
|
+
}
|
|
467
903
|
const handle = this.pool.get(sessionId);
|
|
468
904
|
if (!handle) {
|
|
469
905
|
this.sessionLastSeenAt.delete(sessionId);
|
|
@@ -532,6 +968,74 @@ var BridgeManager = class {
|
|
|
532
968
|
requestKey(sessionId, requestId) {
|
|
533
969
|
return `${sessionId}:${requestId}`;
|
|
534
970
|
}
|
|
971
|
+
async releaseRequestLease(sessionId, requestId, reason) {
|
|
972
|
+
const key = this.requestKey(sessionId, requestId);
|
|
973
|
+
const state = this.requestDispatches.get(key);
|
|
974
|
+
if (!state || state.cleaned) {
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
state.cleaned = true;
|
|
978
|
+
if (state.stopLeaseHeartbeat) {
|
|
979
|
+
try {
|
|
980
|
+
state.stopLeaseHeartbeat();
|
|
981
|
+
} catch {
|
|
982
|
+
}
|
|
983
|
+
state.stopLeaseHeartbeat = void 0;
|
|
984
|
+
}
|
|
985
|
+
if (state.lease) {
|
|
986
|
+
try {
|
|
987
|
+
await state.lease.release(reason);
|
|
988
|
+
} catch (err) {
|
|
989
|
+
log.warn(`Failed to release local queue lease: ${err}`);
|
|
990
|
+
}
|
|
991
|
+
} else {
|
|
992
|
+
state.abortController.abort();
|
|
993
|
+
try {
|
|
994
|
+
await this.runtimeQueue.cancelQueued(state.queueInput);
|
|
995
|
+
} catch (err) {
|
|
996
|
+
log.warn(`Failed to remove local queue entry: ${err}`);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
this.requestDispatches.delete(key);
|
|
1000
|
+
}
|
|
1001
|
+
cleanupRequestDispatchState(requestKey) {
|
|
1002
|
+
const state = this.requestDispatches.get(requestKey);
|
|
1003
|
+
if (!state) return;
|
|
1004
|
+
state.cleaned = true;
|
|
1005
|
+
if (state.stopLeaseHeartbeat) {
|
|
1006
|
+
try {
|
|
1007
|
+
state.stopLeaseHeartbeat();
|
|
1008
|
+
} catch {
|
|
1009
|
+
}
|
|
1010
|
+
state.stopLeaseHeartbeat = void 0;
|
|
1011
|
+
}
|
|
1012
|
+
this.requestDispatches.delete(requestKey);
|
|
1013
|
+
}
|
|
1014
|
+
cleanupRequestDispatches(reason) {
|
|
1015
|
+
for (const [requestKey, state] of Array.from(this.requestDispatches.entries())) {
|
|
1016
|
+
if (state.cleaned) continue;
|
|
1017
|
+
state.cleaned = true;
|
|
1018
|
+
state.cancelled = true;
|
|
1019
|
+
state.abortController.abort();
|
|
1020
|
+
if (state.stopLeaseHeartbeat) {
|
|
1021
|
+
try {
|
|
1022
|
+
state.stopLeaseHeartbeat();
|
|
1023
|
+
} catch {
|
|
1024
|
+
}
|
|
1025
|
+
state.stopLeaseHeartbeat = void 0;
|
|
1026
|
+
}
|
|
1027
|
+
if (state.lease) {
|
|
1028
|
+
void state.lease.release(reason).catch((err) => {
|
|
1029
|
+
log.warn(`Failed to release local queue lease during cleanup: ${err}`);
|
|
1030
|
+
});
|
|
1031
|
+
} else {
|
|
1032
|
+
void this.runtimeQueue.cancelQueued(state.queueInput).catch((err) => {
|
|
1033
|
+
log.warn(`Failed to cancel queued request during cleanup: ${err}`);
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
this.requestDispatches.delete(requestKey);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
535
1039
|
trackRequest(sessionId, requestId, status) {
|
|
536
1040
|
this.requestTracker.set(this.requestKey(sessionId, requestId), {
|
|
537
1041
|
status,
|
|
@@ -555,8 +1059,8 @@ var AgentAdapter = class {
|
|
|
555
1059
|
};
|
|
556
1060
|
|
|
557
1061
|
// src/utils/client-workspace.ts
|
|
558
|
-
import { mkdirSync, readdirSync, symlinkSync, existsSync, lstatSync } from "fs";
|
|
559
|
-
import { join, relative } from "path";
|
|
1062
|
+
import { mkdirSync as mkdirSync2, readdirSync, symlinkSync, existsSync as existsSync2, lstatSync } from "fs";
|
|
1063
|
+
import { join as join2, relative } from "path";
|
|
560
1064
|
var SYMLINK_ALLOW = /* @__PURE__ */ new Set([
|
|
561
1065
|
"CLAUDE.md",
|
|
562
1066
|
".claude",
|
|
@@ -585,19 +1089,19 @@ function shouldInclude(name) {
|
|
|
585
1089
|
return false;
|
|
586
1090
|
}
|
|
587
1091
|
function createClientWorkspace(projectPath, clientId) {
|
|
588
|
-
const wsDir =
|
|
589
|
-
const isNew = !
|
|
590
|
-
|
|
1092
|
+
const wsDir = join2(projectPath, ".bridge-clients", clientId);
|
|
1093
|
+
const isNew = !existsSync2(wsDir);
|
|
1094
|
+
mkdirSync2(wsDir, { recursive: true });
|
|
591
1095
|
const entries = readdirSync(projectPath, { withFileTypes: true });
|
|
592
1096
|
for (const entry of entries) {
|
|
593
1097
|
if (!shouldInclude(entry.name)) continue;
|
|
594
|
-
const link =
|
|
1098
|
+
const link = join2(wsDir, entry.name);
|
|
595
1099
|
try {
|
|
596
1100
|
lstatSync(link);
|
|
597
1101
|
continue;
|
|
598
1102
|
} catch {
|
|
599
1103
|
}
|
|
600
|
-
const target =
|
|
1104
|
+
const target = join2(projectPath, entry.name);
|
|
601
1105
|
const relTarget = relative(wsDir, target);
|
|
602
1106
|
try {
|
|
603
1107
|
symlinkSync(relTarget, link);
|
|
@@ -613,7 +1117,7 @@ function createClientWorkspace(projectPath, clientId) {
|
|
|
613
1117
|
|
|
614
1118
|
// src/utils/auto-upload.ts
|
|
615
1119
|
import { readdir, readFile, stat } from "fs/promises";
|
|
616
|
-
import { join as
|
|
1120
|
+
import { join as join3, relative as relative2 } from "path";
|
|
617
1121
|
var MAX_AUTO_UPLOAD_FILES = 50;
|
|
618
1122
|
var MAX_AUTO_UPLOAD_FILE_SIZE = 10 * 1024 * 1024;
|
|
619
1123
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -656,7 +1160,7 @@ async function collectRealFiles(dir, maxFiles = Infinity) {
|
|
|
656
1160
|
for (const entry of entries) {
|
|
657
1161
|
if (files.length >= maxFiles) return;
|
|
658
1162
|
if (entry.isSymbolicLink()) continue;
|
|
659
|
-
const fullPath =
|
|
1163
|
+
const fullPath = join3(d, entry.name);
|
|
660
1164
|
if (entry.isDirectory()) {
|
|
661
1165
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
662
1166
|
await walk(fullPath);
|
|
@@ -972,7 +1476,7 @@ import { spawn } from "child_process";
|
|
|
972
1476
|
|
|
973
1477
|
// src/utils/sandbox.ts
|
|
974
1478
|
import { execSync } from "child_process";
|
|
975
|
-
import { join as
|
|
1479
|
+
import { join as join4 } from "path";
|
|
976
1480
|
var SRT_PACKAGE = "@anthropic-ai/sandbox-runtime";
|
|
977
1481
|
var SENSITIVE_PATHS = [
|
|
978
1482
|
// SSH & crypto keys
|
|
@@ -1037,7 +1541,7 @@ var sandboxInitialized = false;
|
|
|
1037
1541
|
async function importSandboxManager() {
|
|
1038
1542
|
try {
|
|
1039
1543
|
const globalRoot = execSync("npm root -g", { encoding: "utf-8" }).trim();
|
|
1040
|
-
const srtPath =
|
|
1544
|
+
const srtPath = join4(globalRoot, "@anthropic-ai/sandbox-runtime/dist/index.js");
|
|
1041
1545
|
const mod = await import(srtPath);
|
|
1042
1546
|
return mod.SandboxManager;
|
|
1043
1547
|
} catch {
|
|
@@ -1202,18 +1706,18 @@ async function spawnAgent(command, args, options) {
|
|
|
1202
1706
|
|
|
1203
1707
|
// src/adapters/claude.ts
|
|
1204
1708
|
import { createInterface } from "readline";
|
|
1205
|
-
import { homedir as
|
|
1709
|
+
import { homedir as homedir3 } from "os";
|
|
1206
1710
|
|
|
1207
1711
|
// src/utils/which.ts
|
|
1208
1712
|
import { execFile } from "child_process";
|
|
1209
1713
|
import { access, constants } from "fs/promises";
|
|
1210
|
-
import { homedir } from "os";
|
|
1714
|
+
import { homedir as homedir2 } from "os";
|
|
1211
1715
|
var ALLOWED_COMMANDS = /^[a-zA-Z0-9._-]+$/;
|
|
1212
1716
|
var FALLBACK_PATHS = {
|
|
1213
1717
|
claude: [
|
|
1214
1718
|
"/opt/homebrew/bin/claude",
|
|
1215
1719
|
"/usr/local/bin/claude",
|
|
1216
|
-
`${
|
|
1720
|
+
`${homedir2()}/.local/bin/claude`
|
|
1217
1721
|
]
|
|
1218
1722
|
};
|
|
1219
1723
|
async function resolveFallbackPath(command) {
|
|
@@ -1244,10 +1748,10 @@ function which(command) {
|
|
|
1244
1748
|
|
|
1245
1749
|
// src/adapters/claude.ts
|
|
1246
1750
|
import { readFile as readFile2, writeFile, mkdir } from "fs/promises";
|
|
1247
|
-
import { join as
|
|
1751
|
+
import { join as join5, relative as relative3, basename } from "path";
|
|
1248
1752
|
var DEFAULT_IDLE_TIMEOUT = 30 * 60 * 1e3;
|
|
1249
1753
|
var MIN_IDLE_TIMEOUT = 60 * 1e3;
|
|
1250
|
-
var HOME_DIR =
|
|
1754
|
+
var HOME_DIR = homedir3();
|
|
1251
1755
|
var CLAUDE_RUNTIME_ALLOW_WRITE_PATHS = [
|
|
1252
1756
|
`${HOME_DIR}/.claude`,
|
|
1253
1757
|
`${HOME_DIR}/.claude.json`,
|
|
@@ -1331,7 +1835,7 @@ var ClaudeSession = class {
|
|
|
1331
1835
|
await mkdir(workspaceRoot, { recursive: true });
|
|
1332
1836
|
for (const att of attachments) {
|
|
1333
1837
|
const safeName = basename(att.name).replace(/[^a-zA-Z0-9._-]/g, "_") || "attachment";
|
|
1334
|
-
const destPath =
|
|
1838
|
+
const destPath = join5(workspaceRoot, safeName);
|
|
1335
1839
|
try {
|
|
1336
1840
|
const res = await fetch(att.url);
|
|
1337
1841
|
if (!res.ok) {
|
|
@@ -1743,7 +2247,7 @@ function logWorkspaceHint(slug, projectPath) {
|
|
|
1743
2247
|
console.log(` ${GRAY}Workspace: ${RESET}${projectPath}`);
|
|
1744
2248
|
console.log(` ${GRAY}Put CLAUDE.md (role instructions) and .claude/skills/ here.${RESET}`);
|
|
1745
2249
|
}
|
|
1746
|
-
function
|
|
2250
|
+
function sleep2(ms) {
|
|
1747
2251
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1748
2252
|
}
|
|
1749
2253
|
function createAdapter(type, config) {
|
|
@@ -1820,14 +2324,14 @@ function registerConnectCommand(program2) {
|
|
|
1820
2324
|
type = ticketData.agent_type;
|
|
1821
2325
|
} else {
|
|
1822
2326
|
const pid = spawnBackground(slug, entry, config.token);
|
|
1823
|
-
await
|
|
2327
|
+
await sleep2(500);
|
|
1824
2328
|
if (isProcessAlive(pid)) {
|
|
1825
2329
|
console.log(` ${GREEN}\u2713${RESET} ${BOLD}${slug}${RESET} started (PID: ${pid})`);
|
|
1826
2330
|
} else {
|
|
1827
2331
|
log.error(`Failed to start. Check logs: ${getLogPath(slug)}`);
|
|
1828
2332
|
process.exit(1);
|
|
1829
2333
|
}
|
|
1830
|
-
const { ListTUI } = await import("./list-
|
|
2334
|
+
const { ListTUI } = await import("./list-KMJ463VL.js");
|
|
1831
2335
|
const tui = new ListTUI();
|
|
1832
2336
|
await tui.run();
|
|
1833
2337
|
return;
|
|
@@ -2166,7 +2670,7 @@ function registerStatusCommand(program2) {
|
|
|
2166
2670
|
}
|
|
2167
2671
|
|
|
2168
2672
|
// src/commands/start.ts
|
|
2169
|
-
function
|
|
2673
|
+
function sleep3(ms) {
|
|
2170
2674
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
2171
2675
|
}
|
|
2172
2676
|
function registerStartCommand(program2) {
|
|
@@ -2201,7 +2705,7 @@ function registerStartCommand(program2) {
|
|
|
2201
2705
|
continue;
|
|
2202
2706
|
}
|
|
2203
2707
|
const newPid = spawnBackground(t, entry, config.token);
|
|
2204
|
-
await
|
|
2708
|
+
await sleep3(500);
|
|
2205
2709
|
if (isProcessAlive(newPid)) {
|
|
2206
2710
|
console.log(` ${GREEN}\u2713${RESET} ${BOLD}${t}${RESET} started (PID: ${newPid})`);
|
|
2207
2711
|
console.log(` Logs: ${GRAY}${getLogPath(t)}${RESET}`);
|
|
@@ -2261,7 +2765,7 @@ function registerStopCommand(program2) {
|
|
|
2261
2765
|
}
|
|
2262
2766
|
|
|
2263
2767
|
// src/commands/restart.ts
|
|
2264
|
-
function
|
|
2768
|
+
function sleep4(ms) {
|
|
2265
2769
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
2266
2770
|
}
|
|
2267
2771
|
function registerRestartCommand(program2) {
|
|
@@ -2290,10 +2794,10 @@ function registerRestartCommand(program2) {
|
|
|
2290
2794
|
let restarted = 0;
|
|
2291
2795
|
for (const t of targets) {
|
|
2292
2796
|
await stopProcess(t);
|
|
2293
|
-
await
|
|
2797
|
+
await sleep4(1e3);
|
|
2294
2798
|
const entry = agents[t];
|
|
2295
2799
|
const newPid = spawnBackground(t, entry, config.token);
|
|
2296
|
-
await
|
|
2800
|
+
await sleep4(500);
|
|
2297
2801
|
if (isProcessAlive(newPid)) {
|
|
2298
2802
|
console.log(` ${GREEN}\u2713${RESET} ${BOLD}${t}${RESET} restarted (PID: ${newPid})`);
|
|
2299
2803
|
console.log(` Logs: ${GRAY}${getLogPath(t)}${RESET}`);
|
|
@@ -2312,7 +2816,7 @@ function registerRestartCommand(program2) {
|
|
|
2312
2816
|
|
|
2313
2817
|
// src/commands/logs.ts
|
|
2314
2818
|
import { spawn as spawn2 } from "child_process";
|
|
2315
|
-
import { existsSync as
|
|
2819
|
+
import { existsSync as existsSync3 } from "fs";
|
|
2316
2820
|
function registerLogsCommand(program2) {
|
|
2317
2821
|
program2.command("logs <name>").description("View agent logs (follows in real-time)").option("-n, --lines <number>", "Number of lines to show", "50").action((name, opts) => {
|
|
2318
2822
|
const entry = getAgent(name);
|
|
@@ -2321,7 +2825,7 @@ function registerLogsCommand(program2) {
|
|
|
2321
2825
|
process.exit(1);
|
|
2322
2826
|
}
|
|
2323
2827
|
const logPath = getLogPath(name);
|
|
2324
|
-
if (!
|
|
2828
|
+
if (!existsSync3(logPath)) {
|
|
2325
2829
|
log.error(`No log file found for "${name}". Has this agent been started before?`);
|
|
2326
2830
|
process.exit(1);
|
|
2327
2831
|
}
|
|
@@ -2402,14 +2906,14 @@ function registerOpenCommand(program2) {
|
|
|
2402
2906
|
}
|
|
2403
2907
|
|
|
2404
2908
|
// src/commands/install.ts
|
|
2405
|
-
import { writeFileSync, existsSync as
|
|
2406
|
-
import { join as
|
|
2407
|
-
import { homedir as
|
|
2909
|
+
import { writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
2910
|
+
import { join as join6 } from "path";
|
|
2911
|
+
import { homedir as homedir4 } from "os";
|
|
2408
2912
|
import { execSync as execSync2 } from "child_process";
|
|
2409
2913
|
var LABEL = "com.agents-hot.agent-mesh";
|
|
2410
|
-
var PLIST_DIR =
|
|
2411
|
-
var PLIST_PATH =
|
|
2412
|
-
var LOG_PATH =
|
|
2914
|
+
var PLIST_DIR = join6(homedir4(), "Library", "LaunchAgents");
|
|
2915
|
+
var PLIST_PATH = join6(PLIST_DIR, `${LABEL}.plist`);
|
|
2916
|
+
var LOG_PATH = join6(homedir4(), ".agent-mesh", "logs", "launchd.log");
|
|
2413
2917
|
function detectPaths() {
|
|
2414
2918
|
return {
|
|
2415
2919
|
node: process.execPath,
|
|
@@ -2442,7 +2946,7 @@ function generatePlist(nodePath, scriptPath) {
|
|
|
2442
2946
|
<string>${escapeXml(LOG_PATH)}</string>
|
|
2443
2947
|
|
|
2444
2948
|
<key>WorkingDirectory</key>
|
|
2445
|
-
<string>${escapeXml(
|
|
2949
|
+
<string>${escapeXml(homedir4())}</string>
|
|
2446
2950
|
</dict>
|
|
2447
2951
|
</plist>
|
|
2448
2952
|
`;
|
|
@@ -2456,7 +2960,7 @@ function registerInstallCommand(program2) {
|
|
|
2456
2960
|
log.error("LaunchAgent is macOS only. On Linux, use systemd user service instead.");
|
|
2457
2961
|
process.exit(1);
|
|
2458
2962
|
}
|
|
2459
|
-
if (
|
|
2963
|
+
if (existsSync4(PLIST_PATH) && !opts.force) {
|
|
2460
2964
|
console.log(`
|
|
2461
2965
|
${YELLOW}\u2298${RESET} LaunchAgent already installed at:`);
|
|
2462
2966
|
console.log(` ${GRAY}${PLIST_PATH}${RESET}`);
|
|
@@ -2466,17 +2970,17 @@ function registerInstallCommand(program2) {
|
|
|
2466
2970
|
return;
|
|
2467
2971
|
}
|
|
2468
2972
|
const { node, script } = detectPaths();
|
|
2469
|
-
if (!
|
|
2470
|
-
|
|
2973
|
+
if (!existsSync4(PLIST_DIR)) {
|
|
2974
|
+
mkdirSync3(PLIST_DIR, { recursive: true });
|
|
2471
2975
|
}
|
|
2472
|
-
if (
|
|
2976
|
+
if (existsSync4(PLIST_PATH)) {
|
|
2473
2977
|
try {
|
|
2474
2978
|
execSync2(`launchctl bootout gui/$(id -u) "${PLIST_PATH}" 2>/dev/null`, { stdio: "ignore" });
|
|
2475
2979
|
} catch {
|
|
2476
2980
|
}
|
|
2477
2981
|
}
|
|
2478
2982
|
const plist = generatePlist(node, script);
|
|
2479
|
-
|
|
2983
|
+
writeFileSync2(PLIST_PATH, plist, { encoding: "utf-8" });
|
|
2480
2984
|
try {
|
|
2481
2985
|
execSync2(`launchctl bootstrap gui/$(id -u) "${PLIST_PATH}"`, { stdio: "pipe" });
|
|
2482
2986
|
} catch {
|
|
@@ -2502,19 +3006,19 @@ function registerInstallCommand(program2) {
|
|
|
2502
3006
|
}
|
|
2503
3007
|
|
|
2504
3008
|
// src/commands/uninstall.ts
|
|
2505
|
-
import { existsSync as
|
|
2506
|
-
import { join as
|
|
2507
|
-
import { homedir as
|
|
3009
|
+
import { existsSync as existsSync5, unlinkSync as unlinkSync2 } from "fs";
|
|
3010
|
+
import { join as join7 } from "path";
|
|
3011
|
+
import { homedir as homedir5 } from "os";
|
|
2508
3012
|
import { execSync as execSync3 } from "child_process";
|
|
2509
3013
|
var LABEL2 = "com.agents-hot.agent-mesh";
|
|
2510
|
-
var PLIST_PATH2 =
|
|
3014
|
+
var PLIST_PATH2 = join7(homedir5(), "Library", "LaunchAgents", `${LABEL2}.plist`);
|
|
2511
3015
|
function registerUninstallCommand(program2) {
|
|
2512
3016
|
program2.command("uninstall").description("Remove macOS LaunchAgent (agents will no longer auto-start)").action(async () => {
|
|
2513
3017
|
if (process.platform !== "darwin") {
|
|
2514
3018
|
log.error("LaunchAgent is macOS only.");
|
|
2515
3019
|
process.exit(1);
|
|
2516
3020
|
}
|
|
2517
|
-
if (!
|
|
3021
|
+
if (!existsSync5(PLIST_PATH2)) {
|
|
2518
3022
|
console.log(`
|
|
2519
3023
|
${YELLOW}\u2298${RESET} No LaunchAgent found at ${GRAY}${PLIST_PATH2}${RESET}
|
|
2520
3024
|
`);
|
|
@@ -2899,7 +3403,7 @@ function parseSseChunk(raw, carry) {
|
|
|
2899
3403
|
|
|
2900
3404
|
// src/commands/chat.ts
|
|
2901
3405
|
var DEFAULT_BASE_URL3 = "https://agents.hot";
|
|
2902
|
-
function
|
|
3406
|
+
function sleep5(ms) {
|
|
2903
3407
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
2904
3408
|
}
|
|
2905
3409
|
async function asyncChat(opts) {
|
|
@@ -2931,7 +3435,7 @@ async function asyncChat(opts) {
|
|
|
2931
3435
|
const startTime = Date.now();
|
|
2932
3436
|
while (Date.now() - startTime < maxWait) {
|
|
2933
3437
|
if (opts.signal?.aborted) throw new Error("Aborted");
|
|
2934
|
-
await
|
|
3438
|
+
await sleep5(pollInterval);
|
|
2935
3439
|
const pollRes = await fetch(`${opts.baseUrl}/api/agents/${opts.agentId}/task-status/${request_id}`, {
|
|
2936
3440
|
headers: { Authorization: `Bearer ${opts.token}` },
|
|
2937
3441
|
signal: opts.signal
|
|
@@ -3153,11 +3657,11 @@ function registerChatCommand(program2) {
|
|
|
3153
3657
|
|
|
3154
3658
|
// src/commands/skills.ts
|
|
3155
3659
|
import { readFile as readFile4, writeFile as writeFile3, readdir as readdir2, mkdir as mkdir2, rm, symlink, unlink } from "fs/promises";
|
|
3156
|
-
import { join as
|
|
3660
|
+
import { join as join9, resolve, relative as relative4 } from "path";
|
|
3157
3661
|
|
|
3158
3662
|
// src/utils/skill-parser.ts
|
|
3159
3663
|
import { readFile as readFile3, writeFile as writeFile2, stat as stat2 } from "fs/promises";
|
|
3160
|
-
import { join as
|
|
3664
|
+
import { join as join8 } from "path";
|
|
3161
3665
|
function parseSkillMd(raw) {
|
|
3162
3666
|
const trimmed = raw.trimStart();
|
|
3163
3667
|
if (!trimmed.startsWith("---")) {
|
|
@@ -3217,7 +3721,7 @@ function parseSkillMd(raw) {
|
|
|
3217
3721
|
return { frontmatter, content };
|
|
3218
3722
|
}
|
|
3219
3723
|
async function loadSkillManifest(dir) {
|
|
3220
|
-
const skillMdPath =
|
|
3724
|
+
const skillMdPath = join8(dir, "SKILL.md");
|
|
3221
3725
|
try {
|
|
3222
3726
|
const raw = await readFile3(skillMdPath, "utf-8");
|
|
3223
3727
|
const { frontmatter } = parseSkillMd(raw);
|
|
@@ -3469,13 +3973,13 @@ function skillApiPath(authorLogin, slug) {
|
|
|
3469
3973
|
}
|
|
3470
3974
|
async function resolveSkillsRootAsync(pathArg) {
|
|
3471
3975
|
const projectRoot = pathArg ? resolve(pathArg) : process.cwd();
|
|
3472
|
-
const skillsDir =
|
|
3473
|
-
const claudeSkillsDir =
|
|
3976
|
+
const skillsDir = join9(projectRoot, ".agents", "skills");
|
|
3977
|
+
const claudeSkillsDir = join9(projectRoot, ".claude", "skills");
|
|
3474
3978
|
return { projectRoot, skillsDir, claudeSkillsDir };
|
|
3475
3979
|
}
|
|
3476
3980
|
async function ensureClaudeSymlink(claudeSkillsDir, slug) {
|
|
3477
3981
|
await mkdir2(claudeSkillsDir, { recursive: true });
|
|
3478
|
-
const linkPath =
|
|
3982
|
+
const linkPath = join9(claudeSkillsDir, slug);
|
|
3479
3983
|
try {
|
|
3480
3984
|
await unlink(linkPath);
|
|
3481
3985
|
} catch {
|
|
@@ -3495,7 +3999,7 @@ async function collectPackFiles(dir, manifest) {
|
|
|
3495
3999
|
}
|
|
3496
4000
|
const mainFile = manifest.main || "SKILL.md";
|
|
3497
4001
|
if (!results.includes(mainFile)) {
|
|
3498
|
-
const mainPath =
|
|
4002
|
+
const mainPath = join9(dir, mainFile);
|
|
3499
4003
|
if (await pathExists(mainPath)) {
|
|
3500
4004
|
results.unshift(mainFile);
|
|
3501
4005
|
}
|
|
@@ -3512,7 +4016,7 @@ async function walkDir(dir) {
|
|
|
3512
4016
|
}
|
|
3513
4017
|
for (const entry of entries) {
|
|
3514
4018
|
if (entry.isSymbolicLink()) continue;
|
|
3515
|
-
const fullPath =
|
|
4019
|
+
const fullPath = join9(dir, entry.name);
|
|
3516
4020
|
if (entry.isDirectory()) {
|
|
3517
4021
|
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
3518
4022
|
const sub = await walkDir(fullPath);
|
|
@@ -3530,7 +4034,7 @@ async function packSkill(dir, manifest) {
|
|
|
3530
4034
|
}
|
|
3531
4035
|
const entries = [];
|
|
3532
4036
|
for (const relPath of fileList) {
|
|
3533
|
-
const absPath =
|
|
4037
|
+
const absPath = join9(dir, relPath);
|
|
3534
4038
|
try {
|
|
3535
4039
|
const data = await readFile4(absPath);
|
|
3536
4040
|
entries.push({ path: relPath.replace(/\\/g, "/"), data });
|
|
@@ -3564,7 +4068,7 @@ function bumpVersion(current, bump) {
|
|
|
3564
4068
|
}
|
|
3565
4069
|
async function downloadAndInstallSkill(client, authorLogin, slug, skillsDir) {
|
|
3566
4070
|
const meta = await client.get(skillApiPath(authorLogin, slug));
|
|
3567
|
-
const targetDir =
|
|
4071
|
+
const targetDir = join9(skillsDir, slug);
|
|
3568
4072
|
await mkdir2(targetDir, { recursive: true });
|
|
3569
4073
|
if (meta.has_files) {
|
|
3570
4074
|
const res = await client.getRaw(`${skillApiPath(authorLogin, slug)}/download`);
|
|
@@ -3572,8 +4076,8 @@ async function downloadAndInstallSkill(client, authorLogin, slug, skillsDir) {
|
|
|
3572
4076
|
const buf = Buffer.from(arrayBuf);
|
|
3573
4077
|
const entries = extractZipBuffer(buf);
|
|
3574
4078
|
for (const entry of entries) {
|
|
3575
|
-
const filePath =
|
|
3576
|
-
const dir =
|
|
4079
|
+
const filePath = join9(targetDir, entry.path);
|
|
4080
|
+
const dir = join9(filePath, "..");
|
|
3577
4081
|
await mkdir2(dir, { recursive: true });
|
|
3578
4082
|
await writeFile3(filePath, entry.data);
|
|
3579
4083
|
}
|
|
@@ -3586,7 +4090,7 @@ async function downloadAndInstallSkill(client, authorLogin, slug, skillsDir) {
|
|
|
3586
4090
|
} else {
|
|
3587
4091
|
const res = await client.getRaw(`${skillApiPath(authorLogin, slug)}/raw`);
|
|
3588
4092
|
const content = await res.text();
|
|
3589
|
-
await writeFile3(
|
|
4093
|
+
await writeFile3(join9(targetDir, "SKILL.md"), content);
|
|
3590
4094
|
return {
|
|
3591
4095
|
slug,
|
|
3592
4096
|
name: meta.name,
|
|
@@ -3615,7 +4119,7 @@ function registerSkillsCommand(program2) {
|
|
|
3615
4119
|
try {
|
|
3616
4120
|
const dir = resolveSkillDir(pathArg);
|
|
3617
4121
|
await mkdir2(dir, { recursive: true });
|
|
3618
|
-
const skillMdPath =
|
|
4122
|
+
const skillMdPath = join9(dir, "SKILL.md");
|
|
3619
4123
|
if (await pathExists(skillMdPath)) {
|
|
3620
4124
|
const raw = await readFile4(skillMdPath, "utf-8");
|
|
3621
4125
|
const { frontmatter } = parseSkillMd(raw);
|
|
@@ -3644,7 +4148,7 @@ function registerSkillsCommand(program2) {
|
|
|
3644
4148
|
const dir = resolveSkillDir(pathArg);
|
|
3645
4149
|
const manifest = await loadSkillManifest(dir);
|
|
3646
4150
|
const result = await packSkill(dir, manifest);
|
|
3647
|
-
const outPath =
|
|
4151
|
+
const outPath = join9(dir, result.filename);
|
|
3648
4152
|
await writeFile3(outPath, result.buffer);
|
|
3649
4153
|
slog.info(`Packed ${result.files.length} files \u2192 ${result.filename} (${result.size} bytes)`);
|
|
3650
4154
|
outputJson({
|
|
@@ -3690,7 +4194,7 @@ function registerSkillsCommand(program2) {
|
|
|
3690
4194
|
if (opts.name) manifest.name = opts.name;
|
|
3691
4195
|
if (opts.version) manifest.version = opts.version;
|
|
3692
4196
|
if (opts.private !== void 0) manifest.private = opts.private;
|
|
3693
|
-
content = await readFile4(
|
|
4197
|
+
content = await readFile4(join9(dir, manifest.main || "SKILL.md"), "utf-8");
|
|
3694
4198
|
packResult = await packSkill(dir, manifest);
|
|
3695
4199
|
slog.info(`Packed ${packResult.files.length} files (${packResult.size} bytes)`);
|
|
3696
4200
|
}
|
|
@@ -3827,7 +4331,7 @@ function registerSkillsCommand(program2) {
|
|
|
3827
4331
|
skills.command("version <bump> [path]").description("Bump skill version (patch | minor | major | x.y.z)").action(async (bump, pathArg) => {
|
|
3828
4332
|
try {
|
|
3829
4333
|
const dir = resolveSkillDir(pathArg);
|
|
3830
|
-
const skillMdPath =
|
|
4334
|
+
const skillMdPath = join9(dir, "SKILL.md");
|
|
3831
4335
|
if (!await pathExists(skillMdPath)) {
|
|
3832
4336
|
outputError("not_found", "No SKILL.md found. Run `agent-mesh skills init` first.");
|
|
3833
4337
|
}
|
|
@@ -3847,7 +4351,7 @@ function registerSkillsCommand(program2) {
|
|
|
3847
4351
|
try {
|
|
3848
4352
|
const { authorLogin, slug } = parseSkillRef(ref);
|
|
3849
4353
|
const { skillsDir, claudeSkillsDir } = await resolveSkillsRootAsync(pathArg);
|
|
3850
|
-
const targetDir =
|
|
4354
|
+
const targetDir = join9(skillsDir, slug);
|
|
3851
4355
|
if (await pathExists(targetDir)) {
|
|
3852
4356
|
if (!opts.force) {
|
|
3853
4357
|
outputError("already_installed", `Skill "${slug}" is already installed at ${targetDir}. Use --force to overwrite.`);
|
|
@@ -3886,11 +4390,11 @@ function registerSkillsCommand(program2) {
|
|
|
3886
4390
|
const failed = [];
|
|
3887
4391
|
if (ref) {
|
|
3888
4392
|
const { authorLogin, slug } = parseSkillRef(ref);
|
|
3889
|
-
const targetDir =
|
|
4393
|
+
const targetDir = join9(skillsDir, slug);
|
|
3890
4394
|
if (!await pathExists(targetDir)) {
|
|
3891
4395
|
outputError("not_installed", `Skill "${slug}" is not installed. Use "skills install ${ref}" first.`);
|
|
3892
4396
|
}
|
|
3893
|
-
const skillMdPath =
|
|
4397
|
+
const skillMdPath = join9(targetDir, "SKILL.md");
|
|
3894
4398
|
let localVersion = "0.0.0";
|
|
3895
4399
|
if (await pathExists(skillMdPath)) {
|
|
3896
4400
|
const raw = await readFile4(skillMdPath, "utf-8");
|
|
@@ -3917,7 +4421,7 @@ function registerSkillsCommand(program2) {
|
|
|
3917
4421
|
for (const entry of entries) {
|
|
3918
4422
|
if (!entry.isDirectory()) continue;
|
|
3919
4423
|
const slug = entry.name;
|
|
3920
|
-
const skillMdPath =
|
|
4424
|
+
const skillMdPath = join9(skillsDir, slug, "SKILL.md");
|
|
3921
4425
|
if (!await pathExists(skillMdPath)) {
|
|
3922
4426
|
skipped.push({ slug, reason: "no_skill_md" });
|
|
3923
4427
|
continue;
|
|
@@ -3937,7 +4441,7 @@ function registerSkillsCommand(program2) {
|
|
|
3937
4441
|
skipped.push({ slug, reason: "up_to_date" });
|
|
3938
4442
|
} else {
|
|
3939
4443
|
slog.info(`Updating ${slug}: v${localVersion} \u2192 v${remoteVersion}...`);
|
|
3940
|
-
await rm(
|
|
4444
|
+
await rm(join9(skillsDir, slug), { recursive: true, force: true });
|
|
3941
4445
|
await downloadAndInstallSkill(client, authorLogin, slug, skillsDir);
|
|
3942
4446
|
updated.push({ slug, name: remote.name, old_version: localVersion, new_version: remoteVersion });
|
|
3943
4447
|
}
|
|
@@ -3958,13 +4462,13 @@ function registerSkillsCommand(program2) {
|
|
|
3958
4462
|
skills.command("remove <slug> [path]").description("Remove a locally installed skill").action(async (slug, pathArg) => {
|
|
3959
4463
|
try {
|
|
3960
4464
|
const { skillsDir, claudeSkillsDir } = await resolveSkillsRootAsync(pathArg);
|
|
3961
|
-
const targetDir =
|
|
4465
|
+
const targetDir = join9(skillsDir, slug);
|
|
3962
4466
|
if (!await pathExists(targetDir)) {
|
|
3963
4467
|
outputError("not_installed", `Skill "${slug}" is not installed at ${targetDir}`);
|
|
3964
4468
|
}
|
|
3965
4469
|
await rm(targetDir, { recursive: true, force: true });
|
|
3966
4470
|
try {
|
|
3967
|
-
await unlink(
|
|
4471
|
+
await unlink(join9(claudeSkillsDir, slug));
|
|
3968
4472
|
} catch {
|
|
3969
4473
|
}
|
|
3970
4474
|
slog.success(`Removed skill: ${slug}`);
|
|
@@ -3989,7 +4493,7 @@ function registerSkillsCommand(program2) {
|
|
|
3989
4493
|
for (const entry of entries) {
|
|
3990
4494
|
if (!entry.isDirectory()) continue;
|
|
3991
4495
|
const slug = entry.name;
|
|
3992
|
-
const skillMdPath =
|
|
4496
|
+
const skillMdPath = join9(skillsDir, slug, "SKILL.md");
|
|
3993
4497
|
if (!await pathExists(skillMdPath)) continue;
|
|
3994
4498
|
const raw = await readFile4(skillMdPath, "utf-8");
|
|
3995
4499
|
const { frontmatter } = parseSkillMd(raw);
|
|
@@ -4101,7 +4605,7 @@ function registerDiscoverCommand(program2) {
|
|
|
4101
4605
|
}
|
|
4102
4606
|
|
|
4103
4607
|
// src/commands/call.ts
|
|
4104
|
-
import { readFileSync, writeFileSync as
|
|
4608
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
4105
4609
|
var DEFAULT_BASE_URL4 = "https://agents.hot";
|
|
4106
4610
|
async function submitRating(baseUrl, token, agentId, callId, rating) {
|
|
4107
4611
|
const res = await fetch(`${baseUrl}/api/agents/${agentId}/rate`, {
|
|
@@ -4122,7 +4626,7 @@ async function submitRating(baseUrl, token, agentId, callId, rating) {
|
|
|
4122
4626
|
throw new Error(msg);
|
|
4123
4627
|
}
|
|
4124
4628
|
}
|
|
4125
|
-
function
|
|
4629
|
+
function sleep6(ms) {
|
|
4126
4630
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
4127
4631
|
}
|
|
4128
4632
|
function handleError2(err) {
|
|
@@ -4177,7 +4681,7 @@ async function asyncCall(opts) {
|
|
|
4177
4681
|
log.error("Aborted");
|
|
4178
4682
|
process.exit(1);
|
|
4179
4683
|
}
|
|
4180
|
-
await
|
|
4684
|
+
await sleep6(pollInterval);
|
|
4181
4685
|
const pollRes = await fetch(`${DEFAULT_BASE_URL4}/api/agents/${opts.id}/task-status/${request_id}`, {
|
|
4182
4686
|
headers: { Authorization: `Bearer ${opts.token}` },
|
|
4183
4687
|
signal: opts.signal
|
|
@@ -4211,7 +4715,7 @@ async function asyncCall(opts) {
|
|
|
4211
4715
|
}
|
|
4212
4716
|
}
|
|
4213
4717
|
if (opts.outputFile && result) {
|
|
4214
|
-
|
|
4718
|
+
writeFileSync3(opts.outputFile, result);
|
|
4215
4719
|
if (!opts.json) log.info(`Saved to ${opts.outputFile}`);
|
|
4216
4720
|
}
|
|
4217
4721
|
if (!opts.json) {
|
|
@@ -4364,7 +4868,7 @@ Error: ${event.message}
|
|
|
4364
4868
|
}
|
|
4365
4869
|
}
|
|
4366
4870
|
if (opts.outputFile && outputBuffer) {
|
|
4367
|
-
|
|
4871
|
+
writeFileSync3(opts.outputFile, outputBuffer);
|
|
4368
4872
|
if (!opts.json) log.info(`Saved to ${opts.outputFile}`);
|
|
4369
4873
|
}
|
|
4370
4874
|
if (!opts.json) {
|
|
@@ -4388,7 +4892,7 @@ function registerCallCommand(program2) {
|
|
|
4388
4892
|
const { id, name } = await resolveAgentId(agentInput, client);
|
|
4389
4893
|
let taskDescription = opts.task;
|
|
4390
4894
|
if (opts.inputFile) {
|
|
4391
|
-
const content =
|
|
4895
|
+
const content = readFileSync2(opts.inputFile, "utf-8");
|
|
4392
4896
|
taskDescription = `${taskDescription}
|
|
4393
4897
|
|
|
4394
4898
|
---
|
|
@@ -4748,6 +5252,80 @@ function registerRateCommand(program2) {
|
|
|
4748
5252
|
});
|
|
4749
5253
|
}
|
|
4750
5254
|
|
|
5255
|
+
// src/commands/runtime.ts
|
|
5256
|
+
function parsePositiveInt(value, flag) {
|
|
5257
|
+
const parsed = Number.parseInt(value, 10);
|
|
5258
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
5259
|
+
throw new Error(`${flag} must be a positive integer`);
|
|
5260
|
+
}
|
|
5261
|
+
return parsed;
|
|
5262
|
+
}
|
|
5263
|
+
async function renderRuntimeConfig(title) {
|
|
5264
|
+
const cfg = getRuntimeConfig();
|
|
5265
|
+
const queue = createLocalRuntimeQueue(cfg);
|
|
5266
|
+
try {
|
|
5267
|
+
const snap = await queue.snapshot();
|
|
5268
|
+
console.log("");
|
|
5269
|
+
console.log(` ${BOLD}${title}${RESET}`);
|
|
5270
|
+
console.log("");
|
|
5271
|
+
console.log(` ${GRAY}Max active requests${RESET} ${cfg.max_active_requests}`);
|
|
5272
|
+
console.log(` ${GRAY}Queue wait timeout${RESET} ${Math.floor(cfg.queue_wait_timeout_ms / 1e3)}s`);
|
|
5273
|
+
console.log(` ${GRAY}Queue max length${RESET} ${cfg.queue_max_length}`);
|
|
5274
|
+
console.log("");
|
|
5275
|
+
console.log(` ${GRAY}Current active${RESET} ${snap.active}`);
|
|
5276
|
+
console.log(` ${GRAY}Current queued${RESET} ${snap.queued}`);
|
|
5277
|
+
console.log("");
|
|
5278
|
+
} catch (err) {
|
|
5279
|
+
log.warn(`Failed to read local runtime queue status: ${String(err)}`);
|
|
5280
|
+
}
|
|
5281
|
+
}
|
|
5282
|
+
function registerRuntimeCommand(program2) {
|
|
5283
|
+
const runtime = program2.command("runtime").description("View or update local runtime queue limits (machine-local)");
|
|
5284
|
+
runtime.command("show").description("Show current local runtime limits and queue status").action(async () => {
|
|
5285
|
+
await renderRuntimeConfig("Local Runtime Settings");
|
|
5286
|
+
});
|
|
5287
|
+
runtime.command("set").description("Update local runtime limits").option("--max-active-requests <n>", "Max active requests running at once on this machine").option("--queue-wait-timeout <seconds>", "Max queue wait before failing (seconds)").option("--queue-max-length <n>", "Max queued requests before rejecting").action(async (opts) => {
|
|
5288
|
+
try {
|
|
5289
|
+
const updates = {};
|
|
5290
|
+
if (opts.maxActiveRequests !== void 0) {
|
|
5291
|
+
const n = parsePositiveInt(opts.maxActiveRequests, "--max-active-requests");
|
|
5292
|
+
if (n > 1e4) throw new Error("--max-active-requests must be <= 10000");
|
|
5293
|
+
updates.max_active_requests = n;
|
|
5294
|
+
}
|
|
5295
|
+
if (opts.queueWaitTimeout !== void 0) {
|
|
5296
|
+
const sec = parsePositiveInt(opts.queueWaitTimeout, "--queue-wait-timeout");
|
|
5297
|
+
if (sec > 86400) throw new Error("--queue-wait-timeout must be <= 86400 seconds");
|
|
5298
|
+
updates.queue_wait_timeout_ms = sec * 1e3;
|
|
5299
|
+
}
|
|
5300
|
+
if (opts.queueMaxLength !== void 0) {
|
|
5301
|
+
const n = parsePositiveInt(opts.queueMaxLength, "--queue-max-length");
|
|
5302
|
+
if (n > 1e5) throw new Error("--queue-max-length must be <= 100000");
|
|
5303
|
+
updates.queue_max_length = n;
|
|
5304
|
+
}
|
|
5305
|
+
if (Object.keys(updates).length === 0) {
|
|
5306
|
+
throw new Error("No settings provided. Use --max-active-requests / --queue-wait-timeout / --queue-max-length");
|
|
5307
|
+
}
|
|
5308
|
+
const next = updateRuntimeConfig(updates);
|
|
5309
|
+
log.success("Local runtime settings updated");
|
|
5310
|
+
console.log(` ${GRAY}max_active_requests${RESET} = ${next.max_active_requests}`);
|
|
5311
|
+
console.log(` ${GRAY}queue_wait_timeout_ms${RESET} = ${next.queue_wait_timeout_ms}`);
|
|
5312
|
+
console.log(` ${GRAY}queue_max_length${RESET} = ${next.queue_max_length}`);
|
|
5313
|
+
console.log(` ${GRAY}Note${RESET}: restart running agent processes to apply new limits.`);
|
|
5314
|
+
} catch (err) {
|
|
5315
|
+
log.error(err.message);
|
|
5316
|
+
process.exit(1);
|
|
5317
|
+
}
|
|
5318
|
+
});
|
|
5319
|
+
runtime.command("reset").description("Reset local runtime limits to defaults").action(async () => {
|
|
5320
|
+
resetRuntimeConfig();
|
|
5321
|
+
log.success("Local runtime settings reset to defaults");
|
|
5322
|
+
console.log(` ${GRAY}max_active_requests${RESET} = ${DEFAULT_RUNTIME_CONFIG.max_active_requests}`);
|
|
5323
|
+
console.log(` ${GRAY}queue_wait_timeout_ms${RESET} = ${DEFAULT_RUNTIME_CONFIG.queue_wait_timeout_ms}`);
|
|
5324
|
+
console.log(` ${GRAY}queue_max_length${RESET} = ${DEFAULT_RUNTIME_CONFIG.queue_max_length}`);
|
|
5325
|
+
console.log(` ${GRAY}Note${RESET}: restart running agent processes to apply new limits.`);
|
|
5326
|
+
});
|
|
5327
|
+
}
|
|
5328
|
+
|
|
4751
5329
|
// src/index.ts
|
|
4752
5330
|
var require2 = createRequire(import.meta.url);
|
|
4753
5331
|
var { version } = require2("../package.json");
|
|
@@ -4777,4 +5355,5 @@ registerStatsCommand(program);
|
|
|
4777
5355
|
registerSubscribeCommand(program);
|
|
4778
5356
|
registerRegisterCommand(program);
|
|
4779
5357
|
registerRateCommand(program);
|
|
5358
|
+
registerRuntimeCommand(program);
|
|
4780
5359
|
program.parse();
|