@annals/agent-mesh 0.16.2 → 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
|
|
@@ -3152,12 +3656,12 @@ function registerChatCommand(program2) {
|
|
|
3152
3656
|
}
|
|
3153
3657
|
|
|
3154
3658
|
// src/commands/skills.ts
|
|
3155
|
-
import { readFile as readFile4, writeFile as writeFile3, readdir as readdir2, mkdir as mkdir2, rm } from "fs/promises";
|
|
3156
|
-
import { join as
|
|
3659
|
+
import { readFile as readFile4, writeFile as writeFile3, readdir as readdir2, mkdir as mkdir2, rm, symlink, unlink } from "fs/promises";
|
|
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,15 +3973,22 @@ function skillApiPath(authorLogin, slug) {
|
|
|
3469
3973
|
}
|
|
3470
3974
|
async function resolveSkillsRootAsync(pathArg) {
|
|
3471
3975
|
const projectRoot = pathArg ? resolve(pathArg) : process.cwd();
|
|
3472
|
-
const
|
|
3473
|
-
|
|
3474
|
-
|
|
3976
|
+
const skillsDir = join9(projectRoot, ".agents", "skills");
|
|
3977
|
+
const claudeSkillsDir = join9(projectRoot, ".claude", "skills");
|
|
3978
|
+
return { projectRoot, skillsDir, claudeSkillsDir };
|
|
3979
|
+
}
|
|
3980
|
+
async function ensureClaudeSymlink(claudeSkillsDir, slug) {
|
|
3981
|
+
await mkdir2(claudeSkillsDir, { recursive: true });
|
|
3982
|
+
const linkPath = join9(claudeSkillsDir, slug);
|
|
3983
|
+
try {
|
|
3984
|
+
await unlink(linkPath);
|
|
3985
|
+
} catch {
|
|
3475
3986
|
}
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3987
|
+
try {
|
|
3988
|
+
await rm(linkPath, { recursive: true, force: true });
|
|
3989
|
+
} catch {
|
|
3479
3990
|
}
|
|
3480
|
-
|
|
3991
|
+
await symlink(`../../.agents/skills/${slug}`, linkPath);
|
|
3481
3992
|
}
|
|
3482
3993
|
async function collectPackFiles(dir, manifest) {
|
|
3483
3994
|
const results = [];
|
|
@@ -3488,7 +3999,7 @@ async function collectPackFiles(dir, manifest) {
|
|
|
3488
3999
|
}
|
|
3489
4000
|
const mainFile = manifest.main || "SKILL.md";
|
|
3490
4001
|
if (!results.includes(mainFile)) {
|
|
3491
|
-
const mainPath =
|
|
4002
|
+
const mainPath = join9(dir, mainFile);
|
|
3492
4003
|
if (await pathExists(mainPath)) {
|
|
3493
4004
|
results.unshift(mainFile);
|
|
3494
4005
|
}
|
|
@@ -3505,7 +4016,7 @@ async function walkDir(dir) {
|
|
|
3505
4016
|
}
|
|
3506
4017
|
for (const entry of entries) {
|
|
3507
4018
|
if (entry.isSymbolicLink()) continue;
|
|
3508
|
-
const fullPath =
|
|
4019
|
+
const fullPath = join9(dir, entry.name);
|
|
3509
4020
|
if (entry.isDirectory()) {
|
|
3510
4021
|
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
3511
4022
|
const sub = await walkDir(fullPath);
|
|
@@ -3523,7 +4034,7 @@ async function packSkill(dir, manifest) {
|
|
|
3523
4034
|
}
|
|
3524
4035
|
const entries = [];
|
|
3525
4036
|
for (const relPath of fileList) {
|
|
3526
|
-
const absPath =
|
|
4037
|
+
const absPath = join9(dir, relPath);
|
|
3527
4038
|
try {
|
|
3528
4039
|
const data = await readFile4(absPath);
|
|
3529
4040
|
entries.push({ path: relPath.replace(/\\/g, "/"), data });
|
|
@@ -3557,7 +4068,7 @@ function bumpVersion(current, bump) {
|
|
|
3557
4068
|
}
|
|
3558
4069
|
async function downloadAndInstallSkill(client, authorLogin, slug, skillsDir) {
|
|
3559
4070
|
const meta = await client.get(skillApiPath(authorLogin, slug));
|
|
3560
|
-
const targetDir =
|
|
4071
|
+
const targetDir = join9(skillsDir, slug);
|
|
3561
4072
|
await mkdir2(targetDir, { recursive: true });
|
|
3562
4073
|
if (meta.has_files) {
|
|
3563
4074
|
const res = await client.getRaw(`${skillApiPath(authorLogin, slug)}/download`);
|
|
@@ -3565,8 +4076,8 @@ async function downloadAndInstallSkill(client, authorLogin, slug, skillsDir) {
|
|
|
3565
4076
|
const buf = Buffer.from(arrayBuf);
|
|
3566
4077
|
const entries = extractZipBuffer(buf);
|
|
3567
4078
|
for (const entry of entries) {
|
|
3568
|
-
const filePath =
|
|
3569
|
-
const dir =
|
|
4079
|
+
const filePath = join9(targetDir, entry.path);
|
|
4080
|
+
const dir = join9(filePath, "..");
|
|
3570
4081
|
await mkdir2(dir, { recursive: true });
|
|
3571
4082
|
await writeFile3(filePath, entry.data);
|
|
3572
4083
|
}
|
|
@@ -3579,7 +4090,7 @@ async function downloadAndInstallSkill(client, authorLogin, slug, skillsDir) {
|
|
|
3579
4090
|
} else {
|
|
3580
4091
|
const res = await client.getRaw(`${skillApiPath(authorLogin, slug)}/raw`);
|
|
3581
4092
|
const content = await res.text();
|
|
3582
|
-
await writeFile3(
|
|
4093
|
+
await writeFile3(join9(targetDir, "SKILL.md"), content);
|
|
3583
4094
|
return {
|
|
3584
4095
|
slug,
|
|
3585
4096
|
name: meta.name,
|
|
@@ -3608,7 +4119,7 @@ function registerSkillsCommand(program2) {
|
|
|
3608
4119
|
try {
|
|
3609
4120
|
const dir = resolveSkillDir(pathArg);
|
|
3610
4121
|
await mkdir2(dir, { recursive: true });
|
|
3611
|
-
const skillMdPath =
|
|
4122
|
+
const skillMdPath = join9(dir, "SKILL.md");
|
|
3612
4123
|
if (await pathExists(skillMdPath)) {
|
|
3613
4124
|
const raw = await readFile4(skillMdPath, "utf-8");
|
|
3614
4125
|
const { frontmatter } = parseSkillMd(raw);
|
|
@@ -3637,7 +4148,7 @@ function registerSkillsCommand(program2) {
|
|
|
3637
4148
|
const dir = resolveSkillDir(pathArg);
|
|
3638
4149
|
const manifest = await loadSkillManifest(dir);
|
|
3639
4150
|
const result = await packSkill(dir, manifest);
|
|
3640
|
-
const outPath =
|
|
4151
|
+
const outPath = join9(dir, result.filename);
|
|
3641
4152
|
await writeFile3(outPath, result.buffer);
|
|
3642
4153
|
slog.info(`Packed ${result.files.length} files \u2192 ${result.filename} (${result.size} bytes)`);
|
|
3643
4154
|
outputJson({
|
|
@@ -3683,7 +4194,7 @@ function registerSkillsCommand(program2) {
|
|
|
3683
4194
|
if (opts.name) manifest.name = opts.name;
|
|
3684
4195
|
if (opts.version) manifest.version = opts.version;
|
|
3685
4196
|
if (opts.private !== void 0) manifest.private = opts.private;
|
|
3686
|
-
content = await readFile4(
|
|
4197
|
+
content = await readFile4(join9(dir, manifest.main || "SKILL.md"), "utf-8");
|
|
3687
4198
|
packResult = await packSkill(dir, manifest);
|
|
3688
4199
|
slog.info(`Packed ${packResult.files.length} files (${packResult.size} bytes)`);
|
|
3689
4200
|
}
|
|
@@ -3820,7 +4331,7 @@ function registerSkillsCommand(program2) {
|
|
|
3820
4331
|
skills.command("version <bump> [path]").description("Bump skill version (patch | minor | major | x.y.z)").action(async (bump, pathArg) => {
|
|
3821
4332
|
try {
|
|
3822
4333
|
const dir = resolveSkillDir(pathArg);
|
|
3823
|
-
const skillMdPath =
|
|
4334
|
+
const skillMdPath = join9(dir, "SKILL.md");
|
|
3824
4335
|
if (!await pathExists(skillMdPath)) {
|
|
3825
4336
|
outputError("not_found", "No SKILL.md found. Run `agent-mesh skills init` first.");
|
|
3826
4337
|
}
|
|
@@ -3839,8 +4350,8 @@ function registerSkillsCommand(program2) {
|
|
|
3839
4350
|
skills.command("install <ref> [path]").description("Install a skill from agents.hot (use author/slug format)").option("--force", "Overwrite if already installed").action(async (ref, pathArg, opts) => {
|
|
3840
4351
|
try {
|
|
3841
4352
|
const { authorLogin, slug } = parseSkillRef(ref);
|
|
3842
|
-
const { skillsDir } = await resolveSkillsRootAsync(pathArg);
|
|
3843
|
-
const targetDir =
|
|
4353
|
+
const { skillsDir, claudeSkillsDir } = await resolveSkillsRootAsync(pathArg);
|
|
4354
|
+
const targetDir = join9(skillsDir, slug);
|
|
3844
4355
|
if (await pathExists(targetDir)) {
|
|
3845
4356
|
if (!opts.force) {
|
|
3846
4357
|
outputError("already_installed", `Skill "${slug}" is already installed at ${targetDir}. Use --force to overwrite.`);
|
|
@@ -3850,6 +4361,7 @@ function registerSkillsCommand(program2) {
|
|
|
3850
4361
|
slog.info(`Installing ${authorLogin}/${slug}...`);
|
|
3851
4362
|
const client = createClient();
|
|
3852
4363
|
const result = await downloadAndInstallSkill(client, authorLogin, slug, skillsDir);
|
|
4364
|
+
await ensureClaudeSymlink(claudeSkillsDir, slug);
|
|
3853
4365
|
slog.success(`Installed ${result.name} (${result.files_count} files)`);
|
|
3854
4366
|
outputJson({
|
|
3855
4367
|
success: true,
|
|
@@ -3878,11 +4390,11 @@ function registerSkillsCommand(program2) {
|
|
|
3878
4390
|
const failed = [];
|
|
3879
4391
|
if (ref) {
|
|
3880
4392
|
const { authorLogin, slug } = parseSkillRef(ref);
|
|
3881
|
-
const targetDir =
|
|
4393
|
+
const targetDir = join9(skillsDir, slug);
|
|
3882
4394
|
if (!await pathExists(targetDir)) {
|
|
3883
4395
|
outputError("not_installed", `Skill "${slug}" is not installed. Use "skills install ${ref}" first.`);
|
|
3884
4396
|
}
|
|
3885
|
-
const skillMdPath =
|
|
4397
|
+
const skillMdPath = join9(targetDir, "SKILL.md");
|
|
3886
4398
|
let localVersion = "0.0.0";
|
|
3887
4399
|
if (await pathExists(skillMdPath)) {
|
|
3888
4400
|
const raw = await readFile4(skillMdPath, "utf-8");
|
|
@@ -3909,7 +4421,7 @@ function registerSkillsCommand(program2) {
|
|
|
3909
4421
|
for (const entry of entries) {
|
|
3910
4422
|
if (!entry.isDirectory()) continue;
|
|
3911
4423
|
const slug = entry.name;
|
|
3912
|
-
const skillMdPath =
|
|
4424
|
+
const skillMdPath = join9(skillsDir, slug, "SKILL.md");
|
|
3913
4425
|
if (!await pathExists(skillMdPath)) {
|
|
3914
4426
|
skipped.push({ slug, reason: "no_skill_md" });
|
|
3915
4427
|
continue;
|
|
@@ -3929,7 +4441,7 @@ function registerSkillsCommand(program2) {
|
|
|
3929
4441
|
skipped.push({ slug, reason: "up_to_date" });
|
|
3930
4442
|
} else {
|
|
3931
4443
|
slog.info(`Updating ${slug}: v${localVersion} \u2192 v${remoteVersion}...`);
|
|
3932
|
-
await rm(
|
|
4444
|
+
await rm(join9(skillsDir, slug), { recursive: true, force: true });
|
|
3933
4445
|
await downloadAndInstallSkill(client, authorLogin, slug, skillsDir);
|
|
3934
4446
|
updated.push({ slug, name: remote.name, old_version: localVersion, new_version: remoteVersion });
|
|
3935
4447
|
}
|
|
@@ -3949,12 +4461,16 @@ function registerSkillsCommand(program2) {
|
|
|
3949
4461
|
});
|
|
3950
4462
|
skills.command("remove <slug> [path]").description("Remove a locally installed skill").action(async (slug, pathArg) => {
|
|
3951
4463
|
try {
|
|
3952
|
-
const { skillsDir } = await resolveSkillsRootAsync(pathArg);
|
|
3953
|
-
const targetDir =
|
|
4464
|
+
const { skillsDir, claudeSkillsDir } = await resolveSkillsRootAsync(pathArg);
|
|
4465
|
+
const targetDir = join9(skillsDir, slug);
|
|
3954
4466
|
if (!await pathExists(targetDir)) {
|
|
3955
4467
|
outputError("not_installed", `Skill "${slug}" is not installed at ${targetDir}`);
|
|
3956
4468
|
}
|
|
3957
4469
|
await rm(targetDir, { recursive: true, force: true });
|
|
4470
|
+
try {
|
|
4471
|
+
await unlink(join9(claudeSkillsDir, slug));
|
|
4472
|
+
} catch {
|
|
4473
|
+
}
|
|
3958
4474
|
slog.success(`Removed skill: ${slug}`);
|
|
3959
4475
|
outputJson({ success: true, removed: slug, path: targetDir });
|
|
3960
4476
|
} catch (err) {
|
|
@@ -3977,7 +4493,7 @@ function registerSkillsCommand(program2) {
|
|
|
3977
4493
|
for (const entry of entries) {
|
|
3978
4494
|
if (!entry.isDirectory()) continue;
|
|
3979
4495
|
const slug = entry.name;
|
|
3980
|
-
const skillMdPath =
|
|
4496
|
+
const skillMdPath = join9(skillsDir, slug, "SKILL.md");
|
|
3981
4497
|
if (!await pathExists(skillMdPath)) continue;
|
|
3982
4498
|
const raw = await readFile4(skillMdPath, "utf-8");
|
|
3983
4499
|
const { frontmatter } = parseSkillMd(raw);
|
|
@@ -4089,7 +4605,7 @@ function registerDiscoverCommand(program2) {
|
|
|
4089
4605
|
}
|
|
4090
4606
|
|
|
4091
4607
|
// src/commands/call.ts
|
|
4092
|
-
import { readFileSync, writeFileSync as
|
|
4608
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
4093
4609
|
var DEFAULT_BASE_URL4 = "https://agents.hot";
|
|
4094
4610
|
async function submitRating(baseUrl, token, agentId, callId, rating) {
|
|
4095
4611
|
const res = await fetch(`${baseUrl}/api/agents/${agentId}/rate`, {
|
|
@@ -4110,7 +4626,7 @@ async function submitRating(baseUrl, token, agentId, callId, rating) {
|
|
|
4110
4626
|
throw new Error(msg);
|
|
4111
4627
|
}
|
|
4112
4628
|
}
|
|
4113
|
-
function
|
|
4629
|
+
function sleep6(ms) {
|
|
4114
4630
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
4115
4631
|
}
|
|
4116
4632
|
function handleError2(err) {
|
|
@@ -4165,7 +4681,7 @@ async function asyncCall(opts) {
|
|
|
4165
4681
|
log.error("Aborted");
|
|
4166
4682
|
process.exit(1);
|
|
4167
4683
|
}
|
|
4168
|
-
await
|
|
4684
|
+
await sleep6(pollInterval);
|
|
4169
4685
|
const pollRes = await fetch(`${DEFAULT_BASE_URL4}/api/agents/${opts.id}/task-status/${request_id}`, {
|
|
4170
4686
|
headers: { Authorization: `Bearer ${opts.token}` },
|
|
4171
4687
|
signal: opts.signal
|
|
@@ -4199,7 +4715,7 @@ async function asyncCall(opts) {
|
|
|
4199
4715
|
}
|
|
4200
4716
|
}
|
|
4201
4717
|
if (opts.outputFile && result) {
|
|
4202
|
-
|
|
4718
|
+
writeFileSync3(opts.outputFile, result);
|
|
4203
4719
|
if (!opts.json) log.info(`Saved to ${opts.outputFile}`);
|
|
4204
4720
|
}
|
|
4205
4721
|
if (!opts.json) {
|
|
@@ -4352,7 +4868,7 @@ Error: ${event.message}
|
|
|
4352
4868
|
}
|
|
4353
4869
|
}
|
|
4354
4870
|
if (opts.outputFile && outputBuffer) {
|
|
4355
|
-
|
|
4871
|
+
writeFileSync3(opts.outputFile, outputBuffer);
|
|
4356
4872
|
if (!opts.json) log.info(`Saved to ${opts.outputFile}`);
|
|
4357
4873
|
}
|
|
4358
4874
|
if (!opts.json) {
|
|
@@ -4376,7 +4892,7 @@ function registerCallCommand(program2) {
|
|
|
4376
4892
|
const { id, name } = await resolveAgentId(agentInput, client);
|
|
4377
4893
|
let taskDescription = opts.task;
|
|
4378
4894
|
if (opts.inputFile) {
|
|
4379
|
-
const content =
|
|
4895
|
+
const content = readFileSync2(opts.inputFile, "utf-8");
|
|
4380
4896
|
taskDescription = `${taskDescription}
|
|
4381
4897
|
|
|
4382
4898
|
---
|
|
@@ -4736,6 +5252,80 @@ function registerRateCommand(program2) {
|
|
|
4736
5252
|
});
|
|
4737
5253
|
}
|
|
4738
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
|
+
|
|
4739
5329
|
// src/index.ts
|
|
4740
5330
|
var require2 = createRequire(import.meta.url);
|
|
4741
5331
|
var { version } = require2("../package.json");
|
|
@@ -4765,4 +5355,5 @@ registerStatsCommand(program);
|
|
|
4765
5355
|
registerSubscribeCommand(program);
|
|
4766
5356
|
registerRegisterCommand(program);
|
|
4767
5357
|
registerRateCommand(program);
|
|
5358
|
+
registerRuntimeCommand(program);
|
|
4768
5359
|
program.parse();
|