@annals/agent-mesh 0.16.2 → 0.16.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,6 +8,11 @@ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
8
8
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
9
9
  import { join } from "path";
10
10
  import { homedir } from "os";
11
+ var DEFAULT_RUNTIME_CONFIG = {
12
+ max_active_requests: 100,
13
+ queue_wait_timeout_ms: 10 * 6e4,
14
+ queue_max_length: 1e3
15
+ };
11
16
  var CONFIG_DIR = join(homedir(), ".agent-mesh");
12
17
  var CONFIG_FILE = join(CONFIG_DIR, "config.json");
13
18
  var PIDS_DIR = join(CONFIG_DIR, "pids");
@@ -35,6 +40,34 @@ function updateConfig(partial) {
35
40
  const existing = loadConfig();
36
41
  saveConfig({ ...existing, ...partial });
37
42
  }
43
+ function parsePositiveInt(raw) {
44
+ if (typeof raw !== "number" || !Number.isFinite(raw)) return void 0;
45
+ const n = Math.floor(raw);
46
+ return n > 0 ? n : void 0;
47
+ }
48
+ function resolveRuntimeConfig(config) {
49
+ const runtime = (config || loadConfig()).runtime || {};
50
+ return {
51
+ max_active_requests: parsePositiveInt(runtime.max_active_requests) ?? DEFAULT_RUNTIME_CONFIG.max_active_requests,
52
+ queue_wait_timeout_ms: parsePositiveInt(runtime.queue_wait_timeout_ms) ?? DEFAULT_RUNTIME_CONFIG.queue_wait_timeout_ms,
53
+ queue_max_length: parsePositiveInt(runtime.queue_max_length) ?? DEFAULT_RUNTIME_CONFIG.queue_max_length
54
+ };
55
+ }
56
+ function getRuntimeConfig() {
57
+ return resolveRuntimeConfig(loadConfig());
58
+ }
59
+ function updateRuntimeConfig(partial) {
60
+ const config = loadConfig();
61
+ config.runtime = { ...config.runtime || {}, ...partial };
62
+ saveConfig(config);
63
+ return resolveRuntimeConfig(config);
64
+ }
65
+ function resetRuntimeConfig() {
66
+ const config = loadConfig();
67
+ config.runtime = { ...DEFAULT_RUNTIME_CONFIG };
68
+ saveConfig(config);
69
+ return resolveRuntimeConfig(config);
70
+ }
38
71
  function getConfigPath() {
39
72
  return CONFIG_FILE;
40
73
  }
@@ -908,8 +941,13 @@ function registerListCommand(program) {
908
941
  }
909
942
 
910
943
  export {
944
+ DEFAULT_RUNTIME_CONFIG,
911
945
  loadConfig,
912
946
  updateConfig,
947
+ resolveRuntimeConfig,
948
+ getRuntimeConfig,
949
+ updateRuntimeConfig,
950
+ resetRuntimeConfig,
913
951
  getConfigPath,
914
952
  getAgent,
915
953
  addAgent,
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  } from "./chunk-W24WCWEC.js";
6
6
  import {
7
7
  BOLD,
8
+ DEFAULT_RUNTIME_CONFIG,
8
9
  GRAY,
9
10
  GREEN,
10
11
  RESET,
@@ -15,6 +16,7 @@ import {
15
16
  getAgentWorkspaceDir,
16
17
  getConfigPath,
17
18
  getLogPath,
19
+ getRuntimeConfig,
18
20
  isProcessAlive,
19
21
  listAgents,
20
22
  loadConfig,
@@ -23,12 +25,15 @@ import {
23
25
  removeAgent,
24
26
  removePid,
25
27
  renderTable,
28
+ resetRuntimeConfig,
29
+ resolveRuntimeConfig,
26
30
  spawnBackground,
27
31
  stopProcess,
28
32
  uniqueSlug,
29
33
  updateConfig,
34
+ updateRuntimeConfig,
30
35
  writePid
31
- } from "./chunk-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
@@ -3152,12 +3656,12 @@ function registerChatCommand(program2) {
3152
3656
  }
3153
3657
 
3154
3658
  // src/commands/skills.ts
3155
- import { readFile as readFile4, writeFile as writeFile3, readdir as readdir2, mkdir as mkdir2, rm } from "fs/promises";
3156
- import { join as join8, resolve, relative as relative4 } from "path";
3659
+ import { readFile as readFile4, writeFile as writeFile3, readdir as readdir2, mkdir as mkdir2, rm, symlink, unlink } from "fs/promises";
3660
+ import { join as join9, resolve, relative as relative4 } from "path";
3157
3661
 
3158
3662
  // src/utils/skill-parser.ts
3159
3663
  import { readFile as readFile3, writeFile as writeFile2, stat as stat2 } from "fs/promises";
3160
- import { join as 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,15 +3973,22 @@ function skillApiPath(authorLogin, slug) {
3469
3973
  }
3470
3974
  async function resolveSkillsRootAsync(pathArg) {
3471
3975
  const projectRoot = pathArg ? resolve(pathArg) : process.cwd();
3472
- const claudeDir = join8(projectRoot, ".claude", "skills");
3473
- if (await pathExists(claudeDir)) {
3474
- return { projectRoot, skillsDir: claudeDir, convention: "claude" };
3976
+ const skillsDir = join9(projectRoot, ".agents", "skills");
3977
+ const claudeSkillsDir = join9(projectRoot, ".claude", "skills");
3978
+ return { projectRoot, skillsDir, claudeSkillsDir };
3979
+ }
3980
+ async function ensureClaudeSymlink(claudeSkillsDir, slug) {
3981
+ await mkdir2(claudeSkillsDir, { recursive: true });
3982
+ const linkPath = join9(claudeSkillsDir, slug);
3983
+ try {
3984
+ await unlink(linkPath);
3985
+ } catch {
3475
3986
  }
3476
- const agentsDir = join8(projectRoot, ".agents", "skills");
3477
- if (await pathExists(agentsDir)) {
3478
- return { projectRoot, skillsDir: agentsDir, convention: "agents" };
3987
+ try {
3988
+ await rm(linkPath, { recursive: true, force: true });
3989
+ } catch {
3479
3990
  }
3480
- return { projectRoot, skillsDir: claudeDir, convention: "claude" };
3991
+ await symlink(`../../.agents/skills/${slug}`, linkPath);
3481
3992
  }
3482
3993
  async function collectPackFiles(dir, manifest) {
3483
3994
  const results = [];
@@ -3488,7 +3999,7 @@ async function collectPackFiles(dir, manifest) {
3488
3999
  }
3489
4000
  const mainFile = manifest.main || "SKILL.md";
3490
4001
  if (!results.includes(mainFile)) {
3491
- const mainPath = join8(dir, mainFile);
4002
+ const mainPath = join9(dir, mainFile);
3492
4003
  if (await pathExists(mainPath)) {
3493
4004
  results.unshift(mainFile);
3494
4005
  }
@@ -3505,7 +4016,7 @@ async function walkDir(dir) {
3505
4016
  }
3506
4017
  for (const entry of entries) {
3507
4018
  if (entry.isSymbolicLink()) continue;
3508
- const fullPath = join8(dir, entry.name);
4019
+ const fullPath = join9(dir, entry.name);
3509
4020
  if (entry.isDirectory()) {
3510
4021
  if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
3511
4022
  const sub = await walkDir(fullPath);
@@ -3523,7 +4034,7 @@ async function packSkill(dir, manifest) {
3523
4034
  }
3524
4035
  const entries = [];
3525
4036
  for (const relPath of fileList) {
3526
- const absPath = join8(dir, relPath);
4037
+ const absPath = join9(dir, relPath);
3527
4038
  try {
3528
4039
  const data = await readFile4(absPath);
3529
4040
  entries.push({ path: relPath.replace(/\\/g, "/"), data });
@@ -3557,7 +4068,7 @@ function bumpVersion(current, bump) {
3557
4068
  }
3558
4069
  async function downloadAndInstallSkill(client, authorLogin, slug, skillsDir) {
3559
4070
  const meta = await client.get(skillApiPath(authorLogin, slug));
3560
- const targetDir = join8(skillsDir, slug);
4071
+ const targetDir = join9(skillsDir, slug);
3561
4072
  await mkdir2(targetDir, { recursive: true });
3562
4073
  if (meta.has_files) {
3563
4074
  const res = await client.getRaw(`${skillApiPath(authorLogin, slug)}/download`);
@@ -3565,8 +4076,8 @@ async function downloadAndInstallSkill(client, authorLogin, slug, skillsDir) {
3565
4076
  const buf = Buffer.from(arrayBuf);
3566
4077
  const entries = extractZipBuffer(buf);
3567
4078
  for (const entry of entries) {
3568
- const filePath = join8(targetDir, entry.path);
3569
- const dir = join8(filePath, "..");
4079
+ const filePath = join9(targetDir, entry.path);
4080
+ const dir = join9(filePath, "..");
3570
4081
  await mkdir2(dir, { recursive: true });
3571
4082
  await writeFile3(filePath, entry.data);
3572
4083
  }
@@ -3579,7 +4090,7 @@ async function downloadAndInstallSkill(client, authorLogin, slug, skillsDir) {
3579
4090
  } else {
3580
4091
  const res = await client.getRaw(`${skillApiPath(authorLogin, slug)}/raw`);
3581
4092
  const content = await res.text();
3582
- await writeFile3(join8(targetDir, "SKILL.md"), content);
4093
+ await writeFile3(join9(targetDir, "SKILL.md"), content);
3583
4094
  return {
3584
4095
  slug,
3585
4096
  name: meta.name,
@@ -3608,7 +4119,7 @@ function registerSkillsCommand(program2) {
3608
4119
  try {
3609
4120
  const dir = resolveSkillDir(pathArg);
3610
4121
  await mkdir2(dir, { recursive: true });
3611
- const skillMdPath = join8(dir, "SKILL.md");
4122
+ const skillMdPath = join9(dir, "SKILL.md");
3612
4123
  if (await pathExists(skillMdPath)) {
3613
4124
  const raw = await readFile4(skillMdPath, "utf-8");
3614
4125
  const { frontmatter } = parseSkillMd(raw);
@@ -3637,7 +4148,7 @@ function registerSkillsCommand(program2) {
3637
4148
  const dir = resolveSkillDir(pathArg);
3638
4149
  const manifest = await loadSkillManifest(dir);
3639
4150
  const result = await packSkill(dir, manifest);
3640
- const outPath = join8(dir, result.filename);
4151
+ const outPath = join9(dir, result.filename);
3641
4152
  await writeFile3(outPath, result.buffer);
3642
4153
  slog.info(`Packed ${result.files.length} files \u2192 ${result.filename} (${result.size} bytes)`);
3643
4154
  outputJson({
@@ -3683,7 +4194,7 @@ function registerSkillsCommand(program2) {
3683
4194
  if (opts.name) manifest.name = opts.name;
3684
4195
  if (opts.version) manifest.version = opts.version;
3685
4196
  if (opts.private !== void 0) manifest.private = opts.private;
3686
- content = await readFile4(join8(dir, manifest.main || "SKILL.md"), "utf-8");
4197
+ content = await readFile4(join9(dir, manifest.main || "SKILL.md"), "utf-8");
3687
4198
  packResult = await packSkill(dir, manifest);
3688
4199
  slog.info(`Packed ${packResult.files.length} files (${packResult.size} bytes)`);
3689
4200
  }
@@ -3820,7 +4331,7 @@ function registerSkillsCommand(program2) {
3820
4331
  skills.command("version <bump> [path]").description("Bump skill version (patch | minor | major | x.y.z)").action(async (bump, pathArg) => {
3821
4332
  try {
3822
4333
  const dir = resolveSkillDir(pathArg);
3823
- const skillMdPath = join8(dir, "SKILL.md");
4334
+ const skillMdPath = join9(dir, "SKILL.md");
3824
4335
  if (!await pathExists(skillMdPath)) {
3825
4336
  outputError("not_found", "No SKILL.md found. Run `agent-mesh skills init` first.");
3826
4337
  }
@@ -3839,8 +4350,8 @@ function registerSkillsCommand(program2) {
3839
4350
  skills.command("install <ref> [path]").description("Install a skill from agents.hot (use author/slug format)").option("--force", "Overwrite if already installed").action(async (ref, pathArg, opts) => {
3840
4351
  try {
3841
4352
  const { authorLogin, slug } = parseSkillRef(ref);
3842
- const { skillsDir } = await resolveSkillsRootAsync(pathArg);
3843
- const targetDir = join8(skillsDir, slug);
4353
+ const { skillsDir, claudeSkillsDir } = await resolveSkillsRootAsync(pathArg);
4354
+ const targetDir = join9(skillsDir, slug);
3844
4355
  if (await pathExists(targetDir)) {
3845
4356
  if (!opts.force) {
3846
4357
  outputError("already_installed", `Skill "${slug}" is already installed at ${targetDir}. Use --force to overwrite.`);
@@ -3850,6 +4361,7 @@ function registerSkillsCommand(program2) {
3850
4361
  slog.info(`Installing ${authorLogin}/${slug}...`);
3851
4362
  const client = createClient();
3852
4363
  const result = await downloadAndInstallSkill(client, authorLogin, slug, skillsDir);
4364
+ await ensureClaudeSymlink(claudeSkillsDir, slug);
3853
4365
  slog.success(`Installed ${result.name} (${result.files_count} files)`);
3854
4366
  outputJson({
3855
4367
  success: true,
@@ -3878,11 +4390,11 @@ function registerSkillsCommand(program2) {
3878
4390
  const failed = [];
3879
4391
  if (ref) {
3880
4392
  const { authorLogin, slug } = parseSkillRef(ref);
3881
- const targetDir = join8(skillsDir, slug);
4393
+ const targetDir = join9(skillsDir, slug);
3882
4394
  if (!await pathExists(targetDir)) {
3883
4395
  outputError("not_installed", `Skill "${slug}" is not installed. Use "skills install ${ref}" first.`);
3884
4396
  }
3885
- const skillMdPath = join8(targetDir, "SKILL.md");
4397
+ const skillMdPath = join9(targetDir, "SKILL.md");
3886
4398
  let localVersion = "0.0.0";
3887
4399
  if (await pathExists(skillMdPath)) {
3888
4400
  const raw = await readFile4(skillMdPath, "utf-8");
@@ -3909,7 +4421,7 @@ function registerSkillsCommand(program2) {
3909
4421
  for (const entry of entries) {
3910
4422
  if (!entry.isDirectory()) continue;
3911
4423
  const slug = entry.name;
3912
- const skillMdPath = join8(skillsDir, slug, "SKILL.md");
4424
+ const skillMdPath = join9(skillsDir, slug, "SKILL.md");
3913
4425
  if (!await pathExists(skillMdPath)) {
3914
4426
  skipped.push({ slug, reason: "no_skill_md" });
3915
4427
  continue;
@@ -3929,7 +4441,7 @@ function registerSkillsCommand(program2) {
3929
4441
  skipped.push({ slug, reason: "up_to_date" });
3930
4442
  } else {
3931
4443
  slog.info(`Updating ${slug}: v${localVersion} \u2192 v${remoteVersion}...`);
3932
- await rm(join8(skillsDir, slug), { recursive: true, force: true });
4444
+ await rm(join9(skillsDir, slug), { recursive: true, force: true });
3933
4445
  await downloadAndInstallSkill(client, authorLogin, slug, skillsDir);
3934
4446
  updated.push({ slug, name: remote.name, old_version: localVersion, new_version: remoteVersion });
3935
4447
  }
@@ -3949,12 +4461,16 @@ function registerSkillsCommand(program2) {
3949
4461
  });
3950
4462
  skills.command("remove <slug> [path]").description("Remove a locally installed skill").action(async (slug, pathArg) => {
3951
4463
  try {
3952
- const { skillsDir } = await resolveSkillsRootAsync(pathArg);
3953
- const targetDir = join8(skillsDir, slug);
4464
+ const { skillsDir, claudeSkillsDir } = await resolveSkillsRootAsync(pathArg);
4465
+ const targetDir = join9(skillsDir, slug);
3954
4466
  if (!await pathExists(targetDir)) {
3955
4467
  outputError("not_installed", `Skill "${slug}" is not installed at ${targetDir}`);
3956
4468
  }
3957
4469
  await rm(targetDir, { recursive: true, force: true });
4470
+ try {
4471
+ await unlink(join9(claudeSkillsDir, slug));
4472
+ } catch {
4473
+ }
3958
4474
  slog.success(`Removed skill: ${slug}`);
3959
4475
  outputJson({ success: true, removed: slug, path: targetDir });
3960
4476
  } catch (err) {
@@ -3977,7 +4493,7 @@ function registerSkillsCommand(program2) {
3977
4493
  for (const entry of entries) {
3978
4494
  if (!entry.isDirectory()) continue;
3979
4495
  const slug = entry.name;
3980
- const skillMdPath = join8(skillsDir, slug, "SKILL.md");
4496
+ const skillMdPath = join9(skillsDir, slug, "SKILL.md");
3981
4497
  if (!await pathExists(skillMdPath)) continue;
3982
4498
  const raw = await readFile4(skillMdPath, "utf-8");
3983
4499
  const { frontmatter } = parseSkillMd(raw);
@@ -4089,7 +4605,7 @@ function registerDiscoverCommand(program2) {
4089
4605
  }
4090
4606
 
4091
4607
  // src/commands/call.ts
4092
- import { readFileSync, writeFileSync as writeFileSync2 } from "fs";
4608
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
4093
4609
  var DEFAULT_BASE_URL4 = "https://agents.hot";
4094
4610
  async function submitRating(baseUrl, token, agentId, callId, rating) {
4095
4611
  const res = await fetch(`${baseUrl}/api/agents/${agentId}/rate`, {
@@ -4110,7 +4626,7 @@ async function submitRating(baseUrl, token, agentId, callId, rating) {
4110
4626
  throw new Error(msg);
4111
4627
  }
4112
4628
  }
4113
- function sleep5(ms) {
4629
+ function sleep6(ms) {
4114
4630
  return new Promise((resolve2) => setTimeout(resolve2, ms));
4115
4631
  }
4116
4632
  function handleError2(err) {
@@ -4165,7 +4681,7 @@ async function asyncCall(opts) {
4165
4681
  log.error("Aborted");
4166
4682
  process.exit(1);
4167
4683
  }
4168
- await sleep5(pollInterval);
4684
+ await sleep6(pollInterval);
4169
4685
  const pollRes = await fetch(`${DEFAULT_BASE_URL4}/api/agents/${opts.id}/task-status/${request_id}`, {
4170
4686
  headers: { Authorization: `Bearer ${opts.token}` },
4171
4687
  signal: opts.signal
@@ -4199,7 +4715,7 @@ async function asyncCall(opts) {
4199
4715
  }
4200
4716
  }
4201
4717
  if (opts.outputFile && result) {
4202
- writeFileSync2(opts.outputFile, result);
4718
+ writeFileSync3(opts.outputFile, result);
4203
4719
  if (!opts.json) log.info(`Saved to ${opts.outputFile}`);
4204
4720
  }
4205
4721
  if (!opts.json) {
@@ -4352,7 +4868,7 @@ Error: ${event.message}
4352
4868
  }
4353
4869
  }
4354
4870
  if (opts.outputFile && outputBuffer) {
4355
- writeFileSync2(opts.outputFile, outputBuffer);
4871
+ writeFileSync3(opts.outputFile, outputBuffer);
4356
4872
  if (!opts.json) log.info(`Saved to ${opts.outputFile}`);
4357
4873
  }
4358
4874
  if (!opts.json) {
@@ -4376,7 +4892,7 @@ function registerCallCommand(program2) {
4376
4892
  const { id, name } = await resolveAgentId(agentInput, client);
4377
4893
  let taskDescription = opts.task;
4378
4894
  if (opts.inputFile) {
4379
- const content = readFileSync(opts.inputFile, "utf-8");
4895
+ const content = readFileSync2(opts.inputFile, "utf-8");
4380
4896
  taskDescription = `${taskDescription}
4381
4897
 
4382
4898
  ---
@@ -4736,6 +5252,80 @@ function registerRateCommand(program2) {
4736
5252
  });
4737
5253
  }
4738
5254
 
5255
+ // src/commands/runtime.ts
5256
+ function parsePositiveInt(value, flag) {
5257
+ const parsed = Number.parseInt(value, 10);
5258
+ if (!Number.isFinite(parsed) || parsed <= 0) {
5259
+ throw new Error(`${flag} must be a positive integer`);
5260
+ }
5261
+ return parsed;
5262
+ }
5263
+ async function renderRuntimeConfig(title) {
5264
+ const cfg = getRuntimeConfig();
5265
+ const queue = createLocalRuntimeQueue(cfg);
5266
+ try {
5267
+ const snap = await queue.snapshot();
5268
+ console.log("");
5269
+ console.log(` ${BOLD}${title}${RESET}`);
5270
+ console.log("");
5271
+ console.log(` ${GRAY}Max active requests${RESET} ${cfg.max_active_requests}`);
5272
+ console.log(` ${GRAY}Queue wait timeout${RESET} ${Math.floor(cfg.queue_wait_timeout_ms / 1e3)}s`);
5273
+ console.log(` ${GRAY}Queue max length${RESET} ${cfg.queue_max_length}`);
5274
+ console.log("");
5275
+ console.log(` ${GRAY}Current active${RESET} ${snap.active}`);
5276
+ console.log(` ${GRAY}Current queued${RESET} ${snap.queued}`);
5277
+ console.log("");
5278
+ } catch (err) {
5279
+ log.warn(`Failed to read local runtime queue status: ${String(err)}`);
5280
+ }
5281
+ }
5282
+ function registerRuntimeCommand(program2) {
5283
+ const runtime = program2.command("runtime").description("View or update local runtime queue limits (machine-local)");
5284
+ runtime.command("show").description("Show current local runtime limits and queue status").action(async () => {
5285
+ await renderRuntimeConfig("Local Runtime Settings");
5286
+ });
5287
+ runtime.command("set").description("Update local runtime limits").option("--max-active-requests <n>", "Max active requests running at once on this machine").option("--queue-wait-timeout <seconds>", "Max queue wait before failing (seconds)").option("--queue-max-length <n>", "Max queued requests before rejecting").action(async (opts) => {
5288
+ try {
5289
+ const updates = {};
5290
+ if (opts.maxActiveRequests !== void 0) {
5291
+ const n = parsePositiveInt(opts.maxActiveRequests, "--max-active-requests");
5292
+ if (n > 1e4) throw new Error("--max-active-requests must be <= 10000");
5293
+ updates.max_active_requests = n;
5294
+ }
5295
+ if (opts.queueWaitTimeout !== void 0) {
5296
+ const sec = parsePositiveInt(opts.queueWaitTimeout, "--queue-wait-timeout");
5297
+ if (sec > 86400) throw new Error("--queue-wait-timeout must be <= 86400 seconds");
5298
+ updates.queue_wait_timeout_ms = sec * 1e3;
5299
+ }
5300
+ if (opts.queueMaxLength !== void 0) {
5301
+ const n = parsePositiveInt(opts.queueMaxLength, "--queue-max-length");
5302
+ if (n > 1e5) throw new Error("--queue-max-length must be <= 100000");
5303
+ updates.queue_max_length = n;
5304
+ }
5305
+ if (Object.keys(updates).length === 0) {
5306
+ throw new Error("No settings provided. Use --max-active-requests / --queue-wait-timeout / --queue-max-length");
5307
+ }
5308
+ const next = updateRuntimeConfig(updates);
5309
+ log.success("Local runtime settings updated");
5310
+ console.log(` ${GRAY}max_active_requests${RESET} = ${next.max_active_requests}`);
5311
+ console.log(` ${GRAY}queue_wait_timeout_ms${RESET} = ${next.queue_wait_timeout_ms}`);
5312
+ console.log(` ${GRAY}queue_max_length${RESET} = ${next.queue_max_length}`);
5313
+ console.log(` ${GRAY}Note${RESET}: restart running agent processes to apply new limits.`);
5314
+ } catch (err) {
5315
+ log.error(err.message);
5316
+ process.exit(1);
5317
+ }
5318
+ });
5319
+ runtime.command("reset").description("Reset local runtime limits to defaults").action(async () => {
5320
+ resetRuntimeConfig();
5321
+ log.success("Local runtime settings reset to defaults");
5322
+ console.log(` ${GRAY}max_active_requests${RESET} = ${DEFAULT_RUNTIME_CONFIG.max_active_requests}`);
5323
+ console.log(` ${GRAY}queue_wait_timeout_ms${RESET} = ${DEFAULT_RUNTIME_CONFIG.queue_wait_timeout_ms}`);
5324
+ console.log(` ${GRAY}queue_max_length${RESET} = ${DEFAULT_RUNTIME_CONFIG.queue_max_length}`);
5325
+ console.log(` ${GRAY}Note${RESET}: restart running agent processes to apply new limits.`);
5326
+ });
5327
+ }
5328
+
4739
5329
  // src/index.ts
4740
5330
  var require2 = createRequire(import.meta.url);
4741
5331
  var { version } = require2("../package.json");
@@ -4765,4 +5355,5 @@ registerStatsCommand(program);
4765
5355
  registerSubscribeCommand(program);
4766
5356
  registerRegisterCommand(program);
4767
5357
  registerRateCommand(program);
5358
+ registerRuntimeCommand(program);
4768
5359
  program.parse();
@@ -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.2",
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": {