open-agents-ai 0.186.12 → 0.186.14

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 +231 -3
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -7270,6 +7270,179 @@ async function handleCmd(cmd) {
7270
7270
  writeResp(id, { ok: true, output: 'pong' });
7271
7271
  break;
7272
7272
  }
7273
+
7274
+ // \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
7275
+ // Persistent NKN addresses for fallback agent-to-agent communication.
7276
+ // Each OA instance gets a deterministic NKN address derived from the
7277
+ // mnemonic agent name. Messages are signed with HMAC to verify the
7278
+ // AGENT sent them (not a human spoofing).
7279
+
7280
+ case 'nkn_status': {
7281
+ var _nknIdFile = join(nexusDir, 'nkn-identity.json');
7282
+ var _nknPeersFile = join(nexusDir, 'nkn-peers.json');
7283
+ var _nknId = existsSync(_nknIdFile) ? JSON.parse(readFileSync(_nknIdFile, 'utf8')) : null;
7284
+ var _nknPeers = existsSync(_nknPeersFile) ? JSON.parse(readFileSync(_nknPeersFile, 'utf8')) : { peers: [] };
7285
+ var _nknConnected = nexus.nkn && nexus.nkn.isConnected;
7286
+ writeResp(id, { ok: true, output: JSON.stringify({
7287
+ enabled: !!nexus.nkn,
7288
+ connected: _nknConnected,
7289
+ address: _nknId ? _nknId.address : (nexus.nkn ? nexus.nkn.address : null),
7290
+ publicKey: _nknId ? _nknId.publicKey : null,
7291
+ peerCount: _nknPeers.peers ? _nknPeers.peers.length : 0,
7292
+ libp2pPeerId: nexus.peerId || null,
7293
+ agentName: agentName,
7294
+ }) });
7295
+ break;
7296
+ }
7297
+
7298
+ case 'nkn_peers': {
7299
+ // List all known NKN peer addresses (agents we've discovered)
7300
+ var _npFile = join(nexusDir, 'nkn-peers.json');
7301
+ var _npData = existsSync(_npFile) ? JSON.parse(readFileSync(_npFile, 'utf8')) : { peers: [] };
7302
+ writeResp(id, { ok: true, output: JSON.stringify(_npData) });
7303
+ break;
7304
+ }
7305
+
7306
+ case 'nkn_inbox': {
7307
+ // Read NKN inbox messages (most recent first, capped)
7308
+ var _niDir = join(inboxDir, '_nkn');
7309
+ var _niMessages = [];
7310
+ try {
7311
+ if (existsSync(_niDir)) {
7312
+ var _niFiles = readdirSync(_niDir).filter(f => f.endsWith('.json')).sort().reverse().slice(0, 50);
7313
+ for (var _niF of _niFiles) {
7314
+ try { _niMessages.push(JSON.parse(readFileSync(join(_niDir, _niF), 'utf8'))); } catch {}
7315
+ }
7316
+ }
7317
+ } catch {}
7318
+ writeResp(id, { ok: true, output: JSON.stringify({ messages: _niMessages, count: _niMessages.length }) });
7319
+ break;
7320
+ }
7321
+
7322
+ case 'nkn_send': {
7323
+ // Send a DM to another agent via NKN
7324
+ // args: { to: "nkn-address", message: "text", verify: true }
7325
+ var _nsTo = args.to || args.address;
7326
+ var _nsMsg = args.message || args.content || '';
7327
+ if (!_nsTo || !_nsMsg) {
7328
+ writeResp(id, { ok: false, output: 'Required: to (NKN address) and message' });
7329
+ break;
7330
+ }
7331
+ // Lazy NKN init
7332
+ if (!nexus.nkn || !nexus.nkn.isConnected) {
7333
+ try {
7334
+ // Load or generate NKN seed from global identity
7335
+ var _nsSeedFile = join(nexusDir, 'nkn-seed.hex');
7336
+ var _nsSeed = undefined;
7337
+ if (existsSync(_nsSeedFile)) {
7338
+ _nsSeed = readFileSync(_nsSeedFile, 'utf8').trim();
7339
+ } else {
7340
+ // Derive NKN seed from agent name + identity key (deterministic)
7341
+ var _nsKeyData = existsSync(keyPath) ? readFileSync(keyPath) : Buffer.from(agentName);
7342
+ _nsSeed = createHash('sha256').update(_nsKeyData).update('nkn-seed-v1').digest('hex');
7343
+ writeFileSync(_nsSeedFile, _nsSeed, { mode: 0o600 });
7344
+ }
7345
+ // Connect NKN via nexus client if available
7346
+ if (nexus.nkn && typeof nexus.nkn.connect === 'function') {
7347
+ await nexus.nkn.connect(_nsSeed);
7348
+ } else {
7349
+ // Direct NKN init if nexus client doesn't expose nkn
7350
+ var nknSdk = await import('nkn-sdk');
7351
+ var _nknMC = new nknSdk.default.MultiClient({ seed: _nsSeed, identifier: 'oa-' + agentName.replace(/[^a-zA-Z0-9_-]/g, '').slice(0, 20), numSubClients: 3 });
7352
+ await new Promise(function(resolve, reject) {
7353
+ _nknMC.onConnect(resolve);
7354
+ setTimeout(function() { reject(new Error('NKN connect timeout (15s)')); }, 15000);
7355
+ });
7356
+ // Store address
7357
+ var _nsIdFile = join(nexusDir, 'nkn-identity.json');
7358
+ writeFileSync(_nsIdFile, JSON.stringify({
7359
+ address: _nknMC.addr,
7360
+ publicKey: _nknMC.getPublicKey(),
7361
+ agentName: agentName,
7362
+ libp2pPeerId: nexus.peerId || null,
7363
+ createdAt: new Date().toISOString(),
7364
+ }, null, 2), { mode: 0o600 });
7365
+ dlog('NKN connected: ' + _nknMC.addr);
7366
+ // Receive handler \u2014 store in inbox
7367
+ _nknMC.onMessage(function({ src, payload }) {
7368
+ var _body = typeof payload === 'string' ? payload : new TextDecoder().decode(payload);
7369
+ var _niDir2 = join(inboxDir, '_nkn');
7370
+ mkdirSync(_niDir2, { recursive: true });
7371
+ var _entry = { sender: src, raw: _body, receivedAt: Date.now(), verified: false };
7372
+ // Verify HMAC if present
7373
+ try {
7374
+ var _parsed = JSON.parse(_body);
7375
+ if (_parsed._hmac && _parsed._agentName && _parsed.content) {
7376
+ // Verify: HMAC(agentName + content + timestamp, shared_secret)
7377
+ var _sharedSecret = createHash('sha256').update(src).update(_nknMC.addr).update('oa-nkn-v1').digest('hex');
7378
+ var _expectedHmac = createHmac('sha256', _sharedSecret).update(_parsed._agentName + _parsed.content + String(_parsed.timestamp || 0)).digest('hex').slice(0, 16);
7379
+ _entry.verified = (_parsed._hmac === _expectedHmac);
7380
+ _entry.agentName = _parsed._agentName;
7381
+ _entry.content = _parsed.content;
7382
+ _entry.timestamp = _parsed.timestamp;
7383
+ }
7384
+ } catch { /* raw text message \u2014 not verified */ }
7385
+ try { writeFileSync(join(_niDir2, Date.now() + '.json'), JSON.stringify(_entry, null, 2)); } catch {}
7386
+ });
7387
+ // Stash the client for reuse
7388
+ nexus._nknDirect = _nknMC;
7389
+ }
7390
+ } catch (nknErr) {
7391
+ writeResp(id, { ok: false, output: 'NKN connect failed: ' + (nknErr.message || nknErr) });
7392
+ break;
7393
+ }
7394
+ }
7395
+ // Build verified message with HMAC
7396
+ var _nsClient = nexus._nknDirect || (nexus.nkn && nexus.nkn.client);
7397
+ if (!_nsClient) { writeResp(id, { ok: false, output: 'NKN not connected' }); break; }
7398
+ var _nsMyAddr = _nsClient.addr || (nexus.nkn ? nexus.nkn.address : '');
7399
+ var _nsTimestamp = Date.now();
7400
+ var _nsSharedSecret = createHash('sha256').update(_nsMyAddr).update(_nsTo).update('oa-nkn-v1').digest('hex');
7401
+ var _nsHmac = createHmac('sha256', _nsSharedSecret).update(agentName + _nsMsg + String(_nsTimestamp)).digest('hex').slice(0, 16);
7402
+ var _nsEnvelope = JSON.stringify({
7403
+ _agentName: agentName,
7404
+ content: _nsMsg,
7405
+ timestamp: _nsTimestamp,
7406
+ libp2pPeerId: nexus.peerId || null,
7407
+ _hmac: _nsHmac,
7408
+ });
7409
+ try {
7410
+ await _nsClient.send(_nsTo, _nsEnvelope, { msgHoldingSeconds: 3600 });
7411
+ // Add to peer list if not already there
7412
+ var _nsPeersFile = join(nexusDir, 'nkn-peers.json');
7413
+ var _nsPeers = existsSync(_nsPeersFile) ? JSON.parse(readFileSync(_nsPeersFile, 'utf8')) : { peers: [] };
7414
+ if (!_nsPeers.peers.find(function(p) { return p.nknAddress === _nsTo; })) {
7415
+ _nsPeers.peers.push({ nknAddress: _nsTo, lastContact: _nsTimestamp, agentName: args.to_agent || 'unknown' });
7416
+ writeFileSync(_nsPeersFile, JSON.stringify(_nsPeers, null, 2));
7417
+ }
7418
+ writeResp(id, { ok: true, output: 'Sent to ' + _nsTo + ' (HMAC verified, held 1h if offline)' });
7419
+ } catch (sendErr) {
7420
+ writeResp(id, { ok: false, output: 'NKN send failed: ' + (sendErr.message || sendErr) });
7421
+ }
7422
+ break;
7423
+ }
7424
+
7425
+ case 'nkn_add_peer': {
7426
+ // Manually add a peer's NKN address to the address book
7427
+ var _napAddr = args.nkn_address || args.address;
7428
+ var _napName = args.agent_name || args.name || 'unknown';
7429
+ var _napPeerId = args.libp2p_peer_id || args.peer_id || '';
7430
+ if (!_napAddr) { writeResp(id, { ok: false, output: 'Required: nkn_address' }); break; }
7431
+ var _napFile = join(nexusDir, 'nkn-peers.json');
7432
+ var _napData = existsSync(_napFile) ? JSON.parse(readFileSync(_napFile, 'utf8')) : { peers: [] };
7433
+ var _napExisting = _napData.peers.findIndex(function(p) { return p.nknAddress === _napAddr; });
7434
+ if (_napExisting >= 0) {
7435
+ _napData.peers[_napExisting].agentName = _napName;
7436
+ _napData.peers[_napExisting].libp2pPeerId = _napPeerId || _napData.peers[_napExisting].libp2pPeerId;
7437
+ _napData.peers[_napExisting].lastContact = Date.now();
7438
+ } else {
7439
+ _napData.peers.push({ nknAddress: _napAddr, agentName: _napName, libp2pPeerId: _napPeerId, addedAt: Date.now(), lastContact: Date.now() });
7440
+ }
7441
+ writeFileSync(_napFile, JSON.stringify(_napData, null, 2));
7442
+ writeResp(id, { ok: true, output: 'Added peer: ' + _napName + ' (' + _napAddr.slice(0, 30) + '...)' });
7443
+ break;
7444
+ }
7445
+
7273
7446
  default:
7274
7447
  writeResp(id, { ok: false, output: 'Unknown daemon command: ' + action });
7275
7448
  }
@@ -8218,9 +8391,14 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
8218
8391
  "ipfs_add",
8219
8392
  "ipfs_pin",
8220
8393
  "ipfs_ls",
8221
- "cohere_publish_insight"
8394
+ "cohere_publish_insight",
8395
+ "nkn_status",
8396
+ "nkn_peers",
8397
+ "nkn_inbox",
8398
+ "nkn_send",
8399
+ "nkn_add_peer"
8222
8400
  ],
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."
8401
+ 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
8402
  },
8225
8403
  room_id: {
8226
8404
  type: "string",
@@ -49013,6 +49191,41 @@ import * as nodeOs from "node:os";
49013
49191
  import { execSync as nodeExecSync } from "node:child_process";
49014
49192
  import { existsSync as existsSync44, readFileSync as readFileSync33, writeFileSync as writeFileSync21, mkdirSync as mkdirSync20, readdirSync as readdirSync13, statSync as statSync15, rmSync } from "node:fs";
49015
49193
  import { join as join60 } from "node:path";
49194
+ function startSponsorHeartbeat(payload, getExposeGateway) {
49195
+ stopSponsorHeartbeat();
49196
+ _lastRegisteredSponsorPayload = { ...payload };
49197
+ const HEARTBEAT_MS = 5 * 60 * 1e3;
49198
+ _sponsorHeartbeatTimer = setInterval(async () => {
49199
+ if (!_lastRegisteredSponsorPayload)
49200
+ return;
49201
+ try {
49202
+ const gw = getExposeGateway?.();
49203
+ if (gw && gw.tunnelUrl && gw.tunnelUrl !== _lastRegisteredSponsorPayload.tunnelUrl) {
49204
+ _lastRegisteredSponsorPayload.tunnelUrl = gw.tunnelUrl;
49205
+ _lastRegisteredSponsorPayload.status = "active";
49206
+ }
49207
+ } catch {
49208
+ }
49209
+ try {
49210
+ await fetch("https://openagents.nexus/api/v1/sponsors", {
49211
+ method: "POST",
49212
+ headers: { "Content-Type": "application/json" },
49213
+ body: JSON.stringify(_lastRegisteredSponsorPayload),
49214
+ signal: AbortSignal.timeout(1e4)
49215
+ });
49216
+ } catch {
49217
+ }
49218
+ }, HEARTBEAT_MS);
49219
+ if (_sponsorHeartbeatTimer.unref)
49220
+ _sponsorHeartbeatTimer.unref();
49221
+ }
49222
+ function stopSponsorHeartbeat() {
49223
+ if (_sponsorHeartbeatTimer) {
49224
+ clearInterval(_sponsorHeartbeatTimer);
49225
+ _sponsorHeartbeatTimer = null;
49226
+ }
49227
+ _lastRegisteredSponsorPayload = null;
49228
+ }
49016
49229
  function safeLog(text) {
49017
49230
  if (isNeovimActive()) {
49018
49231
  writeToNeovimOutput(text + "\n");
@@ -50773,6 +50986,7 @@ Clone a new voice: /voice clone <wav-file> [name]`);
50773
50986
  if (arg === "pause" && existingConfig?.status === "active") {
50774
50987
  existingConfig.status = "paused";
50775
50988
  saveSponsorConfig2(projectDir, existingConfig);
50989
+ stopSponsorHeartbeat();
50776
50990
  const pauseGw = ctx.getExposeGateway?.();
50777
50991
  if (pauseGw && "setSponsorLimits" in pauseGw) {
50778
50992
  pauseGw.setSponsorLimits({ maxRequestsPerMinute: 0, maxTokensPerDay: 0, maxConcurrent: 0, allowedModels: [] });
@@ -50784,6 +50998,7 @@ Clone a new voice: /voice clone <wav-file> [name]`);
50784
50998
  if (arg === "remove" && existingConfig) {
50785
50999
  existingConfig.status = "inactive";
50786
51000
  saveSponsorConfig2(projectDir, existingConfig);
51001
+ stopSponsorHeartbeat();
50787
51002
  if (ctx.isExposeActive?.()) {
50788
51003
  try {
50789
51004
  await ctx.exposeStop?.();
@@ -51000,6 +51215,8 @@ Clone a new voice: /voice clone <wav-file> [name]`);
51000
51215
  const kvResult = await kvResp.json();
51001
51216
  if (kvResult.persisted) {
51002
51217
  renderInfo("Registered in sponsor directory \u2014 consumers can discover you via /endpoint sponsor");
51218
+ startSponsorHeartbeat(sponsorPayload, ctx.getExposeGateway);
51219
+ renderInfo("Heartbeat active \u2014 re-registering every 5 min");
51003
51220
  } else {
51004
51221
  renderWarning(`Sponsor directory: ${kvResult.reason || "not persisted"}`);
51005
51222
  }
@@ -53026,6 +53243,15 @@ async function handleSponsoredEndpoint(ctx, local) {
53026
53243
  }
53027
53244
  sponsors.length = 0;
53028
53245
  sponsors.push(...verified);
53246
+ if (verified.length > 0) {
53247
+ try {
53248
+ const { mkdirSync: mkdirSync34, writeFileSync: writeFileSync32 } = __require("node:fs");
53249
+ mkdirSync34(sponsorDir2, { recursive: true });
53250
+ const cached = verified.map((s) => ({ ...s, lastVerified: Date.now() }));
53251
+ writeFileSync32(knownFile, JSON.stringify(cached, null, 2));
53252
+ } catch {
53253
+ }
53254
+ }
53029
53255
  }
53030
53256
  process.stdout.write("\n");
53031
53257
  if (sponsors.length === 0) {
@@ -54222,7 +54448,7 @@ async function showExposeDashboard(gateway, rl, ctx) {
54222
54448
  renderInfo("Expose gateway stopped.");
54223
54449
  }
54224
54450
  }
54225
- var DASH_INTERNAL;
54451
+ var _sponsorHeartbeatTimer, _lastRegisteredSponsorPayload, DASH_INTERNAL;
54226
54452
  var init_commands = __esm({
54227
54453
  "packages/cli/dist/tui/commands.js"() {
54228
54454
  "use strict";
@@ -54242,6 +54468,8 @@ var init_commands = __esm({
54242
54468
  init_drop_panel();
54243
54469
  init_neovim_mode();
54244
54470
  init_daemon_registry();
54471
+ _sponsorHeartbeatTimer = null;
54472
+ _lastRegisteredSponsorPayload = null;
54245
54473
  DASH_INTERNAL = /* @__PURE__ */ new Set(["system_metrics", "__list_capabilities"]);
54246
54474
  }
54247
54475
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.186.12",
4
- "description": "AI coding agent powered by open-source models (Ollama/vLLM) \u2014 interactive TUI with agentic tool-calling loop",
3
+ "version": "0.186.14",
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",
7
7
  "types": "./dist/index.d.ts",