@annals/agent-mesh 0.17.9 → 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
|
}
|
|
@@ -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 } 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,99 @@ 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
|
+
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
|
+
}
|
|
3580
3911
|
function sleep5(ms) {
|
|
3581
3912
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
3582
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
|
+
}
|
|
3583
3977
|
async function asyncChat(opts) {
|
|
3584
3978
|
const res = await fetch(`${opts.baseUrl}/api/agents/${opts.agentId}/chat`, {
|
|
3585
3979
|
method: "POST",
|
|
@@ -3587,7 +3981,11 @@ async function asyncChat(opts) {
|
|
|
3587
3981
|
Authorization: `Bearer ${opts.token}`,
|
|
3588
3982
|
"Content-Type": "application/json"
|
|
3589
3983
|
},
|
|
3590
|
-
body: JSON.stringify({
|
|
3984
|
+
body: JSON.stringify({
|
|
3985
|
+
message: opts.message,
|
|
3986
|
+
mode: "async",
|
|
3987
|
+
...opts.fileUploadOffer ? { file_upload_offer: opts.fileUploadOffer } : {}
|
|
3988
|
+
}),
|
|
3591
3989
|
signal: opts.signal
|
|
3592
3990
|
});
|
|
3593
3991
|
if (!res.ok) {
|
|
@@ -3655,7 +4053,10 @@ async function streamChat(opts) {
|
|
|
3655
4053
|
Authorization: `Bearer ${opts.token}`,
|
|
3656
4054
|
"Content-Type": "application/json"
|
|
3657
4055
|
},
|
|
3658
|
-
body: JSON.stringify({
|
|
4056
|
+
body: JSON.stringify({
|
|
4057
|
+
message: opts.message,
|
|
4058
|
+
...opts.fileUploadOffer ? { file_upload_offer: opts.fileUploadOffer } : {}
|
|
4059
|
+
}),
|
|
3659
4060
|
signal: opts.signal
|
|
3660
4061
|
});
|
|
3661
4062
|
if (!res.ok) {
|
|
@@ -3789,8 +4190,10 @@ function registerChatCommand(program2) {
|
|
|
3789
4190
|
process.exit(1);
|
|
3790
4191
|
}
|
|
3791
4192
|
log.banner(`Chat with ${agentName}`);
|
|
3792
|
-
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}
|
|
3793
4194
|
`);
|
|
4195
|
+
let pendingUploadOffer;
|
|
4196
|
+
let pendingUploadZipBuffer;
|
|
3794
4197
|
const rl = createInterface4({
|
|
3795
4198
|
input: process.stdin,
|
|
3796
4199
|
output: process.stdout,
|
|
@@ -3813,7 +4216,34 @@ function registerChatCommand(program2) {
|
|
|
3813
4216
|
rl.close();
|
|
3814
4217
|
return;
|
|
3815
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
|
+
}
|
|
3816
4242
|
console.log("");
|
|
4243
|
+
const uploadOffer = pendingUploadOffer;
|
|
4244
|
+
const uploadZipBuffer = pendingUploadZipBuffer;
|
|
4245
|
+
pendingUploadOffer = void 0;
|
|
4246
|
+
pendingUploadZipBuffer = void 0;
|
|
3817
4247
|
try {
|
|
3818
4248
|
await streamChat({
|
|
3819
4249
|
agentId,
|
|
@@ -3821,8 +4251,12 @@ function registerChatCommand(program2) {
|
|
|
3821
4251
|
token,
|
|
3822
4252
|
baseUrl: opts.baseUrl,
|
|
3823
4253
|
showThinking: opts.thinking,
|
|
3824
|
-
mode
|
|
4254
|
+
mode,
|
|
4255
|
+
fileUploadOffer: uploadOffer
|
|
3825
4256
|
});
|
|
4257
|
+
if (uploadOffer && uploadZipBuffer) {
|
|
4258
|
+
await chatWebrtcUpload(agentId, uploadOffer, uploadZipBuffer, token, opts.baseUrl);
|
|
4259
|
+
}
|
|
3826
4260
|
} catch (err) {
|
|
3827
4261
|
if (abortController.signal.aborted) return;
|
|
3828
4262
|
log.error(err.message);
|
|
@@ -3835,11 +4269,11 @@ function registerChatCommand(program2) {
|
|
|
3835
4269
|
|
|
3836
4270
|
// src/commands/skills.ts
|
|
3837
4271
|
import { readFile as readFile3, writeFile as writeFile3, readdir as readdir2, mkdir as mkdir2, rm, symlink, unlink } from "fs/promises";
|
|
3838
|
-
import { join as
|
|
4272
|
+
import { join as join11, resolve, relative as relative4 } from "path";
|
|
3839
4273
|
|
|
3840
4274
|
// src/utils/skill-parser.ts
|
|
3841
4275
|
import { readFile as readFile2, writeFile as writeFile2, stat as stat3 } from "fs/promises";
|
|
3842
|
-
import { join as
|
|
4276
|
+
import { join as join10 } from "path";
|
|
3843
4277
|
function parseSkillMd(raw) {
|
|
3844
4278
|
const trimmed = raw.trimStart();
|
|
3845
4279
|
if (!trimmed.startsWith("---")) {
|
|
@@ -3921,7 +4355,7 @@ function parseSkillMd(raw) {
|
|
|
3921
4355
|
return { frontmatter, content };
|
|
3922
4356
|
}
|
|
3923
4357
|
async function loadSkillManifest(dir) {
|
|
3924
|
-
const skillMdPath =
|
|
4358
|
+
const skillMdPath = join10(dir, "SKILL.md");
|
|
3925
4359
|
try {
|
|
3926
4360
|
const raw = await readFile2(skillMdPath, "utf-8");
|
|
3927
4361
|
const { frontmatter } = parseSkillMd(raw);
|
|
@@ -4026,13 +4460,13 @@ function skillApiPath(authorLogin, slug) {
|
|
|
4026
4460
|
}
|
|
4027
4461
|
async function resolveSkillsRootAsync(pathArg) {
|
|
4028
4462
|
const projectRoot = pathArg ? resolve(pathArg) : process.cwd();
|
|
4029
|
-
const skillsDir =
|
|
4030
|
-
const claudeSkillsDir =
|
|
4463
|
+
const skillsDir = join11(projectRoot, ".agents", "skills");
|
|
4464
|
+
const claudeSkillsDir = join11(projectRoot, ".claude", "skills");
|
|
4031
4465
|
return { projectRoot, skillsDir, claudeSkillsDir };
|
|
4032
4466
|
}
|
|
4033
4467
|
async function ensureClaudeSymlink(claudeSkillsDir, slug) {
|
|
4034
4468
|
await mkdir2(claudeSkillsDir, { recursive: true });
|
|
4035
|
-
const linkPath =
|
|
4469
|
+
const linkPath = join11(claudeSkillsDir, slug);
|
|
4036
4470
|
try {
|
|
4037
4471
|
await unlink(linkPath);
|
|
4038
4472
|
} catch {
|
|
@@ -4052,7 +4486,7 @@ async function collectPackFiles(dir, manifest) {
|
|
|
4052
4486
|
}
|
|
4053
4487
|
const mainFile = manifest.main || "SKILL.md";
|
|
4054
4488
|
if (!results.includes(mainFile)) {
|
|
4055
|
-
const mainPath =
|
|
4489
|
+
const mainPath = join11(dir, mainFile);
|
|
4056
4490
|
if (await pathExists(mainPath)) {
|
|
4057
4491
|
results.unshift(mainFile);
|
|
4058
4492
|
}
|
|
@@ -4069,7 +4503,7 @@ async function walkDir(dir) {
|
|
|
4069
4503
|
}
|
|
4070
4504
|
for (const entry of entries) {
|
|
4071
4505
|
if (entry.isSymbolicLink()) continue;
|
|
4072
|
-
const fullPath =
|
|
4506
|
+
const fullPath = join11(dir, entry.name);
|
|
4073
4507
|
if (entry.isDirectory()) {
|
|
4074
4508
|
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
4075
4509
|
const sub = await walkDir(fullPath);
|
|
@@ -4087,7 +4521,7 @@ async function packSkill(dir, manifest) {
|
|
|
4087
4521
|
}
|
|
4088
4522
|
const entries = [];
|
|
4089
4523
|
for (const relPath of fileList) {
|
|
4090
|
-
const absPath =
|
|
4524
|
+
const absPath = join11(dir, relPath);
|
|
4091
4525
|
try {
|
|
4092
4526
|
const data = await readFile3(absPath);
|
|
4093
4527
|
entries.push({ path: relPath.replace(/\\/g, "/"), data });
|
|
@@ -4121,7 +4555,7 @@ function bumpVersion(current, bump) {
|
|
|
4121
4555
|
}
|
|
4122
4556
|
async function downloadAndInstallSkill(client, authorLogin, slug, skillsDir) {
|
|
4123
4557
|
const meta = await client.get(skillApiPath(authorLogin, slug));
|
|
4124
|
-
const targetDir =
|
|
4558
|
+
const targetDir = join11(skillsDir, slug);
|
|
4125
4559
|
await mkdir2(targetDir, { recursive: true });
|
|
4126
4560
|
if (meta.has_files) {
|
|
4127
4561
|
const res = await client.getRaw(`${skillApiPath(authorLogin, slug)}/download`);
|
|
@@ -4129,8 +4563,8 @@ async function downloadAndInstallSkill(client, authorLogin, slug, skillsDir) {
|
|
|
4129
4563
|
const buf = Buffer.from(arrayBuf);
|
|
4130
4564
|
const entries = extractZipBuffer(buf);
|
|
4131
4565
|
for (const entry of entries) {
|
|
4132
|
-
const filePath =
|
|
4133
|
-
const dir =
|
|
4566
|
+
const filePath = join11(targetDir, entry.path);
|
|
4567
|
+
const dir = join11(filePath, "..");
|
|
4134
4568
|
await mkdir2(dir, { recursive: true });
|
|
4135
4569
|
await writeFile3(filePath, entry.data);
|
|
4136
4570
|
}
|
|
@@ -4143,7 +4577,7 @@ async function downloadAndInstallSkill(client, authorLogin, slug, skillsDir) {
|
|
|
4143
4577
|
} else {
|
|
4144
4578
|
const res = await client.getRaw(`${skillApiPath(authorLogin, slug)}/raw`);
|
|
4145
4579
|
const content = await res.text();
|
|
4146
|
-
await writeFile3(
|
|
4580
|
+
await writeFile3(join11(targetDir, "SKILL.md"), content);
|
|
4147
4581
|
return {
|
|
4148
4582
|
slug,
|
|
4149
4583
|
name: meta.name,
|
|
@@ -4172,7 +4606,7 @@ function registerSkillsCommand(program2) {
|
|
|
4172
4606
|
try {
|
|
4173
4607
|
const dir = resolveSkillDir(pathArg);
|
|
4174
4608
|
await mkdir2(dir, { recursive: true });
|
|
4175
|
-
const skillMdPath =
|
|
4609
|
+
const skillMdPath = join11(dir, "SKILL.md");
|
|
4176
4610
|
if (await pathExists(skillMdPath)) {
|
|
4177
4611
|
const raw = await readFile3(skillMdPath, "utf-8");
|
|
4178
4612
|
const { frontmatter } = parseSkillMd(raw);
|
|
@@ -4201,7 +4635,7 @@ function registerSkillsCommand(program2) {
|
|
|
4201
4635
|
const dir = resolveSkillDir(pathArg);
|
|
4202
4636
|
const manifest = await loadSkillManifest(dir);
|
|
4203
4637
|
const result = await packSkill(dir, manifest);
|
|
4204
|
-
const outPath =
|
|
4638
|
+
const outPath = join11(dir, result.filename);
|
|
4205
4639
|
await writeFile3(outPath, result.buffer);
|
|
4206
4640
|
slog.info(`Packed ${result.files.length} files \u2192 ${result.filename} (${result.size} bytes)`);
|
|
4207
4641
|
outputJson({
|
|
@@ -4247,7 +4681,7 @@ function registerSkillsCommand(program2) {
|
|
|
4247
4681
|
if (opts.name) manifest.name = opts.name;
|
|
4248
4682
|
if (opts.version) manifest.version = opts.version;
|
|
4249
4683
|
if (opts.private !== void 0) manifest.private = opts.private;
|
|
4250
|
-
content = await readFile3(
|
|
4684
|
+
content = await readFile3(join11(dir, manifest.main || "SKILL.md"), "utf-8");
|
|
4251
4685
|
packResult = await packSkill(dir, manifest);
|
|
4252
4686
|
slog.info(`Packed ${packResult.files.length} files (${packResult.size} bytes)`);
|
|
4253
4687
|
}
|
|
@@ -4384,7 +4818,7 @@ function registerSkillsCommand(program2) {
|
|
|
4384
4818
|
skills.command("version <bump> [path]").description("Bump skill version (patch | minor | major | x.y.z)").action(async (bump, pathArg) => {
|
|
4385
4819
|
try {
|
|
4386
4820
|
const dir = resolveSkillDir(pathArg);
|
|
4387
|
-
const skillMdPath =
|
|
4821
|
+
const skillMdPath = join11(dir, "SKILL.md");
|
|
4388
4822
|
if (!await pathExists(skillMdPath)) {
|
|
4389
4823
|
outputError("not_found", "No SKILL.md found. Run `agent-mesh skills init` first.");
|
|
4390
4824
|
}
|
|
@@ -4404,7 +4838,7 @@ function registerSkillsCommand(program2) {
|
|
|
4404
4838
|
try {
|
|
4405
4839
|
const { authorLogin, slug } = parseSkillRef(ref);
|
|
4406
4840
|
const { skillsDir, claudeSkillsDir } = await resolveSkillsRootAsync(pathArg);
|
|
4407
|
-
const targetDir =
|
|
4841
|
+
const targetDir = join11(skillsDir, slug);
|
|
4408
4842
|
if (await pathExists(targetDir)) {
|
|
4409
4843
|
if (!opts.force) {
|
|
4410
4844
|
outputError("already_installed", `Skill "${slug}" is already installed at ${targetDir}. Use --force to overwrite.`);
|
|
@@ -4443,11 +4877,11 @@ function registerSkillsCommand(program2) {
|
|
|
4443
4877
|
const failed = [];
|
|
4444
4878
|
if (ref) {
|
|
4445
4879
|
const { authorLogin, slug } = parseSkillRef(ref);
|
|
4446
|
-
const targetDir =
|
|
4880
|
+
const targetDir = join11(skillsDir, slug);
|
|
4447
4881
|
if (!await pathExists(targetDir)) {
|
|
4448
4882
|
outputError("not_installed", `Skill "${slug}" is not installed. Use "skills install ${ref}" first.`);
|
|
4449
4883
|
}
|
|
4450
|
-
const skillMdPath =
|
|
4884
|
+
const skillMdPath = join11(targetDir, "SKILL.md");
|
|
4451
4885
|
let localVersion = "0.0.0";
|
|
4452
4886
|
if (await pathExists(skillMdPath)) {
|
|
4453
4887
|
const raw = await readFile3(skillMdPath, "utf-8");
|
|
@@ -4474,7 +4908,7 @@ function registerSkillsCommand(program2) {
|
|
|
4474
4908
|
for (const entry of entries) {
|
|
4475
4909
|
if (!entry.isDirectory()) continue;
|
|
4476
4910
|
const slug = entry.name;
|
|
4477
|
-
const skillMdPath =
|
|
4911
|
+
const skillMdPath = join11(skillsDir, slug, "SKILL.md");
|
|
4478
4912
|
if (!await pathExists(skillMdPath)) {
|
|
4479
4913
|
skipped.push({ slug, reason: "no_skill_md" });
|
|
4480
4914
|
continue;
|
|
@@ -4494,7 +4928,7 @@ function registerSkillsCommand(program2) {
|
|
|
4494
4928
|
skipped.push({ slug, reason: "up_to_date" });
|
|
4495
4929
|
} else {
|
|
4496
4930
|
slog.info(`Updating ${slug}: v${localVersion} \u2192 v${remoteVersion}...`);
|
|
4497
|
-
await rm(
|
|
4931
|
+
await rm(join11(skillsDir, slug), { recursive: true, force: true });
|
|
4498
4932
|
await downloadAndInstallSkill(client, authorLogin, slug, skillsDir);
|
|
4499
4933
|
updated.push({ slug, name: remote.name, old_version: localVersion, new_version: remoteVersion });
|
|
4500
4934
|
}
|
|
@@ -4515,13 +4949,13 @@ function registerSkillsCommand(program2) {
|
|
|
4515
4949
|
skills.command("remove <slug> [path]").description("Remove a locally installed skill").action(async (slug, pathArg) => {
|
|
4516
4950
|
try {
|
|
4517
4951
|
const { skillsDir, claudeSkillsDir } = await resolveSkillsRootAsync(pathArg);
|
|
4518
|
-
const targetDir =
|
|
4952
|
+
const targetDir = join11(skillsDir, slug);
|
|
4519
4953
|
if (!await pathExists(targetDir)) {
|
|
4520
4954
|
outputError("not_installed", `Skill "${slug}" is not installed at ${targetDir}`);
|
|
4521
4955
|
}
|
|
4522
4956
|
await rm(targetDir, { recursive: true, force: true });
|
|
4523
4957
|
try {
|
|
4524
|
-
await unlink(
|
|
4958
|
+
await unlink(join11(claudeSkillsDir, slug));
|
|
4525
4959
|
} catch {
|
|
4526
4960
|
}
|
|
4527
4961
|
slog.success(`Removed skill: ${slug}`);
|
|
@@ -4546,7 +4980,7 @@ function registerSkillsCommand(program2) {
|
|
|
4546
4980
|
for (const entry of entries) {
|
|
4547
4981
|
if (!entry.isDirectory()) continue;
|
|
4548
4982
|
const slug = entry.name;
|
|
4549
|
-
const skillMdPath =
|
|
4983
|
+
const skillMdPath = join11(skillsDir, slug, "SKILL.md");
|
|
4550
4984
|
if (!await pathExists(skillMdPath)) continue;
|
|
4551
4985
|
const raw = await readFile3(skillMdPath, "utf-8");
|
|
4552
4986
|
const { frontmatter } = parseSkillMd(raw);
|
|
@@ -4658,9 +5092,9 @@ function registerDiscoverCommand(program2) {
|
|
|
4658
5092
|
}
|
|
4659
5093
|
|
|
4660
5094
|
// src/commands/call.ts
|
|
4661
|
-
import { readFileSync as
|
|
4662
|
-
import { execSync as
|
|
4663
|
-
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";
|
|
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`, {
|
|
@@ -4754,12 +5188,12 @@ async function webrtcDownload(agentId, offer, token, outputDir, json) {
|
|
|
4754
5188
|
poll()
|
|
4755
5189
|
]);
|
|
4756
5190
|
mkdirSync5(outputDir, { recursive: true });
|
|
4757
|
-
const zipPath =
|
|
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,120 @@ async function webrtcDownload(agentId, offer, token, outputDir, json) {
|
|
|
4788
5222
|
receiver.close();
|
|
4789
5223
|
}
|
|
4790
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
|
+
}
|
|
4791
5339
|
async function asyncCall(opts) {
|
|
4792
5340
|
const selfAgentId = process.env.AGENT_BRIDGE_AGENT_ID;
|
|
4793
5341
|
const res = await fetch(`${DEFAULT_BASE_URL4}/api/agents/${opts.id}/call`, {
|
|
@@ -4800,7 +5348,8 @@ async function asyncCall(opts) {
|
|
|
4800
5348
|
body: JSON.stringify({
|
|
4801
5349
|
task_description: opts.taskDescription,
|
|
4802
5350
|
mode: "async",
|
|
4803
|
-
...opts.withFiles ? { with_files: true } : {}
|
|
5351
|
+
...opts.withFiles ? { with_files: true } : {},
|
|
5352
|
+
...opts.uploadOffer ? { file_upload_offer: opts.uploadOffer } : {}
|
|
4804
5353
|
}),
|
|
4805
5354
|
signal: opts.signal
|
|
4806
5355
|
});
|
|
@@ -4880,7 +5429,7 @@ async function asyncCall(opts) {
|
|
|
4880
5429
|
if (!opts.json) log.info(`Saved to ${opts.outputFile}`);
|
|
4881
5430
|
}
|
|
4882
5431
|
if (offer && opts.withFiles) {
|
|
4883
|
-
const outputDir = opts.outputFile ?
|
|
5432
|
+
const outputDir = opts.outputFile ? join12(opts.outputFile, "..", "files") : join12(process.cwd(), "agent-output");
|
|
4884
5433
|
await webrtcDownload(opts.id, offer, opts.token, outputDir, opts.json);
|
|
4885
5434
|
}
|
|
4886
5435
|
if (!opts.json) {
|
|
@@ -4919,7 +5468,8 @@ async function streamCall(opts) {
|
|
|
4919
5468
|
},
|
|
4920
5469
|
body: JSON.stringify({
|
|
4921
5470
|
task_description: opts.taskDescription,
|
|
4922
|
-
...opts.withFiles ? { with_files: true } : {}
|
|
5471
|
+
...opts.withFiles ? { with_files: true } : {},
|
|
5472
|
+
...opts.uploadOffer ? { file_upload_offer: opts.uploadOffer } : {}
|
|
4923
5473
|
}),
|
|
4924
5474
|
signal: opts.signal
|
|
4925
5475
|
});
|
|
@@ -5054,7 +5604,7 @@ Error: ${event.message}
|
|
|
5054
5604
|
}
|
|
5055
5605
|
if (fileOffer && opts.withFiles) {
|
|
5056
5606
|
console.log("");
|
|
5057
|
-
const outputDir = opts.outputFile ?
|
|
5607
|
+
const outputDir = opts.outputFile ? join12(opts.outputFile, "..", "files") : join12(process.cwd(), "agent-output");
|
|
5058
5608
|
await webrtcDownload(opts.id, fileOffer, opts.token, outputDir, opts.json);
|
|
5059
5609
|
}
|
|
5060
5610
|
if (!opts.json) {
|
|
@@ -5070,7 +5620,7 @@ Error: ${event.message}
|
|
|
5070
5620
|
return { callId, ...sessionKey ? { sessionKey } : {} };
|
|
5071
5621
|
}
|
|
5072
5622
|
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) => {
|
|
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) => {
|
|
5074
5624
|
try {
|
|
5075
5625
|
const token = loadToken();
|
|
5076
5626
|
if (!token) {
|
|
@@ -5081,13 +5631,25 @@ function registerCallCommand(program2) {
|
|
|
5081
5631
|
const { id, name } = await resolveAgentId(agentInput, client);
|
|
5082
5632
|
let taskDescription = opts.task;
|
|
5083
5633
|
if (opts.inputFile) {
|
|
5084
|
-
const content =
|
|
5634
|
+
const content = readFileSync3(opts.inputFile, "utf-8");
|
|
5085
5635
|
taskDescription = `${taskDescription}
|
|
5086
5636
|
|
|
5087
5637
|
---
|
|
5088
5638
|
|
|
5089
5639
|
${content}`;
|
|
5090
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
|
+
}
|
|
5091
5653
|
const timeoutMs = parseInt(opts.timeout || "300", 10) * 1e3;
|
|
5092
5654
|
const abortController = new AbortController();
|
|
5093
5655
|
const timer = setTimeout(() => abortController.abort(), timeoutMs);
|
|
@@ -5100,13 +5662,20 @@ ${content}`;
|
|
|
5100
5662
|
json: opts.json,
|
|
5101
5663
|
outputFile: opts.outputFile,
|
|
5102
5664
|
signal: abortController.signal,
|
|
5103
|
-
withFiles: opts.withFiles
|
|
5665
|
+
withFiles: opts.withFiles,
|
|
5666
|
+
uploadOffer
|
|
5104
5667
|
};
|
|
5105
5668
|
let result;
|
|
5106
5669
|
if (opts.stream) {
|
|
5670
|
+
if (uploadOffer && uploadZipBuffer) {
|
|
5671
|
+
void webrtcUpload(id, uploadOffer, uploadZipBuffer, token, opts.json);
|
|
5672
|
+
}
|
|
5107
5673
|
result = await streamCall(callOpts);
|
|
5108
5674
|
} else {
|
|
5109
5675
|
result = await asyncCall(callOpts);
|
|
5676
|
+
if (uploadOffer && uploadZipBuffer) {
|
|
5677
|
+
await webrtcUpload(id, uploadOffer, uploadZipBuffer, token, opts.json);
|
|
5678
|
+
}
|
|
5110
5679
|
}
|
|
5111
5680
|
clearTimeout(timer);
|
|
5112
5681
|
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.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",
|