@annals/agent-mesh 0.16.13 → 0.17.2

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 CHANGED
@@ -33,7 +33,7 @@ import {
33
33
  } from "./chunk-KEUGYA3L.js";
34
34
 
35
35
  // src/index.ts
36
- import { createRequire } from "module";
36
+ import { createRequire as createRequire2 } from "module";
37
37
  import { program } from "commander";
38
38
 
39
39
  // src/platform/auth.ts
@@ -269,6 +269,137 @@ 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
+ import { createRequire } from "module";
275
+ import { existsSync, copyFileSync, mkdirSync } from "fs";
276
+ import { join, dirname } from "path";
277
+ var ICE_SERVERS = ["stun:stun.l.google.com:19302"];
278
+ var CHUNK_SIZE = 64 * 1024;
279
+ var ndcModule;
280
+ function ensurePrebuilt() {
281
+ try {
282
+ const require3 = createRequire(import.meta.url);
283
+ const ndcMain = require3.resolve("node-datachannel");
284
+ let ndcRoot = dirname(ndcMain);
285
+ for (let i = 0; i < 5; i++) {
286
+ if (existsSync(join(ndcRoot, "package.json"))) break;
287
+ ndcRoot = dirname(ndcRoot);
288
+ }
289
+ const target = join(ndcRoot, "build", "Release", "node_datachannel.node");
290
+ if (existsSync(target)) return true;
291
+ const platform = process.platform;
292
+ const arch = process.arch;
293
+ const ourPkgRoot = join(dirname(import.meta.url.replace("file://", "")), "..", "..");
294
+ const prebuiltSrc = join(ourPkgRoot, "prebuilds", `${platform}-${arch}`, "node_datachannel.node");
295
+ if (!existsSync(prebuiltSrc)) {
296
+ log.warn(`No prebuilt binary for ${platform}-${arch}`);
297
+ return false;
298
+ }
299
+ mkdirSync(join(ndcRoot, "build", "Release"), { recursive: true });
300
+ copyFileSync(prebuiltSrc, target);
301
+ log.info(`Installed node-datachannel prebuilt for ${platform}-${arch}`);
302
+ return true;
303
+ } catch {
304
+ return false;
305
+ }
306
+ }
307
+ async function loadNdc() {
308
+ if (ndcModule !== void 0) return ndcModule;
309
+ try {
310
+ ensurePrebuilt();
311
+ ndcModule = await import("node-datachannel");
312
+ return ndcModule;
313
+ } catch {
314
+ log.warn("node-datachannel not available \u2014 WebRTC file transfer disabled");
315
+ ndcModule = null;
316
+ return null;
317
+ }
318
+ }
319
+ var FileSender = class {
320
+ peer = null;
321
+ transferId;
322
+ zipBuffer;
323
+ pendingCandidates = [];
324
+ signalCallback = null;
325
+ closed = false;
326
+ constructor(transferId, zipBuffer) {
327
+ this.transferId = transferId;
328
+ this.zipBuffer = zipBuffer;
329
+ }
330
+ onSignal(cb) {
331
+ this.signalCallback = cb;
332
+ }
333
+ async handleSignal(signal) {
334
+ const ndc = await loadNdc();
335
+ if (!ndc || this.closed) return;
336
+ if (!this.peer) {
337
+ this.peer = new ndc.PeerConnection(`sender-${this.transferId}`, {
338
+ iceServers: ICE_SERVERS
339
+ });
340
+ this.peer.onLocalDescription((sdp, type) => {
341
+ this.signalCallback?.({ signal_type: type, payload: sdp });
342
+ });
343
+ this.peer.onLocalCandidate((candidate, mid) => {
344
+ this.signalCallback?.({
345
+ signal_type: "candidate",
346
+ payload: JSON.stringify({ candidate, mid })
347
+ });
348
+ });
349
+ this.peer.onDataChannel((dc) => {
350
+ dc.onOpen(() => {
351
+ void this.sendZip(dc);
352
+ });
353
+ });
354
+ }
355
+ if (signal.signal_type === "offer" || signal.signal_type === "answer") {
356
+ this.peer.setRemoteDescription(signal.payload, signal.signal_type);
357
+ for (const c of this.pendingCandidates) {
358
+ this.peer.addRemoteCandidate(c.candidate, c.mid);
359
+ }
360
+ this.pendingCandidates = [];
361
+ } else if (signal.signal_type === "candidate") {
362
+ const { candidate, mid } = JSON.parse(signal.payload);
363
+ if (this.peer.remoteDescription()) {
364
+ this.peer.addRemoteCandidate(candidate, mid);
365
+ } else {
366
+ this.pendingCandidates.push({ candidate, mid });
367
+ }
368
+ }
369
+ }
370
+ async sendZip(dc) {
371
+ try {
372
+ dc.sendMessage(JSON.stringify({
373
+ type: "header",
374
+ transfer_id: this.transferId,
375
+ zip_size: this.zipBuffer.length
376
+ }));
377
+ let offset = 0;
378
+ while (offset < this.zipBuffer.length) {
379
+ const end = Math.min(offset + CHUNK_SIZE, this.zipBuffer.length);
380
+ const chunk = this.zipBuffer.subarray(offset, end);
381
+ dc.sendMessageBinary(chunk);
382
+ offset = end;
383
+ }
384
+ dc.sendMessage(JSON.stringify({ type: "complete" }));
385
+ log.info(`[WebRTC] Sent ${this.zipBuffer.length} bytes in ${Math.ceil(this.zipBuffer.length / CHUNK_SIZE)} chunks`);
386
+ } catch (err) {
387
+ log.error(`[WebRTC] Send failed: ${err}`);
388
+ }
389
+ }
390
+ close() {
391
+ this.closed = true;
392
+ try {
393
+ this.peer?.close();
394
+ } catch {
395
+ }
396
+ this.peer = null;
397
+ }
398
+ };
399
+ function sha256Hex(data) {
400
+ return createHash("sha256").update(data).digest("hex");
401
+ }
402
+
272
403
  // src/bridge/session-pool.ts
273
404
  var SessionPool = class {
274
405
  sessions = /* @__PURE__ */ new Map();
@@ -311,15 +442,15 @@ var SessionPool = class {
311
442
 
312
443
  // src/utils/local-runtime-queue.ts
313
444
  import {
314
- existsSync,
315
- mkdirSync,
445
+ existsSync as existsSync2,
446
+ mkdirSync as mkdirSync2,
316
447
  readFileSync,
317
448
  renameSync,
318
449
  rmSync,
319
450
  statSync,
320
451
  writeFileSync
321
452
  } from "fs";
322
- import { join } from "path";
453
+ import { join as join2 } from "path";
323
454
  import { homedir } from "os";
324
455
  var DEFAULT_LOCK_STALE_MS = 3e4;
325
456
  var DEFAULT_LOCK_WAIT_MS = 1e4;
@@ -376,10 +507,10 @@ var LocalRuntimeQueue = class {
376
507
  leaseHeartbeatMs;
377
508
  constructor(config, opts = {}) {
378
509
  this.config = config;
379
- const baseDir = opts.baseDir || join(homedir(), ".agent-mesh");
380
- this.runtimeRoot = join(baseDir, "runtime");
381
- this.statePath = join(this.runtimeRoot, "queue-state.json");
382
- this.lockPath = join(this.runtimeRoot, "queue.lock");
510
+ const baseDir = opts.baseDir || join2(homedir(), ".agent-mesh");
511
+ this.runtimeRoot = join2(baseDir, "runtime");
512
+ this.statePath = join2(this.runtimeRoot, "queue-state.json");
513
+ this.lockPath = join2(this.runtimeRoot, "queue.lock");
383
514
  this.lockStaleMs = opts.lockStaleMs ?? DEFAULT_LOCK_STALE_MS;
384
515
  this.lockWaitMs = opts.lockWaitMs ?? DEFAULT_LOCK_WAIT_MS;
385
516
  this.lockRetryMs = opts.lockRetryMs ?? DEFAULT_LOCK_RETRY_MS;
@@ -551,8 +682,8 @@ var LocalRuntimeQueue = class {
551
682
  }
552
683
  }
553
684
  ensureRuntimeDir() {
554
- if (!existsSync(this.runtimeRoot)) {
555
- mkdirSync(this.runtimeRoot, { recursive: true, mode: 448 });
685
+ if (!existsSync2(this.runtimeRoot)) {
686
+ mkdirSync2(this.runtimeRoot, { recursive: true, mode: 448 });
556
687
  }
557
688
  }
558
689
  defaultState() {
@@ -607,7 +738,7 @@ var LocalRuntimeQueue = class {
607
738
  const acquiredAt = Date.now();
608
739
  while (true) {
609
740
  try {
610
- mkdirSync(this.lockPath, { mode: 448 });
741
+ mkdirSync2(this.lockPath, { mode: 448 });
611
742
  break;
612
743
  } catch (err) {
613
744
  const e = err;
@@ -688,6 +819,8 @@ var BridgeManager = class {
688
819
  requestDispatches = /* @__PURE__ */ new Map();
689
820
  cleanupTimer = null;
690
821
  runtimeQueue;
822
+ /** Pending WebRTC file transfers: transfer_id → FileSender + cleanup timer */
823
+ pendingTransfers = /* @__PURE__ */ new Map();
691
824
  constructor(opts) {
692
825
  this.wsClient = opts.wsClient;
693
826
  this.adapter = opts.adapter;
@@ -715,6 +848,7 @@ var BridgeManager = class {
715
848
  this.wiredSessions.clear();
716
849
  this.sessionLastSeenAt.clear();
717
850
  this.cleanupRequestDispatches("shutdown");
851
+ this.cleanupPendingTransfers();
718
852
  log.info("Bridge manager stopped");
719
853
  }
720
854
  /**
@@ -746,6 +880,9 @@ var BridgeManager = class {
746
880
  break;
747
881
  case "registered":
748
882
  break;
883
+ case "rtc_signal_relay":
884
+ this.handleRtcSignalRelay(msg);
885
+ break;
749
886
  default:
750
887
  log.warn(`Unknown message type from worker: ${msg.type}`);
751
888
  }
@@ -811,7 +948,7 @@ var BridgeManager = class {
811
948
  }
812
949
  async dispatchWithLocalQueue(opts) {
813
950
  const { msg, handle, requestKey } = opts;
814
- const { session_id, request_id, content, attachments, upload_url, upload_token, client_id, platform_task } = msg;
951
+ const { session_id, request_id, content, attachments, client_id, with_files } = msg;
815
952
  const state = this.requestDispatches.get(requestKey);
816
953
  if (!state) return;
817
954
  try {
@@ -830,9 +967,8 @@ var BridgeManager = class {
830
967
  await this.releaseRequestLease(session_id, request_id, "cancel");
831
968
  return;
832
969
  }
833
- const uploadCredentials = upload_url && upload_token ? { uploadUrl: upload_url, uploadToken: upload_token } : void 0;
834
970
  try {
835
- handle.send(content, attachments, uploadCredentials, client_id, platform_task);
971
+ handle.send(content, attachments, client_id, with_files);
836
972
  this.sessionLastSeenAt.set(session_id, Date.now());
837
973
  } catch (err) {
838
974
  log.error(`Failed to send to adapter: ${err}`);
@@ -893,20 +1029,24 @@ var BridgeManager = class {
893
1029
  handle.onDone((payload) => {
894
1030
  void this.releaseRequestLease(sessionId, requestRef.requestId, "done");
895
1031
  const attachments = payload?.attachments;
896
- const fileManifest = payload?.fileManifest;
1032
+ const fileTransferOffer = payload?.fileTransferOffer;
1033
+ const zipBuffer = payload?.zipBuffer;
1034
+ if (fileTransferOffer && zipBuffer) {
1035
+ this.registerPendingTransfer(fileTransferOffer, zipBuffer);
1036
+ }
897
1037
  const done = {
898
1038
  type: "done",
899
1039
  session_id: sessionId,
900
1040
  request_id: requestRef.requestId,
901
1041
  ...attachments && attachments.length > 0 && { attachments },
902
- ...fileManifest && fileManifest.length > 0 && { file_manifest: fileManifest },
1042
+ ...fileTransferOffer && { file_transfer_offer: fileTransferOffer },
903
1043
  ...fullResponseBuffer && { result: fullResponseBuffer }
904
1044
  };
905
1045
  this.trackRequest(sessionId, requestRef.requestId, "done");
906
1046
  this.wsClient.send(done);
907
1047
  fullResponseBuffer = "";
908
1048
  this.sessionLastSeenAt.set(sessionId, Date.now());
909
- const fileInfo = attachments && attachments.length > 0 ? ` (${attachments.length} files)` : "";
1049
+ const fileInfo = fileTransferOffer ? ` (${fileTransferOffer.file_count} files, transfer=${fileTransferOffer.transfer_id.slice(0, 8)}...)` : "";
910
1050
  log.info(`Request done: session=${sessionId.slice(0, 8)}... request=${requestRef.requestId.slice(0, 8)}...${fileInfo}`);
911
1051
  });
912
1052
  handle.onError((err) => {
@@ -1087,6 +1227,50 @@ var BridgeManager = class {
1087
1227
  }
1088
1228
  }
1089
1229
  }
1230
+ // ========================================================
1231
+ // WebRTC signaling relay
1232
+ // ========================================================
1233
+ registerPendingTransfer(offer, zipBuffer) {
1234
+ const sender = new FileSender(offer.transfer_id, zipBuffer);
1235
+ sender.onSignal((signal) => {
1236
+ const entry = this.pendingTransfers.get(offer.transfer_id);
1237
+ if (!entry) return;
1238
+ const rtcSignal = {
1239
+ type: "rtc_signal",
1240
+ transfer_id: offer.transfer_id,
1241
+ target_agent_id: entry.targetAgentId || "",
1242
+ signal_type: signal.signal_type,
1243
+ payload: signal.payload
1244
+ };
1245
+ this.wsClient.send(rtcSignal);
1246
+ });
1247
+ const timer = setTimeout(() => {
1248
+ sender.close();
1249
+ this.pendingTransfers.delete(offer.transfer_id);
1250
+ log.debug(`Transfer ${offer.transfer_id.slice(0, 8)}... expired`);
1251
+ }, 5 * 6e4);
1252
+ timer.unref?.();
1253
+ this.pendingTransfers.set(offer.transfer_id, { sender, timer });
1254
+ }
1255
+ handleRtcSignalRelay(msg) {
1256
+ const entry = this.pendingTransfers.get(msg.transfer_id);
1257
+ if (!entry) {
1258
+ log.debug(`No pending transfer for ${msg.transfer_id.slice(0, 8)}...`);
1259
+ return;
1260
+ }
1261
+ entry.targetAgentId = msg.from_agent_id;
1262
+ void entry.sender.handleSignal({
1263
+ signal_type: msg.signal_type,
1264
+ payload: msg.payload
1265
+ });
1266
+ }
1267
+ cleanupPendingTransfers() {
1268
+ for (const [id, entry] of this.pendingTransfers) {
1269
+ clearTimeout(entry.timer);
1270
+ entry.sender.close();
1271
+ }
1272
+ this.pendingTransfers.clear();
1273
+ }
1090
1274
  updateSessionCount() {
1091
1275
  this.wsClient.setActiveSessions(this.pool.size);
1092
1276
  }
@@ -1101,7 +1285,7 @@ import { spawn } from "child_process";
1101
1285
 
1102
1286
  // src/utils/sandbox.ts
1103
1287
  import { execSync } from "child_process";
1104
- import { join as join2 } from "path";
1288
+ import { join as join3 } from "path";
1105
1289
  var SRT_PACKAGE = "@anthropic-ai/sandbox-runtime";
1106
1290
  var SENSITIVE_PATHS = [
1107
1291
  // SSH & crypto keys
@@ -1160,7 +1344,7 @@ var sandboxInitialized = false;
1160
1344
  async function importSandboxManager() {
1161
1345
  try {
1162
1346
  const globalRoot = execSync("npm root -g", { encoding: "utf-8" }).trim();
1163
- const srtPath = join2(globalRoot, "@anthropic-ai/sandbox-runtime/dist/index.js");
1347
+ const srtPath = join3(globalRoot, "@anthropic-ai/sandbox-runtime/dist/index.js");
1164
1348
  const mod = await import(srtPath);
1165
1349
  return mod.SandboxManager;
1166
1350
  } catch {
@@ -1366,8 +1550,8 @@ function which(command) {
1366
1550
  }
1367
1551
 
1368
1552
  // src/utils/client-workspace.ts
1369
- import { mkdirSync as mkdirSync2, readdirSync, symlinkSync, existsSync as existsSync2, lstatSync } from "fs";
1370
- import { join as join3, relative } from "path";
1553
+ import { mkdirSync as mkdirSync3, readdirSync, symlinkSync, existsSync as existsSync3, lstatSync } from "fs";
1554
+ import { join as join4, relative } from "path";
1371
1555
  var SYMLINK_ALLOW = /* @__PURE__ */ new Set([
1372
1556
  "CLAUDE.md",
1373
1557
  ".claude",
@@ -1396,19 +1580,19 @@ function shouldInclude(name) {
1396
1580
  return false;
1397
1581
  }
1398
1582
  function createClientWorkspace(projectPath, clientId) {
1399
- const wsDir = join3(projectPath, ".bridge-clients", clientId);
1400
- const isNew = !existsSync2(wsDir);
1401
- mkdirSync2(wsDir, { recursive: true });
1583
+ const wsDir = join4(projectPath, ".bridge-clients", clientId);
1584
+ const isNew = !existsSync3(wsDir);
1585
+ mkdirSync3(wsDir, { recursive: true });
1402
1586
  const entries = readdirSync(projectPath, { withFileTypes: true });
1403
1587
  for (const entry of entries) {
1404
1588
  if (!shouldInclude(entry.name)) continue;
1405
- const link = join3(wsDir, entry.name);
1589
+ const link = join4(wsDir, entry.name);
1406
1590
  try {
1407
1591
  lstatSync(link);
1408
1592
  continue;
1409
1593
  } catch {
1410
1594
  }
1411
- const target = join3(projectPath, entry.name);
1595
+ const target = join4(projectPath, entry.name);
1412
1596
  const relTarget = relative(wsDir, target);
1413
1597
  try {
1414
1598
  symlinkSync(relTarget, link);
@@ -1423,12 +1607,12 @@ function createClientWorkspace(projectPath, clientId) {
1423
1607
  }
1424
1608
 
1425
1609
  // src/adapters/claude.ts
1426
- import { readFile as readFile2, writeFile, mkdir, stat as stat2 } from "fs/promises";
1427
- import { join as join5, relative as relative3, basename } from "path";
1610
+ import { writeFile, mkdir, stat as stat2 } from "fs/promises";
1611
+ import { join as join6, relative as relative3, basename } from "path";
1428
1612
 
1429
1613
  // src/utils/auto-upload.ts
1430
1614
  import { readdir, readFile, stat } from "fs/promises";
1431
- import { join as join4, relative as relative2 } from "path";
1615
+ import { join as join5, relative as relative2 } from "path";
1432
1616
  var MAX_AUTO_UPLOAD_FILE_SIZE = 10 * 1024 * 1024;
1433
1617
  var SKIP_DIRS = /* @__PURE__ */ new Set([
1434
1618
  ".git",
@@ -1440,23 +1624,6 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
1440
1624
  "coverage",
1441
1625
  ".turbo"
1442
1626
  ]);
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
1627
  async function collectRealFiles(dir, maxFiles = Infinity) {
1461
1628
  const files = [];
1462
1629
  const walk = async (d) => {
@@ -1470,7 +1637,7 @@ async function collectRealFiles(dir, maxFiles = Infinity) {
1470
1637
  for (const entry of entries) {
1471
1638
  if (files.length >= maxFiles) return;
1472
1639
  if (entry.isSymbolicLink()) continue;
1473
- const fullPath = join4(d, entry.name);
1640
+ const fullPath = join5(d, entry.name);
1474
1641
  if (entry.isDirectory()) {
1475
1642
  if (SKIP_DIRS.has(entry.name)) continue;
1476
1643
  await walk(fullPath);
@@ -1641,7 +1808,6 @@ var CLAUDE_RUNTIME_ALLOW_WRITE_PATHS = [
1641
1808
  `${HOME_DIR}/.claude.json.tmp`,
1642
1809
  `${HOME_DIR}/.local/state/claude`
1643
1810
  ];
1644
- var MAX_UPLOAD_FILE_SIZE = 200 * 1024 * 1024;
1645
1811
  var MAX_COLLECT_FILES = 5e3;
1646
1812
  var DEFAULT_ZIP_MAX_BYTES = 200 * 1024 * 1024;
1647
1813
  function resolveIdleTimeoutMs() {
@@ -1675,29 +1841,23 @@ var ClaudeSession = class {
1675
1841
  activeToolName = null;
1676
1842
  /** Track current content block type to distinguish thinking vs text deltas */
1677
1843
  currentBlockType = null;
1678
- /** Upload credentials provided by the platform for auto-uploading output files */
1679
- uploadCredentials = null;
1680
1844
  /** Per-client workspace path (symlink-based), set on each send() */
1681
1845
  currentWorkspace;
1682
- send(message, attachments, uploadCredentials, clientId, platformTask) {
1846
+ /** Whether caller requested file transfer */
1847
+ withFiles = false;
1848
+ send(message, attachments, clientId, withFiles) {
1683
1849
  this.resetIdleTimer();
1684
1850
  this.doneFired = false;
1685
1851
  this.chunksEmitted = false;
1686
1852
  this.activeToolCallId = null;
1687
1853
  this.activeToolName = null;
1688
1854
  this.currentBlockType = null;
1689
- if (uploadCredentials) {
1690
- this.uploadCredentials = uploadCredentials;
1691
- }
1855
+ this.withFiles = withFiles || false;
1692
1856
  if (clientId && this.config.project) {
1693
1857
  this.currentWorkspace = createClientWorkspace(this.config.project, clientId);
1694
1858
  } else {
1695
1859
  this.currentWorkspace = void 0;
1696
1860
  }
1697
- if (platformTask) {
1698
- void this.runPlatformTask(platformTask);
1699
- return;
1700
- }
1701
1861
  const args = ["-p", message, "--continue", "--output-format", "stream-json", "--verbose", "--include-partial-messages", "--dangerously-skip-permissions"];
1702
1862
  void this.downloadAttachments(attachments).then(() => {
1703
1863
  this.launchProcess(args);
@@ -1714,7 +1874,7 @@ var ClaudeSession = class {
1714
1874
  await mkdir(workspaceRoot, { recursive: true });
1715
1875
  for (const att of attachments) {
1716
1876
  const safeName = basename(att.name).replace(/[^a-zA-Z0-9._-]/g, "_") || "attachment";
1717
- const destPath = join5(workspaceRoot, safeName);
1877
+ const destPath = join6(workspaceRoot, safeName);
1718
1878
  try {
1719
1879
  const res = await fetch(att.url);
1720
1880
  if (!res.ok) {
@@ -1786,145 +1946,64 @@ var ClaudeSession = class {
1786
1946
  getWorkspaceRoot() {
1787
1947
  return this.currentWorkspace || this.config.project || process.cwd();
1788
1948
  }
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
1949
  async collectWorkspaceFiles(workspaceRoot) {
1890
1950
  return collectRealFiles(workspaceRoot, MAX_COLLECT_FILES);
1891
1951
  }
1892
- async collectWorkspaceManifest(workspaceRoot) {
1952
+ /**
1953
+ * Collect workspace files into a ZIP buffer + compute SHA-256.
1954
+ * Only called when with_files is true.
1955
+ */
1956
+ async createWorkspaceZip(workspaceRoot) {
1893
1957
  const files = await this.collectWorkspaceFiles(workspaceRoot);
1894
- const manifest = [];
1958
+ if (files.length === 0) return null;
1959
+ const entries = [];
1960
+ let totalBytes = 0;
1895
1961
  for (const absPath of files) {
1896
1962
  const relPath = relative3(workspaceRoot, absPath).replace(/\\/g, "/");
1897
1963
  if (!relPath || relPath.startsWith("..")) continue;
1898
1964
  try {
1899
1965
  const fileStat = await stat2(absPath);
1900
1966
  if (!fileStat.isFile()) continue;
1901
- const ext = relPath.split(".").pop()?.toLowerCase() || "";
1902
- manifest.push({
1903
- path: relPath,
1904
- size: fileStat.size,
1905
- mtime_ms: Math.floor(fileStat.mtimeMs),
1906
- type: MIME_MAP[ext] || "application/octet-stream"
1907
- });
1967
+ const { readFile: readFile4 } = await import("fs/promises");
1968
+ const buffer = await readFile4(absPath);
1969
+ if (buffer.length === 0) continue;
1970
+ totalBytes += buffer.length;
1971
+ if (totalBytes > DEFAULT_ZIP_MAX_BYTES) {
1972
+ log.warn(`Workspace exceeds ${DEFAULT_ZIP_MAX_BYTES / 1024 / 1024}MB limit, truncating`);
1973
+ break;
1974
+ }
1975
+ entries.push({ path: relPath, data: buffer });
1908
1976
  } catch {
1909
1977
  }
1910
1978
  }
1911
- manifest.sort((a, b) => a.path.localeCompare(b.path));
1912
- return manifest;
1979
+ if (entries.length === 0) return null;
1980
+ const zipBuffer = createZipBuffer(entries);
1981
+ return { zipBuffer, fileCount: entries.length };
1913
1982
  }
1914
1983
  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
1984
  const payload = {};
1923
1985
  if (attachments && attachments.length > 0) {
1924
1986
  payload.attachments = attachments;
1925
1987
  }
1926
- if (fileManifest) {
1927
- payload.fileManifest = fileManifest;
1988
+ if (this.withFiles) {
1989
+ const workspaceRoot = this.getWorkspaceRoot();
1990
+ try {
1991
+ const result = await this.createWorkspaceZip(workspaceRoot);
1992
+ if (result) {
1993
+ const transferId = crypto.randomUUID();
1994
+ const zipSha256 = sha256Hex(result.zipBuffer);
1995
+ payload.fileTransferOffer = {
1996
+ transfer_id: transferId,
1997
+ zip_size: result.zipBuffer.length,
1998
+ zip_sha256: zipSha256,
1999
+ file_count: result.fileCount
2000
+ };
2001
+ payload.zipBuffer = result.zipBuffer;
2002
+ log.info(`[WebRTC] ZIP ready: ${result.fileCount} files, ${result.zipBuffer.length} bytes, transfer=${transferId.slice(0, 8)}...`);
2003
+ }
2004
+ } catch (error) {
2005
+ log.warn(`ZIP creation failed: ${error}`);
2006
+ }
1928
2007
  }
1929
2008
  for (const cb of this.doneCallbacks) cb(payload);
1930
2009
  }
@@ -2071,34 +2150,28 @@ var ClaudeSession = class {
2071
2150
  return;
2072
2151
  }
2073
2152
  }
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
2153
  emitChunk(text) {
2081
2154
  this.chunksEmitted = true;
2082
- for (const cb of this.chunkCallbacks) cb(this.stripWorkspacePaths(text));
2155
+ for (const cb of this.chunkCallbacks) cb(text);
2083
2156
  }
2084
2157
  emitToolEvent(event) {
2085
- for (const cb of this.toolCallbacks) cb({ ...event, delta: this.stripWorkspacePaths(event.delta) });
2158
+ for (const cb of this.toolCallbacks) cb(event);
2086
2159
  }
2087
2160
  emitTextAsChunks(text) {
2088
- const CHUNK_SIZE = 60;
2089
- if (text.length <= CHUNK_SIZE) {
2161
+ const CHUNK_SIZE2 = 60;
2162
+ if (text.length <= CHUNK_SIZE2) {
2090
2163
  this.emitChunk(text);
2091
2164
  return;
2092
2165
  }
2093
2166
  let pos = 0;
2094
2167
  while (pos < text.length) {
2095
- let end = Math.min(pos + CHUNK_SIZE, text.length);
2168
+ let end = Math.min(pos + CHUNK_SIZE2, text.length);
2096
2169
  if (end < text.length) {
2097
2170
  const slice = text.slice(pos, end + 20);
2098
2171
  const breakPoints = ["\n", "\u3002", "\uFF01", "\uFF1F", ". ", "! ", "? ", "\uFF0C", ", ", " "];
2099
2172
  for (const bp of breakPoints) {
2100
- const idx = slice.indexOf(bp, CHUNK_SIZE - 20);
2101
- if (idx >= 0 && idx < CHUNK_SIZE + 20) {
2173
+ const idx = slice.indexOf(bp, CHUNK_SIZE2 - 20);
2174
+ if (idx >= 0 && idx < CHUNK_SIZE2 + 20) {
2102
2175
  end = pos + idx + bp.length;
2103
2176
  break;
2104
2177
  }
@@ -2718,7 +2791,7 @@ function registerRestartCommand(program2) {
2718
2791
 
2719
2792
  // src/commands/logs.ts
2720
2793
  import { spawn as spawn2 } from "child_process";
2721
- import { existsSync as existsSync3 } from "fs";
2794
+ import { existsSync as existsSync4 } from "fs";
2722
2795
  function registerLogsCommand(program2) {
2723
2796
  program2.command("logs <name>").description("View agent logs (follows in real-time)").option("-n, --lines <number>", "Number of lines to show", "50").action((name, opts) => {
2724
2797
  const entry = getAgent(name);
@@ -2727,7 +2800,7 @@ function registerLogsCommand(program2) {
2727
2800
  process.exit(1);
2728
2801
  }
2729
2802
  const logPath = getLogPath(name);
2730
- if (!existsSync3(logPath)) {
2803
+ if (!existsSync4(logPath)) {
2731
2804
  log.error(`No log file found for "${name}". Has this agent been started before?`);
2732
2805
  process.exit(1);
2733
2806
  }
@@ -2808,14 +2881,14 @@ function registerOpenCommand(program2) {
2808
2881
  }
2809
2882
 
2810
2883
  // src/commands/install.ts
2811
- import { writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
2812
- import { join as join6 } from "path";
2884
+ import { writeFileSync as writeFileSync2, existsSync as existsSync5, mkdirSync as mkdirSync4 } from "fs";
2885
+ import { join as join7 } from "path";
2813
2886
  import { homedir as homedir4 } from "os";
2814
2887
  import { execSync as execSync2 } from "child_process";
2815
2888
  var LABEL = "com.agents-hot.agent-mesh";
2816
- var PLIST_DIR = join6(homedir4(), "Library", "LaunchAgents");
2817
- var PLIST_PATH = join6(PLIST_DIR, `${LABEL}.plist`);
2818
- var LOG_PATH = join6(homedir4(), ".agent-mesh", "logs", "launchd.log");
2889
+ var PLIST_DIR = join7(homedir4(), "Library", "LaunchAgents");
2890
+ var PLIST_PATH = join7(PLIST_DIR, `${LABEL}.plist`);
2891
+ var LOG_PATH = join7(homedir4(), ".agent-mesh", "logs", "launchd.log");
2819
2892
  function detectPaths() {
2820
2893
  return {
2821
2894
  node: process.execPath,
@@ -2862,7 +2935,7 @@ function registerInstallCommand(program2) {
2862
2935
  log.error("LaunchAgent is macOS only. On Linux, use systemd user service instead.");
2863
2936
  process.exit(1);
2864
2937
  }
2865
- if (existsSync4(PLIST_PATH) && !opts.force) {
2938
+ if (existsSync5(PLIST_PATH) && !opts.force) {
2866
2939
  console.log(`
2867
2940
  ${YELLOW}\u2298${RESET} LaunchAgent already installed at:`);
2868
2941
  console.log(` ${GRAY}${PLIST_PATH}${RESET}`);
@@ -2872,10 +2945,10 @@ function registerInstallCommand(program2) {
2872
2945
  return;
2873
2946
  }
2874
2947
  const { node, script } = detectPaths();
2875
- if (!existsSync4(PLIST_DIR)) {
2876
- mkdirSync3(PLIST_DIR, { recursive: true });
2948
+ if (!existsSync5(PLIST_DIR)) {
2949
+ mkdirSync4(PLIST_DIR, { recursive: true });
2877
2950
  }
2878
- if (existsSync4(PLIST_PATH)) {
2951
+ if (existsSync5(PLIST_PATH)) {
2879
2952
  try {
2880
2953
  execSync2(`launchctl bootout gui/$(id -u) "${PLIST_PATH}" 2>/dev/null`, { stdio: "ignore" });
2881
2954
  } catch {
@@ -2908,19 +2981,19 @@ function registerInstallCommand(program2) {
2908
2981
  }
2909
2982
 
2910
2983
  // src/commands/uninstall.ts
2911
- import { existsSync as existsSync5, unlinkSync as unlinkSync2 } from "fs";
2912
- import { join as join7 } from "path";
2984
+ import { existsSync as existsSync6, unlinkSync as unlinkSync2 } from "fs";
2985
+ import { join as join8 } from "path";
2913
2986
  import { homedir as homedir5 } from "os";
2914
2987
  import { execSync as execSync3 } from "child_process";
2915
2988
  var LABEL2 = "com.agents-hot.agent-mesh";
2916
- var PLIST_PATH2 = join7(homedir5(), "Library", "LaunchAgents", `${LABEL2}.plist`);
2989
+ var PLIST_PATH2 = join8(homedir5(), "Library", "LaunchAgents", `${LABEL2}.plist`);
2917
2990
  function registerUninstallCommand(program2) {
2918
2991
  program2.command("uninstall").description("Remove macOS LaunchAgent (agents will no longer auto-start)").action(async () => {
2919
2992
  if (process.platform !== "darwin") {
2920
2993
  log.error("LaunchAgent is macOS only.");
2921
2994
  process.exit(1);
2922
2995
  }
2923
- if (!existsSync5(PLIST_PATH2)) {
2996
+ if (!existsSync6(PLIST_PATH2)) {
2924
2997
  console.log(`
2925
2998
  ${YELLOW}\u2298${RESET} No LaunchAgent found at ${GRAY}${PLIST_PATH2}${RESET}
2926
2999
  `);
@@ -3399,8 +3472,8 @@ async function asyncChat(opts) {
3399
3472
  `);
3400
3473
  }
3401
3474
  }
3402
- if (task.file_manifest) {
3403
- process.stdout.write(`${GRAY}[manifest: ${task.file_manifest.length} files]${RESET}
3475
+ if (task.file_transfer_offer) {
3476
+ process.stdout.write(`${GRAY}[files: ${task.file_transfer_offer.file_count} available via WebRTC]${RESET}
3404
3477
  `);
3405
3478
  }
3406
3479
  return;
@@ -3605,12 +3678,12 @@ function registerChatCommand(program2) {
3605
3678
  }
3606
3679
 
3607
3680
  // src/commands/skills.ts
3608
- import { readFile as readFile4, writeFile as writeFile3, readdir as readdir2, mkdir as mkdir2, rm, symlink, unlink } from "fs/promises";
3609
- import { join as join9, resolve, relative as relative4 } from "path";
3681
+ import { readFile as readFile3, writeFile as writeFile3, readdir as readdir2, mkdir as mkdir2, rm, symlink, unlink } from "fs/promises";
3682
+ import { join as join10, resolve, relative as relative4 } from "path";
3610
3683
 
3611
3684
  // src/utils/skill-parser.ts
3612
- import { readFile as readFile3, writeFile as writeFile2, stat as stat3 } from "fs/promises";
3613
- import { join as join8 } from "path";
3685
+ import { readFile as readFile2, writeFile as writeFile2, stat as stat3 } from "fs/promises";
3686
+ import { join as join9 } from "path";
3614
3687
  function parseSkillMd(raw) {
3615
3688
  const trimmed = raw.trimStart();
3616
3689
  if (!trimmed.startsWith("---")) {
@@ -3692,9 +3765,9 @@ function parseSkillMd(raw) {
3692
3765
  return { frontmatter, content };
3693
3766
  }
3694
3767
  async function loadSkillManifest(dir) {
3695
- const skillMdPath = join8(dir, "SKILL.md");
3768
+ const skillMdPath = join9(dir, "SKILL.md");
3696
3769
  try {
3697
- const raw = await readFile3(skillMdPath, "utf-8");
3770
+ const raw = await readFile2(skillMdPath, "utf-8");
3698
3771
  const { frontmatter } = parseSkillMd(raw);
3699
3772
  const name = frontmatter.name;
3700
3773
  if (!name) {
@@ -3727,7 +3800,7 @@ async function pathExists(p) {
3727
3800
  }
3728
3801
  }
3729
3802
  async function updateFrontmatterField(filePath, field, value) {
3730
- const raw = await readFile3(filePath, "utf-8");
3803
+ const raw = await readFile2(filePath, "utf-8");
3731
3804
  const trimmed = raw.trimStart();
3732
3805
  if (!trimmed.startsWith("---")) {
3733
3806
  throw new Error("SKILL.md has no frontmatter block");
@@ -3797,13 +3870,13 @@ function skillApiPath(authorLogin, slug) {
3797
3870
  }
3798
3871
  async function resolveSkillsRootAsync(pathArg) {
3799
3872
  const projectRoot = pathArg ? resolve(pathArg) : process.cwd();
3800
- const skillsDir = join9(projectRoot, ".agents", "skills");
3801
- const claudeSkillsDir = join9(projectRoot, ".claude", "skills");
3873
+ const skillsDir = join10(projectRoot, ".agents", "skills");
3874
+ const claudeSkillsDir = join10(projectRoot, ".claude", "skills");
3802
3875
  return { projectRoot, skillsDir, claudeSkillsDir };
3803
3876
  }
3804
3877
  async function ensureClaudeSymlink(claudeSkillsDir, slug) {
3805
3878
  await mkdir2(claudeSkillsDir, { recursive: true });
3806
- const linkPath = join9(claudeSkillsDir, slug);
3879
+ const linkPath = join10(claudeSkillsDir, slug);
3807
3880
  try {
3808
3881
  await unlink(linkPath);
3809
3882
  } catch {
@@ -3823,7 +3896,7 @@ async function collectPackFiles(dir, manifest) {
3823
3896
  }
3824
3897
  const mainFile = manifest.main || "SKILL.md";
3825
3898
  if (!results.includes(mainFile)) {
3826
- const mainPath = join9(dir, mainFile);
3899
+ const mainPath = join10(dir, mainFile);
3827
3900
  if (await pathExists(mainPath)) {
3828
3901
  results.unshift(mainFile);
3829
3902
  }
@@ -3840,7 +3913,7 @@ async function walkDir(dir) {
3840
3913
  }
3841
3914
  for (const entry of entries) {
3842
3915
  if (entry.isSymbolicLink()) continue;
3843
- const fullPath = join9(dir, entry.name);
3916
+ const fullPath = join10(dir, entry.name);
3844
3917
  if (entry.isDirectory()) {
3845
3918
  if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
3846
3919
  const sub = await walkDir(fullPath);
@@ -3858,9 +3931,9 @@ async function packSkill(dir, manifest) {
3858
3931
  }
3859
3932
  const entries = [];
3860
3933
  for (const relPath of fileList) {
3861
- const absPath = join9(dir, relPath);
3934
+ const absPath = join10(dir, relPath);
3862
3935
  try {
3863
- const data = await readFile4(absPath);
3936
+ const data = await readFile3(absPath);
3864
3937
  entries.push({ path: relPath.replace(/\\/g, "/"), data });
3865
3938
  } catch {
3866
3939
  slog.warn(`Skipping unreadable file: ${relPath}`);
@@ -3892,7 +3965,7 @@ function bumpVersion(current, bump) {
3892
3965
  }
3893
3966
  async function downloadAndInstallSkill(client, authorLogin, slug, skillsDir) {
3894
3967
  const meta = await client.get(skillApiPath(authorLogin, slug));
3895
- const targetDir = join9(skillsDir, slug);
3968
+ const targetDir = join10(skillsDir, slug);
3896
3969
  await mkdir2(targetDir, { recursive: true });
3897
3970
  if (meta.has_files) {
3898
3971
  const res = await client.getRaw(`${skillApiPath(authorLogin, slug)}/download`);
@@ -3900,8 +3973,8 @@ async function downloadAndInstallSkill(client, authorLogin, slug, skillsDir) {
3900
3973
  const buf = Buffer.from(arrayBuf);
3901
3974
  const entries = extractZipBuffer(buf);
3902
3975
  for (const entry of entries) {
3903
- const filePath = join9(targetDir, entry.path);
3904
- const dir = join9(filePath, "..");
3976
+ const filePath = join10(targetDir, entry.path);
3977
+ const dir = join10(filePath, "..");
3905
3978
  await mkdir2(dir, { recursive: true });
3906
3979
  await writeFile3(filePath, entry.data);
3907
3980
  }
@@ -3914,7 +3987,7 @@ async function downloadAndInstallSkill(client, authorLogin, slug, skillsDir) {
3914
3987
  } else {
3915
3988
  const res = await client.getRaw(`${skillApiPath(authorLogin, slug)}/raw`);
3916
3989
  const content = await res.text();
3917
- await writeFile3(join9(targetDir, "SKILL.md"), content);
3990
+ await writeFile3(join10(targetDir, "SKILL.md"), content);
3918
3991
  return {
3919
3992
  slug,
3920
3993
  name: meta.name,
@@ -3943,9 +4016,9 @@ function registerSkillsCommand(program2) {
3943
4016
  try {
3944
4017
  const dir = resolveSkillDir(pathArg);
3945
4018
  await mkdir2(dir, { recursive: true });
3946
- const skillMdPath = join9(dir, "SKILL.md");
4019
+ const skillMdPath = join10(dir, "SKILL.md");
3947
4020
  if (await pathExists(skillMdPath)) {
3948
- const raw = await readFile4(skillMdPath, "utf-8");
4021
+ const raw = await readFile3(skillMdPath, "utf-8");
3949
4022
  const { frontmatter } = parseSkillMd(raw);
3950
4023
  if (frontmatter.name) {
3951
4024
  slog.info(`SKILL.md already exists with name: ${frontmatter.name}`);
@@ -3972,7 +4045,7 @@ function registerSkillsCommand(program2) {
3972
4045
  const dir = resolveSkillDir(pathArg);
3973
4046
  const manifest = await loadSkillManifest(dir);
3974
4047
  const result = await packSkill(dir, manifest);
3975
- const outPath = join9(dir, result.filename);
4048
+ const outPath = join10(dir, result.filename);
3976
4049
  await writeFile3(outPath, result.buffer);
3977
4050
  slog.info(`Packed ${result.files.length} files \u2192 ${result.filename} (${result.size} bytes)`);
3978
4051
  outputJson({
@@ -4018,7 +4091,7 @@ function registerSkillsCommand(program2) {
4018
4091
  if (opts.name) manifest.name = opts.name;
4019
4092
  if (opts.version) manifest.version = opts.version;
4020
4093
  if (opts.private !== void 0) manifest.private = opts.private;
4021
- content = await readFile4(join9(dir, manifest.main || "SKILL.md"), "utf-8");
4094
+ content = await readFile3(join10(dir, manifest.main || "SKILL.md"), "utf-8");
4022
4095
  packResult = await packSkill(dir, manifest);
4023
4096
  slog.info(`Packed ${packResult.files.length} files (${packResult.size} bytes)`);
4024
4097
  }
@@ -4155,11 +4228,11 @@ function registerSkillsCommand(program2) {
4155
4228
  skills.command("version <bump> [path]").description("Bump skill version (patch | minor | major | x.y.z)").action(async (bump, pathArg) => {
4156
4229
  try {
4157
4230
  const dir = resolveSkillDir(pathArg);
4158
- const skillMdPath = join9(dir, "SKILL.md");
4231
+ const skillMdPath = join10(dir, "SKILL.md");
4159
4232
  if (!await pathExists(skillMdPath)) {
4160
4233
  outputError("not_found", "No SKILL.md found. Run `agent-mesh skills init` first.");
4161
4234
  }
4162
- const raw = await readFile4(skillMdPath, "utf-8");
4235
+ const raw = await readFile3(skillMdPath, "utf-8");
4163
4236
  const { frontmatter } = parseSkillMd(raw);
4164
4237
  const oldVersion = frontmatter.version || "0.0.0";
4165
4238
  const newVersion = bumpVersion(oldVersion, bump);
@@ -4175,7 +4248,7 @@ function registerSkillsCommand(program2) {
4175
4248
  try {
4176
4249
  const { authorLogin, slug } = parseSkillRef(ref);
4177
4250
  const { skillsDir, claudeSkillsDir } = await resolveSkillsRootAsync(pathArg);
4178
- const targetDir = join9(skillsDir, slug);
4251
+ const targetDir = join10(skillsDir, slug);
4179
4252
  if (await pathExists(targetDir)) {
4180
4253
  if (!opts.force) {
4181
4254
  outputError("already_installed", `Skill "${slug}" is already installed at ${targetDir}. Use --force to overwrite.`);
@@ -4214,14 +4287,14 @@ function registerSkillsCommand(program2) {
4214
4287
  const failed = [];
4215
4288
  if (ref) {
4216
4289
  const { authorLogin, slug } = parseSkillRef(ref);
4217
- const targetDir = join9(skillsDir, slug);
4290
+ const targetDir = join10(skillsDir, slug);
4218
4291
  if (!await pathExists(targetDir)) {
4219
4292
  outputError("not_installed", `Skill "${slug}" is not installed. Use "skills install ${ref}" first.`);
4220
4293
  }
4221
- const skillMdPath = join9(targetDir, "SKILL.md");
4294
+ const skillMdPath = join10(targetDir, "SKILL.md");
4222
4295
  let localVersion = "0.0.0";
4223
4296
  if (await pathExists(skillMdPath)) {
4224
- const raw = await readFile4(skillMdPath, "utf-8");
4297
+ const raw = await readFile3(skillMdPath, "utf-8");
4225
4298
  const { frontmatter } = parseSkillMd(raw);
4226
4299
  localVersion = frontmatter.version || "0.0.0";
4227
4300
  }
@@ -4245,12 +4318,12 @@ function registerSkillsCommand(program2) {
4245
4318
  for (const entry of entries) {
4246
4319
  if (!entry.isDirectory()) continue;
4247
4320
  const slug = entry.name;
4248
- const skillMdPath = join9(skillsDir, slug, "SKILL.md");
4321
+ const skillMdPath = join10(skillsDir, slug, "SKILL.md");
4249
4322
  if (!await pathExists(skillMdPath)) {
4250
4323
  skipped.push({ slug, reason: "no_skill_md" });
4251
4324
  continue;
4252
4325
  }
4253
- const raw = await readFile4(skillMdPath, "utf-8");
4326
+ const raw = await readFile3(skillMdPath, "utf-8");
4254
4327
  const { frontmatter } = parseSkillMd(raw);
4255
4328
  const localVersion = frontmatter.version || "0.0.0";
4256
4329
  const authorLogin = frontmatter.author;
@@ -4265,7 +4338,7 @@ function registerSkillsCommand(program2) {
4265
4338
  skipped.push({ slug, reason: "up_to_date" });
4266
4339
  } else {
4267
4340
  slog.info(`Updating ${slug}: v${localVersion} \u2192 v${remoteVersion}...`);
4268
- await rm(join9(skillsDir, slug), { recursive: true, force: true });
4341
+ await rm(join10(skillsDir, slug), { recursive: true, force: true });
4269
4342
  await downloadAndInstallSkill(client, authorLogin, slug, skillsDir);
4270
4343
  updated.push({ slug, name: remote.name, old_version: localVersion, new_version: remoteVersion });
4271
4344
  }
@@ -4286,13 +4359,13 @@ function registerSkillsCommand(program2) {
4286
4359
  skills.command("remove <slug> [path]").description("Remove a locally installed skill").action(async (slug, pathArg) => {
4287
4360
  try {
4288
4361
  const { skillsDir, claudeSkillsDir } = await resolveSkillsRootAsync(pathArg);
4289
- const targetDir = join9(skillsDir, slug);
4362
+ const targetDir = join10(skillsDir, slug);
4290
4363
  if (!await pathExists(targetDir)) {
4291
4364
  outputError("not_installed", `Skill "${slug}" is not installed at ${targetDir}`);
4292
4365
  }
4293
4366
  await rm(targetDir, { recursive: true, force: true });
4294
4367
  try {
4295
- await unlink(join9(claudeSkillsDir, slug));
4368
+ await unlink(join10(claudeSkillsDir, slug));
4296
4369
  } catch {
4297
4370
  }
4298
4371
  slog.success(`Removed skill: ${slug}`);
@@ -4317,9 +4390,9 @@ function registerSkillsCommand(program2) {
4317
4390
  for (const entry of entries) {
4318
4391
  if (!entry.isDirectory()) continue;
4319
4392
  const slug = entry.name;
4320
- const skillMdPath = join9(skillsDir, slug, "SKILL.md");
4393
+ const skillMdPath = join10(skillsDir, slug, "SKILL.md");
4321
4394
  if (!await pathExists(skillMdPath)) continue;
4322
- const raw = await readFile4(skillMdPath, "utf-8");
4395
+ const raw = await readFile3(skillMdPath, "utf-8");
4323
4396
  const { frontmatter } = parseSkillMd(raw);
4324
4397
  const skillInfo = {
4325
4398
  slug,
@@ -4470,7 +4543,11 @@ async function asyncCall(opts) {
4470
4543
  "Content-Type": "application/json",
4471
4544
  ...selfAgentId ? { "X-Caller-Agent-Id": selfAgentId } : {}
4472
4545
  },
4473
- body: JSON.stringify({ task_description: opts.taskDescription, mode: "async" }),
4546
+ body: JSON.stringify({
4547
+ task_description: opts.taskDescription,
4548
+ mode: "async",
4549
+ ...opts.withFiles ? { with_files: true } : {}
4550
+ }),
4474
4551
  signal: opts.signal
4475
4552
  });
4476
4553
  if (!res.ok) {
@@ -4529,7 +4606,7 @@ async function asyncCall(opts) {
4529
4606
  status: "completed",
4530
4607
  result,
4531
4608
  ...task.attachments?.length ? { attachments: task.attachments } : {},
4532
- ...Array.isArray(task.file_manifest) ? { file_manifest: task.file_manifest } : {},
4609
+ ...task.file_transfer_offer ? { file_transfer_offer: task.file_transfer_offer } : {},
4533
4610
  rate_hint: `POST /api/agents/${opts.id}/rate body: { call_id: "${call_id}", rating: 1-5 }`
4534
4611
  }));
4535
4612
  } else {
@@ -4539,9 +4616,9 @@ async function asyncCall(opts) {
4539
4616
  log.info(` ${GRAY}File:${RESET} ${att.name} ${GRAY}${att.url}${RESET}`);
4540
4617
  }
4541
4618
  }
4542
- const manifest = task.file_manifest;
4543
- if (Array.isArray(manifest)) {
4544
- log.info(` ${GRAY}Manifest:${RESET} ${manifest.length} file(s)`);
4619
+ const offer = task.file_transfer_offer;
4620
+ if (offer) {
4621
+ log.info(` ${GRAY}Files:${RESET} ${offer.file_count} file(s) available via WebRTC`);
4545
4622
  }
4546
4623
  if (session_key) {
4547
4624
  log.info(` ${GRAY}Session:${RESET} ${session_key}`);
@@ -4585,7 +4662,10 @@ async function streamCall(opts) {
4585
4662
  Accept: "text/event-stream",
4586
4663
  ...selfAgentId ? { "X-Caller-Agent-Id": selfAgentId } : {}
4587
4664
  },
4588
- body: JSON.stringify({ task_description: opts.taskDescription }),
4665
+ body: JSON.stringify({
4666
+ task_description: opts.taskDescription,
4667
+ ...opts.withFiles ? { with_files: true } : {}
4668
+ }),
4589
4669
  signal: opts.signal
4590
4670
  });
4591
4671
  if (!res.ok) {
@@ -4668,13 +4748,15 @@ async function streamCall(opts) {
4668
4748
  outputBuffer += delta;
4669
4749
  }
4670
4750
  }
4671
- } else if (event.type === "done" && event.attachments?.length) {
4672
- console.log("");
4673
- for (const att of event.attachments) {
4674
- log.info(` ${GRAY}File:${RESET} ${att.name} ${GRAY}${att.url}${RESET}`);
4751
+ } else if (event.type === "done") {
4752
+ if (event.attachments?.length) {
4753
+ console.log("");
4754
+ for (const att of event.attachments) {
4755
+ log.info(` ${GRAY}File:${RESET} ${att.name} ${GRAY}${att.url}${RESET}`);
4756
+ }
4675
4757
  }
4676
- if (Array.isArray(event.file_manifest)) {
4677
- log.info(` ${GRAY}Manifest:${RESET} ${event.file_manifest.length} file(s)`);
4758
+ if (event.file_transfer_offer) {
4759
+ log.info(` ${GRAY}Files:${RESET} ${event.file_transfer_offer.file_count} file(s) available via WebRTC`);
4678
4760
  }
4679
4761
  } else if (event.type === "error") {
4680
4762
  process.stderr.write(`
@@ -4724,7 +4806,7 @@ Error: ${event.message}
4724
4806
  return { callId, ...sessionKey ? { sessionKey } : {} };
4725
4807
  }
4726
4808
  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) => {
4809
+ 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
4810
  try {
4729
4811
  const token = loadToken();
4730
4812
  if (!token) {
@@ -4753,7 +4835,8 @@ ${content}`;
4753
4835
  timeoutMs,
4754
4836
  json: opts.json,
4755
4837
  outputFile: opts.outputFile,
4756
- signal: abortController.signal
4838
+ signal: abortController.signal,
4839
+ withFiles: opts.withFiles
4757
4840
  };
4758
4841
  let result;
4759
4842
  if (opts.stream) {
@@ -5220,21 +5303,11 @@ function registerProfileCommand(program2) {
5220
5303
  }
5221
5304
 
5222
5305
  // src/commands/files.ts
5223
- import { writeFileSync as writeFileSync4 } from "fs";
5224
- import { basename as basename2 } from "path";
5225
5306
  function formatBytes(bytes) {
5226
5307
  if (bytes < 1024) return `${bytes}B`;
5227
5308
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
5228
5309
  return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
5229
5310
  }
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
5311
  function handleError7(err) {
5239
5312
  if (err instanceof PlatformApiError) {
5240
5313
  log.error(err.message);
@@ -5247,16 +5320,8 @@ async function resolveTargetAgent(agentInput) {
5247
5320
  const client = createClient();
5248
5321
  return resolveAgentId(agentInput, client);
5249
5322
  }
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
5323
  function registerFilesCommand(program2) {
5259
- const files = program2.command("files").description("Session file manifest and on-demand upload/download commands");
5324
+ const files = program2.command("files").description("Session file commands (WebRTC P2P transfer)");
5260
5325
  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
5326
  try {
5262
5327
  const { id, name } = await resolveTargetAgent(opts.agent);
@@ -5279,90 +5344,7 @@ function registerFilesCommand(program2) {
5279
5344
  console.log(` ${f.path} ${GRAY}${formatBytes(f.size)}${RESET}`);
5280
5345
  }
5281
5346
  console.log("");
5282
- } catch (err) {
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}`);
5347
+ console.log(` ${GRAY}Use --with-files in call/chat to receive files via WebRTC P2P${RESET}`);
5366
5348
  } catch (err) {
5367
5349
  handleError7(err);
5368
5350
  }
@@ -5372,12 +5354,9 @@ function registerFilesCommand(program2) {
5372
5354
  command: "agent-mesh files",
5373
5355
  docs: "https://agents.hot/docs/cli/files",
5374
5356
  commands: [
5375
- { name: "list", required: ["--agent", "--session"], optional: ["--json"] },
5376
- { name: "upload", required: ["--agent", "--session", "--path"], optional: ["--json"] },
5377
- { name: "upload-all", required: ["--agent", "--session"], optional: ["--json"] },
5378
- { name: "download", required: ["--agent", "--session", "--path"], optional: ["--output", "--json"] },
5379
- { name: "download-all", required: ["--agent", "--session"], optional: ["--output", "--json"] }
5380
- ]
5357
+ { name: "list", required: ["--agent", "--session"], optional: ["--json"] }
5358
+ ],
5359
+ notes: "File transfer now uses WebRTC P2P. Use --with-files flag in call/chat commands."
5381
5360
  };
5382
5361
  if (opts.json) {
5383
5362
  console.log(JSON.stringify(reference));
@@ -5388,6 +5367,8 @@ function registerFilesCommand(program2) {
5388
5367
  for (const item of reference.commands) {
5389
5368
  console.log(` ${item.name}`);
5390
5369
  }
5370
+ console.log("");
5371
+ console.log(` ${GRAY}${reference.notes}${RESET}`);
5391
5372
  });
5392
5373
  }
5393
5374
 
@@ -5506,7 +5487,7 @@ function maybeAutoUpgradeOnStartup(opts) {
5506
5487
  }
5507
5488
 
5508
5489
  // src/index.ts
5509
- var require2 = createRequire(import.meta.url);
5490
+ var require2 = createRequire2(import.meta.url);
5510
5491
  var { version } = require2("../package.json");
5511
5492
  var autoUpgrade = maybeAutoUpgradeOnStartup({ currentVersion: version });
5512
5493
  if (autoUpgrade.relaunched) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@annals/agent-mesh",
3
- "version": "0.16.13",
3
+ "version": "0.17.2",
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": {
@@ -18,7 +19,8 @@
18
19
  "typescript": "^5.7.0"
19
20
  },
20
21
  "files": [
21
- "dist"
22
+ "dist",
23
+ "prebuilds"
22
24
  ],
23
25
  "license": "MIT",
24
26
  "repository": {