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.
- package/dist/index.js +271 -5
- 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:
|
|
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
|
-
|
|
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,
|
|
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