open-agents-ai 0.83.0 → 0.84.0

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 CHANGED
@@ -11223,12 +11223,13 @@ var init_nexus = __esm({
11223
11223
  /**
11224
11224
  * nexus-daemon.mjs \u2014 Standalone nexus process with real TCP/UDP sockets.
11225
11225
  * Spawned by the open-agents nexus tool. Communicates via JSON files.
11226
+ * v1.5.0: capability registration, peer blocking, metering, room members.
11226
11227
  *
11227
11228
  * Usage: node nexus-daemon.mjs <nexus-dir> <agent-name> [agent-type]
11228
11229
  */
11229
11230
 
11230
- import { NexusClient } from 'open-agents-nexus';
11231
- import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync, readdirSync } from 'node:fs';
11231
+ import { NexusClient, createFileAuditHook } from 'open-agents-nexus';
11232
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync, readdirSync, appendFileSync } from 'node:fs';
11232
11233
  import { join } from 'node:path';
11233
11234
 
11234
11235
  const nexusDir = process.argv[2];
@@ -11239,8 +11240,11 @@ const respFile = join(nexusDir, 'resp.json');
11239
11240
  const statusFile = join(nexusDir, 'status.json');
11240
11241
  const inboxDir = join(nexusDir, 'inbox');
11241
11242
  const pidFile = join(nexusDir, 'daemon.pid');
11243
+ const invocationsDir = join(nexusDir, 'invocations');
11244
+ const meteringFile = join(nexusDir, 'metering.jsonl');
11242
11245
 
11243
11246
  mkdirSync(inboxDir, { recursive: true });
11247
+ mkdirSync(invocationsDir, { recursive: true });
11244
11248
 
11245
11249
  // Write PID so the agent can kill us
11246
11250
  writeFileSync(pidFile, String(process.pid));
@@ -11257,12 +11261,16 @@ const nexus = new NexusClient({
11257
11261
  natsServers: ['wss://demo.nats.io:8443'],
11258
11262
  enableCircuitRelay: true,
11259
11263
  usePublicBootstrap: true,
11264
+ trustPolicy: { denylist: [], allowlist: [] },
11260
11265
  });
11261
11266
 
11262
11267
  const rooms = new Map();
11263
11268
  let connected = false;
11269
+ const blockedPeers = [];
11264
11270
 
11265
11271
  function writeStatus(extra = {}) {
11272
+ const caps = typeof nexus.getRegisteredCapabilities === 'function'
11273
+ ? nexus.getRegisteredCapabilities() : [];
11266
11274
  const data = {
11267
11275
  pid: process.pid,
11268
11276
  connected,
@@ -11270,6 +11278,8 @@ function writeStatus(extra = {}) {
11270
11278
  agentName,
11271
11279
  agentType,
11272
11280
  rooms: [...rooms.keys()],
11281
+ capabilities: caps,
11282
+ blockedPeers,
11273
11283
  connectedAt: connected ? new Date().toISOString() : null,
11274
11284
  ...extra,
11275
11285
  };
@@ -11289,6 +11299,7 @@ async function handleCmd(cmd) {
11289
11299
  if (rooms.has(roomId)) { writeResp(id, { ok: true, output: 'Already in room: ' + roomId }); return; }
11290
11300
  const room = await nexus.joinRoom(roomId);
11291
11301
  rooms.set(roomId, room);
11302
+ // Per-room message listener (also captured by client-level event)
11292
11303
  room.on('message', (msg) => {
11293
11304
  const roomInbox = join(inboxDir, roomId);
11294
11305
  mkdirSync(roomInbox, { recursive: true });
@@ -11302,6 +11313,15 @@ async function handleCmd(cmd) {
11302
11313
  };
11303
11314
  try { writeFileSync(join(roomInbox, fname), JSON.stringify(entry, null, 2)); } catch {}
11304
11315
  });
11316
+ // v1.5.0: Room member tracking
11317
+ if (room.on) {
11318
+ room.on('member:join', (member) => {
11319
+ console.log('Member joined ' + roomId + ': ' + (member.agentName || member.peerId));
11320
+ });
11321
+ room.on('member:leave', (member) => {
11322
+ console.log('Member left ' + roomId + ': ' + (member.agentName || member.peerId));
11323
+ });
11324
+ }
11305
11325
  writeStatus();
11306
11326
  writeResp(id, { ok: true, output: 'Joined room: ' + roomId });
11307
11327
  break;
@@ -11372,6 +11392,165 @@ async function handleCmd(cmd) {
11372
11392
  writeResp(id, { ok: true, output: JSON.stringify(data, null, 2) });
11373
11393
  break;
11374
11394
  }
11395
+
11396
+ // \u2500\u2500 v1.5.0: Capability Registration \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
11397
+ case 'register_capability': {
11398
+ const name = args.capability;
11399
+ if (!name) { writeResp(id, { ok: false, output: 'capability name is required' }); return; }
11400
+ if (typeof nexus.registerCapability !== 'function') {
11401
+ writeResp(id, { ok: false, output: 'registerCapability not available (nexus version too old)' });
11402
+ return;
11403
+ }
11404
+ nexus.registerCapability(name, async (request, stream) => {
11405
+ // Log inbound invocation
11406
+ const logEntry = {
11407
+ ts: Date.now(),
11408
+ from: request.from || 'unknown',
11409
+ capability: request.capability || name,
11410
+ requestId: request.requestId,
11411
+ };
11412
+ const logFile = join(invocationsDir, Date.now() + '-' + name + '.json');
11413
+ try { writeFileSync(logFile, JSON.stringify(logEntry, null, 2)); } catch {}
11414
+
11415
+ // Collect input chunks
11416
+ let inputData = '';
11417
+ stream.onData((msg) => {
11418
+ if (msg.type === 'invoke.chunk') {
11419
+ inputData += (typeof msg.data === 'string' ? msg.data : JSON.stringify(msg.data));
11420
+ }
11421
+ });
11422
+
11423
+ // Accept the invocation
11424
+ await stream.write({
11425
+ type: 'invoke.accept', version: 1,
11426
+ requestId: request.requestId, accepted: true,
11427
+ });
11428
+
11429
+ // Send event with acknowledgment
11430
+ await stream.write({
11431
+ type: 'invoke.event', version: 1,
11432
+ requestId: request.requestId, seq: 0,
11433
+ event: 'ack', data: agentName + ' received invocation for ' + name,
11434
+ });
11435
+
11436
+ // Complete
11437
+ await stream.write({
11438
+ type: 'invoke.done', version: 1,
11439
+ requestId: request.requestId,
11440
+ usage: { inputBytes: inputData.length, outputBytes: 0 },
11441
+ });
11442
+ stream.close();
11443
+ });
11444
+ writeStatus();
11445
+ writeResp(id, { ok: true, output: 'Capability registered: ' + name + '. Now advertised via NATS.' });
11446
+ break;
11447
+ }
11448
+ case 'unregister_capability': {
11449
+ const name = args.capability;
11450
+ if (!name) { writeResp(id, { ok: false, output: 'capability name is required' }); return; }
11451
+ if (typeof nexus.unregisterCapability === 'function') {
11452
+ nexus.unregisterCapability(name);
11453
+ }
11454
+ writeStatus();
11455
+ writeResp(id, { ok: true, output: 'Capability unregistered: ' + name });
11456
+ break;
11457
+ }
11458
+ case 'list_capabilities': {
11459
+ const caps = typeof nexus.getRegisteredCapabilities === 'function'
11460
+ ? nexus.getRegisteredCapabilities() : [];
11461
+ writeResp(id, { ok: true, output: caps.length ? 'Capabilities: ' + caps.join(', ') : 'No capabilities registered.' });
11462
+ break;
11463
+ }
11464
+
11465
+ // \u2500\u2500 v1.5.0: Peer Blocking \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\u2500
11466
+ case 'block_peer': {
11467
+ const peerId = args.target_peer || args.peer_id;
11468
+ if (!peerId) { writeResp(id, { ok: false, output: 'peer_id or target_peer is required' }); return; }
11469
+ if (typeof nexus.blockPeer === 'function') {
11470
+ nexus.blockPeer(peerId);
11471
+ }
11472
+ if (!blockedPeers.includes(peerId)) blockedPeers.push(peerId);
11473
+ writeStatus();
11474
+ writeResp(id, { ok: true, output: 'Blocked peer: ' + peerId.slice(0, 20) + '...' });
11475
+ break;
11476
+ }
11477
+ case 'unblock_peer': {
11478
+ const peerId = args.target_peer || args.peer_id;
11479
+ if (!peerId) { writeResp(id, { ok: false, output: 'peer_id or target_peer is required' }); return; }
11480
+ if (typeof nexus.unblockPeer === 'function') {
11481
+ nexus.unblockPeer(peerId);
11482
+ }
11483
+ const idx = blockedPeers.indexOf(peerId);
11484
+ if (idx >= 0) blockedPeers.splice(idx, 1);
11485
+ writeStatus();
11486
+ writeResp(id, { ok: true, output: 'Unblocked peer: ' + peerId.slice(0, 20) + '...' });
11487
+ break;
11488
+ }
11489
+
11490
+ // \u2500\u2500 v1.5.0: Metering \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\u2500\u2500\u2500\u2500\u2500\u2500
11491
+ case 'metering_status': {
11492
+ if (!nexus.metering) {
11493
+ writeResp(id, { ok: true, output: 'Metering not available (nexus version too old).' });
11494
+ return;
11495
+ }
11496
+ const filter = {};
11497
+ if (args.peer_id) filter.peerId = args.peer_id;
11498
+ if (args.capability) filter.service = args.capability;
11499
+
11500
+ // Try per-peer summary first
11501
+ if (args.peer_id && typeof nexus.metering.getSummary === 'function') {
11502
+ const summary = nexus.metering.getSummary(args.peer_id);
11503
+ if (summary) {
11504
+ writeResp(id, { ok: true, output: JSON.stringify(summary, null, 2) });
11505
+ return;
11506
+ }
11507
+ }
11508
+
11509
+ // All summaries
11510
+ if (typeof nexus.metering.getAllSummaries === 'function') {
11511
+ const all = nexus.metering.getAllSummaries();
11512
+ if (all && (all.size > 0 || Object.keys(all).length > 0)) {
11513
+ const entries = all instanceof Map ? Object.fromEntries(all) : all;
11514
+ writeResp(id, { ok: true, output: 'Metering summaries:\\n' + JSON.stringify(entries, null, 2) });
11515
+ return;
11516
+ }
11517
+ }
11518
+
11519
+ // Fall back to raw records
11520
+ if (typeof nexus.metering.getRecords === 'function') {
11521
+ const records = nexus.metering.getRecords(filter);
11522
+ writeResp(id, { ok: true, output: 'Records: ' + records.length + '\\n' + JSON.stringify(records.slice(-10), null, 2) });
11523
+ return;
11524
+ }
11525
+
11526
+ writeResp(id, { ok: true, output: 'No metering data yet.' });
11527
+ break;
11528
+ }
11529
+
11530
+ // \u2500\u2500 v1.5.0: Room Members \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\u2500\u2500
11531
+ case 'room_members': {
11532
+ const roomId = args.room_id;
11533
+ if (!roomId) { writeResp(id, { ok: false, output: 'room_id is required' }); return; }
11534
+ const room = rooms.get(roomId);
11535
+ if (!room) { writeResp(id, { ok: false, output: 'Not in room: ' + roomId }); return; }
11536
+
11537
+ const members = room.members || [];
11538
+ if (members.length === 0) {
11539
+ writeResp(id, { ok: true, output: 'No members tracked in room: ' + roomId });
11540
+ return;
11541
+ }
11542
+ const lines = ['Members in ' + roomId + ' (' + members.length + '):'];
11543
+ for (const m of members) {
11544
+ const name = m.agentName || 'unknown';
11545
+ const type = m.agentType || '?';
11546
+ const caps = m.capabilities?.join(', ') || 'none';
11547
+ const status = m.status || 'active';
11548
+ lines.push(' ' + m.peerId.slice(0, 16) + '... ' + name + ' (' + type + ') [' + status + '] caps: ' + caps);
11549
+ }
11550
+ writeResp(id, { ok: true, output: lines.join('\\n') });
11551
+ break;
11552
+ }
11553
+
11375
11554
  case 'ping': {
11376
11555
  writeResp(id, { ok: true, output: 'pong' });
11377
11556
  break;
@@ -11418,6 +11597,41 @@ process.on('unhandledRejection', (reason) => {
11418
11597
  await node.start();
11419
11598
  }
11420
11599
  connected = true;
11600
+
11601
+ // v1.5.0: Setup metering audit hook
11602
+ try {
11603
+ if (nexus.metering && typeof nexus.metering.addHook === 'function') {
11604
+ nexus.metering.addHook(createFileAuditHook(meteringFile));
11605
+ }
11606
+ } catch {}
11607
+
11608
+ // v1.5.0: Client-level events for global message/DM/invoke routing
11609
+ try {
11610
+ if (typeof nexus.on === 'function') {
11611
+ nexus.on('message', ({ roomId, message }) => {
11612
+ // Already handled by per-room listener, but log globally
11613
+ console.log('[msg] ' + roomId + ' from ' + (message?.sender || '?').slice(0, 16));
11614
+ });
11615
+ nexus.on('dm', ({ from, content, format, messageId }) => {
11616
+ // Log DMs to inbox/dm/
11617
+ const dmDir = join(inboxDir, '_dm');
11618
+ mkdirSync(dmDir, { recursive: true });
11619
+ const entry = {
11620
+ sender: from,
11621
+ content: content || '',
11622
+ format: format || 'text/plain',
11623
+ timestamp: Date.now(),
11624
+ id: messageId,
11625
+ };
11626
+ try { writeFileSync(join(dmDir, Date.now() + '.json'), JSON.stringify(entry, null, 2)); } catch {}
11627
+ console.log('[dm] from ' + (from || '?').slice(0, 16));
11628
+ });
11629
+ nexus.on('invoke', ({ from, capability, requestId }) => {
11630
+ console.log('[invoke] ' + capability + ' from ' + (from || '?').slice(0, 16) + ' req=' + requestId);
11631
+ });
11632
+ }
11633
+ } catch {}
11634
+
11421
11635
  writeStatus();
11422
11636
  console.log('Nexus daemon connected as ' + nexus.peerId);
11423
11637
  } catch (err) {
@@ -11445,7 +11659,7 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
11445
11659
  ];
11446
11660
  NexusTool = class {
11447
11661
  name = "nexus";
11448
- description = "Decentralized agent-to-agent communication via open-agents-nexus v1.4.0. Spawns a background Node.js process with real network sockets for libp2p + NATS P2P mesh. Simple flow: connect \u2192 join room \u2192 send messages. Auto-installs/updates nexus if needed. Also supports direct peer invoke (streaming inference), IPFS content storage, direct messages, peer discovery, x402 micropayments, and inference proofs. Onboarding: curl openagents.nexus/llms.txt for full instructions, /.well-known/agent.json for machine-readable manifest.";
11662
+ description = "Decentralized agent-to-agent communication via open-agents-nexus v1.5.0. Spawns a background Node.js process with real network sockets for libp2p + NATS P2P mesh. Simple flow: connect \u2192 join room \u2192 send messages. Auto-installs/updates nexus if needed. v1.5.0: register_capability (serve invocations), block_peer/unblock_peer (trust policy), metering_status (usage tracking), room_members (live member tracking). Also supports direct peer invoke (streaming inference), IPFS content storage, direct messages, peer discovery, x402 micropayments, and inference proofs.";
11449
11663
  parameters = {
11450
11664
  type: "object",
11451
11665
  properties: {
@@ -11468,7 +11682,14 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
11468
11682
  "retrieve_content",
11469
11683
  "wallet_status",
11470
11684
  "wallet_create",
11471
- "inference_proof"
11685
+ "inference_proof",
11686
+ "register_capability",
11687
+ "unregister_capability",
11688
+ "list_capabilities",
11689
+ "block_peer",
11690
+ "unblock_peer",
11691
+ "metering_status",
11692
+ "room_members"
11472
11693
  ],
11473
11694
  description: "The nexus action to perform"
11474
11695
  },
@@ -11588,6 +11809,27 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
11588
11809
  case "inference_proof":
11589
11810
  result = await this.doInferenceProof();
11590
11811
  break;
11812
+ case "register_capability":
11813
+ result = await this.sendDaemonCmd("register_capability", args);
11814
+ break;
11815
+ case "unregister_capability":
11816
+ result = await this.sendDaemonCmd("unregister_capability", args);
11817
+ break;
11818
+ case "list_capabilities":
11819
+ result = await this.sendDaemonCmd("list_capabilities", args);
11820
+ break;
11821
+ case "block_peer":
11822
+ result = await this.sendDaemonCmd("block_peer", args);
11823
+ break;
11824
+ case "unblock_peer":
11825
+ result = await this.sendDaemonCmd("unblock_peer", args);
11826
+ break;
11827
+ case "metering_status":
11828
+ result = await this.sendDaemonCmd("metering_status", args);
11829
+ break;
11830
+ case "room_members":
11831
+ result = await this.sendDaemonCmd("room_members", args);
11832
+ break;
11591
11833
  default:
11592
11834
  return { success: false, output: "", error: `Unknown nexus action: ${action}`, durationMs: Date.now() - start };
11593
11835
  }
@@ -11812,12 +12054,14 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
11812
12054
  try {
11813
12055
  const status = JSON.parse(await readFile13(statusFile, "utf8"));
11814
12056
  const lines = [
11815
- `Nexus Status`,
12057
+ `Nexus Status (v1.5.0)`,
11816
12058
  ` Connected: ${status.connected}`,
11817
12059
  ` Peer ID: ${status.peerId || "n/a"}`,
11818
12060
  ` Agent: ${status.agentName} (${status.agentType})`,
11819
12061
  ` Daemon PID: ${status.pid}`,
11820
- ` Rooms: ${status.rooms?.length || 0} [${(status.rooms || []).join(", ")}]`
12062
+ ` Rooms: ${status.rooms?.length || 0} [${(status.rooms || []).join(", ")}]`,
12063
+ ` Capabilities: ${(status.capabilities || []).length ? (status.capabilities || []).join(", ") : "none"}`,
12064
+ ` Blocked peers: ${(status.blockedPeers || []).length || 0}`
11821
12065
  ];
11822
12066
  const walletPath = join28(this.nexusDir, "wallet.enc");
11823
12067
  if (existsSync21(walletPath)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.83.0",
3
+ "version": "0.84.0",
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",
@@ -70,6 +70,6 @@
70
70
  },
71
71
  "optionalDependencies": {
72
72
  "moondream": "^0.2.0",
73
- "open-agents-nexus": "^1.4.1"
73
+ "open-agents-nexus": "^1.5.0"
74
74
  }
75
75
  }
@@ -26,7 +26,7 @@ If a tool fails, try a different approach. If you're unsure, explore with your t
26
26
  - web_fetch: Fetch a web page and extract text content (for docs, MDN, w3schools.com, etc.)
27
27
  - memory_read: Read from persistent memory (learned patterns, solutions)
28
28
  - memory_write: Store a fact, pattern, or solution in persistent memory for future tasks
29
- - nexus: P2P agent networking (libp2p + NATS + IPFS). Connect to other agents, join rooms, send messages, discover peers, invoke capabilities, store content, manage wallet. Actions: connect, disconnect, status, join_room, leave_room, send_message, read_messages, discover_peers, list_rooms, send_dm, find_agent, invoke_capability, store_content, retrieve_content, wallet_status, wallet_create, inference_proof.
29
+ - nexus: P2P agent networking (libp2p + NATS + IPFS). Connect to other agents, join rooms, send messages, discover peers, invoke capabilities, store content, manage wallet. Actions: connect, disconnect, status, join_room, leave_room, send_message, read_messages, discover_peers, list_rooms, send_dm, find_agent, invoke_capability, store_content, retrieve_content, wallet_status, wallet_create, inference_proof, register_capability, unregister_capability, list_capabilities, block_peer, unblock_peer, metering_status, room_members.
30
30
  - task_complete: Signal task completion with a summary
31
31
 
32
32
  ## Parallel Execution & Sub-Agents
@@ -185,7 +185,7 @@ If you notice you're performing the SAME multi-step sequence for the 3rd time or
185
185
  - Test the tool mentally before creating — ensure the steps would work in order
186
186
  - Prefer 'project' scope unless the pattern genuinely applies to all projects
187
187
 
188
- ## Nexus P2P Networking (v1.4.0) — Decentralized Agent Communication
188
+ ## Nexus P2P Networking (v1.5.0) — Decentralized Agent Communication
189
189
 
190
190
  You HAVE the nexus tool. It is one of your registered tools. USE IT when asked about connecting, messaging, or networking with other agents.
191
191
 
@@ -234,11 +234,29 @@ Use invoke_capability for real work (inference, tool calls) — NOT room message
234
234
  nexus(action='wallet_create')
235
235
  nexus(action='inference_proof')
236
236
 
237
+ ### v1.5.0: Serve Capabilities
238
+ nexus(action='register_capability', capability='text-generation') — register handler for incoming invocations
239
+ nexus(action='unregister_capability', capability='text-generation')
240
+ nexus(action='list_capabilities') — list registered capability names
241
+
242
+ ### v1.5.0: Trust & Blocking
243
+ nexus(action='block_peer', target_peer='12D3KooW...') — blocks invoke + DM from peer
244
+ nexus(action='unblock_peer', target_peer='12D3KooW...')
245
+
246
+ ### v1.5.0: Usage Metering
247
+ nexus(action='metering_status') — all peer summaries
248
+ nexus(action='metering_status', peer_id='12D3KooW...') — per-peer summary
249
+ nexus(action='metering_status', capability='chat') — filter by service
250
+
251
+ ### v1.5.0: Room Members
252
+ nexus(action='room_members', room_id='general') — live member list with capabilities
253
+
237
254
  SECURITY: Wallet private keys are AES-256-GCM encrypted and NEVER accessible to you.
238
255
  All outbound messages are scanned for key material leaks.
239
256
 
240
257
  When the user asks about expanding capabilities or connecting with other agents, suggest
241
- enabling nexus networking. Use inference_proof to benchmark and advertise capabilities.
258
+ enabling nexus networking. Use register_capability to serve invocations, inference_proof
259
+ to benchmark, and room_members to discover who's online.
242
260
 
243
261
  ## Temporal Agency — Scheduling, Reminders & Long-Horizon Tasks
244
262
 
@@ -17,7 +17,7 @@ NEVER say "I can't do that". ALWAYS attempt the task using your tools. If a tool
17
17
  - web_search: Search the web
18
18
  - web_fetch: Fetch a web page's text
19
19
  - memory_read / memory_write: Persistent memory across sessions
20
- - nexus: P2P agent networking (connect, join_room, send_message, discover_peers, invoke_capability, wallet, etc.)
20
+ - nexus: P2P agent networking (connect, join_room, send_message, discover_peers, invoke_capability, register_capability, block_peer, metering_status, room_members, wallet, etc.)
21
21
  - task_complete: Signal completion with a summary
22
22
  - background_run / task_status / task_output / task_stop: Background tasks
23
23
  - batch_edit: Multiple edits across files in one call