open-agents-ai 0.103.43 → 0.103.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +128 -19
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -12601,6 +12601,18 @@ async function handleCmd(cmd) {
12601
12601
 
12602
12602
  // \u2500\u2500 Metered Inference Exposure \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
12603
12603
  case 'expose': {
12604
+ // Clear old inference capabilities before registering new ones
12605
+ // (prevents stale local Ollama models when switching to passthrough, or vice versa)
12606
+ if (typeof nexus.getRegisteredCapabilities === 'function' && typeof nexus.unregisterCapability === 'function') {
12607
+ var oldCaps = nexus.getRegisteredCapabilities();
12608
+ for (var oci = 0; oci < oldCaps.length; oci++) {
12609
+ if (oldCaps[oci].startsWith('inference:') || oldCaps[oci] === 'system_metrics' || oldCaps[oci] === '__list_capabilities') {
12610
+ try { nexus.unregisterCapability(oldCaps[oci]); } catch {}
12611
+ }
12612
+ }
12613
+ dlog('expose: cleared ' + oldCaps.length + ' old capabilities');
12614
+ }
12615
+
12604
12616
  // Auth key for gating inference access (passed from ExposeP2PGateway)
12605
12617
  var exposeAuthKey = args.auth_key || '';
12606
12618
  if (exposeAuthKey) {
@@ -12754,6 +12766,16 @@ async function handleCmd(cmd) {
12754
12766
  const logFile = join(invocationsDir, Date.now() + '-' + capName + '.json');
12755
12767
  try { writeFileSync(logFile, JSON.stringify(logEntry, null, 2)); } catch {}
12756
12768
 
12769
+ // Safe stream write \u2014 consumer may have timed out and closed the stream
12770
+ var streamClosed = false;
12771
+ async function swrite(msg) {
12772
+ if (streamClosed) return;
12773
+ try { await stream.write(msg); } catch (swErr) {
12774
+ dlog('stream.write failed (consumer likely timed out): ' + (swErr.message || swErr));
12775
+ streamClosed = true;
12776
+ }
12777
+ }
12778
+
12757
12779
  // Collect input via stream data events
12758
12780
  // NOTE: auth_key arrives in invoke.chunk data, NOT in invoke.open (request).
12759
12781
  // We must accept + read data FIRST, then validate auth.
@@ -12771,7 +12793,7 @@ async function handleCmd(cmd) {
12771
12793
  });
12772
12794
 
12773
12795
  // Accept the invocation so the consumer sends data
12774
- await stream.write({
12796
+ await swrite({
12775
12797
  type: 'invoke.accept', version: 1,
12776
12798
  requestId: request.requestId, accepted: true,
12777
12799
  });
@@ -12802,9 +12824,9 @@ async function handleCmd(cmd) {
12802
12824
  }
12803
12825
  if (reqAuthKey !== exposeAuthKey) {
12804
12826
  dlog('expose: auth REJECTED for ' + capName + ' from ' + (request.from || 'unknown'));
12805
- await stream.write({ type: 'invoke.event', version: 1, requestId: request.requestId, seq: 0, event: 'error', data: 'Unauthorized \u2014 invalid or missing auth key' });
12806
- await stream.write({ type: 'invoke.done', version: 1, requestId: request.requestId, usage: { inputBytes: 0, outputBytes: 0 } });
12807
- stream.close();
12827
+ await swrite({ type: 'invoke.event', version: 1, requestId: request.requestId, seq: 0, event: 'error', data: 'Unauthorized \u2014 invalid or missing auth key' });
12828
+ await swrite({ type: 'invoke.done', version: 1, requestId: request.requestId, usage: { inputBytes: 0, outputBytes: 0 } });
12829
+ try { stream.close(); } catch {}
12808
12830
  return;
12809
12831
  }
12810
12832
  dlog('expose: auth OK for ' + capName);
@@ -12923,14 +12945,14 @@ async function handleCmd(cmd) {
12923
12945
  }
12924
12946
 
12925
12947
  // Stream result back
12926
- await stream.write({
12948
+ await swrite({
12927
12949
  type: 'invoke.event', version: 1,
12928
12950
  requestId: request.requestId, seq: 0,
12929
12951
  event: 'result',
12930
12952
  data: JSON.stringify(responsePayload),
12931
12953
  });
12932
12954
 
12933
- await stream.write({
12955
+ await swrite({
12934
12956
  type: 'invoke.done', version: 1,
12935
12957
  requestId: request.requestId,
12936
12958
  usage: {
@@ -12940,18 +12962,19 @@ async function handleCmd(cmd) {
12940
12962
  },
12941
12963
  });
12942
12964
  } catch (e) {
12943
- await stream.write({
12965
+ dlog('expose: inference error: ' + (e.message || e));
12966
+ await swrite({
12944
12967
  type: 'invoke.event', version: 1,
12945
12968
  requestId: request.requestId, seq: 0,
12946
12969
  event: 'error', data: 'Inference failed: ' + e.message,
12947
12970
  });
12948
- await stream.write({
12971
+ await swrite({
12949
12972
  type: 'invoke.done', version: 1,
12950
12973
  requestId: request.requestId,
12951
12974
  usage: { inputBytes: 0, outputBytes: 0 },
12952
12975
  });
12953
12976
  }
12954
- stream.close();
12977
+ try { stream.close(); } catch {}
12955
12978
  }, capOpts);
12956
12979
  }
12957
12980
  }
@@ -13350,7 +13373,7 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
13350
13373
  },
13351
13374
  margin: {
13352
13375
  type: "string",
13353
- description: "Price margin multiplier for expose (0.5 = 50% of market rate, 0 = free). Default: 0.5"
13376
+ description: "Price margin multiplier for expose (0.5 = 50% of market rate, 0 = free). Default: 0 (free)"
13354
13377
  },
13355
13378
  daily_limit: {
13356
13379
  type: "string",
@@ -13618,23 +13641,57 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
13618
13641
  // =========================================================================
13619
13642
  async doConnect(args) {
13620
13643
  await this.ensureDir();
13644
+ const currentScriptHash = createHash("sha256").update(DAEMON_SCRIPT).digest("hex").slice(0, 16);
13621
13645
  const existingPid = this.getDaemonPid();
13622
13646
  if (existingPid) {
13623
- const statusFile2 = join30(this.nexusDir, "status.json");
13624
- if (existsSync23(statusFile2)) {
13647
+ const daemonPath2 = join30(this.nexusDir, "nexus-daemon.mjs");
13648
+ let needsRestart = false;
13649
+ if (existsSync23(daemonPath2)) {
13625
13650
  try {
13626
- const status = JSON.parse(await readFile13(statusFile2, "utf8"));
13627
- if (status.connected && status.peerId) {
13628
- return `Already connected (pid: ${existingPid}, peerId: ${status.peerId})`;
13651
+ const onDisk = readFileSync16(daemonPath2, "utf8");
13652
+ const diskHash = createHash("sha256").update(onDisk).digest("hex").slice(0, 16);
13653
+ if (diskHash !== currentScriptHash) {
13654
+ needsRestart = true;
13629
13655
  }
13630
13656
  } catch {
13657
+ needsRestart = true;
13631
13658
  }
13632
13659
  }
13633
- try {
13634
- process.kill(existingPid, "SIGTERM");
13635
- } catch {
13660
+ if (needsRestart) {
13661
+ try {
13662
+ process.kill(existingPid, "SIGTERM");
13663
+ } catch {
13664
+ }
13665
+ await new Promise((r) => setTimeout(r, 1e3));
13666
+ try {
13667
+ process.kill(existingPid, 0);
13668
+ process.kill(existingPid, "SIGKILL");
13669
+ } catch {
13670
+ }
13671
+ for (const f of ["daemon.pid", "status.json", "cmd.json", "resp.json"]) {
13672
+ const p = join30(this.nexusDir, f);
13673
+ if (existsSync23(p))
13674
+ await unlink(p).catch(() => {
13675
+ });
13676
+ }
13677
+ } else {
13678
+ const statusFile2 = join30(this.nexusDir, "status.json");
13679
+ if (existsSync23(statusFile2)) {
13680
+ try {
13681
+ const status = JSON.parse(await readFile13(statusFile2, "utf8"));
13682
+ if (status.connected && status.peerId) {
13683
+ await this.ensureWallet();
13684
+ return `Already connected (pid: ${existingPid}, peerId: ${status.peerId})`;
13685
+ }
13686
+ } catch {
13687
+ }
13688
+ }
13689
+ try {
13690
+ process.kill(existingPid, "SIGTERM");
13691
+ } catch {
13692
+ }
13693
+ await new Promise((r) => setTimeout(r, 500));
13636
13694
  }
13637
- await new Promise((r) => setTimeout(r, 500));
13638
13695
  }
13639
13696
  for (const f of ["daemon.pid", "status.json", "cmd.json", "resp.json"]) {
13640
13697
  const p = join30(this.nexusDir, f);
@@ -13703,6 +13760,7 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
13703
13760
  }
13704
13761
  }
13705
13762
  }
13763
+ await this.ensureWallet();
13706
13764
  const daemonPath = join30(this.nexusDir, "nexus-daemon.mjs");
13707
13765
  await writeFile12(daemonPath, DAEMON_SCRIPT);
13708
13766
  const agentName = args.agent_name || "open-agents-node";
@@ -14019,6 +14077,57 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
14019
14077
  `Free inference (margin=0) works without funding.`
14020
14078
  ].join("\n");
14021
14079
  }
14080
+ /**
14081
+ * Auto-create wallet + x402-wallet.key if none exists.
14082
+ * Called from doConnect() before daemon spawn so the daemon starts x402-ready.
14083
+ * Silent — no user output, just ensures the files exist.
14084
+ */
14085
+ async ensureWallet() {
14086
+ const walletPath = join30(this.nexusDir, "wallet.enc");
14087
+ if (existsSync23(walletPath))
14088
+ return;
14089
+ let address;
14090
+ let privKeyHex;
14091
+ try {
14092
+ const { privateKeyToAccount } = await import("viem/accounts");
14093
+ const privKey = randomBytes6(32);
14094
+ privKeyHex = privKey.toString("hex");
14095
+ const account = privateKeyToAccount(`0x${privKeyHex}`);
14096
+ address = account.address;
14097
+ privKey.fill(0);
14098
+ } catch {
14099
+ const privKey = randomBytes6(32);
14100
+ privKeyHex = privKey.toString("hex");
14101
+ address = "0x" + createHash("sha256").update(privKey).digest("hex").slice(0, 40);
14102
+ privKey.fill(0);
14103
+ }
14104
+ const salt = randomBytes6(32);
14105
+ const key = scryptSync(`${hostname()}:${userInfo().username}:nexus-wallet`, salt, 32, { N: 16384, r: 8, p: 1 });
14106
+ const iv = randomBytes6(16);
14107
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
14108
+ let enc = cipher.update(privKeyHex, "utf8", "hex");
14109
+ enc += cipher.final("hex");
14110
+ const x402KeyPath = join30(this.nexusDir, "x402-wallet.key");
14111
+ const x402Fh = await fsOpen(x402KeyPath, "w", 384);
14112
+ await x402Fh.writeFile("0x" + privKeyHex);
14113
+ await x402Fh.close();
14114
+ privKeyHex = "0".repeat(64);
14115
+ const w = {
14116
+ version: 2,
14117
+ address,
14118
+ encryptedKey: enc,
14119
+ iv: iv.toString("hex"),
14120
+ authTag: cipher.getAuthTag().toString("hex"),
14121
+ salt: salt.toString("hex"),
14122
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
14123
+ network: "base",
14124
+ chainId: 8453
14125
+ };
14126
+ const walletFh = await fsOpen(walletPath, "w", 384);
14127
+ await walletFh.writeFile(JSON.stringify(w, null, 2));
14128
+ await walletFh.close();
14129
+ await this.ensureDefaultBudget();
14130
+ }
14022
14131
  // ---------------------------------------------------------------------------
14023
14132
  // Step 2: USDC Balance Query via Base RPC
14024
14133
  // ---------------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.103.43",
3
+ "version": "0.103.45",
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",