open-agents-ai 0.103.66 → 0.103.68

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 +246 -19
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -12126,6 +12126,11 @@ const rooms = new Map();
12126
12126
  let connected = false;
12127
12127
  const blockedPeers = [];
12128
12128
 
12129
+ // NATS invoke relay \u2014 for cross-NAT fallback when direct libp2p dial fails.
12130
+ // Both peers connect to wss://demo.nats.io:8443, so NATS request/reply bridges the gap.
12131
+ var _natsConn = null;
12132
+ var _natsCodec = null;
12133
+
12129
12134
  function writeStatus(extra = {}) {
12130
12135
  const caps = typeof nexus.getRegisteredCapabilities === 'function'
12131
12136
  ? nexus.getRegisteredCapabilities() : [];
@@ -12285,8 +12290,37 @@ async function handleCmd(cmd) {
12285
12290
  dlog('query_peer_caps: SUCCESS');
12286
12291
  writeResp(id, { ok: true, output: typeof qpcResult === 'string' ? qpcResult : JSON.stringify(qpcResult, null, 2) });
12287
12292
  } catch (qpcErr) {
12288
- dlog('query_peer_caps: ERROR ' + (qpcErr.message || String(qpcErr)));
12289
- writeResp(id, { ok: false, output: 'query_peer_caps error: ' + (qpcErr.message || String(qpcErr)) });
12293
+ var qpcErrMsg = qpcErr.message || String(qpcErr);
12294
+ dlog('query_peer_caps: ERROR ' + qpcErrMsg);
12295
+ // NATS fallback for peer cap discovery across NAT
12296
+ if (_natsConn && _natsCodec && qpcErrMsg.includes('multiaddrs failed')) {
12297
+ dlog('query_peer_caps: trying NATS relay fallback');
12298
+ try {
12299
+ var _qpcNatsSubject = 'nexus.invoke.' + String(qpcPeerId);
12300
+ var _qpcNatsPayload = JSON.stringify({
12301
+ capability: '__list_capabilities',
12302
+ input: qpcInput,
12303
+ from: nexus.peerId || '',
12304
+ });
12305
+ var _qpcNatsResp = await _natsConn.request(
12306
+ _qpcNatsSubject,
12307
+ _natsCodec.encode(_qpcNatsPayload),
12308
+ { timeout: 15000 }
12309
+ );
12310
+ var _qpcNatsResult = JSON.parse(_natsCodec.decode(_qpcNatsResp.data));
12311
+ if (_qpcNatsResult && _qpcNatsResult.error) {
12312
+ writeResp(id, { ok: false, output: 'query_peer_caps (NATS): ' + _qpcNatsResult.error });
12313
+ } else {
12314
+ dlog('query_peer_caps: SUCCESS via NATS relay');
12315
+ writeResp(id, { ok: true, output: typeof _qpcNatsResult === 'string' ? _qpcNatsResult : JSON.stringify(_qpcNatsResult, null, 2) });
12316
+ }
12317
+ } catch (_qpcNatsErr) {
12318
+ dlog('query_peer_caps: NATS fallback also failed: ' + (_qpcNatsErr.message || _qpcNatsErr));
12319
+ writeResp(id, { ok: false, output: 'query_peer_caps error (libp2p + NATS): ' + qpcErrMsg });
12320
+ }
12321
+ } else {
12322
+ writeResp(id, { ok: false, output: 'query_peer_caps error: ' + qpcErrMsg });
12323
+ }
12290
12324
  }
12291
12325
  break;
12292
12326
  }
@@ -12305,8 +12339,37 @@ async function handleCmd(cmd) {
12305
12339
  dlog('invoke_capability: SUCCESS');
12306
12340
  writeResp(id, { ok: true, output: JSON.stringify(result, null, 2) });
12307
12341
  } catch (invokeErr) {
12308
- dlog('invoke_capability: ERROR ' + (invokeErr.message || String(invokeErr)));
12309
- writeResp(id, { ok: false, output: 'Invoke error: ' + (invokeErr.message || String(invokeErr)) });
12342
+ var invokeErrMsg = invokeErr.message || String(invokeErr);
12343
+ 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')) {
12346
+ dlog('invoke_capability: trying NATS relay fallback for ' + peerId.slice(0, 20));
12347
+ try {
12348
+ var _icNatsSubject = 'nexus.invoke.' + peerId;
12349
+ var _icNatsPayload = JSON.stringify({
12350
+ capability: capability,
12351
+ input: typeof input === 'string' ? { prompt: input } : input,
12352
+ from: nexus.peerId || '',
12353
+ });
12354
+ var _icNatsResp = await _natsConn.request(
12355
+ _icNatsSubject,
12356
+ _natsCodec.encode(_icNatsPayload),
12357
+ { timeout: 240000 }
12358
+ );
12359
+ var _icNatsResult = JSON.parse(_natsCodec.decode(_icNatsResp.data));
12360
+ if (_icNatsResult && _icNatsResult.error) {
12361
+ writeResp(id, { ok: false, output: 'NATS relay error: ' + _icNatsResult.error });
12362
+ } else {
12363
+ dlog('invoke_capability: SUCCESS via NATS relay');
12364
+ writeResp(id, { ok: true, output: typeof _icNatsResult === 'string' ? _icNatsResult : JSON.stringify(_icNatsResult, null, 2) });
12365
+ }
12366
+ } catch (_icNatsErr) {
12367
+ dlog('invoke_capability: NATS fallback also failed: ' + (_icNatsErr.message || _icNatsErr));
12368
+ writeResp(id, { ok: false, output: 'Invoke error (libp2p + NATS fallback failed): ' + invokeErrMsg });
12369
+ }
12370
+ } else {
12371
+ writeResp(id, { ok: false, output: 'Invoke error: ' + invokeErrMsg });
12372
+ }
12310
12373
  }
12311
12374
  break;
12312
12375
  }
@@ -12355,9 +12418,39 @@ async function handleCmd(cmd) {
12355
12418
  var riResult = await Promise.race([riInvokeP, riTimeoutP]);
12356
12419
  dlog('remote_infer: SUCCESS (explicit peer)');
12357
12420
  } catch (riErr) {
12358
- dlog('remote_infer: ERROR (explicit peer) ' + (riErr.message || String(riErr)));
12359
- writeResp(id, { ok: false, output: 'Remote inference failed: ' + (riErr.message || String(riErr)) });
12360
- return;
12421
+ var riErrMsg = riErr.message || String(riErr);
12422
+ 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')) {
12425
+ dlog('remote_infer: trying NATS relay fallback for ' + riTargetPeer.slice(0, 20));
12426
+ try {
12427
+ var _riNatsSubject = 'nexus.invoke.' + riTargetPeer;
12428
+ var _riNatsPayload = JSON.stringify({
12429
+ capability: riCapName,
12430
+ input: riData,
12431
+ from: nexus.peerId || '',
12432
+ });
12433
+ var _riNatsResp = await _natsConn.request(
12434
+ _riNatsSubject,
12435
+ _natsCodec.encode(_riNatsPayload),
12436
+ { timeout: 240000 }
12437
+ );
12438
+ var _riNatsResult = JSON.parse(_natsCodec.decode(_riNatsResp.data));
12439
+ if (_riNatsResult && _riNatsResult.error) {
12440
+ writeResp(id, { ok: false, output: 'Remote inference failed (NATS relay): ' + _riNatsResult.error });
12441
+ return;
12442
+ }
12443
+ riResult = _riNatsResult;
12444
+ dlog('remote_infer: SUCCESS via NATS relay');
12445
+ } catch (_riNatsErr) {
12446
+ dlog('remote_infer: NATS fallback also failed: ' + (_riNatsErr.message || _riNatsErr));
12447
+ writeResp(id, { ok: false, output: 'Remote inference failed (libp2p + NATS): ' + riErrMsg });
12448
+ return;
12449
+ }
12450
+ } else {
12451
+ writeResp(id, { ok: false, output: 'Remote inference failed: ' + riErrMsg });
12452
+ return;
12453
+ }
12361
12454
  }
12362
12455
  } else {
12363
12456
  // Auto-discover: try speculative invoke on each 12D3KooW peer
@@ -12402,14 +12495,28 @@ async function handleCmd(cmd) {
12402
12495
  }
12403
12496
 
12404
12497
  // riResult and riTargetPeer are available from either path above
12405
- // Check if the invoke returned an error message instead of real data
12498
+ // Check if the invoke returned an error message instead of real data.
12499
+ // IMPORTANT: Only flag short non-JSON strings as errors. Valid inference
12500
+ // responses are JSON with model/response/choices \u2014 the model's natural
12501
+ // language output often contains words like "error" or "not found" which
12502
+ // would cause false positives if we checked the entire JSON string.
12406
12503
  var riIsError = false;
12407
12504
  if (typeof riResult === 'string') {
12408
- var riLower = riResult.toLowerCase();
12409
- if (riLower.includes('unauthorized') || riLower.includes('forbidden') ||
12410
- riLower.includes('not found') || riLower.includes('error') ||
12411
- riLower.includes('payment') || riLower.includes('rejected')) {
12412
- riIsError = true;
12505
+ // Try to parse as JSON \u2014 if it has model+response or choices, it's valid
12506
+ var riParsedCheck = null;
12507
+ try { riParsedCheck = JSON.parse(riResult); } catch {}
12508
+ if (riParsedCheck && typeof riParsedCheck === 'object' &&
12509
+ (riParsedCheck.model || riParsedCheck.choices || riParsedCheck.response !== undefined)) {
12510
+ // Valid inference response \u2014 NOT an error
12511
+ riIsError = false;
12512
+ } else {
12513
+ // Non-JSON or unstructured response \u2014 check for error keywords
12514
+ var riLower = riResult.toLowerCase();
12515
+ if (riLower.includes('unauthorized') || riLower.includes('forbidden') ||
12516
+ riLower.includes('not found') || riLower.includes('error') ||
12517
+ riLower.includes('payment') || riLower.includes('rejected')) {
12518
+ riIsError = true;
12519
+ }
12413
12520
  }
12414
12521
  }
12415
12522
  if (riIsError) {
@@ -12993,22 +13100,51 @@ async function handleCmd(cmd) {
12993
13100
  // Register system_metrics capability \u2014 returns CPU/GPU/memory utilization
12994
13101
  if (typeof nexus.registerCapability === 'function') {
12995
13102
  nexus.registerCapability('system_metrics', async (request, stream) => {
12996
- // Auth check for system_metrics too
13103
+ // Collect input via stream data events (auth_key arrives in invoke.chunk, NOT invoke.open)
13104
+ var smDataChunks = [];
13105
+ var smInputDone = false;
13106
+ stream.onData(function(msg) {
13107
+ if (msg.type === 'invoke.chunk') {
13108
+ smDataChunks.push(typeof msg.data === 'string' ? msg.data : JSON.stringify(msg.data));
13109
+ }
13110
+ if (msg.type === 'invoke.done' || msg.type === 'invoke.end') {
13111
+ smInputDone = true;
13112
+ }
13113
+ });
13114
+
13115
+ // Accept invocation so consumer sends data
13116
+ await stream.write({ type: 'invoke.accept', version: 1, requestId: request.requestId, accepted: true });
13117
+
13118
+ // Wait briefly for data (auth key arrives in chunk)
13119
+ var smWait = 0;
13120
+ while (!smInputDone && smDataChunks.length === 0 && smWait < 2000) {
13121
+ await new Promise(function(r) { setTimeout(r, 10); });
13122
+ smWait += 10;
13123
+ }
13124
+
13125
+ // Auth check \u2014 extract auth_key from chunk data
12997
13126
  if (exposeAuthKey) {
12998
13127
  var smAuthKey = '';
12999
- if (request.data && typeof request.data === 'object' && request.data.auth_key) {
13000
- smAuthKey = request.data.auth_key;
13001
- } else if (request.metadata && request.metadata.auth_key) {
13128
+ var smPayload = smDataChunks.join('');
13129
+ try {
13130
+ var smParsed = JSON.parse(smPayload);
13131
+ if (smParsed && typeof smParsed === 'object' && smParsed.auth_key) {
13132
+ smAuthKey = smParsed.auth_key;
13133
+ }
13134
+ } catch {}
13135
+ // Fallback: check invoke.open metadata (future-proofing)
13136
+ if (!smAuthKey && request.metadata && request.metadata.auth_key) {
13002
13137
  smAuthKey = request.metadata.auth_key;
13003
13138
  }
13004
13139
  if (smAuthKey !== exposeAuthKey) {
13005
- await stream.write({ type: 'invoke.accept', version: 1, requestId: request.requestId, accepted: false });
13140
+ 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' });
13006
13142
  await stream.write({ type: 'invoke.done', version: 1, requestId: request.requestId, usage: { inputBytes: 0, outputBytes: 0 } });
13007
13143
  stream.close();
13008
13144
  return;
13009
13145
  }
13146
+ dlog('system_metrics: auth OK');
13010
13147
  }
13011
- await stream.write({ type: 'invoke.accept', version: 1, requestId: request.requestId, accepted: true });
13012
13148
  try {
13013
13149
  var os = require('os');
13014
13150
  var loads = os.loadavg();
@@ -13359,6 +13495,97 @@ process.on('unhandledRejection', (reason) => {
13359
13495
  dlog('NATS subscribe failed: ' + (natsSubErr.message || natsSubErr));
13360
13496
  }
13361
13497
 
13498
+ // NATS invoke relay \u2014 subscribe to nexus.invoke.<myPeerId> for cross-NAT invoke fallback.
13499
+ // When a remote peer can't direct-dial us (all multiaddrs are private), they retry
13500
+ // via NATS request/reply. We dispatch to the registered capability handler using a
13501
+ // mock stream interface, then reply with the result.
13502
+ try {
13503
+ var _nc = nexus.nats && nexus.nats.nc ? nexus.nats.nc : null;
13504
+ if (_nc) {
13505
+ var _nws = await import('nats.ws');
13506
+ _natsConn = _nc;
13507
+ _natsCodec = _nws.StringCodec();
13508
+ var _natsInvSubject = 'nexus.invoke.' + nexus.peerId;
13509
+ var _natsInvSub = _nc.subscribe(_natsInvSubject);
13510
+ dlog('NATS invoke relay listening on ' + _natsInvSubject);
13511
+
13512
+ (async function _natsInvokeLoop() {
13513
+ for await (var _nm of _natsInvSub) {
13514
+ try {
13515
+ var _nReq = JSON.parse(_natsCodec.decode(_nm.data));
13516
+ var _nCap = _nReq.capability || '';
13517
+ var _nInput = _nReq.input || {};
13518
+ var _nInputStr = typeof _nInput === 'string' ? _nInput : JSON.stringify(_nInput);
13519
+ dlog('NATS relay invoke: cap=' + _nCap + ' from=' + (_nReq.from || 'unknown').slice(0, 20));
13520
+
13521
+ // Look up registered handler
13522
+ var _nHandler = null;
13523
+ if (nexus.capabilityHandlers && typeof nexus.capabilityHandlers.get === 'function') {
13524
+ _nHandler = nexus.capabilityHandlers.get(_nCap);
13525
+ }
13526
+ if (!_nHandler) {
13527
+ var _nCaps = typeof nexus.getRegisteredCapabilities === 'function'
13528
+ ? nexus.getRegisteredCapabilities() : [];
13529
+ _nm.respond(_natsCodec.encode(JSON.stringify({
13530
+ error: 'Capability not found: ' + _nCap + '. Available: ' + _nCaps.join(', ')
13531
+ })));
13532
+ continue;
13533
+ }
13534
+
13535
+ // Create mock stream handle that captures the handler's output
13536
+ var _nResults = [];
13537
+ var _nHandleDone = false;
13538
+ var _nHandle = {
13539
+ write: async function(msg) {
13540
+ if (msg && msg.type === 'invoke.event') _nResults.push(msg);
13541
+ if (msg && msg.type === 'invoke.done') _nHandleDone = true;
13542
+ },
13543
+ onData: function(cb) {
13544
+ // Deliver input data asynchronously after handler registers its listener
13545
+ setTimeout(function() {
13546
+ try {
13547
+ cb({ type: 'invoke.chunk', data: _nInputStr, seq: 0 });
13548
+ cb({ type: 'invoke.done' });
13549
+ } catch {}
13550
+ }, 5);
13551
+ },
13552
+ close: function() {},
13553
+ };
13554
+
13555
+ var _nRequest = {
13556
+ type: 'invoke.open',
13557
+ requestId: 'nats-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8),
13558
+ capability: _nCap,
13559
+ from: _nReq.from || 'nats-peer',
13560
+ };
13561
+
13562
+ await _nHandler(_nRequest, _nHandle);
13563
+
13564
+ // Extract result from captured invoke.event messages
13565
+ var _nResult = null;
13566
+ if (_nResults.length === 1) {
13567
+ _nResult = _nResults[0].data;
13568
+ } else if (_nResults.length > 1) {
13569
+ _nResult = _nResults.map(function(r) { return r.data; });
13570
+ }
13571
+
13572
+ _nm.respond(_natsCodec.encode(JSON.stringify(_nResult !== null && _nResult !== undefined ? _nResult : { ok: true })));
13573
+ dlog('NATS relay invoke SUCCESS: ' + _nCap);
13574
+ } catch (_nErr) {
13575
+ dlog('NATS relay invoke error: ' + (_nErr.message || _nErr));
13576
+ try {
13577
+ _nm.respond(_natsCodec.encode(JSON.stringify({ error: String(_nErr.message || _nErr) })));
13578
+ } catch {}
13579
+ }
13580
+ }
13581
+ })().catch(function(e) { dlog('NATS invoke relay loop error: ' + (e.message || e)); });
13582
+ } else {
13583
+ dlog('NATS not connected \u2014 invoke relay disabled (cross-NAT invoke will not work)');
13584
+ }
13585
+ } catch (_nSetupErr) {
13586
+ dlog('NATS invoke relay setup failed: ' + (_nSetupErr.message || _nSetupErr));
13587
+ }
13588
+
13362
13589
  writeStatus();
13363
13590
  console.log('Nexus daemon connected as ' + nexus.peerId);
13364
13591
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.103.66",
3
+ "version": "0.103.68",
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",