open-agents-ai 0.103.28 → 0.103.30

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 +237 -23
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -12206,7 +12206,43 @@ async function handleCmd(cmd) {
12206
12206
  const node = nexus.network?.node;
12207
12207
  const peers = node?.getPeers?.() || [];
12208
12208
  const list = peers.slice(0, 20).map(p => p.toString?.() || String(p));
12209
- writeResp(id, { ok: true, output: 'Peers: ' + list.length + '\\n' + list.map(p => ' ' + p.slice(0, 20) + '...').join('\\n') });
12209
+ // Include NATS-discovered peers with capabilities
12210
+ var dpNatsFile = join(nexusDir, 'discovered-peers.json');
12211
+ var dpNats = {};
12212
+ try { if (existsSync(dpNatsFile)) dpNats = JSON.parse(readFileSync(dpNatsFile, 'utf8')); } catch {}
12213
+ var dpNatsCount = Object.keys(dpNats).length;
12214
+ var dpLines = ['Connected peers: ' + list.length];
12215
+ for (var dpi = 0; dpi < list.length; dpi++) {
12216
+ dpLines.push(' ' + list[dpi]);
12217
+ }
12218
+ if (dpNatsCount > 0) {
12219
+ dpLines.push('NATS-discovered peers: ' + dpNatsCount);
12220
+ for (var dpKey of Object.keys(dpNats)) {
12221
+ var dpEntry = dpNats[dpKey];
12222
+ var dpAge = Math.round((Date.now() - dpEntry.lastSeen) / 1000);
12223
+ dpLines.push(' ' + dpKey.slice(0, 20) + '... (' + dpEntry.agentName + ') caps=' + (dpEntry.capabilities || []).length + ' seen=' + dpAge + 's ago');
12224
+ }
12225
+ }
12226
+ writeResp(id, { ok: true, output: dpLines.join('\\n') });
12227
+ break;
12228
+ }
12229
+ case 'discover_peer_caps': {
12230
+ var dpcPeerId = args.peer_id;
12231
+ if (!dpcPeerId) { writeResp(id, { ok: false, output: 'peer_id is required' }); return; }
12232
+ var dpcFile = join(nexusDir, 'discovered-peers.json');
12233
+ var dpcPeers = {};
12234
+ try { if (existsSync(dpcFile)) dpcPeers = JSON.parse(readFileSync(dpcFile, 'utf8')); } catch {}
12235
+ var dpcEntry = dpcPeers[String(dpcPeerId)];
12236
+ if (dpcEntry && dpcEntry.capabilities && dpcEntry.capabilities.length > 0) {
12237
+ var dpcAge = Date.now() - dpcEntry.lastSeen;
12238
+ if (dpcAge < 5 * 60 * 1000) { // 5 minute freshness
12239
+ writeResp(id, { ok: true, output: JSON.stringify(dpcEntry, null, 2) });
12240
+ } else {
12241
+ writeResp(id, { ok: false, output: 'Peer data stale (' + Math.round(dpcAge / 1000) + 's old). Wait for NATS re-announce.' });
12242
+ }
12243
+ } else {
12244
+ writeResp(id, { ok: false, output: 'Peer not found in NATS discovery cache. Peer may not have announced yet (announcements every 30s).' });
12245
+ }
12210
12246
  break;
12211
12247
  }
12212
12248
  case 'list_rooms': {
@@ -12224,8 +12260,34 @@ async function handleCmd(cmd) {
12224
12260
  case 'find_agent': {
12225
12261
  const peerId = args.peer_id;
12226
12262
  if (!peerId) { writeResp(id, { ok: false, output: 'peer_id is required' }); return; }
12227
- const profile = await nexus.findAgent(peerId);
12228
- writeResp(id, { ok: true, output: profile ? JSON.stringify(profile, null, 2) : 'Agent not found: ' + peerId });
12263
+ try {
12264
+ var FIND_TIMEOUT = 15000;
12265
+ var findPromise = nexus.findAgent(peerId);
12266
+ var findTimeoutPromise = new Promise(function(resolve) { setTimeout(function() { resolve(null); }, FIND_TIMEOUT); });
12267
+ var profile = await Promise.race([findPromise, findTimeoutPromise]);
12268
+ writeResp(id, { ok: true, output: profile ? JSON.stringify(profile, null, 2) : 'Agent not found: ' + peerId });
12269
+ } catch (findErr) {
12270
+ writeResp(id, { ok: false, output: 'find_agent error: ' + (findErr.message || String(findErr)) });
12271
+ }
12272
+ break;
12273
+ }
12274
+ case 'query_peer_caps': {
12275
+ var qpcPeerId = args.peer_id;
12276
+ if (!qpcPeerId) { writeResp(id, { ok: false, output: 'peer_id is required' }); return; }
12277
+ dlog('query_peer_caps: peer=' + String(qpcPeerId).slice(0, 20));
12278
+ try {
12279
+ var QPC_TIMEOUT = 15000;
12280
+ var qpcInput = {};
12281
+ if (args.auth_key) qpcInput.auth_key = args.auth_key;
12282
+ var qpcPromise = nexus.invokeCapability(String(qpcPeerId), '__list_capabilities', qpcInput, { stream: false, maxDurationMs: 10000 });
12283
+ var qpcTimeoutPromise = new Promise(function(_, reject) { setTimeout(function() { reject(new Error('query_peer_caps timed out after 15s')); }, QPC_TIMEOUT); });
12284
+ var qpcResult = await Promise.race([qpcPromise, qpcTimeoutPromise]);
12285
+ dlog('query_peer_caps: SUCCESS');
12286
+ writeResp(id, { ok: true, output: typeof qpcResult === 'string' ? qpcResult : JSON.stringify(qpcResult, null, 2) });
12287
+ } 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)) });
12290
+ }
12229
12291
  break;
12230
12292
  }
12231
12293
  case 'invoke_capability': {
@@ -12849,6 +12911,30 @@ async function handleCmd(cmd) {
12849
12911
  dlog('system_metrics capability registered');
12850
12912
  }
12851
12913
 
12914
+ // Register __list_capabilities \u2014 allows remote peers to discover available models
12915
+ if (typeof nexus.registerCapability === 'function') {
12916
+ nexus.registerCapability('__list_capabilities', async (request, stream) => {
12917
+ dlog('__list_capabilities invoked from ' + (request.from || 'unknown'));
12918
+ await stream.write({ type: 'invoke.accept', version: 1, requestId: request.requestId, accepted: true });
12919
+ var allCaps = typeof nexus.getRegisteredCapabilities === 'function' ? nexus.getRegisteredCapabilities() : [];
12920
+ var modelsInfo = [];
12921
+ for (var ci = 0; ci < pricingMenu.length; ci++) {
12922
+ var pm = pricingMenu[ci];
12923
+ modelsInfo.push({
12924
+ name: pm.model,
12925
+ parameterSize: pm.parameterSize || '',
12926
+ family: pm.family || '',
12927
+ quantization: pm.quantization || '',
12928
+ });
12929
+ }
12930
+ var capsPayload = JSON.stringify({ capabilities: allCaps, models: modelsInfo, agentName: agentName, peerId: nexus.peerId });
12931
+ await stream.write({ type: 'invoke.event', version: 1, requestId: request.requestId, seq: 0, event: 'result', data: capsPayload });
12932
+ await stream.write({ type: 'invoke.done', version: 1, requestId: request.requestId, usage: { inputBytes: 0, outputBytes: capsPayload.length } });
12933
+ stream.close();
12934
+ });
12935
+ dlog('__list_capabilities capability registered');
12936
+ }
12937
+
12852
12938
  // Write pricing menu to file
12853
12939
  const pricingFile = join(nexusDir, 'pricing.json');
12854
12940
  writeFileSync(pricingFile, JSON.stringify({ updated: new Date().toISOString(), models: pricingMenu }, null, 2));
@@ -13014,6 +13100,35 @@ process.on('unhandledRejection', (reason) => {
13014
13100
  }
13015
13101
  } catch {}
13016
13102
 
13103
+ // Subscribe to NATS peer announcements \u2014 cache remote peer capabilities
13104
+ var discoveredPeers = {};
13105
+ var discoveredPeersFile = join(nexusDir, 'discovered-peers.json');
13106
+ try {
13107
+ if (existsSync(discoveredPeersFile)) {
13108
+ discoveredPeers = JSON.parse(readFileSync(discoveredPeersFile, 'utf8'));
13109
+ }
13110
+ } catch {}
13111
+ try {
13112
+ if (nexus.nats && typeof nexus.nats.subscribe === 'function') {
13113
+ nexus.nats.subscribe(function(announcement) {
13114
+ if (!announcement || !announcement.peerId) return;
13115
+ if (announcement.peerId === nexus.peerId) return; // skip self
13116
+ discoveredPeers[announcement.peerId] = {
13117
+ peerId: announcement.peerId,
13118
+ agentName: announcement.agentName || '',
13119
+ capabilities: announcement.capabilities || [],
13120
+ multiaddrs: announcement.multiaddrs || [],
13121
+ lastSeen: Date.now(),
13122
+ };
13123
+ try { writeFileSync(discoveredPeersFile, JSON.stringify(discoveredPeers, null, 2)); } catch {}
13124
+ dlog('NATS peer discovered: ' + String(announcement.peerId).slice(0, 20) + ' caps=' + (announcement.capabilities || []).length);
13125
+ });
13126
+ dlog('NATS peer discovery subscription active');
13127
+ }
13128
+ } catch (natsSubErr) {
13129
+ dlog('NATS subscribe failed: ' + (natsSubErr.message || natsSubErr));
13130
+ }
13131
+
13017
13132
  writeStatus();
13018
13133
  console.log('Nexus daemon connected as ' + nexus.peerId);
13019
13134
  } catch (err) {
@@ -13215,6 +13330,9 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
13215
13330
  case "discover_peers":
13216
13331
  result = await this.sendDaemonCmd("discover_peers", args);
13217
13332
  break;
13333
+ case "discover_peer_caps":
13334
+ result = await this.sendDaemonCmd("discover_peer_caps", args, 5e3);
13335
+ break;
13218
13336
  case "list_rooms":
13219
13337
  result = await this.sendDaemonCmd("list_rooms", args);
13220
13338
  break;
@@ -13224,6 +13342,9 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
13224
13342
  case "find_agent":
13225
13343
  result = await this.sendDaemonCmd("find_agent", args, 3e4);
13226
13344
  break;
13345
+ case "query_peer_caps":
13346
+ result = await this.sendDaemonCmd("query_peer_caps", args, 2e4);
13347
+ break;
13227
13348
  case "invoke_capability":
13228
13349
  result = await this.sendDaemonCmd("invoke_capability", args, 1e5);
13229
13350
  break;
@@ -29845,26 +29966,102 @@ async function fetchPeerModels(peerId) {
29845
29966
  } catch {
29846
29967
  }
29847
29968
  }
29848
- const result = await nexusTool.execute({
29849
- action: "find_agent",
29850
- peer_id: peerId
29851
- });
29852
- if (result.success && result.output) {
29853
- const models = [];
29854
- const capMatches = result.output.matchAll(/inference:([^\s,\]]+)/g);
29855
- for (const m of capMatches) {
29856
- const capName = m[1];
29857
- const modelName = capName.replace(/_(\d+[bBmMkK])$/, ":$1").replace(/_latest$/, ":latest");
29858
- models.push({
29859
- name: modelName,
29860
- size: "",
29861
- modified: "",
29862
- sizeBytes: 0,
29863
- parameterSize: "remote"
29864
- });
29969
+ try {
29970
+ const capsResult = await nexusTool.execute({
29971
+ action: "query_peer_caps",
29972
+ peer_id: peerId
29973
+ });
29974
+ if (capsResult.success && capsResult.output) {
29975
+ let capsData = null;
29976
+ try {
29977
+ capsData = JSON.parse(capsResult.output);
29978
+ } catch {
29979
+ }
29980
+ if (capsData?.models && capsData.models.length > 0) {
29981
+ return capsData.models.map((m) => ({
29982
+ name: m.name || "unknown",
29983
+ size: m.parameterSize || "",
29984
+ modified: "",
29985
+ sizeBytes: 0,
29986
+ parameterSize: m.parameterSize || "remote"
29987
+ }));
29988
+ }
29989
+ if (capsData?.capabilities && capsData.capabilities.length > 0) {
29990
+ const models = [];
29991
+ for (const cap of capsData.capabilities) {
29992
+ if (typeof cap === "string" && cap.startsWith("inference:")) {
29993
+ const capName = cap.slice(10);
29994
+ const modelName = capName.replace(/_(\d+[bBmMkK])$/, ":$1").replace(/_latest$/, ":latest");
29995
+ models.push({
29996
+ name: modelName,
29997
+ size: "",
29998
+ modified: "",
29999
+ sizeBytes: 0,
30000
+ parameterSize: "remote"
30001
+ });
30002
+ }
30003
+ }
30004
+ if (models.length > 0)
30005
+ return models;
30006
+ }
29865
30007
  }
29866
- if (models.length > 0)
29867
- return models;
30008
+ } catch {
30009
+ }
30010
+ try {
30011
+ const natsResult = await nexusTool.execute({
30012
+ action: "discover_peer_caps",
30013
+ peer_id: peerId
30014
+ });
30015
+ if (natsResult.success && natsResult.output) {
30016
+ let natsPeer = null;
30017
+ try {
30018
+ natsPeer = JSON.parse(natsResult.output);
30019
+ } catch {
30020
+ }
30021
+ if (natsPeer?.capabilities && natsPeer.capabilities.length > 0) {
30022
+ const models = [];
30023
+ for (const cap of natsPeer.capabilities) {
30024
+ if (typeof cap === "string" && cap.startsWith("inference:")) {
30025
+ const capName = cap.slice(10);
30026
+ const modelName = capName.replace(/_(\d+[bBmMkK])$/, ":$1").replace(/_latest$/, ":latest");
30027
+ models.push({
30028
+ name: modelName,
30029
+ size: "",
30030
+ modified: "",
30031
+ sizeBytes: 0,
30032
+ parameterSize: "remote"
30033
+ });
30034
+ }
30035
+ }
30036
+ if (models.length > 0)
30037
+ return models;
30038
+ }
30039
+ }
30040
+ } catch {
30041
+ }
30042
+ try {
30043
+ const result = await nexusTool.execute({
30044
+ action: "find_agent",
30045
+ peer_id: peerId
30046
+ });
30047
+ if (result.success && result.output) {
30048
+ const models = [];
30049
+ const capMatches = result.output.matchAll(/inference:([^\s,\]]+)/g);
30050
+ for (const m of capMatches) {
30051
+ const capName = m[1];
30052
+ const modelName = capName.replace(/_(\d+[bBmMkK])$/, ":$1").replace(/_latest$/, ":latest");
30053
+ models.push({
30054
+ name: modelName,
30055
+ size: "",
30056
+ modified: "",
30057
+ sizeBytes: 0,
30058
+ parameterSize: "remote"
30059
+ });
30060
+ }
30061
+ if (models.length > 0)
30062
+ return models;
30063
+ }
30064
+ } catch {
29868
30065
  }
29869
30066
  if (isLocalPeer) {
29870
30067
  const pricingPath = join55(nexusDir, "pricing.json");
@@ -46476,11 +46673,24 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
46476
46673
  p2pGateway = null;
46477
46674
  }
46478
46675
  const nexusTool = new NexusTool(repoRoot);
46676
+ let exposeSpinnerMsg = "Connecting to nexus P2P network...";
46677
+ const exposeFrames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
46678
+ let exposeFrame = 0;
46679
+ const exposeSpinner = setInterval(() => {
46680
+ process.stdout.write(`\r ${c2.cyan(exposeFrames[exposeFrame % exposeFrames.length])} ${c2.dim(exposeSpinnerMsg)}`);
46681
+ exposeFrame++;
46682
+ }, 80);
46479
46683
  const newP2P = new ExposeP2PGateway({
46480
46684
  kind,
46481
46685
  targetUrl,
46482
46686
  authKey,
46483
- stateDir: join50(repoRoot, ".oa")
46687
+ stateDir: join50(repoRoot, ".oa"),
46688
+ onInfo: (msg) => {
46689
+ exposeSpinnerMsg = msg;
46690
+ },
46691
+ onError: (msg) => {
46692
+ exposeSpinnerMsg = msg;
46693
+ }
46484
46694
  }, nexusTool);
46485
46695
  newP2P.on("stats", (stats) => {
46486
46696
  statusBar.setExposeStatus({
@@ -46492,12 +46702,16 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
46492
46702
  });
46493
46703
  try {
46494
46704
  const peerId = await newP2P.start();
46705
+ clearInterval(exposeSpinner);
46706
+ process.stdout.write(`\r\x1B[2K`);
46495
46707
  p2pGateway = newP2P;
46496
46708
  writeContent(() => {
46497
46709
  process.stdout.write("\n" + newP2P.formatConnectionInfo() + "\n\n");
46498
46710
  });
46499
46711
  return peerId;
46500
46712
  } catch (err) {
46713
+ clearInterval(exposeSpinner);
46714
+ process.stdout.write(`\r\x1B[2K`);
46501
46715
  if (!transport) {
46502
46716
  writeContent(() => {
46503
46717
  renderWarning(`libp2p expose failed: ${err instanceof Error ? err.message : String(err)}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.103.28",
3
+ "version": "0.103.30",
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",