open-agents-ai 0.186.13 → 0.186.15

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 +271 -5
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5343,7 +5343,7 @@ var init_nexus = __esm({
5343
5343
  * Usage: node nexus-daemon.mjs <nexus-dir> <agent-name> [agent-type]
5344
5344
  */
5345
5345
 
5346
- import { NexusClient } from 'open-agents-nexus';
5346
+ import { NexusClient, buildNknEnvelope, parseNknEnvelope, deriveNknSeed } from 'open-agents-nexus';
5347
5347
  import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync, readdirSync, appendFileSync, watch as fsWatch } from 'node:fs';
5348
5348
  import { join } from 'node:path';
5349
5349
  import { homedir, hostname } from 'node:os';
@@ -5639,12 +5639,33 @@ async function handleCmd(cmd) {
5639
5639
  writeResp(id, { ok: false, output: 'NATS not connected \u2014 cannot announce sponsorship' });
5640
5640
  return;
5641
5641
  }
5642
+ // NX-07: Fetch per-model metadata from Ollama for capacity announcements
5643
+ var _saModels = args.models || [];
5644
+ var _saModelDetails = [];
5645
+ try {
5646
+ var _saOllamaUrl = process.env.OLLAMA_HOST || 'http://localhost:11434';
5647
+ var _saTagsResp = await fetch(_saOllamaUrl + '/api/tags');
5648
+ var _saTags = await _saTagsResp.json();
5649
+ for (var _saM of (_saTags.models || [])) {
5650
+ var _saName = _saM.name || '';
5651
+ if (_saModels.length > 0 && !_saModels.includes(_saName)) continue;
5652
+ _saModelDetails.push({
5653
+ name: _saName,
5654
+ sizeGB: Math.round((_saM.size || 0) / (1024*1024*1024) * 10) / 10,
5655
+ paramCount: _saM.details?.parameter_size || '',
5656
+ quantization: _saM.details?.quantization_level || '',
5657
+ family: _saM.details?.family || '',
5658
+ contextLength: 0, // would need /api/show per model \u2014 too slow for announce
5659
+ });
5660
+ }
5661
+ } catch {}
5642
5662
  var sponsorData = {
5643
5663
  type: 'sponsor.announce',
5644
5664
  peerId: globalThis._daemonPeerId || 'unknown',
5645
5665
  libp2pPeerId: globalThis._daemonPeerId || '',
5646
5666
  name: args.name || 'Anonymous Sponsor',
5647
- models: args.models || [],
5667
+ models: _saModels.length > 0 ? _saModels : _saModelDetails.map(function(m) { return m.name; }),
5668
+ modelDetails: _saModelDetails, // NX-07: per-model capacity
5648
5669
  tunnelUrl: args.tunnel_url || null,
5649
5670
  authKey: args.auth_key || '',
5650
5671
  limits: {
@@ -5690,9 +5711,101 @@ async function handleCmd(cmd) {
5690
5711
  await rooms.get('sponsors').send(JSON.stringify(sponsorData), { format: 'application/json' });
5691
5712
  } catch {}
5692
5713
  }
5693
- writeResp(id, { ok: true, output: 'Sponsor announced: ' + sponsorData.name + ' (' + sponsorData.models.length + ' models)' });
5714
+ // NX-04: GossipSub meta topic announcement (multi-layer discovery)
5715
+ try {
5716
+ var _node = nexus.network ? nexus.network.node : nexus.node;
5717
+ if (_node && _node.services && _node.services.pubsub) {
5718
+ var _metaTopic = '/nexus/meta';
5719
+ var _metaMsg = JSON.stringify({
5720
+ version: 1,
5721
+ type: 'capability',
5722
+ id: Date.now().toString(36) + Math.random().toString(36).slice(2, 6),
5723
+ timestamp: Date.now(),
5724
+ sender: nexus.peerId || 'unknown',
5725
+ topic: _metaTopic,
5726
+ payload: {
5727
+ capabilities: [{
5728
+ name: 'sponsor_inference',
5729
+ protocol: '/nexus/invoke/1.1.0',
5730
+ description: 'Sponsored inference via ' + sponsorData.name,
5731
+ pricing: 'free',
5732
+ rateLimit: String(sponsorData.limits.maxRequestsPerMinute) + '/min',
5733
+ }],
5734
+ sponsor: sponsorData,
5735
+ },
5736
+ });
5737
+ try { await _node.services.pubsub.subscribe(_metaTopic); } catch {}
5738
+ await _node.services.pubsub.publish(_metaTopic, new TextEncoder().encode(_metaMsg));
5739
+ dlog('sponsor_announce: published to GossipSub /nexus/meta');
5740
+ }
5741
+ } catch (gsErr) {
5742
+ dlog('sponsor_announce: GossipSub meta failed: ' + (gsErr.message || gsErr));
5743
+ }
5744
+ writeResp(id, { ok: true, output: 'Sponsor announced: ' + sponsorData.name + ' (' + sponsorData.models.length + ' models) [NATS+KV+Room+GossipSub]' });
5745
+ break;
5746
+ }
5747
+ // NX-05 + NX-06: Register sponsor_inference capability for P2P inference relay.
5748
+ // When a sponsor activates, this registers an invoke handler that proxies
5749
+ // chat completions to local Ollama \u2014 no tunnel needed for P2P inference.
5750
+ case 'register_sponsor_inference': {
5751
+ var _rsiOllamaUrl = args.ollama_url || process.env.OLLAMA_HOST || 'http://localhost:11434';
5752
+ var _rsiAllowedModels = args.models ? String(args.models).split(',') : null;
5753
+ if (typeof nexus.registerCapability !== 'function') {
5754
+ writeResp(id, { ok: false, output: 'registerCapability not available' });
5755
+ break;
5756
+ }
5757
+ nexus.registerCapability('sponsor_inference', async (request, stream) => {
5758
+ var _rsiInput = '';
5759
+ stream.onData(function(msg) {
5760
+ if (msg.type === 'invoke.chunk') {
5761
+ _rsiInput += (typeof msg.data === 'string' ? msg.data : JSON.stringify(msg.data));
5762
+ }
5763
+ });
5764
+ // Wait for input to finish (accept fires when sender signals done)
5765
+ await stream.accept();
5766
+ try {
5767
+ var _rsiReq = JSON.parse(_rsiInput);
5768
+ var _rsiModel = _rsiReq.model || '';
5769
+ // Check model allowlist
5770
+ if (_rsiAllowedModels && !_rsiAllowedModels.includes(_rsiModel)) {
5771
+ stream.write({ type: 'invoke.error', error: 'Model not allowed: ' + _rsiModel });
5772
+ return;
5773
+ }
5774
+ // Proxy to local Ollama /v1/chat/completions
5775
+ var _rsiBody = JSON.stringify({
5776
+ model: _rsiModel,
5777
+ messages: _rsiReq.messages || [],
5778
+ stream: true,
5779
+ });
5780
+ var _rsiResp = await fetch(_rsiOllamaUrl + '/v1/chat/completions', {
5781
+ method: 'POST',
5782
+ headers: { 'Content-Type': 'application/json' },
5783
+ body: _rsiBody,
5784
+ });
5785
+ if (!_rsiResp.ok) {
5786
+ stream.write({ type: 'invoke.error', error: 'Ollama returned HTTP ' + _rsiResp.status });
5787
+ return;
5788
+ }
5789
+ // Stream response back via invoke protocol
5790
+ var _rsiReader = _rsiResp.body?.getReader();
5791
+ if (_rsiReader) {
5792
+ var _rsiDecoder = new TextDecoder();
5793
+ while (true) {
5794
+ var _rsiChunk = await _rsiReader.read();
5795
+ if (_rsiChunk.done) break;
5796
+ stream.write({ type: 'invoke.event', data: _rsiDecoder.decode(_rsiChunk.value, { stream: true }) });
5797
+ }
5798
+ }
5799
+ stream.write({ type: 'invoke.done' });
5800
+ dlog('sponsor_inference: served ' + _rsiModel + ' to ' + (request.from || '?').slice(0, 16));
5801
+ } catch (rsiErr) {
5802
+ stream.write({ type: 'invoke.error', error: String(rsiErr.message || rsiErr) });
5803
+ }
5804
+ });
5805
+ writeResp(id, { ok: true, output: 'Registered sponsor_inference capability (P2P relay active)' });
5694
5806
  break;
5695
5807
  }
5808
+
5696
5809
  case 'sponsor_discover': {
5697
5810
  var foundSponsors = [];
5698
5811
  var discoverTimeout = parseInt(args.timeout_ms || '5000', 10);
@@ -7270,6 +7383,153 @@ async function handleCmd(cmd) {
7270
7383
  writeResp(id, { ok: true, output: 'pong' });
7271
7384
  break;
7272
7385
  }
7386
+
7387
+ // \u2500\u2500 NKN Backup Comms Layer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7388
+ // Persistent NKN addresses for fallback agent-to-agent communication.
7389
+ // Each OA instance gets a deterministic NKN address derived from the
7390
+ // mnemonic agent name. Messages are signed with HMAC to verify the
7391
+ // AGENT sent them (not a human spoofing).
7392
+
7393
+ case 'nkn_status': {
7394
+ var _nknIdFile = join(nexusDir, 'nkn-identity.json');
7395
+ var _nknPeersFile = join(nexusDir, 'nkn-peers.json');
7396
+ var _nknId = existsSync(_nknIdFile) ? JSON.parse(readFileSync(_nknIdFile, 'utf8')) : null;
7397
+ var _nknPeers = existsSync(_nknPeersFile) ? JSON.parse(readFileSync(_nknPeersFile, 'utf8')) : { peers: [] };
7398
+ var _nknConnected = nexus.nkn && nexus.nkn.isConnected;
7399
+ writeResp(id, { ok: true, output: JSON.stringify({
7400
+ enabled: !!nexus.nkn,
7401
+ connected: _nknConnected,
7402
+ address: _nknId ? _nknId.address : (nexus.nkn ? nexus.nkn.address : null),
7403
+ publicKey: _nknId ? _nknId.publicKey : null,
7404
+ peerCount: _nknPeers.peers ? _nknPeers.peers.length : 0,
7405
+ libp2pPeerId: nexus.peerId || null,
7406
+ agentName: agentName,
7407
+ }) });
7408
+ break;
7409
+ }
7410
+
7411
+ case 'nkn_peers': {
7412
+ // List all known NKN peer addresses (agents we've discovered)
7413
+ var _npFile = join(nexusDir, 'nkn-peers.json');
7414
+ var _npData = existsSync(_npFile) ? JSON.parse(readFileSync(_npFile, 'utf8')) : { peers: [] };
7415
+ writeResp(id, { ok: true, output: JSON.stringify(_npData) });
7416
+ break;
7417
+ }
7418
+
7419
+ case 'nkn_inbox': {
7420
+ // Read NKN inbox messages (most recent first, capped)
7421
+ var _niDir = join(inboxDir, '_nkn');
7422
+ var _niMessages = [];
7423
+ try {
7424
+ if (existsSync(_niDir)) {
7425
+ var _niFiles = readdirSync(_niDir).filter(f => f.endsWith('.json')).sort().reverse().slice(0, 50);
7426
+ for (var _niF of _niFiles) {
7427
+ try { _niMessages.push(JSON.parse(readFileSync(join(_niDir, _niF), 'utf8'))); } catch {}
7428
+ }
7429
+ }
7430
+ } catch {}
7431
+ writeResp(id, { ok: true, output: JSON.stringify({ messages: _niMessages, count: _niMessages.length }) });
7432
+ break;
7433
+ }
7434
+
7435
+ case 'nkn_send': {
7436
+ // Send a DM to another agent via NKN
7437
+ // args: { to: "nkn-address", message: "text", verify: true }
7438
+ var _nsTo = args.to || args.address;
7439
+ var _nsMsg = args.message || args.content || '';
7440
+ if (!_nsTo || !_nsMsg) {
7441
+ writeResp(id, { ok: false, output: 'Required: to (NKN address) and message' });
7442
+ break;
7443
+ }
7444
+ // Lazy NKN init \u2014 uses open-agents-nexus API (deriveNknSeed, NknFallback)
7445
+ if (!nexus.nkn || !nexus.nkn.isConnected) {
7446
+ try {
7447
+ var _nsSeedFile = join(nexusDir, 'nkn-seed.hex');
7448
+ var _nsSeed = undefined;
7449
+ if (existsSync(_nsSeedFile)) {
7450
+ _nsSeed = readFileSync(_nsSeedFile, 'utf8').trim();
7451
+ } else {
7452
+ // Use open-agents-nexus deriveNknSeed() for deterministic address
7453
+ var _nsKeyData = existsSync(keyPath) ? readFileSync(keyPath) : Buffer.from(agentName);
7454
+ _nsSeed = deriveNknSeed(_nsKeyData);
7455
+ writeFileSync(_nsSeedFile, _nsSeed, { mode: 0o600 });
7456
+ }
7457
+ if (nexus.nkn && typeof nexus.nkn.connect === 'function') {
7458
+ await nexus.nkn.connect(_nsSeed);
7459
+ } else {
7460
+ // Direct NKN init via nkn-sdk
7461
+ var nknSdk = await import('nkn-sdk');
7462
+ var _nknMC = new nknSdk.default.MultiClient({ seed: _nsSeed, identifier: 'oa-' + agentName.replace(/[^a-zA-Z0-9_-]/g, '').slice(0, 20), numSubClients: 3 });
7463
+ await new Promise(function(resolve, reject) {
7464
+ _nknMC.onConnect(resolve);
7465
+ setTimeout(function() { reject(new Error('NKN connect timeout (15s)')); }, 15000);
7466
+ });
7467
+ var _nsIdFile = join(nexusDir, 'nkn-identity.json');
7468
+ writeFileSync(_nsIdFile, JSON.stringify({
7469
+ address: _nknMC.addr,
7470
+ publicKey: _nknMC.getPublicKey(),
7471
+ agentName: agentName,
7472
+ libp2pPeerId: nexus.peerId || null,
7473
+ createdAt: new Date().toISOString(),
7474
+ }, null, 2), { mode: 0o600 });
7475
+ dlog('NKN connected: ' + _nknMC.addr);
7476
+ // Receive handler \u2014 uses open-agents-nexus parseNknEnvelope() for HMAC verification
7477
+ _nknMC.onMessage(function({ src, payload }) {
7478
+ var _body = typeof payload === 'string' ? payload : new TextDecoder().decode(payload);
7479
+ var _niDir2 = join(inboxDir, '_nkn');
7480
+ mkdirSync(_niDir2, { recursive: true });
7481
+ var _entry = parseNknEnvelope(src, _nknMC.addr, _body);
7482
+ try { writeFileSync(join(_niDir2, Date.now() + '.json'), JSON.stringify(_entry, null, 2)); } catch {}
7483
+ });
7484
+ nexus._nknDirect = _nknMC;
7485
+ }
7486
+ } catch (nknErr) {
7487
+ writeResp(id, { ok: false, output: 'NKN connect failed: ' + (nknErr.message || nknErr) });
7488
+ break;
7489
+ }
7490
+ }
7491
+ // Build verified message using open-agents-nexus buildNknEnvelope()
7492
+ var _nsClient = nexus._nknDirect || (nexus.nkn && nexus.nkn.client);
7493
+ if (!_nsClient) { writeResp(id, { ok: false, output: 'NKN not connected' }); break; }
7494
+ var _nsMyAddr = _nsClient.addr || (nexus.nkn ? nexus.nkn.address : '');
7495
+ var _nsEnvelope = buildNknEnvelope(_nsMyAddr, _nsTo, agentName, _nsMsg, nexus.peerId || undefined);
7496
+ try {
7497
+ await _nsClient.send(_nsTo, _nsEnvelope, { msgHoldingSeconds: 3600 });
7498
+ // Add to peer list if not already there
7499
+ var _nsPeersFile = join(nexusDir, 'nkn-peers.json');
7500
+ var _nsPeers = existsSync(_nsPeersFile) ? JSON.parse(readFileSync(_nsPeersFile, 'utf8')) : { peers: [] };
7501
+ if (!_nsPeers.peers.find(function(p) { return p.nknAddress === _nsTo; })) {
7502
+ _nsPeers.peers.push({ nknAddress: _nsTo, lastContact: _nsTimestamp, agentName: args.to_agent || 'unknown' });
7503
+ writeFileSync(_nsPeersFile, JSON.stringify(_nsPeers, null, 2));
7504
+ }
7505
+ writeResp(id, { ok: true, output: 'Sent to ' + _nsTo + ' (HMAC verified, held 1h if offline)' });
7506
+ } catch (sendErr) {
7507
+ writeResp(id, { ok: false, output: 'NKN send failed: ' + (sendErr.message || sendErr) });
7508
+ }
7509
+ break;
7510
+ }
7511
+
7512
+ case 'nkn_add_peer': {
7513
+ // Manually add a peer's NKN address to the address book
7514
+ var _napAddr = args.nkn_address || args.address;
7515
+ var _napName = args.agent_name || args.name || 'unknown';
7516
+ var _napPeerId = args.libp2p_peer_id || args.peer_id || '';
7517
+ if (!_napAddr) { writeResp(id, { ok: false, output: 'Required: nkn_address' }); break; }
7518
+ var _napFile = join(nexusDir, 'nkn-peers.json');
7519
+ var _napData = existsSync(_napFile) ? JSON.parse(readFileSync(_napFile, 'utf8')) : { peers: [] };
7520
+ var _napExisting = _napData.peers.findIndex(function(p) { return p.nknAddress === _napAddr; });
7521
+ if (_napExisting >= 0) {
7522
+ _napData.peers[_napExisting].agentName = _napName;
7523
+ _napData.peers[_napExisting].libp2pPeerId = _napPeerId || _napData.peers[_napExisting].libp2pPeerId;
7524
+ _napData.peers[_napExisting].lastContact = Date.now();
7525
+ } else {
7526
+ _napData.peers.push({ nknAddress: _napAddr, agentName: _napName, libp2pPeerId: _napPeerId, addedAt: Date.now(), lastContact: Date.now() });
7527
+ }
7528
+ writeFileSync(_napFile, JSON.stringify(_napData, null, 2));
7529
+ writeResp(id, { ok: true, output: 'Added peer: ' + _napName + ' (' + _napAddr.slice(0, 30) + '...)' });
7530
+ break;
7531
+ }
7532
+
7273
7533
  default:
7274
7534
  writeResp(id, { ok: false, output: 'Unknown daemon command: ' + action });
7275
7535
  }
@@ -8218,9 +8478,15 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
8218
8478
  "ipfs_add",
8219
8479
  "ipfs_pin",
8220
8480
  "ipfs_ls",
8221
- "cohere_publish_insight"
8481
+ "cohere_publish_insight",
8482
+ "nkn_status",
8483
+ "nkn_peers",
8484
+ "nkn_inbox",
8485
+ "nkn_send",
8486
+ "nkn_add_peer",
8487
+ "register_sponsor_inference"
8222
8488
  ],
8223
- description: "The nexus action. MUST call 'connect' first (spawns daemon). Then: join_room, send_message, discover_peers, expose, status, cohere_stats, cohere_enable, cohere_disable, cohere_allow_model, cohere_deny_model, cohere_list_models, ipfs_add, etc."
8489
+ description: "The nexus action. MUST call 'connect' first (spawns daemon). Then: join_room, send_message, discover_peers, expose, status, nkn_status (NKN backup comms), nkn_peers (address book), nkn_inbox (read messages), nkn_send (DM via NKN with HMAC verification), nkn_add_peer (add to address book), cohere_stats, ipfs_add, etc."
8224
8490
  },
8225
8491
  room_id: {
8226
8492
  type: "string",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.186.13",
3
+ "version": "0.186.15",
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",