@annals/agent-mesh 0.16.3 → 0.16.5
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("---")) {
|
|
@@ -3172,8 +3676,21 @@ function parseSkillMd(raw) {
|
|
|
3172
3676
|
const frontmatter = {};
|
|
3173
3677
|
let currentKey = null;
|
|
3174
3678
|
let currentArray = null;
|
|
3175
|
-
|
|
3679
|
+
let currentMultiline = null;
|
|
3680
|
+
const lines = yamlBlock.split("\n");
|
|
3681
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3682
|
+
const line = lines[i];
|
|
3176
3683
|
const trimLine = line.trim();
|
|
3684
|
+
if (currentKey && currentMultiline) {
|
|
3685
|
+
if (line.match(/^[ ]{2,}/) && (trimLine || currentMultiline.lines.length > 0)) {
|
|
3686
|
+
currentMultiline.lines.push(trimLine);
|
|
3687
|
+
continue;
|
|
3688
|
+
}
|
|
3689
|
+
const joined = currentMultiline.style === "|" ? currentMultiline.lines.join("\n") : currentMultiline.lines.join(" ");
|
|
3690
|
+
frontmatter[currentKey] = joined.trim();
|
|
3691
|
+
currentKey = null;
|
|
3692
|
+
currentMultiline = null;
|
|
3693
|
+
}
|
|
3177
3694
|
if (!trimLine || trimLine.startsWith("#")) continue;
|
|
3178
3695
|
if (trimLine.startsWith("- ") && currentKey && currentArray) {
|
|
3179
3696
|
currentArray.push(trimLine.slice(2).trim().replace(/^["']|["']$/g, ""));
|
|
@@ -3193,6 +3710,11 @@ function parseSkillMd(raw) {
|
|
|
3193
3710
|
currentArray = [];
|
|
3194
3711
|
continue;
|
|
3195
3712
|
}
|
|
3713
|
+
if (rawVal === "|" || rawVal === ">") {
|
|
3714
|
+
currentKey = key;
|
|
3715
|
+
currentMultiline = { style: rawVal, lines: [] };
|
|
3716
|
+
continue;
|
|
3717
|
+
}
|
|
3196
3718
|
if (rawVal.startsWith("[") && rawVal.endsWith("]")) {
|
|
3197
3719
|
frontmatter[key] = rawVal.slice(1, -1).split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
|
|
3198
3720
|
continue;
|
|
@@ -3211,13 +3733,17 @@ function parseSkillMd(raw) {
|
|
|
3211
3733
|
}
|
|
3212
3734
|
frontmatter[key] = rawVal.replace(/^["']|["']$/g, "");
|
|
3213
3735
|
}
|
|
3736
|
+
if (currentKey && currentMultiline) {
|
|
3737
|
+
const joined = currentMultiline.style === "|" ? currentMultiline.lines.join("\n") : currentMultiline.lines.join(" ");
|
|
3738
|
+
frontmatter[currentKey] = joined.trim();
|
|
3739
|
+
}
|
|
3214
3740
|
if (currentKey && currentArray) {
|
|
3215
3741
|
frontmatter[currentKey] = currentArray;
|
|
3216
3742
|
}
|
|
3217
3743
|
return { frontmatter, content };
|
|
3218
3744
|
}
|
|
3219
3745
|
async function loadSkillManifest(dir) {
|
|
3220
|
-
const skillMdPath =
|
|
3746
|
+
const skillMdPath = join8(dir, "SKILL.md");
|
|
3221
3747
|
try {
|
|
3222
3748
|
const raw = await readFile3(skillMdPath, "utf-8");
|
|
3223
3749
|
const { frontmatter } = parseSkillMd(raw);
|
|
@@ -3469,13 +3995,13 @@ function skillApiPath(authorLogin, slug) {
|
|
|
3469
3995
|
}
|
|
3470
3996
|
async function resolveSkillsRootAsync(pathArg) {
|
|
3471
3997
|
const projectRoot = pathArg ? resolve(pathArg) : process.cwd();
|
|
3472
|
-
const skillsDir =
|
|
3473
|
-
const claudeSkillsDir =
|
|
3998
|
+
const skillsDir = join9(projectRoot, ".agents", "skills");
|
|
3999
|
+
const claudeSkillsDir = join9(projectRoot, ".claude", "skills");
|
|
3474
4000
|
return { projectRoot, skillsDir, claudeSkillsDir };
|
|
3475
4001
|
}
|
|
3476
4002
|
async function ensureClaudeSymlink(claudeSkillsDir, slug) {
|
|
3477
4003
|
await mkdir2(claudeSkillsDir, { recursive: true });
|
|
3478
|
-
const linkPath =
|
|
4004
|
+
const linkPath = join9(claudeSkillsDir, slug);
|
|
3479
4005
|
try {
|
|
3480
4006
|
await unlink(linkPath);
|
|
3481
4007
|
} catch {
|
|
@@ -3495,7 +4021,7 @@ async function collectPackFiles(dir, manifest) {
|
|
|
3495
4021
|
}
|
|
3496
4022
|
const mainFile = manifest.main || "SKILL.md";
|
|
3497
4023
|
if (!results.includes(mainFile)) {
|
|
3498
|
-
const mainPath =
|
|
4024
|
+
const mainPath = join9(dir, mainFile);
|
|
3499
4025
|
if (await pathExists(mainPath)) {
|
|
3500
4026
|
results.unshift(mainFile);
|
|
3501
4027
|
}
|
|
@@ -3512,7 +4038,7 @@ async function walkDir(dir) {
|
|
|
3512
4038
|
}
|
|
3513
4039
|
for (const entry of entries) {
|
|
3514
4040
|
if (entry.isSymbolicLink()) continue;
|
|
3515
|
-
const fullPath =
|
|
4041
|
+
const fullPath = join9(dir, entry.name);
|
|
3516
4042
|
if (entry.isDirectory()) {
|
|
3517
4043
|
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
3518
4044
|
const sub = await walkDir(fullPath);
|
|
@@ -3530,7 +4056,7 @@ async function packSkill(dir, manifest) {
|
|
|
3530
4056
|
}
|
|
3531
4057
|
const entries = [];
|
|
3532
4058
|
for (const relPath of fileList) {
|
|
3533
|
-
const absPath =
|
|
4059
|
+
const absPath = join9(dir, relPath);
|
|
3534
4060
|
try {
|
|
3535
4061
|
const data = await readFile4(absPath);
|
|
3536
4062
|
entries.push({ path: relPath.replace(/\\/g, "/"), data });
|
|
@@ -3564,7 +4090,7 @@ function bumpVersion(current, bump) {
|
|
|
3564
4090
|
}
|
|
3565
4091
|
async function downloadAndInstallSkill(client, authorLogin, slug, skillsDir) {
|
|
3566
4092
|
const meta = await client.get(skillApiPath(authorLogin, slug));
|
|
3567
|
-
const targetDir =
|
|
4093
|
+
const targetDir = join9(skillsDir, slug);
|
|
3568
4094
|
await mkdir2(targetDir, { recursive: true });
|
|
3569
4095
|
if (meta.has_files) {
|
|
3570
4096
|
const res = await client.getRaw(`${skillApiPath(authorLogin, slug)}/download`);
|
|
@@ -3572,8 +4098,8 @@ async function downloadAndInstallSkill(client, authorLogin, slug, skillsDir) {
|
|
|
3572
4098
|
const buf = Buffer.from(arrayBuf);
|
|
3573
4099
|
const entries = extractZipBuffer(buf);
|
|
3574
4100
|
for (const entry of entries) {
|
|
3575
|
-
const filePath =
|
|
3576
|
-
const dir =
|
|
4101
|
+
const filePath = join9(targetDir, entry.path);
|
|
4102
|
+
const dir = join9(filePath, "..");
|
|
3577
4103
|
await mkdir2(dir, { recursive: true });
|
|
3578
4104
|
await writeFile3(filePath, entry.data);
|
|
3579
4105
|
}
|
|
@@ -3586,7 +4112,7 @@ async function downloadAndInstallSkill(client, authorLogin, slug, skillsDir) {
|
|
|
3586
4112
|
} else {
|
|
3587
4113
|
const res = await client.getRaw(`${skillApiPath(authorLogin, slug)}/raw`);
|
|
3588
4114
|
const content = await res.text();
|
|
3589
|
-
await writeFile3(
|
|
4115
|
+
await writeFile3(join9(targetDir, "SKILL.md"), content);
|
|
3590
4116
|
return {
|
|
3591
4117
|
slug,
|
|
3592
4118
|
name: meta.name,
|
|
@@ -3615,7 +4141,7 @@ function registerSkillsCommand(program2) {
|
|
|
3615
4141
|
try {
|
|
3616
4142
|
const dir = resolveSkillDir(pathArg);
|
|
3617
4143
|
await mkdir2(dir, { recursive: true });
|
|
3618
|
-
const skillMdPath =
|
|
4144
|
+
const skillMdPath = join9(dir, "SKILL.md");
|
|
3619
4145
|
if (await pathExists(skillMdPath)) {
|
|
3620
4146
|
const raw = await readFile4(skillMdPath, "utf-8");
|
|
3621
4147
|
const { frontmatter } = parseSkillMd(raw);
|
|
@@ -3644,7 +4170,7 @@ function registerSkillsCommand(program2) {
|
|
|
3644
4170
|
const dir = resolveSkillDir(pathArg);
|
|
3645
4171
|
const manifest = await loadSkillManifest(dir);
|
|
3646
4172
|
const result = await packSkill(dir, manifest);
|
|
3647
|
-
const outPath =
|
|
4173
|
+
const outPath = join9(dir, result.filename);
|
|
3648
4174
|
await writeFile3(outPath, result.buffer);
|
|
3649
4175
|
slog.info(`Packed ${result.files.length} files \u2192 ${result.filename} (${result.size} bytes)`);
|
|
3650
4176
|
outputJson({
|
|
@@ -3690,7 +4216,7 @@ function registerSkillsCommand(program2) {
|
|
|
3690
4216
|
if (opts.name) manifest.name = opts.name;
|
|
3691
4217
|
if (opts.version) manifest.version = opts.version;
|
|
3692
4218
|
if (opts.private !== void 0) manifest.private = opts.private;
|
|
3693
|
-
content = await readFile4(
|
|
4219
|
+
content = await readFile4(join9(dir, manifest.main || "SKILL.md"), "utf-8");
|
|
3694
4220
|
packResult = await packSkill(dir, manifest);
|
|
3695
4221
|
slog.info(`Packed ${packResult.files.length} files (${packResult.size} bytes)`);
|
|
3696
4222
|
}
|
|
@@ -3827,7 +4353,7 @@ function registerSkillsCommand(program2) {
|
|
|
3827
4353
|
skills.command("version <bump> [path]").description("Bump skill version (patch | minor | major | x.y.z)").action(async (bump, pathArg) => {
|
|
3828
4354
|
try {
|
|
3829
4355
|
const dir = resolveSkillDir(pathArg);
|
|
3830
|
-
const skillMdPath =
|
|
4356
|
+
const skillMdPath = join9(dir, "SKILL.md");
|
|
3831
4357
|
if (!await pathExists(skillMdPath)) {
|
|
3832
4358
|
outputError("not_found", "No SKILL.md found. Run `agent-mesh skills init` first.");
|
|
3833
4359
|
}
|
|
@@ -3847,7 +4373,7 @@ function registerSkillsCommand(program2) {
|
|
|
3847
4373
|
try {
|
|
3848
4374
|
const { authorLogin, slug } = parseSkillRef(ref);
|
|
3849
4375
|
const { skillsDir, claudeSkillsDir } = await resolveSkillsRootAsync(pathArg);
|
|
3850
|
-
const targetDir =
|
|
4376
|
+
const targetDir = join9(skillsDir, slug);
|
|
3851
4377
|
if (await pathExists(targetDir)) {
|
|
3852
4378
|
if (!opts.force) {
|
|
3853
4379
|
outputError("already_installed", `Skill "${slug}" is already installed at ${targetDir}. Use --force to overwrite.`);
|
|
@@ -3886,11 +4412,11 @@ function registerSkillsCommand(program2) {
|
|
|
3886
4412
|
const failed = [];
|
|
3887
4413
|
if (ref) {
|
|
3888
4414
|
const { authorLogin, slug } = parseSkillRef(ref);
|
|
3889
|
-
const targetDir =
|
|
4415
|
+
const targetDir = join9(skillsDir, slug);
|
|
3890
4416
|
if (!await pathExists(targetDir)) {
|
|
3891
4417
|
outputError("not_installed", `Skill "${slug}" is not installed. Use "skills install ${ref}" first.`);
|
|
3892
4418
|
}
|
|
3893
|
-
const skillMdPath =
|
|
4419
|
+
const skillMdPath = join9(targetDir, "SKILL.md");
|
|
3894
4420
|
let localVersion = "0.0.0";
|
|
3895
4421
|
if (await pathExists(skillMdPath)) {
|
|
3896
4422
|
const raw = await readFile4(skillMdPath, "utf-8");
|
|
@@ -3917,7 +4443,7 @@ function registerSkillsCommand(program2) {
|
|
|
3917
4443
|
for (const entry of entries) {
|
|
3918
4444
|
if (!entry.isDirectory()) continue;
|
|
3919
4445
|
const slug = entry.name;
|
|
3920
|
-
const skillMdPath =
|
|
4446
|
+
const skillMdPath = join9(skillsDir, slug, "SKILL.md");
|
|
3921
4447
|
if (!await pathExists(skillMdPath)) {
|
|
3922
4448
|
skipped.push({ slug, reason: "no_skill_md" });
|
|
3923
4449
|
continue;
|
|
@@ -3937,7 +4463,7 @@ function registerSkillsCommand(program2) {
|
|
|
3937
4463
|
skipped.push({ slug, reason: "up_to_date" });
|
|
3938
4464
|
} else {
|
|
3939
4465
|
slog.info(`Updating ${slug}: v${localVersion} \u2192 v${remoteVersion}...`);
|
|
3940
|
-
await rm(
|
|
4466
|
+
await rm(join9(skillsDir, slug), { recursive: true, force: true });
|
|
3941
4467
|
await downloadAndInstallSkill(client, authorLogin, slug, skillsDir);
|
|
3942
4468
|
updated.push({ slug, name: remote.name, old_version: localVersion, new_version: remoteVersion });
|
|
3943
4469
|
}
|
|
@@ -3958,13 +4484,13 @@ function registerSkillsCommand(program2) {
|
|
|
3958
4484
|
skills.command("remove <slug> [path]").description("Remove a locally installed skill").action(async (slug, pathArg) => {
|
|
3959
4485
|
try {
|
|
3960
4486
|
const { skillsDir, claudeSkillsDir } = await resolveSkillsRootAsync(pathArg);
|
|
3961
|
-
const targetDir =
|
|
4487
|
+
const targetDir = join9(skillsDir, slug);
|
|
3962
4488
|
if (!await pathExists(targetDir)) {
|
|
3963
4489
|
outputError("not_installed", `Skill "${slug}" is not installed at ${targetDir}`);
|
|
3964
4490
|
}
|
|
3965
4491
|
await rm(targetDir, { recursive: true, force: true });
|
|
3966
4492
|
try {
|
|
3967
|
-
await unlink(
|
|
4493
|
+
await unlink(join9(claudeSkillsDir, slug));
|
|
3968
4494
|
} catch {
|
|
3969
4495
|
}
|
|
3970
4496
|
slog.success(`Removed skill: ${slug}`);
|
|
@@ -3989,7 +4515,7 @@ function registerSkillsCommand(program2) {
|
|
|
3989
4515
|
for (const entry of entries) {
|
|
3990
4516
|
if (!entry.isDirectory()) continue;
|
|
3991
4517
|
const slug = entry.name;
|
|
3992
|
-
const skillMdPath =
|
|
4518
|
+
const skillMdPath = join9(skillsDir, slug, "SKILL.md");
|
|
3993
4519
|
if (!await pathExists(skillMdPath)) continue;
|
|
3994
4520
|
const raw = await readFile4(skillMdPath, "utf-8");
|
|
3995
4521
|
const { frontmatter } = parseSkillMd(raw);
|
|
@@ -4101,7 +4627,7 @@ function registerDiscoverCommand(program2) {
|
|
|
4101
4627
|
}
|
|
4102
4628
|
|
|
4103
4629
|
// src/commands/call.ts
|
|
4104
|
-
import { readFileSync, writeFileSync as
|
|
4630
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
4105
4631
|
var DEFAULT_BASE_URL4 = "https://agents.hot";
|
|
4106
4632
|
async function submitRating(baseUrl, token, agentId, callId, rating) {
|
|
4107
4633
|
const res = await fetch(`${baseUrl}/api/agents/${agentId}/rate`, {
|
|
@@ -4122,7 +4648,7 @@ async function submitRating(baseUrl, token, agentId, callId, rating) {
|
|
|
4122
4648
|
throw new Error(msg);
|
|
4123
4649
|
}
|
|
4124
4650
|
}
|
|
4125
|
-
function
|
|
4651
|
+
function sleep6(ms) {
|
|
4126
4652
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
4127
4653
|
}
|
|
4128
4654
|
function handleError2(err) {
|
|
@@ -4177,7 +4703,7 @@ async function asyncCall(opts) {
|
|
|
4177
4703
|
log.error("Aborted");
|
|
4178
4704
|
process.exit(1);
|
|
4179
4705
|
}
|
|
4180
|
-
await
|
|
4706
|
+
await sleep6(pollInterval);
|
|
4181
4707
|
const pollRes = await fetch(`${DEFAULT_BASE_URL4}/api/agents/${opts.id}/task-status/${request_id}`, {
|
|
4182
4708
|
headers: { Authorization: `Bearer ${opts.token}` },
|
|
4183
4709
|
signal: opts.signal
|
|
@@ -4211,7 +4737,7 @@ async function asyncCall(opts) {
|
|
|
4211
4737
|
}
|
|
4212
4738
|
}
|
|
4213
4739
|
if (opts.outputFile && result) {
|
|
4214
|
-
|
|
4740
|
+
writeFileSync3(opts.outputFile, result);
|
|
4215
4741
|
if (!opts.json) log.info(`Saved to ${opts.outputFile}`);
|
|
4216
4742
|
}
|
|
4217
4743
|
if (!opts.json) {
|
|
@@ -4364,7 +4890,7 @@ Error: ${event.message}
|
|
|
4364
4890
|
}
|
|
4365
4891
|
}
|
|
4366
4892
|
if (opts.outputFile && outputBuffer) {
|
|
4367
|
-
|
|
4893
|
+
writeFileSync3(opts.outputFile, outputBuffer);
|
|
4368
4894
|
if (!opts.json) log.info(`Saved to ${opts.outputFile}`);
|
|
4369
4895
|
}
|
|
4370
4896
|
if (!opts.json) {
|
|
@@ -4388,7 +4914,7 @@ function registerCallCommand(program2) {
|
|
|
4388
4914
|
const { id, name } = await resolveAgentId(agentInput, client);
|
|
4389
4915
|
let taskDescription = opts.task;
|
|
4390
4916
|
if (opts.inputFile) {
|
|
4391
|
-
const content =
|
|
4917
|
+
const content = readFileSync2(opts.inputFile, "utf-8");
|
|
4392
4918
|
taskDescription = `${taskDescription}
|
|
4393
4919
|
|
|
4394
4920
|
---
|
|
@@ -4748,6 +5274,80 @@ function registerRateCommand(program2) {
|
|
|
4748
5274
|
});
|
|
4749
5275
|
}
|
|
4750
5276
|
|
|
5277
|
+
// src/commands/runtime.ts
|
|
5278
|
+
function parsePositiveInt(value, flag) {
|
|
5279
|
+
const parsed = Number.parseInt(value, 10);
|
|
5280
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
5281
|
+
throw new Error(`${flag} must be a positive integer`);
|
|
5282
|
+
}
|
|
5283
|
+
return parsed;
|
|
5284
|
+
}
|
|
5285
|
+
async function renderRuntimeConfig(title) {
|
|
5286
|
+
const cfg = getRuntimeConfig();
|
|
5287
|
+
const queue = createLocalRuntimeQueue(cfg);
|
|
5288
|
+
try {
|
|
5289
|
+
const snap = await queue.snapshot();
|
|
5290
|
+
console.log("");
|
|
5291
|
+
console.log(` ${BOLD}${title}${RESET}`);
|
|
5292
|
+
console.log("");
|
|
5293
|
+
console.log(` ${GRAY}Max active requests${RESET} ${cfg.max_active_requests}`);
|
|
5294
|
+
console.log(` ${GRAY}Queue wait timeout${RESET} ${Math.floor(cfg.queue_wait_timeout_ms / 1e3)}s`);
|
|
5295
|
+
console.log(` ${GRAY}Queue max length${RESET} ${cfg.queue_max_length}`);
|
|
5296
|
+
console.log("");
|
|
5297
|
+
console.log(` ${GRAY}Current active${RESET} ${snap.active}`);
|
|
5298
|
+
console.log(` ${GRAY}Current queued${RESET} ${snap.queued}`);
|
|
5299
|
+
console.log("");
|
|
5300
|
+
} catch (err) {
|
|
5301
|
+
log.warn(`Failed to read local runtime queue status: ${String(err)}`);
|
|
5302
|
+
}
|
|
5303
|
+
}
|
|
5304
|
+
function registerRuntimeCommand(program2) {
|
|
5305
|
+
const runtime = program2.command("runtime").description("View or update local runtime queue limits (machine-local)");
|
|
5306
|
+
runtime.command("show").description("Show current local runtime limits and queue status").action(async () => {
|
|
5307
|
+
await renderRuntimeConfig("Local Runtime Settings");
|
|
5308
|
+
});
|
|
5309
|
+
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) => {
|
|
5310
|
+
try {
|
|
5311
|
+
const updates = {};
|
|
5312
|
+
if (opts.maxActiveRequests !== void 0) {
|
|
5313
|
+
const n = parsePositiveInt(opts.maxActiveRequests, "--max-active-requests");
|
|
5314
|
+
if (n > 1e4) throw new Error("--max-active-requests must be <= 10000");
|
|
5315
|
+
updates.max_active_requests = n;
|
|
5316
|
+
}
|
|
5317
|
+
if (opts.queueWaitTimeout !== void 0) {
|
|
5318
|
+
const sec = parsePositiveInt(opts.queueWaitTimeout, "--queue-wait-timeout");
|
|
5319
|
+
if (sec > 86400) throw new Error("--queue-wait-timeout must be <= 86400 seconds");
|
|
5320
|
+
updates.queue_wait_timeout_ms = sec * 1e3;
|
|
5321
|
+
}
|
|
5322
|
+
if (opts.queueMaxLength !== void 0) {
|
|
5323
|
+
const n = parsePositiveInt(opts.queueMaxLength, "--queue-max-length");
|
|
5324
|
+
if (n > 1e5) throw new Error("--queue-max-length must be <= 100000");
|
|
5325
|
+
updates.queue_max_length = n;
|
|
5326
|
+
}
|
|
5327
|
+
if (Object.keys(updates).length === 0) {
|
|
5328
|
+
throw new Error("No settings provided. Use --max-active-requests / --queue-wait-timeout / --queue-max-length");
|
|
5329
|
+
}
|
|
5330
|
+
const next = updateRuntimeConfig(updates);
|
|
5331
|
+
log.success("Local runtime settings updated");
|
|
5332
|
+
console.log(` ${GRAY}max_active_requests${RESET} = ${next.max_active_requests}`);
|
|
5333
|
+
console.log(` ${GRAY}queue_wait_timeout_ms${RESET} = ${next.queue_wait_timeout_ms}`);
|
|
5334
|
+
console.log(` ${GRAY}queue_max_length${RESET} = ${next.queue_max_length}`);
|
|
5335
|
+
console.log(` ${GRAY}Note${RESET}: restart running agent processes to apply new limits.`);
|
|
5336
|
+
} catch (err) {
|
|
5337
|
+
log.error(err.message);
|
|
5338
|
+
process.exit(1);
|
|
5339
|
+
}
|
|
5340
|
+
});
|
|
5341
|
+
runtime.command("reset").description("Reset local runtime limits to defaults").action(async () => {
|
|
5342
|
+
resetRuntimeConfig();
|
|
5343
|
+
log.success("Local runtime settings reset to defaults");
|
|
5344
|
+
console.log(` ${GRAY}max_active_requests${RESET} = ${DEFAULT_RUNTIME_CONFIG.max_active_requests}`);
|
|
5345
|
+
console.log(` ${GRAY}queue_wait_timeout_ms${RESET} = ${DEFAULT_RUNTIME_CONFIG.queue_wait_timeout_ms}`);
|
|
5346
|
+
console.log(` ${GRAY}queue_max_length${RESET} = ${DEFAULT_RUNTIME_CONFIG.queue_max_length}`);
|
|
5347
|
+
console.log(` ${GRAY}Note${RESET}: restart running agent processes to apply new limits.`);
|
|
5348
|
+
});
|
|
5349
|
+
}
|
|
5350
|
+
|
|
4751
5351
|
// src/index.ts
|
|
4752
5352
|
var require2 = createRequire(import.meta.url);
|
|
4753
5353
|
var { version } = require2("../package.json");
|
|
@@ -4777,4 +5377,5 @@ registerStatsCommand(program);
|
|
|
4777
5377
|
registerSubscribeCommand(program);
|
|
4778
5378
|
registerRegisterCommand(program);
|
|
4779
5379
|
registerRateCommand(program);
|
|
5380
|
+
registerRuntimeCommand(program);
|
|
4780
5381
|
program.parse();
|