open-agents-ai 0.103.68 → 0.103.70

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 +171 -64
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -12130,6 +12130,26 @@ const blockedPeers = [];
12130
12130
  // Both peers connect to wss://demo.nats.io:8443, so NATS request/reply bridges the gap.
12131
12131
  var _natsConn = null;
12132
12132
  var _natsCodec = null;
12133
+ var _tokensByRequest = {};
12134
+
12135
+ // Check if an error is a connectivity/dial failure (should trigger NATS fallback)
12136
+ // vs an application-level error (auth rejected, capability not found \u2014 no point retrying via NATS)
12137
+ function isDialFailure(errMsg) {
12138
+ var appErrors = ['unauthorized', 'capability not found', 'not registered', 'auth rejected'];
12139
+ var lower = errMsg.toLowerCase();
12140
+ for (var i = 0; i < appErrors.length; i++) {
12141
+ if (lower.includes(appErrors[i])) return false;
12142
+ }
12143
+ // Any network/transport error should fall back to NATS
12144
+ return lower.includes('multiaddrs failed') || lower.includes('econnrefused') ||
12145
+ lower.includes('etimedout') || lower.includes('dial to peer') ||
12146
+ lower.includes('no route') || lower.includes('circuit relay') ||
12147
+ lower.includes('stream was reset') || lower.includes('timeout') ||
12148
+ lower.includes('connection reset') || lower.includes('ehostunreach') ||
12149
+ lower.includes('enetunreach') || lower.includes('peer not reachable') ||
12150
+ lower.includes('dial error') || lower.includes('aborted') ||
12151
+ lower.includes('connection refused');
12152
+ }
12133
12153
 
12134
12154
  function writeStatus(extra = {}) {
12135
12155
  const caps = typeof nexus.getRegisteredCapabilities === 'function'
@@ -12293,7 +12313,7 @@ async function handleCmd(cmd) {
12293
12313
  var qpcErrMsg = qpcErr.message || String(qpcErr);
12294
12314
  dlog('query_peer_caps: ERROR ' + qpcErrMsg);
12295
12315
  // NATS fallback for peer cap discovery across NAT
12296
- if (_natsConn && _natsCodec && qpcErrMsg.includes('multiaddrs failed')) {
12316
+ if (_natsConn && _natsCodec && isDialFailure(qpcErrMsg)) {
12297
12317
  dlog('query_peer_caps: trying NATS relay fallback');
12298
12318
  try {
12299
12319
  var _qpcNatsSubject = 'nexus.invoke.' + String(qpcPeerId);
@@ -12341,8 +12361,8 @@ async function handleCmd(cmd) {
12341
12361
  } catch (invokeErr) {
12342
12362
  var invokeErrMsg = invokeErr.message || String(invokeErr);
12343
12363
  dlog('invoke_capability: ERROR ' + invokeErrMsg);
12344
- // NATS fallback \u2014 if direct dial failed (NAT), retry via NATS relay
12345
- if (_natsConn && _natsCodec && invokeErrMsg.includes('multiaddrs failed')) {
12364
+ // NATS fallback \u2014 if direct dial failed (NAT/timeout/refused), retry via NATS relay
12365
+ if (_natsConn && _natsCodec && isDialFailure(invokeErrMsg)) {
12346
12366
  dlog('invoke_capability: trying NATS relay fallback for ' + peerId.slice(0, 20));
12347
12367
  try {
12348
12368
  var _icNatsSubject = 'nexus.invoke.' + peerId;
@@ -12420,8 +12440,8 @@ async function handleCmd(cmd) {
12420
12440
  } catch (riErr) {
12421
12441
  var riErrMsg = riErr.message || String(riErr);
12422
12442
  dlog('remote_infer: ERROR (explicit peer) ' + riErrMsg);
12423
- // NATS fallback \u2014 if direct dial failed (NAT/firewall), retry via NATS relay
12424
- if (_natsConn && _natsCodec && riErrMsg.includes('multiaddrs failed')) {
12443
+ // NATS fallback \u2014 if direct dial failed (NAT/firewall/timeout), retry via NATS relay
12444
+ if (_natsConn && _natsCodec && isDialFailure(riErrMsg)) {
12425
12445
  dlog('remote_infer: trying NATS relay fallback for ' + riTargetPeer.slice(0, 20));
12426
12446
  try {
12427
12447
  var _riNatsSubject = 'nexus.invoke.' + riTargetPeer;
@@ -12488,6 +12508,36 @@ async function handleCmd(cmd) {
12488
12508
  }
12489
12509
  dlog('remote_infer: auto-discover took ' + (Date.now() - discStart) + 'ms');
12490
12510
 
12511
+ // If libp2p auto-discover failed, try NATS relay against discovered peers from cache
12512
+ if (!riResult && _natsConn && _natsCodec) {
12513
+ try {
12514
+ var _dpFile = join(nexusDir, 'discovered-peers.json');
12515
+ if (existsSync(_dpFile)) {
12516
+ var _dpData = JSON.parse(readFileSync(_dpFile, 'utf8'));
12517
+ var _dpPeers = Object.keys(_dpData).filter(function(p) { return p.startsWith('12D3KooW'); });
12518
+ dlog('remote_infer: trying NATS relay on ' + _dpPeers.length + ' cached peers');
12519
+ for (var _dpi = 0; _dpi < _dpPeers.length && !riResult; _dpi++) {
12520
+ var _dpPid = _dpPeers[_dpi];
12521
+ try {
12522
+ var _dpSubject = 'nexus.invoke.' + _dpPid;
12523
+ var _dpPayload = JSON.stringify({ capability: riCapName, input: riData, from: nexus.peerId || '' });
12524
+ var _dpResp = await _natsConn.request(_dpSubject, _natsCodec.encode(_dpPayload), { timeout: 180000 });
12525
+ var _dpResult = JSON.parse(_natsCodec.decode(_dpResp.data));
12526
+ if (_dpResult && !_dpResult.error) {
12527
+ riResult = _dpResult;
12528
+ riTargetPeer = _dpPid;
12529
+ dlog('remote_infer: SUCCESS via NATS relay peer ' + _dpPid.slice(0, 20));
12530
+ }
12531
+ } catch (_dpErr) {
12532
+ dlog('remote_infer: NATS peer ' + _dpPid.slice(0, 20) + ' failed: ' + (_dpErr.message || _dpErr));
12533
+ }
12534
+ }
12535
+ }
12536
+ } catch (_dpCacheErr) {
12537
+ dlog('remote_infer: NATS discovery cache error: ' + (_dpCacheErr.message || _dpCacheErr));
12538
+ }
12539
+ }
12540
+
12491
12541
  if (!riResult) {
12492
12542
  writeResp(id, { ok: false, output: 'No peer found with capability: ' + riCapName + '. Ensure a provider is running expose with model ' + riModel });
12493
12543
  return;
@@ -13100,6 +13150,12 @@ async function handleCmd(cmd) {
13100
13150
  // Register system_metrics capability \u2014 returns CPU/GPU/memory utilization
13101
13151
  if (typeof nexus.registerCapability === 'function') {
13102
13152
  nexus.registerCapability('system_metrics', async (request, stream) => {
13153
+ // Stream safety wrapper \u2014 prevents unguarded writes after consumer disconnects
13154
+ var smStreamClosed = false;
13155
+ async function smWrite(msg) {
13156
+ if (smStreamClosed) return;
13157
+ try { await stream.write(msg); } catch { smStreamClosed = true; }
13158
+ }
13103
13159
  // Collect input via stream data events (auth_key arrives in invoke.chunk, NOT invoke.open)
13104
13160
  var smDataChunks = [];
13105
13161
  var smInputDone = false;
@@ -13113,7 +13169,7 @@ async function handleCmd(cmd) {
13113
13169
  });
13114
13170
 
13115
13171
  // Accept invocation so consumer sends data
13116
- await stream.write({ type: 'invoke.accept', version: 1, requestId: request.requestId, accepted: true });
13172
+ await smWrite({ type: 'invoke.accept', version: 1, requestId: request.requestId, accepted: true });
13117
13173
 
13118
13174
  // Wait briefly for data (auth key arrives in chunk)
13119
13175
  var smWait = 0;
@@ -13138,9 +13194,9 @@ async function handleCmd(cmd) {
13138
13194
  }
13139
13195
  if (smAuthKey !== exposeAuthKey) {
13140
13196
  dlog('system_metrics: auth REJECTED from ' + (request.from || 'unknown'));
13141
- await stream.write({ type: 'invoke.event', version: 1, requestId: request.requestId, seq: 0, event: 'error', data: 'Unauthorized' });
13142
- await stream.write({ type: 'invoke.done', version: 1, requestId: request.requestId, usage: { inputBytes: 0, outputBytes: 0 } });
13143
- stream.close();
13197
+ await smWrite({ type: 'invoke.event', version: 1, requestId: request.requestId, seq: 0, event: 'error', data: 'Unauthorized' });
13198
+ await smWrite({ type: 'invoke.done', version: 1, requestId: request.requestId, usage: { inputBytes: 0, outputBytes: 0 } });
13199
+ try { stream.close(); } catch {}
13144
13200
  return;
13145
13201
  }
13146
13202
  dlog('system_metrics: auth OK');
@@ -13175,13 +13231,13 @@ async function handleCmd(cmd) {
13175
13231
  gpu: gpuInfo,
13176
13232
  timestamp: new Date().toISOString(),
13177
13233
  };
13178
- await stream.write({ type: 'invoke.event', version: 1, requestId: request.requestId, seq: 0, event: 'result', data: JSON.stringify(metricsPayload) });
13179
- await stream.write({ type: 'invoke.done', version: 1, requestId: request.requestId, usage: { inputBytes: 0, outputBytes: JSON.stringify(metricsPayload).length } });
13234
+ await smWrite({ type: 'invoke.event', version: 1, requestId: request.requestId, seq: 0, event: 'result', data: JSON.stringify(metricsPayload) });
13235
+ await smWrite({ type: 'invoke.done', version: 1, requestId: request.requestId, usage: { inputBytes: 0, outputBytes: JSON.stringify(metricsPayload).length } });
13180
13236
  } catch (me) {
13181
- await stream.write({ type: 'invoke.event', version: 1, requestId: request.requestId, seq: 0, event: 'error', data: 'metrics error: ' + me.message });
13182
- await stream.write({ type: 'invoke.done', version: 1, requestId: request.requestId, usage: { inputBytes: 0, outputBytes: 0 } });
13237
+ await smWrite({ type: 'invoke.event', version: 1, requestId: request.requestId, seq: 0, event: 'error', data: 'metrics error: ' + me.message });
13238
+ await smWrite({ type: 'invoke.done', version: 1, requestId: request.requestId, usage: { inputBytes: 0, outputBytes: 0 } });
13183
13239
  }
13184
- stream.close();
13240
+ try { stream.close(); } catch {}
13185
13241
  });
13186
13242
  dlog('system_metrics capability registered');
13187
13243
  }
@@ -13380,7 +13436,6 @@ process.on('unhandledRejection', (reason) => {
13380
13436
  // The nexus library fires this hook AFTER handler returns, so it has the correct
13381
13437
  // peerId from connection context. We stash token data per requestId so the hook
13382
13438
  // can merge them into one complete record written to metering.jsonl.
13383
- var _tokensByRequest = {};
13384
13439
  try {
13385
13440
  if (nexus.metering && typeof nexus.metering.addHook === 'function') {
13386
13441
  nexus.metering.addHook(function(record) {
@@ -13532,25 +13587,39 @@ process.on('unhandledRejection', (reason) => {
13532
13587
  continue;
13533
13588
  }
13534
13589
 
13535
- // Create mock stream handle that captures the handler's output
13590
+ // Create mock stream handle that captures the handler's output.
13591
+ // Uses retroactive delivery: if handler registers onData after data is
13592
+ // already queued, deliver immediately. Eliminates timing race.
13536
13593
  var _nResults = [];
13537
- var _nHandleDone = false;
13594
+ var _nDataCbs = [];
13595
+ var _nDataDelivered = false;
13538
13596
  var _nHandle = {
13539
13597
  write: async function(msg) {
13540
13598
  if (msg && msg.type === 'invoke.event') _nResults.push(msg);
13541
- if (msg && msg.type === 'invoke.done') _nHandleDone = true;
13542
13599
  },
13543
13600
  onData: function(cb) {
13544
- // Deliver input data asynchronously after handler registers its listener
13545
- setTimeout(function() {
13601
+ if (_nDataDelivered) {
13602
+ // Data already flushed \u2014 deliver retroactively
13546
13603
  try {
13547
13604
  cb({ type: 'invoke.chunk', data: _nInputStr, seq: 0 });
13548
13605
  cb({ type: 'invoke.done' });
13549
13606
  } catch {}
13550
- }, 5);
13607
+ } else {
13608
+ _nDataCbs.push(cb);
13609
+ }
13551
13610
  },
13552
13611
  close: function() {},
13553
13612
  };
13613
+ // Flush input data to any registered onData listeners after 50ms
13614
+ setTimeout(function() {
13615
+ _nDataDelivered = true;
13616
+ for (var _di = 0; _di < _nDataCbs.length; _di++) {
13617
+ try {
13618
+ _nDataCbs[_di]({ type: 'invoke.chunk', data: _nInputStr, seq: 0 });
13619
+ _nDataCbs[_di]({ type: 'invoke.done' });
13620
+ } catch {}
13621
+ }
13622
+ }, 50);
13554
13623
 
13555
13624
  var _nRequest = {
13556
13625
  type: 'invoke.open',
@@ -13803,7 +13872,7 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
13803
13872
  result = await this.sendDaemonCmd("query_peer_caps", args, 2e4);
13804
13873
  break;
13805
13874
  case "invoke_capability":
13806
- result = await this.sendDaemonCmd("invoke_capability", args, 1e5);
13875
+ result = await this.sendDaemonCmd("invoke_capability", args, 32e4);
13807
13876
  break;
13808
13877
  case "store_content":
13809
13878
  result = await this.sendDaemonCmd("store_content", args);
@@ -14899,7 +14968,7 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
14899
14968
  } else {
14900
14969
  daemonArgs.prompt = prompt;
14901
14970
  }
14902
- const result = await this.sendDaemonCmd("remote_infer", daemonArgs, 12e4);
14971
+ const result = await this.sendDaemonCmd("remote_infer", daemonArgs, 32e4);
14903
14972
  let peerUsed = args.target_peer || "auto-selected";
14904
14973
  try {
14905
14974
  const parsed = JSON.parse(result);
@@ -35530,12 +35599,6 @@ async function handleUpdate(subcommand, ctx) {
35530
35599
  process.stdout.write("\n");
35531
35600
  const checkSpinner = startInlineSpinner(`Checking for updates ${c2.dim(`(current: v${currentVersion})`)}`);
35532
35601
  const info = await checkForUpdate(currentVersion, true);
35533
- if (!info) {
35534
- checkSpinner.stop(`You're on the latest version (v${currentVersion}).`);
35535
- process.stdout.write("\n");
35536
- return;
35537
- }
35538
- checkSpinner.stop(`Update available: v${info.currentVersion} \u2192 v${c2.bold(c2.green(info.latestVersion))}`);
35539
35602
  const { exec: exec3, execSync: es2 } = await import("node:child_process");
35540
35603
  let needsSudo = false;
35541
35604
  try {
@@ -35548,21 +35611,6 @@ async function handleUpdate(subcommand, ctx) {
35548
35611
  }
35549
35612
  } catch {
35550
35613
  }
35551
- if (needsSudo) {
35552
- renderInfo("Global npm directory requires elevated permissions.");
35553
- renderInfo("You'll be asked for your password once \u2014 it covers all update steps.");
35554
- process.stdout.write("\n");
35555
- try {
35556
- es2("sudo -v", { stdio: "inherit", timeout: 6e4 });
35557
- } catch {
35558
- renderWarning("Could not acquire sudo credentials. Try: sudo npm i -g open-agents-ai");
35559
- return;
35560
- }
35561
- }
35562
- const sudoPrefix = needsSudo ? "sudo " : "";
35563
- const installSpinner = startInlineSpinner("Installing update");
35564
- const installCmd = `${sudoPrefix}npm install -g open-agents-ai@latest --prefer-online`;
35565
- let installOk = false;
35566
35614
  let installError = "";
35567
35615
  const runInstall2 = (cmd) => new Promise((resolve31) => {
35568
35616
  const child = exec3(cmd, { timeout: 18e4 }, (err, _stdout, stderr) => {
@@ -35572,35 +35620,84 @@ async function handleUpdate(subcommand, ctx) {
35572
35620
  });
35573
35621
  child.stdout?.resume();
35574
35622
  });
35575
- installOk = await runInstall2(installCmd);
35576
- if (!installOk && /ENOTEMPTY|errno -39/i.test(installError)) {
35577
- installSpinner.stop("Cleaning stale npm temp files...");
35623
+ if (needsSudo) {
35624
+ renderInfo("Global npm directory requires elevated permissions.");
35625
+ renderInfo("You'll be asked for your password once \u2014 it covers all update steps.");
35626
+ process.stdout.write("\n");
35578
35627
  try {
35579
- const prefix = es2("npm prefix -g", { encoding: "utf8", timeout: 5e3 }).trim();
35580
- const globalModules = prefix.endsWith("/lib") ? prefix + "/node_modules" : prefix + "/lib/node_modules";
35581
- es2(`${sudoPrefix}find "${globalModules}" -maxdepth 1 -name ".open-agents-ai-*" -type d -exec rm -rf {} + 2>/dev/null || true`, { timeout: 15e3 });
35582
- es2(`${sudoPrefix}rm -rf "${globalModules}/open-agents-ai" 2>/dev/null || true`, { timeout: 15e3 });
35628
+ es2("sudo -v", { stdio: "inherit", timeout: 6e4 });
35583
35629
  } catch {
35630
+ renderWarning("Could not acquire sudo credentials. Try: sudo npm i -g open-agents-ai");
35631
+ return;
35584
35632
  }
35585
- const retrySpinner = startInlineSpinner("Retrying install");
35586
- installError = "";
35633
+ }
35634
+ const sudoPrefix = needsSudo ? "sudo " : "";
35635
+ let primaryUpdated = false;
35636
+ if (!info) {
35637
+ checkSpinner.stop(`Primary package up to date (v${currentVersion}).`);
35638
+ } else {
35639
+ checkSpinner.stop(`Update available: v${info.currentVersion} \u2192 v${c2.bold(c2.green(info.latestVersion))}`);
35640
+ const installSpinner = startInlineSpinner("Installing update");
35641
+ const installCmd = `${sudoPrefix}npm install -g open-agents-ai@latest --prefer-online`;
35642
+ let installOk = false;
35587
35643
  installOk = await runInstall2(installCmd);
35644
+ if (!installOk && /ENOTEMPTY|errno -39/i.test(installError)) {
35645
+ installSpinner.stop("Cleaning stale npm temp files...");
35646
+ try {
35647
+ const prefix = es2("npm prefix -g", { encoding: "utf8", timeout: 5e3 }).trim();
35648
+ const globalModules = prefix.endsWith("/lib") ? prefix + "/node_modules" : prefix + "/lib/node_modules";
35649
+ es2(`${sudoPrefix}find "${globalModules}" -maxdepth 1 -name ".open-agents-ai-*" -type d -exec rm -rf {} + 2>/dev/null || true`, { timeout: 15e3 });
35650
+ es2(`${sudoPrefix}rm -rf "${globalModules}/open-agents-ai" 2>/dev/null || true`, { timeout: 15e3 });
35651
+ } catch {
35652
+ }
35653
+ const retrySpinner = startInlineSpinner("Retrying install");
35654
+ installError = "";
35655
+ installOk = await runInstall2(installCmd);
35656
+ if (!installOk) {
35657
+ retrySpinner.stop("Retry failed.");
35658
+ } else {
35659
+ retrySpinner.stop(`Update installed (v${info.latestVersion}).`);
35660
+ }
35661
+ }
35588
35662
  if (!installOk) {
35589
- retrySpinner.stop("Retry failed.");
35590
- } else {
35591
- retrySpinner.stop(`Update installed (v${info.latestVersion}).`);
35663
+ installSpinner.stop("Update install failed.");
35664
+ const errPreview = installError.split("\n").filter((l) => l.trim()).slice(-3).join("\n ");
35665
+ if (errPreview)
35666
+ renderWarning(`Error:
35667
+ ${errPreview}`);
35668
+ renderWarning(`Try manually: ${sudoPrefix}npm i -g open-agents-ai`);
35669
+ return;
35592
35670
  }
35671
+ installSpinner.stop(`Update installed (v${info.latestVersion}).`);
35672
+ primaryUpdated = true;
35593
35673
  }
35594
- if (!installOk) {
35595
- installSpinner.stop("Update install failed.");
35596
- const errPreview = installError.split("\n").filter((l) => l.trim()).slice(-3).join("\n ");
35597
- if (errPreview)
35598
- renderWarning(`Error:
35599
- ${errPreview}`);
35600
- renderWarning(`Try manually: ${sudoPrefix}npm i -g open-agents-ai`);
35674
+ const depsSpinner = startInlineSpinner("Checking subordinate dependencies");
35675
+ let depsUpdated = false;
35676
+ try {
35677
+ const prefix = es2("npm prefix -g", { encoding: "utf8", timeout: 5e3 }).trim();
35678
+ const globalModules = prefix.endsWith("/lib") ? prefix + "/node_modules" : prefix + "/lib/node_modules";
35679
+ const { existsSync: fe, readFileSync: rf } = await import("node:fs");
35680
+ const { join: pj } = await import("node:path");
35681
+ const pkgPath = pj(globalModules, "open-agents-ai", "package.json");
35682
+ if (fe(pkgPath)) {
35683
+ const pkg = JSON.parse(rf(pkgPath, "utf8"));
35684
+ const allDeps = { ...pkg.dependencies || {}, ...pkg.optionalDependencies || {} };
35685
+ const depNames = Object.keys(allDeps);
35686
+ if (depNames.length > 0) {
35687
+ const updateCmd = `${sudoPrefix}npm update --prefer-online --prefix "${pj(globalModules, "open-agents-ai")}" 2>/dev/null || true`;
35688
+ const updateOk = await runInstall2(updateCmd);
35689
+ if (updateOk)
35690
+ depsUpdated = true;
35691
+ }
35692
+ }
35693
+ } catch {
35694
+ }
35695
+ depsSpinner.stop(depsUpdated ? "Dependencies updated to latest." : "Dependencies checked.");
35696
+ if (!primaryUpdated && !depsUpdated) {
35697
+ renderInfo("Everything is up to date \u2014 no changes needed.");
35698
+ process.stdout.write("\n");
35601
35699
  return;
35602
35700
  }
35603
- installSpinner.stop(`Update installed (v${info.latestVersion}).`);
35604
35701
  const rebuildSpinner = startInlineSpinner("Rebuilding native modules");
35605
35702
  const rebuildOk = await new Promise((resolve31) => {
35606
35703
  const child = exec3(`${sudoPrefix}npm rebuild -g open-agents-ai 2>/dev/null || true`, { timeout: 12e4 }, () => resolve31(true));
@@ -47499,6 +47596,16 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
47499
47596
  });
47500
47597
  }
47501
47598
  }
47599
+ try {
47600
+ const autoNexus = new NexusTool(repoRoot);
47601
+ autoNexus.execute({ action: "connect" }).then((r) => {
47602
+ if (r.success && r.output.includes("Connected")) {
47603
+ writeContent(() => renderInfo("Nexus P2P network connected."));
47604
+ }
47605
+ }).catch(() => {
47606
+ });
47607
+ } catch {
47608
+ }
47502
47609
  try {
47503
47610
  const oaDir = join50(repoRoot, ".oa");
47504
47611
  const reconnected = await ExposeGateway.checkAndReconnect(oaDir, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.103.68",
3
+ "version": "0.103.70",
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",