open-agents-ai 0.103.69 → 0.103.71

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 +240 -167
  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'
@@ -12284,43 +12304,34 @@ async function handleCmd(cmd) {
12284
12304
  var QPC_TIMEOUT = 15000;
12285
12305
  var qpcInput = {};
12286
12306
  if (args.auth_key) qpcInput.auth_key = args.auth_key;
12287
- var qpcPromise = nexus.invokeCapability(String(qpcPeerId), '__list_capabilities', qpcInput, { stream: false, maxDurationMs: 10000 });
12288
- var qpcTimeoutPromise = new Promise(function(_, reject) { setTimeout(function() { reject(new Error('query_peer_caps timed out after 15s')); }, QPC_TIMEOUT); });
12289
- var qpcResult = await Promise.race([qpcPromise, qpcTimeoutPromise]);
12307
+
12308
+ // Race libp2p + NATS in parallel \u2014 fastest route wins
12309
+ var qpcLibp2p = nexus.invokeCapability(String(qpcPeerId), '__list_capabilities', qpcInput, { stream: false, maxDurationMs: 10000 });
12310
+ var qpcTimeout = new Promise(function(_, reject) { setTimeout(function() { reject(new Error('query_peer_caps timed out')); }, QPC_TIMEOUT); });
12311
+ var qpcRace = [qpcLibp2p, qpcTimeout];
12312
+
12313
+ if (_natsConn && _natsCodec) {
12314
+ var _qpcNatsP = (async function() {
12315
+ var _s = 'nexus.invoke.' + String(qpcPeerId);
12316
+ var _p = JSON.stringify({ capability: '__list_capabilities', input: qpcInput, from: nexus.peerId || '' });
12317
+ var _r = await _natsConn.request(_s, _natsCodec.encode(_p), { timeout: 15000 });
12318
+ var _res = JSON.parse(_natsCodec.decode(_r.data));
12319
+ if (_res && _res.error) throw new Error('NATS: ' + _res.error);
12320
+ dlog('query_peer_caps: NATS won the race');
12321
+ return _res;
12322
+ })();
12323
+ qpcRace.push(_qpcNatsP);
12324
+ }
12325
+
12326
+ var qpcResult = await Promise.any(qpcRace.map(function(p) {
12327
+ return p.catch(function(e) { return Promise.reject(e); });
12328
+ }));
12290
12329
  dlog('query_peer_caps: SUCCESS');
12291
12330
  writeResp(id, { ok: true, output: typeof qpcResult === 'string' ? qpcResult : JSON.stringify(qpcResult, null, 2) });
12292
12331
  } catch (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
- }
12332
+ var qpcErrMsg = qpcErr.errors ? qpcErr.errors.map(function(e) { return e.message || String(e); }).join('; ') : (qpcErr.message || String(qpcErr));
12333
+ dlog('query_peer_caps: ALL paths failed: ' + qpcErrMsg);
12334
+ writeResp(id, { ok: false, output: 'query_peer_caps error: ' + qpcErrMsg });
12324
12335
  }
12325
12336
  break;
12326
12337
  }
@@ -12331,45 +12342,36 @@ async function handleCmd(cmd) {
12331
12342
  if (!peerId) { writeResp(id, { ok: false, output: 'target_peer is required' }); return; }
12332
12343
  dlog('invoke_capability: peer=' + peerId.slice(0, 20) + ' cap=' + capability);
12333
12344
  try {
12334
- // Wrap in a 300s timeout \u2014 remote API inference (Chutes, etc.) can take 60-180s for large models
12345
+ // Race libp2p + NATS in parallel \u2014 whichever route works first wins
12335
12346
  const INVOKE_TIMEOUT = 300_000;
12336
- const invokePromise = nexus.invokeCapability(peerId, capability, typeof input === 'string' ? { prompt: input } : input, { stream: false, maxDurationMs: 240000 });
12337
- const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('invoke_capability timed out after ' + (INVOKE_TIMEOUT / 1000) + 's (client-side timeout)')), INVOKE_TIMEOUT));
12338
- const result = await Promise.race([invokePromise, timeoutPromise]);
12347
+ const invokeInput = typeof input === 'string' ? { prompt: input } : input;
12348
+ const invokePromise = nexus.invokeCapability(peerId, capability, invokeInput, { stream: false, maxDurationMs: 240000 });
12349
+ const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('invoke_capability timed out after ' + (INVOKE_TIMEOUT / 1000) + 's')), INVOKE_TIMEOUT));
12350
+
12351
+ var icRaceCandidates = [invokePromise, timeoutPromise];
12352
+
12353
+ if (_natsConn && _natsCodec) {
12354
+ var _icNatsRaceP = (async function() {
12355
+ var _s = 'nexus.invoke.' + peerId;
12356
+ var _p = JSON.stringify({ capability: capability, input: invokeInput, from: nexus.peerId || '' });
12357
+ var _r = await _natsConn.request(_s, _natsCodec.encode(_p), { timeout: 240000 });
12358
+ var _res = JSON.parse(_natsCodec.decode(_r.data));
12359
+ if (_res && _res.error) throw new Error('NATS: ' + _res.error);
12360
+ dlog('invoke_capability: NATS won the race');
12361
+ return _res;
12362
+ })();
12363
+ icRaceCandidates.push(_icNatsRaceP);
12364
+ }
12365
+
12366
+ const result = await Promise.any(icRaceCandidates.map(function(p) {
12367
+ return p.catch(function(e) { return Promise.reject(e); });
12368
+ }));
12339
12369
  dlog('invoke_capability: SUCCESS');
12340
- writeResp(id, { ok: true, output: JSON.stringify(result, null, 2) });
12370
+ writeResp(id, { ok: true, output: typeof result === 'string' ? result : JSON.stringify(result, null, 2) });
12341
12371
  } catch (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
- }
12372
+ var invokeErrMsg = invokeErr.errors ? invokeErr.errors.map(function(e) { return e.message || String(e); }).join('; ') : (invokeErr.message || String(invokeErr));
12373
+ dlog('invoke_capability: ALL paths failed: ' + invokeErrMsg);
12374
+ writeResp(id, { ok: false, output: 'Invoke error: ' + invokeErrMsg });
12373
12375
  }
12374
12376
  break;
12375
12377
  }
@@ -12409,48 +12411,42 @@ async function handleCmd(cmd) {
12409
12411
 
12410
12412
  // If target_peer specified, invoke directly. Otherwise auto-discover.
12411
12413
  if (riTargetPeer) {
12412
- // Direct invoke on specified peer
12413
- dlog('remote_infer: invoking ' + riCapName + ' on explicit peer ' + riTargetPeer.slice(0, 20));
12414
+ // Race libp2p direct dial and NATS relay IN PARALLEL.
12415
+ // For cross-NAT peers, libp2p dial can take 10-60s to fail while NATS
12416
+ // succeeds in <1s. Racing them eliminates the wait.
12417
+ dlog('remote_infer: invoking ' + riCapName + ' on peer ' + riTargetPeer.slice(0, 20) + ' (libp2p' + (_natsConn ? ' + NATS parallel' : '') + ')');
12414
12418
  try {
12415
- var RI_TIMEOUT = 300000; // 5 min \u2014 remote APIs (Chutes, etc.) can be slow for large models
12419
+ var RI_TIMEOUT = 300000;
12416
12420
  var riInvokeP = nexus.invokeCapability(riTargetPeer, riCapName, riData, { stream: false, maxDurationMs: 240000 });
12417
12421
  var riTimeoutP = new Promise(function(_, reject) { setTimeout(function() { reject(new Error('remote_infer timed out after ' + (RI_TIMEOUT / 1000) + 's')); }, RI_TIMEOUT); });
12418
- var riResult = await Promise.race([riInvokeP, riTimeoutP]);
12419
- dlog('remote_infer: SUCCESS (explicit peer)');
12420
- } catch (riErr) {
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;
12422
+
12423
+ // Build race candidates \u2014 libp2p + timeout always present
12424
+ var riRaceCandidates = [riInvokeP, riTimeoutP];
12425
+
12426
+ // Add NATS as a parallel candidate if available
12427
+ if (_natsConn && _natsCodec) {
12428
+ var _riNatsRaceP = (async function() {
12429
+ var _rSubject = 'nexus.invoke.' + riTargetPeer;
12430
+ var _rPayload = JSON.stringify({ capability: riCapName, input: riData, from: nexus.peerId || '' });
12431
+ var _rResp = await _natsConn.request(_rSubject, _natsCodec.encode(_rPayload), { timeout: 240000 });
12432
+ var _rResult = JSON.parse(_natsCodec.decode(_rResp.data));
12433
+ if (_rResult && _rResult.error) throw new Error('NATS: ' + _rResult.error);
12434
+ dlog('remote_infer: NATS won the race');
12435
+ return _rResult;
12436
+ })();
12437
+ riRaceCandidates.push(_riNatsRaceP);
12453
12438
  }
12439
+
12440
+ var riResult = await Promise.any(riRaceCandidates.map(function(p) {
12441
+ return p.catch(function(e) { return Promise.reject(e); });
12442
+ }));
12443
+ dlog('remote_infer: SUCCESS (peer ' + riTargetPeer.slice(0, 20) + ')');
12444
+ } catch (riErr) {
12445
+ // Promise.any throws AggregateError when ALL candidates fail
12446
+ var riErrMsg = riErr.errors ? riErr.errors.map(function(e) { return e.message || String(e); }).join('; ') : (riErr.message || String(riErr));
12447
+ dlog('remote_infer: ALL paths failed: ' + riErrMsg);
12448
+ writeResp(id, { ok: false, output: 'Remote inference failed: ' + riErrMsg });
12449
+ return;
12454
12450
  }
12455
12451
  } else {
12456
12452
  // Auto-discover: try speculative invoke on each 12D3KooW peer
@@ -12488,6 +12484,36 @@ async function handleCmd(cmd) {
12488
12484
  }
12489
12485
  dlog('remote_infer: auto-discover took ' + (Date.now() - discStart) + 'ms');
12490
12486
 
12487
+ // If libp2p auto-discover failed, try NATS relay against discovered peers from cache
12488
+ if (!riResult && _natsConn && _natsCodec) {
12489
+ try {
12490
+ var _dpFile = join(nexusDir, 'discovered-peers.json');
12491
+ if (existsSync(_dpFile)) {
12492
+ var _dpData = JSON.parse(readFileSync(_dpFile, 'utf8'));
12493
+ var _dpPeers = Object.keys(_dpData).filter(function(p) { return p.startsWith('12D3KooW'); });
12494
+ dlog('remote_infer: trying NATS relay on ' + _dpPeers.length + ' cached peers');
12495
+ for (var _dpi = 0; _dpi < _dpPeers.length && !riResult; _dpi++) {
12496
+ var _dpPid = _dpPeers[_dpi];
12497
+ try {
12498
+ var _dpSubject = 'nexus.invoke.' + _dpPid;
12499
+ var _dpPayload = JSON.stringify({ capability: riCapName, input: riData, from: nexus.peerId || '' });
12500
+ var _dpResp = await _natsConn.request(_dpSubject, _natsCodec.encode(_dpPayload), { timeout: 180000 });
12501
+ var _dpResult = JSON.parse(_natsCodec.decode(_dpResp.data));
12502
+ if (_dpResult && !_dpResult.error) {
12503
+ riResult = _dpResult;
12504
+ riTargetPeer = _dpPid;
12505
+ dlog('remote_infer: SUCCESS via NATS relay peer ' + _dpPid.slice(0, 20));
12506
+ }
12507
+ } catch (_dpErr) {
12508
+ dlog('remote_infer: NATS peer ' + _dpPid.slice(0, 20) + ' failed: ' + (_dpErr.message || _dpErr));
12509
+ }
12510
+ }
12511
+ }
12512
+ } catch (_dpCacheErr) {
12513
+ dlog('remote_infer: NATS discovery cache error: ' + (_dpCacheErr.message || _dpCacheErr));
12514
+ }
12515
+ }
12516
+
12491
12517
  if (!riResult) {
12492
12518
  writeResp(id, { ok: false, output: 'No peer found with capability: ' + riCapName + '. Ensure a provider is running expose with model ' + riModel });
12493
12519
  return;
@@ -13100,6 +13126,12 @@ async function handleCmd(cmd) {
13100
13126
  // Register system_metrics capability \u2014 returns CPU/GPU/memory utilization
13101
13127
  if (typeof nexus.registerCapability === 'function') {
13102
13128
  nexus.registerCapability('system_metrics', async (request, stream) => {
13129
+ // Stream safety wrapper \u2014 prevents unguarded writes after consumer disconnects
13130
+ var smStreamClosed = false;
13131
+ async function smWrite(msg) {
13132
+ if (smStreamClosed) return;
13133
+ try { await stream.write(msg); } catch { smStreamClosed = true; }
13134
+ }
13103
13135
  // Collect input via stream data events (auth_key arrives in invoke.chunk, NOT invoke.open)
13104
13136
  var smDataChunks = [];
13105
13137
  var smInputDone = false;
@@ -13113,7 +13145,7 @@ async function handleCmd(cmd) {
13113
13145
  });
13114
13146
 
13115
13147
  // Accept invocation so consumer sends data
13116
- await stream.write({ type: 'invoke.accept', version: 1, requestId: request.requestId, accepted: true });
13148
+ await smWrite({ type: 'invoke.accept', version: 1, requestId: request.requestId, accepted: true });
13117
13149
 
13118
13150
  // Wait briefly for data (auth key arrives in chunk)
13119
13151
  var smWait = 0;
@@ -13138,9 +13170,9 @@ async function handleCmd(cmd) {
13138
13170
  }
13139
13171
  if (smAuthKey !== exposeAuthKey) {
13140
13172
  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();
13173
+ await smWrite({ type: 'invoke.event', version: 1, requestId: request.requestId, seq: 0, event: 'error', data: 'Unauthorized' });
13174
+ await smWrite({ type: 'invoke.done', version: 1, requestId: request.requestId, usage: { inputBytes: 0, outputBytes: 0 } });
13175
+ try { stream.close(); } catch {}
13144
13176
  return;
13145
13177
  }
13146
13178
  dlog('system_metrics: auth OK');
@@ -13175,13 +13207,13 @@ async function handleCmd(cmd) {
13175
13207
  gpu: gpuInfo,
13176
13208
  timestamp: new Date().toISOString(),
13177
13209
  };
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 } });
13210
+ await smWrite({ type: 'invoke.event', version: 1, requestId: request.requestId, seq: 0, event: 'result', data: JSON.stringify(metricsPayload) });
13211
+ await smWrite({ type: 'invoke.done', version: 1, requestId: request.requestId, usage: { inputBytes: 0, outputBytes: JSON.stringify(metricsPayload).length } });
13180
13212
  } 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 } });
13213
+ await smWrite({ type: 'invoke.event', version: 1, requestId: request.requestId, seq: 0, event: 'error', data: 'metrics error: ' + me.message });
13214
+ await smWrite({ type: 'invoke.done', version: 1, requestId: request.requestId, usage: { inputBytes: 0, outputBytes: 0 } });
13183
13215
  }
13184
- stream.close();
13216
+ try { stream.close(); } catch {}
13185
13217
  });
13186
13218
  dlog('system_metrics capability registered');
13187
13219
  }
@@ -13380,7 +13412,6 @@ process.on('unhandledRejection', (reason) => {
13380
13412
  // The nexus library fires this hook AFTER handler returns, so it has the correct
13381
13413
  // peerId from connection context. We stash token data per requestId so the hook
13382
13414
  // can merge them into one complete record written to metering.jsonl.
13383
- var _tokensByRequest = {};
13384
13415
  try {
13385
13416
  if (nexus.metering && typeof nexus.metering.addHook === 'function') {
13386
13417
  nexus.metering.addHook(function(record) {
@@ -13532,25 +13563,39 @@ process.on('unhandledRejection', (reason) => {
13532
13563
  continue;
13533
13564
  }
13534
13565
 
13535
- // Create mock stream handle that captures the handler's output
13566
+ // Create mock stream handle that captures the handler's output.
13567
+ // Uses retroactive delivery: if handler registers onData after data is
13568
+ // already queued, deliver immediately. Eliminates timing race.
13536
13569
  var _nResults = [];
13537
- var _nHandleDone = false;
13570
+ var _nDataCbs = [];
13571
+ var _nDataDelivered = false;
13538
13572
  var _nHandle = {
13539
13573
  write: async function(msg) {
13540
13574
  if (msg && msg.type === 'invoke.event') _nResults.push(msg);
13541
- if (msg && msg.type === 'invoke.done') _nHandleDone = true;
13542
13575
  },
13543
13576
  onData: function(cb) {
13544
- // Deliver input data asynchronously after handler registers its listener
13545
- setTimeout(function() {
13577
+ if (_nDataDelivered) {
13578
+ // Data already flushed \u2014 deliver retroactively
13546
13579
  try {
13547
13580
  cb({ type: 'invoke.chunk', data: _nInputStr, seq: 0 });
13548
13581
  cb({ type: 'invoke.done' });
13549
13582
  } catch {}
13550
- }, 5);
13583
+ } else {
13584
+ _nDataCbs.push(cb);
13585
+ }
13551
13586
  },
13552
13587
  close: function() {},
13553
13588
  };
13589
+ // Flush input data to any registered onData listeners after 50ms
13590
+ setTimeout(function() {
13591
+ _nDataDelivered = true;
13592
+ for (var _di = 0; _di < _nDataCbs.length; _di++) {
13593
+ try {
13594
+ _nDataCbs[_di]({ type: 'invoke.chunk', data: _nInputStr, seq: 0 });
13595
+ _nDataCbs[_di]({ type: 'invoke.done' });
13596
+ } catch {}
13597
+ }
13598
+ }, 50);
13554
13599
 
13555
13600
  var _nRequest = {
13556
13601
  type: 'invoke.open',
@@ -13803,7 +13848,7 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
13803
13848
  result = await this.sendDaemonCmd("query_peer_caps", args, 2e4);
13804
13849
  break;
13805
13850
  case "invoke_capability":
13806
- result = await this.sendDaemonCmd("invoke_capability", args, 1e5);
13851
+ result = await this.sendDaemonCmd("invoke_capability", args, 32e4);
13807
13852
  break;
13808
13853
  case "store_content":
13809
13854
  result = await this.sendDaemonCmd("store_content", args);
@@ -14899,7 +14944,7 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
14899
14944
  } else {
14900
14945
  daemonArgs.prompt = prompt;
14901
14946
  }
14902
- const result = await this.sendDaemonCmd("remote_infer", daemonArgs, 12e4);
14947
+ const result = await this.sendDaemonCmd("remote_infer", daemonArgs, 32e4);
14903
14948
  let peerUsed = args.target_peer || "auto-selected";
14904
14949
  try {
14905
14950
  const parsed = JSON.parse(result);
@@ -35530,12 +35575,6 @@ async function handleUpdate(subcommand, ctx) {
35530
35575
  process.stdout.write("\n");
35531
35576
  const checkSpinner = startInlineSpinner(`Checking for updates ${c2.dim(`(current: v${currentVersion})`)}`);
35532
35577
  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
35578
  const { exec: exec3, execSync: es2 } = await import("node:child_process");
35540
35579
  let needsSudo = false;
35541
35580
  try {
@@ -35548,21 +35587,6 @@ async function handleUpdate(subcommand, ctx) {
35548
35587
  }
35549
35588
  } catch {
35550
35589
  }
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
35590
  let installError = "";
35567
35591
  const runInstall2 = (cmd) => new Promise((resolve31) => {
35568
35592
  const child = exec3(cmd, { timeout: 18e4 }, (err, _stdout, stderr) => {
@@ -35572,35 +35596,84 @@ async function handleUpdate(subcommand, ctx) {
35572
35596
  });
35573
35597
  child.stdout?.resume();
35574
35598
  });
35575
- installOk = await runInstall2(installCmd);
35576
- if (!installOk && /ENOTEMPTY|errno -39/i.test(installError)) {
35577
- installSpinner.stop("Cleaning stale npm temp files...");
35599
+ if (needsSudo) {
35600
+ renderInfo("Global npm directory requires elevated permissions.");
35601
+ renderInfo("You'll be asked for your password once \u2014 it covers all update steps.");
35602
+ process.stdout.write("\n");
35578
35603
  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 });
35604
+ es2("sudo -v", { stdio: "inherit", timeout: 6e4 });
35583
35605
  } catch {
35606
+ renderWarning("Could not acquire sudo credentials. Try: sudo npm i -g open-agents-ai");
35607
+ return;
35584
35608
  }
35585
- const retrySpinner = startInlineSpinner("Retrying install");
35586
- installError = "";
35609
+ }
35610
+ const sudoPrefix = needsSudo ? "sudo " : "";
35611
+ let primaryUpdated = false;
35612
+ if (!info) {
35613
+ checkSpinner.stop(`Primary package up to date (v${currentVersion}).`);
35614
+ } else {
35615
+ checkSpinner.stop(`Update available: v${info.currentVersion} \u2192 v${c2.bold(c2.green(info.latestVersion))}`);
35616
+ const installSpinner = startInlineSpinner("Installing update");
35617
+ const installCmd = `${sudoPrefix}npm install -g open-agents-ai@latest --prefer-online`;
35618
+ let installOk = false;
35587
35619
  installOk = await runInstall2(installCmd);
35620
+ if (!installOk && /ENOTEMPTY|errno -39/i.test(installError)) {
35621
+ installSpinner.stop("Cleaning stale npm temp files...");
35622
+ try {
35623
+ const prefix = es2("npm prefix -g", { encoding: "utf8", timeout: 5e3 }).trim();
35624
+ const globalModules = prefix.endsWith("/lib") ? prefix + "/node_modules" : prefix + "/lib/node_modules";
35625
+ es2(`${sudoPrefix}find "${globalModules}" -maxdepth 1 -name ".open-agents-ai-*" -type d -exec rm -rf {} + 2>/dev/null || true`, { timeout: 15e3 });
35626
+ es2(`${sudoPrefix}rm -rf "${globalModules}/open-agents-ai" 2>/dev/null || true`, { timeout: 15e3 });
35627
+ } catch {
35628
+ }
35629
+ const retrySpinner = startInlineSpinner("Retrying install");
35630
+ installError = "";
35631
+ installOk = await runInstall2(installCmd);
35632
+ if (!installOk) {
35633
+ retrySpinner.stop("Retry failed.");
35634
+ } else {
35635
+ retrySpinner.stop(`Update installed (v${info.latestVersion}).`);
35636
+ }
35637
+ }
35588
35638
  if (!installOk) {
35589
- retrySpinner.stop("Retry failed.");
35590
- } else {
35591
- retrySpinner.stop(`Update installed (v${info.latestVersion}).`);
35639
+ installSpinner.stop("Update install failed.");
35640
+ const errPreview = installError.split("\n").filter((l) => l.trim()).slice(-3).join("\n ");
35641
+ if (errPreview)
35642
+ renderWarning(`Error:
35643
+ ${errPreview}`);
35644
+ renderWarning(`Try manually: ${sudoPrefix}npm i -g open-agents-ai`);
35645
+ return;
35592
35646
  }
35647
+ installSpinner.stop(`Update installed (v${info.latestVersion}).`);
35648
+ primaryUpdated = true;
35593
35649
  }
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`);
35650
+ const depsSpinner = startInlineSpinner("Checking subordinate dependencies");
35651
+ let depsUpdated = false;
35652
+ try {
35653
+ const prefix = es2("npm prefix -g", { encoding: "utf8", timeout: 5e3 }).trim();
35654
+ const globalModules = prefix.endsWith("/lib") ? prefix + "/node_modules" : prefix + "/lib/node_modules";
35655
+ const { existsSync: fe, readFileSync: rf } = await import("node:fs");
35656
+ const { join: pj } = await import("node:path");
35657
+ const pkgPath = pj(globalModules, "open-agents-ai", "package.json");
35658
+ if (fe(pkgPath)) {
35659
+ const pkg = JSON.parse(rf(pkgPath, "utf8"));
35660
+ const allDeps = { ...pkg.dependencies || {}, ...pkg.optionalDependencies || {} };
35661
+ const depNames = Object.keys(allDeps);
35662
+ if (depNames.length > 0) {
35663
+ const updateCmd = `${sudoPrefix}npm update --prefer-online --prefix "${pj(globalModules, "open-agents-ai")}" 2>/dev/null || true`;
35664
+ const updateOk = await runInstall2(updateCmd);
35665
+ if (updateOk)
35666
+ depsUpdated = true;
35667
+ }
35668
+ }
35669
+ } catch {
35670
+ }
35671
+ depsSpinner.stop(depsUpdated ? "Dependencies updated to latest." : "Dependencies checked.");
35672
+ if (!primaryUpdated && !depsUpdated) {
35673
+ renderInfo("Everything is up to date \u2014 no changes needed.");
35674
+ process.stdout.write("\n");
35601
35675
  return;
35602
35676
  }
35603
- installSpinner.stop(`Update installed (v${info.latestVersion}).`);
35604
35677
  const rebuildSpinner = startInlineSpinner("Rebuilding native modules");
35605
35678
  const rebuildOk = await new Promise((resolve31) => {
35606
35679
  const child = exec3(`${sudoPrefix}npm rebuild -g open-agents-ai 2>/dev/null || true`, { timeout: 12e4 }, () => resolve31(true));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.103.69",
3
+ "version": "0.103.71",
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",