@annals/agent-mesh 0.17.5 → 0.17.7
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.
- package/dist/index.js +226 -86
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -290,6 +290,7 @@ import { existsSync, copyFileSync, mkdirSync } from "fs";
|
|
|
290
290
|
import { join, dirname } from "path";
|
|
291
291
|
var ICE_SERVERS = ["stun:stun.l.google.com:19302"];
|
|
292
292
|
var CHUNK_SIZE = 64 * 1024;
|
|
293
|
+
var CONNECT_TIMEOUT_MS = 1e4;
|
|
293
294
|
var ndcModule;
|
|
294
295
|
function ensurePrebuilt() {
|
|
295
296
|
try {
|
|
@@ -410,6 +411,126 @@ var FileSender = class {
|
|
|
410
411
|
this.peer = null;
|
|
411
412
|
}
|
|
412
413
|
};
|
|
414
|
+
var FileReceiver = class {
|
|
415
|
+
peer = null;
|
|
416
|
+
dc = null;
|
|
417
|
+
expectedSize;
|
|
418
|
+
expectedSha256;
|
|
419
|
+
chunks = [];
|
|
420
|
+
receivedBytes = 0;
|
|
421
|
+
resolveComplete = null;
|
|
422
|
+
rejectComplete = null;
|
|
423
|
+
signalCallback = null;
|
|
424
|
+
pendingCandidates = [];
|
|
425
|
+
closed = false;
|
|
426
|
+
constructor(expectedSize, expectedSha256) {
|
|
427
|
+
this.expectedSize = expectedSize;
|
|
428
|
+
this.expectedSha256 = expectedSha256;
|
|
429
|
+
}
|
|
430
|
+
onSignal(cb) {
|
|
431
|
+
this.signalCallback = cb;
|
|
432
|
+
}
|
|
433
|
+
async createOffer() {
|
|
434
|
+
const ndc = await loadNdc();
|
|
435
|
+
if (!ndc) return null;
|
|
436
|
+
this.peer = new ndc.PeerConnection("receiver", {
|
|
437
|
+
iceServers: ICE_SERVERS
|
|
438
|
+
});
|
|
439
|
+
return new Promise((resolve2) => {
|
|
440
|
+
this.peer.onLocalDescription((sdp, type) => {
|
|
441
|
+
if (type === "offer") {
|
|
442
|
+
resolve2(sdp);
|
|
443
|
+
}
|
|
444
|
+
this.signalCallback?.({ signal_type: type, payload: sdp });
|
|
445
|
+
});
|
|
446
|
+
this.peer.onLocalCandidate((candidate, mid) => {
|
|
447
|
+
this.signalCallback?.({
|
|
448
|
+
signal_type: "candidate",
|
|
449
|
+
payload: JSON.stringify({ candidate, mid })
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
this.dc = this.peer.createDataChannel("file-transfer");
|
|
453
|
+
this.dc.onMessage((msg) => {
|
|
454
|
+
if (typeof msg === "string") {
|
|
455
|
+
try {
|
|
456
|
+
const ctrl = JSON.parse(msg);
|
|
457
|
+
if (ctrl.type === "complete") {
|
|
458
|
+
this.finalizeReceive();
|
|
459
|
+
}
|
|
460
|
+
} catch {
|
|
461
|
+
}
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
const buf = Buffer.isBuffer(msg) ? msg : Buffer.from(msg);
|
|
465
|
+
this.chunks.push(buf);
|
|
466
|
+
this.receivedBytes += buf.length;
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
async handleSignal(signal) {
|
|
471
|
+
if (!this.peer || this.closed) return;
|
|
472
|
+
if (signal.signal_type === "answer" || signal.signal_type === "offer") {
|
|
473
|
+
this.peer.setRemoteDescription(signal.payload, signal.signal_type);
|
|
474
|
+
for (const c of this.pendingCandidates) {
|
|
475
|
+
this.peer.addRemoteCandidate(c.candidate, c.mid);
|
|
476
|
+
}
|
|
477
|
+
this.pendingCandidates = [];
|
|
478
|
+
} else if (signal.signal_type === "candidate") {
|
|
479
|
+
const { candidate, mid } = JSON.parse(signal.payload);
|
|
480
|
+
if (this.peer.remoteDescription()) {
|
|
481
|
+
this.peer.addRemoteCandidate(candidate, mid);
|
|
482
|
+
} else {
|
|
483
|
+
this.pendingCandidates.push({ candidate, mid });
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
waitForCompletion(timeoutMs = CONNECT_TIMEOUT_MS) {
|
|
488
|
+
return new Promise((resolve2, reject) => {
|
|
489
|
+
this.resolveComplete = resolve2;
|
|
490
|
+
this.rejectComplete = reject;
|
|
491
|
+
setTimeout(() => {
|
|
492
|
+
if (!this.closed) {
|
|
493
|
+
reject(new Error(`WebRTC file transfer timed out after ${timeoutMs}ms`));
|
|
494
|
+
this.close();
|
|
495
|
+
}
|
|
496
|
+
}, timeoutMs);
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
finalizeReceive() {
|
|
500
|
+
const zipBuffer = Buffer.concat(this.chunks);
|
|
501
|
+
const actualSha256 = createHash("sha256").update(zipBuffer).digest("hex");
|
|
502
|
+
if (zipBuffer.length !== this.expectedSize) {
|
|
503
|
+
this.rejectComplete?.(
|
|
504
|
+
new Error(`Size mismatch: expected ${this.expectedSize}, got ${zipBuffer.length}`)
|
|
505
|
+
);
|
|
506
|
+
this.close();
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
if (actualSha256 !== this.expectedSha256) {
|
|
510
|
+
this.rejectComplete?.(
|
|
511
|
+
new Error(`SHA-256 mismatch: expected ${this.expectedSha256}, got ${actualSha256}`)
|
|
512
|
+
);
|
|
513
|
+
this.close();
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
log.info(`[WebRTC] Received ${zipBuffer.length} bytes, SHA-256 verified`);
|
|
517
|
+
this.resolveComplete?.(zipBuffer);
|
|
518
|
+
this.close();
|
|
519
|
+
}
|
|
520
|
+
close() {
|
|
521
|
+
this.closed = true;
|
|
522
|
+
try {
|
|
523
|
+
this.dc?.close();
|
|
524
|
+
} catch {
|
|
525
|
+
}
|
|
526
|
+
try {
|
|
527
|
+
this.peer?.close();
|
|
528
|
+
} catch {
|
|
529
|
+
}
|
|
530
|
+
this.peer = null;
|
|
531
|
+
this.dc = null;
|
|
532
|
+
}
|
|
533
|
+
};
|
|
413
534
|
function sha256Hex(data) {
|
|
414
535
|
return createHash("sha256").update(data).digest("hex");
|
|
415
536
|
}
|
|
@@ -1045,9 +1166,6 @@ var BridgeManager = class {
|
|
|
1045
1166
|
const attachments = payload?.attachments;
|
|
1046
1167
|
const fileTransferOffer = payload?.fileTransferOffer;
|
|
1047
1168
|
const zipBuffer = payload?.zipBuffer;
|
|
1048
|
-
if (fileTransferOffer && zipBuffer) {
|
|
1049
|
-
this.registerPendingTransfer(fileTransferOffer, zipBuffer);
|
|
1050
|
-
}
|
|
1051
1169
|
const done = {
|
|
1052
1170
|
type: "done",
|
|
1053
1171
|
session_id: sessionId,
|
|
@@ -1060,6 +1178,9 @@ var BridgeManager = class {
|
|
|
1060
1178
|
this.wsClient.send(done);
|
|
1061
1179
|
fullResponseBuffer = "";
|
|
1062
1180
|
this.sessionLastSeenAt.set(sessionId, Date.now());
|
|
1181
|
+
if (fileTransferOffer && zipBuffer) {
|
|
1182
|
+
this.registerPendingTransfer(fileTransferOffer, zipBuffer);
|
|
1183
|
+
}
|
|
1063
1184
|
const fileInfo = fileTransferOffer ? ` (${fileTransferOffer.file_count} files, transfer=${fileTransferOffer.transfer_id.slice(0, 8)}...)` : "";
|
|
1064
1185
|
log.info(`Request done: session=${sessionId.slice(0, 8)}... request=${requestRef.requestId.slice(0, 8)}...${fileInfo}`);
|
|
1065
1186
|
});
|
|
@@ -1265,32 +1386,7 @@ var BridgeManager = class {
|
|
|
1265
1386
|
}, 5 * 6e4);
|
|
1266
1387
|
timer.unref?.();
|
|
1267
1388
|
this.pendingTransfers.set(offer.transfer_id, { sender, timer });
|
|
1268
|
-
|
|
1269
|
-
}
|
|
1270
|
-
/** Push ZIP buffer to Worker DO via chunked transfer_upload messages */
|
|
1271
|
-
pushZipToWorker(transferId, zipBuffer) {
|
|
1272
|
-
const CHUNK_SIZE2 = 64 * 1024;
|
|
1273
|
-
const totalChunks = Math.ceil(zipBuffer.length / CHUNK_SIZE2);
|
|
1274
|
-
log.debug(`Pushing ZIP to Worker: transfer=${transferId.slice(0, 8)}... size=${zipBuffer.length} chunks=${totalChunks}`);
|
|
1275
|
-
for (let i = 0; i < totalChunks; i++) {
|
|
1276
|
-
const start = i * CHUNK_SIZE2;
|
|
1277
|
-
const end = Math.min(start + CHUNK_SIZE2, zipBuffer.length);
|
|
1278
|
-
const chunk = zipBuffer.subarray(start, end);
|
|
1279
|
-
const msg = {
|
|
1280
|
-
type: "transfer_upload",
|
|
1281
|
-
transfer_id: transferId,
|
|
1282
|
-
chunk_index: i,
|
|
1283
|
-
total_chunks: totalChunks,
|
|
1284
|
-
data: chunk.toString("base64")
|
|
1285
|
-
};
|
|
1286
|
-
this.wsClient.send(msg);
|
|
1287
|
-
}
|
|
1288
|
-
const complete = {
|
|
1289
|
-
type: "transfer_upload_complete",
|
|
1290
|
-
transfer_id: transferId
|
|
1291
|
-
};
|
|
1292
|
-
this.wsClient.send(complete);
|
|
1293
|
-
log.info(`ZIP pushed to Worker: transfer=${transferId.slice(0, 8)}... (${totalChunks} chunks)`);
|
|
1389
|
+
log.info(`WebRTC transfer registered: transfer=${offer.transfer_id.slice(0, 8)}... (${(zipBuffer.length / 1024).toFixed(1)} KB in memory)`);
|
|
1294
1390
|
}
|
|
1295
1391
|
handleRtcSignalRelay(msg) {
|
|
1296
1392
|
const entry = this.pendingTransfers.get(msg.transfer_id);
|
|
@@ -1666,8 +1762,18 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
1666
1762
|
]);
|
|
1667
1763
|
async function collectRealFiles(dir, maxFiles = Infinity) {
|
|
1668
1764
|
const files = [];
|
|
1765
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1669
1766
|
const walk = async (d) => {
|
|
1670
1767
|
if (files.length >= maxFiles) return;
|
|
1768
|
+
let realDir;
|
|
1769
|
+
try {
|
|
1770
|
+
const { realpath } = await import("fs/promises");
|
|
1771
|
+
realDir = await realpath(d);
|
|
1772
|
+
} catch {
|
|
1773
|
+
return;
|
|
1774
|
+
}
|
|
1775
|
+
if (visited.has(realDir)) return;
|
|
1776
|
+
visited.add(realDir);
|
|
1671
1777
|
let entries;
|
|
1672
1778
|
try {
|
|
1673
1779
|
entries = await readdir(d, { withFileTypes: true });
|
|
@@ -1676,11 +1782,19 @@ async function collectRealFiles(dir, maxFiles = Infinity) {
|
|
|
1676
1782
|
}
|
|
1677
1783
|
for (const entry of entries) {
|
|
1678
1784
|
if (files.length >= maxFiles) return;
|
|
1679
|
-
if (entry.isSymbolicLink()) continue;
|
|
1680
1785
|
const fullPath = join5(d, entry.name);
|
|
1681
1786
|
if (entry.isDirectory()) {
|
|
1682
1787
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
1683
1788
|
await walk(fullPath);
|
|
1789
|
+
} else if (entry.isSymbolicLink()) {
|
|
1790
|
+
try {
|
|
1791
|
+
const s = await stat(fullPath);
|
|
1792
|
+
if (s.isDirectory()) {
|
|
1793
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
1794
|
+
await walk(fullPath);
|
|
1795
|
+
}
|
|
1796
|
+
} catch {
|
|
1797
|
+
}
|
|
1684
1798
|
} else if (entry.isFile()) {
|
|
1685
1799
|
files.push(fullPath);
|
|
1686
1800
|
}
|
|
@@ -4543,7 +4657,6 @@ function registerDiscoverCommand(program2) {
|
|
|
4543
4657
|
|
|
4544
4658
|
// src/commands/call.ts
|
|
4545
4659
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync3, mkdirSync as mkdirSync5 } from "fs";
|
|
4546
|
-
import { createHash as createHash2 } from "crypto";
|
|
4547
4660
|
import { execSync as execSync4 } from "child_process";
|
|
4548
4661
|
import { join as join11 } from "path";
|
|
4549
4662
|
var DEFAULT_BASE_URL4 = "https://agents.hot";
|
|
@@ -4577,74 +4690,101 @@ function handleError2(err) {
|
|
|
4577
4690
|
}
|
|
4578
4691
|
process.exit(1);
|
|
4579
4692
|
}
|
|
4580
|
-
async function
|
|
4581
|
-
const url = `${DEFAULT_BASE_URL4}/api/agents/${agentId}/transfer/${offer.transfer_id}`;
|
|
4693
|
+
async function webrtcDownload(agentId, offer, token, outputDir, json) {
|
|
4582
4694
|
if (!json) {
|
|
4583
|
-
log.info(`Downloading ${offer.file_count} file(s) (${(offer.zip_size / 1024).toFixed(1)} KB)...`);
|
|
4695
|
+
log.info(`[WebRTC] Downloading ${offer.file_count} file(s) (${(offer.zip_size / 1024).toFixed(1)} KB)...`);
|
|
4584
4696
|
}
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
for (let attempt = 0; attempt < 5; attempt++) {
|
|
4697
|
+
const receiver = new FileReceiver(offer.zip_size, offer.zip_sha256);
|
|
4698
|
+
const exchangeSignals = async (signal) => {
|
|
4588
4699
|
try {
|
|
4589
|
-
const res = await fetch(
|
|
4590
|
-
|
|
4700
|
+
const res = await fetch(`${DEFAULT_BASE_URL4}/api/agents/${agentId}/rtc-signal`, {
|
|
4701
|
+
method: "POST",
|
|
4702
|
+
headers: {
|
|
4703
|
+
Authorization: `Bearer ${token}`,
|
|
4704
|
+
"Content-Type": "application/json"
|
|
4705
|
+
},
|
|
4706
|
+
body: JSON.stringify({
|
|
4707
|
+
transfer_id: offer.transfer_id,
|
|
4708
|
+
signal_type: signal.signal_type,
|
|
4709
|
+
payload: signal.payload
|
|
4710
|
+
})
|
|
4591
4711
|
});
|
|
4592
|
-
if (res.
|
|
4593
|
-
await
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
if (res.status === 404) {
|
|
4597
|
-
if (attempt < 4) {
|
|
4598
|
-
await sleep6(2e3);
|
|
4599
|
-
continue;
|
|
4712
|
+
if (res.ok) {
|
|
4713
|
+
const { signals } = await res.json();
|
|
4714
|
+
for (const s of signals) {
|
|
4715
|
+
await receiver.handleSignal(s);
|
|
4600
4716
|
}
|
|
4601
|
-
log.warn("Transfer expired or not found");
|
|
4602
|
-
return;
|
|
4603
4717
|
}
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
log.warn(`SHA-256 mismatch: expected ${offer.zip_sha256.slice(0, 12)}... got ${hash.slice(0, 12)}...`);
|
|
4613
|
-
return;
|
|
4614
|
-
}
|
|
4615
|
-
mkdirSync5(outputDir, { recursive: true });
|
|
4616
|
-
const zipPath = join11(outputDir, `.transfer-${offer.transfer_id.slice(0, 8)}.zip`);
|
|
4617
|
-
writeFileSync3(zipPath, zipBuffer);
|
|
4718
|
+
} catch {
|
|
4719
|
+
}
|
|
4720
|
+
};
|
|
4721
|
+
receiver.onSignal(exchangeSignals);
|
|
4722
|
+
await receiver.createOffer();
|
|
4723
|
+
const poll = async () => {
|
|
4724
|
+
for (let i = 0; i < 20; i++) {
|
|
4725
|
+
await sleep6(500);
|
|
4618
4726
|
try {
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4727
|
+
const res = await fetch(`${DEFAULT_BASE_URL4}/api/agents/${agentId}/rtc-signal`, {
|
|
4728
|
+
method: "POST",
|
|
4729
|
+
headers: {
|
|
4730
|
+
Authorization: `Bearer ${token}`,
|
|
4731
|
+
"Content-Type": "application/json"
|
|
4732
|
+
},
|
|
4733
|
+
body: JSON.stringify({
|
|
4734
|
+
transfer_id: offer.transfer_id,
|
|
4735
|
+
signal_type: "poll",
|
|
4736
|
+
payload: ""
|
|
4737
|
+
})
|
|
4738
|
+
});
|
|
4739
|
+
if (res.ok) {
|
|
4740
|
+
const { signals } = await res.json();
|
|
4741
|
+
for (const s of signals) {
|
|
4742
|
+
await receiver.handleSignal(s);
|
|
4743
|
+
}
|
|
4623
4744
|
}
|
|
4624
4745
|
} catch {
|
|
4625
|
-
log.warn(`Failed to extract ZIP. Saved to: ${zipPath}`);
|
|
4626
|
-
return;
|
|
4627
4746
|
}
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4747
|
+
}
|
|
4748
|
+
};
|
|
4749
|
+
try {
|
|
4750
|
+
const [zipBuffer] = await Promise.all([
|
|
4751
|
+
receiver.waitForCompletion(3e4),
|
|
4752
|
+
poll()
|
|
4753
|
+
]);
|
|
4754
|
+
mkdirSync5(outputDir, { recursive: true });
|
|
4755
|
+
const zipPath = join11(outputDir, ".transfer.zip");
|
|
4756
|
+
writeFileSync3(zipPath, zipBuffer);
|
|
4757
|
+
try {
|
|
4758
|
+
execSync4(`unzip -o -q "${zipPath}" -d "${outputDir}"`);
|
|
4759
|
+
try {
|
|
4760
|
+
execSync4(`rm "${zipPath}"`);
|
|
4761
|
+
} catch {
|
|
4638
4762
|
}
|
|
4763
|
+
} catch {
|
|
4764
|
+
log.warn(`Failed to extract ZIP. Saved to: ${zipPath}`);
|
|
4639
4765
|
return;
|
|
4640
|
-
} catch (err) {
|
|
4641
|
-
lastError = err;
|
|
4642
|
-
if (attempt < 4) {
|
|
4643
|
-
await sleep6(2e3);
|
|
4644
|
-
}
|
|
4645
4766
|
}
|
|
4767
|
+
if (json) {
|
|
4768
|
+
console.log(JSON.stringify({
|
|
4769
|
+
type: "files_downloaded",
|
|
4770
|
+
file_count: offer.file_count,
|
|
4771
|
+
output_dir: outputDir,
|
|
4772
|
+
zip_size: zipBuffer.length,
|
|
4773
|
+
sha256_verified: true
|
|
4774
|
+
}));
|
|
4775
|
+
} else {
|
|
4776
|
+
log.success(`[WebRTC] ${offer.file_count} file(s) extracted to ${outputDir}`);
|
|
4777
|
+
}
|
|
4778
|
+
} catch (err) {
|
|
4779
|
+
const msg = err.message;
|
|
4780
|
+
if (json) {
|
|
4781
|
+
console.log(JSON.stringify({ type: "file_transfer_failed", error: msg }));
|
|
4782
|
+
} else {
|
|
4783
|
+
log.warn(`[WebRTC] File transfer failed: ${msg}`);
|
|
4784
|
+
}
|
|
4785
|
+
} finally {
|
|
4786
|
+
receiver.close();
|
|
4646
4787
|
}
|
|
4647
|
-
log.warn(`Download failed after retries: ${lastError?.message || "unknown error"}`);
|
|
4648
4788
|
}
|
|
4649
4789
|
async function asyncCall(opts) {
|
|
4650
4790
|
const selfAgentId = process.env.AGENT_BRIDGE_AGENT_ID;
|
|
@@ -4739,7 +4879,7 @@ async function asyncCall(opts) {
|
|
|
4739
4879
|
}
|
|
4740
4880
|
if (offer && opts.withFiles) {
|
|
4741
4881
|
const outputDir = opts.outputFile ? join11(opts.outputFile, "..", "files") : join11(process.cwd(), "agent-output");
|
|
4742
|
-
await
|
|
4882
|
+
await webrtcDownload(opts.id, offer, opts.token, outputDir, opts.json);
|
|
4743
4883
|
}
|
|
4744
4884
|
if (!opts.json) {
|
|
4745
4885
|
log.info(`${GRAY}Rate this call: agent-mesh rate ${call_id} <1-5> --agent ${opts.id}${RESET}`);
|