@annals/agent-mesh 0.17.6 → 0.17.8
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 +208 -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);
|
|
@@ -4561,7 +4657,6 @@ function registerDiscoverCommand(program2) {
|
|
|
4561
4657
|
|
|
4562
4658
|
// src/commands/call.ts
|
|
4563
4659
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync3, mkdirSync as mkdirSync5 } from "fs";
|
|
4564
|
-
import { createHash as createHash2 } from "crypto";
|
|
4565
4660
|
import { execSync as execSync4 } from "child_process";
|
|
4566
4661
|
import { join as join11 } from "path";
|
|
4567
4662
|
var DEFAULT_BASE_URL4 = "https://agents.hot";
|
|
@@ -4595,74 +4690,101 @@ function handleError2(err) {
|
|
|
4595
4690
|
}
|
|
4596
4691
|
process.exit(1);
|
|
4597
4692
|
}
|
|
4598
|
-
async function
|
|
4599
|
-
const url = `${DEFAULT_BASE_URL4}/api/agents/${agentId}/transfer/${offer.transfer_id}`;
|
|
4693
|
+
async function webrtcDownload(agentId, offer, token, outputDir, json) {
|
|
4600
4694
|
if (!json) {
|
|
4601
|
-
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)...`);
|
|
4602
4696
|
}
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
for (let attempt = 0; attempt < 5; attempt++) {
|
|
4697
|
+
const receiver = new FileReceiver(offer.zip_size, offer.zip_sha256);
|
|
4698
|
+
const exchangeSignals = async (signal) => {
|
|
4606
4699
|
try {
|
|
4607
|
-
const res = await fetch(
|
|
4608
|
-
|
|
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
|
+
})
|
|
4609
4711
|
});
|
|
4610
|
-
if (res.
|
|
4611
|
-
await
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
if (res.status === 404) {
|
|
4615
|
-
if (attempt < 4) {
|
|
4616
|
-
await sleep6(2e3);
|
|
4617
|
-
continue;
|
|
4712
|
+
if (res.ok) {
|
|
4713
|
+
const { signals } = await res.json();
|
|
4714
|
+
for (const s of signals) {
|
|
4715
|
+
await receiver.handleSignal(s);
|
|
4618
4716
|
}
|
|
4619
|
-
log.warn("Transfer expired or not found");
|
|
4620
|
-
return;
|
|
4621
|
-
}
|
|
4622
|
-
if (!res.ok) {
|
|
4623
|
-
log.warn(`Download failed: HTTP ${res.status}`);
|
|
4624
|
-
return;
|
|
4625
|
-
}
|
|
4626
|
-
const arrayBuffer = await res.arrayBuffer();
|
|
4627
|
-
const zipBuffer = Buffer.from(arrayBuffer);
|
|
4628
|
-
const hash = createHash2("sha256").update(zipBuffer).digest("hex");
|
|
4629
|
-
if (hash !== offer.zip_sha256) {
|
|
4630
|
-
log.warn(`SHA-256 mismatch: expected ${offer.zip_sha256.slice(0, 12)}... got ${hash.slice(0, 12)}...`);
|
|
4631
|
-
return;
|
|
4632
4717
|
}
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
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);
|
|
4636
4726
|
try {
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
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
|
+
}
|
|
4641
4744
|
}
|
|
4642
4745
|
} catch {
|
|
4643
|
-
log.warn(`Failed to extract ZIP. Saved to: ${zipPath}`);
|
|
4644
|
-
return;
|
|
4645
4746
|
}
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
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 {
|
|
4656
4762
|
}
|
|
4763
|
+
} catch {
|
|
4764
|
+
log.warn(`Failed to extract ZIP. Saved to: ${zipPath}`);
|
|
4657
4765
|
return;
|
|
4658
|
-
} catch (err) {
|
|
4659
|
-
lastError = err;
|
|
4660
|
-
if (attempt < 4) {
|
|
4661
|
-
await sleep6(2e3);
|
|
4662
|
-
}
|
|
4663
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();
|
|
4664
4787
|
}
|
|
4665
|
-
log.warn(`Download failed after retries: ${lastError?.message || "unknown error"}`);
|
|
4666
4788
|
}
|
|
4667
4789
|
async function asyncCall(opts) {
|
|
4668
4790
|
const selfAgentId = process.env.AGENT_BRIDGE_AGENT_ID;
|
|
@@ -4757,7 +4879,7 @@ async function asyncCall(opts) {
|
|
|
4757
4879
|
}
|
|
4758
4880
|
if (offer && opts.withFiles) {
|
|
4759
4881
|
const outputDir = opts.outputFile ? join11(opts.outputFile, "..", "files") : join11(process.cwd(), "agent-output");
|
|
4760
|
-
await
|
|
4882
|
+
await webrtcDownload(opts.id, offer, opts.token, outputDir, opts.json);
|
|
4761
4883
|
}
|
|
4762
4884
|
if (!opts.json) {
|
|
4763
4885
|
log.info(`${GRAY}Rate this call: agent-mesh rate ${call_id} <1-5> --agent ${opts.id}${RESET}`);
|
|
@@ -4931,7 +5053,7 @@ Error: ${event.message}
|
|
|
4931
5053
|
if (fileOffer && opts.withFiles) {
|
|
4932
5054
|
console.log("");
|
|
4933
5055
|
const outputDir = opts.outputFile ? join11(opts.outputFile, "..", "files") : join11(process.cwd(), "agent-output");
|
|
4934
|
-
await
|
|
5056
|
+
await webrtcDownload(opts.id, fileOffer, opts.token, outputDir, opts.json);
|
|
4935
5057
|
}
|
|
4936
5058
|
if (!opts.json) {
|
|
4937
5059
|
console.log("\n");
|