open-agents-ai 0.103.15 → 0.103.17

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.
Files changed (2) hide show
  1. package/dist/index.js +535 -56
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6598,8 +6598,8 @@ async function loadTranscribeCli() {
6598
6598
  const nvmBase = join16(homedir6(), ".nvm", "versions", "node");
6599
6599
  if (existsSync13(nvmBase)) {
6600
6600
  try {
6601
- const { readdirSync: readdirSync15 } = await import("node:fs");
6602
- for (const ver of readdirSync15(nvmBase)) {
6601
+ const { readdirSync: readdirSync16 } = await import("node:fs");
6602
+ for (const ver of readdirSync16(nvmBase)) {
6603
6603
  const tcPath = join16(nvmBase, ver, "lib", "node_modules", "transcribe-cli");
6604
6604
  if (existsSync13(join16(tcPath, "dist", "index.js"))) {
6605
6605
  const { createRequire: createRequire4 } = await import("node:module");
@@ -13062,7 +13062,7 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
13062
13062
  result = await this.sendDaemonCmd("room_members", args);
13063
13063
  break;
13064
13064
  case "expose":
13065
- result = await this.sendDaemonCmd("expose", args, 3e4);
13065
+ result = await this.sendDaemonCmd("expose", args, 6e4);
13066
13066
  break;
13067
13067
  case "pricing_menu":
13068
13068
  result = await this.sendDaemonCmd("pricing_menu", args);
@@ -20590,8 +20590,8 @@ var init_listen = __esm({
20590
20590
  const nvmBase = join34(homedir8(), ".nvm", "versions", "node");
20591
20591
  if (existsSync25(nvmBase)) {
20592
20592
  try {
20593
- const { readdirSync: readdirSync15 } = await import("node:fs");
20594
- for (const ver of readdirSync15(nvmBase)) {
20593
+ const { readdirSync: readdirSync16 } = await import("node:fs");
20594
+ for (const ver of readdirSync16(nvmBase)) {
20595
20595
  const tcPath = join34(nvmBase, ver, "lib", "node_modules", "transcribe-cli");
20596
20596
  if (existsSync25(join34(tcPath, "dist", "index.js"))) {
20597
20597
  const { createRequire: createRequire4 } = await import("node:module");
@@ -26203,7 +26203,7 @@ import { EventEmitter as EventEmitter3 } from "node:events";
26203
26203
  import { randomBytes as randomBytes7 } from "node:crypto";
26204
26204
  import { URL as URL2 } from "node:url";
26205
26205
  import { loadavg, cpus, totalmem, freemem } from "node:os";
26206
- import { existsSync as existsSync26, readFileSync as readFileSync18, writeFileSync as writeFileSync8, unlinkSync as unlinkSync3, mkdirSync as mkdirSync8 } from "node:fs";
26206
+ import { existsSync as existsSync26, readFileSync as readFileSync18, writeFileSync as writeFileSync8, unlinkSync as unlinkSync3, mkdirSync as mkdirSync8, readdirSync as readdirSync7 } from "node:fs";
26207
26207
  import { join as join35 } from "node:path";
26208
26208
  function cleanForwardHeaders(raw, targetHost) {
26209
26209
  const out = {};
@@ -26223,6 +26223,13 @@ function cleanForwardHeaders(raw, targetHost) {
26223
26223
  out.host = targetHost;
26224
26224
  return out;
26225
26225
  }
26226
+ function fmtTokens(n) {
26227
+ if (n < 1e3)
26228
+ return String(n);
26229
+ if (n < 1e6)
26230
+ return `${(n / 1e3).toFixed(1)}K`;
26231
+ return `${(n / 1e6).toFixed(1)}M`;
26232
+ }
26226
26233
  function readExposeState(stateDir) {
26227
26234
  try {
26228
26235
  const path = join35(stateDir, STATE_FILE_NAME);
@@ -26306,7 +26313,7 @@ async function collectSystemMetricsAsync() {
26306
26313
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
26307
26314
  };
26308
26315
  }
26309
- var HOP_BY_HOP_HEADERS, CF_HEADERS_PREFIX, DEFAULT_TARGETS, STATE_FILE_NAME, ExposeGateway;
26316
+ var HOP_BY_HOP_HEADERS, CF_HEADERS_PREFIX, DEFAULT_TARGETS, STATE_FILE_NAME, ExposeGateway, ExposeP2PGateway;
26310
26317
  var init_expose = __esm({
26311
26318
  "packages/cli/dist/tui/expose.js"() {
26312
26319
  "use strict";
@@ -26347,7 +26354,11 @@ var init_expose = __esm({
26347
26354
  totalRequests: 0,
26348
26355
  activeConnections: 0,
26349
26356
  errors: 0,
26350
- modelUsage: /* @__PURE__ */ new Map()
26357
+ totalTokensIn: 0,
26358
+ totalTokensOut: 0,
26359
+ startedAt: Date.now(),
26360
+ modelUsage: /* @__PURE__ */ new Map(),
26361
+ users: /* @__PURE__ */ new Map()
26351
26362
  };
26352
26363
  get tunnelUrl() {
26353
26364
  return this._tunnelUrl;
@@ -26509,7 +26520,20 @@ var init_expose = __esm({
26509
26520
  totalRequests: this._stats.totalRequests,
26510
26521
  activeConnections: this._stats.activeConnections,
26511
26522
  errors: this._stats.errors,
26512
- modelUsage: Object.fromEntries(this._stats.modelUsage)
26523
+ totalTokensIn: this._stats.totalTokensIn,
26524
+ totalTokensOut: this._stats.totalTokensOut,
26525
+ uptimeSeconds: Math.floor((Date.now() - this._stats.startedAt) / 1e3),
26526
+ modelUsage: Object.fromEntries(this._stats.modelUsage),
26527
+ users: Array.from(this._stats.users.values()).map((u) => ({
26528
+ ip: u.ip,
26529
+ requests: u.requests,
26530
+ tokensIn: u.tokensIn,
26531
+ tokensOut: u.tokensOut,
26532
+ activeRequests: u.activeRequests,
26533
+ firstSeen: new Date(u.firstSeen).toISOString(),
26534
+ lastSeen: new Date(u.lastSeen).toISOString(),
26535
+ models: Object.fromEntries(Array.from(u.models.entries()).map(([m, v]) => [m, { requests: v.requests, tokensIn: v.tokensIn, tokensOut: v.tokensOut }]))
26536
+ }))
26513
26537
  };
26514
26538
  res.writeHead(200, { "Content-Type": "application/json" });
26515
26539
  res.end(JSON.stringify({ ...metrics, gateway: gatewayStats }));
@@ -26519,8 +26543,26 @@ var init_expose = __esm({
26519
26543
  });
26520
26544
  return;
26521
26545
  }
26546
+ const userIp = req.headers["cf-connecting-ip"] ?? req.headers["x-forwarded-for"]?.split(",")[0]?.trim() ?? req.socket.remoteAddress ?? "unknown";
26547
+ let user = this._stats.users.get(userIp);
26548
+ if (!user) {
26549
+ user = {
26550
+ ip: userIp,
26551
+ firstSeen: Date.now(),
26552
+ lastSeen: Date.now(),
26553
+ requests: 0,
26554
+ tokensIn: 0,
26555
+ tokensOut: 0,
26556
+ activeRequests: 0,
26557
+ models: /* @__PURE__ */ new Map()
26558
+ };
26559
+ this._stats.users.set(userIp, user);
26560
+ }
26522
26561
  this._stats.totalRequests++;
26523
26562
  this._stats.activeConnections++;
26563
+ user.requests++;
26564
+ user.activeRequests++;
26565
+ user.lastSeen = Date.now();
26524
26566
  this.emitStats();
26525
26567
  url.searchParams.delete("key");
26526
26568
  const forwardPath = url.pathname + url.search;
@@ -26529,14 +26571,22 @@ var init_expose = __esm({
26529
26571
  req.on("end", () => {
26530
26572
  const body = Buffer.concat(bodyChunks);
26531
26573
  let isStreaming = false;
26574
+ let requestModel = "";
26532
26575
  if (req.method === "POST" && body.length > 0) {
26533
26576
  try {
26534
26577
  const text = body.toString("utf8", 0, Math.min(body.length, 1024));
26535
26578
  const modelMatch = text.match(/"model"\s*:\s*"([^"]+)"/);
26536
26579
  if (modelMatch) {
26537
- const model = modelMatch[1];
26538
- const count = this._stats.modelUsage.get(model) ?? 0;
26539
- this._stats.modelUsage.set(model, count + 1);
26580
+ requestModel = modelMatch[1];
26581
+ const count = this._stats.modelUsage.get(requestModel) ?? 0;
26582
+ this._stats.modelUsage.set(requestModel, count + 1);
26583
+ let mm = user.models.get(requestModel);
26584
+ if (!mm) {
26585
+ mm = { requests: 0, tokensIn: 0, tokensOut: 0, lastUsed: Date.now() };
26586
+ user.models.set(requestModel, mm);
26587
+ }
26588
+ mm.requests++;
26589
+ mm.lastUsed = Date.now();
26540
26590
  }
26541
26591
  isStreaming = /"stream"\s*:\s*true/.test(text);
26542
26592
  } catch {
@@ -26567,6 +26617,50 @@ var init_expose = __esm({
26567
26617
  heartbeatTimer = null;
26568
26618
  }
26569
26619
  };
26620
+ let responseTail = "";
26621
+ const finalizeRequest = () => {
26622
+ user.activeRequests = Math.max(0, user.activeRequests - 1);
26623
+ this._stats.activeConnections = Math.max(0, this._stats.activeConnections - 1);
26624
+ try {
26625
+ const promptEval = responseTail.match(/"prompt_eval_count"\s*:\s*(\d+)/);
26626
+ const evalCount = responseTail.match(/"eval_count"\s*:\s*(\d+)/);
26627
+ if (promptEval || evalCount) {
26628
+ const tIn = parseInt(promptEval?.[1] ?? "0", 10);
26629
+ const tOut = parseInt(evalCount?.[1] ?? "0", 10);
26630
+ this._stats.totalTokensIn += tIn;
26631
+ this._stats.totalTokensOut += tOut;
26632
+ user.tokensIn += tIn;
26633
+ user.tokensOut += tOut;
26634
+ if (requestModel) {
26635
+ const mm = user.models.get(requestModel);
26636
+ if (mm) {
26637
+ mm.tokensIn += tIn;
26638
+ mm.tokensOut += tOut;
26639
+ }
26640
+ }
26641
+ } else {
26642
+ const promptTokens = responseTail.match(/"prompt_tokens"\s*:\s*(\d+)/);
26643
+ const completionTokens = responseTail.match(/"completion_tokens"\s*:\s*(\d+)/);
26644
+ if (promptTokens || completionTokens) {
26645
+ const tIn = parseInt(promptTokens?.[1] ?? "0", 10);
26646
+ const tOut = parseInt(completionTokens?.[1] ?? "0", 10);
26647
+ this._stats.totalTokensIn += tIn;
26648
+ this._stats.totalTokensOut += tOut;
26649
+ user.tokensIn += tIn;
26650
+ user.tokensOut += tOut;
26651
+ if (requestModel) {
26652
+ const mm = user.models.get(requestModel);
26653
+ if (mm) {
26654
+ mm.tokensIn += tIn;
26655
+ mm.tokensOut += tOut;
26656
+ }
26657
+ }
26658
+ }
26659
+ }
26660
+ } catch {
26661
+ }
26662
+ this.emitStats();
26663
+ };
26570
26664
  const proxyReq = httpRequest({
26571
26665
  hostname: target.hostname,
26572
26666
  port: target.port || (target.protocol === "https:" ? 443 : 80),
@@ -26576,8 +26670,14 @@ var init_expose = __esm({
26576
26670
  }, (proxyRes) => {
26577
26671
  cleanupHeartbeat();
26578
26672
  proxyRes.on("error", () => {
26579
- this._stats.activeConnections = Math.max(0, this._stats.activeConnections - 1);
26580
- this.emitStats();
26673
+ finalizeRequest();
26674
+ });
26675
+ proxyRes.on("data", (chunk) => {
26676
+ const text = chunk.toString();
26677
+ responseTail += text;
26678
+ if (responseTail.length > 2048) {
26679
+ responseTail = responseTail.slice(-2048);
26680
+ }
26581
26681
  });
26582
26682
  if (isStreaming) {
26583
26683
  if (proxyRes.statusCode !== 200) {
@@ -26597,8 +26697,7 @@ var init_expose = __esm({
26597
26697
  } catch {
26598
26698
  }
26599
26699
  this._stats.errors++;
26600
- this._stats.activeConnections = Math.max(0, this._stats.activeConnections - 1);
26601
- this.emitStats();
26700
+ finalizeRequest();
26602
26701
  });
26603
26702
  return;
26604
26703
  }
@@ -26608,14 +26707,12 @@ var init_expose = __esm({
26608
26707
  proxyRes.pipe(res);
26609
26708
  }
26610
26709
  proxyRes.on("end", () => {
26611
- this._stats.activeConnections = Math.max(0, this._stats.activeConnections - 1);
26612
- this.emitStats();
26710
+ finalizeRequest();
26613
26711
  });
26614
26712
  });
26615
26713
  proxyReq.on("error", (err) => {
26616
26714
  cleanupHeartbeat();
26617
26715
  this._stats.errors++;
26618
- this._stats.activeConnections = Math.max(0, this._stats.activeConnections - 1);
26619
26716
  if (!res.headersSent) {
26620
26717
  res.writeHead(502, { "Content-Type": "application/json" });
26621
26718
  res.end(JSON.stringify({ error: `Backend unreachable: ${err.message}` }));
@@ -26631,7 +26728,7 @@ var init_expose = __esm({
26631
26728
  } catch {
26632
26729
  }
26633
26730
  }
26634
- this.emitStats();
26731
+ finalizeRequest();
26635
26732
  });
26636
26733
  if (body.length > 0) {
26637
26734
  proxyReq.write(body);
@@ -26639,6 +26736,7 @@ var init_expose = __esm({
26639
26736
  proxyReq.end();
26640
26737
  });
26641
26738
  req.on("error", () => {
26739
+ user.activeRequests = Math.max(0, user.activeRequests - 1);
26642
26740
  this._stats.activeConnections = Math.max(0, this._stats.activeConnections - 1);
26643
26741
  this.emitStats();
26644
26742
  });
@@ -26669,8 +26767,12 @@ var init_expose = __esm({
26669
26767
  });
26670
26768
  this._cloudflaredPid = this.cloudflaredProcess.pid ?? null;
26671
26769
  let urlFound = false;
26770
+ let stderrOutput = "";
26672
26771
  const handleOutput = (data) => {
26673
26772
  const text = data.toString();
26773
+ stderrOutput += text;
26774
+ if (stderrOutput.length > 4096)
26775
+ stderrOutput = stderrOutput.slice(-4096);
26674
26776
  const urlMatch = text.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/);
26675
26777
  if (urlMatch && !urlFound) {
26676
26778
  urlFound = true;
@@ -26690,7 +26792,8 @@ var init_expose = __esm({
26690
26792
  this.cloudflaredProcess.on("close", (code) => {
26691
26793
  if (!urlFound) {
26692
26794
  clearTimeout(timeout);
26693
- reject(new Error(`Cloudflared exited with code ${code} before providing URL`));
26795
+ const hint = stderrOutput.trim().split("\n").slice(-3).join(" | ");
26796
+ reject(new Error(`Cloudflared exited with code ${code}. ${hint ? "Output: " + hint : ""}`));
26694
26797
  }
26695
26798
  if (this._stats.status === "active") {
26696
26799
  this.options.onInfo?.("Cloudflared tunnel died \u2014 restarting...");
@@ -26769,7 +26872,11 @@ ${this.formatConnectionInfo()}`);
26769
26872
  });
26770
26873
  }
26771
26874
  emitStats() {
26772
- this.emit("stats", { ...this._stats, modelUsage: new Map(this._stats.modelUsage) });
26875
+ this.emit("stats", {
26876
+ ...this._stats,
26877
+ modelUsage: new Map(this._stats.modelUsage),
26878
+ users: new Map(this._stats.users)
26879
+ });
26773
26880
  }
26774
26881
  /** Format connection info for display */
26775
26882
  formatConnectionInfo() {
@@ -26795,15 +26902,337 @@ ${this.formatConnectionInfo()}`);
26795
26902
  formatStats() {
26796
26903
  const s = this._stats;
26797
26904
  const lines = [];
26905
+ const uptimeSec = Math.floor((Date.now() - s.startedAt) / 1e3);
26906
+ const uptimeStr = uptimeSec < 60 ? `${uptimeSec}s` : uptimeSec < 3600 ? `${Math.floor(uptimeSec / 60)}m ${uptimeSec % 60}s` : `${Math.floor(uptimeSec / 3600)}h ${Math.floor(uptimeSec % 3600 / 60)}m`;
26798
26907
  lines.push(` ${c2.bold("Expose Gateway Stats")}`);
26799
- lines.push(` ${c2.cyan("Status".padEnd(16))} ${s.status === "active" ? c2.green("active") : s.status === "error" ? c2.red("error") : c2.yellow("standby")}`);
26800
- lines.push(` ${c2.cyan("Total requests".padEnd(16))} ${s.totalRequests}`);
26801
- lines.push(` ${c2.cyan("Active conns".padEnd(16))} ${s.activeConnections}`);
26802
- lines.push(` ${c2.cyan("Errors".padEnd(16))} ${s.errors}`);
26908
+ lines.push(` ${c2.cyan("Status".padEnd(18))} ${s.status === "active" ? c2.green("active") : s.status === "error" ? c2.red("error") : c2.yellow("standby")}`);
26909
+ lines.push(` ${c2.cyan("Uptime".padEnd(18))} ${uptimeStr}`);
26910
+ lines.push(` ${c2.cyan("Total requests".padEnd(18))} ${s.totalRequests}`);
26911
+ lines.push(` ${c2.cyan("Active conns".padEnd(18))} ${s.activeConnections}`);
26912
+ lines.push(` ${c2.cyan("Errors".padEnd(18))} ${s.errors}`);
26913
+ lines.push(` ${c2.cyan("Tokens in".padEnd(18))} ${fmtTokens(s.totalTokensIn)}`);
26914
+ lines.push(` ${c2.cyan("Tokens out".padEnd(18))} ${fmtTokens(s.totalTokensOut)}`);
26803
26915
  if (s.modelUsage.size > 0) {
26804
- lines.push(` ${c2.cyan("Models used:")}`);
26916
+ lines.push("");
26917
+ lines.push(` ${c2.bold("Models")}`);
26805
26918
  for (const [model, count] of s.modelUsage) {
26806
- lines.push(` ${model}: ${count} requests`);
26919
+ let mIn = 0, mOut = 0;
26920
+ for (const user of s.users.values()) {
26921
+ const mm = user.models.get(model);
26922
+ if (mm) {
26923
+ mIn += mm.tokensIn;
26924
+ mOut += mm.tokensOut;
26925
+ }
26926
+ }
26927
+ lines.push(` ${c2.cyan(model.padEnd(30))} ${count} reqs ${c2.dim(`in:${fmtTokens(mIn)} out:${fmtTokens(mOut)}`)}`);
26928
+ }
26929
+ }
26930
+ if (s.users.size > 0) {
26931
+ lines.push("");
26932
+ lines.push(` ${c2.bold("Active Users")} (${s.users.size})`);
26933
+ for (const user of s.users.values()) {
26934
+ const ageSec = Math.floor((Date.now() - user.firstSeen) / 1e3);
26935
+ const ageStr = ageSec < 60 ? `${ageSec}s` : ageSec < 3600 ? `${Math.floor(ageSec / 60)}m` : `${Math.floor(ageSec / 3600)}h ${Math.floor(ageSec % 3600 / 60)}m`;
26936
+ const lastSec = Math.floor((Date.now() - user.lastSeen) / 1e3);
26937
+ const lastStr = lastSec < 5 ? "now" : lastSec < 60 ? `${lastSec}s ago` : `${Math.floor(lastSec / 60)}m ago`;
26938
+ const activeTag = user.activeRequests > 0 ? c2.green(` [${user.activeRequests} active]`) : "";
26939
+ lines.push(` ${c2.bold(user.ip)}${activeTag}`);
26940
+ lines.push(` ${c2.dim("Session:")} ${ageStr} ${c2.dim("Last seen:")} ${lastStr} ${c2.dim("Requests:")} ${user.requests}`);
26941
+ lines.push(` ${c2.dim("Tokens:")} in:${fmtTokens(user.tokensIn)} out:${fmtTokens(user.tokensOut)} ${c2.dim("Total:")} ${fmtTokens(user.tokensIn + user.tokensOut)}`);
26942
+ if (user.models.size > 0) {
26943
+ const modelParts = [];
26944
+ for (const [m, mm] of user.models) {
26945
+ modelParts.push(`${m} (${mm.requests} reqs, ${fmtTokens(mm.tokensIn + mm.tokensOut)} tokens)`);
26946
+ }
26947
+ lines.push(` ${c2.dim("Models:")} ${modelParts.join(", ")}`);
26948
+ }
26949
+ }
26950
+ }
26951
+ return lines.join("\n");
26952
+ }
26953
+ };
26954
+ ExposeP2PGateway = class extends EventEmitter3 {
26955
+ _nexusTool;
26956
+ // NexusTool instance
26957
+ _kind;
26958
+ _targetUrl;
26959
+ _peerId = null;
26960
+ _capabilities = [];
26961
+ _exposedModels = 0;
26962
+ _stateDir;
26963
+ _margin;
26964
+ _pollTimer = null;
26965
+ _stats = {
26966
+ status: "standby",
26967
+ totalRequests: 0,
26968
+ activeConnections: 0,
26969
+ errors: 0,
26970
+ totalTokensIn: 0,
26971
+ totalTokensOut: 0,
26972
+ startedAt: Date.now(),
26973
+ modelUsage: /* @__PURE__ */ new Map(),
26974
+ users: /* @__PURE__ */ new Map()
26975
+ };
26976
+ get peerId() {
26977
+ return this._peerId;
26978
+ }
26979
+ get stats() {
26980
+ return this._stats;
26981
+ }
26982
+ get kind() {
26983
+ return this._kind;
26984
+ }
26985
+ get isActive() {
26986
+ return this._stats.status === "active";
26987
+ }
26988
+ get transport() {
26989
+ return "libp2p";
26990
+ }
26991
+ _onInfo;
26992
+ _onError;
26993
+ constructor(options, nexusTool) {
26994
+ super();
26995
+ this._nexusTool = nexusTool;
26996
+ this._kind = options.kind;
26997
+ this._targetUrl = options.targetUrl ?? DEFAULT_TARGETS[options.kind];
26998
+ this._stateDir = options.stateDir ?? null;
26999
+ this._margin = options.margin ?? 0.5;
27000
+ this._onInfo = options.onInfo;
27001
+ this._onError = options.onError;
27002
+ }
27003
+ async start() {
27004
+ this._onInfo?.("Connecting to nexus P2P network...");
27005
+ const connectResult = await this._nexusTool.execute({
27006
+ action: "connect",
27007
+ agent_name: "open-agents-node",
27008
+ agent_type: "general"
27009
+ });
27010
+ if (!connectResult.success) {
27011
+ throw new Error(`Nexus daemon connect failed: ${connectResult.error}`);
27012
+ }
27013
+ await new Promise((r) => setTimeout(r, 2e3));
27014
+ this._onInfo?.("Registering inference capabilities...");
27015
+ let exposeResult = await this._nexusTool.execute({
27016
+ action: "expose",
27017
+ ollama_url: this._targetUrl,
27018
+ margin: String(this._margin)
27019
+ });
27020
+ if (!exposeResult.success && exposeResult.error?.includes("not running")) {
27021
+ await new Promise((r) => setTimeout(r, 3e3));
27022
+ exposeResult = await this._nexusTool.execute({
27023
+ action: "expose",
27024
+ ollama_url: this._targetUrl,
27025
+ margin: String(this._margin)
27026
+ });
27027
+ }
27028
+ if (!exposeResult.success) {
27029
+ throw new Error(`Expose failed: ${exposeResult.error}`);
27030
+ }
27031
+ const nexusDir = this._nexusTool.getNexusDir();
27032
+ const statusPath = join35(nexusDir, "status.json");
27033
+ for (let i = 0; i < 60; i++) {
27034
+ try {
27035
+ const raw = readFileSync18(statusPath, "utf8");
27036
+ if (raw.length > 10) {
27037
+ const status = JSON.parse(raw);
27038
+ if (status.connected && status.peerId) {
27039
+ this._peerId = status.peerId;
27040
+ this._capabilities = status.capabilities || [];
27041
+ this._exposedModels = status.exposedModels || 0;
27042
+ break;
27043
+ }
27044
+ }
27045
+ } catch {
27046
+ }
27047
+ await new Promise((r) => setTimeout(r, 500));
27048
+ }
27049
+ this._stats.status = "active";
27050
+ this._stats.startedAt = Date.now();
27051
+ this.emitStats();
27052
+ this.startMeteringPoll(nexusDir);
27053
+ return this._peerId ?? "connected (peer ID pending)";
27054
+ }
27055
+ async stop() {
27056
+ this.stopMeteringPoll();
27057
+ this._stats.status = "standby";
27058
+ this._stats.activeConnections = 0;
27059
+ this.emitStats();
27060
+ }
27061
+ /** Poll the daemon's metering.jsonl and status.json for stats */
27062
+ startMeteringPoll(nexusDir) {
27063
+ this.stopMeteringPoll();
27064
+ let lastMeteringSize = 0;
27065
+ this._pollTimer = setInterval(() => {
27066
+ try {
27067
+ const statusPath = join35(nexusDir, "status.json");
27068
+ if (existsSync26(statusPath)) {
27069
+ const status = JSON.parse(readFileSync18(statusPath, "utf8"));
27070
+ if (status.peerId && !this._peerId) {
27071
+ this._peerId = status.peerId;
27072
+ }
27073
+ this._capabilities = status.capabilities || [];
27074
+ }
27075
+ } catch {
27076
+ }
27077
+ try {
27078
+ const invocDir = join35(nexusDir, "invocations");
27079
+ if (existsSync26(invocDir)) {
27080
+ const files = readdirSync7(invocDir);
27081
+ const invocCount = files.filter((f) => f.endsWith(".json")).length;
27082
+ if (invocCount > this._stats.totalRequests) {
27083
+ this._stats.totalRequests = invocCount;
27084
+ this.emitStats();
27085
+ }
27086
+ }
27087
+ } catch {
27088
+ }
27089
+ try {
27090
+ const meteringFile = join35(nexusDir, "metering.jsonl");
27091
+ if (existsSync26(meteringFile)) {
27092
+ const content = readFileSync18(meteringFile, "utf8");
27093
+ if (content.length > lastMeteringSize) {
27094
+ const newContent = content.slice(lastMeteringSize);
27095
+ lastMeteringSize = content.length;
27096
+ for (const line of newContent.split("\n")) {
27097
+ if (!line.trim())
27098
+ continue;
27099
+ try {
27100
+ const record = JSON.parse(line);
27101
+ if (record.tokens) {
27102
+ this._stats.totalTokensIn += record.tokens.input || 0;
27103
+ this._stats.totalTokensOut += record.tokens.output || 0;
27104
+ }
27105
+ const peerId = record.from || record.peerId || "unknown";
27106
+ const shortPeer = peerId.length > 16 ? peerId.slice(0, 16) + "..." : peerId;
27107
+ let user = this._stats.users.get(shortPeer);
27108
+ if (!user) {
27109
+ user = {
27110
+ ip: shortPeer,
27111
+ firstSeen: Date.now(),
27112
+ lastSeen: Date.now(),
27113
+ requests: 0,
27114
+ tokensIn: 0,
27115
+ tokensOut: 0,
27116
+ activeRequests: 0,
27117
+ models: /* @__PURE__ */ new Map()
27118
+ };
27119
+ this._stats.users.set(shortPeer, user);
27120
+ }
27121
+ user.requests++;
27122
+ user.lastSeen = Date.now();
27123
+ if (record.tokens) {
27124
+ user.tokensIn += record.tokens.input || 0;
27125
+ user.tokensOut += record.tokens.output || 0;
27126
+ }
27127
+ if (record.model || record.capability) {
27128
+ const modelName = record.model || record.capability.replace("inference:", "") || "unknown";
27129
+ this._stats.modelUsage.set(modelName, (this._stats.modelUsage.get(modelName) ?? 0) + 1);
27130
+ let mm = user.models.get(modelName);
27131
+ if (!mm) {
27132
+ mm = { requests: 0, tokensIn: 0, tokensOut: 0, lastUsed: Date.now() };
27133
+ user.models.set(modelName, mm);
27134
+ }
27135
+ mm.requests++;
27136
+ mm.lastUsed = Date.now();
27137
+ if (record.tokens) {
27138
+ mm.tokensIn += record.tokens.input || 0;
27139
+ mm.tokensOut += record.tokens.output || 0;
27140
+ }
27141
+ }
27142
+ } catch {
27143
+ }
27144
+ }
27145
+ this.emitStats();
27146
+ }
27147
+ }
27148
+ } catch {
27149
+ }
27150
+ }, 5e3);
27151
+ this._pollTimer.unref();
27152
+ }
27153
+ stopMeteringPoll() {
27154
+ if (this._pollTimer) {
27155
+ clearInterval(this._pollTimer);
27156
+ this._pollTimer = null;
27157
+ }
27158
+ }
27159
+ emitStats() {
27160
+ this.emit("stats", {
27161
+ ...this._stats,
27162
+ modelUsage: new Map(this._stats.modelUsage),
27163
+ users: new Map(this._stats.users)
27164
+ });
27165
+ }
27166
+ /** Format connection info for display */
27167
+ formatConnectionInfo() {
27168
+ const lines = [];
27169
+ lines.push(` ${c2.green("\u25CF")} Expose gateway active ${c2.dim("(libp2p)")}`);
27170
+ lines.push(` ${c2.cyan("Backend".padEnd(12))} ${this._kind} (${this._targetUrl})`);
27171
+ lines.push(` ${c2.cyan("Transport".padEnd(12))} ${c2.bold("libp2p")} (DHT + mDNS + NATS relay)`);
27172
+ lines.push(` ${c2.cyan("Peer ID".padEnd(12))} ${c2.bold(this._peerId ?? "connecting...")}`);
27173
+ lines.push(` ${c2.cyan("Models".padEnd(12))} ${this._exposedModels || this._capabilities.filter((cap) => cap.startsWith("inference:")).length} exposed`);
27174
+ lines.push("");
27175
+ lines.push(` ${c2.dim("Remote peers discover your models via nexus network.")}`);
27176
+ lines.push(` ${c2.dim("Consumers use: /nexus invoke_capability inference:<model>")}`);
27177
+ const infCaps = this._capabilities.filter((cap) => cap.startsWith("inference:"));
27178
+ if (infCaps.length > 0) {
27179
+ lines.push("");
27180
+ lines.push(` ${c2.bold("Exposed capabilities:")}`);
27181
+ for (const cap of infCaps) {
27182
+ lines.push(` ${c2.cyan(cap)}`);
27183
+ }
27184
+ }
27185
+ return lines.join("\n");
27186
+ }
27187
+ /** Format usage stats for display */
27188
+ formatStats() {
27189
+ const s = this._stats;
27190
+ const lines = [];
27191
+ const uptimeSec = Math.floor((Date.now() - s.startedAt) / 1e3);
27192
+ const uptimeStr = uptimeSec < 60 ? `${uptimeSec}s` : uptimeSec < 3600 ? `${Math.floor(uptimeSec / 60)}m ${uptimeSec % 60}s` : `${Math.floor(uptimeSec / 3600)}h ${Math.floor(uptimeSec % 3600 / 60)}m`;
27193
+ lines.push(` ${c2.bold("Expose Gateway Stats")} ${c2.dim("(libp2p)")}`);
27194
+ lines.push(` ${c2.cyan("Status".padEnd(18))} ${s.status === "active" ? c2.green("active") : s.status === "error" ? c2.red("error") : c2.yellow("standby")}`);
27195
+ lines.push(` ${c2.cyan("Transport".padEnd(18))} libp2p`);
27196
+ lines.push(` ${c2.cyan("Peer ID".padEnd(18))} ${this._peerId ?? "n/a"}`);
27197
+ lines.push(` ${c2.cyan("Uptime".padEnd(18))} ${uptimeStr}`);
27198
+ lines.push(` ${c2.cyan("Total requests".padEnd(18))} ${s.totalRequests}`);
27199
+ lines.push(` ${c2.cyan("Active conns".padEnd(18))} ${s.activeConnections}`);
27200
+ lines.push(` ${c2.cyan("Errors".padEnd(18))} ${s.errors}`);
27201
+ lines.push(` ${c2.cyan("Tokens in".padEnd(18))} ${fmtTokens(s.totalTokensIn)}`);
27202
+ lines.push(` ${c2.cyan("Tokens out".padEnd(18))} ${fmtTokens(s.totalTokensOut)}`);
27203
+ if (s.modelUsage.size > 0) {
27204
+ lines.push("");
27205
+ lines.push(` ${c2.bold("Models")}`);
27206
+ for (const [model, count] of s.modelUsage) {
27207
+ let mIn = 0, mOut = 0;
27208
+ for (const user of s.users.values()) {
27209
+ const mm = user.models.get(model);
27210
+ if (mm) {
27211
+ mIn += mm.tokensIn;
27212
+ mOut += mm.tokensOut;
27213
+ }
27214
+ }
27215
+ lines.push(` ${c2.cyan(model.padEnd(30))} ${count} reqs ${c2.dim(`in:${fmtTokens(mIn)} out:${fmtTokens(mOut)}`)}`);
27216
+ }
27217
+ }
27218
+ if (s.users.size > 0) {
27219
+ lines.push("");
27220
+ lines.push(` ${c2.bold("Active Peers")} (${s.users.size})`);
27221
+ for (const user of s.users.values()) {
27222
+ const ageSec = Math.floor((Date.now() - user.firstSeen) / 1e3);
27223
+ const ageStr = ageSec < 60 ? `${ageSec}s` : ageSec < 3600 ? `${Math.floor(ageSec / 60)}m` : `${Math.floor(ageSec / 3600)}h ${Math.floor(ageSec % 3600 / 60)}m`;
27224
+ const lastSec = Math.floor((Date.now() - user.lastSeen) / 1e3);
27225
+ const lastStr = lastSec < 5 ? "now" : lastSec < 60 ? `${lastSec}s ago` : `${Math.floor(lastSec / 60)}m ago`;
27226
+ lines.push(` ${c2.bold(user.ip)}`);
27227
+ lines.push(` ${c2.dim("Session:")} ${ageStr} ${c2.dim("Last seen:")} ${lastStr} ${c2.dim("Requests:")} ${user.requests}`);
27228
+ lines.push(` ${c2.dim("Tokens:")} in:${fmtTokens(user.tokensIn)} out:${fmtTokens(user.tokensOut)}`);
27229
+ if (user.models.size > 0) {
27230
+ const modelParts = [];
27231
+ for (const [m, mm] of user.models) {
27232
+ modelParts.push(`${m} (${mm.requests} reqs, ${fmtTokens(mm.tokensIn + mm.tokensOut)} tokens)`);
27233
+ }
27234
+ lines.push(` ${c2.dim("Models:")} ${modelParts.join(", ")}`);
27235
+ }
26807
27236
  }
26808
27237
  }
26809
27238
  return lines.join("\n");
@@ -28422,7 +28851,7 @@ var init_dist6 = __esm({
28422
28851
  });
28423
28852
 
28424
28853
  // packages/cli/dist/tui/oa-directory.js
28425
- import { existsSync as existsSync29, mkdirSync as mkdirSync10, readFileSync as readFileSync21, writeFileSync as writeFileSync10, readdirSync as readdirSync7, statSync as statSync9, unlinkSync as unlinkSync4 } from "node:fs";
28854
+ import { existsSync as existsSync29, mkdirSync as mkdirSync10, readFileSync as readFileSync21, writeFileSync as writeFileSync10, readdirSync as readdirSync8, statSync as statSync9, unlinkSync as unlinkSync4 } from "node:fs";
28426
28855
  import { join as join39, relative as relative2, basename as basename9, extname as extname8 } from "node:path";
28427
28856
  import { homedir as homedir9 } from "node:os";
28428
28857
  function initOaDirectory(repoRoot) {
@@ -28614,7 +29043,7 @@ function loadRecentSessions(repoRoot, limit = 5) {
28614
29043
  if (!existsSync29(historyDir))
28615
29044
  return [];
28616
29045
  try {
28617
- const files = readdirSync7(historyDir).filter((f) => f.endsWith(".json") && f !== "pending-task.json").map((f) => {
29046
+ const files = readdirSync8(historyDir).filter((f) => f.endsWith(".json") && f !== "pending-task.json").map((f) => {
28618
29047
  const stat5 = statSync9(join39(historyDir, f));
28619
29048
  return { file: f, mtime: stat5.mtimeMs };
28620
29049
  }).sort((a, b) => b.mtime - a.mtime).slice(0, limit);
@@ -28767,7 +29196,7 @@ function buildDirTree(root, maxDepth, prefix = "", depth = 0) {
28767
29196
  return "";
28768
29197
  let result = "";
28769
29198
  try {
28770
- const entries = readdirSync7(root, { withFileTypes: true }).filter((e) => !e.name.startsWith(".") || e.name === ".github").filter((e) => !SKIP_DIRS.has(e.name)).sort((a, b) => {
29199
+ const entries = readdirSync8(root, { withFileTypes: true }).filter((e) => !e.name.startsWith(".") || e.name === ".github").filter((e) => !SKIP_DIRS.has(e.name)).sort((a, b) => {
28771
29200
  if (a.isDirectory() && !b.isDirectory())
28772
29201
  return -1;
28773
29202
  if (!a.isDirectory() && b.isDirectory())
@@ -28782,7 +29211,7 @@ function buildDirTree(root, maxDepth, prefix = "", depth = 0) {
28782
29211
  if (entry.isDirectory()) {
28783
29212
  let fileCount = 0;
28784
29213
  try {
28785
- fileCount = readdirSync7(join39(root, entry.name)).filter((f) => !f.startsWith(".")).length;
29214
+ fileCount = readdirSync8(join39(root, entry.name)).filter((f) => !f.startsWith(".")).length;
28786
29215
  } catch {
28787
29216
  }
28788
29217
  result += `${prefix}${connector}${entry.name}/ (${fileCount})
@@ -31398,15 +31827,17 @@ async function handleSlashCommand(input, ctx) {
31398
31827
  if (info && ctx.isExposeActive?.()) {
31399
31828
  process.stdout.write("\n" + info.stats + "\n\n");
31400
31829
  } else {
31401
- renderInfo("Usage: /expose <backend> [--key <auth-key>]");
31402
- renderInfo(" /expose ollama Expose local Ollama (11434)");
31830
+ renderInfo("Usage: /expose <backend> [options]");
31831
+ renderInfo(" /expose ollama Expose local Ollama via libp2p (default)");
31832
+ renderInfo(" /expose ollama --tunnel Expose via cloudflared tunnel");
31833
+ renderInfo(" /expose ollama --libp2p Expose via libp2p (explicit)");
31403
31834
  renderInfo(" /expose vllm Expose local vLLM (8000)");
31404
31835
  renderInfo(" /expose llvm Expose local LLVM (8080)");
31405
31836
  renderInfo(" /expose host:port Expose custom address");
31406
31837
  renderInfo(" /expose stop Stop the gateway");
31407
31838
  renderInfo(" /expose status Show usage stats");
31408
- renderInfo(" --key <key> Use specific auth key");
31409
- renderInfo(" --key Auto-generate auth key");
31839
+ renderInfo(" --key <key> Use specific auth key (tunnel mode)");
31840
+ renderInfo(" --key Auto-generate auth key (tunnel mode)");
31410
31841
  }
31411
31842
  return "handled";
31412
31843
  }
@@ -31416,12 +31847,18 @@ async function handleSlashCommand(input, ctx) {
31416
31847
  const exposeParts = arg.split(/\s+/);
31417
31848
  const kindOrUrl = exposeParts[0];
31418
31849
  let exposeAuthKey;
31850
+ let exposeTransport;
31419
31851
  const keyIdx = exposeParts.indexOf("--key");
31420
31852
  if (keyIdx !== -1) {
31421
31853
  exposeAuthKey = exposeParts[keyIdx + 1] ?? "";
31422
31854
  }
31855
+ if (exposeParts.includes("--tunnel")) {
31856
+ exposeTransport = "tunnel";
31857
+ } else if (exposeParts.includes("--libp2p")) {
31858
+ exposeTransport = "libp2p";
31859
+ }
31423
31860
  try {
31424
- const tunnelUrl = await ctx.exposeStart(kindOrUrl, exposeAuthKey);
31861
+ const tunnelUrl = await ctx.exposeStart(kindOrUrl, exposeAuthKey, exposeTransport);
31425
31862
  if (tunnelUrl) {
31426
31863
  const info = ctx.getExposeInfo?.();
31427
31864
  if (info) {
@@ -32619,7 +33056,7 @@ var init_commands = __esm({
32619
33056
  });
32620
33057
 
32621
33058
  // packages/cli/dist/tui/project-context.js
32622
- import { existsSync as existsSync31, readFileSync as readFileSync22, readdirSync as readdirSync8 } from "node:fs";
33059
+ import { existsSync as existsSync31, readFileSync as readFileSync22, readdirSync as readdirSync9 } from "node:fs";
32623
33060
  import { join as join41, basename as basename10 } from "node:path";
32624
33061
  import { execSync as execSync26 } from "node:child_process";
32625
33062
  import { homedir as homedir11, platform as platform2, release } from "node:os";
@@ -32720,7 +33157,7 @@ function loadMemoryDir(memDir, scope) {
32720
33157
  return "";
32721
33158
  const lines = [];
32722
33159
  try {
32723
- const files = readdirSync8(memDir).filter((f) => f.endsWith(".json"));
33160
+ const files = readdirSync9(memDir).filter((f) => f.endsWith(".json"));
32724
33161
  for (const file of files.slice(0, 10)) {
32725
33162
  try {
32726
33163
  const raw = readFileSync22(join41(memDir, file), "utf-8");
@@ -33746,7 +34183,7 @@ var init_carousel = __esm({
33746
34183
  });
33747
34184
 
33748
34185
  // packages/cli/dist/tui/carousel-descriptors.js
33749
- import { existsSync as existsSync32, readFileSync as readFileSync23, writeFileSync as writeFileSync12, mkdirSync as mkdirSync12, readdirSync as readdirSync9 } from "node:fs";
34186
+ import { existsSync as existsSync32, readFileSync as readFileSync23, writeFileSync as writeFileSync12, mkdirSync as mkdirSync12, readdirSync as readdirSync10 } from "node:fs";
33750
34187
  import { join as join42, basename as basename11 } from "node:path";
33751
34188
  function loadToolProfile(repoRoot) {
33752
34189
  const filePath = join42(repoRoot, OA_DIR, "context", TOOL_PROFILE_FILE);
@@ -33959,7 +34396,7 @@ function extractFromMemory(repoRoot, tags) {
33959
34396
  try {
33960
34397
  if (!existsSync32(memoryDir))
33961
34398
  return;
33962
- const files = readdirSync9(memoryDir).filter((f) => f.endsWith(".json"));
34399
+ const files = readdirSync10(memoryDir).filter((f) => f.endsWith(".json"));
33963
34400
  for (const file of files) {
33964
34401
  const topic = file.replace(/\.json$/, "").replace(/[-_]/g, " ");
33965
34402
  tags.push(topic);
@@ -36520,7 +36957,7 @@ var init_promptLoader3 = __esm({
36520
36957
  });
36521
36958
 
36522
36959
  // packages/cli/dist/tui/dream-engine.js
36523
- import { mkdirSync as mkdirSync15, writeFileSync as writeFileSync14, readFileSync as readFileSync26, existsSync as existsSync35, cpSync, rmSync, readdirSync as readdirSync10 } from "node:fs";
36960
+ import { mkdirSync as mkdirSync15, writeFileSync as writeFileSync14, readFileSync as readFileSync26, existsSync as existsSync35, cpSync, rmSync, readdirSync as readdirSync11 } from "node:fs";
36524
36961
  import { join as join46, basename as basename12 } from "node:path";
36525
36962
  import { execSync as execSync28 } from "node:child_process";
36526
36963
  function loadAutoresearchMemory(repoRoot) {
@@ -37726,7 +38163,7 @@ Each proposal includes implementation entrypoints and estimated effort.
37726
38163
  /** Update the master proposal index */
37727
38164
  updateProposalIndex() {
37728
38165
  try {
37729
- const files = readdirSync10(this.dreamsDir).filter((f) => f.endsWith(".md") && f !== "PROPOSAL-INDEX.md" && f !== "dream-state.json").sort();
38166
+ const files = readdirSync11(this.dreamsDir).filter((f) => f.endsWith(".md") && f !== "PROPOSAL-INDEX.md" && f !== "dream-state.json").sort();
37730
38167
  const index = `# Dream Proposals Index
37731
38168
 
37732
38169
  **Last updated**: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
@@ -38128,7 +38565,7 @@ var init_bless_engine = __esm({
38128
38565
  });
38129
38566
 
38130
38567
  // packages/cli/dist/tui/dmn-engine.js
38131
- import { existsSync as existsSync36, readFileSync as readFileSync27, writeFileSync as writeFileSync15, mkdirSync as mkdirSync16, readdirSync as readdirSync11, unlinkSync as unlinkSync6 } from "node:fs";
38568
+ import { existsSync as existsSync36, readFileSync as readFileSync27, writeFileSync as writeFileSync15, mkdirSync as mkdirSync16, readdirSync as readdirSync12, unlinkSync as unlinkSync6 } from "node:fs";
38132
38569
  import { join as join47, basename as basename13 } from "node:path";
38133
38570
  function buildDMNGatherPrompt(recentTaskSummaries, dueReminders, attentionItems, memoryTopics, capabilities, competence, reflectionBuffer) {
38134
38571
  const competenceReport = competence.length > 0 ? competence.map((c3) => {
@@ -38840,7 +39277,7 @@ OUTPUT: Call task_complete with JSON:
38840
39277
  if (!existsSync36(dir))
38841
39278
  continue;
38842
39279
  try {
38843
- const files = readdirSync11(dir).filter((f) => f.endsWith(".json"));
39280
+ const files = readdirSync12(dir).filter((f) => f.endsWith(".json"));
38844
39281
  for (const f of files) {
38845
39282
  const topic = basename13(f, ".json");
38846
39283
  if (!topics.includes(topic))
@@ -38871,7 +39308,7 @@ OUTPUT: Call task_complete with JSON:
38871
39308
  try {
38872
39309
  const filename = `cycle-${result.cycleNumber}-${Date.now()}.json`;
38873
39310
  writeFileSync15(join47(this.historyDir, filename), JSON.stringify(result, null, 2) + "\n", "utf-8");
38874
- const files = readdirSync11(this.historyDir).filter((f) => f.startsWith("cycle-") && f.endsWith(".json")).sort();
39311
+ const files = readdirSync12(this.historyDir).filter((f) => f.startsWith("cycle-") && f.endsWith(".json")).sort();
38875
39312
  if (files.length > 50) {
38876
39313
  for (const old of files.slice(0, files.length - 50)) {
38877
39314
  try {
@@ -38888,7 +39325,7 @@ OUTPUT: Call task_complete with JSON:
38888
39325
  });
38889
39326
 
38890
39327
  // packages/cli/dist/tui/snr-engine.js
38891
- import { existsSync as existsSync37, readdirSync as readdirSync12, readFileSync as readFileSync28 } from "node:fs";
39328
+ import { existsSync as existsSync37, readdirSync as readdirSync13, readFileSync as readFileSync28 } from "node:fs";
38892
39329
  import { join as join48, basename as basename14 } from "node:path";
38893
39330
  function computeDPrime(signalScores, noiseScores) {
38894
39331
  if (signalScores.length === 0 || noiseScores.length === 0)
@@ -39136,7 +39573,7 @@ Call task_complete with the JSON array when done.`, onEvent)
39136
39573
  if (!existsSync37(dir))
39137
39574
  continue;
39138
39575
  try {
39139
- const files = readdirSync12(dir).filter((f) => f.endsWith(".json"));
39576
+ const files = readdirSync13(dir).filter((f) => f.endsWith(".json"));
39140
39577
  for (const f of files) {
39141
39578
  const topic = basename14(f, ".json");
39142
39579
  if (topics.length > 0 && !topics.includes(topic))
@@ -39709,7 +40146,7 @@ var init_tool_policy = __esm({
39709
40146
  });
39710
40147
 
39711
40148
  // packages/cli/dist/tui/telegram-bridge.js
39712
- import { mkdirSync as mkdirSync17, existsSync as existsSync38, unlinkSync as unlinkSync7, readdirSync as readdirSync13, statSync as statSync10 } from "node:fs";
40149
+ import { mkdirSync as mkdirSync17, existsSync as existsSync38, unlinkSync as unlinkSync7, readdirSync as readdirSync14, statSync as statSync10 } from "node:fs";
39713
40150
  import { join as join49, resolve as resolve27 } from "node:path";
39714
40151
  import { writeFile as writeFileAsync } from "node:fs/promises";
39715
40152
  function convertMarkdownToTelegramHTML(md) {
@@ -42311,7 +42748,7 @@ import { cwd } from "node:process";
42311
42748
  import { resolve as resolve28, join as join50, dirname as dirname17, extname as extname9 } from "node:path";
42312
42749
  import { createRequire as createRequire2 } from "node:module";
42313
42750
  import { fileURLToPath as fileURLToPath12 } from "node:url";
42314
- import { readFileSync as readFileSync29, writeFileSync as writeFileSync16, appendFileSync as appendFileSync3, rmSync as rmSync2, readdirSync as readdirSync14, mkdirSync as mkdirSync18 } from "node:fs";
42751
+ import { readFileSync as readFileSync29, writeFileSync as writeFileSync16, appendFileSync as appendFileSync3, rmSync as rmSync2, readdirSync as readdirSync15, mkdirSync as mkdirSync18 } from "node:fs";
42315
42752
  import { existsSync as existsSync39 } from "node:fs";
42316
42753
  import { homedir as homedir13 } from "node:os";
42317
42754
  function formatTimeAgo(date) {
@@ -42550,7 +42987,7 @@ function gatherMemorySnippets(root) {
42550
42987
  if (!existsSync39(dir))
42551
42988
  continue;
42552
42989
  try {
42553
- for (const f of readdirSync14(dir).filter((f2) => f2.endsWith(".json"))) {
42990
+ for (const f of readdirSync15(dir).filter((f2) => f2.endsWith(".json"))) {
42554
42991
  const data = JSON.parse(readFileSync29(join50(dir, f), "utf-8"));
42555
42992
  for (const val of Object.values(data)) {
42556
42993
  const v = typeof val === "object" && val !== null && "value" in val ? String(val.value) : String(val);
@@ -44277,7 +44714,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
44277
44714
  getCallUrl() {
44278
44715
  return voiceSession?.tunnelUrl ?? null;
44279
44716
  },
44280
- async exposeStart(kindOrUrl, authKey) {
44717
+ async exposeStart(kindOrUrl, authKey, transport) {
44281
44718
  const knownKinds = ["ollama", "vllm", "llvm"];
44282
44719
  let kind;
44283
44720
  let targetUrl;
@@ -44287,8 +44724,42 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
44287
44724
  kind = "custom";
44288
44725
  targetUrl = kindOrUrl.includes("://") ? kindOrUrl : `http://${kindOrUrl}`;
44289
44726
  }
44290
- exposeGateway = new ExposeGateway({ kind, targetUrl, authKey, stateDir: join50(repoRoot, ".oa") });
44291
- exposeGateway.on("stats", (stats) => {
44727
+ const effectiveTransport = transport ?? "libp2p";
44728
+ if (effectiveTransport === "libp2p") {
44729
+ const nexusTool = new NexusTool(repoRoot);
44730
+ const p2pGateway = new ExposeP2PGateway({
44731
+ kind,
44732
+ targetUrl,
44733
+ stateDir: join50(repoRoot, ".oa")
44734
+ }, nexusTool);
44735
+ p2pGateway.on("stats", (stats) => {
44736
+ statusBar.setExposeStatus({
44737
+ status: stats.status,
44738
+ totalRequests: stats.totalRequests,
44739
+ activeConnections: stats.activeConnections,
44740
+ modelUsage: stats.modelUsage
44741
+ });
44742
+ });
44743
+ try {
44744
+ const peerId = await p2pGateway.start();
44745
+ exposeGateway = p2pGateway;
44746
+ writeContent(() => {
44747
+ process.stdout.write("\n" + p2pGateway.formatConnectionInfo() + "\n\n");
44748
+ });
44749
+ return peerId;
44750
+ } catch (err) {
44751
+ if (!transport) {
44752
+ writeContent(() => {
44753
+ renderWarning(`libp2p expose failed: ${err instanceof Error ? err.message : String(err)}`);
44754
+ renderInfo("Falling back to cloudflared tunnel...");
44755
+ });
44756
+ } else {
44757
+ throw err;
44758
+ }
44759
+ }
44760
+ }
44761
+ const tunnelGateway = new ExposeGateway({ kind, targetUrl, authKey, stateDir: join50(repoRoot, ".oa") });
44762
+ tunnelGateway.on("stats", (stats) => {
44292
44763
  statusBar.setExposeStatus({
44293
44764
  status: stats.status,
44294
44765
  totalRequests: stats.totalRequests,
@@ -44297,9 +44768,10 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
44297
44768
  });
44298
44769
  });
44299
44770
  try {
44300
- const url = await exposeGateway.start();
44771
+ const url = await tunnelGateway.start();
44772
+ exposeGateway = tunnelGateway;
44301
44773
  writeContent(() => {
44302
- process.stdout.write("\n" + exposeGateway.formatConnectionInfo() + "\n\n");
44774
+ process.stdout.write("\n" + tunnelGateway.formatConnectionInfo() + "\n\n");
44303
44775
  });
44304
44776
  return url;
44305
44777
  } catch (err) {
@@ -44320,6 +44792,13 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
44320
44792
  getExposeInfo() {
44321
44793
  if (!exposeGateway)
44322
44794
  return null;
44795
+ if (exposeGateway instanceof ExposeP2PGateway) {
44796
+ return {
44797
+ tunnelUrl: exposeGateway.peerId,
44798
+ authKey: "",
44799
+ stats: exposeGateway.formatStats()
44800
+ };
44801
+ }
44323
44802
  return {
44324
44803
  tunnelUrl: exposeGateway.tunnelUrl,
44325
44804
  authKey: exposeGateway.authKey,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.103.15",
3
+ "version": "0.103.17",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",