@annals/agent-mesh 0.16.13 → 0.17.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.
- package/dist/index.js +254 -304
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -269,6 +269,106 @@ var BridgeWSClient = class extends EventEmitter {
|
|
|
269
269
|
// src/bridge/manager.ts
|
|
270
270
|
import { BridgeErrorCode } from "@annals/bridge-protocol";
|
|
271
271
|
|
|
272
|
+
// src/utils/webrtc-transfer.ts
|
|
273
|
+
import { createHash } from "crypto";
|
|
274
|
+
var ICE_SERVERS = ["stun:stun.l.google.com:19302"];
|
|
275
|
+
var CHUNK_SIZE = 64 * 1024;
|
|
276
|
+
var ndcModule;
|
|
277
|
+
async function loadNdc() {
|
|
278
|
+
if (ndcModule !== void 0) return ndcModule;
|
|
279
|
+
try {
|
|
280
|
+
ndcModule = await import("node-datachannel");
|
|
281
|
+
return ndcModule;
|
|
282
|
+
} catch {
|
|
283
|
+
log.warn("node-datachannel not available \u2014 WebRTC file transfer disabled");
|
|
284
|
+
ndcModule = null;
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
var FileSender = class {
|
|
289
|
+
peer = null;
|
|
290
|
+
transferId;
|
|
291
|
+
zipBuffer;
|
|
292
|
+
pendingCandidates = [];
|
|
293
|
+
signalCallback = null;
|
|
294
|
+
closed = false;
|
|
295
|
+
constructor(transferId, zipBuffer) {
|
|
296
|
+
this.transferId = transferId;
|
|
297
|
+
this.zipBuffer = zipBuffer;
|
|
298
|
+
}
|
|
299
|
+
onSignal(cb) {
|
|
300
|
+
this.signalCallback = cb;
|
|
301
|
+
}
|
|
302
|
+
async handleSignal(signal) {
|
|
303
|
+
const ndc = await loadNdc();
|
|
304
|
+
if (!ndc || this.closed) return;
|
|
305
|
+
if (!this.peer) {
|
|
306
|
+
this.peer = new ndc.PeerConnection(`sender-${this.transferId}`, {
|
|
307
|
+
iceServers: ICE_SERVERS
|
|
308
|
+
});
|
|
309
|
+
this.peer.onLocalDescription((sdp, type) => {
|
|
310
|
+
this.signalCallback?.({ signal_type: type, payload: sdp });
|
|
311
|
+
});
|
|
312
|
+
this.peer.onLocalCandidate((candidate, mid) => {
|
|
313
|
+
this.signalCallback?.({
|
|
314
|
+
signal_type: "candidate",
|
|
315
|
+
payload: JSON.stringify({ candidate, mid })
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
this.peer.onDataChannel((dc) => {
|
|
319
|
+
dc.onOpen(() => {
|
|
320
|
+
void this.sendZip(dc);
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
if (signal.signal_type === "offer" || signal.signal_type === "answer") {
|
|
325
|
+
this.peer.setRemoteDescription(signal.payload, signal.signal_type);
|
|
326
|
+
for (const c of this.pendingCandidates) {
|
|
327
|
+
this.peer.addRemoteCandidate(c.candidate, c.mid);
|
|
328
|
+
}
|
|
329
|
+
this.pendingCandidates = [];
|
|
330
|
+
} else if (signal.signal_type === "candidate") {
|
|
331
|
+
const { candidate, mid } = JSON.parse(signal.payload);
|
|
332
|
+
if (this.peer.remoteDescription()) {
|
|
333
|
+
this.peer.addRemoteCandidate(candidate, mid);
|
|
334
|
+
} else {
|
|
335
|
+
this.pendingCandidates.push({ candidate, mid });
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
async sendZip(dc) {
|
|
340
|
+
try {
|
|
341
|
+
dc.sendMessage(JSON.stringify({
|
|
342
|
+
type: "header",
|
|
343
|
+
transfer_id: this.transferId,
|
|
344
|
+
zip_size: this.zipBuffer.length
|
|
345
|
+
}));
|
|
346
|
+
let offset = 0;
|
|
347
|
+
while (offset < this.zipBuffer.length) {
|
|
348
|
+
const end = Math.min(offset + CHUNK_SIZE, this.zipBuffer.length);
|
|
349
|
+
const chunk = this.zipBuffer.subarray(offset, end);
|
|
350
|
+
dc.sendMessageBinary(chunk);
|
|
351
|
+
offset = end;
|
|
352
|
+
}
|
|
353
|
+
dc.sendMessage(JSON.stringify({ type: "complete" }));
|
|
354
|
+
log.info(`[WebRTC] Sent ${this.zipBuffer.length} bytes in ${Math.ceil(this.zipBuffer.length / CHUNK_SIZE)} chunks`);
|
|
355
|
+
} catch (err) {
|
|
356
|
+
log.error(`[WebRTC] Send failed: ${err}`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
close() {
|
|
360
|
+
this.closed = true;
|
|
361
|
+
try {
|
|
362
|
+
this.peer?.close();
|
|
363
|
+
} catch {
|
|
364
|
+
}
|
|
365
|
+
this.peer = null;
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
function sha256Hex(data) {
|
|
369
|
+
return createHash("sha256").update(data).digest("hex");
|
|
370
|
+
}
|
|
371
|
+
|
|
272
372
|
// src/bridge/session-pool.ts
|
|
273
373
|
var SessionPool = class {
|
|
274
374
|
sessions = /* @__PURE__ */ new Map();
|
|
@@ -688,6 +788,8 @@ var BridgeManager = class {
|
|
|
688
788
|
requestDispatches = /* @__PURE__ */ new Map();
|
|
689
789
|
cleanupTimer = null;
|
|
690
790
|
runtimeQueue;
|
|
791
|
+
/** Pending WebRTC file transfers: transfer_id → FileSender + cleanup timer */
|
|
792
|
+
pendingTransfers = /* @__PURE__ */ new Map();
|
|
691
793
|
constructor(opts) {
|
|
692
794
|
this.wsClient = opts.wsClient;
|
|
693
795
|
this.adapter = opts.adapter;
|
|
@@ -715,6 +817,7 @@ var BridgeManager = class {
|
|
|
715
817
|
this.wiredSessions.clear();
|
|
716
818
|
this.sessionLastSeenAt.clear();
|
|
717
819
|
this.cleanupRequestDispatches("shutdown");
|
|
820
|
+
this.cleanupPendingTransfers();
|
|
718
821
|
log.info("Bridge manager stopped");
|
|
719
822
|
}
|
|
720
823
|
/**
|
|
@@ -746,6 +849,9 @@ var BridgeManager = class {
|
|
|
746
849
|
break;
|
|
747
850
|
case "registered":
|
|
748
851
|
break;
|
|
852
|
+
case "rtc_signal_relay":
|
|
853
|
+
this.handleRtcSignalRelay(msg);
|
|
854
|
+
break;
|
|
749
855
|
default:
|
|
750
856
|
log.warn(`Unknown message type from worker: ${msg.type}`);
|
|
751
857
|
}
|
|
@@ -811,7 +917,7 @@ var BridgeManager = class {
|
|
|
811
917
|
}
|
|
812
918
|
async dispatchWithLocalQueue(opts) {
|
|
813
919
|
const { msg, handle, requestKey } = opts;
|
|
814
|
-
const { session_id, request_id, content, attachments,
|
|
920
|
+
const { session_id, request_id, content, attachments, client_id, with_files } = msg;
|
|
815
921
|
const state = this.requestDispatches.get(requestKey);
|
|
816
922
|
if (!state) return;
|
|
817
923
|
try {
|
|
@@ -830,9 +936,8 @@ var BridgeManager = class {
|
|
|
830
936
|
await this.releaseRequestLease(session_id, request_id, "cancel");
|
|
831
937
|
return;
|
|
832
938
|
}
|
|
833
|
-
const uploadCredentials = upload_url && upload_token ? { uploadUrl: upload_url, uploadToken: upload_token } : void 0;
|
|
834
939
|
try {
|
|
835
|
-
handle.send(content, attachments,
|
|
940
|
+
handle.send(content, attachments, client_id, with_files);
|
|
836
941
|
this.sessionLastSeenAt.set(session_id, Date.now());
|
|
837
942
|
} catch (err) {
|
|
838
943
|
log.error(`Failed to send to adapter: ${err}`);
|
|
@@ -893,20 +998,24 @@ var BridgeManager = class {
|
|
|
893
998
|
handle.onDone((payload) => {
|
|
894
999
|
void this.releaseRequestLease(sessionId, requestRef.requestId, "done");
|
|
895
1000
|
const attachments = payload?.attachments;
|
|
896
|
-
const
|
|
1001
|
+
const fileTransferOffer = payload?.fileTransferOffer;
|
|
1002
|
+
const zipBuffer = payload?.zipBuffer;
|
|
1003
|
+
if (fileTransferOffer && zipBuffer) {
|
|
1004
|
+
this.registerPendingTransfer(fileTransferOffer, zipBuffer);
|
|
1005
|
+
}
|
|
897
1006
|
const done = {
|
|
898
1007
|
type: "done",
|
|
899
1008
|
session_id: sessionId,
|
|
900
1009
|
request_id: requestRef.requestId,
|
|
901
1010
|
...attachments && attachments.length > 0 && { attachments },
|
|
902
|
-
...
|
|
1011
|
+
...fileTransferOffer && { file_transfer_offer: fileTransferOffer },
|
|
903
1012
|
...fullResponseBuffer && { result: fullResponseBuffer }
|
|
904
1013
|
};
|
|
905
1014
|
this.trackRequest(sessionId, requestRef.requestId, "done");
|
|
906
1015
|
this.wsClient.send(done);
|
|
907
1016
|
fullResponseBuffer = "";
|
|
908
1017
|
this.sessionLastSeenAt.set(sessionId, Date.now());
|
|
909
|
-
const fileInfo =
|
|
1018
|
+
const fileInfo = fileTransferOffer ? ` (${fileTransferOffer.file_count} files, transfer=${fileTransferOffer.transfer_id.slice(0, 8)}...)` : "";
|
|
910
1019
|
log.info(`Request done: session=${sessionId.slice(0, 8)}... request=${requestRef.requestId.slice(0, 8)}...${fileInfo}`);
|
|
911
1020
|
});
|
|
912
1021
|
handle.onError((err) => {
|
|
@@ -1087,6 +1196,50 @@ var BridgeManager = class {
|
|
|
1087
1196
|
}
|
|
1088
1197
|
}
|
|
1089
1198
|
}
|
|
1199
|
+
// ========================================================
|
|
1200
|
+
// WebRTC signaling relay
|
|
1201
|
+
// ========================================================
|
|
1202
|
+
registerPendingTransfer(offer, zipBuffer) {
|
|
1203
|
+
const sender = new FileSender(offer.transfer_id, zipBuffer);
|
|
1204
|
+
sender.onSignal((signal) => {
|
|
1205
|
+
const entry = this.pendingTransfers.get(offer.transfer_id);
|
|
1206
|
+
if (!entry) return;
|
|
1207
|
+
const rtcSignal = {
|
|
1208
|
+
type: "rtc_signal",
|
|
1209
|
+
transfer_id: offer.transfer_id,
|
|
1210
|
+
target_agent_id: entry.targetAgentId || "",
|
|
1211
|
+
signal_type: signal.signal_type,
|
|
1212
|
+
payload: signal.payload
|
|
1213
|
+
};
|
|
1214
|
+
this.wsClient.send(rtcSignal);
|
|
1215
|
+
});
|
|
1216
|
+
const timer = setTimeout(() => {
|
|
1217
|
+
sender.close();
|
|
1218
|
+
this.pendingTransfers.delete(offer.transfer_id);
|
|
1219
|
+
log.debug(`Transfer ${offer.transfer_id.slice(0, 8)}... expired`);
|
|
1220
|
+
}, 5 * 6e4);
|
|
1221
|
+
timer.unref?.();
|
|
1222
|
+
this.pendingTransfers.set(offer.transfer_id, { sender, timer });
|
|
1223
|
+
}
|
|
1224
|
+
handleRtcSignalRelay(msg) {
|
|
1225
|
+
const entry = this.pendingTransfers.get(msg.transfer_id);
|
|
1226
|
+
if (!entry) {
|
|
1227
|
+
log.debug(`No pending transfer for ${msg.transfer_id.slice(0, 8)}...`);
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
entry.targetAgentId = msg.from_agent_id;
|
|
1231
|
+
void entry.sender.handleSignal({
|
|
1232
|
+
signal_type: msg.signal_type,
|
|
1233
|
+
payload: msg.payload
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
cleanupPendingTransfers() {
|
|
1237
|
+
for (const [id, entry] of this.pendingTransfers) {
|
|
1238
|
+
clearTimeout(entry.timer);
|
|
1239
|
+
entry.sender.close();
|
|
1240
|
+
}
|
|
1241
|
+
this.pendingTransfers.clear();
|
|
1242
|
+
}
|
|
1090
1243
|
updateSessionCount() {
|
|
1091
1244
|
this.wsClient.setActiveSessions(this.pool.size);
|
|
1092
1245
|
}
|
|
@@ -1423,7 +1576,7 @@ function createClientWorkspace(projectPath, clientId) {
|
|
|
1423
1576
|
}
|
|
1424
1577
|
|
|
1425
1578
|
// src/adapters/claude.ts
|
|
1426
|
-
import {
|
|
1579
|
+
import { writeFile, mkdir, stat as stat2 } from "fs/promises";
|
|
1427
1580
|
import { join as join5, relative as relative3, basename } from "path";
|
|
1428
1581
|
|
|
1429
1582
|
// src/utils/auto-upload.ts
|
|
@@ -1440,23 +1593,6 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
1440
1593
|
"coverage",
|
|
1441
1594
|
".turbo"
|
|
1442
1595
|
]);
|
|
1443
|
-
var MIME_MAP = {
|
|
1444
|
-
md: "text/markdown",
|
|
1445
|
-
txt: "text/plain",
|
|
1446
|
-
json: "application/json",
|
|
1447
|
-
js: "text/javascript",
|
|
1448
|
-
ts: "text/typescript",
|
|
1449
|
-
py: "text/x-python",
|
|
1450
|
-
html: "text/html",
|
|
1451
|
-
css: "text/css",
|
|
1452
|
-
csv: "text/csv",
|
|
1453
|
-
png: "image/png",
|
|
1454
|
-
jpg: "image/jpeg",
|
|
1455
|
-
jpeg: "image/jpeg",
|
|
1456
|
-
gif: "image/gif",
|
|
1457
|
-
svg: "image/svg+xml",
|
|
1458
|
-
pdf: "application/pdf"
|
|
1459
|
-
};
|
|
1460
1596
|
async function collectRealFiles(dir, maxFiles = Infinity) {
|
|
1461
1597
|
const files = [];
|
|
1462
1598
|
const walk = async (d) => {
|
|
@@ -1641,7 +1777,6 @@ var CLAUDE_RUNTIME_ALLOW_WRITE_PATHS = [
|
|
|
1641
1777
|
`${HOME_DIR}/.claude.json.tmp`,
|
|
1642
1778
|
`${HOME_DIR}/.local/state/claude`
|
|
1643
1779
|
];
|
|
1644
|
-
var MAX_UPLOAD_FILE_SIZE = 200 * 1024 * 1024;
|
|
1645
1780
|
var MAX_COLLECT_FILES = 5e3;
|
|
1646
1781
|
var DEFAULT_ZIP_MAX_BYTES = 200 * 1024 * 1024;
|
|
1647
1782
|
function resolveIdleTimeoutMs() {
|
|
@@ -1675,29 +1810,23 @@ var ClaudeSession = class {
|
|
|
1675
1810
|
activeToolName = null;
|
|
1676
1811
|
/** Track current content block type to distinguish thinking vs text deltas */
|
|
1677
1812
|
currentBlockType = null;
|
|
1678
|
-
/** Upload credentials provided by the platform for auto-uploading output files */
|
|
1679
|
-
uploadCredentials = null;
|
|
1680
1813
|
/** Per-client workspace path (symlink-based), set on each send() */
|
|
1681
1814
|
currentWorkspace;
|
|
1682
|
-
|
|
1815
|
+
/** Whether caller requested file transfer */
|
|
1816
|
+
withFiles = false;
|
|
1817
|
+
send(message, attachments, clientId, withFiles) {
|
|
1683
1818
|
this.resetIdleTimer();
|
|
1684
1819
|
this.doneFired = false;
|
|
1685
1820
|
this.chunksEmitted = false;
|
|
1686
1821
|
this.activeToolCallId = null;
|
|
1687
1822
|
this.activeToolName = null;
|
|
1688
1823
|
this.currentBlockType = null;
|
|
1689
|
-
|
|
1690
|
-
this.uploadCredentials = uploadCredentials;
|
|
1691
|
-
}
|
|
1824
|
+
this.withFiles = withFiles || false;
|
|
1692
1825
|
if (clientId && this.config.project) {
|
|
1693
1826
|
this.currentWorkspace = createClientWorkspace(this.config.project, clientId);
|
|
1694
1827
|
} else {
|
|
1695
1828
|
this.currentWorkspace = void 0;
|
|
1696
1829
|
}
|
|
1697
|
-
if (platformTask) {
|
|
1698
|
-
void this.runPlatformTask(platformTask);
|
|
1699
|
-
return;
|
|
1700
|
-
}
|
|
1701
1830
|
const args = ["-p", message, "--continue", "--output-format", "stream-json", "--verbose", "--include-partial-messages", "--dangerously-skip-permissions"];
|
|
1702
1831
|
void this.downloadAttachments(attachments).then(() => {
|
|
1703
1832
|
this.launchProcess(args);
|
|
@@ -1786,145 +1915,64 @@ var ClaudeSession = class {
|
|
|
1786
1915
|
getWorkspaceRoot() {
|
|
1787
1916
|
return this.currentWorkspace || this.config.project || process.cwd();
|
|
1788
1917
|
}
|
|
1789
|
-
normalizeRelativePath(inputPath) {
|
|
1790
|
-
const normalized = inputPath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
1791
|
-
if (!normalized || normalized.includes("\0")) return null;
|
|
1792
|
-
if (normalized.split("/").some((seg) => seg === "..")) return null;
|
|
1793
|
-
return normalized;
|
|
1794
|
-
}
|
|
1795
|
-
async runPlatformTask(task) {
|
|
1796
|
-
try {
|
|
1797
|
-
if (!this.uploadCredentials) {
|
|
1798
|
-
throw new Error("Missing upload credentials for platform task");
|
|
1799
|
-
}
|
|
1800
|
-
if (task.type === "upload_file") {
|
|
1801
|
-
await this.runUploadFileTask(task.path);
|
|
1802
|
-
} else if (task.type === "upload_all_zip") {
|
|
1803
|
-
await this.runUploadAllZipTask(task.zip_name, task.max_bytes);
|
|
1804
|
-
} else {
|
|
1805
|
-
throw new Error(`Unsupported platform task: ${task.type || "unknown"}`);
|
|
1806
|
-
}
|
|
1807
|
-
this.doneFired = true;
|
|
1808
|
-
} catch (error) {
|
|
1809
|
-
this.emitError(new Error(`Platform task failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
1810
|
-
}
|
|
1811
|
-
}
|
|
1812
|
-
async runUploadFileTask(path) {
|
|
1813
|
-
const workspaceRoot = this.getWorkspaceRoot();
|
|
1814
|
-
const relPath = this.normalizeRelativePath(path);
|
|
1815
|
-
if (!relPath) {
|
|
1816
|
-
throw new Error("Invalid file path");
|
|
1817
|
-
}
|
|
1818
|
-
const absPath = join5(workspaceRoot, relPath);
|
|
1819
|
-
const info = await stat2(absPath);
|
|
1820
|
-
if (!info.isFile()) {
|
|
1821
|
-
throw new Error("Path is not a regular file");
|
|
1822
|
-
}
|
|
1823
|
-
const buffer = await readFile2(absPath);
|
|
1824
|
-
if (buffer.length === 0 || buffer.length > MAX_UPLOAD_FILE_SIZE) {
|
|
1825
|
-
throw new Error(`File size out of bounds: ${buffer.length}`);
|
|
1826
|
-
}
|
|
1827
|
-
const uploaded = await this.uploadBuffer(relPath, buffer);
|
|
1828
|
-
for (const cb of this.doneCallbacks) cb({ attachments: [uploaded] });
|
|
1829
|
-
}
|
|
1830
|
-
async runUploadAllZipTask(zipName, maxBytes) {
|
|
1831
|
-
const workspaceRoot = this.getWorkspaceRoot();
|
|
1832
|
-
const files = await this.collectWorkspaceFiles(workspaceRoot);
|
|
1833
|
-
if (files.length === 0) {
|
|
1834
|
-
throw new Error("No files found");
|
|
1835
|
-
}
|
|
1836
|
-
const maxZipBytes = typeof maxBytes === "number" && Number.isFinite(maxBytes) && maxBytes > 0 ? Math.floor(maxBytes) : DEFAULT_ZIP_MAX_BYTES;
|
|
1837
|
-
const entries = [];
|
|
1838
|
-
let totalBytes = 0;
|
|
1839
|
-
for (const absPath of files) {
|
|
1840
|
-
this.resetIdleTimer();
|
|
1841
|
-
const relPath = relative3(workspaceRoot, absPath).replace(/\\/g, "/");
|
|
1842
|
-
if (!relPath || relPath.startsWith("..")) continue;
|
|
1843
|
-
const buffer = await readFile2(absPath);
|
|
1844
|
-
if (buffer.length === 0) continue;
|
|
1845
|
-
totalBytes += buffer.length;
|
|
1846
|
-
if (totalBytes > maxZipBytes) {
|
|
1847
|
-
throw new Error(`ZIP_TOO_LARGE:${totalBytes}`);
|
|
1848
|
-
}
|
|
1849
|
-
entries.push({ path: relPath, data: buffer });
|
|
1850
|
-
}
|
|
1851
|
-
const zipBuffer = createZipBuffer(entries);
|
|
1852
|
-
if (zipBuffer.length > maxZipBytes) {
|
|
1853
|
-
throw new Error(`ZIP_TOO_LARGE:${zipBuffer.length}`);
|
|
1854
|
-
}
|
|
1855
|
-
const safeZipName = this.normalizeRelativePath(zipName || "") || `workspace-${this.sessionId.slice(0, 8)}.zip`;
|
|
1856
|
-
const uploaded = await this.uploadBuffer(safeZipName.endsWith(".zip") ? safeZipName : `${safeZipName}.zip`, zipBuffer);
|
|
1857
|
-
for (const cb of this.doneCallbacks) cb({ attachments: [uploaded] });
|
|
1858
|
-
}
|
|
1859
|
-
async uploadBuffer(filename, buffer) {
|
|
1860
|
-
const creds = this.uploadCredentials;
|
|
1861
|
-
if (!creds) {
|
|
1862
|
-
throw new Error("Upload credentials missing");
|
|
1863
|
-
}
|
|
1864
|
-
const response = await fetch(creds.uploadUrl, {
|
|
1865
|
-
method: "POST",
|
|
1866
|
-
headers: {
|
|
1867
|
-
"X-Upload-Token": creds.uploadToken,
|
|
1868
|
-
"Content-Type": "application/json"
|
|
1869
|
-
},
|
|
1870
|
-
body: JSON.stringify({
|
|
1871
|
-
filename,
|
|
1872
|
-
content: buffer.toString("base64")
|
|
1873
|
-
})
|
|
1874
|
-
});
|
|
1875
|
-
if (!response.ok) {
|
|
1876
|
-
throw new Error(`Upload failed (${response.status}) for ${filename}`);
|
|
1877
|
-
}
|
|
1878
|
-
const payload = await response.json();
|
|
1879
|
-
if (typeof payload.url !== "string" || payload.url.length === 0) {
|
|
1880
|
-
throw new Error(`Upload response missing url for ${filename}`);
|
|
1881
|
-
}
|
|
1882
|
-
const ext = filename.split(".").pop()?.toLowerCase() || "";
|
|
1883
|
-
return {
|
|
1884
|
-
name: filename,
|
|
1885
|
-
url: payload.url,
|
|
1886
|
-
type: MIME_MAP[ext] || "application/octet-stream"
|
|
1887
|
-
};
|
|
1888
|
-
}
|
|
1889
1918
|
async collectWorkspaceFiles(workspaceRoot) {
|
|
1890
1919
|
return collectRealFiles(workspaceRoot, MAX_COLLECT_FILES);
|
|
1891
1920
|
}
|
|
1892
|
-
|
|
1921
|
+
/**
|
|
1922
|
+
* Collect workspace files into a ZIP buffer + compute SHA-256.
|
|
1923
|
+
* Only called when with_files is true.
|
|
1924
|
+
*/
|
|
1925
|
+
async createWorkspaceZip(workspaceRoot) {
|
|
1893
1926
|
const files = await this.collectWorkspaceFiles(workspaceRoot);
|
|
1894
|
-
|
|
1927
|
+
if (files.length === 0) return null;
|
|
1928
|
+
const entries = [];
|
|
1929
|
+
let totalBytes = 0;
|
|
1895
1930
|
for (const absPath of files) {
|
|
1896
1931
|
const relPath = relative3(workspaceRoot, absPath).replace(/\\/g, "/");
|
|
1897
1932
|
if (!relPath || relPath.startsWith("..")) continue;
|
|
1898
1933
|
try {
|
|
1899
1934
|
const fileStat = await stat2(absPath);
|
|
1900
1935
|
if (!fileStat.isFile()) continue;
|
|
1901
|
-
const
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1936
|
+
const { readFile: readFile4 } = await import("fs/promises");
|
|
1937
|
+
const buffer = await readFile4(absPath);
|
|
1938
|
+
if (buffer.length === 0) continue;
|
|
1939
|
+
totalBytes += buffer.length;
|
|
1940
|
+
if (totalBytes > DEFAULT_ZIP_MAX_BYTES) {
|
|
1941
|
+
log.warn(`Workspace exceeds ${DEFAULT_ZIP_MAX_BYTES / 1024 / 1024}MB limit, truncating`);
|
|
1942
|
+
break;
|
|
1943
|
+
}
|
|
1944
|
+
entries.push({ path: relPath, data: buffer });
|
|
1908
1945
|
} catch {
|
|
1909
1946
|
}
|
|
1910
1947
|
}
|
|
1911
|
-
|
|
1912
|
-
|
|
1948
|
+
if (entries.length === 0) return null;
|
|
1949
|
+
const zipBuffer = createZipBuffer(entries);
|
|
1950
|
+
return { zipBuffer, fileCount: entries.length };
|
|
1913
1951
|
}
|
|
1914
1952
|
async finalizeDone(attachments) {
|
|
1915
|
-
const workspaceRoot = this.getWorkspaceRoot();
|
|
1916
|
-
let fileManifest;
|
|
1917
|
-
try {
|
|
1918
|
-
fileManifest = await this.collectWorkspaceManifest(workspaceRoot);
|
|
1919
|
-
} catch (error) {
|
|
1920
|
-
log.warn(`Manifest collection failed: ${error}`);
|
|
1921
|
-
}
|
|
1922
1953
|
const payload = {};
|
|
1923
1954
|
if (attachments && attachments.length > 0) {
|
|
1924
1955
|
payload.attachments = attachments;
|
|
1925
1956
|
}
|
|
1926
|
-
if (
|
|
1927
|
-
|
|
1957
|
+
if (this.withFiles) {
|
|
1958
|
+
const workspaceRoot = this.getWorkspaceRoot();
|
|
1959
|
+
try {
|
|
1960
|
+
const result = await this.createWorkspaceZip(workspaceRoot);
|
|
1961
|
+
if (result) {
|
|
1962
|
+
const transferId = crypto.randomUUID();
|
|
1963
|
+
const zipSha256 = sha256Hex(result.zipBuffer);
|
|
1964
|
+
payload.fileTransferOffer = {
|
|
1965
|
+
transfer_id: transferId,
|
|
1966
|
+
zip_size: result.zipBuffer.length,
|
|
1967
|
+
zip_sha256: zipSha256,
|
|
1968
|
+
file_count: result.fileCount
|
|
1969
|
+
};
|
|
1970
|
+
payload.zipBuffer = result.zipBuffer;
|
|
1971
|
+
log.info(`[WebRTC] ZIP ready: ${result.fileCount} files, ${result.zipBuffer.length} bytes, transfer=${transferId.slice(0, 8)}...`);
|
|
1972
|
+
}
|
|
1973
|
+
} catch (error) {
|
|
1974
|
+
log.warn(`ZIP creation failed: ${error}`);
|
|
1975
|
+
}
|
|
1928
1976
|
}
|
|
1929
1977
|
for (const cb of this.doneCallbacks) cb(payload);
|
|
1930
1978
|
}
|
|
@@ -2071,34 +2119,28 @@ var ClaudeSession = class {
|
|
|
2071
2119
|
return;
|
|
2072
2120
|
}
|
|
2073
2121
|
}
|
|
2074
|
-
stripWorkspacePaths(text) {
|
|
2075
|
-
const root = this.getWorkspaceRoot();
|
|
2076
|
-
if (!root) return text;
|
|
2077
|
-
const prefix = root.endsWith("/") ? root : root + "/";
|
|
2078
|
-
return text.replaceAll(prefix, "");
|
|
2079
|
-
}
|
|
2080
2122
|
emitChunk(text) {
|
|
2081
2123
|
this.chunksEmitted = true;
|
|
2082
|
-
for (const cb of this.chunkCallbacks) cb(
|
|
2124
|
+
for (const cb of this.chunkCallbacks) cb(text);
|
|
2083
2125
|
}
|
|
2084
2126
|
emitToolEvent(event) {
|
|
2085
|
-
for (const cb of this.toolCallbacks) cb(
|
|
2127
|
+
for (const cb of this.toolCallbacks) cb(event);
|
|
2086
2128
|
}
|
|
2087
2129
|
emitTextAsChunks(text) {
|
|
2088
|
-
const
|
|
2089
|
-
if (text.length <=
|
|
2130
|
+
const CHUNK_SIZE2 = 60;
|
|
2131
|
+
if (text.length <= CHUNK_SIZE2) {
|
|
2090
2132
|
this.emitChunk(text);
|
|
2091
2133
|
return;
|
|
2092
2134
|
}
|
|
2093
2135
|
let pos = 0;
|
|
2094
2136
|
while (pos < text.length) {
|
|
2095
|
-
let end = Math.min(pos +
|
|
2137
|
+
let end = Math.min(pos + CHUNK_SIZE2, text.length);
|
|
2096
2138
|
if (end < text.length) {
|
|
2097
2139
|
const slice = text.slice(pos, end + 20);
|
|
2098
2140
|
const breakPoints = ["\n", "\u3002", "\uFF01", "\uFF1F", ". ", "! ", "? ", "\uFF0C", ", ", " "];
|
|
2099
2141
|
for (const bp of breakPoints) {
|
|
2100
|
-
const idx = slice.indexOf(bp,
|
|
2101
|
-
if (idx >= 0 && idx <
|
|
2142
|
+
const idx = slice.indexOf(bp, CHUNK_SIZE2 - 20);
|
|
2143
|
+
if (idx >= 0 && idx < CHUNK_SIZE2 + 20) {
|
|
2102
2144
|
end = pos + idx + bp.length;
|
|
2103
2145
|
break;
|
|
2104
2146
|
}
|
|
@@ -3399,8 +3441,8 @@ async function asyncChat(opts) {
|
|
|
3399
3441
|
`);
|
|
3400
3442
|
}
|
|
3401
3443
|
}
|
|
3402
|
-
if (task.
|
|
3403
|
-
process.stdout.write(`${GRAY}[
|
|
3444
|
+
if (task.file_transfer_offer) {
|
|
3445
|
+
process.stdout.write(`${GRAY}[files: ${task.file_transfer_offer.file_count} available via WebRTC]${RESET}
|
|
3404
3446
|
`);
|
|
3405
3447
|
}
|
|
3406
3448
|
return;
|
|
@@ -3605,11 +3647,11 @@ function registerChatCommand(program2) {
|
|
|
3605
3647
|
}
|
|
3606
3648
|
|
|
3607
3649
|
// src/commands/skills.ts
|
|
3608
|
-
import { readFile as
|
|
3650
|
+
import { readFile as readFile3, writeFile as writeFile3, readdir as readdir2, mkdir as mkdir2, rm, symlink, unlink } from "fs/promises";
|
|
3609
3651
|
import { join as join9, resolve, relative as relative4 } from "path";
|
|
3610
3652
|
|
|
3611
3653
|
// src/utils/skill-parser.ts
|
|
3612
|
-
import { readFile as
|
|
3654
|
+
import { readFile as readFile2, writeFile as writeFile2, stat as stat3 } from "fs/promises";
|
|
3613
3655
|
import { join as join8 } from "path";
|
|
3614
3656
|
function parseSkillMd(raw) {
|
|
3615
3657
|
const trimmed = raw.trimStart();
|
|
@@ -3694,7 +3736,7 @@ function parseSkillMd(raw) {
|
|
|
3694
3736
|
async function loadSkillManifest(dir) {
|
|
3695
3737
|
const skillMdPath = join8(dir, "SKILL.md");
|
|
3696
3738
|
try {
|
|
3697
|
-
const raw = await
|
|
3739
|
+
const raw = await readFile2(skillMdPath, "utf-8");
|
|
3698
3740
|
const { frontmatter } = parseSkillMd(raw);
|
|
3699
3741
|
const name = frontmatter.name;
|
|
3700
3742
|
if (!name) {
|
|
@@ -3727,7 +3769,7 @@ async function pathExists(p) {
|
|
|
3727
3769
|
}
|
|
3728
3770
|
}
|
|
3729
3771
|
async function updateFrontmatterField(filePath, field, value) {
|
|
3730
|
-
const raw = await
|
|
3772
|
+
const raw = await readFile2(filePath, "utf-8");
|
|
3731
3773
|
const trimmed = raw.trimStart();
|
|
3732
3774
|
if (!trimmed.startsWith("---")) {
|
|
3733
3775
|
throw new Error("SKILL.md has no frontmatter block");
|
|
@@ -3860,7 +3902,7 @@ async function packSkill(dir, manifest) {
|
|
|
3860
3902
|
for (const relPath of fileList) {
|
|
3861
3903
|
const absPath = join9(dir, relPath);
|
|
3862
3904
|
try {
|
|
3863
|
-
const data = await
|
|
3905
|
+
const data = await readFile3(absPath);
|
|
3864
3906
|
entries.push({ path: relPath.replace(/\\/g, "/"), data });
|
|
3865
3907
|
} catch {
|
|
3866
3908
|
slog.warn(`Skipping unreadable file: ${relPath}`);
|
|
@@ -3945,7 +3987,7 @@ function registerSkillsCommand(program2) {
|
|
|
3945
3987
|
await mkdir2(dir, { recursive: true });
|
|
3946
3988
|
const skillMdPath = join9(dir, "SKILL.md");
|
|
3947
3989
|
if (await pathExists(skillMdPath)) {
|
|
3948
|
-
const raw = await
|
|
3990
|
+
const raw = await readFile3(skillMdPath, "utf-8");
|
|
3949
3991
|
const { frontmatter } = parseSkillMd(raw);
|
|
3950
3992
|
if (frontmatter.name) {
|
|
3951
3993
|
slog.info(`SKILL.md already exists with name: ${frontmatter.name}`);
|
|
@@ -4018,7 +4060,7 @@ function registerSkillsCommand(program2) {
|
|
|
4018
4060
|
if (opts.name) manifest.name = opts.name;
|
|
4019
4061
|
if (opts.version) manifest.version = opts.version;
|
|
4020
4062
|
if (opts.private !== void 0) manifest.private = opts.private;
|
|
4021
|
-
content = await
|
|
4063
|
+
content = await readFile3(join9(dir, manifest.main || "SKILL.md"), "utf-8");
|
|
4022
4064
|
packResult = await packSkill(dir, manifest);
|
|
4023
4065
|
slog.info(`Packed ${packResult.files.length} files (${packResult.size} bytes)`);
|
|
4024
4066
|
}
|
|
@@ -4159,7 +4201,7 @@ function registerSkillsCommand(program2) {
|
|
|
4159
4201
|
if (!await pathExists(skillMdPath)) {
|
|
4160
4202
|
outputError("not_found", "No SKILL.md found. Run `agent-mesh skills init` first.");
|
|
4161
4203
|
}
|
|
4162
|
-
const raw = await
|
|
4204
|
+
const raw = await readFile3(skillMdPath, "utf-8");
|
|
4163
4205
|
const { frontmatter } = parseSkillMd(raw);
|
|
4164
4206
|
const oldVersion = frontmatter.version || "0.0.0";
|
|
4165
4207
|
const newVersion = bumpVersion(oldVersion, bump);
|
|
@@ -4221,7 +4263,7 @@ function registerSkillsCommand(program2) {
|
|
|
4221
4263
|
const skillMdPath = join9(targetDir, "SKILL.md");
|
|
4222
4264
|
let localVersion = "0.0.0";
|
|
4223
4265
|
if (await pathExists(skillMdPath)) {
|
|
4224
|
-
const raw = await
|
|
4266
|
+
const raw = await readFile3(skillMdPath, "utf-8");
|
|
4225
4267
|
const { frontmatter } = parseSkillMd(raw);
|
|
4226
4268
|
localVersion = frontmatter.version || "0.0.0";
|
|
4227
4269
|
}
|
|
@@ -4250,7 +4292,7 @@ function registerSkillsCommand(program2) {
|
|
|
4250
4292
|
skipped.push({ slug, reason: "no_skill_md" });
|
|
4251
4293
|
continue;
|
|
4252
4294
|
}
|
|
4253
|
-
const raw = await
|
|
4295
|
+
const raw = await readFile3(skillMdPath, "utf-8");
|
|
4254
4296
|
const { frontmatter } = parseSkillMd(raw);
|
|
4255
4297
|
const localVersion = frontmatter.version || "0.0.0";
|
|
4256
4298
|
const authorLogin = frontmatter.author;
|
|
@@ -4319,7 +4361,7 @@ function registerSkillsCommand(program2) {
|
|
|
4319
4361
|
const slug = entry.name;
|
|
4320
4362
|
const skillMdPath = join9(skillsDir, slug, "SKILL.md");
|
|
4321
4363
|
if (!await pathExists(skillMdPath)) continue;
|
|
4322
|
-
const raw = await
|
|
4364
|
+
const raw = await readFile3(skillMdPath, "utf-8");
|
|
4323
4365
|
const { frontmatter } = parseSkillMd(raw);
|
|
4324
4366
|
const skillInfo = {
|
|
4325
4367
|
slug,
|
|
@@ -4470,7 +4512,11 @@ async function asyncCall(opts) {
|
|
|
4470
4512
|
"Content-Type": "application/json",
|
|
4471
4513
|
...selfAgentId ? { "X-Caller-Agent-Id": selfAgentId } : {}
|
|
4472
4514
|
},
|
|
4473
|
-
body: JSON.stringify({
|
|
4515
|
+
body: JSON.stringify({
|
|
4516
|
+
task_description: opts.taskDescription,
|
|
4517
|
+
mode: "async",
|
|
4518
|
+
...opts.withFiles ? { with_files: true } : {}
|
|
4519
|
+
}),
|
|
4474
4520
|
signal: opts.signal
|
|
4475
4521
|
});
|
|
4476
4522
|
if (!res.ok) {
|
|
@@ -4529,7 +4575,7 @@ async function asyncCall(opts) {
|
|
|
4529
4575
|
status: "completed",
|
|
4530
4576
|
result,
|
|
4531
4577
|
...task.attachments?.length ? { attachments: task.attachments } : {},
|
|
4532
|
-
...
|
|
4578
|
+
...task.file_transfer_offer ? { file_transfer_offer: task.file_transfer_offer } : {},
|
|
4533
4579
|
rate_hint: `POST /api/agents/${opts.id}/rate body: { call_id: "${call_id}", rating: 1-5 }`
|
|
4534
4580
|
}));
|
|
4535
4581
|
} else {
|
|
@@ -4539,9 +4585,9 @@ async function asyncCall(opts) {
|
|
|
4539
4585
|
log.info(` ${GRAY}File:${RESET} ${att.name} ${GRAY}${att.url}${RESET}`);
|
|
4540
4586
|
}
|
|
4541
4587
|
}
|
|
4542
|
-
const
|
|
4543
|
-
if (
|
|
4544
|
-
log.info(` ${GRAY}
|
|
4588
|
+
const offer = task.file_transfer_offer;
|
|
4589
|
+
if (offer) {
|
|
4590
|
+
log.info(` ${GRAY}Files:${RESET} ${offer.file_count} file(s) available via WebRTC`);
|
|
4545
4591
|
}
|
|
4546
4592
|
if (session_key) {
|
|
4547
4593
|
log.info(` ${GRAY}Session:${RESET} ${session_key}`);
|
|
@@ -4585,7 +4631,10 @@ async function streamCall(opts) {
|
|
|
4585
4631
|
Accept: "text/event-stream",
|
|
4586
4632
|
...selfAgentId ? { "X-Caller-Agent-Id": selfAgentId } : {}
|
|
4587
4633
|
},
|
|
4588
|
-
body: JSON.stringify({
|
|
4634
|
+
body: JSON.stringify({
|
|
4635
|
+
task_description: opts.taskDescription,
|
|
4636
|
+
...opts.withFiles ? { with_files: true } : {}
|
|
4637
|
+
}),
|
|
4589
4638
|
signal: opts.signal
|
|
4590
4639
|
});
|
|
4591
4640
|
if (!res.ok) {
|
|
@@ -4668,13 +4717,15 @@ async function streamCall(opts) {
|
|
|
4668
4717
|
outputBuffer += delta;
|
|
4669
4718
|
}
|
|
4670
4719
|
}
|
|
4671
|
-
} else if (event.type === "done"
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4720
|
+
} else if (event.type === "done") {
|
|
4721
|
+
if (event.attachments?.length) {
|
|
4722
|
+
console.log("");
|
|
4723
|
+
for (const att of event.attachments) {
|
|
4724
|
+
log.info(` ${GRAY}File:${RESET} ${att.name} ${GRAY}${att.url}${RESET}`);
|
|
4725
|
+
}
|
|
4675
4726
|
}
|
|
4676
|
-
if (
|
|
4677
|
-
log.info(` ${GRAY}
|
|
4727
|
+
if (event.file_transfer_offer) {
|
|
4728
|
+
log.info(` ${GRAY}Files:${RESET} ${event.file_transfer_offer.file_count} file(s) available via WebRTC`);
|
|
4678
4729
|
}
|
|
4679
4730
|
} else if (event.type === "error") {
|
|
4680
4731
|
process.stderr.write(`
|
|
@@ -4724,7 +4775,7 @@ Error: ${event.message}
|
|
|
4724
4775
|
return { callId, ...sessionKey ? { sessionKey } : {} };
|
|
4725
4776
|
}
|
|
4726
4777
|
function registerCallCommand(program2) {
|
|
4727
|
-
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("--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) => {
|
|
4778
|
+
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) => {
|
|
4728
4779
|
try {
|
|
4729
4780
|
const token = loadToken();
|
|
4730
4781
|
if (!token) {
|
|
@@ -4753,7 +4804,8 @@ ${content}`;
|
|
|
4753
4804
|
timeoutMs,
|
|
4754
4805
|
json: opts.json,
|
|
4755
4806
|
outputFile: opts.outputFile,
|
|
4756
|
-
signal: abortController.signal
|
|
4807
|
+
signal: abortController.signal,
|
|
4808
|
+
withFiles: opts.withFiles
|
|
4757
4809
|
};
|
|
4758
4810
|
let result;
|
|
4759
4811
|
if (opts.stream) {
|
|
@@ -5220,21 +5272,11 @@ function registerProfileCommand(program2) {
|
|
|
5220
5272
|
}
|
|
5221
5273
|
|
|
5222
5274
|
// src/commands/files.ts
|
|
5223
|
-
import { writeFileSync as writeFileSync4 } from "fs";
|
|
5224
|
-
import { basename as basename2 } from "path";
|
|
5225
5275
|
function formatBytes(bytes) {
|
|
5226
5276
|
if (bytes < 1024) return `${bytes}B`;
|
|
5227
5277
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
5228
5278
|
return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
|
|
5229
5279
|
}
|
|
5230
|
-
function defaultOutputPath(filePath) {
|
|
5231
|
-
const name = basename2(filePath || "") || "file";
|
|
5232
|
-
return name;
|
|
5233
|
-
}
|
|
5234
|
-
function defaultZipOutputPath(sessionKey) {
|
|
5235
|
-
const suffix = sessionKey.split(":").at(-1) || "session";
|
|
5236
|
-
return `session-${suffix}.zip`;
|
|
5237
|
-
}
|
|
5238
5280
|
function handleError7(err) {
|
|
5239
5281
|
if (err instanceof PlatformApiError) {
|
|
5240
5282
|
log.error(err.message);
|
|
@@ -5247,16 +5289,8 @@ async function resolveTargetAgent(agentInput) {
|
|
|
5247
5289
|
const client = createClient();
|
|
5248
5290
|
return resolveAgentId(agentInput, client);
|
|
5249
5291
|
}
|
|
5250
|
-
async function downloadToFile(url, outputPath) {
|
|
5251
|
-
const res = await fetch(url);
|
|
5252
|
-
if (!res.ok) {
|
|
5253
|
-
throw new Error(`Download failed: HTTP ${res.status}`);
|
|
5254
|
-
}
|
|
5255
|
-
const buf = Buffer.from(await res.arrayBuffer());
|
|
5256
|
-
writeFileSync4(outputPath, buf);
|
|
5257
|
-
}
|
|
5258
5292
|
function registerFilesCommand(program2) {
|
|
5259
|
-
const files = program2.command("files").description("Session file
|
|
5293
|
+
const files = program2.command("files").description("Session file commands (WebRTC P2P transfer)");
|
|
5260
5294
|
files.command("list").requiredOption("--agent <agent>", "Target agent ID or name").requiredOption("--session <session_key>", "Session key").option("--json", "Output raw JSON").action(async (opts) => {
|
|
5261
5295
|
try {
|
|
5262
5296
|
const { id, name } = await resolveTargetAgent(opts.agent);
|
|
@@ -5279,90 +5313,7 @@ function registerFilesCommand(program2) {
|
|
|
5279
5313
|
console.log(` ${f.path} ${GRAY}${formatBytes(f.size)}${RESET}`);
|
|
5280
5314
|
}
|
|
5281
5315
|
console.log("");
|
|
5282
|
-
|
|
5283
|
-
handleError7(err);
|
|
5284
|
-
}
|
|
5285
|
-
});
|
|
5286
|
-
files.command("upload").requiredOption("--agent <agent>", "Target agent ID or name").requiredOption("--session <session_key>", "Session key").requiredOption("--path <file_path>", "Relative file path in session workspace").option("--json", "Output raw JSON").action(async (opts) => {
|
|
5287
|
-
try {
|
|
5288
|
-
const { id } = await resolveTargetAgent(opts.agent);
|
|
5289
|
-
const client = createClient();
|
|
5290
|
-
const data = await client.post(`/api/agents/${id}/files/upload`, {
|
|
5291
|
-
session_key: opts.session,
|
|
5292
|
-
path: opts.path
|
|
5293
|
-
});
|
|
5294
|
-
if (opts.json) {
|
|
5295
|
-
console.log(JSON.stringify(data));
|
|
5296
|
-
return;
|
|
5297
|
-
}
|
|
5298
|
-
if (!data.file?.url) {
|
|
5299
|
-
throw new Error("No file URL returned");
|
|
5300
|
-
}
|
|
5301
|
-
log.success(`Uploaded ${opts.path}`);
|
|
5302
|
-
console.log(` ${GRAY}URL${RESET} ${data.file.url}`);
|
|
5303
|
-
} catch (err) {
|
|
5304
|
-
handleError7(err);
|
|
5305
|
-
}
|
|
5306
|
-
});
|
|
5307
|
-
files.command("upload-all").requiredOption("--agent <agent>", "Target agent ID or name").requiredOption("--session <session_key>", "Session key").option("--json", "Output raw JSON").action(async (opts) => {
|
|
5308
|
-
try {
|
|
5309
|
-
const { id } = await resolveTargetAgent(opts.agent);
|
|
5310
|
-
const client = createClient();
|
|
5311
|
-
const data = await client.post(`/api/agents/${id}/files/upload-all`, {
|
|
5312
|
-
session_key: opts.session
|
|
5313
|
-
});
|
|
5314
|
-
if (opts.json) {
|
|
5315
|
-
console.log(JSON.stringify(data));
|
|
5316
|
-
return;
|
|
5317
|
-
}
|
|
5318
|
-
if (!data.file?.url) {
|
|
5319
|
-
throw new Error("No ZIP URL returned");
|
|
5320
|
-
}
|
|
5321
|
-
log.success("Uploaded session ZIP");
|
|
5322
|
-
console.log(` ${GRAY}URL${RESET} ${data.file.url}`);
|
|
5323
|
-
} catch (err) {
|
|
5324
|
-
handleError7(err);
|
|
5325
|
-
}
|
|
5326
|
-
});
|
|
5327
|
-
files.command("download").requiredOption("--agent <agent>", "Target agent ID or name").requiredOption("--session <session_key>", "Session key").requiredOption("--path <file_path>", "Relative file path in session workspace").option("--output <path>", "Local output path").option("--json", "Output raw JSON").action(async (opts) => {
|
|
5328
|
-
try {
|
|
5329
|
-
const { id } = await resolveTargetAgent(opts.agent);
|
|
5330
|
-
const client = createClient();
|
|
5331
|
-
const data = await client.post(`/api/agents/${id}/files/upload`, {
|
|
5332
|
-
session_key: opts.session,
|
|
5333
|
-
path: opts.path
|
|
5334
|
-
});
|
|
5335
|
-
const url = data.file?.url;
|
|
5336
|
-
if (!url) throw new Error("No file URL returned");
|
|
5337
|
-
const output = opts.output || defaultOutputPath(opts.path);
|
|
5338
|
-
await downloadToFile(url, output);
|
|
5339
|
-
if (opts.json) {
|
|
5340
|
-
console.log(JSON.stringify({ success: true, output, url }));
|
|
5341
|
-
return;
|
|
5342
|
-
}
|
|
5343
|
-
log.success(`Downloaded ${opts.path}`);
|
|
5344
|
-
console.log(` ${GRAY}Saved${RESET} ${output}`);
|
|
5345
|
-
} catch (err) {
|
|
5346
|
-
handleError7(err);
|
|
5347
|
-
}
|
|
5348
|
-
});
|
|
5349
|
-
files.command("download-all").requiredOption("--agent <agent>", "Target agent ID or name").requiredOption("--session <session_key>", "Session key").option("--output <path>", "Local output path").option("--json", "Output raw JSON").action(async (opts) => {
|
|
5350
|
-
try {
|
|
5351
|
-
const { id } = await resolveTargetAgent(opts.agent);
|
|
5352
|
-
const client = createClient();
|
|
5353
|
-
const data = await client.post(`/api/agents/${id}/files/upload-all`, {
|
|
5354
|
-
session_key: opts.session
|
|
5355
|
-
});
|
|
5356
|
-
const url = data.file?.url;
|
|
5357
|
-
if (!url) throw new Error("No ZIP URL returned");
|
|
5358
|
-
const output = opts.output || defaultZipOutputPath(opts.session);
|
|
5359
|
-
await downloadToFile(url, output);
|
|
5360
|
-
if (opts.json) {
|
|
5361
|
-
console.log(JSON.stringify({ success: true, output, url }));
|
|
5362
|
-
return;
|
|
5363
|
-
}
|
|
5364
|
-
log.success("Downloaded session ZIP");
|
|
5365
|
-
console.log(` ${GRAY}Saved${RESET} ${output}`);
|
|
5316
|
+
console.log(` ${GRAY}Use --with-files in call/chat to receive files via WebRTC P2P${RESET}`);
|
|
5366
5317
|
} catch (err) {
|
|
5367
5318
|
handleError7(err);
|
|
5368
5319
|
}
|
|
@@ -5372,12 +5323,9 @@ function registerFilesCommand(program2) {
|
|
|
5372
5323
|
command: "agent-mesh files",
|
|
5373
5324
|
docs: "https://agents.hot/docs/cli/files",
|
|
5374
5325
|
commands: [
|
|
5375
|
-
{ name: "list", required: ["--agent", "--session"], optional: ["--json"] }
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
{ name: "download", required: ["--agent", "--session", "--path"], optional: ["--output", "--json"] },
|
|
5379
|
-
{ name: "download-all", required: ["--agent", "--session"], optional: ["--output", "--json"] }
|
|
5380
|
-
]
|
|
5326
|
+
{ name: "list", required: ["--agent", "--session"], optional: ["--json"] }
|
|
5327
|
+
],
|
|
5328
|
+
notes: "File transfer now uses WebRTC P2P. Use --with-files flag in call/chat commands."
|
|
5381
5329
|
};
|
|
5382
5330
|
if (opts.json) {
|
|
5383
5331
|
console.log(JSON.stringify(reference));
|
|
@@ -5388,6 +5336,8 @@ function registerFilesCommand(program2) {
|
|
|
5388
5336
|
for (const item of reference.commands) {
|
|
5389
5337
|
console.log(` ${item.name}`);
|
|
5390
5338
|
}
|
|
5339
|
+
console.log("");
|
|
5340
|
+
console.log(` ${GRAY}${reference.notes}${RESET}`);
|
|
5391
5341
|
});
|
|
5392
5342
|
}
|
|
5393
5343
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@annals/agent-mesh",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "CLI bridge connecting local AI agents to the Agents.Hot platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@annals/bridge-protocol": "^0.2.0",
|
|
12
12
|
"commander": "^13.0.0",
|
|
13
|
+
"node-datachannel": "^0.32.0",
|
|
13
14
|
"ws": "^8.18.0"
|
|
14
15
|
},
|
|
15
16
|
"devDependencies": {
|