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.
- package/dist/index.js +231 -3
- 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,
|
|
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.
|
|
4
|
-
"description": "AI coding agent powered by open-source models (Ollama/vLLM)
|
|
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",
|