open-agents-ai 0.85.0 → 0.87.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/README.md +58 -2
- package/dist/index.js +570 -27
- package/package.json +8 -3
- package/prompts/agentic/system-large.md +37 -4
package/README.md
CHANGED
|
@@ -66,7 +66,7 @@ D8AgCTrxpDKD5meJ2bpAfVwcST3NF3EPuy9xczYycnXn
|
|
|
66
66
|
- **OpenCode delegation** — offload coding tasks to opencode (sst/opencode) as an autonomous sub-agent with auto-install, progress monitoring, and result evaluation
|
|
67
67
|
- **Long-horizon cron agents** — schedule recurring autonomous agent tasks with goals, completion criteria, execution history, and automatic evaluation (daily code reviews, weekly dep updates, continuous monitoring)
|
|
68
68
|
- **Nexus P2P networking** — decentralized agent-to-agent communication via [open-agents-nexus](https://www.npmjs.com/package/open-agents-nexus). Join rooms, discover peers, share resources, and communicate across the agent mesh with encrypted P2P transport
|
|
69
|
-
- **x402 micropayments** —
|
|
69
|
+
- **x402 micropayments** — native x402 payment rails via open-agents-nexus@1.5.6. Agents create secp256k1/EVM wallets (AES-256-GCM encrypted, keys never exposed to LLM), register inference with USDC pricing on Base, auto-handle `payment_required`/`payment_proof` negotiation, track earnings/spending in ledger.jsonl, enforce budget policies, and sign gasless EIP-3009 transfers
|
|
70
70
|
- **Inference capability proof** — benchmark local models with anti-spoofing SHA-256 hashed proofs, generate capability scorecards for peer verification
|
|
71
71
|
- **Ralph Loop** — iterative task execution that keeps retrying until completion criteria are met
|
|
72
72
|
- **Dream Mode** — creative idle exploration modeled after real sleep architecture (NREM→REM cycles)
|
|
@@ -926,7 +926,7 @@ The steering sub-agent uses the same model and backend as the main agent with `m
|
|
|
926
926
|
- **LATS** (Zhou et al., 2024) — mid-execution replanning with user-provided value signals improves task completion on complex multi-step problems
|
|
927
927
|
- **AutoGen** (Wu et al., 2023) — human-in-the-loop patterns work best when user messages are expanded into structured instructions, reducing ambiguity for the primary agent
|
|
928
928
|
|
|
929
|
-
## Tools (
|
|
929
|
+
## Tools (54)
|
|
930
930
|
|
|
931
931
|
| Tool | Description |
|
|
932
932
|
|------|-------------|
|
|
@@ -994,6 +994,13 @@ The steering sub-agent uses the same model and backend as the main agent with `m
|
|
|
994
994
|
| `aiwg_setup` | Deploy AIWG SDLC framework |
|
|
995
995
|
| `aiwg_health` | Analyze project SDLC health and readiness |
|
|
996
996
|
| `aiwg_workflow` | Execute AIWG commands and workflows |
|
|
997
|
+
| **Nexus P2P & x402 Payments** | |
|
|
998
|
+
| `nexus` | Decentralized agent networking — connect, rooms, DMs, peer discovery, invoke capabilities, metering, trust/blocking, IPFS storage |
|
|
999
|
+
| `nexus:expose` | Expose local Ollama models as metered inference capabilities with OpenRouter-based pricing |
|
|
1000
|
+
| `nexus:wallet_create` | Generate secp256k1/EVM wallet (Base mainnet USDC) with AES-256-GCM encryption + x402-wallet.key |
|
|
1001
|
+
| `nexus:spend` | Sign EIP-3009 USDC TransferWithAuthorization — budget-checked, gasless for payer |
|
|
1002
|
+
| `nexus:ledger_status` | Transaction history (earned/spent/pending USDC) |
|
|
1003
|
+
| `nexus:budget_set` | Configure spending limits — daily cap, per-invoke max, auto-approve threshold |
|
|
997
1004
|
|
|
998
1005
|
Read-only tools execute concurrently when called in the same turn. Mutating tools run sequentially.
|
|
999
1006
|
|
|
@@ -1045,6 +1052,55 @@ All context-dependent values scale automatically with the actual context window
|
|
|
1045
1052
|
| Tool output cap | 2K-8K chars (scales with context) |
|
|
1046
1053
|
| File read limits | 80-120 line cap for small/medium context windows |
|
|
1047
1054
|
|
|
1055
|
+
## x402 Payment Rails & Nexus P2P
|
|
1056
|
+
|
|
1057
|
+
Agents can earn and spend USDC on Base mainnet through the native x402 protocol built into [open-agents-nexus@1.5.6](https://www.npmjs.com/package/open-agents-nexus).
|
|
1058
|
+
|
|
1059
|
+
### Wallet & Identity
|
|
1060
|
+
```
|
|
1061
|
+
nexus(action='wallet_create') # Generate secp256k1/EVM wallet
|
|
1062
|
+
nexus(action='wallet_status') # Address, balance, ledger summary
|
|
1063
|
+
```
|
|
1064
|
+
Creates `wallet.enc` (AES-256-GCM encrypted) and `x402-wallet.key` (plaintext, 0600 perms for daemon x402 module). Keys never enter LLM context.
|
|
1065
|
+
|
|
1066
|
+
### Expose Inference with Pricing
|
|
1067
|
+
```
|
|
1068
|
+
nexus(action='expose', margin='0.5') # 50% of OpenRouter market rate
|
|
1069
|
+
nexus(action='expose', margin='0') # Free (self-hosted)
|
|
1070
|
+
nexus(action='pricing_menu') # Current pricing for exposed models
|
|
1071
|
+
```
|
|
1072
|
+
When margin > 0, capabilities are registered with USDC pricing metadata. The daemon auto-handles `invoke.payment_required` → `payment_proof` negotiation via x402.
|
|
1073
|
+
|
|
1074
|
+
### Spend — Gasless USDC Transfers (EIP-3009)
|
|
1075
|
+
```
|
|
1076
|
+
nexus(action='spend', target_address='0x...', amount_usdc='0.10')
|
|
1077
|
+
```
|
|
1078
|
+
Signs an EIP-3009 `TransferWithAuthorization`. Budget-checked before signing. The recipient (or any facilitator) submits on-chain — no gas needed from the payer. Proof saved to `.oa/nexus/pending-transfer.json`.
|
|
1079
|
+
|
|
1080
|
+
### Ledger & Budget
|
|
1081
|
+
```
|
|
1082
|
+
nexus(action='ledger_status') # Earned/spent/pending history
|
|
1083
|
+
nexus(action='budget_status') # Limits and today's usage
|
|
1084
|
+
nexus(action='budget_set', daily_limit='1.00') # Max daily spend
|
|
1085
|
+
nexus(action='budget_set', per_invoke_max='0.10') # Max per invocation
|
|
1086
|
+
nexus(action='budget_set', auto_approve_below='0.01') # Auto-approve micropayments
|
|
1087
|
+
```
|
|
1088
|
+
|
|
1089
|
+
### How x402 Works (End to End)
|
|
1090
|
+
1. **wallet_create** → generates wallet + x402-wallet.key for daemon signing
|
|
1091
|
+
2. **expose** with margin > 0 → registers capabilities with USDC pricing
|
|
1092
|
+
3. Peer calls **invoke_capability** → daemon sends `payment_required` with terms
|
|
1093
|
+
4. Consumer's daemon auto-signs `payment_proof` → provider validates → invoke proceeds
|
|
1094
|
+
5. Metering hook writes payment events to `ledger.jsonl`
|
|
1095
|
+
6. **spend** → direct agent-to-agent USDC transfers (EIP-3009, gasless)
|
|
1096
|
+
|
|
1097
|
+
### Security Model
|
|
1098
|
+
- Private keys: AES-256-GCM encrypted in `wallet.enc` (scrypt-derived key)
|
|
1099
|
+
- `x402-wallet.key`: plaintext (0600 perms) — used only by daemon subprocess
|
|
1100
|
+
- Budget policy: daily limits, per-invoke caps, circuit breaker, peer denylist
|
|
1101
|
+
- All outbound messages scanned for key material before sending
|
|
1102
|
+
- Keys NEVER appear in tool output, logs, or LLM context
|
|
1103
|
+
|
|
1048
1104
|
## Voice Feedback (TTS)
|
|
1049
1105
|
|
|
1050
1106
|
```bash
|
package/dist/index.js
CHANGED
|
@@ -11239,9 +11239,13 @@ const cmdFile = join(nexusDir, 'cmd.json');
|
|
|
11239
11239
|
const respFile = join(nexusDir, 'resp.json');
|
|
11240
11240
|
const statusFile = join(nexusDir, 'status.json');
|
|
11241
11241
|
const inboxDir = join(nexusDir, 'inbox');
|
|
11242
|
+
const logFile = join(nexusDir, 'daemon.log');
|
|
11243
|
+
function dlog(msg) { try { appendFileSync(logFile, new Date().toISOString() + ' ' + msg + '\\n'); } catch {} }
|
|
11242
11244
|
const pidFile = join(nexusDir, 'daemon.pid');
|
|
11243
11245
|
const invocationsDir = join(nexusDir, 'invocations');
|
|
11244
11246
|
const meteringFile = join(nexusDir, 'metering.jsonl');
|
|
11247
|
+
const x402KeyPath = join(nexusDir, 'x402-wallet.key');
|
|
11248
|
+
const hasX402Key = existsSync(x402KeyPath);
|
|
11245
11249
|
|
|
11246
11250
|
mkdirSync(inboxDir, { recursive: true });
|
|
11247
11251
|
mkdirSync(invocationsDir, { recursive: true });
|
|
@@ -11250,7 +11254,7 @@ mkdirSync(invocationsDir, { recursive: true });
|
|
|
11250
11254
|
writeFileSync(pidFile, String(process.pid));
|
|
11251
11255
|
|
|
11252
11256
|
const keyPath = join(nexusDir, 'identity.key');
|
|
11253
|
-
|
|
11257
|
+
var nexusOpts = {
|
|
11254
11258
|
keyStorePath: keyPath,
|
|
11255
11259
|
agentName,
|
|
11256
11260
|
agentType,
|
|
@@ -11262,7 +11266,18 @@ const nexus = new NexusClient({
|
|
|
11262
11266
|
enableCircuitRelay: true,
|
|
11263
11267
|
usePublicBootstrap: true,
|
|
11264
11268
|
trustPolicy: { denylist: [], allowlist: [] },
|
|
11265
|
-
}
|
|
11269
|
+
};
|
|
11270
|
+
if (hasX402Key) {
|
|
11271
|
+
nexusOpts.x402 = {
|
|
11272
|
+
enabled: true,
|
|
11273
|
+
walletKeyPath: x402KeyPath,
|
|
11274
|
+
maxPaymentPerRequest: '5000000',
|
|
11275
|
+
};
|
|
11276
|
+
if (process.env.ALCHEMY_API_KEY) {
|
|
11277
|
+
nexusOpts.x402.alchemyApiKey = process.env.ALCHEMY_API_KEY;
|
|
11278
|
+
}
|
|
11279
|
+
}
|
|
11280
|
+
const nexus = new NexusClient(nexusOpts);
|
|
11266
11281
|
|
|
11267
11282
|
const rooms = new Map();
|
|
11268
11283
|
let connected = false;
|
|
@@ -11292,6 +11307,7 @@ function writeResp(id, result) {
|
|
|
11292
11307
|
|
|
11293
11308
|
async function handleCmd(cmd) {
|
|
11294
11309
|
const { id, action, args } = cmd;
|
|
11310
|
+
dlog('handleCmd: action=' + action + ' id=' + id);
|
|
11295
11311
|
try {
|
|
11296
11312
|
switch (action) {
|
|
11297
11313
|
case 'join_room': {
|
|
@@ -11374,8 +11390,19 @@ async function handleCmd(cmd) {
|
|
|
11374
11390
|
const capability = args.capability || 'text-generation';
|
|
11375
11391
|
const input = args.input || {};
|
|
11376
11392
|
if (!peerId) { writeResp(id, { ok: false, output: 'target_peer is required' }); return; }
|
|
11377
|
-
|
|
11378
|
-
|
|
11393
|
+
dlog('invoke_capability: peer=' + peerId.slice(0, 20) + ' cap=' + capability);
|
|
11394
|
+
try {
|
|
11395
|
+
// Wrap in a 90s timeout to prevent hangs from nexus library issues
|
|
11396
|
+
const INVOKE_TIMEOUT = 90_000;
|
|
11397
|
+
const invokePromise = nexus.invokeCapability(peerId, capability, typeof input === 'string' ? { prompt: input } : input, { stream: false, maxDurationMs: 60000 });
|
|
11398
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('invoke_capability timed out after ' + (INVOKE_TIMEOUT / 1000) + 's (client-side timeout)')), INVOKE_TIMEOUT));
|
|
11399
|
+
const result = await Promise.race([invokePromise, timeoutPromise]);
|
|
11400
|
+
dlog('invoke_capability: SUCCESS');
|
|
11401
|
+
writeResp(id, { ok: true, output: JSON.stringify(result, null, 2) });
|
|
11402
|
+
} catch (invokeErr) {
|
|
11403
|
+
dlog('invoke_capability: ERROR ' + (invokeErr.message || String(invokeErr)));
|
|
11404
|
+
writeResp(id, { ok: false, output: 'Invoke error: ' + (invokeErr.message || String(invokeErr)) });
|
|
11405
|
+
}
|
|
11379
11406
|
break;
|
|
11380
11407
|
}
|
|
11381
11408
|
case 'store_content': {
|
|
@@ -11629,6 +11656,18 @@ async function handleCmd(cmd) {
|
|
|
11629
11656
|
// Register as nexus capability
|
|
11630
11657
|
const capName = 'inference:' + model.name.replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
11631
11658
|
if (typeof nexus.registerCapability === 'function') {
|
|
11659
|
+
var capOpts = {};
|
|
11660
|
+
if (margin > 0 && entry.pricing.input_per_1m_tokens > 0) {
|
|
11661
|
+
var tokensPerReq = 1000;
|
|
11662
|
+
var amountPerReq = Math.round(entry.pricing.input_per_1m_tokens * tokensPerReq);
|
|
11663
|
+
if (amountPerReq > 0) {
|
|
11664
|
+
capOpts.pricing = {
|
|
11665
|
+
amount: String(amountPerReq),
|
|
11666
|
+
currency: 'USDC',
|
|
11667
|
+
description: 'Inference: ' + model.name,
|
|
11668
|
+
};
|
|
11669
|
+
}
|
|
11670
|
+
}
|
|
11632
11671
|
nexus.registerCapability(capName, async (request, stream) => {
|
|
11633
11672
|
const logEntry = {
|
|
11634
11673
|
ts: Date.now(),
|
|
@@ -11705,7 +11744,7 @@ async function handleCmd(cmd) {
|
|
|
11705
11744
|
});
|
|
11706
11745
|
}
|
|
11707
11746
|
stream.close();
|
|
11708
|
-
});
|
|
11747
|
+
}, capOpts);
|
|
11709
11748
|
}
|
|
11710
11749
|
}
|
|
11711
11750
|
|
|
@@ -11773,7 +11812,9 @@ setInterval(() => {
|
|
|
11773
11812
|
const cmd = JSON.parse(raw);
|
|
11774
11813
|
if (cmd.id === lastCmdId) return; // already processed
|
|
11775
11814
|
lastCmdId = cmd.id;
|
|
11776
|
-
handleCmd(cmd)
|
|
11815
|
+
handleCmd(cmd).catch((err) => {
|
|
11816
|
+
writeResp(cmd.id, { ok: false, output: 'Error: ' + (err.message || String(err)) });
|
|
11817
|
+
});
|
|
11777
11818
|
} catch {}
|
|
11778
11819
|
}, 500);
|
|
11779
11820
|
|
|
@@ -11806,6 +11847,37 @@ process.on('unhandledRejection', (reason) => {
|
|
|
11806
11847
|
}
|
|
11807
11848
|
} catch {}
|
|
11808
11849
|
|
|
11850
|
+
// Write payment events to ledger.jsonl
|
|
11851
|
+
try {
|
|
11852
|
+
if (nexus.metering && typeof nexus.metering.addHook === 'function') {
|
|
11853
|
+
nexus.metering.addHook(function(record) {
|
|
11854
|
+
if (!record.payment) return;
|
|
11855
|
+
var ledgerFile = join(nexusDir, 'ledger.jsonl');
|
|
11856
|
+
var entry = {
|
|
11857
|
+
timestamp: new Date().toISOString(),
|
|
11858
|
+
type: record.direction === 'inbound' ? 'earned' : 'spent',
|
|
11859
|
+
amount: String(record.payment.amount || 0),
|
|
11860
|
+
amountUsd: (Number(record.payment.amount || 0) / 1000000).toFixed(6),
|
|
11861
|
+
peer: record.peerId || 'unknown',
|
|
11862
|
+
capability: record.service || record.capability || 'unknown',
|
|
11863
|
+
txHash: record.payment.txHash || '',
|
|
11864
|
+
note: 'auto:metering',
|
|
11865
|
+
};
|
|
11866
|
+
try { appendFileSync(ledgerFile, JSON.stringify(entry) + '\\n'); } catch {}
|
|
11867
|
+
});
|
|
11868
|
+
}
|
|
11869
|
+
} catch {}
|
|
11870
|
+
|
|
11871
|
+
// Init x402 wallet if key file exists
|
|
11872
|
+
if (hasX402Key && nexus.x402 && typeof nexus.x402.initWallet === 'function') {
|
|
11873
|
+
try {
|
|
11874
|
+
var addr = nexus.x402.initWallet(x402KeyPath);
|
|
11875
|
+
dlog('x402 wallet initialized: ' + addr);
|
|
11876
|
+
} catch (e) {
|
|
11877
|
+
dlog('x402 wallet init failed: ' + (e.message || e));
|
|
11878
|
+
}
|
|
11879
|
+
}
|
|
11880
|
+
|
|
11809
11881
|
// v1.5.0: Client-level events for global message/DM/invoke routing
|
|
11810
11882
|
try {
|
|
11811
11883
|
if (typeof nexus.on === 'function') {
|
|
@@ -11860,7 +11932,7 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
11860
11932
|
];
|
|
11861
11933
|
NexusTool = class {
|
|
11862
11934
|
name = "nexus";
|
|
11863
|
-
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.";
|
|
11935
|
+
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.";
|
|
11864
11936
|
parameters = {
|
|
11865
11937
|
type: "object",
|
|
11866
11938
|
properties: {
|
|
@@ -11892,7 +11964,11 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
11892
11964
|
"metering_status",
|
|
11893
11965
|
"room_members",
|
|
11894
11966
|
"expose",
|
|
11895
|
-
"pricing_menu"
|
|
11967
|
+
"pricing_menu",
|
|
11968
|
+
"ledger_status",
|
|
11969
|
+
"budget_status",
|
|
11970
|
+
"budget_set",
|
|
11971
|
+
"spend"
|
|
11896
11972
|
],
|
|
11897
11973
|
description: "The nexus action to perform"
|
|
11898
11974
|
},
|
|
@@ -11947,6 +12023,26 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
11947
12023
|
margin: {
|
|
11948
12024
|
type: "string",
|
|
11949
12025
|
description: "Price margin multiplier for expose (0.5 = 50% of market rate, 0 = free). Default: 0.5"
|
|
12026
|
+
},
|
|
12027
|
+
daily_limit: {
|
|
12028
|
+
type: "string",
|
|
12029
|
+
description: "For budget_set: max daily USDC spend (e.g. '1.00')"
|
|
12030
|
+
},
|
|
12031
|
+
per_invoke_max: {
|
|
12032
|
+
type: "string",
|
|
12033
|
+
description: "For budget_set: max per-invoke USDC spend (e.g. '0.10')"
|
|
12034
|
+
},
|
|
12035
|
+
auto_approve_below: {
|
|
12036
|
+
type: "string",
|
|
12037
|
+
description: "For budget_set: auto-approve payments below this USD amount (e.g. '0.01')"
|
|
12038
|
+
},
|
|
12039
|
+
target_address: {
|
|
12040
|
+
type: "string",
|
|
12041
|
+
description: "For spend: target EVM address (0x + 40 hex chars)"
|
|
12042
|
+
},
|
|
12043
|
+
amount_usdc: {
|
|
12044
|
+
type: "string",
|
|
12045
|
+
description: "For spend: USDC amount to transfer (e.g. '0.10')"
|
|
11950
12046
|
}
|
|
11951
12047
|
},
|
|
11952
12048
|
required: ["action"],
|
|
@@ -12000,10 +12096,10 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
12000
12096
|
result = await this.doSendDM(args);
|
|
12001
12097
|
break;
|
|
12002
12098
|
case "find_agent":
|
|
12003
|
-
result = await this.sendDaemonCmd("find_agent", args);
|
|
12099
|
+
result = await this.sendDaemonCmd("find_agent", args, 3e4);
|
|
12004
12100
|
break;
|
|
12005
12101
|
case "invoke_capability":
|
|
12006
|
-
result = await this.sendDaemonCmd("invoke_capability", args);
|
|
12102
|
+
result = await this.sendDaemonCmd("invoke_capability", args, 1e5);
|
|
12007
12103
|
break;
|
|
12008
12104
|
case "store_content":
|
|
12009
12105
|
result = await this.sendDaemonCmd("store_content", args);
|
|
@@ -12047,6 +12143,18 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
12047
12143
|
case "pricing_menu":
|
|
12048
12144
|
result = await this.sendDaemonCmd("pricing_menu", args);
|
|
12049
12145
|
break;
|
|
12146
|
+
case "ledger_status":
|
|
12147
|
+
result = await this.doLedgerStatus();
|
|
12148
|
+
break;
|
|
12149
|
+
case "budget_status":
|
|
12150
|
+
result = await this.doBudgetStatus();
|
|
12151
|
+
break;
|
|
12152
|
+
case "budget_set":
|
|
12153
|
+
result = await this.doBudgetSet(args);
|
|
12154
|
+
break;
|
|
12155
|
+
case "spend":
|
|
12156
|
+
result = await this.doSpend(args);
|
|
12157
|
+
break;
|
|
12050
12158
|
default:
|
|
12051
12159
|
return { success: false, output: "", error: `Unknown nexus action: ${action}`, durationMs: Date.now() - start };
|
|
12052
12160
|
}
|
|
@@ -12380,6 +12488,9 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
12380
12488
|
}
|
|
12381
12489
|
return lines.join("\n");
|
|
12382
12490
|
}
|
|
12491
|
+
// ---------------------------------------------------------------------------
|
|
12492
|
+
// Step 1: Wallet v2 — secp256k1 via viem for proper EVM addresses
|
|
12493
|
+
// ---------------------------------------------------------------------------
|
|
12383
12494
|
async doWalletStatus() {
|
|
12384
12495
|
await this.ensureDir();
|
|
12385
12496
|
const walletPath = join28(this.nexusDir, "wallet.enc");
|
|
@@ -12388,13 +12499,33 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
12388
12499
|
}
|
|
12389
12500
|
try {
|
|
12390
12501
|
const w = JSON.parse(await readFile13(walletPath, "utf8"));
|
|
12391
|
-
|
|
12502
|
+
const lines = [
|
|
12392
12503
|
`Wallet Status:`,
|
|
12393
12504
|
` Address: ${w.address}`,
|
|
12505
|
+
` Version: ${w.version} (${w.version === 2 ? "secp256k1/EVM" : "legacy SHA256"})`,
|
|
12506
|
+
` Network: ${w.network || "none"} (chain ${w.chainId || "N/A"})`,
|
|
12394
12507
|
` Created: ${w.createdAt}`,
|
|
12395
12508
|
` Encryption: AES-256-GCM + scrypt`,
|
|
12396
12509
|
` NOTE: Private key is encrypted and NEVER accessible to the agent.`
|
|
12397
|
-
]
|
|
12510
|
+
];
|
|
12511
|
+
if (w.version === 2 && w.address) {
|
|
12512
|
+
const balance = await this.queryUsdcBalance(w.address, w.chainId || 8453);
|
|
12513
|
+
if (balance !== null) {
|
|
12514
|
+
const usdcHuman = (Number(balance) / 1e6).toFixed(6);
|
|
12515
|
+
lines.push(` USDC Balance: $${usdcHuman}`);
|
|
12516
|
+
const budget = await this.loadBudget();
|
|
12517
|
+
if (budget && Number(balance) < budget.circuitBreakerUsdc) {
|
|
12518
|
+
lines.push(` \u26A0 CIRCUIT BREAKER: Balance below $${(budget.circuitBreakerUsdc / 1e6).toFixed(2)} \u2014 all payments BLOCKED`);
|
|
12519
|
+
} else if (budget && Number(balance) < budget.lowBalanceAlertUsdc) {
|
|
12520
|
+
lines.push(` \u26A0 LOW BALANCE: Below $${(budget.lowBalanceAlertUsdc / 1e6).toFixed(2)} threshold`);
|
|
12521
|
+
}
|
|
12522
|
+
}
|
|
12523
|
+
}
|
|
12524
|
+
const ledgerSummary = await this.getLedgerSummary();
|
|
12525
|
+
if (ledgerSummary) {
|
|
12526
|
+
lines.push(` Earned: $${ledgerSummary.totalEarned} Spent: $${ledgerSummary.totalSpent} Net: $${ledgerSummary.net}`);
|
|
12527
|
+
}
|
|
12528
|
+
return lines.join("\n");
|
|
12398
12529
|
} catch {
|
|
12399
12530
|
return "Wallet file exists but could not be read.";
|
|
12400
12531
|
}
|
|
@@ -12403,51 +12534,444 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
12403
12534
|
await this.ensureDir();
|
|
12404
12535
|
const walletPath = join28(this.nexusDir, "wallet.enc");
|
|
12405
12536
|
if (existsSync21(walletPath))
|
|
12406
|
-
return "Wallet already exists.";
|
|
12537
|
+
return "Wallet already exists. Delete wallet.enc to create a new one.";
|
|
12407
12538
|
const userAddress = args.wallet_address;
|
|
12408
12539
|
if (userAddress) {
|
|
12409
12540
|
if (!/^0x[0-9a-fA-F]{40}$/.test(userAddress)) {
|
|
12410
12541
|
return "Invalid address format. Expected 0x + 40 hex chars.";
|
|
12411
12542
|
}
|
|
12412
12543
|
const w2 = {
|
|
12413
|
-
version:
|
|
12544
|
+
version: 2,
|
|
12414
12545
|
address: userAddress,
|
|
12415
12546
|
encryptedKey: "",
|
|
12416
12547
|
iv: "",
|
|
12417
12548
|
authTag: "",
|
|
12418
12549
|
salt: "",
|
|
12419
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
12550
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12551
|
+
network: "base",
|
|
12552
|
+
chainId: 8453
|
|
12420
12553
|
};
|
|
12421
12554
|
await writeFile12(walletPath, JSON.stringify(w2, null, 2));
|
|
12422
12555
|
await chmod(walletPath, 384);
|
|
12423
|
-
|
|
12556
|
+
await this.ensureDefaultBudget();
|
|
12557
|
+
return `Wallet configured: ${userAddress} (user-managed, Base mainnet, no x402 key \u2014 spend/sign requires self-hosted wallet)`;
|
|
12558
|
+
}
|
|
12559
|
+
let address;
|
|
12560
|
+
let privKeyHex;
|
|
12561
|
+
try {
|
|
12562
|
+
const { privateKeyToAccount } = await import("viem/accounts");
|
|
12563
|
+
const privKey = randomBytes6(32);
|
|
12564
|
+
privKeyHex = privKey.toString("hex");
|
|
12565
|
+
const account = privateKeyToAccount(`0x${privKeyHex}`);
|
|
12566
|
+
address = account.address;
|
|
12567
|
+
privKey.fill(0);
|
|
12568
|
+
} catch {
|
|
12569
|
+
const privKey = randomBytes6(32);
|
|
12570
|
+
privKeyHex = privKey.toString("hex");
|
|
12571
|
+
address = "0x" + createHash("sha256").update(privKey).digest("hex").slice(0, 40);
|
|
12572
|
+
privKey.fill(0);
|
|
12424
12573
|
}
|
|
12425
|
-
const privKey = randomBytes6(32);
|
|
12426
|
-
const address = "0x" + createHash("sha256").update(privKey).digest("hex").slice(0, 40);
|
|
12427
12574
|
const salt = randomBytes6(32);
|
|
12428
12575
|
const key = scryptSync(`${hostname()}:${userInfo().username}:nexus-wallet`, salt, 32, { N: 16384, r: 8, p: 1 });
|
|
12429
12576
|
const iv = randomBytes6(16);
|
|
12430
12577
|
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
12431
|
-
let enc = cipher.update(
|
|
12578
|
+
let enc = cipher.update(privKeyHex, "utf8", "hex");
|
|
12432
12579
|
enc += cipher.final("hex");
|
|
12433
|
-
|
|
12580
|
+
const x402KeyPath = join28(this.nexusDir, "x402-wallet.key");
|
|
12581
|
+
await writeFile12(x402KeyPath, "0x" + privKeyHex);
|
|
12582
|
+
await chmod(x402KeyPath, 384);
|
|
12583
|
+
privKeyHex = "0".repeat(64);
|
|
12434
12584
|
const w = {
|
|
12435
|
-
version:
|
|
12585
|
+
version: 2,
|
|
12436
12586
|
address,
|
|
12437
12587
|
encryptedKey: enc,
|
|
12438
12588
|
iv: iv.toString("hex"),
|
|
12439
12589
|
authTag: cipher.getAuthTag().toString("hex"),
|
|
12440
12590
|
salt: salt.toString("hex"),
|
|
12441
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
12591
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12592
|
+
network: "base",
|
|
12593
|
+
chainId: 8453
|
|
12442
12594
|
};
|
|
12443
12595
|
await writeFile12(walletPath, JSON.stringify(w, null, 2));
|
|
12444
12596
|
await chmod(walletPath, 384);
|
|
12597
|
+
await this.ensureDefaultBudget();
|
|
12445
12598
|
return [
|
|
12446
|
-
`Wallet created
|
|
12599
|
+
`Wallet created (v2, secp256k1/EVM):`,
|
|
12600
|
+
` Address: ${address}`,
|
|
12601
|
+
` Network: Base mainnet (chain 8453)`,
|
|
12602
|
+
` Token: USDC`,
|
|
12447
12603
|
` Encrypted with AES-256-GCM (key NEVER visible to agent)`,
|
|
12448
|
-
` File: ${walletPath} (0600)
|
|
12604
|
+
` File: ${walletPath} (0600)`,
|
|
12605
|
+
``,
|
|
12606
|
+
`Fund this address with USDC on Base to enable paid inference.`,
|
|
12607
|
+
`Free inference (margin=0) works without funding.`
|
|
12449
12608
|
].join("\n");
|
|
12450
12609
|
}
|
|
12610
|
+
// ---------------------------------------------------------------------------
|
|
12611
|
+
// Step 2: USDC Balance Query via Base RPC
|
|
12612
|
+
// ---------------------------------------------------------------------------
|
|
12613
|
+
balanceCache = null;
|
|
12614
|
+
async queryUsdcBalance(address, chainId) {
|
|
12615
|
+
if (this.balanceCache && Date.now() - this.balanceCache.fetchedAt < 6e4) {
|
|
12616
|
+
return this.balanceCache.value;
|
|
12617
|
+
}
|
|
12618
|
+
const rpcUrl = chainId === 84532 ? "https://sepolia.base.org" : "https://mainnet.base.org";
|
|
12619
|
+
const usdcContract = chainId === 84532 ? "0x036CbD53842c5426634e7929541eC2318f3dCF7e" : "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
12620
|
+
const paddedAddress = address.slice(2).toLowerCase().padStart(64, "0");
|
|
12621
|
+
const data = `0x70a08231${paddedAddress}`;
|
|
12622
|
+
try {
|
|
12623
|
+
const resp = await fetch(rpcUrl, {
|
|
12624
|
+
method: "POST",
|
|
12625
|
+
headers: { "Content-Type": "application/json" },
|
|
12626
|
+
body: JSON.stringify({
|
|
12627
|
+
jsonrpc: "2.0",
|
|
12628
|
+
id: 1,
|
|
12629
|
+
method: "eth_call",
|
|
12630
|
+
params: [{ to: usdcContract, data }, "latest"]
|
|
12631
|
+
}),
|
|
12632
|
+
signal: AbortSignal.timeout(5e3)
|
|
12633
|
+
});
|
|
12634
|
+
const json = await resp.json();
|
|
12635
|
+
if (json.result) {
|
|
12636
|
+
const balance = BigInt(json.result).toString();
|
|
12637
|
+
this.balanceCache = { value: balance, fetchedAt: Date.now() };
|
|
12638
|
+
return balance;
|
|
12639
|
+
}
|
|
12640
|
+
} catch {
|
|
12641
|
+
}
|
|
12642
|
+
return null;
|
|
12643
|
+
}
|
|
12644
|
+
// ---------------------------------------------------------------------------
|
|
12645
|
+
// Step 3: Ledger — Track earnings and expenditures
|
|
12646
|
+
// ---------------------------------------------------------------------------
|
|
12647
|
+
async doLedgerStatus() {
|
|
12648
|
+
await this.ensureDir();
|
|
12649
|
+
const ledgerPath = join28(this.nexusDir, "ledger.jsonl");
|
|
12650
|
+
if (!existsSync21(ledgerPath)) {
|
|
12651
|
+
return "No ledger entries. Earnings and spending will be tracked here after x402 transactions.";
|
|
12652
|
+
}
|
|
12653
|
+
try {
|
|
12654
|
+
const raw = await readFile13(ledgerPath, "utf8");
|
|
12655
|
+
const entries = raw.trim().split("\n").filter((l) => l.trim()).map((l) => JSON.parse(l));
|
|
12656
|
+
if (entries.length === 0) {
|
|
12657
|
+
return "Ledger is empty. No transactions recorded.";
|
|
12658
|
+
}
|
|
12659
|
+
let totalEarned = 0n, totalSpent = 0n, pending = 0n;
|
|
12660
|
+
for (const e of entries) {
|
|
12661
|
+
const amt = BigInt(e.amount);
|
|
12662
|
+
if (e.type === "earned")
|
|
12663
|
+
totalEarned += amt;
|
|
12664
|
+
else if (e.type === "spent")
|
|
12665
|
+
totalSpent += amt;
|
|
12666
|
+
else if (e.type === "pending")
|
|
12667
|
+
pending += amt;
|
|
12668
|
+
}
|
|
12669
|
+
const lines = [
|
|
12670
|
+
`Ledger Status (${entries.length} entries):`,
|
|
12671
|
+
` Total Earned: $${(Number(totalEarned) / 1e6).toFixed(6)} USDC`,
|
|
12672
|
+
` Total Spent: $${(Number(totalSpent) / 1e6).toFixed(6)} USDC`,
|
|
12673
|
+
` Net: $${(Number(totalEarned - totalSpent) / 1e6).toFixed(6)} USDC`
|
|
12674
|
+
];
|
|
12675
|
+
if (pending > 0n) {
|
|
12676
|
+
lines.push(` Pending: $${(Number(pending) / 1e6).toFixed(6)} USDC`);
|
|
12677
|
+
}
|
|
12678
|
+
const recent = entries.slice(-5);
|
|
12679
|
+
lines.push(``, `Recent transactions:`);
|
|
12680
|
+
for (const e of recent) {
|
|
12681
|
+
const sign = e.type === "earned" ? "+" : e.type === "spent" ? "-" : "~";
|
|
12682
|
+
const peerShort = (e.peer || "?").slice(0, 16) + "...";
|
|
12683
|
+
lines.push(` ${e.timestamp.slice(0, 19)} ${sign}$${e.amountUsd} ${e.capability} (${peerShort})`);
|
|
12684
|
+
}
|
|
12685
|
+
return lines.join("\n");
|
|
12686
|
+
} catch {
|
|
12687
|
+
return "Error reading ledger file.";
|
|
12688
|
+
}
|
|
12689
|
+
}
|
|
12690
|
+
async getLedgerSummary() {
|
|
12691
|
+
const ledgerPath = join28(this.nexusDir, "ledger.jsonl");
|
|
12692
|
+
if (!existsSync21(ledgerPath))
|
|
12693
|
+
return null;
|
|
12694
|
+
try {
|
|
12695
|
+
const raw = await readFile13(ledgerPath, "utf8");
|
|
12696
|
+
const entries = raw.trim().split("\n").filter((l) => l.trim()).map((l) => JSON.parse(l));
|
|
12697
|
+
let totalEarned = 0n, totalSpent = 0n;
|
|
12698
|
+
for (const e of entries) {
|
|
12699
|
+
const amt = BigInt(e.amount);
|
|
12700
|
+
if (e.type === "earned")
|
|
12701
|
+
totalEarned += amt;
|
|
12702
|
+
else if (e.type === "spent")
|
|
12703
|
+
totalSpent += amt;
|
|
12704
|
+
}
|
|
12705
|
+
return {
|
|
12706
|
+
totalEarned: (Number(totalEarned) / 1e6).toFixed(6),
|
|
12707
|
+
totalSpent: (Number(totalSpent) / 1e6).toFixed(6),
|
|
12708
|
+
net: (Number(totalEarned - totalSpent) / 1e6).toFixed(6)
|
|
12709
|
+
};
|
|
12710
|
+
} catch {
|
|
12711
|
+
return null;
|
|
12712
|
+
}
|
|
12713
|
+
}
|
|
12714
|
+
async appendLedger(entry) {
|
|
12715
|
+
await this.ensureDir();
|
|
12716
|
+
const ledgerPath = join28(this.nexusDir, "ledger.jsonl");
|
|
12717
|
+
const line = JSON.stringify(entry) + "\n";
|
|
12718
|
+
await writeFile12(ledgerPath, existsSync21(ledgerPath) ? await readFile13(ledgerPath, "utf8") + line : line);
|
|
12719
|
+
}
|
|
12720
|
+
// ---------------------------------------------------------------------------
|
|
12721
|
+
// Step 4: Budget Policy — Spending limits and auto-approve thresholds
|
|
12722
|
+
// ---------------------------------------------------------------------------
|
|
12723
|
+
async loadBudget() {
|
|
12724
|
+
const budgetPath = join28(this.nexusDir, "budget.json");
|
|
12725
|
+
if (!existsSync21(budgetPath))
|
|
12726
|
+
return null;
|
|
12727
|
+
try {
|
|
12728
|
+
return JSON.parse(await readFile13(budgetPath, "utf8"));
|
|
12729
|
+
} catch {
|
|
12730
|
+
return null;
|
|
12731
|
+
}
|
|
12732
|
+
}
|
|
12733
|
+
async ensureDefaultBudget() {
|
|
12734
|
+
const budgetPath = join28(this.nexusDir, "budget.json");
|
|
12735
|
+
if (existsSync21(budgetPath))
|
|
12736
|
+
return;
|
|
12737
|
+
const defaultBudget = {
|
|
12738
|
+
version: 1,
|
|
12739
|
+
autoApproveBelow: 1e3,
|
|
12740
|
+
// $0.001 — auto-approve micropayments
|
|
12741
|
+
dailyLimitUsdc: 1e6,
|
|
12742
|
+
// $1.00/day default
|
|
12743
|
+
perInvokeMaxUsdc: 1e5,
|
|
12744
|
+
// $0.10 per invoke max
|
|
12745
|
+
requireConfirmationAbove: 5e5,
|
|
12746
|
+
// $0.50 — ask human above this
|
|
12747
|
+
lowBalanceAlertUsdc: 5e5,
|
|
12748
|
+
// $0.50 — warn when low
|
|
12749
|
+
circuitBreakerUsdc: 1e5,
|
|
12750
|
+
// $0.10 — refuse payments below this balance
|
|
12751
|
+
allowedCapabilities: ["inference:*", "transfer:direct"],
|
|
12752
|
+
deniedPeers: []
|
|
12753
|
+
};
|
|
12754
|
+
await this.ensureDir();
|
|
12755
|
+
await writeFile12(budgetPath, JSON.stringify(defaultBudget, null, 2));
|
|
12756
|
+
}
|
|
12757
|
+
async doBudgetStatus() {
|
|
12758
|
+
await this.ensureDir();
|
|
12759
|
+
const budget = await this.loadBudget();
|
|
12760
|
+
if (!budget) {
|
|
12761
|
+
return "No budget policy configured. Use wallet_create to initialize defaults, or budget_set to configure.";
|
|
12762
|
+
}
|
|
12763
|
+
const todaySpent = await this.getTodaySpending();
|
|
12764
|
+
const lines = [
|
|
12765
|
+
`Budget Policy:`,
|
|
12766
|
+
` Auto-approve below: $${(budget.autoApproveBelow / 1e6).toFixed(6)}`,
|
|
12767
|
+
` Per-invoke max: $${(budget.perInvokeMaxUsdc / 1e6).toFixed(6)}`,
|
|
12768
|
+
` Daily limit: $${(budget.dailyLimitUsdc / 1e6).toFixed(6)}`,
|
|
12769
|
+
` Today's spending: $${(todaySpent / 1e6).toFixed(6)} / $${(budget.dailyLimitUsdc / 1e6).toFixed(6)}`,
|
|
12770
|
+
` Confirm above: $${(budget.requireConfirmationAbove / 1e6).toFixed(6)}`,
|
|
12771
|
+
` Low balance alert: $${(budget.lowBalanceAlertUsdc / 1e6).toFixed(6)}`,
|
|
12772
|
+
` Circuit breaker: $${(budget.circuitBreakerUsdc / 1e6).toFixed(6)}`,
|
|
12773
|
+
` Allowed caps: ${budget.allowedCapabilities.join(", ")}`
|
|
12774
|
+
];
|
|
12775
|
+
if (budget.deniedPeers.length > 0) {
|
|
12776
|
+
lines.push(` Denied peers: ${budget.deniedPeers.length} blocked`);
|
|
12777
|
+
}
|
|
12778
|
+
return lines.join("\n");
|
|
12779
|
+
}
|
|
12780
|
+
async doBudgetSet(args) {
|
|
12781
|
+
await this.ensureDir();
|
|
12782
|
+
let budget = await this.loadBudget();
|
|
12783
|
+
if (!budget) {
|
|
12784
|
+
await this.ensureDefaultBudget();
|
|
12785
|
+
budget = await this.loadBudget();
|
|
12786
|
+
}
|
|
12787
|
+
const parseUsdc = (s) => Math.round(parseFloat(s) * 1e6);
|
|
12788
|
+
const changes = [];
|
|
12789
|
+
if (args.daily_limit) {
|
|
12790
|
+
budget.dailyLimitUsdc = parseUsdc(args.daily_limit);
|
|
12791
|
+
changes.push(`daily_limit=$${args.daily_limit}`);
|
|
12792
|
+
}
|
|
12793
|
+
if (args.per_invoke_max) {
|
|
12794
|
+
budget.perInvokeMaxUsdc = parseUsdc(args.per_invoke_max);
|
|
12795
|
+
changes.push(`per_invoke_max=$${args.per_invoke_max}`);
|
|
12796
|
+
}
|
|
12797
|
+
if (args.auto_approve_below) {
|
|
12798
|
+
budget.autoApproveBelow = parseUsdc(args.auto_approve_below);
|
|
12799
|
+
changes.push(`auto_approve_below=$${args.auto_approve_below}`);
|
|
12800
|
+
}
|
|
12801
|
+
if (changes.length === 0) {
|
|
12802
|
+
return "No budget parameters provided. Use daily_limit, per_invoke_max, or auto_approve_below.";
|
|
12803
|
+
}
|
|
12804
|
+
const budgetPath = join28(this.nexusDir, "budget.json");
|
|
12805
|
+
await writeFile12(budgetPath, JSON.stringify(budget, null, 2));
|
|
12806
|
+
return `Budget updated: ${changes.join(", ")}`;
|
|
12807
|
+
}
|
|
12808
|
+
async getTodaySpending() {
|
|
12809
|
+
const ledgerPath = join28(this.nexusDir, "ledger.jsonl");
|
|
12810
|
+
if (!existsSync21(ledgerPath))
|
|
12811
|
+
return 0;
|
|
12812
|
+
try {
|
|
12813
|
+
const todayPrefix = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
12814
|
+
const raw = await readFile13(ledgerPath, "utf8");
|
|
12815
|
+
let total = 0;
|
|
12816
|
+
for (const line of raw.trim().split("\n")) {
|
|
12817
|
+
if (!line.trim())
|
|
12818
|
+
continue;
|
|
12819
|
+
const entry = JSON.parse(line);
|
|
12820
|
+
if (entry.type === "spent" && entry.timestamp.startsWith(todayPrefix)) {
|
|
12821
|
+
total += Number(BigInt(entry.amount));
|
|
12822
|
+
}
|
|
12823
|
+
}
|
|
12824
|
+
return total;
|
|
12825
|
+
} catch {
|
|
12826
|
+
return 0;
|
|
12827
|
+
}
|
|
12828
|
+
}
|
|
12829
|
+
async checkBudget(amountUsdc, capability, peerId) {
|
|
12830
|
+
const budget = await this.loadBudget();
|
|
12831
|
+
if (!budget)
|
|
12832
|
+
return { allowed: true };
|
|
12833
|
+
if (budget.deniedPeers.includes(peerId)) {
|
|
12834
|
+
return { allowed: false, reason: `Peer ${peerId.slice(0, 16)}... is denied by budget policy` };
|
|
12835
|
+
}
|
|
12836
|
+
const capAllowed = budget.allowedCapabilities.some((pattern) => {
|
|
12837
|
+
if (pattern === "*")
|
|
12838
|
+
return true;
|
|
12839
|
+
if (pattern.endsWith(":*"))
|
|
12840
|
+
return capability.startsWith(pattern.slice(0, -1));
|
|
12841
|
+
return capability === pattern;
|
|
12842
|
+
});
|
|
12843
|
+
if (!capAllowed) {
|
|
12844
|
+
return { allowed: false, reason: `Capability "${capability}" not in allowed list` };
|
|
12845
|
+
}
|
|
12846
|
+
if (amountUsdc > budget.perInvokeMaxUsdc) {
|
|
12847
|
+
return { allowed: false, reason: `Amount $${(amountUsdc / 1e6).toFixed(6)} exceeds per-invoke max $${(budget.perInvokeMaxUsdc / 1e6).toFixed(6)}` };
|
|
12848
|
+
}
|
|
12849
|
+
const todaySpent = await this.getTodaySpending();
|
|
12850
|
+
if (todaySpent + amountUsdc > budget.dailyLimitUsdc) {
|
|
12851
|
+
return { allowed: false, reason: `Would exceed daily limit ($${((todaySpent + amountUsdc) / 1e6).toFixed(6)} > $${(budget.dailyLimitUsdc / 1e6).toFixed(6)})` };
|
|
12852
|
+
}
|
|
12853
|
+
const walletPath = join28(this.nexusDir, "wallet.enc");
|
|
12854
|
+
if (existsSync21(walletPath)) {
|
|
12855
|
+
try {
|
|
12856
|
+
const w = JSON.parse(await readFile13(walletPath, "utf8"));
|
|
12857
|
+
if (w.version === 2 && w.address) {
|
|
12858
|
+
const balance = await this.queryUsdcBalance(w.address, w.chainId || 8453);
|
|
12859
|
+
if (balance !== null && Number(balance) < budget.circuitBreakerUsdc) {
|
|
12860
|
+
return { allowed: false, reason: `Balance $${(Number(balance) / 1e6).toFixed(6)} below circuit breaker threshold $${(budget.circuitBreakerUsdc / 1e6).toFixed(6)}` };
|
|
12861
|
+
}
|
|
12862
|
+
}
|
|
12863
|
+
} catch {
|
|
12864
|
+
}
|
|
12865
|
+
}
|
|
12866
|
+
return { allowed: true };
|
|
12867
|
+
}
|
|
12868
|
+
// ---------------------------------------------------------------------------
|
|
12869
|
+
// Step 5: Spend — Agent-initiated USDC transfer (EIP-3009)
|
|
12870
|
+
// ---------------------------------------------------------------------------
|
|
12871
|
+
async doSpend(args) {
|
|
12872
|
+
const targetAddress = args.target_address;
|
|
12873
|
+
const amountUsd = args.amount_usdc;
|
|
12874
|
+
if (!targetAddress || !/^0x[0-9a-fA-F]{40}$/.test(targetAddress))
|
|
12875
|
+
throw new Error("Valid target_address (0x + 40 hex) is required");
|
|
12876
|
+
if (!amountUsd || isNaN(parseFloat(amountUsd)))
|
|
12877
|
+
throw new Error("amount_usdc is required (e.g. '0.10')");
|
|
12878
|
+
const amountSmallest = Math.round(parseFloat(amountUsd) * 1e6);
|
|
12879
|
+
if (amountSmallest <= 0)
|
|
12880
|
+
throw new Error("amount must be positive");
|
|
12881
|
+
const budgetResult = await this.checkBudget(amountSmallest, "transfer:direct", "");
|
|
12882
|
+
if (!budgetResult.allowed)
|
|
12883
|
+
return `BLOCKED by budget policy: ${budgetResult.reason}`;
|
|
12884
|
+
const walletPath = join28(this.nexusDir, "wallet.enc");
|
|
12885
|
+
if (!existsSync21(walletPath))
|
|
12886
|
+
throw new Error("No wallet. Use wallet_create first.");
|
|
12887
|
+
const w = JSON.parse(await readFile13(walletPath, "utf8"));
|
|
12888
|
+
if (!w.encryptedKey)
|
|
12889
|
+
throw new Error("User-managed wallet cannot spend (no private key)");
|
|
12890
|
+
const salt = Buffer.from(w.salt, "hex");
|
|
12891
|
+
const key = scryptSync(`${hostname()}:${userInfo().username}:nexus-wallet`, salt, 32, { N: 16384, r: 8, p: 1 });
|
|
12892
|
+
const decipher = createDecipheriv("aes-256-gcm", key, Buffer.from(w.iv, "hex"));
|
|
12893
|
+
decipher.setAuthTag(Buffer.from(w.authTag, "hex"));
|
|
12894
|
+
let privKeyHex = decipher.update(w.encryptedKey, "hex", "utf8");
|
|
12895
|
+
privKeyHex += decipher.final("utf8");
|
|
12896
|
+
try {
|
|
12897
|
+
const { privateKeyToAccount } = await import("viem/accounts");
|
|
12898
|
+
const account = privateKeyToAccount(`0x${privKeyHex}`);
|
|
12899
|
+
const nonce = "0x" + randomBytes6(32).toString("hex");
|
|
12900
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
12901
|
+
const validAfter = now - 60;
|
|
12902
|
+
const validBefore = now + 3600;
|
|
12903
|
+
const USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
12904
|
+
const domain = {
|
|
12905
|
+
name: "USD Coin",
|
|
12906
|
+
version: "2",
|
|
12907
|
+
chainId: 8453,
|
|
12908
|
+
verifyingContract: USDC_ADDRESS
|
|
12909
|
+
};
|
|
12910
|
+
const types = {
|
|
12911
|
+
TransferWithAuthorization: [
|
|
12912
|
+
{ name: "from", type: "address" },
|
|
12913
|
+
{ name: "to", type: "address" },
|
|
12914
|
+
{ name: "value", type: "uint256" },
|
|
12915
|
+
{ name: "validAfter", type: "uint256" },
|
|
12916
|
+
{ name: "validBefore", type: "uint256" },
|
|
12917
|
+
{ name: "nonce", type: "bytes32" }
|
|
12918
|
+
]
|
|
12919
|
+
};
|
|
12920
|
+
const message = {
|
|
12921
|
+
from: account.address,
|
|
12922
|
+
to: targetAddress,
|
|
12923
|
+
value: BigInt(amountSmallest),
|
|
12924
|
+
validAfter: BigInt(validAfter),
|
|
12925
|
+
validBefore: BigInt(validBefore),
|
|
12926
|
+
nonce
|
|
12927
|
+
};
|
|
12928
|
+
const { signTypedData } = await import("viem/accounts");
|
|
12929
|
+
const signature = await signTypedData({
|
|
12930
|
+
privateKey: `0x${privKeyHex}`,
|
|
12931
|
+
domain,
|
|
12932
|
+
types,
|
|
12933
|
+
primaryType: "TransferWithAuthorization",
|
|
12934
|
+
message
|
|
12935
|
+
});
|
|
12936
|
+
await this.appendLedger({
|
|
12937
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12938
|
+
type: "spent",
|
|
12939
|
+
amount: String(amountSmallest),
|
|
12940
|
+
amountUsd: (amountSmallest / 1e6).toFixed(6),
|
|
12941
|
+
peer: targetAddress,
|
|
12942
|
+
capability: "transfer:direct",
|
|
12943
|
+
note: "signed, awaiting submission"
|
|
12944
|
+
});
|
|
12945
|
+
const proofFile = join28(this.nexusDir, "pending-transfer.json");
|
|
12946
|
+
const proof = {
|
|
12947
|
+
from: account.address,
|
|
12948
|
+
to: targetAddress,
|
|
12949
|
+
value: String(amountSmallest),
|
|
12950
|
+
validAfter: String(validAfter),
|
|
12951
|
+
validBefore: String(validBefore),
|
|
12952
|
+
nonce,
|
|
12953
|
+
signature,
|
|
12954
|
+
chainId: 8453,
|
|
12955
|
+
usdcContract: USDC_ADDRESS,
|
|
12956
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
12957
|
+
};
|
|
12958
|
+
await writeFile12(proofFile, JSON.stringify(proof, null, 2));
|
|
12959
|
+
return [
|
|
12960
|
+
`Transfer signed (EIP-3009 TransferWithAuthorization):`,
|
|
12961
|
+
` From: ${account.address}`,
|
|
12962
|
+
` To: ${targetAddress}`,
|
|
12963
|
+
` Amount: $${(amountSmallest / 1e6).toFixed(6)} USDC`,
|
|
12964
|
+
` Valid: ${new Date(validAfter * 1e3).toISOString()} to ${new Date(validBefore * 1e3).toISOString()}`,
|
|
12965
|
+
` Signature: ${signature.slice(0, 20)}...`,
|
|
12966
|
+
` Proof saved: ${proofFile}`,
|
|
12967
|
+
``,
|
|
12968
|
+
`To submit on-chain: anyone can call USDC.transferWithAuthorization() with this proof.`,
|
|
12969
|
+
`The recipient or a facilitator can submit it \u2014 no gas needed from the payer.`
|
|
12970
|
+
].join("\n");
|
|
12971
|
+
} finally {
|
|
12972
|
+
privKeyHex = "0".repeat(64);
|
|
12973
|
+
}
|
|
12974
|
+
}
|
|
12451
12975
|
async doInferenceProof() {
|
|
12452
12976
|
try {
|
|
12453
12977
|
const psRaw = execSync21("ollama ps 2>/dev/null", { timeout: 1e4, encoding: "utf8" });
|
|
@@ -40683,7 +41207,7 @@ ${result.text}`;
|
|
|
40683
41207
|
const steerPrompt = [
|
|
40684
41208
|
`The user typed a mid-task message while the agent is working. Your job:`,
|
|
40685
41209
|
`1. Understand what the user wants changed/added/corrected`,
|
|
40686
|
-
`2. Produce a brief spoken acknowledgment
|
|
41210
|
+
`2. Produce a brief, UNIQUE spoken acknowledgment that reflects WHAT they asked for`,
|
|
40687
41211
|
`3. Expand their input into a clear, structured steering instruction for the main agent`,
|
|
40688
41212
|
``,
|
|
40689
41213
|
`Current task goal: "${steerTaskGoal.slice(0, 500)}"`,
|
|
@@ -40694,11 +41218,30 @@ ${result.text}`;
|
|
|
40694
41218
|
`User's mid-task message: "${input}"`,
|
|
40695
41219
|
``,
|
|
40696
41220
|
`Call task_complete with:`,
|
|
40697
|
-
`- acknowledgment: brief spoken response to the user
|
|
41221
|
+
`- acknowledgment: a brief spoken response SPECIFIC to what the user said. Reference the actual change they want. DO NOT use generic phrases like "Got it" or "adjusting". Instead describe what you'll do differently. Examples of GOOD acknowledgments:`,
|
|
41222
|
+
` - "Switching to the new API endpoint now"`,
|
|
41223
|
+
` - "Right, I'll use TypeScript instead"`,
|
|
41224
|
+
` - "Understood, dropping the database mock"`,
|
|
41225
|
+
` - "On it, pulling in that dependency first"`,
|
|
41226
|
+
` - "Yep, let me rethink the caching layer"`,
|
|
41227
|
+
` - "Makes sense, I'll refactor that into a separate module"`,
|
|
40698
41228
|
`- summary: expanded instruction for the main agent (e.g. "USER STEERING: The user wants X instead of Y. Adjust your approach to prioritize Z. Specifically, they are asking you to...")`
|
|
40699
41229
|
].join("\n");
|
|
40700
41230
|
const result = await steerAgent.run(steerPrompt, "Steering sub-agent \u2014 interpret user input and produce instruction.");
|
|
40701
|
-
|
|
41231
|
+
const STEER_FALLBACKS = [
|
|
41232
|
+
`Noted, changing course.`,
|
|
41233
|
+
`On it, shifting approach.`,
|
|
41234
|
+
`Understood, recalibrating.`,
|
|
41235
|
+
`Right, let me rework that.`,
|
|
41236
|
+
`Okay, taking a different angle.`,
|
|
41237
|
+
`Heard you, pivoting now.`,
|
|
41238
|
+
`Sure thing, rethinking this.`,
|
|
41239
|
+
`Copy that, adapting.`,
|
|
41240
|
+
`Makes sense, re-routing.`,
|
|
41241
|
+
`Roger, new plan.`
|
|
41242
|
+
];
|
|
41243
|
+
const fbIdx = Math.floor(Math.random() * STEER_FALLBACKS.length);
|
|
41244
|
+
let acknowledgment = STEER_FALLBACKS[fbIdx];
|
|
40702
41245
|
let steering = `USER STEERING: ${input}`;
|
|
40703
41246
|
try {
|
|
40704
41247
|
const parsed = JSON.parse(result.summary || "{}");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "open-agents-ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.87.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",
|
|
@@ -49,7 +49,11 @@
|
|
|
49
49
|
"peer-to-peer",
|
|
50
50
|
"inference-network",
|
|
51
51
|
"secret-vault",
|
|
52
|
-
"websocket"
|
|
52
|
+
"websocket",
|
|
53
|
+
"x402",
|
|
54
|
+
"usdc",
|
|
55
|
+
"eip-3009",
|
|
56
|
+
"micropayments"
|
|
53
57
|
],
|
|
54
58
|
"author": "robit-man",
|
|
55
59
|
"license": "MIT",
|
|
@@ -70,6 +74,7 @@
|
|
|
70
74
|
},
|
|
71
75
|
"optionalDependencies": {
|
|
72
76
|
"moondream": "^0.2.0",
|
|
73
|
-
"open-agents-nexus": "^1.5.
|
|
77
|
+
"open-agents-nexus": "^1.5.6",
|
|
78
|
+
"viem": "^2.47.4"
|
|
74
79
|
}
|
|
75
80
|
}
|
|
@@ -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.5.
|
|
188
|
+
## Nexus P2P Networking (v1.5.6) — Decentralized Agent Communication + x402 Payments
|
|
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
|
|
|
@@ -263,13 +263,46 @@ expose queries local Ollama for models, fetches live market rates from OpenRoute
|
|
|
263
263
|
nexus capability (inference:{model_name}), and writes pricing to .oa/nexus/pricing.json.
|
|
264
264
|
Peers can invoke your models via invoke_capability and see metered usage.
|
|
265
265
|
|
|
266
|
+
### x402 Payment Rails (native, wired to open-agents-nexus@1.5.6)
|
|
267
|
+
|
|
268
|
+
wallet_create generates a secp256k1/EVM wallet on Base mainnet. An `x402-wallet.key` file
|
|
269
|
+
is auto-created alongside `wallet.enc` for the daemon's x402 module. When margin > 0 in
|
|
270
|
+
expose, registerCapability passes pricing metadata — the daemon auto-handles
|
|
271
|
+
`invoke.payment_required` → `payment_proof` negotiation.
|
|
272
|
+
|
|
273
|
+
nexus(action='wallet_create') — generate new EVM wallet (secp256k1, Base, USDC)
|
|
274
|
+
nexus(action='wallet_create', wallet_address='0x...') — register existing address (no x402 signing)
|
|
275
|
+
nexus(action='wallet_status') — address, USDC balance, ledger summary
|
|
276
|
+
|
|
277
|
+
### Ledger & Budget
|
|
278
|
+
nexus(action='ledger_status') — transaction history (earned/spent/pending)
|
|
279
|
+
nexus(action='budget_status') — spending limits and today's usage
|
|
280
|
+
nexus(action='budget_set', daily_limit='1.00') — set daily USDC limit
|
|
281
|
+
nexus(action='budget_set', per_invoke_max='0.10') — max per invocation
|
|
282
|
+
nexus(action='budget_set', auto_approve_below='0.01') — auto-approve micropayments
|
|
283
|
+
|
|
284
|
+
### Spend — Agent-Initiated USDC Transfer (EIP-3009)
|
|
285
|
+
nexus(action='spend', target_address='0x...', amount_usdc='0.10')
|
|
286
|
+
|
|
287
|
+
Signs an EIP-3009 TransferWithAuthorization for USDC on Base. Budget-checked before signing.
|
|
288
|
+
The signed proof is saved to `.oa/nexus/pending-transfer.json` — anyone can submit it on-chain
|
|
289
|
+
via `USDC.transferWithAuthorization()`. No gas needed from the payer.
|
|
290
|
+
|
|
291
|
+
### x402 Flow Summary
|
|
292
|
+
1. wallet_create → generates wallet + x402-wallet.key (plaintext, 0600, for daemon)
|
|
293
|
+
2. expose with margin > 0 → registers capabilities with USDC pricing
|
|
294
|
+
3. Peers invoke_capability → daemon auto-handles payment_required/payment_proof
|
|
295
|
+
4. Metering hook writes payment events to ledger.jsonl
|
|
296
|
+
5. spend → sign direct USDC transfers (EIP-3009)
|
|
297
|
+
|
|
266
298
|
SECURITY: Wallet private keys are AES-256-GCM encrypted and NEVER accessible to you.
|
|
267
|
-
All outbound messages are scanned
|
|
299
|
+
x402-wallet.key is 0600-permissioned for daemon use only. All outbound messages are scanned
|
|
300
|
+
for key material leaks.
|
|
268
301
|
|
|
269
302
|
When the user asks about expanding capabilities or connecting with other agents, suggest
|
|
270
303
|
enabling nexus networking. Use expose to share your models with the network, pricing_menu
|
|
271
|
-
to check rates, register_capability to serve custom invocations,
|
|
272
|
-
discover who's online.
|
|
304
|
+
to check rates, register_capability to serve custom invocations, room_members to
|
|
305
|
+
discover who's online, and spend for direct USDC transfers.
|
|
273
306
|
|
|
274
307
|
## Temporal Agency — Scheduling, Reminders & Long-Horizon Tasks
|
|
275
308
|
|