@annals/agent-mesh 0.17.8 → 0.18.0
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.
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
2
8
|
|
|
3
9
|
// src/commands/list.ts
|
|
4
10
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -951,6 +957,7 @@ function registerListCommand(program) {
|
|
|
951
957
|
}
|
|
952
958
|
|
|
953
959
|
export {
|
|
960
|
+
__require,
|
|
954
961
|
DEFAULT_RUNTIME_CONFIG,
|
|
955
962
|
loadConfig,
|
|
956
963
|
updateConfig,
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
GREEN,
|
|
7
7
|
RESET,
|
|
8
8
|
YELLOW,
|
|
9
|
+
__require,
|
|
9
10
|
addAgent,
|
|
10
11
|
findAgentByAgentId,
|
|
11
12
|
getAgent,
|
|
@@ -30,7 +31,7 @@ import {
|
|
|
30
31
|
updateConfig,
|
|
31
32
|
updateRuntimeConfig,
|
|
32
33
|
writePid
|
|
33
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-5CMYB6XV.js";
|
|
34
35
|
|
|
35
36
|
// src/index.ts
|
|
36
37
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -531,6 +532,225 @@ var FileReceiver = class {
|
|
|
531
532
|
this.dc = null;
|
|
532
533
|
}
|
|
533
534
|
};
|
|
535
|
+
var FileUploadReceiver = class {
|
|
536
|
+
peer = null;
|
|
537
|
+
expectedSize;
|
|
538
|
+
expectedSha256;
|
|
539
|
+
chunks = [];
|
|
540
|
+
receivedBytes = 0;
|
|
541
|
+
resolveComplete = null;
|
|
542
|
+
rejectComplete = null;
|
|
543
|
+
signalCallback = null;
|
|
544
|
+
pendingCandidates = [];
|
|
545
|
+
closed = false;
|
|
546
|
+
constructor(expectedSize, expectedSha256) {
|
|
547
|
+
this.expectedSize = expectedSize;
|
|
548
|
+
this.expectedSha256 = expectedSha256;
|
|
549
|
+
}
|
|
550
|
+
onSignal(cb) {
|
|
551
|
+
this.signalCallback = cb;
|
|
552
|
+
}
|
|
553
|
+
async handleSignal(signal) {
|
|
554
|
+
const ndc = await loadNdc();
|
|
555
|
+
if (!ndc || this.closed) return;
|
|
556
|
+
if (!this.peer) {
|
|
557
|
+
this.peer = new ndc.PeerConnection(`upload-receiver-${Date.now()}`, {
|
|
558
|
+
iceServers: ICE_SERVERS
|
|
559
|
+
});
|
|
560
|
+
this.peer.onLocalDescription((sdp, type) => {
|
|
561
|
+
this.signalCallback?.({ signal_type: type, payload: sdp });
|
|
562
|
+
});
|
|
563
|
+
this.peer.onLocalCandidate((candidate, mid) => {
|
|
564
|
+
this.signalCallback?.({
|
|
565
|
+
signal_type: "candidate",
|
|
566
|
+
payload: JSON.stringify({ candidate, mid })
|
|
567
|
+
});
|
|
568
|
+
});
|
|
569
|
+
this.peer.onDataChannel((dc) => {
|
|
570
|
+
dc.onMessage((msg) => {
|
|
571
|
+
if (typeof msg === "string") {
|
|
572
|
+
try {
|
|
573
|
+
const ctrl = JSON.parse(msg);
|
|
574
|
+
if (ctrl.type === "complete") {
|
|
575
|
+
this.finalizeReceive();
|
|
576
|
+
}
|
|
577
|
+
} catch {
|
|
578
|
+
}
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
const buf = Buffer.isBuffer(msg) ? msg : Buffer.from(msg);
|
|
582
|
+
this.chunks.push(buf);
|
|
583
|
+
this.receivedBytes += buf.length;
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
if (signal.signal_type === "offer" || signal.signal_type === "answer") {
|
|
588
|
+
this.peer.setRemoteDescription(signal.payload, signal.signal_type);
|
|
589
|
+
for (const c of this.pendingCandidates) {
|
|
590
|
+
this.peer.addRemoteCandidate(c.candidate, c.mid);
|
|
591
|
+
}
|
|
592
|
+
this.pendingCandidates = [];
|
|
593
|
+
} else if (signal.signal_type === "candidate") {
|
|
594
|
+
const { candidate, mid } = JSON.parse(signal.payload);
|
|
595
|
+
if (this.peer.remoteDescription()) {
|
|
596
|
+
this.peer.addRemoteCandidate(candidate, mid);
|
|
597
|
+
} else {
|
|
598
|
+
this.pendingCandidates.push({ candidate, mid });
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
waitForCompletion(timeoutMs = 3e4) {
|
|
603
|
+
return new Promise((resolve2, reject) => {
|
|
604
|
+
this.resolveComplete = resolve2;
|
|
605
|
+
this.rejectComplete = reject;
|
|
606
|
+
setTimeout(() => {
|
|
607
|
+
if (!this.closed) {
|
|
608
|
+
reject(new Error(`Upload receive timed out after ${timeoutMs}ms`));
|
|
609
|
+
this.close();
|
|
610
|
+
}
|
|
611
|
+
}, timeoutMs);
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
finalizeReceive() {
|
|
615
|
+
const zipBuffer = Buffer.concat(this.chunks);
|
|
616
|
+
const actualSha256 = createHash("sha256").update(zipBuffer).digest("hex");
|
|
617
|
+
if (zipBuffer.length !== this.expectedSize) {
|
|
618
|
+
this.rejectComplete?.(
|
|
619
|
+
new Error(`Size mismatch: expected ${this.expectedSize}, got ${zipBuffer.length}`)
|
|
620
|
+
);
|
|
621
|
+
this.close();
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
if (actualSha256 !== this.expectedSha256) {
|
|
625
|
+
this.rejectComplete?.(
|
|
626
|
+
new Error(`SHA-256 mismatch: expected ${this.expectedSha256}, got ${actualSha256}`)
|
|
627
|
+
);
|
|
628
|
+
this.close();
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
log.info(`[WebRTC] Upload received ${zipBuffer.length} bytes, SHA-256 verified`);
|
|
632
|
+
this.resolveComplete?.(zipBuffer);
|
|
633
|
+
this.close();
|
|
634
|
+
}
|
|
635
|
+
close() {
|
|
636
|
+
this.closed = true;
|
|
637
|
+
try {
|
|
638
|
+
this.peer?.close();
|
|
639
|
+
} catch {
|
|
640
|
+
}
|
|
641
|
+
this.peer = null;
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
var FileUploadSender = class {
|
|
645
|
+
peer = null;
|
|
646
|
+
dc = null;
|
|
647
|
+
transferId;
|
|
648
|
+
zipBuffer;
|
|
649
|
+
signalCallback = null;
|
|
650
|
+
pendingCandidates = [];
|
|
651
|
+
resolveComplete = null;
|
|
652
|
+
rejectComplete = null;
|
|
653
|
+
closed = false;
|
|
654
|
+
constructor(transferId, zipBuffer) {
|
|
655
|
+
this.transferId = transferId;
|
|
656
|
+
this.zipBuffer = zipBuffer;
|
|
657
|
+
}
|
|
658
|
+
onSignal(cb) {
|
|
659
|
+
this.signalCallback = cb;
|
|
660
|
+
}
|
|
661
|
+
async createOffer() {
|
|
662
|
+
const ndc = await loadNdc();
|
|
663
|
+
if (!ndc) return null;
|
|
664
|
+
this.peer = new ndc.PeerConnection("upload-sender", {
|
|
665
|
+
iceServers: ICE_SERVERS
|
|
666
|
+
});
|
|
667
|
+
return new Promise((resolve2) => {
|
|
668
|
+
this.peer.onLocalDescription((sdp, type) => {
|
|
669
|
+
if (type === "offer") {
|
|
670
|
+
resolve2(sdp);
|
|
671
|
+
}
|
|
672
|
+
this.signalCallback?.({ signal_type: type, payload: sdp });
|
|
673
|
+
});
|
|
674
|
+
this.peer.onLocalCandidate((candidate, mid) => {
|
|
675
|
+
this.signalCallback?.({
|
|
676
|
+
signal_type: "candidate",
|
|
677
|
+
payload: JSON.stringify({ candidate, mid })
|
|
678
|
+
});
|
|
679
|
+
});
|
|
680
|
+
this.dc = this.peer.createDataChannel("file-transfer");
|
|
681
|
+
this.dc.onOpen(() => {
|
|
682
|
+
void this.sendZip();
|
|
683
|
+
});
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
async handleSignal(signal) {
|
|
687
|
+
if (!this.peer || this.closed) return;
|
|
688
|
+
if (signal.signal_type === "answer" || signal.signal_type === "offer") {
|
|
689
|
+
this.peer.setRemoteDescription(signal.payload, signal.signal_type);
|
|
690
|
+
for (const c of this.pendingCandidates) {
|
|
691
|
+
this.peer.addRemoteCandidate(c.candidate, c.mid);
|
|
692
|
+
}
|
|
693
|
+
this.pendingCandidates = [];
|
|
694
|
+
} else if (signal.signal_type === "candidate") {
|
|
695
|
+
const { candidate, mid } = JSON.parse(signal.payload);
|
|
696
|
+
if (this.peer.remoteDescription()) {
|
|
697
|
+
this.peer.addRemoteCandidate(candidate, mid);
|
|
698
|
+
} else {
|
|
699
|
+
this.pendingCandidates.push({ candidate, mid });
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
waitForCompletion(timeoutMs = 3e4) {
|
|
704
|
+
return new Promise((resolve2, reject) => {
|
|
705
|
+
this.resolveComplete = resolve2;
|
|
706
|
+
this.rejectComplete = reject;
|
|
707
|
+
setTimeout(() => {
|
|
708
|
+
if (!this.closed) {
|
|
709
|
+
reject(new Error(`Upload send timed out after ${timeoutMs}ms`));
|
|
710
|
+
this.close();
|
|
711
|
+
}
|
|
712
|
+
}, timeoutMs);
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
async sendZip() {
|
|
716
|
+
if (!this.dc) return;
|
|
717
|
+
try {
|
|
718
|
+
this.dc.sendMessage(JSON.stringify({
|
|
719
|
+
type: "header",
|
|
720
|
+
transfer_id: this.transferId,
|
|
721
|
+
zip_size: this.zipBuffer.length
|
|
722
|
+
}));
|
|
723
|
+
let offset = 0;
|
|
724
|
+
while (offset < this.zipBuffer.length) {
|
|
725
|
+
const end = Math.min(offset + CHUNK_SIZE, this.zipBuffer.length);
|
|
726
|
+
const chunk = this.zipBuffer.subarray(offset, end);
|
|
727
|
+
this.dc.sendMessageBinary(chunk);
|
|
728
|
+
offset = end;
|
|
729
|
+
}
|
|
730
|
+
this.dc.sendMessage(JSON.stringify({ type: "complete" }));
|
|
731
|
+
log.info(`[WebRTC] Upload sent ${this.zipBuffer.length} bytes in ${Math.ceil(this.zipBuffer.length / CHUNK_SIZE)} chunks`);
|
|
732
|
+
this.resolveComplete?.();
|
|
733
|
+
this.close();
|
|
734
|
+
} catch (err) {
|
|
735
|
+
log.error(`[WebRTC] Upload send failed: ${err}`);
|
|
736
|
+
this.rejectComplete?.(err instanceof Error ? err : new Error(String(err)));
|
|
737
|
+
this.close();
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
close() {
|
|
741
|
+
this.closed = true;
|
|
742
|
+
try {
|
|
743
|
+
this.dc?.close();
|
|
744
|
+
} catch {
|
|
745
|
+
}
|
|
746
|
+
try {
|
|
747
|
+
this.peer?.close();
|
|
748
|
+
} catch {
|
|
749
|
+
}
|
|
750
|
+
this.peer = null;
|
|
751
|
+
this.dc = null;
|
|
752
|
+
}
|
|
753
|
+
};
|
|
534
754
|
function sha256Hex(data) {
|
|
535
755
|
return createHash("sha256").update(data).digest("hex");
|
|
536
756
|
}
|
|
@@ -954,8 +1174,10 @@ var BridgeManager = class {
|
|
|
954
1174
|
requestDispatches = /* @__PURE__ */ new Map();
|
|
955
1175
|
cleanupTimer = null;
|
|
956
1176
|
runtimeQueue;
|
|
957
|
-
/** Pending WebRTC file transfers: transfer_id → FileSender + cleanup timer */
|
|
1177
|
+
/** Pending WebRTC file transfers (download): transfer_id → FileSender + cleanup timer */
|
|
958
1178
|
pendingTransfers = /* @__PURE__ */ new Map();
|
|
1179
|
+
/** Pending WebRTC file uploads (caller→agent): transfer_id → FileUploadReceiver + cleanup timer */
|
|
1180
|
+
pendingUploads = /* @__PURE__ */ new Map();
|
|
959
1181
|
constructor(opts) {
|
|
960
1182
|
this.wsClient = opts.wsClient;
|
|
961
1183
|
this.adapter = opts.adapter;
|
|
@@ -984,6 +1206,7 @@ var BridgeManager = class {
|
|
|
984
1206
|
this.sessionLastSeenAt.clear();
|
|
985
1207
|
this.cleanupRequestDispatches("shutdown");
|
|
986
1208
|
this.cleanupPendingTransfers();
|
|
1209
|
+
this.cleanupPendingUploads();
|
|
987
1210
|
log.info("Bridge manager stopped");
|
|
988
1211
|
}
|
|
989
1212
|
/**
|
|
@@ -1083,7 +1306,10 @@ var BridgeManager = class {
|
|
|
1083
1306
|
}
|
|
1084
1307
|
async dispatchWithLocalQueue(opts) {
|
|
1085
1308
|
const { msg, handle, requestKey } = opts;
|
|
1086
|
-
const { session_id, request_id, content, attachments, client_id, with_files } = msg;
|
|
1309
|
+
const { session_id, request_id, content, attachments, client_id, with_files, file_upload_offer } = msg;
|
|
1310
|
+
if (file_upload_offer) {
|
|
1311
|
+
this.registerPendingUpload(file_upload_offer, session_id, request_id);
|
|
1312
|
+
}
|
|
1087
1313
|
const state = this.requestDispatches.get(requestKey);
|
|
1088
1314
|
if (!state) return;
|
|
1089
1315
|
try {
|
|
@@ -1389,19 +1615,92 @@ var BridgeManager = class {
|
|
|
1389
1615
|
log.info(`WebRTC transfer registered: transfer=${offer.transfer_id.slice(0, 8)}... (${(zipBuffer.length / 1024).toFixed(1)} KB in memory)`);
|
|
1390
1616
|
}
|
|
1391
1617
|
handleRtcSignalRelay(msg) {
|
|
1392
|
-
const
|
|
1393
|
-
if (
|
|
1394
|
-
|
|
1618
|
+
const downloadEntry = this.pendingTransfers.get(msg.transfer_id);
|
|
1619
|
+
if (downloadEntry) {
|
|
1620
|
+
downloadEntry.targetAgentId = msg.from_agent_id;
|
|
1621
|
+
void downloadEntry.sender.handleSignal({
|
|
1622
|
+
signal_type: msg.signal_type,
|
|
1623
|
+
payload: msg.payload
|
|
1624
|
+
});
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
const uploadEntry = this.pendingUploads.get(msg.transfer_id);
|
|
1628
|
+
if (uploadEntry) {
|
|
1629
|
+
void uploadEntry.receiver.handleSignal({
|
|
1630
|
+
signal_type: msg.signal_type,
|
|
1631
|
+
payload: msg.payload
|
|
1632
|
+
});
|
|
1395
1633
|
return;
|
|
1396
1634
|
}
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1635
|
+
log.debug(`No pending transfer for ${msg.transfer_id.slice(0, 8)}...`);
|
|
1636
|
+
}
|
|
1637
|
+
// ========================================================
|
|
1638
|
+
// Upload (Caller → Agent) WebRTC signaling
|
|
1639
|
+
// ========================================================
|
|
1640
|
+
registerPendingUpload(offer, _sessionId, _requestId) {
|
|
1641
|
+
const receiver = new FileUploadReceiver(offer.zip_size, offer.zip_sha256);
|
|
1642
|
+
receiver.onSignal((signal) => {
|
|
1643
|
+
const rtcSignal = {
|
|
1644
|
+
type: "rtc_signal",
|
|
1645
|
+
transfer_id: offer.transfer_id,
|
|
1646
|
+
target_agent_id: "http-caller",
|
|
1647
|
+
signal_type: signal.signal_type,
|
|
1648
|
+
payload: signal.payload
|
|
1649
|
+
};
|
|
1650
|
+
this.wsClient.send(rtcSignal);
|
|
1401
1651
|
});
|
|
1652
|
+
const timer = setTimeout(() => {
|
|
1653
|
+
receiver.close();
|
|
1654
|
+
this.pendingUploads.delete(offer.transfer_id);
|
|
1655
|
+
log.debug(`Upload transfer ${offer.transfer_id.slice(0, 8)}... expired`);
|
|
1656
|
+
}, 5 * 6e4);
|
|
1657
|
+
timer.unref?.();
|
|
1658
|
+
this.pendingUploads.set(offer.transfer_id, { receiver, timer });
|
|
1659
|
+
void receiver.waitForCompletion(5 * 6e4).then((zipBuffer) => {
|
|
1660
|
+
clearTimeout(timer);
|
|
1661
|
+
this.pendingUploads.delete(offer.transfer_id);
|
|
1662
|
+
const workspaceDir = this.resolveUploadWorkspace(_sessionId);
|
|
1663
|
+
try {
|
|
1664
|
+
const { mkdirSync: mkdirSync6, writeFileSync: writeFileSync4 } = __require("fs");
|
|
1665
|
+
const { join: join13 } = __require("path");
|
|
1666
|
+
const { execSync: execSync6 } = __require("child_process");
|
|
1667
|
+
mkdirSync6(workspaceDir, { recursive: true });
|
|
1668
|
+
const zipPath = join13(workspaceDir, ".upload.zip");
|
|
1669
|
+
writeFileSync4(zipPath, zipBuffer);
|
|
1670
|
+
try {
|
|
1671
|
+
execSync6(`unzip -o -q "${zipPath}" -d "${workspaceDir}"`);
|
|
1672
|
+
try {
|
|
1673
|
+
execSync6(`rm "${zipPath}"`);
|
|
1674
|
+
} catch {
|
|
1675
|
+
}
|
|
1676
|
+
log.info(`[WebRTC] Upload: ${offer.file_count} file(s) extracted to ${workspaceDir}`);
|
|
1677
|
+
} catch {
|
|
1678
|
+
log.warn(`[WebRTC] Upload: Failed to extract ZIP. Saved to: ${zipPath}`);
|
|
1679
|
+
}
|
|
1680
|
+
} catch (err) {
|
|
1681
|
+
log.error(`[WebRTC] Upload extraction failed: ${err}`);
|
|
1682
|
+
}
|
|
1683
|
+
}).catch((err) => {
|
|
1684
|
+
log.warn(`[WebRTC] Upload receive failed: ${err.message}`);
|
|
1685
|
+
this.pendingUploads.delete(offer.transfer_id);
|
|
1686
|
+
});
|
|
1687
|
+
log.info(`[WebRTC] Upload registered: transfer=${offer.transfer_id.slice(0, 8)}... (${offer.file_count} files, ${(offer.zip_size / 1024).toFixed(1)} KB)`);
|
|
1688
|
+
}
|
|
1689
|
+
resolveUploadWorkspace(sessionId) {
|
|
1690
|
+
const { join: join13 } = __require("path");
|
|
1691
|
+
const { homedir: homedir6 } = __require("os");
|
|
1692
|
+
const safeSessionId = sessionId.replace(/[^a-zA-Z0-9_:-]/g, "_").slice(0, 64);
|
|
1693
|
+
return join13(homedir6(), ".agent-mesh", "uploads", safeSessionId);
|
|
1694
|
+
}
|
|
1695
|
+
cleanupPendingUploads() {
|
|
1696
|
+
for (const [, entry] of this.pendingUploads) {
|
|
1697
|
+
clearTimeout(entry.timer);
|
|
1698
|
+
entry.receiver.close();
|
|
1699
|
+
}
|
|
1700
|
+
this.pendingUploads.clear();
|
|
1402
1701
|
}
|
|
1403
1702
|
cleanupPendingTransfers() {
|
|
1404
|
-
for (const [
|
|
1703
|
+
for (const [, entry] of this.pendingTransfers) {
|
|
1405
1704
|
clearTimeout(entry.timer);
|
|
1406
1705
|
entry.sender.close();
|
|
1407
1706
|
}
|
|
@@ -1515,11 +1814,11 @@ async function initSandbox(agentType) {
|
|
|
1515
1814
|
const filesystem = getSandboxPreset(agentType);
|
|
1516
1815
|
try {
|
|
1517
1816
|
await mgr.initialize({
|
|
1518
|
-
network: { allowedDomains: ["placeholder.example.com"], deniedDomains: [] },
|
|
1817
|
+
network: { allowedDomains: ["placeholder.example.com"], deniedDomains: [], allowLocalBinding: true },
|
|
1519
1818
|
filesystem
|
|
1520
1819
|
});
|
|
1521
1820
|
mgr.updateConfig({
|
|
1522
|
-
network: { deniedDomains: [] },
|
|
1821
|
+
network: { allowedDomains: [], deniedDomains: [], allowLocalBinding: true },
|
|
1523
1822
|
filesystem
|
|
1524
1823
|
});
|
|
1525
1824
|
sandboxManager = mgr;
|
|
@@ -1534,7 +1833,9 @@ async function initSandbox(agentType) {
|
|
|
1534
1833
|
async function wrapWithSandbox(command, filesystemOverride) {
|
|
1535
1834
|
if (!sandboxInitialized || !sandboxManager) return null;
|
|
1536
1835
|
if (filesystemOverride) {
|
|
1836
|
+
const currentConfig = sandboxManager.getConfig();
|
|
1537
1837
|
sandboxManager.updateConfig({
|
|
1838
|
+
network: currentConfig?.network ?? { allowedDomains: [], deniedDomains: [], allowLocalBinding: true },
|
|
1538
1839
|
filesystem: filesystemOverride
|
|
1539
1840
|
});
|
|
1540
1841
|
}
|
|
@@ -1548,6 +1849,7 @@ async function wrapWithSandbox(command, filesystemOverride) {
|
|
|
1548
1849
|
async function resetSandbox() {
|
|
1549
1850
|
if (sandboxManager) {
|
|
1550
1851
|
try {
|
|
1852
|
+
sandboxManager.cleanupAfterCommand?.();
|
|
1551
1853
|
await sandboxManager.reset();
|
|
1552
1854
|
} catch {
|
|
1553
1855
|
}
|
|
@@ -2474,7 +2776,7 @@ function registerConnectCommand(program2) {
|
|
|
2474
2776
|
log.error(`Failed to start. Check logs: ${getLogPath(slug)}`);
|
|
2475
2777
|
process.exit(1);
|
|
2476
2778
|
}
|
|
2477
|
-
const { ListTUI } = await import("./list-
|
|
2779
|
+
const { ListTUI } = await import("./list-RV7HOA77.js");
|
|
2478
2780
|
const tui = new ListTUI();
|
|
2479
2781
|
await tui.run();
|
|
2480
2782
|
return;
|
|
@@ -3331,7 +3633,6 @@ var SUPPORTED_VISIBILITIES = ["public", "private"];
|
|
|
3331
3633
|
function normalizeAgentType(input) {
|
|
3332
3634
|
if (!input) return null;
|
|
3333
3635
|
const normalized = input.trim().toLowerCase();
|
|
3334
|
-
if (normalized === "claude-code" || normalized === "claudecode") return "claude";
|
|
3335
3636
|
if (SUPPORTED_AGENT_TYPES.includes(normalized)) {
|
|
3336
3637
|
return normalized;
|
|
3337
3638
|
}
|
|
@@ -3340,7 +3641,7 @@ function normalizeAgentType(input) {
|
|
|
3340
3641
|
function parseAgentTypeOrExit(input) {
|
|
3341
3642
|
const agentType = normalizeAgentType(input);
|
|
3342
3643
|
if (agentType) return agentType;
|
|
3343
|
-
log.error(`Invalid agent type: ${input}. Supported: ${SUPPORTED_AGENT_TYPES.join(", ")}
|
|
3644
|
+
log.error(`Invalid agent type: ${input}. Supported: ${SUPPORTED_AGENT_TYPES.join(", ")}.`);
|
|
3344
3645
|
process.exit(1);
|
|
3345
3646
|
}
|
|
3346
3647
|
function normalizeVisibility(input) {
|
|
@@ -3553,6 +3854,11 @@ function registerAgentsCommand(program2) {
|
|
|
3553
3854
|
|
|
3554
3855
|
// src/commands/chat.ts
|
|
3555
3856
|
import { createInterface as createInterface4 } from "readline";
|
|
3857
|
+
import { existsSync as existsSync7, readFileSync as readFileSync2 } from "fs";
|
|
3858
|
+
import { basename as basename2, join as join9 } from "path";
|
|
3859
|
+
import { execSync as execSync4 } from "child_process";
|
|
3860
|
+
import { createHash as createHash2, randomUUID } from "crypto";
|
|
3861
|
+
import { tmpdir } from "os";
|
|
3556
3862
|
|
|
3557
3863
|
// src/utils/sse-parser.ts
|
|
3558
3864
|
function parseSseChunk(raw, carry) {
|
|
@@ -3575,9 +3881,99 @@ function parseSseChunk(raw, carry) {
|
|
|
3575
3881
|
|
|
3576
3882
|
// src/commands/chat.ts
|
|
3577
3883
|
var DEFAULT_BASE_URL3 = "https://agents.hot";
|
|
3884
|
+
function prepareUploadFile(filePath) {
|
|
3885
|
+
const fileName = basename2(filePath);
|
|
3886
|
+
const tempDir = join9(tmpdir(), `chat-upload-${Date.now()}`);
|
|
3887
|
+
const { mkdirSync: mkdirSync6 } = __require("fs");
|
|
3888
|
+
mkdirSync6(tempDir, { recursive: true });
|
|
3889
|
+
const tempFile = join9(tempDir, fileName);
|
|
3890
|
+
const { copyFileSync: copyFileSync2 } = __require("fs");
|
|
3891
|
+
copyFileSync2(filePath, tempFile);
|
|
3892
|
+
const zipPath = join9(tempDir, "upload.zip");
|
|
3893
|
+
execSync4(`cd "${tempDir}" && zip -q "${zipPath}" "${fileName}"`);
|
|
3894
|
+
const zipBuffer = readFileSync2(zipPath);
|
|
3895
|
+
try {
|
|
3896
|
+
execSync4(`rm -rf "${tempDir}"`);
|
|
3897
|
+
} catch {
|
|
3898
|
+
}
|
|
3899
|
+
const zipSha256 = createHash2("sha256").update(zipBuffer).digest("hex");
|
|
3900
|
+
const transferId = randomUUID();
|
|
3901
|
+
return {
|
|
3902
|
+
offer: {
|
|
3903
|
+
transfer_id: transferId,
|
|
3904
|
+
zip_size: zipBuffer.length,
|
|
3905
|
+
zip_sha256: zipSha256,
|
|
3906
|
+
file_count: 1
|
|
3907
|
+
},
|
|
3908
|
+
zipBuffer: Buffer.from(zipBuffer)
|
|
3909
|
+
};
|
|
3910
|
+
}
|
|
3578
3911
|
function sleep5(ms) {
|
|
3579
3912
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
3580
3913
|
}
|
|
3914
|
+
async function chatWebrtcUpload(agentId, offer, zipBuffer, token, baseUrl) {
|
|
3915
|
+
log.info(`[WebRTC] Uploading file (${(offer.zip_size / 1024).toFixed(1)} KB)...`);
|
|
3916
|
+
const sender = new FileUploadSender(offer.transfer_id, zipBuffer);
|
|
3917
|
+
const exchangeSignals = async (signal) => {
|
|
3918
|
+
try {
|
|
3919
|
+
const res = await fetch(`${baseUrl}/api/agents/${agentId}/rtc-signal`, {
|
|
3920
|
+
method: "POST",
|
|
3921
|
+
headers: {
|
|
3922
|
+
Authorization: `Bearer ${token}`,
|
|
3923
|
+
"Content-Type": "application/json"
|
|
3924
|
+
},
|
|
3925
|
+
body: JSON.stringify({
|
|
3926
|
+
transfer_id: offer.transfer_id,
|
|
3927
|
+
signal_type: signal.signal_type,
|
|
3928
|
+
payload: signal.payload
|
|
3929
|
+
})
|
|
3930
|
+
});
|
|
3931
|
+
if (res.ok) {
|
|
3932
|
+
const { signals } = await res.json();
|
|
3933
|
+
for (const s of signals) {
|
|
3934
|
+
await sender.handleSignal(s);
|
|
3935
|
+
}
|
|
3936
|
+
}
|
|
3937
|
+
} catch {
|
|
3938
|
+
}
|
|
3939
|
+
};
|
|
3940
|
+
sender.onSignal(exchangeSignals);
|
|
3941
|
+
await sender.createOffer();
|
|
3942
|
+
const poll = async () => {
|
|
3943
|
+
for (let i = 0; i < 20; i++) {
|
|
3944
|
+
await sleep5(500);
|
|
3945
|
+
try {
|
|
3946
|
+
const res = await fetch(`${baseUrl}/api/agents/${agentId}/rtc-signal`, {
|
|
3947
|
+
method: "POST",
|
|
3948
|
+
headers: {
|
|
3949
|
+
Authorization: `Bearer ${token}`,
|
|
3950
|
+
"Content-Type": "application/json"
|
|
3951
|
+
},
|
|
3952
|
+
body: JSON.stringify({
|
|
3953
|
+
transfer_id: offer.transfer_id,
|
|
3954
|
+
signal_type: "poll",
|
|
3955
|
+
payload: ""
|
|
3956
|
+
})
|
|
3957
|
+
});
|
|
3958
|
+
if (res.ok) {
|
|
3959
|
+
const { signals } = await res.json();
|
|
3960
|
+
for (const s of signals) {
|
|
3961
|
+
await sender.handleSignal(s);
|
|
3962
|
+
}
|
|
3963
|
+
}
|
|
3964
|
+
} catch {
|
|
3965
|
+
}
|
|
3966
|
+
}
|
|
3967
|
+
};
|
|
3968
|
+
try {
|
|
3969
|
+
await Promise.all([sender.waitForCompletion(3e4), poll()]);
|
|
3970
|
+
log.success(`[WebRTC] File uploaded successfully`);
|
|
3971
|
+
} catch (err) {
|
|
3972
|
+
log.warn(`[WebRTC] Upload failed: ${err.message}`);
|
|
3973
|
+
} finally {
|
|
3974
|
+
sender.close();
|
|
3975
|
+
}
|
|
3976
|
+
}
|
|
3581
3977
|
async function asyncChat(opts) {
|
|
3582
3978
|
const res = await fetch(`${opts.baseUrl}/api/agents/${opts.agentId}/chat`, {
|
|
3583
3979
|
method: "POST",
|
|
@@ -3585,7 +3981,11 @@ async function asyncChat(opts) {
|
|
|
3585
3981
|
Authorization: `Bearer ${opts.token}`,
|
|
3586
3982
|
"Content-Type": "application/json"
|
|
3587
3983
|
},
|
|
3588
|
-
body: JSON.stringify({
|
|
3984
|
+
body: JSON.stringify({
|
|
3985
|
+
message: opts.message,
|
|
3986
|
+
mode: "async",
|
|
3987
|
+
...opts.fileUploadOffer ? { file_upload_offer: opts.fileUploadOffer } : {}
|
|
3988
|
+
}),
|
|
3589
3989
|
signal: opts.signal
|
|
3590
3990
|
});
|
|
3591
3991
|
if (!res.ok) {
|
|
@@ -3653,7 +4053,10 @@ async function streamChat(opts) {
|
|
|
3653
4053
|
Authorization: `Bearer ${opts.token}`,
|
|
3654
4054
|
"Content-Type": "application/json"
|
|
3655
4055
|
},
|
|
3656
|
-
body: JSON.stringify({
|
|
4056
|
+
body: JSON.stringify({
|
|
4057
|
+
message: opts.message,
|
|
4058
|
+
...opts.fileUploadOffer ? { file_upload_offer: opts.fileUploadOffer } : {}
|
|
4059
|
+
}),
|
|
3657
4060
|
signal: opts.signal
|
|
3658
4061
|
});
|
|
3659
4062
|
if (!res.ok) {
|
|
@@ -3787,8 +4190,10 @@ function registerChatCommand(program2) {
|
|
|
3787
4190
|
process.exit(1);
|
|
3788
4191
|
}
|
|
3789
4192
|
log.banner(`Chat with ${agentName}`);
|
|
3790
|
-
console.log(`${GRAY}Type your message and press Enter.
|
|
4193
|
+
console.log(`${GRAY}Type your message and press Enter. /upload <path> to send a file. /quit to exit.${RESET}
|
|
3791
4194
|
`);
|
|
4195
|
+
let pendingUploadOffer;
|
|
4196
|
+
let pendingUploadZipBuffer;
|
|
3792
4197
|
const rl = createInterface4({
|
|
3793
4198
|
input: process.stdin,
|
|
3794
4199
|
output: process.stdout,
|
|
@@ -3811,7 +4216,34 @@ function registerChatCommand(program2) {
|
|
|
3811
4216
|
rl.close();
|
|
3812
4217
|
return;
|
|
3813
4218
|
}
|
|
4219
|
+
if (trimmed.startsWith("/upload ")) {
|
|
4220
|
+
const filePath = trimmed.slice(8).trim();
|
|
4221
|
+
if (!filePath) {
|
|
4222
|
+
log.error("Usage: /upload <path>");
|
|
4223
|
+
rl.prompt();
|
|
4224
|
+
return;
|
|
4225
|
+
}
|
|
4226
|
+
if (!existsSync7(filePath)) {
|
|
4227
|
+
log.error(`File not found: ${filePath}`);
|
|
4228
|
+
rl.prompt();
|
|
4229
|
+
return;
|
|
4230
|
+
}
|
|
4231
|
+
try {
|
|
4232
|
+
const prepared = prepareUploadFile(filePath);
|
|
4233
|
+
pendingUploadOffer = prepared.offer;
|
|
4234
|
+
pendingUploadZipBuffer = prepared.zipBuffer;
|
|
4235
|
+
log.info(`File staged: ${basename2(filePath)} (${(prepared.offer.zip_size / 1024).toFixed(1)} KB). Type a message to send with the file.`);
|
|
4236
|
+
} catch (err) {
|
|
4237
|
+
log.error(`Failed to prepare file: ${err.message}`);
|
|
4238
|
+
}
|
|
4239
|
+
rl.prompt();
|
|
4240
|
+
return;
|
|
4241
|
+
}
|
|
3814
4242
|
console.log("");
|
|
4243
|
+
const uploadOffer = pendingUploadOffer;
|
|
4244
|
+
const uploadZipBuffer = pendingUploadZipBuffer;
|
|
4245
|
+
pendingUploadOffer = void 0;
|
|
4246
|
+
pendingUploadZipBuffer = void 0;
|
|
3815
4247
|
try {
|
|
3816
4248
|
await streamChat({
|
|
3817
4249
|
agentId,
|
|
@@ -3819,8 +4251,12 @@ function registerChatCommand(program2) {
|
|
|
3819
4251
|
token,
|
|
3820
4252
|
baseUrl: opts.baseUrl,
|
|
3821
4253
|
showThinking: opts.thinking,
|
|
3822
|
-
mode
|
|
4254
|
+
mode,
|
|
4255
|
+
fileUploadOffer: uploadOffer
|
|
3823
4256
|
});
|
|
4257
|
+
if (uploadOffer && uploadZipBuffer) {
|
|
4258
|
+
await chatWebrtcUpload(agentId, uploadOffer, uploadZipBuffer, token, opts.baseUrl);
|
|
4259
|
+
}
|
|
3824
4260
|
} catch (err) {
|
|
3825
4261
|
if (abortController.signal.aborted) return;
|
|
3826
4262
|
log.error(err.message);
|
|
@@ -3833,11 +4269,11 @@ function registerChatCommand(program2) {
|
|
|
3833
4269
|
|
|
3834
4270
|
// src/commands/skills.ts
|
|
3835
4271
|
import { readFile as readFile3, writeFile as writeFile3, readdir as readdir2, mkdir as mkdir2, rm, symlink, unlink } from "fs/promises";
|
|
3836
|
-
import { join as
|
|
4272
|
+
import { join as join11, resolve, relative as relative4 } from "path";
|
|
3837
4273
|
|
|
3838
4274
|
// src/utils/skill-parser.ts
|
|
3839
4275
|
import { readFile as readFile2, writeFile as writeFile2, stat as stat3 } from "fs/promises";
|
|
3840
|
-
import { join as
|
|
4276
|
+
import { join as join10 } from "path";
|
|
3841
4277
|
function parseSkillMd(raw) {
|
|
3842
4278
|
const trimmed = raw.trimStart();
|
|
3843
4279
|
if (!trimmed.startsWith("---")) {
|
|
@@ -3919,7 +4355,7 @@ function parseSkillMd(raw) {
|
|
|
3919
4355
|
return { frontmatter, content };
|
|
3920
4356
|
}
|
|
3921
4357
|
async function loadSkillManifest(dir) {
|
|
3922
|
-
const skillMdPath =
|
|
4358
|
+
const skillMdPath = join10(dir, "SKILL.md");
|
|
3923
4359
|
try {
|
|
3924
4360
|
const raw = await readFile2(skillMdPath, "utf-8");
|
|
3925
4361
|
const { frontmatter } = parseSkillMd(raw);
|
|
@@ -4024,13 +4460,13 @@ function skillApiPath(authorLogin, slug) {
|
|
|
4024
4460
|
}
|
|
4025
4461
|
async function resolveSkillsRootAsync(pathArg) {
|
|
4026
4462
|
const projectRoot = pathArg ? resolve(pathArg) : process.cwd();
|
|
4027
|
-
const skillsDir =
|
|
4028
|
-
const claudeSkillsDir =
|
|
4463
|
+
const skillsDir = join11(projectRoot, ".agents", "skills");
|
|
4464
|
+
const claudeSkillsDir = join11(projectRoot, ".claude", "skills");
|
|
4029
4465
|
return { projectRoot, skillsDir, claudeSkillsDir };
|
|
4030
4466
|
}
|
|
4031
4467
|
async function ensureClaudeSymlink(claudeSkillsDir, slug) {
|
|
4032
4468
|
await mkdir2(claudeSkillsDir, { recursive: true });
|
|
4033
|
-
const linkPath =
|
|
4469
|
+
const linkPath = join11(claudeSkillsDir, slug);
|
|
4034
4470
|
try {
|
|
4035
4471
|
await unlink(linkPath);
|
|
4036
4472
|
} catch {
|
|
@@ -4050,7 +4486,7 @@ async function collectPackFiles(dir, manifest) {
|
|
|
4050
4486
|
}
|
|
4051
4487
|
const mainFile = manifest.main || "SKILL.md";
|
|
4052
4488
|
if (!results.includes(mainFile)) {
|
|
4053
|
-
const mainPath =
|
|
4489
|
+
const mainPath = join11(dir, mainFile);
|
|
4054
4490
|
if (await pathExists(mainPath)) {
|
|
4055
4491
|
results.unshift(mainFile);
|
|
4056
4492
|
}
|
|
@@ -4067,7 +4503,7 @@ async function walkDir(dir) {
|
|
|
4067
4503
|
}
|
|
4068
4504
|
for (const entry of entries) {
|
|
4069
4505
|
if (entry.isSymbolicLink()) continue;
|
|
4070
|
-
const fullPath =
|
|
4506
|
+
const fullPath = join11(dir, entry.name);
|
|
4071
4507
|
if (entry.isDirectory()) {
|
|
4072
4508
|
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
4073
4509
|
const sub = await walkDir(fullPath);
|
|
@@ -4085,7 +4521,7 @@ async function packSkill(dir, manifest) {
|
|
|
4085
4521
|
}
|
|
4086
4522
|
const entries = [];
|
|
4087
4523
|
for (const relPath of fileList) {
|
|
4088
|
-
const absPath =
|
|
4524
|
+
const absPath = join11(dir, relPath);
|
|
4089
4525
|
try {
|
|
4090
4526
|
const data = await readFile3(absPath);
|
|
4091
4527
|
entries.push({ path: relPath.replace(/\\/g, "/"), data });
|
|
@@ -4119,7 +4555,7 @@ function bumpVersion(current, bump) {
|
|
|
4119
4555
|
}
|
|
4120
4556
|
async function downloadAndInstallSkill(client, authorLogin, slug, skillsDir) {
|
|
4121
4557
|
const meta = await client.get(skillApiPath(authorLogin, slug));
|
|
4122
|
-
const targetDir =
|
|
4558
|
+
const targetDir = join11(skillsDir, slug);
|
|
4123
4559
|
await mkdir2(targetDir, { recursive: true });
|
|
4124
4560
|
if (meta.has_files) {
|
|
4125
4561
|
const res = await client.getRaw(`${skillApiPath(authorLogin, slug)}/download`);
|
|
@@ -4127,8 +4563,8 @@ async function downloadAndInstallSkill(client, authorLogin, slug, skillsDir) {
|
|
|
4127
4563
|
const buf = Buffer.from(arrayBuf);
|
|
4128
4564
|
const entries = extractZipBuffer(buf);
|
|
4129
4565
|
for (const entry of entries) {
|
|
4130
|
-
const filePath =
|
|
4131
|
-
const dir =
|
|
4566
|
+
const filePath = join11(targetDir, entry.path);
|
|
4567
|
+
const dir = join11(filePath, "..");
|
|
4132
4568
|
await mkdir2(dir, { recursive: true });
|
|
4133
4569
|
await writeFile3(filePath, entry.data);
|
|
4134
4570
|
}
|
|
@@ -4141,7 +4577,7 @@ async function downloadAndInstallSkill(client, authorLogin, slug, skillsDir) {
|
|
|
4141
4577
|
} else {
|
|
4142
4578
|
const res = await client.getRaw(`${skillApiPath(authorLogin, slug)}/raw`);
|
|
4143
4579
|
const content = await res.text();
|
|
4144
|
-
await writeFile3(
|
|
4580
|
+
await writeFile3(join11(targetDir, "SKILL.md"), content);
|
|
4145
4581
|
return {
|
|
4146
4582
|
slug,
|
|
4147
4583
|
name: meta.name,
|
|
@@ -4170,7 +4606,7 @@ function registerSkillsCommand(program2) {
|
|
|
4170
4606
|
try {
|
|
4171
4607
|
const dir = resolveSkillDir(pathArg);
|
|
4172
4608
|
await mkdir2(dir, { recursive: true });
|
|
4173
|
-
const skillMdPath =
|
|
4609
|
+
const skillMdPath = join11(dir, "SKILL.md");
|
|
4174
4610
|
if (await pathExists(skillMdPath)) {
|
|
4175
4611
|
const raw = await readFile3(skillMdPath, "utf-8");
|
|
4176
4612
|
const { frontmatter } = parseSkillMd(raw);
|
|
@@ -4199,7 +4635,7 @@ function registerSkillsCommand(program2) {
|
|
|
4199
4635
|
const dir = resolveSkillDir(pathArg);
|
|
4200
4636
|
const manifest = await loadSkillManifest(dir);
|
|
4201
4637
|
const result = await packSkill(dir, manifest);
|
|
4202
|
-
const outPath =
|
|
4638
|
+
const outPath = join11(dir, result.filename);
|
|
4203
4639
|
await writeFile3(outPath, result.buffer);
|
|
4204
4640
|
slog.info(`Packed ${result.files.length} files \u2192 ${result.filename} (${result.size} bytes)`);
|
|
4205
4641
|
outputJson({
|
|
@@ -4245,7 +4681,7 @@ function registerSkillsCommand(program2) {
|
|
|
4245
4681
|
if (opts.name) manifest.name = opts.name;
|
|
4246
4682
|
if (opts.version) manifest.version = opts.version;
|
|
4247
4683
|
if (opts.private !== void 0) manifest.private = opts.private;
|
|
4248
|
-
content = await readFile3(
|
|
4684
|
+
content = await readFile3(join11(dir, manifest.main || "SKILL.md"), "utf-8");
|
|
4249
4685
|
packResult = await packSkill(dir, manifest);
|
|
4250
4686
|
slog.info(`Packed ${packResult.files.length} files (${packResult.size} bytes)`);
|
|
4251
4687
|
}
|
|
@@ -4382,7 +4818,7 @@ function registerSkillsCommand(program2) {
|
|
|
4382
4818
|
skills.command("version <bump> [path]").description("Bump skill version (patch | minor | major | x.y.z)").action(async (bump, pathArg) => {
|
|
4383
4819
|
try {
|
|
4384
4820
|
const dir = resolveSkillDir(pathArg);
|
|
4385
|
-
const skillMdPath =
|
|
4821
|
+
const skillMdPath = join11(dir, "SKILL.md");
|
|
4386
4822
|
if (!await pathExists(skillMdPath)) {
|
|
4387
4823
|
outputError("not_found", "No SKILL.md found. Run `agent-mesh skills init` first.");
|
|
4388
4824
|
}
|
|
@@ -4402,7 +4838,7 @@ function registerSkillsCommand(program2) {
|
|
|
4402
4838
|
try {
|
|
4403
4839
|
const { authorLogin, slug } = parseSkillRef(ref);
|
|
4404
4840
|
const { skillsDir, claudeSkillsDir } = await resolveSkillsRootAsync(pathArg);
|
|
4405
|
-
const targetDir =
|
|
4841
|
+
const targetDir = join11(skillsDir, slug);
|
|
4406
4842
|
if (await pathExists(targetDir)) {
|
|
4407
4843
|
if (!opts.force) {
|
|
4408
4844
|
outputError("already_installed", `Skill "${slug}" is already installed at ${targetDir}. Use --force to overwrite.`);
|
|
@@ -4441,11 +4877,11 @@ function registerSkillsCommand(program2) {
|
|
|
4441
4877
|
const failed = [];
|
|
4442
4878
|
if (ref) {
|
|
4443
4879
|
const { authorLogin, slug } = parseSkillRef(ref);
|
|
4444
|
-
const targetDir =
|
|
4880
|
+
const targetDir = join11(skillsDir, slug);
|
|
4445
4881
|
if (!await pathExists(targetDir)) {
|
|
4446
4882
|
outputError("not_installed", `Skill "${slug}" is not installed. Use "skills install ${ref}" first.`);
|
|
4447
4883
|
}
|
|
4448
|
-
const skillMdPath =
|
|
4884
|
+
const skillMdPath = join11(targetDir, "SKILL.md");
|
|
4449
4885
|
let localVersion = "0.0.0";
|
|
4450
4886
|
if (await pathExists(skillMdPath)) {
|
|
4451
4887
|
const raw = await readFile3(skillMdPath, "utf-8");
|
|
@@ -4472,7 +4908,7 @@ function registerSkillsCommand(program2) {
|
|
|
4472
4908
|
for (const entry of entries) {
|
|
4473
4909
|
if (!entry.isDirectory()) continue;
|
|
4474
4910
|
const slug = entry.name;
|
|
4475
|
-
const skillMdPath =
|
|
4911
|
+
const skillMdPath = join11(skillsDir, slug, "SKILL.md");
|
|
4476
4912
|
if (!await pathExists(skillMdPath)) {
|
|
4477
4913
|
skipped.push({ slug, reason: "no_skill_md" });
|
|
4478
4914
|
continue;
|
|
@@ -4492,7 +4928,7 @@ function registerSkillsCommand(program2) {
|
|
|
4492
4928
|
skipped.push({ slug, reason: "up_to_date" });
|
|
4493
4929
|
} else {
|
|
4494
4930
|
slog.info(`Updating ${slug}: v${localVersion} \u2192 v${remoteVersion}...`);
|
|
4495
|
-
await rm(
|
|
4931
|
+
await rm(join11(skillsDir, slug), { recursive: true, force: true });
|
|
4496
4932
|
await downloadAndInstallSkill(client, authorLogin, slug, skillsDir);
|
|
4497
4933
|
updated.push({ slug, name: remote.name, old_version: localVersion, new_version: remoteVersion });
|
|
4498
4934
|
}
|
|
@@ -4513,13 +4949,13 @@ function registerSkillsCommand(program2) {
|
|
|
4513
4949
|
skills.command("remove <slug> [path]").description("Remove a locally installed skill").action(async (slug, pathArg) => {
|
|
4514
4950
|
try {
|
|
4515
4951
|
const { skillsDir, claudeSkillsDir } = await resolveSkillsRootAsync(pathArg);
|
|
4516
|
-
const targetDir =
|
|
4952
|
+
const targetDir = join11(skillsDir, slug);
|
|
4517
4953
|
if (!await pathExists(targetDir)) {
|
|
4518
4954
|
outputError("not_installed", `Skill "${slug}" is not installed at ${targetDir}`);
|
|
4519
4955
|
}
|
|
4520
4956
|
await rm(targetDir, { recursive: true, force: true });
|
|
4521
4957
|
try {
|
|
4522
|
-
await unlink(
|
|
4958
|
+
await unlink(join11(claudeSkillsDir, slug));
|
|
4523
4959
|
} catch {
|
|
4524
4960
|
}
|
|
4525
4961
|
slog.success(`Removed skill: ${slug}`);
|
|
@@ -4544,7 +4980,7 @@ function registerSkillsCommand(program2) {
|
|
|
4544
4980
|
for (const entry of entries) {
|
|
4545
4981
|
if (!entry.isDirectory()) continue;
|
|
4546
4982
|
const slug = entry.name;
|
|
4547
|
-
const skillMdPath =
|
|
4983
|
+
const skillMdPath = join11(skillsDir, slug, "SKILL.md");
|
|
4548
4984
|
if (!await pathExists(skillMdPath)) continue;
|
|
4549
4985
|
const raw = await readFile3(skillMdPath, "utf-8");
|
|
4550
4986
|
const { frontmatter } = parseSkillMd(raw);
|
|
@@ -4656,9 +5092,9 @@ function registerDiscoverCommand(program2) {
|
|
|
4656
5092
|
}
|
|
4657
5093
|
|
|
4658
5094
|
// src/commands/call.ts
|
|
4659
|
-
import { readFileSync as
|
|
4660
|
-
import { execSync as
|
|
4661
|
-
import { join as
|
|
5095
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync5 } from "fs";
|
|
5096
|
+
import { execSync as execSync5 } from "child_process";
|
|
5097
|
+
import { join as join12 } from "path";
|
|
4662
5098
|
var DEFAULT_BASE_URL4 = "https://agents.hot";
|
|
4663
5099
|
async function submitRating(baseUrl, token, agentId, callId, rating) {
|
|
4664
5100
|
const res = await fetch(`${baseUrl}/api/agents/${agentId}/rate`, {
|
|
@@ -4752,12 +5188,12 @@ async function webrtcDownload(agentId, offer, token, outputDir, json) {
|
|
|
4752
5188
|
poll()
|
|
4753
5189
|
]);
|
|
4754
5190
|
mkdirSync5(outputDir, { recursive: true });
|
|
4755
|
-
const zipPath =
|
|
5191
|
+
const zipPath = join12(outputDir, ".transfer.zip");
|
|
4756
5192
|
writeFileSync3(zipPath, zipBuffer);
|
|
4757
5193
|
try {
|
|
4758
|
-
|
|
5194
|
+
execSync5(`unzip -o -q "${zipPath}" -d "${outputDir}"`);
|
|
4759
5195
|
try {
|
|
4760
|
-
|
|
5196
|
+
execSync5(`rm "${zipPath}"`);
|
|
4761
5197
|
} catch {
|
|
4762
5198
|
}
|
|
4763
5199
|
} catch {
|
|
@@ -4786,6 +5222,120 @@ async function webrtcDownload(agentId, offer, token, outputDir, json) {
|
|
|
4786
5222
|
receiver.close();
|
|
4787
5223
|
}
|
|
4788
5224
|
}
|
|
5225
|
+
function prepareFileForUpload(filePath) {
|
|
5226
|
+
const { readFileSync: _readFileSync } = __require("fs");
|
|
5227
|
+
const { basename: basename3 } = __require("path");
|
|
5228
|
+
const { execSync: _execSync } = __require("child_process");
|
|
5229
|
+
const { createHash: createHash3 } = __require("crypto");
|
|
5230
|
+
const { tmpdir: tmpdir2 } = __require("os");
|
|
5231
|
+
const fileName = basename3(filePath);
|
|
5232
|
+
const tempDir = join12(tmpdir2(), `upload-${Date.now()}`);
|
|
5233
|
+
const { mkdirSync: _mkdirSync } = __require("fs");
|
|
5234
|
+
_mkdirSync(tempDir, { recursive: true });
|
|
5235
|
+
const tempFile = join12(tempDir, fileName);
|
|
5236
|
+
const { copyFileSync: _copyFileSync } = __require("fs");
|
|
5237
|
+
_copyFileSync(filePath, tempFile);
|
|
5238
|
+
const zipPath = join12(tempDir, "upload.zip");
|
|
5239
|
+
_execSync(`cd "${tempDir}" && zip -q "${zipPath}" "${fileName}"`);
|
|
5240
|
+
const zipBuffer = _readFileSync(zipPath);
|
|
5241
|
+
try {
|
|
5242
|
+
_execSync(`rm -rf "${tempDir}"`);
|
|
5243
|
+
} catch {
|
|
5244
|
+
}
|
|
5245
|
+
const zipSha256 = createHash3("sha256").update(zipBuffer).digest("hex");
|
|
5246
|
+
const transferId = __require("crypto").randomUUID();
|
|
5247
|
+
return {
|
|
5248
|
+
offer: {
|
|
5249
|
+
transfer_id: transferId,
|
|
5250
|
+
zip_size: zipBuffer.length,
|
|
5251
|
+
zip_sha256: zipSha256,
|
|
5252
|
+
file_count: 1
|
|
5253
|
+
},
|
|
5254
|
+
zipBuffer: Buffer.from(zipBuffer)
|
|
5255
|
+
};
|
|
5256
|
+
}
|
|
5257
|
+
async function webrtcUpload(agentId, offer, zipBuffer, token, json) {
|
|
5258
|
+
if (!json) {
|
|
5259
|
+
log.info(`[WebRTC] Uploading file (${(offer.zip_size / 1024).toFixed(1)} KB)...`);
|
|
5260
|
+
}
|
|
5261
|
+
const sender = new FileUploadSender(offer.transfer_id, zipBuffer);
|
|
5262
|
+
const exchangeSignals = async (signal) => {
|
|
5263
|
+
try {
|
|
5264
|
+
const res = await fetch(`${DEFAULT_BASE_URL4}/api/agents/${agentId}/rtc-signal`, {
|
|
5265
|
+
method: "POST",
|
|
5266
|
+
headers: {
|
|
5267
|
+
Authorization: `Bearer ${token}`,
|
|
5268
|
+
"Content-Type": "application/json"
|
|
5269
|
+
},
|
|
5270
|
+
body: JSON.stringify({
|
|
5271
|
+
transfer_id: offer.transfer_id,
|
|
5272
|
+
signal_type: signal.signal_type,
|
|
5273
|
+
payload: signal.payload
|
|
5274
|
+
})
|
|
5275
|
+
});
|
|
5276
|
+
if (res.ok) {
|
|
5277
|
+
const { signals } = await res.json();
|
|
5278
|
+
for (const s of signals) {
|
|
5279
|
+
await sender.handleSignal(s);
|
|
5280
|
+
}
|
|
5281
|
+
}
|
|
5282
|
+
} catch {
|
|
5283
|
+
}
|
|
5284
|
+
};
|
|
5285
|
+
sender.onSignal(exchangeSignals);
|
|
5286
|
+
await sender.createOffer();
|
|
5287
|
+
const poll = async () => {
|
|
5288
|
+
for (let i = 0; i < 20; i++) {
|
|
5289
|
+
await sleep6(500);
|
|
5290
|
+
try {
|
|
5291
|
+
const res = await fetch(`${DEFAULT_BASE_URL4}/api/agents/${agentId}/rtc-signal`, {
|
|
5292
|
+
method: "POST",
|
|
5293
|
+
headers: {
|
|
5294
|
+
Authorization: `Bearer ${token}`,
|
|
5295
|
+
"Content-Type": "application/json"
|
|
5296
|
+
},
|
|
5297
|
+
body: JSON.stringify({
|
|
5298
|
+
transfer_id: offer.transfer_id,
|
|
5299
|
+
signal_type: "poll",
|
|
5300
|
+
payload: ""
|
|
5301
|
+
})
|
|
5302
|
+
});
|
|
5303
|
+
if (res.ok) {
|
|
5304
|
+
const { signals } = await res.json();
|
|
5305
|
+
for (const s of signals) {
|
|
5306
|
+
await sender.handleSignal(s);
|
|
5307
|
+
}
|
|
5308
|
+
}
|
|
5309
|
+
} catch {
|
|
5310
|
+
}
|
|
5311
|
+
}
|
|
5312
|
+
};
|
|
5313
|
+
try {
|
|
5314
|
+
await Promise.all([
|
|
5315
|
+
sender.waitForCompletion(3e4),
|
|
5316
|
+
poll()
|
|
5317
|
+
]);
|
|
5318
|
+
if (json) {
|
|
5319
|
+
console.log(JSON.stringify({
|
|
5320
|
+
type: "files_uploaded",
|
|
5321
|
+
file_count: offer.file_count,
|
|
5322
|
+
zip_size: offer.zip_size,
|
|
5323
|
+
sha256: offer.zip_sha256
|
|
5324
|
+
}));
|
|
5325
|
+
} else {
|
|
5326
|
+
log.success(`[WebRTC] File uploaded successfully`);
|
|
5327
|
+
}
|
|
5328
|
+
} catch (err) {
|
|
5329
|
+
const msg = err.message;
|
|
5330
|
+
if (json) {
|
|
5331
|
+
console.log(JSON.stringify({ type: "file_upload_failed", error: msg }));
|
|
5332
|
+
} else {
|
|
5333
|
+
log.warn(`[WebRTC] File upload failed: ${msg}`);
|
|
5334
|
+
}
|
|
5335
|
+
} finally {
|
|
5336
|
+
sender.close();
|
|
5337
|
+
}
|
|
5338
|
+
}
|
|
4789
5339
|
async function asyncCall(opts) {
|
|
4790
5340
|
const selfAgentId = process.env.AGENT_BRIDGE_AGENT_ID;
|
|
4791
5341
|
const res = await fetch(`${DEFAULT_BASE_URL4}/api/agents/${opts.id}/call`, {
|
|
@@ -4798,7 +5348,8 @@ async function asyncCall(opts) {
|
|
|
4798
5348
|
body: JSON.stringify({
|
|
4799
5349
|
task_description: opts.taskDescription,
|
|
4800
5350
|
mode: "async",
|
|
4801
|
-
...opts.withFiles ? { with_files: true } : {}
|
|
5351
|
+
...opts.withFiles ? { with_files: true } : {},
|
|
5352
|
+
...opts.uploadOffer ? { file_upload_offer: opts.uploadOffer } : {}
|
|
4802
5353
|
}),
|
|
4803
5354
|
signal: opts.signal
|
|
4804
5355
|
});
|
|
@@ -4878,7 +5429,7 @@ async function asyncCall(opts) {
|
|
|
4878
5429
|
if (!opts.json) log.info(`Saved to ${opts.outputFile}`);
|
|
4879
5430
|
}
|
|
4880
5431
|
if (offer && opts.withFiles) {
|
|
4881
|
-
const outputDir = opts.outputFile ?
|
|
5432
|
+
const outputDir = opts.outputFile ? join12(opts.outputFile, "..", "files") : join12(process.cwd(), "agent-output");
|
|
4882
5433
|
await webrtcDownload(opts.id, offer, opts.token, outputDir, opts.json);
|
|
4883
5434
|
}
|
|
4884
5435
|
if (!opts.json) {
|
|
@@ -4917,7 +5468,8 @@ async function streamCall(opts) {
|
|
|
4917
5468
|
},
|
|
4918
5469
|
body: JSON.stringify({
|
|
4919
5470
|
task_description: opts.taskDescription,
|
|
4920
|
-
...opts.withFiles ? { with_files: true } : {}
|
|
5471
|
+
...opts.withFiles ? { with_files: true } : {},
|
|
5472
|
+
...opts.uploadOffer ? { file_upload_offer: opts.uploadOffer } : {}
|
|
4921
5473
|
}),
|
|
4922
5474
|
signal: opts.signal
|
|
4923
5475
|
});
|
|
@@ -5052,7 +5604,7 @@ Error: ${event.message}
|
|
|
5052
5604
|
}
|
|
5053
5605
|
if (fileOffer && opts.withFiles) {
|
|
5054
5606
|
console.log("");
|
|
5055
|
-
const outputDir = opts.outputFile ?
|
|
5607
|
+
const outputDir = opts.outputFile ? join12(opts.outputFile, "..", "files") : join12(process.cwd(), "agent-output");
|
|
5056
5608
|
await webrtcDownload(opts.id, fileOffer, opts.token, outputDir, opts.json);
|
|
5057
5609
|
}
|
|
5058
5610
|
if (!opts.json) {
|
|
@@ -5068,7 +5620,7 @@ Error: ${event.message}
|
|
|
5068
5620
|
return { callId, ...sessionKey ? { sessionKey } : {} };
|
|
5069
5621
|
}
|
|
5070
5622
|
function registerCallCommand(program2) {
|
|
5071
|
-
program2.command("call <agent>").description("Call an agent on the A2A network (default: async polling)").requiredOption("--task <description>", "Task description").option("--input-file <path>", "Read file and append to task description").option("--output-file <path>", "Save response text to file").option("--stream", "Use SSE streaming instead of async polling").option("--with-files", "Request file transfer via WebRTC after task completion").option("--json", "Output JSONL events").option("--timeout <seconds>", "Timeout in seconds", "300").option("--rate <rating>", "Rate the agent after call (1-5)", parseInt).action(async (agentInput, opts) => {
|
|
5623
|
+
program2.command("call <agent>").description("Call an agent on the A2A network (default: async polling)").requiredOption("--task <description>", "Task description").option("--input-file <path>", "Read file and append to task description").option("--upload-file <path>", "Upload file to agent via WebRTC P2P").option("--output-file <path>", "Save response text to file").option("--stream", "Use SSE streaming instead of async polling").option("--with-files", "Request file transfer via WebRTC after task completion").option("--json", "Output JSONL events").option("--timeout <seconds>", "Timeout in seconds", "300").option("--rate <rating>", "Rate the agent after call (1-5)", parseInt).action(async (agentInput, opts) => {
|
|
5072
5624
|
try {
|
|
5073
5625
|
const token = loadToken();
|
|
5074
5626
|
if (!token) {
|
|
@@ -5079,13 +5631,25 @@ function registerCallCommand(program2) {
|
|
|
5079
5631
|
const { id, name } = await resolveAgentId(agentInput, client);
|
|
5080
5632
|
let taskDescription = opts.task;
|
|
5081
5633
|
if (opts.inputFile) {
|
|
5082
|
-
const content =
|
|
5634
|
+
const content = readFileSync3(opts.inputFile, "utf-8");
|
|
5083
5635
|
taskDescription = `${taskDescription}
|
|
5084
5636
|
|
|
5085
5637
|
---
|
|
5086
5638
|
|
|
5087
5639
|
${content}`;
|
|
5088
5640
|
}
|
|
5641
|
+
let uploadOffer;
|
|
5642
|
+
let uploadZipBuffer;
|
|
5643
|
+
if (opts.uploadFile) {
|
|
5644
|
+
const { existsSync: existsSync8 } = __require("fs");
|
|
5645
|
+
if (!existsSync8(opts.uploadFile)) {
|
|
5646
|
+
log.error(`File not found: ${opts.uploadFile}`);
|
|
5647
|
+
process.exit(1);
|
|
5648
|
+
}
|
|
5649
|
+
const prepared = prepareFileForUpload(opts.uploadFile);
|
|
5650
|
+
uploadOffer = prepared.offer;
|
|
5651
|
+
uploadZipBuffer = prepared.zipBuffer;
|
|
5652
|
+
}
|
|
5089
5653
|
const timeoutMs = parseInt(opts.timeout || "300", 10) * 1e3;
|
|
5090
5654
|
const abortController = new AbortController();
|
|
5091
5655
|
const timer = setTimeout(() => abortController.abort(), timeoutMs);
|
|
@@ -5098,13 +5662,20 @@ ${content}`;
|
|
|
5098
5662
|
json: opts.json,
|
|
5099
5663
|
outputFile: opts.outputFile,
|
|
5100
5664
|
signal: abortController.signal,
|
|
5101
|
-
withFiles: opts.withFiles
|
|
5665
|
+
withFiles: opts.withFiles,
|
|
5666
|
+
uploadOffer
|
|
5102
5667
|
};
|
|
5103
5668
|
let result;
|
|
5104
5669
|
if (opts.stream) {
|
|
5670
|
+
if (uploadOffer && uploadZipBuffer) {
|
|
5671
|
+
void webrtcUpload(id, uploadOffer, uploadZipBuffer, token, opts.json);
|
|
5672
|
+
}
|
|
5105
5673
|
result = await streamCall(callOpts);
|
|
5106
5674
|
} else {
|
|
5107
5675
|
result = await asyncCall(callOpts);
|
|
5676
|
+
if (uploadOffer && uploadZipBuffer) {
|
|
5677
|
+
await webrtcUpload(id, uploadOffer, uploadZipBuffer, token, opts.json);
|
|
5678
|
+
}
|
|
5108
5679
|
}
|
|
5109
5680
|
clearTimeout(timer);
|
|
5110
5681
|
if (opts.rate && result.callId) {
|
|
@@ -5356,9 +5927,9 @@ function registerSubscribeCommand(program2) {
|
|
|
5356
5927
|
|
|
5357
5928
|
// src/commands/register.ts
|
|
5358
5929
|
var DEFAULT_BASE_URL5 = "https://agents.hot";
|
|
5359
|
-
var VALID_AGENT_TYPES = ["claude"
|
|
5930
|
+
var VALID_AGENT_TYPES = ["claude"];
|
|
5360
5931
|
function registerRegisterCommand(program2) {
|
|
5361
|
-
program2.command("register").description("Register a new agent on the platform and get an API key").requiredOption("--name <name>", "Agent name (alphanumeric + hyphens, 3-64 chars)").option("--type <type>", "Agent type", "claude
|
|
5932
|
+
program2.command("register").description("Register a new agent on the platform and get an API key").requiredOption("--name <name>", "Agent name (alphanumeric + hyphens, 3-64 chars)").option("--type <type>", "Agent type", "claude").option("--description <text>", "Agent description").option("--capabilities <list>", "Comma-separated capabilities").option("--base-url <url>", "Platform base URL", DEFAULT_BASE_URL5).action(async (opts) => {
|
|
5362
5933
|
if (!VALID_AGENT_TYPES.includes(opts.type)) {
|
|
5363
5934
|
log.error(`Invalid agent type: ${opts.type}. Must be one of: ${VALID_AGENT_TYPES.join(", ")}`);
|
|
5364
5935
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@annals/agent-mesh",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "CLI bridge connecting local AI agents to the Agents.Hot platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"commander": "^13.0.0",
|
|
12
12
|
"node-datachannel": "^0.32.0",
|
|
13
13
|
"ws": "^8.18.0",
|
|
14
|
-
"@annals/bridge-protocol": "^0.2.
|
|
14
|
+
"@annals/bridge-protocol": "^0.2.2"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@types/ws": "^8.5.0",
|