open-agents-ai 0.103.27 → 0.103.29

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 +267 -37
  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;
@@ -29809,10 +29930,10 @@ async function fetchPeerModels(peerId) {
29809
29930
  } catch {
29810
29931
  }
29811
29932
  if (isLocalPeer) {
29812
- const pricingPath2 = join55(nexusDir, "pricing.json");
29813
- if (existsSync42(pricingPath2)) {
29933
+ const pricingPath = join55(nexusDir, "pricing.json");
29934
+ if (existsSync42(pricingPath)) {
29814
29935
  try {
29815
- const pricing = JSON.parse(readFileSync31(pricingPath2, "utf8"));
29936
+ const pricing = JSON.parse(readFileSync31(pricingPath, "utf8"));
29816
29937
  const localModels = (pricing.models || []).map((m) => ({
29817
29938
  name: m.model || "unknown",
29818
29939
  size: m.parameterSize || "",
@@ -29826,41 +29947,138 @@ async function fetchPeerModels(peerId) {
29826
29947
  }
29827
29948
  }
29828
29949
  }
29829
- const result = await nexusTool.execute({
29830
- action: "find_agent",
29831
- peer_id: peerId
29832
- });
29833
- if (result.success && result.output) {
29834
- const models = [];
29835
- const capMatches = result.output.matchAll(/inference:([^\s,\]]+)/g);
29836
- for (const m of capMatches) {
29837
- const capName = m[1];
29838
- const modelName = capName.replace(/_(\d+[bBmMkK])$/, ":$1").replace(/_latest$/, ":latest");
29839
- models.push({
29840
- name: modelName,
29841
- size: "",
29842
- modified: "",
29843
- sizeBytes: 0,
29844
- parameterSize: "remote"
29845
- });
29846
- }
29847
- if (models.length > 0)
29848
- return models;
29849
- }
29850
- const pricingPath = join55(nexusDir, "pricing.json");
29851
- if (existsSync42(pricingPath)) {
29950
+ const cachePath = join55(nexusDir, "peer-models-cache.json");
29951
+ if (existsSync42(cachePath)) {
29852
29952
  try {
29853
- const pricing = JSON.parse(readFileSync31(pricingPath, "utf8"));
29854
- return (pricing.models || []).map((m) => ({
29855
- name: m.model || "unknown",
29856
- size: m.parameterSize || "",
29857
- modified: "",
29858
- sizeBytes: 0,
29859
- parameterSize: m.parameterSize || "remote"
29860
- }));
29953
+ const cache4 = JSON.parse(readFileSync31(cachePath, "utf8"));
29954
+ if (cache4.peerId === peerId && cache4.models?.length > 0) {
29955
+ const age = Date.now() - new Date(cache4.cachedAt).getTime();
29956
+ if (age < 5 * 60 * 1e3) {
29957
+ return cache4.models.map((m) => ({
29958
+ name: m.name || "unknown",
29959
+ size: m.size || m.parameterSize || "",
29960
+ modified: "",
29961
+ sizeBytes: 0,
29962
+ parameterSize: m.parameterSize || "remote"
29963
+ }));
29964
+ }
29965
+ }
29861
29966
  } catch {
29862
29967
  }
29863
29968
  }
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
+ }
30007
+ }
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 {
30065
+ }
30066
+ if (isLocalPeer) {
30067
+ const pricingPath = join55(nexusDir, "pricing.json");
30068
+ if (existsSync42(pricingPath)) {
30069
+ try {
30070
+ const pricing = JSON.parse(readFileSync31(pricingPath, "utf8"));
30071
+ return (pricing.models || []).map((m) => ({
30072
+ name: m.model || "unknown",
30073
+ size: m.parameterSize || "",
30074
+ modified: "",
30075
+ sizeBytes: 0,
30076
+ parameterSize: m.parameterSize || "remote"
30077
+ }));
30078
+ } catch {
30079
+ }
30080
+ }
30081
+ }
29864
30082
  return [];
29865
30083
  } catch {
29866
30084
  return [];
@@ -34273,6 +34491,18 @@ async function handlePeerEndpoint(peerId, authKey, ctx, local) {
34273
34491
  try {
34274
34492
  const models = await fetchModels(peerUrl, authKey);
34275
34493
  if (models.length > 0) {
34494
+ try {
34495
+ const { writeFileSync: writeFileSync19, mkdirSync: mkdirSync21 } = await import("node:fs");
34496
+ const { join: join55, dirname: dirname20 } = await import("node:path");
34497
+ const cachePath = join55(ctx.repoRoot || process.cwd(), ".oa", "nexus", "peer-models-cache.json");
34498
+ mkdirSync21(dirname20(cachePath), { recursive: true });
34499
+ writeFileSync19(cachePath, JSON.stringify({
34500
+ peerId,
34501
+ cachedAt: (/* @__PURE__ */ new Date()).toISOString(),
34502
+ models: models.map((m) => ({ name: m.name, size: m.size, parameterSize: m.parameterSize }))
34503
+ }, null, 2));
34504
+ } catch {
34505
+ }
34276
34506
  process.stdout.write(` ${c2.green("\u2714")} Discovered ${models.length} model(s) on peer:
34277
34507
  `);
34278
34508
  for (const m of models.slice(0, 10)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.103.27",
3
+ "version": "0.103.29",
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",