@annals/agent-mesh 0.16.3 → 0.16.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,6 +8,11 @@ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
8
8
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
9
9
  import { join } from "path";
10
10
  import { homedir } from "os";
11
+ var DEFAULT_RUNTIME_CONFIG = {
12
+ max_active_requests: 100,
13
+ queue_wait_timeout_ms: 10 * 6e4,
14
+ queue_max_length: 1e3
15
+ };
11
16
  var CONFIG_DIR = join(homedir(), ".agent-mesh");
12
17
  var CONFIG_FILE = join(CONFIG_DIR, "config.json");
13
18
  var PIDS_DIR = join(CONFIG_DIR, "pids");
@@ -35,6 +40,34 @@ function updateConfig(partial) {
35
40
  const existing = loadConfig();
36
41
  saveConfig({ ...existing, ...partial });
37
42
  }
43
+ function parsePositiveInt(raw) {
44
+ if (typeof raw !== "number" || !Number.isFinite(raw)) return void 0;
45
+ const n = Math.floor(raw);
46
+ return n > 0 ? n : void 0;
47
+ }
48
+ function resolveRuntimeConfig(config) {
49
+ const runtime = (config || loadConfig()).runtime || {};
50
+ return {
51
+ max_active_requests: parsePositiveInt(runtime.max_active_requests) ?? DEFAULT_RUNTIME_CONFIG.max_active_requests,
52
+ queue_wait_timeout_ms: parsePositiveInt(runtime.queue_wait_timeout_ms) ?? DEFAULT_RUNTIME_CONFIG.queue_wait_timeout_ms,
53
+ queue_max_length: parsePositiveInt(runtime.queue_max_length) ?? DEFAULT_RUNTIME_CONFIG.queue_max_length
54
+ };
55
+ }
56
+ function getRuntimeConfig() {
57
+ return resolveRuntimeConfig(loadConfig());
58
+ }
59
+ function updateRuntimeConfig(partial) {
60
+ const config = loadConfig();
61
+ config.runtime = { ...config.runtime || {}, ...partial };
62
+ saveConfig(config);
63
+ return resolveRuntimeConfig(config);
64
+ }
65
+ function resetRuntimeConfig() {
66
+ const config = loadConfig();
67
+ config.runtime = { ...DEFAULT_RUNTIME_CONFIG };
68
+ saveConfig(config);
69
+ return resolveRuntimeConfig(config);
70
+ }
38
71
  function getConfigPath() {
39
72
  return CONFIG_FILE;
40
73
  }
@@ -908,8 +941,13 @@ function registerListCommand(program) {
908
941
  }
909
942
 
910
943
  export {
944
+ DEFAULT_RUNTIME_CONFIG,
911
945
  loadConfig,
912
946
  updateConfig,
947
+ resolveRuntimeConfig,
948
+ getRuntimeConfig,
949
+ updateRuntimeConfig,
950
+ resetRuntimeConfig,
913
951
  getConfigPath,
914
952
  getAgent,
915
953
  addAgent,
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  } from "./chunk-W24WCWEC.js";
6
6
  import {
7
7
  BOLD,
8
+ DEFAULT_RUNTIME_CONFIG,
8
9
  GRAY,
9
10
  GREEN,
10
11
  RESET,
@@ -15,6 +16,7 @@ import {
15
16
  getAgentWorkspaceDir,
16
17
  getConfigPath,
17
18
  getLogPath,
19
+ getRuntimeConfig,
18
20
  isProcessAlive,
19
21
  listAgents,
20
22
  loadConfig,
@@ -23,12 +25,15 @@ import {
23
25
  removeAgent,
24
26
  removePid,
25
27
  renderTable,
28
+ resetRuntimeConfig,
29
+ resolveRuntimeConfig,
26
30
  spawnBackground,
27
31
  stopProcess,
28
32
  uniqueSlug,
29
33
  updateConfig,
34
+ updateRuntimeConfig,
30
35
  writePid
31
- } from "./chunk-GIEYJIVW.js";
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, content, attachments, upload_url, upload_token, client_id } = msg;
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 uploadCredentials = upload_url && upload_token ? { uploadUrl: upload_url, uploadToken: upload_token } : void 0;
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
- handle.send(content, attachments, uploadCredentials, client_id);
405
- this.sessionLastSeenAt.set(session_id, Date.now());
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
- log.error(`Failed to send to adapter: ${err}`);
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.ADAPTER_CRASH, `Adapter send failed: ${err}`);
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 = join(projectPath, ".bridge-clients", clientId);
589
- const isNew = !existsSync(wsDir);
590
- mkdirSync(wsDir, { recursive: true });
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 = join(wsDir, entry.name);
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 = join(projectPath, entry.name);
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 join2, relative as relative2 } from "path";
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 = join2(d, entry.name);
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 join3 } from "path";
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 = join3(globalRoot, "@anthropic-ai/sandbox-runtime/dist/index.js");
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 homedir2 } from "os";
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
- `${homedir()}/.local/bin/claude`
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 join4, relative as relative3, basename } from "path";
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 = homedir2();
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 = join4(workspaceRoot, safeName);
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 sleep(ms) {
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 sleep(500);
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-6CHWMM3O.js");
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 sleep2(ms) {
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 sleep2(500);
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 sleep3(ms) {
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 sleep3(1e3);
2797
+ await sleep4(1e3);
2294
2798
  const entry = agents[t];
2295
2799
  const newPid = spawnBackground(t, entry, config.token);
2296
- await sleep3(500);
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 existsSync2 } from "fs";
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 (!existsSync2(logPath)) {
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 existsSync3, mkdirSync as mkdirSync2 } from "fs";
2406
- import { join as join5 } from "path";
2407
- import { homedir as homedir3 } from "os";
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 = join5(homedir3(), "Library", "LaunchAgents");
2411
- var PLIST_PATH = join5(PLIST_DIR, `${LABEL}.plist`);
2412
- var LOG_PATH = join5(homedir3(), ".agent-mesh", "logs", "launchd.log");
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(homedir3())}</string>
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 (existsSync3(PLIST_PATH) && !opts.force) {
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 (!existsSync3(PLIST_DIR)) {
2470
- mkdirSync2(PLIST_DIR, { recursive: true });
2973
+ if (!existsSync4(PLIST_DIR)) {
2974
+ mkdirSync3(PLIST_DIR, { recursive: true });
2471
2975
  }
2472
- if (existsSync3(PLIST_PATH)) {
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
- writeFileSync(PLIST_PATH, plist, { encoding: "utf-8" });
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 existsSync4, unlinkSync as unlinkSync2 } from "fs";
2506
- import { join as join6 } from "path";
2507
- import { homedir as homedir4 } from "os";
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 = join6(homedir4(), "Library", "LaunchAgents", `${LABEL2}.plist`);
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 (!existsSync4(PLIST_PATH2)) {
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 sleep4(ms) {
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 sleep4(pollInterval);
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 join8, resolve, relative as relative4 } from "path";
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 join7 } from "path";
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 = join7(dir, "SKILL.md");
3724
+ const skillMdPath = join8(dir, "SKILL.md");
3221
3725
  try {
3222
3726
  const raw = await readFile3(skillMdPath, "utf-8");
3223
3727
  const { frontmatter } = parseSkillMd(raw);
@@ -3469,13 +3973,13 @@ function skillApiPath(authorLogin, slug) {
3469
3973
  }
3470
3974
  async function resolveSkillsRootAsync(pathArg) {
3471
3975
  const projectRoot = pathArg ? resolve(pathArg) : process.cwd();
3472
- const skillsDir = join8(projectRoot, ".agents", "skills");
3473
- const claudeSkillsDir = join8(projectRoot, ".claude", "skills");
3976
+ const skillsDir = join9(projectRoot, ".agents", "skills");
3977
+ const claudeSkillsDir = join9(projectRoot, ".claude", "skills");
3474
3978
  return { projectRoot, skillsDir, claudeSkillsDir };
3475
3979
  }
3476
3980
  async function ensureClaudeSymlink(claudeSkillsDir, slug) {
3477
3981
  await mkdir2(claudeSkillsDir, { recursive: true });
3478
- const linkPath = join8(claudeSkillsDir, slug);
3982
+ const linkPath = join9(claudeSkillsDir, slug);
3479
3983
  try {
3480
3984
  await unlink(linkPath);
3481
3985
  } catch {
@@ -3495,7 +3999,7 @@ async function collectPackFiles(dir, manifest) {
3495
3999
  }
3496
4000
  const mainFile = manifest.main || "SKILL.md";
3497
4001
  if (!results.includes(mainFile)) {
3498
- const mainPath = join8(dir, mainFile);
4002
+ const mainPath = join9(dir, mainFile);
3499
4003
  if (await pathExists(mainPath)) {
3500
4004
  results.unshift(mainFile);
3501
4005
  }
@@ -3512,7 +4016,7 @@ async function walkDir(dir) {
3512
4016
  }
3513
4017
  for (const entry of entries) {
3514
4018
  if (entry.isSymbolicLink()) continue;
3515
- const fullPath = join8(dir, entry.name);
4019
+ const fullPath = join9(dir, entry.name);
3516
4020
  if (entry.isDirectory()) {
3517
4021
  if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
3518
4022
  const sub = await walkDir(fullPath);
@@ -3530,7 +4034,7 @@ async function packSkill(dir, manifest) {
3530
4034
  }
3531
4035
  const entries = [];
3532
4036
  for (const relPath of fileList) {
3533
- const absPath = join8(dir, relPath);
4037
+ const absPath = join9(dir, relPath);
3534
4038
  try {
3535
4039
  const data = await readFile4(absPath);
3536
4040
  entries.push({ path: relPath.replace(/\\/g, "/"), data });
@@ -3564,7 +4068,7 @@ function bumpVersion(current, bump) {
3564
4068
  }
3565
4069
  async function downloadAndInstallSkill(client, authorLogin, slug, skillsDir) {
3566
4070
  const meta = await client.get(skillApiPath(authorLogin, slug));
3567
- const targetDir = join8(skillsDir, slug);
4071
+ const targetDir = join9(skillsDir, slug);
3568
4072
  await mkdir2(targetDir, { recursive: true });
3569
4073
  if (meta.has_files) {
3570
4074
  const res = await client.getRaw(`${skillApiPath(authorLogin, slug)}/download`);
@@ -3572,8 +4076,8 @@ async function downloadAndInstallSkill(client, authorLogin, slug, skillsDir) {
3572
4076
  const buf = Buffer.from(arrayBuf);
3573
4077
  const entries = extractZipBuffer(buf);
3574
4078
  for (const entry of entries) {
3575
- const filePath = join8(targetDir, entry.path);
3576
- const dir = join8(filePath, "..");
4079
+ const filePath = join9(targetDir, entry.path);
4080
+ const dir = join9(filePath, "..");
3577
4081
  await mkdir2(dir, { recursive: true });
3578
4082
  await writeFile3(filePath, entry.data);
3579
4083
  }
@@ -3586,7 +4090,7 @@ async function downloadAndInstallSkill(client, authorLogin, slug, skillsDir) {
3586
4090
  } else {
3587
4091
  const res = await client.getRaw(`${skillApiPath(authorLogin, slug)}/raw`);
3588
4092
  const content = await res.text();
3589
- await writeFile3(join8(targetDir, "SKILL.md"), content);
4093
+ await writeFile3(join9(targetDir, "SKILL.md"), content);
3590
4094
  return {
3591
4095
  slug,
3592
4096
  name: meta.name,
@@ -3615,7 +4119,7 @@ function registerSkillsCommand(program2) {
3615
4119
  try {
3616
4120
  const dir = resolveSkillDir(pathArg);
3617
4121
  await mkdir2(dir, { recursive: true });
3618
- const skillMdPath = join8(dir, "SKILL.md");
4122
+ const skillMdPath = join9(dir, "SKILL.md");
3619
4123
  if (await pathExists(skillMdPath)) {
3620
4124
  const raw = await readFile4(skillMdPath, "utf-8");
3621
4125
  const { frontmatter } = parseSkillMd(raw);
@@ -3644,7 +4148,7 @@ function registerSkillsCommand(program2) {
3644
4148
  const dir = resolveSkillDir(pathArg);
3645
4149
  const manifest = await loadSkillManifest(dir);
3646
4150
  const result = await packSkill(dir, manifest);
3647
- const outPath = join8(dir, result.filename);
4151
+ const outPath = join9(dir, result.filename);
3648
4152
  await writeFile3(outPath, result.buffer);
3649
4153
  slog.info(`Packed ${result.files.length} files \u2192 ${result.filename} (${result.size} bytes)`);
3650
4154
  outputJson({
@@ -3690,7 +4194,7 @@ function registerSkillsCommand(program2) {
3690
4194
  if (opts.name) manifest.name = opts.name;
3691
4195
  if (opts.version) manifest.version = opts.version;
3692
4196
  if (opts.private !== void 0) manifest.private = opts.private;
3693
- content = await readFile4(join8(dir, manifest.main || "SKILL.md"), "utf-8");
4197
+ content = await readFile4(join9(dir, manifest.main || "SKILL.md"), "utf-8");
3694
4198
  packResult = await packSkill(dir, manifest);
3695
4199
  slog.info(`Packed ${packResult.files.length} files (${packResult.size} bytes)`);
3696
4200
  }
@@ -3827,7 +4331,7 @@ function registerSkillsCommand(program2) {
3827
4331
  skills.command("version <bump> [path]").description("Bump skill version (patch | minor | major | x.y.z)").action(async (bump, pathArg) => {
3828
4332
  try {
3829
4333
  const dir = resolveSkillDir(pathArg);
3830
- const skillMdPath = join8(dir, "SKILL.md");
4334
+ const skillMdPath = join9(dir, "SKILL.md");
3831
4335
  if (!await pathExists(skillMdPath)) {
3832
4336
  outputError("not_found", "No SKILL.md found. Run `agent-mesh skills init` first.");
3833
4337
  }
@@ -3847,7 +4351,7 @@ function registerSkillsCommand(program2) {
3847
4351
  try {
3848
4352
  const { authorLogin, slug } = parseSkillRef(ref);
3849
4353
  const { skillsDir, claudeSkillsDir } = await resolveSkillsRootAsync(pathArg);
3850
- const targetDir = join8(skillsDir, slug);
4354
+ const targetDir = join9(skillsDir, slug);
3851
4355
  if (await pathExists(targetDir)) {
3852
4356
  if (!opts.force) {
3853
4357
  outputError("already_installed", `Skill "${slug}" is already installed at ${targetDir}. Use --force to overwrite.`);
@@ -3886,11 +4390,11 @@ function registerSkillsCommand(program2) {
3886
4390
  const failed = [];
3887
4391
  if (ref) {
3888
4392
  const { authorLogin, slug } = parseSkillRef(ref);
3889
- const targetDir = join8(skillsDir, slug);
4393
+ const targetDir = join9(skillsDir, slug);
3890
4394
  if (!await pathExists(targetDir)) {
3891
4395
  outputError("not_installed", `Skill "${slug}" is not installed. Use "skills install ${ref}" first.`);
3892
4396
  }
3893
- const skillMdPath = join8(targetDir, "SKILL.md");
4397
+ const skillMdPath = join9(targetDir, "SKILL.md");
3894
4398
  let localVersion = "0.0.0";
3895
4399
  if (await pathExists(skillMdPath)) {
3896
4400
  const raw = await readFile4(skillMdPath, "utf-8");
@@ -3917,7 +4421,7 @@ function registerSkillsCommand(program2) {
3917
4421
  for (const entry of entries) {
3918
4422
  if (!entry.isDirectory()) continue;
3919
4423
  const slug = entry.name;
3920
- const skillMdPath = join8(skillsDir, slug, "SKILL.md");
4424
+ const skillMdPath = join9(skillsDir, slug, "SKILL.md");
3921
4425
  if (!await pathExists(skillMdPath)) {
3922
4426
  skipped.push({ slug, reason: "no_skill_md" });
3923
4427
  continue;
@@ -3937,7 +4441,7 @@ function registerSkillsCommand(program2) {
3937
4441
  skipped.push({ slug, reason: "up_to_date" });
3938
4442
  } else {
3939
4443
  slog.info(`Updating ${slug}: v${localVersion} \u2192 v${remoteVersion}...`);
3940
- await rm(join8(skillsDir, slug), { recursive: true, force: true });
4444
+ await rm(join9(skillsDir, slug), { recursive: true, force: true });
3941
4445
  await downloadAndInstallSkill(client, authorLogin, slug, skillsDir);
3942
4446
  updated.push({ slug, name: remote.name, old_version: localVersion, new_version: remoteVersion });
3943
4447
  }
@@ -3958,13 +4462,13 @@ function registerSkillsCommand(program2) {
3958
4462
  skills.command("remove <slug> [path]").description("Remove a locally installed skill").action(async (slug, pathArg) => {
3959
4463
  try {
3960
4464
  const { skillsDir, claudeSkillsDir } = await resolveSkillsRootAsync(pathArg);
3961
- const targetDir = join8(skillsDir, slug);
4465
+ const targetDir = join9(skillsDir, slug);
3962
4466
  if (!await pathExists(targetDir)) {
3963
4467
  outputError("not_installed", `Skill "${slug}" is not installed at ${targetDir}`);
3964
4468
  }
3965
4469
  await rm(targetDir, { recursive: true, force: true });
3966
4470
  try {
3967
- await unlink(join8(claudeSkillsDir, slug));
4471
+ await unlink(join9(claudeSkillsDir, slug));
3968
4472
  } catch {
3969
4473
  }
3970
4474
  slog.success(`Removed skill: ${slug}`);
@@ -3989,7 +4493,7 @@ function registerSkillsCommand(program2) {
3989
4493
  for (const entry of entries) {
3990
4494
  if (!entry.isDirectory()) continue;
3991
4495
  const slug = entry.name;
3992
- const skillMdPath = join8(skillsDir, slug, "SKILL.md");
4496
+ const skillMdPath = join9(skillsDir, slug, "SKILL.md");
3993
4497
  if (!await pathExists(skillMdPath)) continue;
3994
4498
  const raw = await readFile4(skillMdPath, "utf-8");
3995
4499
  const { frontmatter } = parseSkillMd(raw);
@@ -4101,7 +4605,7 @@ function registerDiscoverCommand(program2) {
4101
4605
  }
4102
4606
 
4103
4607
  // src/commands/call.ts
4104
- import { readFileSync, writeFileSync as writeFileSync2 } from "fs";
4608
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
4105
4609
  var DEFAULT_BASE_URL4 = "https://agents.hot";
4106
4610
  async function submitRating(baseUrl, token, agentId, callId, rating) {
4107
4611
  const res = await fetch(`${baseUrl}/api/agents/${agentId}/rate`, {
@@ -4122,7 +4626,7 @@ async function submitRating(baseUrl, token, agentId, callId, rating) {
4122
4626
  throw new Error(msg);
4123
4627
  }
4124
4628
  }
4125
- function sleep5(ms) {
4629
+ function sleep6(ms) {
4126
4630
  return new Promise((resolve2) => setTimeout(resolve2, ms));
4127
4631
  }
4128
4632
  function handleError2(err) {
@@ -4177,7 +4681,7 @@ async function asyncCall(opts) {
4177
4681
  log.error("Aborted");
4178
4682
  process.exit(1);
4179
4683
  }
4180
- await sleep5(pollInterval);
4684
+ await sleep6(pollInterval);
4181
4685
  const pollRes = await fetch(`${DEFAULT_BASE_URL4}/api/agents/${opts.id}/task-status/${request_id}`, {
4182
4686
  headers: { Authorization: `Bearer ${opts.token}` },
4183
4687
  signal: opts.signal
@@ -4211,7 +4715,7 @@ async function asyncCall(opts) {
4211
4715
  }
4212
4716
  }
4213
4717
  if (opts.outputFile && result) {
4214
- writeFileSync2(opts.outputFile, result);
4718
+ writeFileSync3(opts.outputFile, result);
4215
4719
  if (!opts.json) log.info(`Saved to ${opts.outputFile}`);
4216
4720
  }
4217
4721
  if (!opts.json) {
@@ -4364,7 +4868,7 @@ Error: ${event.message}
4364
4868
  }
4365
4869
  }
4366
4870
  if (opts.outputFile && outputBuffer) {
4367
- writeFileSync2(opts.outputFile, outputBuffer);
4871
+ writeFileSync3(opts.outputFile, outputBuffer);
4368
4872
  if (!opts.json) log.info(`Saved to ${opts.outputFile}`);
4369
4873
  }
4370
4874
  if (!opts.json) {
@@ -4388,7 +4892,7 @@ function registerCallCommand(program2) {
4388
4892
  const { id, name } = await resolveAgentId(agentInput, client);
4389
4893
  let taskDescription = opts.task;
4390
4894
  if (opts.inputFile) {
4391
- const content = readFileSync(opts.inputFile, "utf-8");
4895
+ const content = readFileSync2(opts.inputFile, "utf-8");
4392
4896
  taskDescription = `${taskDescription}
4393
4897
 
4394
4898
  ---
@@ -4748,6 +5252,80 @@ function registerRateCommand(program2) {
4748
5252
  });
4749
5253
  }
4750
5254
 
5255
+ // src/commands/runtime.ts
5256
+ function parsePositiveInt(value, flag) {
5257
+ const parsed = Number.parseInt(value, 10);
5258
+ if (!Number.isFinite(parsed) || parsed <= 0) {
5259
+ throw new Error(`${flag} must be a positive integer`);
5260
+ }
5261
+ return parsed;
5262
+ }
5263
+ async function renderRuntimeConfig(title) {
5264
+ const cfg = getRuntimeConfig();
5265
+ const queue = createLocalRuntimeQueue(cfg);
5266
+ try {
5267
+ const snap = await queue.snapshot();
5268
+ console.log("");
5269
+ console.log(` ${BOLD}${title}${RESET}`);
5270
+ console.log("");
5271
+ console.log(` ${GRAY}Max active requests${RESET} ${cfg.max_active_requests}`);
5272
+ console.log(` ${GRAY}Queue wait timeout${RESET} ${Math.floor(cfg.queue_wait_timeout_ms / 1e3)}s`);
5273
+ console.log(` ${GRAY}Queue max length${RESET} ${cfg.queue_max_length}`);
5274
+ console.log("");
5275
+ console.log(` ${GRAY}Current active${RESET} ${snap.active}`);
5276
+ console.log(` ${GRAY}Current queued${RESET} ${snap.queued}`);
5277
+ console.log("");
5278
+ } catch (err) {
5279
+ log.warn(`Failed to read local runtime queue status: ${String(err)}`);
5280
+ }
5281
+ }
5282
+ function registerRuntimeCommand(program2) {
5283
+ const runtime = program2.command("runtime").description("View or update local runtime queue limits (machine-local)");
5284
+ runtime.command("show").description("Show current local runtime limits and queue status").action(async () => {
5285
+ await renderRuntimeConfig("Local Runtime Settings");
5286
+ });
5287
+ runtime.command("set").description("Update local runtime limits").option("--max-active-requests <n>", "Max active requests running at once on this machine").option("--queue-wait-timeout <seconds>", "Max queue wait before failing (seconds)").option("--queue-max-length <n>", "Max queued requests before rejecting").action(async (opts) => {
5288
+ try {
5289
+ const updates = {};
5290
+ if (opts.maxActiveRequests !== void 0) {
5291
+ const n = parsePositiveInt(opts.maxActiveRequests, "--max-active-requests");
5292
+ if (n > 1e4) throw new Error("--max-active-requests must be <= 10000");
5293
+ updates.max_active_requests = n;
5294
+ }
5295
+ if (opts.queueWaitTimeout !== void 0) {
5296
+ const sec = parsePositiveInt(opts.queueWaitTimeout, "--queue-wait-timeout");
5297
+ if (sec > 86400) throw new Error("--queue-wait-timeout must be <= 86400 seconds");
5298
+ updates.queue_wait_timeout_ms = sec * 1e3;
5299
+ }
5300
+ if (opts.queueMaxLength !== void 0) {
5301
+ const n = parsePositiveInt(opts.queueMaxLength, "--queue-max-length");
5302
+ if (n > 1e5) throw new Error("--queue-max-length must be <= 100000");
5303
+ updates.queue_max_length = n;
5304
+ }
5305
+ if (Object.keys(updates).length === 0) {
5306
+ throw new Error("No settings provided. Use --max-active-requests / --queue-wait-timeout / --queue-max-length");
5307
+ }
5308
+ const next = updateRuntimeConfig(updates);
5309
+ log.success("Local runtime settings updated");
5310
+ console.log(` ${GRAY}max_active_requests${RESET} = ${next.max_active_requests}`);
5311
+ console.log(` ${GRAY}queue_wait_timeout_ms${RESET} = ${next.queue_wait_timeout_ms}`);
5312
+ console.log(` ${GRAY}queue_max_length${RESET} = ${next.queue_max_length}`);
5313
+ console.log(` ${GRAY}Note${RESET}: restart running agent processes to apply new limits.`);
5314
+ } catch (err) {
5315
+ log.error(err.message);
5316
+ process.exit(1);
5317
+ }
5318
+ });
5319
+ runtime.command("reset").description("Reset local runtime limits to defaults").action(async () => {
5320
+ resetRuntimeConfig();
5321
+ log.success("Local runtime settings reset to defaults");
5322
+ console.log(` ${GRAY}max_active_requests${RESET} = ${DEFAULT_RUNTIME_CONFIG.max_active_requests}`);
5323
+ console.log(` ${GRAY}queue_wait_timeout_ms${RESET} = ${DEFAULT_RUNTIME_CONFIG.queue_wait_timeout_ms}`);
5324
+ console.log(` ${GRAY}queue_max_length${RESET} = ${DEFAULT_RUNTIME_CONFIG.queue_max_length}`);
5325
+ console.log(` ${GRAY}Note${RESET}: restart running agent processes to apply new limits.`);
5326
+ });
5327
+ }
5328
+
4751
5329
  // src/index.ts
4752
5330
  var require2 = createRequire(import.meta.url);
4753
5331
  var { version } = require2("../package.json");
@@ -4777,4 +5355,5 @@ registerStatsCommand(program);
4777
5355
  registerSubscribeCommand(program);
4778
5356
  registerRegisterCommand(program);
4779
5357
  registerRateCommand(program);
5358
+ registerRuntimeCommand(program);
4780
5359
  program.parse();
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  ListTUI,
4
4
  registerListCommand
5
- } from "./chunk-GIEYJIVW.js";
5
+ } from "./chunk-U32JDKSN.js";
6
6
  export {
7
7
  ListTUI,
8
8
  registerListCommand
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@annals/agent-mesh",
3
- "version": "0.16.3",
3
+ "version": "0.16.4",
4
4
  "description": "CLI bridge connecting local AI agents to the Agents.Hot platform",
5
5
  "type": "module",
6
6
  "bin": {