open-agents-ai 0.103.9 → 0.103.11

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",
@@ -17464,7 +17464,9 @@ If you're stuck, try a completely different approach. Do NOT repeat what failed
17464
17464
  } catch (reqErr) {
17465
17465
  const recovered = await this.retryOnTransient(reqErr, chatRequest, turn);
17466
17466
  if (!recovered) {
17467
- this.emit({ type: "error", content: `Backend error: ${reqErr instanceof Error ? reqErr.message : String(reqErr)}`, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
17467
+ const errMsg = reqErr instanceof Error ? reqErr.message : String(reqErr);
17468
+ const cause = reqErr instanceof Error && reqErr.cause ? ` (${reqErr.cause.message ?? ""} ${reqErr.cause?.code ?? ""})` : "";
17469
+ this.emit({ type: "error", content: `Backend error: ${errMsg}${cause}`, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
17468
17470
  messages.push({ role: "user", content: "[System: backend request failed, retrying on next turn. The previous request was lost.]" });
17469
17471
  continue;
17470
17472
  }
@@ -17974,7 +17976,9 @@ Integrate this guidance into your current approach. Continue working on the task
17974
17976
  } catch (reqErr) {
17975
17977
  const recovered = await this.retryOnTransient(reqErr, chatRequest, turn);
17976
17978
  if (!recovered) {
17977
- this.emit({ type: "error", content: `Backend error: ${reqErr instanceof Error ? reqErr.message : String(reqErr)}`, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
17979
+ const errMsg2 = reqErr instanceof Error ? reqErr.message : String(reqErr);
17980
+ const cause2 = reqErr instanceof Error && reqErr.cause ? ` (${reqErr.cause.message ?? ""} ${reqErr.cause?.code ?? ""})` : "";
17981
+ this.emit({ type: "error", content: `Backend error: ${errMsg2}${cause2}`, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
17978
17982
  messages.push({ role: "user", content: "[System: backend request failed, retrying on next turn. The previous request was lost.]" });
17979
17983
  continue;
17980
17984
  }
@@ -20358,9 +20362,9 @@ function ensureTranscribeCliBackground() {
20358
20362
  } catch {
20359
20363
  }
20360
20364
  try {
20361
- const { exec } = await import("node:child_process");
20365
+ const { exec: exec2 } = await import("node:child_process");
20362
20366
  return new Promise((resolve31) => {
20363
- exec("npm i -g transcribe-cli", { timeout: 18e4 }, (err) => {
20367
+ exec2("npm i -g transcribe-cli", { timeout: 18e4 }, (err) => {
20364
20368
  resolve31(!err);
20365
20369
  });
20366
20370
  });
@@ -26194,12 +26198,30 @@ var init_voice_session = __esm({
26194
26198
 
26195
26199
  // packages/cli/dist/tui/expose.js
26196
26200
  import { createServer as createServer2, request as httpRequest } from "node:http";
26197
- import { spawn as spawn14, execSync as execSync25 } from "node:child_process";
26201
+ import { spawn as spawn14, exec } from "node:child_process";
26198
26202
  import { EventEmitter as EventEmitter3 } from "node:events";
26199
26203
  import { randomBytes as randomBytes7 } from "node:crypto";
26200
26204
  import { URL as URL2 } from "node:url";
26201
26205
  import { loadavg, cpus, totalmem, freemem } from "node:os";
26202
- function collectSystemMetrics() {
26206
+ function cleanForwardHeaders(raw, targetHost) {
26207
+ const out = {};
26208
+ for (const [key, value] of Object.entries(raw)) {
26209
+ if (HOP_BY_HOP_HEADERS.has(key))
26210
+ continue;
26211
+ if (CF_HEADERS_PREFIX.some((p) => key.startsWith(p)))
26212
+ continue;
26213
+ if (key === "authorization")
26214
+ continue;
26215
+ if (key.startsWith("x-forwarded-"))
26216
+ continue;
26217
+ if (key === "sec-fetch-mode")
26218
+ continue;
26219
+ out[key] = value;
26220
+ }
26221
+ out.host = targetHost;
26222
+ return out;
26223
+ }
26224
+ async function collectSystemMetricsAsync() {
26203
26225
  const [l1, l5, l15] = loadavg();
26204
26226
  const cores = cpus().length;
26205
26227
  const totalMem = totalmem();
@@ -26214,7 +26236,9 @@ function collectSystemMetrics() {
26214
26236
  vramUtilization: 0
26215
26237
  };
26216
26238
  try {
26217
- const smi = execSync25("nvidia-smi --query-gpu=utilization.gpu,memory.used,memory.total,name --format=csv,noheader,nounits 2>/dev/null", { encoding: "utf8", timeout: 3e3 });
26239
+ const smi = await new Promise((resolve31, reject) => {
26240
+ exec("nvidia-smi --query-gpu=utilization.gpu,memory.used,memory.total,name --format=csv,noheader,nounits 2>/dev/null", { encoding: "utf8", timeout: 3e3 }, (err, stdout) => err ? reject(err) : resolve31(stdout));
26241
+ });
26218
26242
  const line = smi.trim().split("\n")[0];
26219
26243
  if (line) {
26220
26244
  const parts = line.split(",").map((s) => s.trim());
@@ -26245,11 +26269,22 @@ function collectSystemMetrics() {
26245
26269
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
26246
26270
  };
26247
26271
  }
26248
- var DEFAULT_TARGETS, ExposeGateway;
26272
+ var HOP_BY_HOP_HEADERS, CF_HEADERS_PREFIX, DEFAULT_TARGETS, ExposeGateway;
26249
26273
  var init_expose = __esm({
26250
26274
  "packages/cli/dist/tui/expose.js"() {
26251
26275
  "use strict";
26252
26276
  init_render();
26277
+ HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
26278
+ "connection",
26279
+ "keep-alive",
26280
+ "proxy-authenticate",
26281
+ "proxy-authorization",
26282
+ "te",
26283
+ "trailers",
26284
+ "transfer-encoding",
26285
+ "upgrade"
26286
+ ]);
26287
+ CF_HEADERS_PREFIX = ["cf-", "cdn-"];
26253
26288
  DEFAULT_TARGETS = {
26254
26289
  ollama: "http://127.0.0.1:11434",
26255
26290
  vllm: "http://127.0.0.1:8000",
@@ -26331,100 +26366,168 @@ var init_expose = __esm({
26331
26366
  // ── Proxy server ────────────────────────────────────────────────────────
26332
26367
  createProxyServer(localPort) {
26333
26368
  const target = new URL2(this._targetUrl);
26334
- return createServer2((req, res) => {
26369
+ const server = createServer2((req, res) => {
26370
+ res.on("error", () => {
26371
+ });
26335
26372
  const authHeader = req.headers.authorization;
26336
26373
  const url = new URL2(req.url ?? "/", `http://127.0.0.1:${localPort}`);
26337
26374
  const queryKey = url.searchParams.get("key");
26338
26375
  const providedKey = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : queryKey;
26339
26376
  if (providedKey !== this._authKey) {
26340
- this._stats.errors++;
26341
26377
  res.writeHead(401, { "Content-Type": "application/json" });
26342
26378
  res.end(JSON.stringify({ error: "Unauthorized \u2014 provide Bearer token or ?key= parameter" }));
26343
- this.emitStats();
26344
26379
  return;
26345
26380
  }
26346
26381
  if (url.pathname === "/v1/system/metrics" && req.method === "GET") {
26347
- const metrics = collectSystemMetrics();
26348
- const gatewayStats = {
26349
- totalRequests: this._stats.totalRequests,
26350
- activeConnections: this._stats.activeConnections,
26351
- errors: this._stats.errors,
26352
- modelUsage: Object.fromEntries(this._stats.modelUsage)
26353
- };
26354
- res.writeHead(200, { "Content-Type": "application/json" });
26355
- res.end(JSON.stringify({ ...metrics, gateway: gatewayStats }));
26382
+ collectSystemMetricsAsync().then((metrics) => {
26383
+ const gatewayStats = {
26384
+ totalRequests: this._stats.totalRequests,
26385
+ activeConnections: this._stats.activeConnections,
26386
+ errors: this._stats.errors,
26387
+ modelUsage: Object.fromEntries(this._stats.modelUsage)
26388
+ };
26389
+ res.writeHead(200, { "Content-Type": "application/json" });
26390
+ res.end(JSON.stringify({ ...metrics, gateway: gatewayStats }));
26391
+ }).catch(() => {
26392
+ res.writeHead(500);
26393
+ res.end("metrics error");
26394
+ });
26356
26395
  return;
26357
26396
  }
26358
26397
  this._stats.totalRequests++;
26359
26398
  this._stats.activeConnections++;
26360
26399
  this.emitStats();
26361
- this.extractModelFromRequest(req);
26362
26400
  url.searchParams.delete("key");
26363
26401
  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", () => {
26402
+ const bodyChunks = [];
26403
+ req.on("data", (chunk) => bodyChunks.push(chunk));
26404
+ req.on("end", () => {
26405
+ const body = Buffer.concat(bodyChunks);
26406
+ let isStreaming = false;
26407
+ if (req.method === "POST" && body.length > 0) {
26408
+ try {
26409
+ const text = body.toString("utf8", 0, Math.min(body.length, 1024));
26410
+ const modelMatch = text.match(/"model"\s*:\s*"([^"]+)"/);
26411
+ if (modelMatch) {
26412
+ const model = modelMatch[1];
26413
+ const count = this._stats.modelUsage.get(model) ?? 0;
26414
+ this._stats.modelUsage.set(model, count + 1);
26415
+ }
26416
+ isStreaming = /"stream"\s*:\s*true/.test(text);
26417
+ } catch {
26418
+ }
26419
+ }
26420
+ const forwardHeaders = cleanForwardHeaders(req.headers, target.host);
26421
+ if (body.length > 0) {
26422
+ forwardHeaders["content-length"] = String(body.length);
26423
+ }
26424
+ let heartbeatTimer = null;
26425
+ if (isStreaming) {
26426
+ res.writeHead(200, {
26427
+ "Content-Type": "text/event-stream",
26428
+ "Cache-Control": "no-cache",
26429
+ "X-Accel-Buffering": "no"
26430
+ // tells nginx/CF not to buffer
26431
+ });
26432
+ heartbeatTimer = setInterval(() => {
26433
+ try {
26434
+ res.write(": heartbeat\n\n");
26435
+ } catch {
26436
+ }
26437
+ }, 15e3);
26438
+ }
26439
+ const cleanupHeartbeat = () => {
26440
+ if (heartbeatTimer) {
26441
+ clearInterval(heartbeatTimer);
26442
+ heartbeatTimer = null;
26443
+ }
26444
+ };
26445
+ const proxyReq = httpRequest({
26446
+ hostname: target.hostname,
26447
+ port: target.port || (target.protocol === "https:" ? 443 : 80),
26448
+ path: forwardPath,
26449
+ method: req.method,
26450
+ headers: forwardHeaders
26451
+ }, (proxyRes) => {
26452
+ cleanupHeartbeat();
26453
+ proxyRes.on("error", () => {
26454
+ this._stats.activeConnections = Math.max(0, this._stats.activeConnections - 1);
26455
+ this.emitStats();
26456
+ });
26457
+ if (isStreaming) {
26458
+ if (proxyRes.statusCode !== 200) {
26459
+ let errBody = "";
26460
+ proxyRes.on("data", (chunk) => {
26461
+ errBody += chunk.toString();
26462
+ });
26463
+ proxyRes.on("end", () => {
26464
+ try {
26465
+ res.write(`data: ${JSON.stringify({ error: `Backend ${proxyRes.statusCode}: ${errBody.slice(0, 200)}` })}
26466
+
26467
+ `);
26468
+ } catch {
26469
+ }
26470
+ try {
26471
+ res.end();
26472
+ } catch {
26473
+ }
26474
+ this._stats.errors++;
26475
+ this._stats.activeConnections = Math.max(0, this._stats.activeConnections - 1);
26476
+ this.emitStats();
26477
+ });
26478
+ return;
26479
+ }
26480
+ proxyRes.pipe(res);
26481
+ } else {
26482
+ res.writeHead(proxyRes.statusCode ?? 502, proxyRes.headers);
26483
+ proxyRes.pipe(res);
26484
+ }
26485
+ proxyRes.on("end", () => {
26486
+ this._stats.activeConnections = Math.max(0, this._stats.activeConnections - 1);
26487
+ this.emitStats();
26488
+ });
26489
+ });
26490
+ proxyReq.on("error", (err) => {
26491
+ cleanupHeartbeat();
26492
+ this._stats.errors++;
26376
26493
  this._stats.activeConnections = Math.max(0, this._stats.activeConnections - 1);
26494
+ if (!res.headersSent) {
26495
+ res.writeHead(502, { "Content-Type": "application/json" });
26496
+ res.end(JSON.stringify({ error: `Backend unreachable: ${err.message}` }));
26497
+ } else {
26498
+ try {
26499
+ res.write(`data: ${JSON.stringify({ error: err.message })}
26500
+
26501
+ `);
26502
+ } catch {
26503
+ }
26504
+ try {
26505
+ res.end();
26506
+ } catch {
26507
+ }
26508
+ }
26377
26509
  this.emitStats();
26378
26510
  });
26511
+ if (body.length > 0) {
26512
+ proxyReq.write(body);
26513
+ }
26514
+ proxyReq.end();
26379
26515
  });
26380
- proxyReq.on("error", (err) => {
26381
- this._stats.errors++;
26516
+ req.on("error", () => {
26382
26517
  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
26518
  this.emitStats();
26389
- setTimeout(() => {
26390
- if (this.server && this._stats.status === "error") {
26391
- this._stats.status = "active";
26392
- this.emitStats();
26393
- }
26394
- }, 5e3);
26395
26519
  });
26396
- req.pipe(proxyReq);
26397
26520
  });
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
- };
26521
+ server.on("error", (err) => {
26522
+ this.options.onError?.(`Proxy server error: ${err.message}`);
26523
+ });
26524
+ return server;
26425
26525
  }
26426
26526
  // ── Cloudflared ─────────────────────────────────────────────────────────
26527
+ /** Port the proxy is listening on (needed for cloudflared restart) */
26528
+ _proxyPort = 0;
26427
26529
  startCloudflared(port) {
26530
+ this._proxyPort = port;
26428
26531
  return new Promise((resolve31, reject) => {
26429
26532
  const timeout = setTimeout(() => {
26430
26533
  reject(new Error("Cloudflared tunnel start timeout (30s)"));
@@ -26432,7 +26535,9 @@ var init_expose = __esm({
26432
26535
  this.cloudflaredProcess = spawn14("cloudflared", [
26433
26536
  "tunnel",
26434
26537
  "--url",
26435
- `http://127.0.0.1:${port}`
26538
+ `http://127.0.0.1:${port}`,
26539
+ "--proxy-keepalive-timeout",
26540
+ "300s"
26436
26541
  ], {
26437
26542
  stdio: ["ignore", "pipe", "pipe"]
26438
26543
  });
@@ -26458,12 +26563,27 @@ var init_expose = __esm({
26458
26563
  reject(new Error(`Cloudflared exited with code ${code} before providing URL`));
26459
26564
  }
26460
26565
  if (this._stats.status === "active") {
26566
+ this.options.onInfo?.("Cloudflared tunnel died \u2014 restarting...");
26461
26567
  this._stats.status = "error";
26462
26568
  this.emitStats();
26569
+ this.restartCloudflared();
26463
26570
  }
26464
26571
  });
26465
26572
  });
26466
26573
  }
26574
+ /** Auto-restart cloudflared after unexpected exit (new URL assigned) */
26575
+ async restartCloudflared() {
26576
+ if (!this.server || this._proxyPort === 0)
26577
+ return;
26578
+ try {
26579
+ this._tunnelUrl = await this.startCloudflared(this._proxyPort);
26580
+ this._stats.status = "active";
26581
+ this.emitStats();
26582
+ this.options.onInfo?.(`Tunnel restarted: ${this._tunnelUrl}`);
26583
+ } catch (err) {
26584
+ this.options.onError?.(`Tunnel restart failed: ${err instanceof Error ? err.message : String(err)}`);
26585
+ }
26586
+ }
26467
26587
  // ── Helpers ─────────────────────────────────────────────────────────────
26468
26588
  findFreePort() {
26469
26589
  return new Promise((resolve31, reject) => {
@@ -28618,7 +28738,7 @@ var init_oa_directory = __esm({
28618
28738
 
28619
28739
  // packages/cli/dist/tui/setup.js
28620
28740
  import * as readline from "node:readline";
28621
- import { execSync as execSync26, spawn as spawn15 } from "node:child_process";
28741
+ import { execSync as execSync25, spawn as spawn15 } from "node:child_process";
28622
28742
  import { existsSync as existsSync29, writeFileSync as writeFileSync10, mkdirSync as mkdirSync10 } from "node:fs";
28623
28743
  import { join as join39 } from "node:path";
28624
28744
  import { homedir as homedir10, platform } from "node:os";
@@ -28628,7 +28748,7 @@ function detectSystemSpecs() {
28628
28748
  let gpuVramGB = 0;
28629
28749
  let gpuName = "";
28630
28750
  try {
28631
- const memInfo = execSync26("free -b 2>/dev/null || sysctl -n hw.memsize 2>/dev/null", {
28751
+ const memInfo = execSync25("free -b 2>/dev/null || sysctl -n hw.memsize 2>/dev/null", {
28632
28752
  encoding: "utf8",
28633
28753
  timeout: 5e3
28634
28754
  });
@@ -28648,7 +28768,7 @@ function detectSystemSpecs() {
28648
28768
  } catch {
28649
28769
  }
28650
28770
  try {
28651
- const nvidiaSmi = execSync26("nvidia-smi --query-gpu=memory.total,name --format=csv,noheader,nounits 2>/dev/null", { encoding: "utf8", timeout: 5e3 });
28771
+ const nvidiaSmi = execSync25("nvidia-smi --query-gpu=memory.total,name --format=csv,noheader,nounits 2>/dev/null", { encoding: "utf8", timeout: 5e3 });
28652
28772
  const lines = nvidiaSmi.trim().split("\n");
28653
28773
  if (lines.length > 0) {
28654
28774
  for (const line of lines) {
@@ -28773,7 +28893,7 @@ function ensureCurl() {
28773
28893
  for (const s of strategies) {
28774
28894
  if (hasCmd(s.check)) {
28775
28895
  try {
28776
- execSync26(s.install, { stdio: "inherit", timeout: 12e4 });
28896
+ execSync25(s.install, { stdio: "inherit", timeout: 12e4 });
28777
28897
  if (hasCmd("curl")) {
28778
28898
  process.stdout.write(` ${c2.green("\u2714")} curl installed via ${s.label}.
28779
28899
  `);
@@ -28787,7 +28907,7 @@ function ensureCurl() {
28787
28907
  }
28788
28908
  if (plat === "darwin") {
28789
28909
  try {
28790
- execSync26("xcode-select --install", { stdio: "inherit", timeout: 3e5 });
28910
+ execSync25("xcode-select --install", { stdio: "inherit", timeout: 3e5 });
28791
28911
  if (hasCmd("curl"))
28792
28912
  return true;
28793
28913
  } catch {
@@ -28822,7 +28942,7 @@ function installOllamaLinux() {
28822
28942
 
28823
28943
  `);
28824
28944
  try {
28825
- execSync26("curl -fsSL https://ollama.com/install.sh | sh", {
28945
+ execSync25("curl -fsSL https://ollama.com/install.sh | sh", {
28826
28946
  stdio: "inherit",
28827
28947
  timeout: 3e5
28828
28948
  });
@@ -28850,7 +28970,7 @@ async function installOllamaMac(_rl) {
28850
28970
 
28851
28971
  `);
28852
28972
  try {
28853
- execSync26('/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"', { stdio: "inherit", timeout: 6e5 });
28973
+ execSync25('/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"', { stdio: "inherit", timeout: 6e5 });
28854
28974
  if (!hasCmd("brew")) {
28855
28975
  try {
28856
28976
  const brewPrefix = existsSync29("/opt/homebrew/bin/brew") ? "/opt/homebrew" : "/usr/local";
@@ -28883,7 +29003,7 @@ async function installOllamaMac(_rl) {
28883
29003
 
28884
29004
  `);
28885
29005
  try {
28886
- execSync26("brew install ollama", {
29006
+ execSync25("brew install ollama", {
28887
29007
  stdio: "inherit",
28888
29008
  timeout: 3e5
28889
29009
  });
@@ -28910,7 +29030,7 @@ function installOllamaWindows() {
28910
29030
 
28911
29031
  `);
28912
29032
  try {
28913
- execSync26('powershell -Command "irm https://ollama.com/install.ps1 | iex"', {
29033
+ execSync25('powershell -Command "irm https://ollama.com/install.ps1 | iex"', {
28914
29034
  stdio: "inherit",
28915
29035
  timeout: 3e5
28916
29036
  });
@@ -28991,7 +29111,7 @@ async function ensureOllamaRunning(backendUrl, rl) {
28991
29111
  }
28992
29112
  function pullModelWithAutoUpdate(tag) {
28993
29113
  try {
28994
- execSync26(`ollama pull ${tag}`, {
29114
+ execSync25(`ollama pull ${tag}`, {
28995
29115
  stdio: "inherit",
28996
29116
  timeout: 36e5
28997
29117
  // 1 hour max
@@ -29011,7 +29131,7 @@ function pullModelWithAutoUpdate(tag) {
29011
29131
 
29012
29132
  `);
29013
29133
  try {
29014
- execSync26("curl -fsSL https://ollama.com/install.sh | sh", {
29134
+ execSync25("curl -fsSL https://ollama.com/install.sh | sh", {
29015
29135
  stdio: "inherit",
29016
29136
  timeout: 3e5
29017
29137
  // 5 min max for install
@@ -29022,7 +29142,7 @@ function pullModelWithAutoUpdate(tag) {
29022
29142
  process.stdout.write(` ${c2.cyan("\u25CF")} Retrying pull of ${c2.bold(tag)}...
29023
29143
 
29024
29144
  `);
29025
- execSync26(`ollama pull ${tag}`, {
29145
+ execSync25(`ollama pull ${tag}`, {
29026
29146
  stdio: "inherit",
29027
29147
  timeout: 36e5
29028
29148
  });
@@ -29124,7 +29244,7 @@ function ensurePython3() {
29124
29244
  if (plat === "darwin") {
29125
29245
  if (hasCmd("brew")) {
29126
29246
  try {
29127
- execSync26("brew install python3", { stdio: "inherit", timeout: 3e5 });
29247
+ execSync25("brew install python3", { stdio: "inherit", timeout: 3e5 });
29128
29248
  if (hasCmd("python3")) {
29129
29249
  process.stdout.write(` ${c2.green("\u2714")} Python3 installed via Homebrew.
29130
29250
  `);
@@ -29137,7 +29257,7 @@ function ensurePython3() {
29137
29257
  for (const s of strategies) {
29138
29258
  if (hasCmd(s.check)) {
29139
29259
  try {
29140
- execSync26(s.install, { stdio: "inherit", timeout: 12e4 });
29260
+ execSync25(s.install, { stdio: "inherit", timeout: 12e4 });
29141
29261
  if (hasCmd("python3") || hasCmd("python")) {
29142
29262
  process.stdout.write(` ${c2.green("\u2714")} Python3 installed via ${s.label}.
29143
29263
  `);
@@ -29153,11 +29273,11 @@ function ensurePython3() {
29153
29273
  }
29154
29274
  function checkPythonVenv() {
29155
29275
  try {
29156
- execSync26("python3 -m venv --help", { stdio: "pipe", timeout: 5e3 });
29276
+ execSync25("python3 -m venv --help", { stdio: "pipe", timeout: 5e3 });
29157
29277
  return true;
29158
29278
  } catch {
29159
29279
  try {
29160
- execSync26("python -m venv --help", { stdio: "pipe", timeout: 5e3 });
29280
+ execSync25("python -m venv --help", { stdio: "pipe", timeout: 5e3 });
29161
29281
  return true;
29162
29282
  } catch {
29163
29283
  return false;
@@ -29176,7 +29296,7 @@ function ensurePythonVenv() {
29176
29296
  for (const s of strategies) {
29177
29297
  if (hasCmd(s.check)) {
29178
29298
  try {
29179
- execSync26(s.install, { stdio: "inherit", timeout: 12e4 });
29299
+ execSync25(s.install, { stdio: "inherit", timeout: 12e4 });
29180
29300
  if (checkPythonVenv()) {
29181
29301
  process.stdout.write(` ${c2.green("\u2714")} python3-venv installed via ${s.label}.
29182
29302
  `);
@@ -29612,7 +29732,7 @@ async function doSetup(config, rl) {
29612
29732
  const modelfilePath = join39(modelDir2, `Modelfile.${customName}`);
29613
29733
  writeFileSync10(modelfilePath, modelfileContent + "\n", "utf8");
29614
29734
  process.stdout.write(` ${c2.dim("Creating model...")} `);
29615
- execSync26(`ollama create ${customName} -f ${modelfilePath}`, {
29735
+ execSync25(`ollama create ${customName} -f ${modelfilePath}`, {
29616
29736
  stdio: "pipe",
29617
29737
  timeout: 12e4
29618
29738
  });
@@ -29663,7 +29783,7 @@ function isFirstRun() {
29663
29783
  function hasCmd(cmd) {
29664
29784
  try {
29665
29785
  const whichCmd = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
29666
- execSync26(whichCmd, { stdio: "pipe", timeout: 3e3 });
29786
+ execSync25(whichCmd, { stdio: "pipe", timeout: 3e3 });
29667
29787
  return true;
29668
29788
  } catch {
29669
29789
  return false;
@@ -29696,7 +29816,7 @@ function getVenvDir() {
29696
29816
  }
29697
29817
  function hasVenvModule() {
29698
29818
  try {
29699
- execSync26("python3 -m venv --help", { stdio: "pipe", timeout: 5e3 });
29819
+ execSync25("python3 -m venv --help", { stdio: "pipe", timeout: 5e3 });
29700
29820
  return true;
29701
29821
  } catch {
29702
29822
  return false;
@@ -29718,8 +29838,8 @@ function ensureVenv(log) {
29718
29838
  }
29719
29839
  try {
29720
29840
  mkdirSync10(join39(homedir10(), ".open-agents"), { recursive: true });
29721
- execSync26(`python3 -m venv "${venvDir}"`, { stdio: "pipe", timeout: 3e4 });
29722
- execSync26(`"${join39(venvDir, "bin", "pip")}" install --upgrade pip`, {
29841
+ execSync25(`python3 -m venv "${venvDir}"`, { stdio: "pipe", timeout: 3e4 });
29842
+ execSync25(`"${join39(venvDir, "bin", "pip")}" install --upgrade pip`, {
29723
29843
  stdio: "pipe",
29724
29844
  timeout: 6e4
29725
29845
  });
@@ -29732,7 +29852,7 @@ function ensureVenv(log) {
29732
29852
  }
29733
29853
  function trySudoPasswordless(cmd, timeoutMs = 12e4) {
29734
29854
  try {
29735
- execSync26(`sudo -n ${cmd}`, {
29855
+ execSync25(`sudo -n ${cmd}`, {
29736
29856
  stdio: "pipe",
29737
29857
  timeout: timeoutMs,
29738
29858
  env: { ...process.env, DEBIAN_FRONTEND: "noninteractive" }
@@ -29744,7 +29864,8 @@ function trySudoPasswordless(cmd, timeoutMs = 12e4) {
29744
29864
  }
29745
29865
  function runWithSudo(cmd, password, timeoutMs = 12e4) {
29746
29866
  try {
29747
- execSync26(`sudo -S ${cmd}`, {
29867
+ const escaped = cmd.replace(/'/g, "'\\''");
29868
+ execSync25(`sudo -S bash -c '${escaped}'`, {
29748
29869
  input: password + "\n",
29749
29870
  stdio: ["pipe", "pipe", "pipe"],
29750
29871
  timeout: timeoutMs,
@@ -29851,7 +29972,7 @@ async function ensureVisionDeps(onInfo, getSudoPassword) {
29851
29972
  ok = await sudoInstall(batchCmd, getPassword, log, cachedPasswordRef, 18e4);
29852
29973
  } else {
29853
29974
  try {
29854
- execSync26(batchCmd, { stdio: "pipe", timeout: 18e4 });
29975
+ execSync25(batchCmd, { stdio: "pipe", timeout: 18e4 });
29855
29976
  ok = true;
29856
29977
  } catch {
29857
29978
  ok = false;
@@ -29888,7 +30009,7 @@ async function ensureVisionDeps(onInfo, getSudoPassword) {
29888
30009
  const venvCmds = {
29889
30010
  apt: () => {
29890
30011
  try {
29891
- const pyVer = execSync26(`python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"`, { encoding: "utf8", stdio: "pipe", timeout: 5e3 }).trim();
30012
+ const pyVer = execSync25(`python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"`, { encoding: "utf8", stdio: "pipe", timeout: 5e3 }).trim();
29892
30013
  return `apt-get install -y python3-venv python${pyVer}-venv`;
29893
30014
  } catch {
29894
30015
  return "apt-get install -y python3-venv";
@@ -29916,12 +30037,12 @@ async function ensureVisionDeps(onInfo, getSudoPassword) {
29916
30037
  const venvPip = join39(venvBin, "pip");
29917
30038
  log("Installing moondream-station in ~/.open-agents/venv...");
29918
30039
  try {
29919
- execSync26(`"${venvPip}" install moondream-station`, { stdio: "pipe", timeout: 3e5 });
30040
+ execSync25(`"${venvPip}" install moondream-station`, { stdio: "pipe", timeout: 3e5 });
29920
30041
  if (existsSync29(venvMoondream)) {
29921
30042
  log("moondream-station installed successfully.");
29922
30043
  } else {
29923
30044
  try {
29924
- const check = execSync26(`"${venvPip}" show moondream-station`, { encoding: "utf8", stdio: "pipe", timeout: 5e3 });
30045
+ const check = execSync25(`"${venvPip}" show moondream-station`, { encoding: "utf8", stdio: "pipe", timeout: 5e3 });
29925
30046
  if (check.includes("moondream")) {
29926
30047
  log("moondream-station package installed.");
29927
30048
  }
@@ -29938,7 +30059,7 @@ async function ensureVisionDeps(onInfo, getSudoPassword) {
29938
30059
  const venvPip2 = join39(venvBin, "pip");
29939
30060
  let ocrStackInstalled = false;
29940
30061
  try {
29941
- execSync26(`"${venvPython}" -c "import cv2, pytesseract, numpy, PIL"`, { stdio: "pipe", timeout: 1e4 });
30062
+ execSync25(`"${venvPython}" -c "import cv2, pytesseract, numpy, PIL"`, { stdio: "pipe", timeout: 1e4 });
29942
30063
  ocrStackInstalled = true;
29943
30064
  } catch {
29944
30065
  }
@@ -29946,9 +30067,9 @@ async function ensureVisionDeps(onInfo, getSudoPassword) {
29946
30067
  const ocrPackages = "pytesseract Pillow opencv-python-headless numpy";
29947
30068
  log("Installing OCR Python stack (pytesseract, OpenCV, Pillow, numpy)...");
29948
30069
  try {
29949
- execSync26(`"${venvPip2}" install ${ocrPackages}`, { stdio: "pipe", timeout: 3e5 });
30070
+ execSync25(`"${venvPip2}" install ${ocrPackages}`, { stdio: "pipe", timeout: 3e5 });
29950
30071
  try {
29951
- execSync26(`"${venvPython}" -c "import cv2, pytesseract, numpy, PIL"`, { stdio: "pipe", timeout: 1e4 });
30072
+ execSync25(`"${venvPython}" -c "import cv2, pytesseract, numpy, PIL"`, { stdio: "pipe", timeout: 1e4 });
29952
30073
  log("OCR Python stack installed successfully.");
29953
30074
  } catch {
29954
30075
  log("OCR Python stack install completed but import verification failed.");
@@ -29982,7 +30103,7 @@ function ensureCloudflaredBackground(onInfo) {
29982
30103
  const archMap = { x64: "amd64", arm64: "arm64", arm: "arm" };
29983
30104
  const cfArch = archMap[arch] ?? "amd64";
29984
30105
  try {
29985
- execSync26(`curl -fsSL "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${cfArch}" -o /tmp/cloudflared && chmod +x /tmp/cloudflared && mkdir -p "${homedir10()}/.local/bin" && mv /tmp/cloudflared "${homedir10()}/.local/bin/cloudflared"`, { stdio: "pipe", timeout: 6e4 });
30106
+ execSync25(`curl -fsSL "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${cfArch}" -o /tmp/cloudflared && chmod +x /tmp/cloudflared && mkdir -p "${homedir10()}/.local/bin" && mv /tmp/cloudflared "${homedir10()}/.local/bin/cloudflared"`, { stdio: "pipe", timeout: 6e4 });
29986
30107
  if (!process.env.PATH?.includes(`${homedir10()}/.local/bin`)) {
29987
30108
  process.env.PATH = `${homedir10()}/.local/bin:${process.env.PATH}`;
29988
30109
  }
@@ -29993,7 +30114,7 @@ function ensureCloudflaredBackground(onInfo) {
29993
30114
  } catch {
29994
30115
  }
29995
30116
  try {
29996
- execSync26(`curl -fsSL "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${cfArch}" -o /tmp/cloudflared && chmod +x /tmp/cloudflared && sudo mv /tmp/cloudflared /usr/local/bin/cloudflared 2>/dev/null`, { stdio: "pipe", timeout: 6e4 });
30117
+ execSync25(`curl -fsSL "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${cfArch}" -o /tmp/cloudflared && chmod +x /tmp/cloudflared && sudo mv /tmp/cloudflared /usr/local/bin/cloudflared 2>/dev/null`, { stdio: "pipe", timeout: 6e4 });
29997
30118
  if (hasCmd("cloudflared")) {
29998
30119
  log("cloudflared installed.");
29999
30120
  return true;
@@ -30002,7 +30123,7 @@ function ensureCloudflaredBackground(onInfo) {
30002
30123
  }
30003
30124
  } else if (os === "darwin") {
30004
30125
  try {
30005
- execSync26("brew install cloudflared", { stdio: "pipe", timeout: 12e4 });
30126
+ execSync25("brew install cloudflared", { stdio: "pipe", timeout: 12e4 });
30006
30127
  if (hasCmd("cloudflared")) {
30007
30128
  log("cloudflared installed via Homebrew.");
30008
30129
  return true;
@@ -30052,7 +30173,7 @@ function createExpandedVariant(baseModel, specs, sizeGB) {
30052
30173
  mkdirSync10(modelDir2, { recursive: true });
30053
30174
  const modelfilePath = join39(modelDir2, `Modelfile.${customName}`);
30054
30175
  writeFileSync10(modelfilePath, modelfileContent + "\n", "utf8");
30055
- execSync26(`ollama create ${customName} -f ${modelfilePath}`, {
30176
+ execSync25(`ollama create ${customName} -f ${modelfilePath}`, {
30056
30177
  stdio: "pipe",
30057
30178
  timeout: 12e4
30058
30179
  });
@@ -31802,10 +31923,38 @@ async function handleEndpoint(arg, ctx, local = false) {
31802
31923
  process.stdout.write(` ${c2.dim(" /endpoint https://api.together.xyz --auth ... Together AI")}
31803
31924
  `);
31804
31925
  process.stdout.write(` ${c2.dim(" /endpoint http://localhost:8000 vLLM")}
31926
+ `);
31927
+ process.stdout.write(` ${c2.dim(" /endpoint stop Reset to local Ollama")}
31805
31928
 
31806
31929
  `);
31807
31930
  return;
31808
31931
  }
31932
+ if (arg === "stop" || arg === "off" || arg.startsWith("stop ") || arg.startsWith("off ")) {
31933
+ if (ctx.isExposeActive?.()) {
31934
+ await ctx.exposeStop?.();
31935
+ renderInfo("Expose gateway stopped.");
31936
+ }
31937
+ if (ctx.hasActiveTask?.()) {
31938
+ ctx.abortActiveTask?.();
31939
+ }
31940
+ const defaultUrl = "http://127.0.0.1:11434";
31941
+ ctx.setEndpoint(defaultUrl, "ollama", void 0);
31942
+ const defaultSettings = { backendUrl: defaultUrl, backendType: "ollama", apiKey: void 0 };
31943
+ if (local) {
31944
+ ctx.saveLocalSettings(defaultSettings);
31945
+ } else {
31946
+ setConfigValue("backendUrl", defaultUrl);
31947
+ setConfigValue("backendType", "ollama");
31948
+ ctx.saveSettings(defaultSettings);
31949
+ }
31950
+ process.stdout.write(`
31951
+ ${c2.green("\u2714")} Endpoint reset to local Ollama (${defaultUrl})
31952
+ `);
31953
+ if (ctx.hasActiveTask?.()) {
31954
+ renderWarning("Active task aborted \u2014 send a new prompt to continue.");
31955
+ }
31956
+ return;
31957
+ }
31809
31958
  const parts = arg.split(/\s+/);
31810
31959
  const url = parts[0];
31811
31960
  let apiKey;
@@ -31906,6 +32055,10 @@ async function handleEndpoint(arg, ctx, local = false) {
31906
32055
  `);
31907
32056
  }
31908
32057
  process.stdout.write("\n");
32058
+ if (ctx.hasActiveTask?.()) {
32059
+ ctx.abortActiveTask?.();
32060
+ renderWarning("Active task aborted \u2014 endpoint changed. Send a new prompt to continue.");
32061
+ }
31909
32062
  try {
31910
32063
  const newModels = await fetchModels(normalizedUrl, apiKey);
31911
32064
  if (newModels.length > 0) {
@@ -31998,7 +32151,7 @@ async function handleUpdate(subcommand, ctx) {
31998
32151
  return;
31999
32152
  }
32000
32153
  checkSpinner.stop(`Update available: v${info.currentVersion} \u2192 v${c2.bold(c2.green(info.latestVersion))}`);
32001
- const { exec, execSync: es2 } = await import("node:child_process");
32154
+ const { exec: exec2, execSync: es2 } = await import("node:child_process");
32002
32155
  let needsSudo = false;
32003
32156
  try {
32004
32157
  const prefix = es2("npm prefix -g", { encoding: "utf8", timeout: 5e3 }).trim();
@@ -32027,7 +32180,7 @@ async function handleUpdate(subcommand, ctx) {
32027
32180
  let installOk = false;
32028
32181
  let installError = "";
32029
32182
  const runInstall2 = (cmd) => new Promise((resolve31) => {
32030
- const child = exec(cmd, { timeout: 18e4 }, (err, _stdout, stderr) => {
32183
+ const child = exec2(cmd, { timeout: 18e4 }, (err, _stdout, stderr) => {
32031
32184
  if (err)
32032
32185
  installError = (stderr || err.message || "").trim();
32033
32186
  resolve31(!err);
@@ -32065,7 +32218,7 @@ async function handleUpdate(subcommand, ctx) {
32065
32218
  installSpinner.stop(`Update installed (v${info.latestVersion}).`);
32066
32219
  const rebuildSpinner = startInlineSpinner("Rebuilding native modules");
32067
32220
  const rebuildOk = await new Promise((resolve31) => {
32068
- const child = exec(`${sudoPrefix}npm rebuild -g open-agents-ai 2>/dev/null || true`, { timeout: 12e4 }, () => resolve31(true));
32221
+ const child = exec2(`${sudoPrefix}npm rebuild -g open-agents-ai 2>/dev/null || true`, { timeout: 12e4 }, () => resolve31(true));
32069
32222
  child.stdout?.resume();
32070
32223
  child.stderr?.resume();
32071
32224
  });
@@ -32098,7 +32251,7 @@ async function handleUpdate(subcommand, ctx) {
32098
32251
  if (fsExists(venvPip)) {
32099
32252
  const pySpinner = startInlineSpinner("Upgrading Python venv packages");
32100
32253
  const pyOk = await new Promise((resolve31) => {
32101
- const child = exec(`"${venvPip}" install --upgrade moondream-station pytesseract Pillow opencv-python-headless numpy 2>/dev/null || true`, { timeout: 3e5 }, (err) => resolve31(!err));
32254
+ const child = exec2(`"${venvPip}" install --upgrade moondream-station pytesseract Pillow opencv-python-headless numpy 2>/dev/null || true`, { timeout: 3e5 }, (err) => resolve31(!err));
32102
32255
  child.stdout?.resume();
32103
32256
  child.stderr?.resume();
32104
32257
  });
@@ -32249,7 +32402,7 @@ var init_commands = __esm({
32249
32402
  // packages/cli/dist/tui/project-context.js
32250
32403
  import { existsSync as existsSync30, readFileSync as readFileSync21, readdirSync as readdirSync8 } from "node:fs";
32251
32404
  import { join as join40, basename as basename10 } from "node:path";
32252
- import { execSync as execSync27 } from "node:child_process";
32405
+ import { execSync as execSync26 } from "node:child_process";
32253
32406
  import { homedir as homedir11, platform as platform2, release } from "node:os";
32254
32407
  function getModelTier(modelName) {
32255
32408
  const m = modelName.toLowerCase();
@@ -32295,19 +32448,19 @@ function loadProjectMap(repoRoot) {
32295
32448
  }
32296
32449
  function getGitInfo(repoRoot) {
32297
32450
  try {
32298
- execSync27("git rev-parse --is-inside-work-tree", { cwd: repoRoot, stdio: "pipe" });
32451
+ execSync26("git rev-parse --is-inside-work-tree", { cwd: repoRoot, stdio: "pipe" });
32299
32452
  } catch {
32300
32453
  return "";
32301
32454
  }
32302
32455
  const lines = [];
32303
32456
  try {
32304
- const branch = execSync27("git branch --show-current", { cwd: repoRoot, encoding: "utf-8", stdio: "pipe" }).trim();
32457
+ const branch = execSync26("git branch --show-current", { cwd: repoRoot, encoding: "utf-8", stdio: "pipe" }).trim();
32305
32458
  if (branch)
32306
32459
  lines.push(`Branch: ${branch}`);
32307
32460
  } catch {
32308
32461
  }
32309
32462
  try {
32310
- const status = execSync27("git status --porcelain", { cwd: repoRoot, encoding: "utf-8", stdio: "pipe" }).trim();
32463
+ const status = execSync26("git status --porcelain", { cwd: repoRoot, encoding: "utf-8", stdio: "pipe" }).trim();
32311
32464
  if (status) {
32312
32465
  const changed = status.split("\n").length;
32313
32466
  lines.push(`Working tree: ${changed} changed file(s)`);
@@ -32317,7 +32470,7 @@ function getGitInfo(repoRoot) {
32317
32470
  } catch {
32318
32471
  }
32319
32472
  try {
32320
- const log = execSync27("git log --oneline -5 --no-decorate", { cwd: repoRoot, encoding: "utf-8", stdio: "pipe" }).trim();
32473
+ const log = execSync26("git log --oneline -5 --no-decorate", { cwd: repoRoot, encoding: "utf-8", stdio: "pipe" }).trim();
32321
32474
  if (log)
32322
32475
  lines.push(`Recent commits:
32323
32476
  ${log}`);
@@ -33734,7 +33887,7 @@ var init_carousel_descriptors = __esm({
33734
33887
  import { existsSync as existsSync32, mkdirSync as mkdirSync12, writeFileSync as writeFileSync12, readFileSync as readFileSync23, unlinkSync as unlinkSync4 } from "node:fs";
33735
33888
  import { join as join42 } from "node:path";
33736
33889
  import { homedir as homedir12, tmpdir as tmpdir6, platform as platform3 } from "node:os";
33737
- import { execSync as execSync28, spawn as nodeSpawn } from "node:child_process";
33890
+ import { execSync as execSync27, spawn as nodeSpawn } from "node:child_process";
33738
33891
  import { createRequire } from "node:module";
33739
33892
  function voiceDir() {
33740
33893
  return join42(homedir12(), ".open-agents", "voice");
@@ -34791,7 +34944,7 @@ var init_voice = __esm({
34791
34944
  }
34792
34945
  for (const player of ["paplay", "pw-play", "aplay"]) {
34793
34946
  try {
34794
- execSync28(`which ${player}`, { stdio: "pipe" });
34947
+ execSync27(`which ${player}`, { stdio: "pipe" });
34795
34948
  return [player, path];
34796
34949
  } catch {
34797
34950
  }
@@ -34820,7 +34973,7 @@ var init_voice = __esm({
34820
34973
  return this.python3Path;
34821
34974
  for (const bin of ["python3", "python"]) {
34822
34975
  try {
34823
- const path = execSync28(`which ${bin}`, { stdio: "pipe", timeout: 5e3 }).toString().trim();
34976
+ const path = execSync27(`which ${bin}`, { stdio: "pipe", timeout: 5e3 }).toString().trim();
34824
34977
  if (path) {
34825
34978
  this.python3Path = path;
34826
34979
  return path;
@@ -34840,7 +34993,7 @@ var init_voice = __esm({
34840
34993
  return false;
34841
34994
  }
34842
34995
  try {
34843
- execSync28(`${py} -c "import mlx_audio"`, { stdio: "pipe", timeout: 1e4 });
34996
+ execSync27(`${py} -c "import mlx_audio"`, { stdio: "pipe", timeout: 1e4 });
34844
34997
  this.mlxInstalled = true;
34845
34998
  return true;
34846
34999
  } catch {
@@ -34864,7 +35017,7 @@ var init_voice = __esm({
34864
35017
  return;
34865
35018
  renderInfo("Installing MLX Audio for voice synthesis (first time setup)...");
34866
35019
  try {
34867
- execSync28(`${py} -m pip install mlx-audio --quiet`, {
35020
+ execSync27(`${py} -m pip install mlx-audio --quiet`, {
34868
35021
  stdio: "pipe",
34869
35022
  timeout: 3e5
34870
35023
  // 5 min — may need to compile
@@ -34872,7 +35025,7 @@ var init_voice = __esm({
34872
35025
  this.mlxInstalled = true;
34873
35026
  } catch (err) {
34874
35027
  try {
34875
- execSync28(`${py} -m pip install mlx-audio --user --quiet`, {
35028
+ execSync27(`${py} -m pip install mlx-audio --user --quiet`, {
34876
35029
  stdio: "pipe",
34877
35030
  timeout: 3e5
34878
35031
  });
@@ -34908,11 +35061,11 @@ Error: ${err2 instanceof Error ? err2.message : String(err2)}`);
34908
35061
  `tts_gen.main(["--model", ${JSON.stringify(mlxModelId)}, "--text", text, "--voice", ${JSON.stringify(mlxVoice)}, "--lang_code", ${JSON.stringify(mlxLangCode)}, "--audio_path", ${JSON.stringify(wavPath)}])`
34909
35062
  ].join("; ");
34910
35063
  try {
34911
- execSync28(`${py} -c ${JSON.stringify(pyScript)} ${JSON.stringify(JSON.stringify(cleaned))}`, { stdio: "pipe", timeout: 6e4, cwd: tmpdir6() });
35064
+ execSync27(`${py} -c ${JSON.stringify(pyScript)} ${JSON.stringify(JSON.stringify(cleaned))}`, { stdio: "pipe", timeout: 6e4, cwd: tmpdir6() });
34912
35065
  } catch (err) {
34913
35066
  try {
34914
35067
  const safeText = cleaned.replace(/'/g, "'\\''");
34915
- execSync28(`${py} -m mlx_audio.tts.generate --model ${mlxModelId} --text '${safeText}' --voice ${mlxVoice} --lang_code ${mlxLangCode} --audio_path ${JSON.stringify(wavPath)}`, { stdio: "pipe", timeout: 6e4, cwd: tmpdir6() });
35068
+ execSync27(`${py} -m mlx_audio.tts.generate --model ${mlxModelId} --text '${safeText}' --voice ${mlxVoice} --lang_code ${mlxLangCode} --audio_path ${JSON.stringify(wavPath)}`, { stdio: "pipe", timeout: 6e4, cwd: tmpdir6() });
34916
35069
  } catch (err2) {
34917
35070
  return;
34918
35071
  }
@@ -34976,11 +35129,11 @@ Error: ${err2 instanceof Error ? err2.message : String(err2)}`);
34976
35129
  `tts_gen.main(["--model", ${JSON.stringify(mlxModelId)}, "--text", text, "--voice", ${JSON.stringify(mlxVoice)}, "--lang_code", ${JSON.stringify(mlxLangCode)}, "--audio_path", ${JSON.stringify(wavPath)}])`
34977
35130
  ].join("; ");
34978
35131
  try {
34979
- execSync28(`${py} -c ${JSON.stringify(pyScript)} ${JSON.stringify(JSON.stringify(cleaned))}`, { stdio: "pipe", timeout: 6e4, cwd: tmpdir6() });
35132
+ execSync27(`${py} -c ${JSON.stringify(pyScript)} ${JSON.stringify(JSON.stringify(cleaned))}`, { stdio: "pipe", timeout: 6e4, cwd: tmpdir6() });
34980
35133
  } catch {
34981
35134
  try {
34982
35135
  const safeText = cleaned.replace(/'/g, "'\\''");
34983
- execSync28(`${py} -m mlx_audio.tts.generate --model ${mlxModelId} --text '${safeText}' --voice ${mlxVoice} --lang_code ${mlxLangCode} --audio_path ${JSON.stringify(wavPath)}`, { stdio: "pipe", timeout: 6e4, cwd: tmpdir6() });
35136
+ execSync27(`${py} -m mlx_audio.tts.generate --model ${mlxModelId} --text '${safeText}' --voice ${mlxVoice} --lang_code ${mlxLangCode} --audio_path ${JSON.stringify(wavPath)}`, { stdio: "pipe", timeout: 6e4, cwd: tmpdir6() });
34984
35137
  } catch {
34985
35138
  return null;
34986
35139
  }
@@ -35028,7 +35181,7 @@ Error: ${err2 instanceof Error ? err2.message : String(err2)}`);
35028
35181
  const voiceRequire = createRequire(join42(voiceDir(), "index.js"));
35029
35182
  const probeOnnx = () => {
35030
35183
  try {
35031
- const result = execSync28(`node -e "try { require('onnxruntime-node'); console.log('OK'); } catch(e) { console.log('FAIL:' + e.message); }"`, { cwd: voiceDir(), stdio: "pipe", timeout: 15e3, env: { ...process.env, NODE_PATH: join42(voiceDir(), "node_modules") } });
35184
+ const result = execSync27(`node -e "try { require('onnxruntime-node'); console.log('OK'); } catch(e) { console.log('FAIL:' + e.message); }"`, { cwd: voiceDir(), stdio: "pipe", timeout: 15e3, env: { ...process.env, NODE_PATH: join42(voiceDir(), "node_modules") } });
35032
35185
  const output = result.toString().trim();
35033
35186
  return output === "OK";
35034
35187
  } catch {
@@ -35045,7 +35198,7 @@ Error: ${err2 instanceof Error ? err2.message : String(err2)}`);
35045
35198
  } catch {
35046
35199
  renderInfo("Installing ONNX runtime for voice synthesis...");
35047
35200
  try {
35048
- execSync28("npm install --no-audit --no-fund", {
35201
+ execSync27("npm install --no-audit --no-fund", {
35049
35202
  cwd: voiceDir(),
35050
35203
  stdio: "pipe",
35051
35204
  timeout: 12e4
@@ -35070,7 +35223,7 @@ Error: ${err instanceof Error ? err.message : String(err)}`);
35070
35223
  } catch {
35071
35224
  renderInfo("Installing phonemizer for voice synthesis...");
35072
35225
  try {
35073
- execSync28("npm install --no-audit --no-fund", {
35226
+ execSync27("npm install --no-audit --no-fund", {
35074
35227
  cwd: voiceDir(),
35075
35228
  stdio: "pipe",
35076
35229
  timeout: 12e4
@@ -36154,7 +36307,7 @@ var init_promptLoader3 = __esm({
36154
36307
  // packages/cli/dist/tui/dream-engine.js
36155
36308
  import { mkdirSync as mkdirSync14, writeFileSync as writeFileSync13, readFileSync as readFileSync25, existsSync as existsSync34, cpSync, rmSync, readdirSync as readdirSync10 } from "node:fs";
36156
36309
  import { join as join45, basename as basename12 } from "node:path";
36157
- import { execSync as execSync29 } from "node:child_process";
36310
+ import { execSync as execSync28 } from "node:child_process";
36158
36311
  function loadAutoresearchMemory(repoRoot) {
36159
36312
  const memoryPath = join45(repoRoot, ".oa", "memory", "autoresearch.json");
36160
36313
  if (!existsSync34(memoryPath))
@@ -36518,7 +36671,7 @@ var init_dream_engine = __esm({
36518
36671
  }
36519
36672
  }
36520
36673
  try {
36521
- const output = execSync29(cmd, {
36674
+ const output = execSync28(cmd, {
36522
36675
  cwd: this.repoRoot,
36523
36676
  timeout: 3e4,
36524
36677
  encoding: "utf-8",
@@ -37295,17 +37448,17 @@ ${summaryResult}
37295
37448
  try {
37296
37449
  mkdirSync14(checkpointDir, { recursive: true });
37297
37450
  try {
37298
- const gitStatus = execSync29("git status --porcelain", {
37451
+ const gitStatus = execSync28("git status --porcelain", {
37299
37452
  cwd: this.repoRoot,
37300
37453
  encoding: "utf-8",
37301
37454
  timeout: 1e4
37302
37455
  });
37303
- const gitDiff = execSync29("git diff", {
37456
+ const gitDiff = execSync28("git diff", {
37304
37457
  cwd: this.repoRoot,
37305
37458
  encoding: "utf-8",
37306
37459
  timeout: 1e4
37307
37460
  });
37308
- const gitHash = execSync29("git rev-parse HEAD 2>/dev/null || echo 'no-git'", {
37461
+ const gitHash = execSync28("git rev-parse HEAD 2>/dev/null || echo 'no-git'", {
37309
37462
  cwd: this.repoRoot,
37310
37463
  encoding: "utf-8",
37311
37464
  timeout: 5e3
@@ -44000,6 +44153,11 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
44000
44153
  },
44001
44154
  refreshModelCache,
44002
44155
  hasActiveTask: () => activeTask !== null,
44156
+ abortActiveTask() {
44157
+ if (!activeTask)
44158
+ return;
44159
+ activeTask.runner.abort();
44160
+ },
44003
44161
  requestCompaction(strategy) {
44004
44162
  if (!activeTask)
44005
44163
  return false;
@@ -44635,8 +44793,8 @@ NEW TASK: ${fullInput}`;
44635
44793
  try {
44636
44794
  const updateInfo = await checkForUpdate(version);
44637
44795
  if (updateInfo) {
44638
- const { exec } = await import("node:child_process");
44639
- exec(`npm install -g open-agents-ai@latest --prefer-online`, { timeout: 18e4 }, (err) => {
44796
+ const { exec: exec2 } = await import("node:child_process");
44797
+ exec2(`npm install -g open-agents-ai@latest --prefer-online`, { timeout: 18e4 }, (err) => {
44640
44798
  if (!err) {
44641
44799
  writeContent(() => renderInfo(`Updated to v${updateInfo.latestVersion} in background. Takes effect next session.`));
44642
44800
  }
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.11",
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.