@annals/agent-mesh 0.17.9 → 0.18.1
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: mkdirSync7, writeFileSync: writeFileSync4 } = __require("fs");
|
|
1665
|
+
const { join: join13 } = __require("path");
|
|
1666
|
+
const { execSync: execSync6 } = __require("child_process");
|
|
1667
|
+
mkdirSync7(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
|
}
|
|
@@ -2477,7 +2776,7 @@ function registerConnectCommand(program2) {
|
|
|
2477
2776
|
log.error(`Failed to start. Check logs: ${getLogPath(slug)}`);
|
|
2478
2777
|
process.exit(1);
|
|
2479
2778
|
}
|
|
2480
|
-
const { ListTUI } = await import("./list-
|
|
2779
|
+
const { ListTUI } = await import("./list-RV7HOA77.js");
|
|
2481
2780
|
const tui = new ListTUI();
|
|
2482
2781
|
await tui.run();
|
|
2483
2782
|
return;
|
|
@@ -3555,6 +3854,11 @@ function registerAgentsCommand(program2) {
|
|
|
3555
3854
|
|
|
3556
3855
|
// src/commands/chat.ts
|
|
3557
3856
|
import { createInterface as createInterface4 } from "readline";
|
|
3857
|
+
import { existsSync as existsSync7, readFileSync as readFileSync2, mkdirSync as mkdirSync5, copyFileSync as copyFileSync2 } 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";
|
|
3558
3862
|
|
|
3559
3863
|
// src/utils/sse-parser.ts
|
|
3560
3864
|
function parseSseChunk(raw, carry) {
|
|
@@ -3577,9 +3881,97 @@ function parseSseChunk(raw, carry) {
|
|
|
3577
3881
|
|
|
3578
3882
|
// src/commands/chat.ts
|
|
3579
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
|
+
mkdirSync5(tempDir, { recursive: true });
|
|
3888
|
+
const tempFile = join9(tempDir, fileName);
|
|
3889
|
+
copyFileSync2(filePath, tempFile);
|
|
3890
|
+
const zipPath = join9(tempDir, "upload.zip");
|
|
3891
|
+
execSync4(`cd "${tempDir}" && zip -q "${zipPath}" "${fileName}"`);
|
|
3892
|
+
const zipBuffer = readFileSync2(zipPath);
|
|
3893
|
+
try {
|
|
3894
|
+
execSync4(`rm -rf "${tempDir}"`);
|
|
3895
|
+
} catch {
|
|
3896
|
+
}
|
|
3897
|
+
const zipSha256 = createHash2("sha256").update(zipBuffer).digest("hex");
|
|
3898
|
+
const transferId = randomUUID();
|
|
3899
|
+
return {
|
|
3900
|
+
offer: {
|
|
3901
|
+
transfer_id: transferId,
|
|
3902
|
+
zip_size: zipBuffer.length,
|
|
3903
|
+
zip_sha256: zipSha256,
|
|
3904
|
+
file_count: 1
|
|
3905
|
+
},
|
|
3906
|
+
zipBuffer: Buffer.from(zipBuffer)
|
|
3907
|
+
};
|
|
3908
|
+
}
|
|
3580
3909
|
function sleep5(ms) {
|
|
3581
3910
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
3582
3911
|
}
|
|
3912
|
+
async function chatWebrtcUpload(agentId, offer, zipBuffer, token, baseUrl) {
|
|
3913
|
+
log.info(`[WebRTC] Uploading file (${(offer.zip_size / 1024).toFixed(1)} KB)...`);
|
|
3914
|
+
const sender = new FileUploadSender(offer.transfer_id, zipBuffer);
|
|
3915
|
+
const exchangeSignals = async (signal) => {
|
|
3916
|
+
try {
|
|
3917
|
+
const res = await fetch(`${baseUrl}/api/agents/${agentId}/rtc-signal`, {
|
|
3918
|
+
method: "POST",
|
|
3919
|
+
headers: {
|
|
3920
|
+
Authorization: `Bearer ${token}`,
|
|
3921
|
+
"Content-Type": "application/json"
|
|
3922
|
+
},
|
|
3923
|
+
body: JSON.stringify({
|
|
3924
|
+
transfer_id: offer.transfer_id,
|
|
3925
|
+
signal_type: signal.signal_type,
|
|
3926
|
+
payload: signal.payload
|
|
3927
|
+
})
|
|
3928
|
+
});
|
|
3929
|
+
if (res.ok) {
|
|
3930
|
+
const { signals } = await res.json();
|
|
3931
|
+
for (const s of signals) {
|
|
3932
|
+
await sender.handleSignal(s);
|
|
3933
|
+
}
|
|
3934
|
+
}
|
|
3935
|
+
} catch {
|
|
3936
|
+
}
|
|
3937
|
+
};
|
|
3938
|
+
sender.onSignal(exchangeSignals);
|
|
3939
|
+
await sender.createOffer();
|
|
3940
|
+
const poll = async () => {
|
|
3941
|
+
for (let i = 0; i < 20; i++) {
|
|
3942
|
+
await sleep5(500);
|
|
3943
|
+
try {
|
|
3944
|
+
const res = await fetch(`${baseUrl}/api/agents/${agentId}/rtc-signal`, {
|
|
3945
|
+
method: "POST",
|
|
3946
|
+
headers: {
|
|
3947
|
+
Authorization: `Bearer ${token}`,
|
|
3948
|
+
"Content-Type": "application/json"
|
|
3949
|
+
},
|
|
3950
|
+
body: JSON.stringify({
|
|
3951
|
+
transfer_id: offer.transfer_id,
|
|
3952
|
+
signal_type: "poll",
|
|
3953
|
+
payload: ""
|
|
3954
|
+
})
|
|
3955
|
+
});
|
|
3956
|
+
if (res.ok) {
|
|
3957
|
+
const { signals } = await res.json();
|
|
3958
|
+
for (const s of signals) {
|
|
3959
|
+
await sender.handleSignal(s);
|
|
3960
|
+
}
|
|
3961
|
+
}
|
|
3962
|
+
} catch {
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
};
|
|
3966
|
+
try {
|
|
3967
|
+
await Promise.all([sender.waitForCompletion(3e4), poll()]);
|
|
3968
|
+
log.success(`[WebRTC] File uploaded successfully`);
|
|
3969
|
+
} catch (err) {
|
|
3970
|
+
log.warn(`[WebRTC] Upload failed: ${err.message}`);
|
|
3971
|
+
} finally {
|
|
3972
|
+
sender.close();
|
|
3973
|
+
}
|
|
3974
|
+
}
|
|
3583
3975
|
async function asyncChat(opts) {
|
|
3584
3976
|
const res = await fetch(`${opts.baseUrl}/api/agents/${opts.agentId}/chat`, {
|
|
3585
3977
|
method: "POST",
|
|
@@ -3587,7 +3979,11 @@ async function asyncChat(opts) {
|
|
|
3587
3979
|
Authorization: `Bearer ${opts.token}`,
|
|
3588
3980
|
"Content-Type": "application/json"
|
|
3589
3981
|
},
|
|
3590
|
-
body: JSON.stringify({
|
|
3982
|
+
body: JSON.stringify({
|
|
3983
|
+
message: opts.message,
|
|
3984
|
+
mode: "async",
|
|
3985
|
+
...opts.fileUploadOffer ? { file_upload_offer: opts.fileUploadOffer } : {}
|
|
3986
|
+
}),
|
|
3591
3987
|
signal: opts.signal
|
|
3592
3988
|
});
|
|
3593
3989
|
if (!res.ok) {
|
|
@@ -3655,7 +4051,10 @@ async function streamChat(opts) {
|
|
|
3655
4051
|
Authorization: `Bearer ${opts.token}`,
|
|
3656
4052
|
"Content-Type": "application/json"
|
|
3657
4053
|
},
|
|
3658
|
-
body: JSON.stringify({
|
|
4054
|
+
body: JSON.stringify({
|
|
4055
|
+
message: opts.message,
|
|
4056
|
+
...opts.fileUploadOffer ? { file_upload_offer: opts.fileUploadOffer } : {}
|
|
4057
|
+
}),
|
|
3659
4058
|
signal: opts.signal
|
|
3660
4059
|
});
|
|
3661
4060
|
if (!res.ok) {
|
|
@@ -3789,8 +4188,10 @@ function registerChatCommand(program2) {
|
|
|
3789
4188
|
process.exit(1);
|
|
3790
4189
|
}
|
|
3791
4190
|
log.banner(`Chat with ${agentName}`);
|
|
3792
|
-
console.log(`${GRAY}Type your message and press Enter.
|
|
4191
|
+
console.log(`${GRAY}Type your message and press Enter. /upload <path> to send a file. /quit to exit.${RESET}
|
|
3793
4192
|
`);
|
|
4193
|
+
let pendingUploadOffer;
|
|
4194
|
+
let pendingUploadZipBuffer;
|
|
3794
4195
|
const rl = createInterface4({
|
|
3795
4196
|
input: process.stdin,
|
|
3796
4197
|
output: process.stdout,
|
|
@@ -3813,7 +4214,34 @@ function registerChatCommand(program2) {
|
|
|
3813
4214
|
rl.close();
|
|
3814
4215
|
return;
|
|
3815
4216
|
}
|
|
4217
|
+
if (trimmed.startsWith("/upload ")) {
|
|
4218
|
+
const filePath = trimmed.slice(8).trim();
|
|
4219
|
+
if (!filePath) {
|
|
4220
|
+
log.error("Usage: /upload <path>");
|
|
4221
|
+
rl.prompt();
|
|
4222
|
+
return;
|
|
4223
|
+
}
|
|
4224
|
+
if (!existsSync7(filePath)) {
|
|
4225
|
+
log.error(`File not found: ${filePath}`);
|
|
4226
|
+
rl.prompt();
|
|
4227
|
+
return;
|
|
4228
|
+
}
|
|
4229
|
+
try {
|
|
4230
|
+
const prepared = prepareUploadFile(filePath);
|
|
4231
|
+
pendingUploadOffer = prepared.offer;
|
|
4232
|
+
pendingUploadZipBuffer = prepared.zipBuffer;
|
|
4233
|
+
log.info(`File staged: ${basename2(filePath)} (${(prepared.offer.zip_size / 1024).toFixed(1)} KB). Type a message to send with the file.`);
|
|
4234
|
+
} catch (err) {
|
|
4235
|
+
log.error(`Failed to prepare file: ${err.message}`);
|
|
4236
|
+
}
|
|
4237
|
+
rl.prompt();
|
|
4238
|
+
return;
|
|
4239
|
+
}
|
|
3816
4240
|
console.log("");
|
|
4241
|
+
const uploadOffer = pendingUploadOffer;
|
|
4242
|
+
const uploadZipBuffer = pendingUploadZipBuffer;
|
|
4243
|
+
pendingUploadOffer = void 0;
|
|
4244
|
+
pendingUploadZipBuffer = void 0;
|
|
3817
4245
|
try {
|
|
3818
4246
|
await streamChat({
|
|
3819
4247
|
agentId,
|
|
@@ -3821,8 +4249,12 @@ function registerChatCommand(program2) {
|
|
|
3821
4249
|
token,
|
|
3822
4250
|
baseUrl: opts.baseUrl,
|
|
3823
4251
|
showThinking: opts.thinking,
|
|
3824
|
-
mode
|
|
4252
|
+
mode,
|
|
4253
|
+
fileUploadOffer: uploadOffer
|
|
3825
4254
|
});
|
|
4255
|
+
if (uploadOffer && uploadZipBuffer) {
|
|
4256
|
+
await chatWebrtcUpload(agentId, uploadOffer, uploadZipBuffer, token, opts.baseUrl);
|
|
4257
|
+
}
|
|
3826
4258
|
} catch (err) {
|
|
3827
4259
|
if (abortController.signal.aborted) return;
|
|
3828
4260
|
log.error(err.message);
|
|
@@ -3835,11 +4267,11 @@ function registerChatCommand(program2) {
|
|
|
3835
4267
|
|
|
3836
4268
|
// src/commands/skills.ts
|
|
3837
4269
|
import { readFile as readFile3, writeFile as writeFile3, readdir as readdir2, mkdir as mkdir2, rm, symlink, unlink } from "fs/promises";
|
|
3838
|
-
import { join as
|
|
4270
|
+
import { join as join11, resolve, relative as relative4 } from "path";
|
|
3839
4271
|
|
|
3840
4272
|
// src/utils/skill-parser.ts
|
|
3841
4273
|
import { readFile as readFile2, writeFile as writeFile2, stat as stat3 } from "fs/promises";
|
|
3842
|
-
import { join as
|
|
4274
|
+
import { join as join10 } from "path";
|
|
3843
4275
|
function parseSkillMd(raw) {
|
|
3844
4276
|
const trimmed = raw.trimStart();
|
|
3845
4277
|
if (!trimmed.startsWith("---")) {
|
|
@@ -3921,7 +4353,7 @@ function parseSkillMd(raw) {
|
|
|
3921
4353
|
return { frontmatter, content };
|
|
3922
4354
|
}
|
|
3923
4355
|
async function loadSkillManifest(dir) {
|
|
3924
|
-
const skillMdPath =
|
|
4356
|
+
const skillMdPath = join10(dir, "SKILL.md");
|
|
3925
4357
|
try {
|
|
3926
4358
|
const raw = await readFile2(skillMdPath, "utf-8");
|
|
3927
4359
|
const { frontmatter } = parseSkillMd(raw);
|
|
@@ -4026,13 +4458,13 @@ function skillApiPath(authorLogin, slug) {
|
|
|
4026
4458
|
}
|
|
4027
4459
|
async function resolveSkillsRootAsync(pathArg) {
|
|
4028
4460
|
const projectRoot = pathArg ? resolve(pathArg) : process.cwd();
|
|
4029
|
-
const skillsDir =
|
|
4030
|
-
const claudeSkillsDir =
|
|
4461
|
+
const skillsDir = join11(projectRoot, ".agents", "skills");
|
|
4462
|
+
const claudeSkillsDir = join11(projectRoot, ".claude", "skills");
|
|
4031
4463
|
return { projectRoot, skillsDir, claudeSkillsDir };
|
|
4032
4464
|
}
|
|
4033
4465
|
async function ensureClaudeSymlink(claudeSkillsDir, slug) {
|
|
4034
4466
|
await mkdir2(claudeSkillsDir, { recursive: true });
|
|
4035
|
-
const linkPath =
|
|
4467
|
+
const linkPath = join11(claudeSkillsDir, slug);
|
|
4036
4468
|
try {
|
|
4037
4469
|
await unlink(linkPath);
|
|
4038
4470
|
} catch {
|
|
@@ -4052,7 +4484,7 @@ async function collectPackFiles(dir, manifest) {
|
|
|
4052
4484
|
}
|
|
4053
4485
|
const mainFile = manifest.main || "SKILL.md";
|
|
4054
4486
|
if (!results.includes(mainFile)) {
|
|
4055
|
-
const mainPath =
|
|
4487
|
+
const mainPath = join11(dir, mainFile);
|
|
4056
4488
|
if (await pathExists(mainPath)) {
|
|
4057
4489
|
results.unshift(mainFile);
|
|
4058
4490
|
}
|
|
@@ -4069,7 +4501,7 @@ async function walkDir(dir) {
|
|
|
4069
4501
|
}
|
|
4070
4502
|
for (const entry of entries) {
|
|
4071
4503
|
if (entry.isSymbolicLink()) continue;
|
|
4072
|
-
const fullPath =
|
|
4504
|
+
const fullPath = join11(dir, entry.name);
|
|
4073
4505
|
if (entry.isDirectory()) {
|
|
4074
4506
|
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
4075
4507
|
const sub = await walkDir(fullPath);
|
|
@@ -4087,7 +4519,7 @@ async function packSkill(dir, manifest) {
|
|
|
4087
4519
|
}
|
|
4088
4520
|
const entries = [];
|
|
4089
4521
|
for (const relPath of fileList) {
|
|
4090
|
-
const absPath =
|
|
4522
|
+
const absPath = join11(dir, relPath);
|
|
4091
4523
|
try {
|
|
4092
4524
|
const data = await readFile3(absPath);
|
|
4093
4525
|
entries.push({ path: relPath.replace(/\\/g, "/"), data });
|
|
@@ -4121,7 +4553,7 @@ function bumpVersion(current, bump) {
|
|
|
4121
4553
|
}
|
|
4122
4554
|
async function downloadAndInstallSkill(client, authorLogin, slug, skillsDir) {
|
|
4123
4555
|
const meta = await client.get(skillApiPath(authorLogin, slug));
|
|
4124
|
-
const targetDir =
|
|
4556
|
+
const targetDir = join11(skillsDir, slug);
|
|
4125
4557
|
await mkdir2(targetDir, { recursive: true });
|
|
4126
4558
|
if (meta.has_files) {
|
|
4127
4559
|
const res = await client.getRaw(`${skillApiPath(authorLogin, slug)}/download`);
|
|
@@ -4129,8 +4561,8 @@ async function downloadAndInstallSkill(client, authorLogin, slug, skillsDir) {
|
|
|
4129
4561
|
const buf = Buffer.from(arrayBuf);
|
|
4130
4562
|
const entries = extractZipBuffer(buf);
|
|
4131
4563
|
for (const entry of entries) {
|
|
4132
|
-
const filePath =
|
|
4133
|
-
const dir =
|
|
4564
|
+
const filePath = join11(targetDir, entry.path);
|
|
4565
|
+
const dir = join11(filePath, "..");
|
|
4134
4566
|
await mkdir2(dir, { recursive: true });
|
|
4135
4567
|
await writeFile3(filePath, entry.data);
|
|
4136
4568
|
}
|
|
@@ -4143,7 +4575,7 @@ async function downloadAndInstallSkill(client, authorLogin, slug, skillsDir) {
|
|
|
4143
4575
|
} else {
|
|
4144
4576
|
const res = await client.getRaw(`${skillApiPath(authorLogin, slug)}/raw`);
|
|
4145
4577
|
const content = await res.text();
|
|
4146
|
-
await writeFile3(
|
|
4578
|
+
await writeFile3(join11(targetDir, "SKILL.md"), content);
|
|
4147
4579
|
return {
|
|
4148
4580
|
slug,
|
|
4149
4581
|
name: meta.name,
|
|
@@ -4172,7 +4604,7 @@ function registerSkillsCommand(program2) {
|
|
|
4172
4604
|
try {
|
|
4173
4605
|
const dir = resolveSkillDir(pathArg);
|
|
4174
4606
|
await mkdir2(dir, { recursive: true });
|
|
4175
|
-
const skillMdPath =
|
|
4607
|
+
const skillMdPath = join11(dir, "SKILL.md");
|
|
4176
4608
|
if (await pathExists(skillMdPath)) {
|
|
4177
4609
|
const raw = await readFile3(skillMdPath, "utf-8");
|
|
4178
4610
|
const { frontmatter } = parseSkillMd(raw);
|
|
@@ -4201,7 +4633,7 @@ function registerSkillsCommand(program2) {
|
|
|
4201
4633
|
const dir = resolveSkillDir(pathArg);
|
|
4202
4634
|
const manifest = await loadSkillManifest(dir);
|
|
4203
4635
|
const result = await packSkill(dir, manifest);
|
|
4204
|
-
const outPath =
|
|
4636
|
+
const outPath = join11(dir, result.filename);
|
|
4205
4637
|
await writeFile3(outPath, result.buffer);
|
|
4206
4638
|
slog.info(`Packed ${result.files.length} files \u2192 ${result.filename} (${result.size} bytes)`);
|
|
4207
4639
|
outputJson({
|
|
@@ -4247,7 +4679,7 @@ function registerSkillsCommand(program2) {
|
|
|
4247
4679
|
if (opts.name) manifest.name = opts.name;
|
|
4248
4680
|
if (opts.version) manifest.version = opts.version;
|
|
4249
4681
|
if (opts.private !== void 0) manifest.private = opts.private;
|
|
4250
|
-
content = await readFile3(
|
|
4682
|
+
content = await readFile3(join11(dir, manifest.main || "SKILL.md"), "utf-8");
|
|
4251
4683
|
packResult = await packSkill(dir, manifest);
|
|
4252
4684
|
slog.info(`Packed ${packResult.files.length} files (${packResult.size} bytes)`);
|
|
4253
4685
|
}
|
|
@@ -4384,7 +4816,7 @@ function registerSkillsCommand(program2) {
|
|
|
4384
4816
|
skills.command("version <bump> [path]").description("Bump skill version (patch | minor | major | x.y.z)").action(async (bump, pathArg) => {
|
|
4385
4817
|
try {
|
|
4386
4818
|
const dir = resolveSkillDir(pathArg);
|
|
4387
|
-
const skillMdPath =
|
|
4819
|
+
const skillMdPath = join11(dir, "SKILL.md");
|
|
4388
4820
|
if (!await pathExists(skillMdPath)) {
|
|
4389
4821
|
outputError("not_found", "No SKILL.md found. Run `agent-mesh skills init` first.");
|
|
4390
4822
|
}
|
|
@@ -4404,7 +4836,7 @@ function registerSkillsCommand(program2) {
|
|
|
4404
4836
|
try {
|
|
4405
4837
|
const { authorLogin, slug } = parseSkillRef(ref);
|
|
4406
4838
|
const { skillsDir, claudeSkillsDir } = await resolveSkillsRootAsync(pathArg);
|
|
4407
|
-
const targetDir =
|
|
4839
|
+
const targetDir = join11(skillsDir, slug);
|
|
4408
4840
|
if (await pathExists(targetDir)) {
|
|
4409
4841
|
if (!opts.force) {
|
|
4410
4842
|
outputError("already_installed", `Skill "${slug}" is already installed at ${targetDir}. Use --force to overwrite.`);
|
|
@@ -4443,11 +4875,11 @@ function registerSkillsCommand(program2) {
|
|
|
4443
4875
|
const failed = [];
|
|
4444
4876
|
if (ref) {
|
|
4445
4877
|
const { authorLogin, slug } = parseSkillRef(ref);
|
|
4446
|
-
const targetDir =
|
|
4878
|
+
const targetDir = join11(skillsDir, slug);
|
|
4447
4879
|
if (!await pathExists(targetDir)) {
|
|
4448
4880
|
outputError("not_installed", `Skill "${slug}" is not installed. Use "skills install ${ref}" first.`);
|
|
4449
4881
|
}
|
|
4450
|
-
const skillMdPath =
|
|
4882
|
+
const skillMdPath = join11(targetDir, "SKILL.md");
|
|
4451
4883
|
let localVersion = "0.0.0";
|
|
4452
4884
|
if (await pathExists(skillMdPath)) {
|
|
4453
4885
|
const raw = await readFile3(skillMdPath, "utf-8");
|
|
@@ -4474,7 +4906,7 @@ function registerSkillsCommand(program2) {
|
|
|
4474
4906
|
for (const entry of entries) {
|
|
4475
4907
|
if (!entry.isDirectory()) continue;
|
|
4476
4908
|
const slug = entry.name;
|
|
4477
|
-
const skillMdPath =
|
|
4909
|
+
const skillMdPath = join11(skillsDir, slug, "SKILL.md");
|
|
4478
4910
|
if (!await pathExists(skillMdPath)) {
|
|
4479
4911
|
skipped.push({ slug, reason: "no_skill_md" });
|
|
4480
4912
|
continue;
|
|
@@ -4494,7 +4926,7 @@ function registerSkillsCommand(program2) {
|
|
|
4494
4926
|
skipped.push({ slug, reason: "up_to_date" });
|
|
4495
4927
|
} else {
|
|
4496
4928
|
slog.info(`Updating ${slug}: v${localVersion} \u2192 v${remoteVersion}...`);
|
|
4497
|
-
await rm(
|
|
4929
|
+
await rm(join11(skillsDir, slug), { recursive: true, force: true });
|
|
4498
4930
|
await downloadAndInstallSkill(client, authorLogin, slug, skillsDir);
|
|
4499
4931
|
updated.push({ slug, name: remote.name, old_version: localVersion, new_version: remoteVersion });
|
|
4500
4932
|
}
|
|
@@ -4515,13 +4947,13 @@ function registerSkillsCommand(program2) {
|
|
|
4515
4947
|
skills.command("remove <slug> [path]").description("Remove a locally installed skill").action(async (slug, pathArg) => {
|
|
4516
4948
|
try {
|
|
4517
4949
|
const { skillsDir, claudeSkillsDir } = await resolveSkillsRootAsync(pathArg);
|
|
4518
|
-
const targetDir =
|
|
4950
|
+
const targetDir = join11(skillsDir, slug);
|
|
4519
4951
|
if (!await pathExists(targetDir)) {
|
|
4520
4952
|
outputError("not_installed", `Skill "${slug}" is not installed at ${targetDir}`);
|
|
4521
4953
|
}
|
|
4522
4954
|
await rm(targetDir, { recursive: true, force: true });
|
|
4523
4955
|
try {
|
|
4524
|
-
await unlink(
|
|
4956
|
+
await unlink(join11(claudeSkillsDir, slug));
|
|
4525
4957
|
} catch {
|
|
4526
4958
|
}
|
|
4527
4959
|
slog.success(`Removed skill: ${slug}`);
|
|
@@ -4546,7 +4978,7 @@ function registerSkillsCommand(program2) {
|
|
|
4546
4978
|
for (const entry of entries) {
|
|
4547
4979
|
if (!entry.isDirectory()) continue;
|
|
4548
4980
|
const slug = entry.name;
|
|
4549
|
-
const skillMdPath =
|
|
4981
|
+
const skillMdPath = join11(skillsDir, slug, "SKILL.md");
|
|
4550
4982
|
if (!await pathExists(skillMdPath)) continue;
|
|
4551
4983
|
const raw = await readFile3(skillMdPath, "utf-8");
|
|
4552
4984
|
const { frontmatter } = parseSkillMd(raw);
|
|
@@ -4658,9 +5090,11 @@ function registerDiscoverCommand(program2) {
|
|
|
4658
5090
|
}
|
|
4659
5091
|
|
|
4660
5092
|
// src/commands/call.ts
|
|
4661
|
-
import { readFileSync as
|
|
4662
|
-
import { execSync as
|
|
4663
|
-
import {
|
|
5093
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync6, copyFileSync as copyFileSync3 } from "fs";
|
|
5094
|
+
import { execSync as execSync5 } from "child_process";
|
|
5095
|
+
import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
|
|
5096
|
+
import { join as join12, basename as basename3 } from "path";
|
|
5097
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
4664
5098
|
var DEFAULT_BASE_URL4 = "https://agents.hot";
|
|
4665
5099
|
async function submitRating(baseUrl, token, agentId, callId, rating) {
|
|
4666
5100
|
const res = await fetch(`${baseUrl}/api/agents/${agentId}/rate`, {
|
|
@@ -4753,13 +5187,13 @@ async function webrtcDownload(agentId, offer, token, outputDir, json) {
|
|
|
4753
5187
|
receiver.waitForCompletion(3e4),
|
|
4754
5188
|
poll()
|
|
4755
5189
|
]);
|
|
4756
|
-
|
|
4757
|
-
const zipPath =
|
|
5190
|
+
mkdirSync6(outputDir, { recursive: true });
|
|
5191
|
+
const zipPath = join12(outputDir, ".transfer.zip");
|
|
4758
5192
|
writeFileSync3(zipPath, zipBuffer);
|
|
4759
5193
|
try {
|
|
4760
|
-
|
|
5194
|
+
execSync5(`unzip -o -q "${zipPath}" -d "${outputDir}"`);
|
|
4761
5195
|
try {
|
|
4762
|
-
|
|
5196
|
+
execSync5(`rm "${zipPath}"`);
|
|
4763
5197
|
} catch {
|
|
4764
5198
|
}
|
|
4765
5199
|
} catch {
|
|
@@ -4788,6 +5222,113 @@ async function webrtcDownload(agentId, offer, token, outputDir, json) {
|
|
|
4788
5222
|
receiver.close();
|
|
4789
5223
|
}
|
|
4790
5224
|
}
|
|
5225
|
+
function prepareFileForUpload(filePath) {
|
|
5226
|
+
const fileName = basename3(filePath);
|
|
5227
|
+
const tempDir = join12(tmpdir2(), `upload-${Date.now()}`);
|
|
5228
|
+
mkdirSync6(tempDir, { recursive: true });
|
|
5229
|
+
const tempFile = join12(tempDir, fileName);
|
|
5230
|
+
copyFileSync3(filePath, tempFile);
|
|
5231
|
+
const zipPath = join12(tempDir, "upload.zip");
|
|
5232
|
+
execSync5(`cd "${tempDir}" && zip -q "${zipPath}" "${fileName}"`);
|
|
5233
|
+
const zipBuffer = readFileSync3(zipPath);
|
|
5234
|
+
try {
|
|
5235
|
+
execSync5(`rm -rf "${tempDir}"`);
|
|
5236
|
+
} catch {
|
|
5237
|
+
}
|
|
5238
|
+
const zipSha256 = createHash3("sha256").update(zipBuffer).digest("hex");
|
|
5239
|
+
const transferId = randomUUID2();
|
|
5240
|
+
return {
|
|
5241
|
+
offer: {
|
|
5242
|
+
transfer_id: transferId,
|
|
5243
|
+
zip_size: zipBuffer.length,
|
|
5244
|
+
zip_sha256: zipSha256,
|
|
5245
|
+
file_count: 1
|
|
5246
|
+
},
|
|
5247
|
+
zipBuffer: Buffer.from(zipBuffer)
|
|
5248
|
+
};
|
|
5249
|
+
}
|
|
5250
|
+
async function webrtcUpload(agentId, offer, zipBuffer, token, json) {
|
|
5251
|
+
if (!json) {
|
|
5252
|
+
log.info(`[WebRTC] Uploading file (${(offer.zip_size / 1024).toFixed(1)} KB)...`);
|
|
5253
|
+
}
|
|
5254
|
+
const sender = new FileUploadSender(offer.transfer_id, zipBuffer);
|
|
5255
|
+
const exchangeSignals = async (signal) => {
|
|
5256
|
+
try {
|
|
5257
|
+
const res = await fetch(`${DEFAULT_BASE_URL4}/api/agents/${agentId}/rtc-signal`, {
|
|
5258
|
+
method: "POST",
|
|
5259
|
+
headers: {
|
|
5260
|
+
Authorization: `Bearer ${token}`,
|
|
5261
|
+
"Content-Type": "application/json"
|
|
5262
|
+
},
|
|
5263
|
+
body: JSON.stringify({
|
|
5264
|
+
transfer_id: offer.transfer_id,
|
|
5265
|
+
signal_type: signal.signal_type,
|
|
5266
|
+
payload: signal.payload
|
|
5267
|
+
})
|
|
5268
|
+
});
|
|
5269
|
+
if (res.ok) {
|
|
5270
|
+
const { signals } = await res.json();
|
|
5271
|
+
for (const s of signals) {
|
|
5272
|
+
await sender.handleSignal(s);
|
|
5273
|
+
}
|
|
5274
|
+
}
|
|
5275
|
+
} catch {
|
|
5276
|
+
}
|
|
5277
|
+
};
|
|
5278
|
+
sender.onSignal(exchangeSignals);
|
|
5279
|
+
await sender.createOffer();
|
|
5280
|
+
const poll = async () => {
|
|
5281
|
+
for (let i = 0; i < 20; i++) {
|
|
5282
|
+
await sleep6(500);
|
|
5283
|
+
try {
|
|
5284
|
+
const res = await fetch(`${DEFAULT_BASE_URL4}/api/agents/${agentId}/rtc-signal`, {
|
|
5285
|
+
method: "POST",
|
|
5286
|
+
headers: {
|
|
5287
|
+
Authorization: `Bearer ${token}`,
|
|
5288
|
+
"Content-Type": "application/json"
|
|
5289
|
+
},
|
|
5290
|
+
body: JSON.stringify({
|
|
5291
|
+
transfer_id: offer.transfer_id,
|
|
5292
|
+
signal_type: "poll",
|
|
5293
|
+
payload: ""
|
|
5294
|
+
})
|
|
5295
|
+
});
|
|
5296
|
+
if (res.ok) {
|
|
5297
|
+
const { signals } = await res.json();
|
|
5298
|
+
for (const s of signals) {
|
|
5299
|
+
await sender.handleSignal(s);
|
|
5300
|
+
}
|
|
5301
|
+
}
|
|
5302
|
+
} catch {
|
|
5303
|
+
}
|
|
5304
|
+
}
|
|
5305
|
+
};
|
|
5306
|
+
try {
|
|
5307
|
+
await Promise.all([
|
|
5308
|
+
sender.waitForCompletion(3e4),
|
|
5309
|
+
poll()
|
|
5310
|
+
]);
|
|
5311
|
+
if (json) {
|
|
5312
|
+
console.log(JSON.stringify({
|
|
5313
|
+
type: "files_uploaded",
|
|
5314
|
+
file_count: offer.file_count,
|
|
5315
|
+
zip_size: offer.zip_size,
|
|
5316
|
+
sha256: offer.zip_sha256
|
|
5317
|
+
}));
|
|
5318
|
+
} else {
|
|
5319
|
+
log.success(`[WebRTC] File uploaded successfully`);
|
|
5320
|
+
}
|
|
5321
|
+
} catch (err) {
|
|
5322
|
+
const msg = err.message;
|
|
5323
|
+
if (json) {
|
|
5324
|
+
console.log(JSON.stringify({ type: "file_upload_failed", error: msg }));
|
|
5325
|
+
} else {
|
|
5326
|
+
log.warn(`[WebRTC] File upload failed: ${msg}`);
|
|
5327
|
+
}
|
|
5328
|
+
} finally {
|
|
5329
|
+
sender.close();
|
|
5330
|
+
}
|
|
5331
|
+
}
|
|
4791
5332
|
async function asyncCall(opts) {
|
|
4792
5333
|
const selfAgentId = process.env.AGENT_BRIDGE_AGENT_ID;
|
|
4793
5334
|
const res = await fetch(`${DEFAULT_BASE_URL4}/api/agents/${opts.id}/call`, {
|
|
@@ -4800,7 +5341,8 @@ async function asyncCall(opts) {
|
|
|
4800
5341
|
body: JSON.stringify({
|
|
4801
5342
|
task_description: opts.taskDescription,
|
|
4802
5343
|
mode: "async",
|
|
4803
|
-
...opts.withFiles ? { with_files: true } : {}
|
|
5344
|
+
...opts.withFiles ? { with_files: true } : {},
|
|
5345
|
+
...opts.uploadOffer ? { file_upload_offer: opts.uploadOffer } : {}
|
|
4804
5346
|
}),
|
|
4805
5347
|
signal: opts.signal
|
|
4806
5348
|
});
|
|
@@ -4880,7 +5422,7 @@ async function asyncCall(opts) {
|
|
|
4880
5422
|
if (!opts.json) log.info(`Saved to ${opts.outputFile}`);
|
|
4881
5423
|
}
|
|
4882
5424
|
if (offer && opts.withFiles) {
|
|
4883
|
-
const outputDir = opts.outputFile ?
|
|
5425
|
+
const outputDir = opts.outputFile ? join12(opts.outputFile, "..", "files") : join12(process.cwd(), "agent-output");
|
|
4884
5426
|
await webrtcDownload(opts.id, offer, opts.token, outputDir, opts.json);
|
|
4885
5427
|
}
|
|
4886
5428
|
if (!opts.json) {
|
|
@@ -4919,7 +5461,8 @@ async function streamCall(opts) {
|
|
|
4919
5461
|
},
|
|
4920
5462
|
body: JSON.stringify({
|
|
4921
5463
|
task_description: opts.taskDescription,
|
|
4922
|
-
...opts.withFiles ? { with_files: true } : {}
|
|
5464
|
+
...opts.withFiles ? { with_files: true } : {},
|
|
5465
|
+
...opts.uploadOffer ? { file_upload_offer: opts.uploadOffer } : {}
|
|
4923
5466
|
}),
|
|
4924
5467
|
signal: opts.signal
|
|
4925
5468
|
});
|
|
@@ -5054,7 +5597,7 @@ Error: ${event.message}
|
|
|
5054
5597
|
}
|
|
5055
5598
|
if (fileOffer && opts.withFiles) {
|
|
5056
5599
|
console.log("");
|
|
5057
|
-
const outputDir = opts.outputFile ?
|
|
5600
|
+
const outputDir = opts.outputFile ? join12(opts.outputFile, "..", "files") : join12(process.cwd(), "agent-output");
|
|
5058
5601
|
await webrtcDownload(opts.id, fileOffer, opts.token, outputDir, opts.json);
|
|
5059
5602
|
}
|
|
5060
5603
|
if (!opts.json) {
|
|
@@ -5070,7 +5613,7 @@ Error: ${event.message}
|
|
|
5070
5613
|
return { callId, ...sessionKey ? { sessionKey } : {} };
|
|
5071
5614
|
}
|
|
5072
5615
|
function registerCallCommand(program2) {
|
|
5073
|
-
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) => {
|
|
5616
|
+
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) => {
|
|
5074
5617
|
try {
|
|
5075
5618
|
const token = loadToken();
|
|
5076
5619
|
if (!token) {
|
|
@@ -5081,13 +5624,25 @@ function registerCallCommand(program2) {
|
|
|
5081
5624
|
const { id, name } = await resolveAgentId(agentInput, client);
|
|
5082
5625
|
let taskDescription = opts.task;
|
|
5083
5626
|
if (opts.inputFile) {
|
|
5084
|
-
const content =
|
|
5627
|
+
const content = readFileSync3(opts.inputFile, "utf-8");
|
|
5085
5628
|
taskDescription = `${taskDescription}
|
|
5086
5629
|
|
|
5087
5630
|
---
|
|
5088
5631
|
|
|
5089
5632
|
${content}`;
|
|
5090
5633
|
}
|
|
5634
|
+
let uploadOffer;
|
|
5635
|
+
let uploadZipBuffer;
|
|
5636
|
+
if (opts.uploadFile) {
|
|
5637
|
+
const { existsSync: existsSync8 } = __require("fs");
|
|
5638
|
+
if (!existsSync8(opts.uploadFile)) {
|
|
5639
|
+
log.error(`File not found: ${opts.uploadFile}`);
|
|
5640
|
+
process.exit(1);
|
|
5641
|
+
}
|
|
5642
|
+
const prepared = prepareFileForUpload(opts.uploadFile);
|
|
5643
|
+
uploadOffer = prepared.offer;
|
|
5644
|
+
uploadZipBuffer = prepared.zipBuffer;
|
|
5645
|
+
}
|
|
5091
5646
|
const timeoutMs = parseInt(opts.timeout || "300", 10) * 1e3;
|
|
5092
5647
|
const abortController = new AbortController();
|
|
5093
5648
|
const timer = setTimeout(() => abortController.abort(), timeoutMs);
|
|
@@ -5100,13 +5655,20 @@ ${content}`;
|
|
|
5100
5655
|
json: opts.json,
|
|
5101
5656
|
outputFile: opts.outputFile,
|
|
5102
5657
|
signal: abortController.signal,
|
|
5103
|
-
withFiles: opts.withFiles
|
|
5658
|
+
withFiles: opts.withFiles,
|
|
5659
|
+
uploadOffer
|
|
5104
5660
|
};
|
|
5105
5661
|
let result;
|
|
5106
5662
|
if (opts.stream) {
|
|
5663
|
+
if (uploadOffer && uploadZipBuffer) {
|
|
5664
|
+
void webrtcUpload(id, uploadOffer, uploadZipBuffer, token, opts.json);
|
|
5665
|
+
}
|
|
5107
5666
|
result = await streamCall(callOpts);
|
|
5108
5667
|
} else {
|
|
5109
5668
|
result = await asyncCall(callOpts);
|
|
5669
|
+
if (uploadOffer && uploadZipBuffer) {
|
|
5670
|
+
await webrtcUpload(id, uploadOffer, uploadZipBuffer, token, opts.json);
|
|
5671
|
+
}
|
|
5110
5672
|
}
|
|
5111
5673
|
clearTimeout(timer);
|
|
5112
5674
|
if (opts.rate && result.callId) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@annals/agent-mesh",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.1",
|
|
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",
|