open-agents-ai 0.103.9 → 0.103.10

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
@@ -12839,7 +12839,7 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
12839
12839
  ];
12840
12840
  NexusTool = class {
12841
12841
  name = "nexus";
12842
- 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. EXPOSE: expose action queries Ollama models, fetches OpenRouter market rates, registers metered inference capabilities so peers can invoke your models. pricing_menu shows rates. v1.5.0: register_capability, block_peer/unblock_peer, metering_status, room_members. Also supports direct peer invoke, IPFS storage, DMs, discovery, x402, inference proofs. WALLET: wallet_create generates EVM wallet (secp256k1, Base mainnet USDC). wallet_status shows address, USDC balance, and ledger summary. PAYMENTS: ledger_status tracks earnings/spending. budget_status/budget_set configure spending limits. spend: sign EIP-3009 USDC transfer (target_address + amount_usdc). x402 native payment via daemon. REMOTE INFERENCE: remote_infer routes a prompt to a remote peer's model on the mesh. Auto-discovers peers with the requested model capability, budget-checks, invokes, and returns the result. Use: nexus(action='remote_infer', model='qwen3.5:70b', prompt='...'). Optional: target_peer, temperature, max_tokens.";
12842
+ description = "P2P agent mesh networking. IMPORTANT: You MUST call action='connect' FIRST \u2014 it spawns the daemon. Nothing works without it. After connect: join_room, send_message, discover_peers, expose (share models), remote_infer (use remote models). Quick start: connect \u2192 join_room \u2192 send_message. Also: wallet_create, wallet_status, ledger_status, budget_set, spend, invoke_capability, store_content.";
12843
12843
  parameters = {
12844
12844
  type: "object",
12845
12845
  properties: {
@@ -12878,7 +12878,7 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
12878
12878
  "spend",
12879
12879
  "remote_infer"
12880
12880
  ],
12881
- description: "The nexus action to perform"
12881
+ description: "The nexus action. MUST call 'connect' first (spawns daemon). Then: join_room, send_message, discover_peers, expose, status, etc."
12882
12882
  },
12883
12883
  room_id: {
12884
12884
  type: "string",
@@ -26331,16 +26331,16 @@ var init_expose = __esm({
26331
26331
  // ── Proxy server ────────────────────────────────────────────────────────
26332
26332
  createProxyServer(localPort) {
26333
26333
  const target = new URL2(this._targetUrl);
26334
- return createServer2((req, res) => {
26334
+ const server = createServer2((req, res) => {
26335
+ res.on("error", () => {
26336
+ });
26335
26337
  const authHeader = req.headers.authorization;
26336
26338
  const url = new URL2(req.url ?? "/", `http://127.0.0.1:${localPort}`);
26337
26339
  const queryKey = url.searchParams.get("key");
26338
26340
  const providedKey = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : queryKey;
26339
26341
  if (providedKey !== this._authKey) {
26340
- this._stats.errors++;
26341
26342
  res.writeHead(401, { "Content-Type": "application/json" });
26342
26343
  res.end(JSON.stringify({ error: "Unauthorized \u2014 provide Bearer token or ?key= parameter" }));
26343
- this.emitStats();
26344
26344
  return;
26345
26345
  }
26346
26346
  if (url.pathname === "/v1/system/metrics" && req.method === "GET") {
@@ -26358,70 +26358,75 @@ var init_expose = __esm({
26358
26358
  this._stats.totalRequests++;
26359
26359
  this._stats.activeConnections++;
26360
26360
  this.emitStats();
26361
- this.extractModelFromRequest(req);
26362
26361
  url.searchParams.delete("key");
26363
26362
  const forwardPath = url.pathname + url.search;
26364
- const forwardHeaders = { ...req.headers, host: target.host };
26365
- delete forwardHeaders.authorization;
26366
- const proxyReq = httpRequest({
26367
- hostname: target.hostname,
26368
- port: target.port || (target.protocol === "https:" ? 443 : 80),
26369
- path: forwardPath,
26370
- method: req.method,
26371
- headers: forwardHeaders
26372
- }, (proxyRes) => {
26373
- res.writeHead(proxyRes.statusCode ?? 502, proxyRes.headers);
26374
- proxyRes.pipe(res);
26375
- proxyRes.on("end", () => {
26363
+ const bodyChunks = [];
26364
+ req.on("data", (chunk) => bodyChunks.push(chunk));
26365
+ req.on("end", () => {
26366
+ const body = Buffer.concat(bodyChunks);
26367
+ if (req.method === "POST" && body.length > 0) {
26368
+ try {
26369
+ const text = body.toString("utf8", 0, Math.min(body.length, 1024));
26370
+ const modelMatch = text.match(/"model"\s*:\s*"([^"]+)"/);
26371
+ if (modelMatch) {
26372
+ const model = modelMatch[1];
26373
+ const count = this._stats.modelUsage.get(model) ?? 0;
26374
+ this._stats.modelUsage.set(model, count + 1);
26375
+ }
26376
+ } catch {
26377
+ }
26378
+ }
26379
+ const forwardHeaders = { ...req.headers, host: target.host };
26380
+ delete forwardHeaders.authorization;
26381
+ if (body.length > 0) {
26382
+ forwardHeaders["content-length"] = String(body.length);
26383
+ }
26384
+ const proxyReq = httpRequest({
26385
+ hostname: target.hostname,
26386
+ port: target.port || (target.protocol === "https:" ? 443 : 80),
26387
+ path: forwardPath,
26388
+ method: req.method,
26389
+ headers: forwardHeaders
26390
+ }, (proxyRes) => {
26391
+ proxyRes.on("error", () => {
26392
+ this._stats.activeConnections = Math.max(0, this._stats.activeConnections - 1);
26393
+ this.emitStats();
26394
+ });
26395
+ res.writeHead(proxyRes.statusCode ?? 502, proxyRes.headers);
26396
+ proxyRes.pipe(res);
26397
+ proxyRes.on("end", () => {
26398
+ this._stats.activeConnections = Math.max(0, this._stats.activeConnections - 1);
26399
+ this.emitStats();
26400
+ });
26401
+ });
26402
+ proxyReq.on("error", (err) => {
26403
+ this._stats.errors++;
26376
26404
  this._stats.activeConnections = Math.max(0, this._stats.activeConnections - 1);
26405
+ if (!res.headersSent) {
26406
+ res.writeHead(502, { "Content-Type": "application/json" });
26407
+ res.end(JSON.stringify({ error: `Backend unreachable: ${err.message}` }));
26408
+ } else {
26409
+ try {
26410
+ res.end();
26411
+ } catch {
26412
+ }
26413
+ }
26377
26414
  this.emitStats();
26378
26415
  });
26416
+ if (body.length > 0) {
26417
+ proxyReq.write(body);
26418
+ }
26419
+ proxyReq.end();
26379
26420
  });
26380
- proxyReq.on("error", (err) => {
26381
- this._stats.errors++;
26421
+ req.on("error", () => {
26382
26422
  this._stats.activeConnections = Math.max(0, this._stats.activeConnections - 1);
26383
- if (!res.headersSent) {
26384
- res.writeHead(502, { "Content-Type": "application/json" });
26385
- res.end(JSON.stringify({ error: `Backend unreachable: ${err.message}` }));
26386
- }
26387
- this._stats.status = "error";
26388
26423
  this.emitStats();
26389
- setTimeout(() => {
26390
- if (this.server && this._stats.status === "error") {
26391
- this._stats.status = "active";
26392
- this.emitStats();
26393
- }
26394
- }, 5e3);
26395
26424
  });
26396
- req.pipe(proxyReq);
26397
26425
  });
26398
- }
26399
- /** Best-effort model name extraction from JSON body for usage tracking */
26400
- extractModelFromRequest(req) {
26401
- if (req.method !== "POST")
26402
- return;
26403
- const chunks = [];
26404
- const origEmit = req.emit.bind(req);
26405
- let peeked = false;
26406
- const stats = this._stats;
26407
- req.emit = function(event, ...args) {
26408
- if (event === "data" && !peeked) {
26409
- peeked = true;
26410
- try {
26411
- const chunk = args[0];
26412
- chunks.push(chunk);
26413
- const text = Buffer.concat(chunks).toString();
26414
- const modelMatch = text.match(/"model"\s*:\s*"([^"]+)"/);
26415
- if (modelMatch) {
26416
- const model = modelMatch[1];
26417
- const count = stats.modelUsage.get(model) ?? 0;
26418
- stats.modelUsage.set(model, count + 1);
26419
- }
26420
- } catch {
26421
- }
26422
- }
26423
- return origEmit(event, ...args);
26424
- };
26426
+ server.on("error", (err) => {
26427
+ this.options.onError?.(`Proxy server error: ${err.message}`);
26428
+ });
26429
+ return server;
26425
26430
  }
26426
26431
  // ── Cloudflared ─────────────────────────────────────────────────────────
26427
26432
  startCloudflared(port) {
@@ -29744,7 +29749,8 @@ function trySudoPasswordless(cmd, timeoutMs = 12e4) {
29744
29749
  }
29745
29750
  function runWithSudo(cmd, password, timeoutMs = 12e4) {
29746
29751
  try {
29747
- execSync26(`sudo -S ${cmd}`, {
29752
+ const escaped = cmd.replace(/'/g, "'\\''");
29753
+ execSync26(`sudo -S bash -c '${escaped}'`, {
29748
29754
  input: password + "\n",
29749
29755
  stdio: ["pipe", "pipe", "pipe"],
29750
29756
  timeout: timeoutMs,
@@ -31906,6 +31912,10 @@ async function handleEndpoint(arg, ctx, local = false) {
31906
31912
  `);
31907
31913
  }
31908
31914
  process.stdout.write("\n");
31915
+ if (ctx.hasActiveTask?.()) {
31916
+ ctx.abortActiveTask?.();
31917
+ renderWarning("Active task aborted \u2014 endpoint changed. Send a new prompt to continue.");
31918
+ }
31909
31919
  try {
31910
31920
  const newModels = await fetchModels(normalizedUrl, apiKey);
31911
31921
  if (newModels.length > 0) {
@@ -44000,6 +44010,11 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
44000
44010
  },
44001
44011
  refreshModelCache,
44002
44012
  hasActiveTask: () => activeTask !== null,
44013
+ abortActiveTask() {
44014
+ if (!activeTask)
44015
+ return;
44016
+ activeTask.runner.abort();
44017
+ },
44003
44018
  requestCompaction(strategy) {
44004
44019
  if (!activeTask)
44005
44020
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.103.9",
3
+ "version": "0.103.10",
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",
@@ -238,11 +238,13 @@ If you notice you're performing the SAME multi-step sequence for the 3rd time or
238
238
 
239
239
  ## Nexus P2P Networking (v1.5.6) — Decentralized Agent Communication + x402 Payments
240
240
 
241
- You HAVE the nexus tool. It is one of your registered tools. USE IT when asked about connecting, messaging, or networking with other agents.
241
+ You HAVE the nexus tool. USE IT when asked about connecting, messaging, or networking with other agents.
242
242
 
243
- open-agents-nexus is auto-installed on first use. Requires Node >= 22 (Promise.withResolvers).
243
+ **CRITICAL: ALWAYS call nexus(action='connect') FIRST.** It spawns the daemon process. No other action works without it.
244
244
 
245
- ### Quick Start (3 steps)
245
+ Auto-installs open-agents-nexus on first use. Requires Node >= 22.
246
+
247
+ ### Quick Start (3 steps — connect MUST be first)
246
248
  nexus(action='connect', agent_name='MyAgent')
247
249
  nexus(action='join_room', room_id='general')
248
250
  nexus(action='send_message', room_id='general', message='Hello from MyAgent!')
@@ -21,7 +21,7 @@ NEVER say "I can't do that". ALWAYS attempt the task using your tools. If a tool
21
21
  - web_search: Search the web
22
22
  - web_fetch: Fetch a web page's text
23
23
  - memory_read / memory_write: Persistent memory across sessions
24
- - nexus: P2P agent networking (connect, join_room, send_message, discover_peers, invoke_capability, register_capability, block_peer, metering_status, room_members, wallet, etc.)
24
+ - nexus: P2P agent mesh. ALWAYS call connect FIRST (spawns daemon). Then: join_room, send_message, discover_peers, expose, etc.
25
25
  - task_complete: Signal completion with a summary
26
26
  - background_run / task_status / task_output / task_stop: Background tasks
27
27
  - sub_agent: Delegate a subtask to an independent agent (use background=true for parallel work)
@@ -67,7 +67,7 @@ You are **Open Agent** (open-agents-ai), an autonomous AI coding agent running o
67
67
  - Web: search documentation and fetch web pages
68
68
  - Memory: persistent cross-session knowledge (memory_read/memory_write)
69
69
  - Skills: 250+ behavioral skills (skill_list), build new ones (skill_build)
70
- - P2P: connect to other agents via nexus (libp2p + NATS mesh)
70
+ - P2P: nexus agent mesh ALWAYS call nexus(action='connect') FIRST, then join_room/send_message/discover_peers/expose
71
71
  - Background tasks: run long commands in background, check status later
72
72
  - Voice/TTS: text-to-speech via ONNX (cross-platform) or MLX (Apple Silicon) — use /voice to enable
73
73
  - Desktop/Vision: screenshot, click UI, OCR (via explore_tools)
@@ -20,5 +20,5 @@ Rules:
20
20
  - Directory entries are RELATIVE. If you list "parent/" and see "child", the path is "parent/child" — NOT ".child".
21
21
  - Use list_directory for directories, NOT file_read. Prefer list_directory over shell ls.
22
22
  - You are **Open Agent** (open-agents-ai) — an AI coding agent running locally via Ollama/vLLM. No cloud APIs.
23
- - Core: code editing, shell commands, web search, memory, 250+ skills (skill_list), P2P networking (nexus), background tasks.
23
+ - Core: code editing, shell commands, web search, memory, 250+ skills (skill_list), P2P mesh (nexus — call connect FIRST), background tasks.
24
24
  - When asked "what can you do?", use explore_tools() and skill_list() to discover and report your actual capabilities. Do NOT hallucinate.