open-agents-ai 0.87.0 → 0.89.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 +9 -0
- package/dist/index.js +592 -38
- package/package.json +1 -1
- package/prompts/agentic/system-large.md +18 -0
package/README.md
CHANGED
|
@@ -999,6 +999,7 @@ The steering sub-agent uses the same model and backend as the main agent with `m
|
|
|
999
999
|
| `nexus:expose` | Expose local Ollama models as metered inference capabilities with OpenRouter-based pricing |
|
|
1000
1000
|
| `nexus:wallet_create` | Generate secp256k1/EVM wallet (Base mainnet USDC) with AES-256-GCM encryption + x402-wallet.key |
|
|
1001
1001
|
| `nexus:spend` | Sign EIP-3009 USDC TransferWithAuthorization — budget-checked, gasless for payer |
|
|
1002
|
+
| `nexus:remote_infer` | Route inference to a remote peer's model — auto-discovers peers, budget-checks, invokes, returns result |
|
|
1002
1003
|
| `nexus:ledger_status` | Transaction history (earned/spent/pending USDC) |
|
|
1003
1004
|
| `nexus:budget_set` | Configure spending limits — daily cap, per-invoke max, auto-approve threshold |
|
|
1004
1005
|
|
|
@@ -1077,6 +1078,13 @@ nexus(action='spend', target_address='0x...', amount_usdc='0.10')
|
|
|
1077
1078
|
```
|
|
1078
1079
|
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
|
|
|
1081
|
+
### Remote Inference — Tap Into the Mesh
|
|
1082
|
+
```
|
|
1083
|
+
nexus(action='remote_infer', model='qwen3.5:70b', prompt='Complex analysis task...')
|
|
1084
|
+
nexus(action='remote_infer', model='llama3.3:70b', prompt='...', target_peer='12D3KooW...')
|
|
1085
|
+
```
|
|
1086
|
+
Route a prompt to a remote peer's model on the P2P mesh. Auto-discovers peers that have the requested model exposed, budget-checks the estimated cost, invokes the inference capability, and returns the response. Use `target_peer` to route to a specific provider, or omit for automatic peer selection. Your 8B laptop can seamlessly tap into a 122B model running on the mesh.
|
|
1087
|
+
|
|
1080
1088
|
### Ledger & Budget
|
|
1081
1089
|
```
|
|
1082
1090
|
nexus(action='ledger_status') # Earned/spent/pending history
|
|
@@ -1093,6 +1101,7 @@ nexus(action='budget_set', auto_approve_below='0.01') # Auto-approve micropayme
|
|
|
1093
1101
|
4. Consumer's daemon auto-signs `payment_proof` → provider validates → invoke proceeds
|
|
1094
1102
|
5. Metering hook writes payment events to `ledger.jsonl`
|
|
1095
1103
|
6. **spend** → direct agent-to-agent USDC transfers (EIP-3009, gasless)
|
|
1104
|
+
7. **remote_infer** → auto-discover + invoke in one action (budget-checked, with ledger entry)
|
|
1096
1105
|
|
|
1097
1106
|
### Security Model
|
|
1098
1107
|
- Private keys: AES-256-GCM encrypted in `wallet.enc` (scrypt-derived key)
|
package/dist/index.js
CHANGED
|
@@ -139,7 +139,7 @@ var init_config = __esm({
|
|
|
139
139
|
verbose: false,
|
|
140
140
|
dbPath: join(homedir(), ".open-agents", "memory.db")
|
|
141
141
|
});
|
|
142
|
-
VALID_BACKEND_TYPES = /* @__PURE__ */ new Set(["ollama", "vllm", "fake"]);
|
|
142
|
+
VALID_BACKEND_TYPES = /* @__PURE__ */ new Set(["ollama", "vllm", "fake", "nexus"]);
|
|
143
143
|
}
|
|
144
144
|
});
|
|
145
145
|
|
|
@@ -11202,7 +11202,7 @@ ${truncated}`, durationMs: performance.now() - start };
|
|
|
11202
11202
|
});
|
|
11203
11203
|
|
|
11204
11204
|
// packages/execution/dist/tools/nexus.js
|
|
11205
|
-
import { readFile as readFile13, writeFile as writeFile12, mkdir as mkdir8, chmod, unlink, readdir as readdir3 } from "node:fs/promises";
|
|
11205
|
+
import { readFile as readFile13, writeFile as writeFile12, mkdir as mkdir8, chmod, unlink, readdir as readdir3, open as fsOpen } from "node:fs/promises";
|
|
11206
11206
|
import { existsSync as existsSync21, readFileSync as readFileSync15 } from "node:fs";
|
|
11207
11207
|
import { resolve as resolve26, join as join28 } from "node:path";
|
|
11208
11208
|
import { randomBytes as randomBytes6, createCipheriv, createDecipheriv, scryptSync, createHash } from "node:crypto";
|
|
@@ -11405,6 +11405,101 @@ async function handleCmd(cmd) {
|
|
|
11405
11405
|
}
|
|
11406
11406
|
break;
|
|
11407
11407
|
}
|
|
11408
|
+
case 'remote_infer': {
|
|
11409
|
+
var riModel = args.model;
|
|
11410
|
+
var riPrompt = args.prompt || args.input || '';
|
|
11411
|
+
var riMessages = args.messages || null;
|
|
11412
|
+
var riTools = args.tools || null;
|
|
11413
|
+
var riTargetPeer = args.target_peer || '';
|
|
11414
|
+
if (!riModel) { writeResp(id, { ok: false, output: 'model is required' }); return; }
|
|
11415
|
+
if (!riPrompt && !riMessages) { writeResp(id, { ok: false, output: 'prompt or messages is required' }); return; }
|
|
11416
|
+
|
|
11417
|
+
// Build invoke data \u2014 structured (messages+tools) or flat (prompt)
|
|
11418
|
+
var riData;
|
|
11419
|
+
if (riMessages) {
|
|
11420
|
+
var riParsedMsgs = typeof riMessages === 'string' ? JSON.parse(riMessages) : riMessages;
|
|
11421
|
+
riData = { messages: riParsedMsgs };
|
|
11422
|
+
if (riTools) {
|
|
11423
|
+
riData.tools = typeof riTools === 'string' ? JSON.parse(riTools) : riTools;
|
|
11424
|
+
}
|
|
11425
|
+
if (args.temperature !== undefined) riData.temperature = Number(args.temperature);
|
|
11426
|
+
if (args.max_tokens) riData.max_tokens = Number(args.max_tokens);
|
|
11427
|
+
} else {
|
|
11428
|
+
riData = { prompt: riPrompt };
|
|
11429
|
+
}
|
|
11430
|
+
|
|
11431
|
+
// Derive capability name from model
|
|
11432
|
+
var riCapName = 'inference:' + riModel.replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
11433
|
+
dlog('remote_infer: model=' + riModel + ' cap=' + riCapName + ' prompt_len=' + riPrompt.length);
|
|
11434
|
+
|
|
11435
|
+
// If target_peer specified, invoke directly. Otherwise auto-discover.
|
|
11436
|
+
if (riTargetPeer) {
|
|
11437
|
+
// Direct invoke on specified peer
|
|
11438
|
+
dlog('remote_infer: invoking ' + riCapName + ' on explicit peer ' + riTargetPeer.slice(0, 20));
|
|
11439
|
+
try {
|
|
11440
|
+
var RI_TIMEOUT = 120000;
|
|
11441
|
+
var riInvokeP = nexus.invokeCapability(riTargetPeer, riCapName, riData, { stream: false, maxDurationMs: 90000 });
|
|
11442
|
+
var riTimeoutP = new Promise(function(_, reject) { setTimeout(function() { reject(new Error('remote_infer timed out after ' + (RI_TIMEOUT / 1000) + 's')); }, RI_TIMEOUT); });
|
|
11443
|
+
var riResult = await Promise.race([riInvokeP, riTimeoutP]);
|
|
11444
|
+
dlog('remote_infer: SUCCESS (explicit peer)');
|
|
11445
|
+
} catch (riErr) {
|
|
11446
|
+
dlog('remote_infer: ERROR (explicit peer) ' + (riErr.message || String(riErr)));
|
|
11447
|
+
writeResp(id, { ok: false, output: 'Remote inference failed: ' + (riErr.message || String(riErr)) });
|
|
11448
|
+
return;
|
|
11449
|
+
}
|
|
11450
|
+
} else {
|
|
11451
|
+
// Auto-discover: try speculative invoke on each 12D3KooW peer
|
|
11452
|
+
// Skip Qm-prefixed bootstrap/relay nodes \u2014 they never host inference
|
|
11453
|
+
var discStart = Date.now();
|
|
11454
|
+
var riResult = null;
|
|
11455
|
+
try {
|
|
11456
|
+
var riNode = nexus.network ? nexus.network.node : null;
|
|
11457
|
+
var riPeers = riNode && typeof riNode.getPeers === 'function' ? riNode.getPeers() : [];
|
|
11458
|
+
var riCandidates = [];
|
|
11459
|
+
for (var rfi = 0; rfi < riPeers.length; rfi++) {
|
|
11460
|
+
var rpStr = riPeers[rfi].toString ? riPeers[rfi].toString() : String(riPeers[rfi]);
|
|
11461
|
+
if (rpStr.startsWith('12D3KooW')) riCandidates.push(rpStr);
|
|
11462
|
+
}
|
|
11463
|
+
dlog('remote_infer: auto-discover across ' + riCandidates.length + ' peers (of ' + riPeers.length + ' total) for ' + riCapName);
|
|
11464
|
+
|
|
11465
|
+
for (var ri = 0; ri < riCandidates.length && !riResult; ri++) {
|
|
11466
|
+
var rPid = riCandidates[ri];
|
|
11467
|
+
dlog('remote_infer: trying peer ' + rPid.slice(0, 20) + '...');
|
|
11468
|
+
try {
|
|
11469
|
+
var PEER_INVOKE_TIMEOUT = 30000; // 30s per peer attempt
|
|
11470
|
+
var specInvoke = nexus.invokeCapability(rPid, riCapName, riData, { stream: false, maxDurationMs: 25000 });
|
|
11471
|
+
var specTimeout = new Promise(function(_, reject) {
|
|
11472
|
+
setTimeout(function() { reject(new Error('peer invoke timeout')); }, PEER_INVOKE_TIMEOUT);
|
|
11473
|
+
});
|
|
11474
|
+
riResult = await Promise.race([specInvoke, specTimeout]);
|
|
11475
|
+
riTargetPeer = rPid;
|
|
11476
|
+
dlog('remote_infer: SUCCESS via peer ' + rPid.slice(0, 20) + ' (' + (Date.now() - discStart) + 'ms)');
|
|
11477
|
+
} catch (specErr) {
|
|
11478
|
+
dlog('remote_infer: peer ' + rPid.slice(0, 20) + ' failed: ' + (specErr.message || specErr));
|
|
11479
|
+
}
|
|
11480
|
+
}
|
|
11481
|
+
} catch (discErr) {
|
|
11482
|
+
dlog('remote_infer: discovery error: ' + (discErr.message || discErr));
|
|
11483
|
+
}
|
|
11484
|
+
dlog('remote_infer: auto-discover took ' + (Date.now() - discStart) + 'ms');
|
|
11485
|
+
|
|
11486
|
+
if (!riResult) {
|
|
11487
|
+
writeResp(id, { ok: false, output: 'No peer found with capability: ' + riCapName + '. Ensure a provider is running expose with model ' + riModel });
|
|
11488
|
+
return;
|
|
11489
|
+
}
|
|
11490
|
+
}
|
|
11491
|
+
|
|
11492
|
+
// riResult and riTargetPeer are available from either path above
|
|
11493
|
+
var riOutput = {
|
|
11494
|
+
success: true,
|
|
11495
|
+
model: riModel,
|
|
11496
|
+
peer: riTargetPeer,
|
|
11497
|
+
capability: riCapName,
|
|
11498
|
+
result: riResult,
|
|
11499
|
+
};
|
|
11500
|
+
writeResp(id, { ok: true, output: JSON.stringify(riOutput, null, 2) });
|
|
11501
|
+
break;
|
|
11502
|
+
}
|
|
11408
11503
|
case 'store_content': {
|
|
11409
11504
|
const data = args.data;
|
|
11410
11505
|
if (!data) { writeResp(id, { ok: false, output: 'data is required' }); return; }
|
|
@@ -11679,11 +11774,17 @@ async function handleCmd(cmd) {
|
|
|
11679
11774
|
const logFile = join(invocationsDir, Date.now() + '-' + capName + '.json');
|
|
11680
11775
|
try { writeFileSync(logFile, JSON.stringify(logEntry, null, 2)); } catch {}
|
|
11681
11776
|
|
|
11682
|
-
// Collect input
|
|
11777
|
+
// Collect input via stream data events
|
|
11683
11778
|
let prompt = '';
|
|
11779
|
+
var dataChunks = [];
|
|
11780
|
+
var inputDone = false;
|
|
11684
11781
|
stream.onData((msg) => {
|
|
11685
11782
|
if (msg.type === 'invoke.chunk') {
|
|
11686
|
-
|
|
11783
|
+
var chunk = typeof msg.data === 'string' ? msg.data : JSON.stringify(msg.data);
|
|
11784
|
+
dataChunks.push(chunk);
|
|
11785
|
+
}
|
|
11786
|
+
if (msg.type === 'invoke.done' || msg.type === 'invoke.end' || msg.type === 'invoke.close') {
|
|
11787
|
+
inputDone = true;
|
|
11687
11788
|
}
|
|
11688
11789
|
});
|
|
11689
11790
|
|
|
@@ -11693,33 +11794,100 @@ async function handleCmd(cmd) {
|
|
|
11693
11794
|
requestId: request.requestId, accepted: true,
|
|
11694
11795
|
});
|
|
11695
11796
|
|
|
11696
|
-
//
|
|
11797
|
+
// Wait for input data to arrive (invoke protocol sends data after accept)
|
|
11798
|
+
var waitMs = 0;
|
|
11799
|
+
while (!inputDone && dataChunks.length === 0 && waitMs < 5000) {
|
|
11800
|
+
await new Promise(function(r) { setTimeout(r, 100); });
|
|
11801
|
+
waitMs += 100;
|
|
11802
|
+
}
|
|
11803
|
+
prompt = dataChunks.join('');
|
|
11804
|
+
dlog('expose: received ' + dataChunks.length + ' chunks, prompt_len=' + prompt.length + ' inputDone=' + inputDone);
|
|
11805
|
+
|
|
11806
|
+
// Forward to Ollama \u2014 supports both flat prompt and structured messages
|
|
11697
11807
|
try {
|
|
11698
|
-
|
|
11699
|
-
|
|
11700
|
-
|
|
11701
|
-
|
|
11808
|
+
var parsedReq = null;
|
|
11809
|
+
try { parsedReq = JSON.parse(prompt); } catch (pe) { dlog('expose: JSON parse error: ' + (pe.message || pe)); }
|
|
11810
|
+
|
|
11811
|
+
var genResp, genData, output, inputTokens, outputTokens, responsePayload;
|
|
11812
|
+
|
|
11813
|
+
if (parsedReq && parsedReq.messages && Array.isArray(parsedReq.messages)) {
|
|
11814
|
+
// Structured request \u2014 use /v1/chat/completions (supports tools + multi-turn)
|
|
11815
|
+
var chatBody = {
|
|
11702
11816
|
model: model.name,
|
|
11703
|
-
|
|
11817
|
+
messages: parsedReq.messages,
|
|
11704
11818
|
stream: false,
|
|
11705
|
-
}
|
|
11706
|
-
|
|
11707
|
-
|
|
11708
|
-
|
|
11709
|
-
|
|
11710
|
-
|
|
11819
|
+
};
|
|
11820
|
+
if (parsedReq.tools && parsedReq.tools.length > 0) {
|
|
11821
|
+
chatBody.tools = parsedReq.tools;
|
|
11822
|
+
}
|
|
11823
|
+
if (parsedReq.temperature !== undefined) chatBody.temperature = parsedReq.temperature;
|
|
11824
|
+
if (parsedReq.max_tokens) chatBody.max_tokens = parsedReq.max_tokens;
|
|
11825
|
+
|
|
11826
|
+
dlog('expose: calling /v1/chat/completions with ' + chatBody.messages.length + ' messages, tools=' + (chatBody.tools ? chatBody.tools.length : 0));
|
|
11827
|
+
genResp = await fetch(ollamaUrl + '/v1/chat/completions', {
|
|
11828
|
+
method: 'POST',
|
|
11829
|
+
headers: { 'Content-Type': 'application/json' },
|
|
11830
|
+
body: JSON.stringify(chatBody),
|
|
11831
|
+
});
|
|
11832
|
+
genData = await genResp.json();
|
|
11833
|
+
dlog('expose: ollama response keys=' + Object.keys(genData).join(',') + ' choices=' + (genData.choices ? genData.choices.length : 0));
|
|
11834
|
+
|
|
11835
|
+
var chatChoices = genData.choices || [];
|
|
11836
|
+
var firstMsg = (chatChoices[0] || {}).message || {};
|
|
11837
|
+
// Strip <think>...</think> tags from content (thinking models)
|
|
11838
|
+
var rawContent = firstMsg.content || '';
|
|
11839
|
+
output = rawContent.replace(/<think>[\\s\\S]*?<\\/think>/g, '').trim();
|
|
11840
|
+
// Fallback: if content is empty but reasoning exists, use reasoning
|
|
11841
|
+
// (happens when max_tokens is too small for thinking models at temp=0)
|
|
11842
|
+
if (!output && firstMsg.reasoning) {
|
|
11843
|
+
output = firstMsg.reasoning;
|
|
11844
|
+
}
|
|
11845
|
+
// Update the choices to reflect final content
|
|
11846
|
+
if (chatChoices[0] && chatChoices[0].message) {
|
|
11847
|
+
chatChoices[0].message.content = output;
|
|
11848
|
+
}
|
|
11849
|
+
dlog('expose: rawContent_len=' + rawContent.length + ' output_len=' + output.length + ' reasoning=' + (firstMsg.reasoning ? firstMsg.reasoning.length : 0) + ' tool_calls=' + (firstMsg.tool_calls ? firstMsg.tool_calls.length : 0));
|
|
11850
|
+
inputTokens = (genData.usage || {}).prompt_tokens || 0;
|
|
11851
|
+
outputTokens = (genData.usage || {}).completion_tokens || 0;
|
|
11852
|
+
|
|
11853
|
+
responsePayload = {
|
|
11854
|
+
model: model.name,
|
|
11855
|
+
response: output,
|
|
11856
|
+
choices: chatChoices,
|
|
11857
|
+
usage: { input_tokens: inputTokens, output_tokens: outputTokens },
|
|
11858
|
+
pricing: entry.pricing,
|
|
11859
|
+
};
|
|
11860
|
+
} else {
|
|
11861
|
+
// Flat prompt \u2014 use /api/generate (original behavior)
|
|
11862
|
+
var flatPrompt = parsedReq && parsedReq.prompt ? parsedReq.prompt : (prompt || 'Hello');
|
|
11863
|
+
genResp = await fetch(ollamaUrl + '/api/generate', {
|
|
11864
|
+
method: 'POST',
|
|
11865
|
+
headers: { 'Content-Type': 'application/json' },
|
|
11866
|
+
body: JSON.stringify({
|
|
11867
|
+
model: model.name,
|
|
11868
|
+
prompt: flatPrompt,
|
|
11869
|
+
stream: false,
|
|
11870
|
+
}),
|
|
11871
|
+
});
|
|
11872
|
+
genData = await genResp.json();
|
|
11873
|
+
output = genData.response || '';
|
|
11874
|
+
inputTokens = genData.prompt_eval_count || 0;
|
|
11875
|
+
outputTokens = genData.eval_count || 0;
|
|
11876
|
+
|
|
11877
|
+
responsePayload = {
|
|
11878
|
+
model: model.name,
|
|
11879
|
+
response: output,
|
|
11880
|
+
usage: { input_tokens: inputTokens, output_tokens: outputTokens },
|
|
11881
|
+
pricing: entry.pricing,
|
|
11882
|
+
};
|
|
11883
|
+
}
|
|
11711
11884
|
|
|
11712
11885
|
// Stream result back
|
|
11713
11886
|
await stream.write({
|
|
11714
11887
|
type: 'invoke.event', version: 1,
|
|
11715
11888
|
requestId: request.requestId, seq: 0,
|
|
11716
11889
|
event: 'result',
|
|
11717
|
-
data: JSON.stringify(
|
|
11718
|
-
model: model.name,
|
|
11719
|
-
response: output,
|
|
11720
|
-
usage: { input_tokens: inputTokens, output_tokens: outputTokens },
|
|
11721
|
-
pricing: entry.pricing,
|
|
11722
|
-
}),
|
|
11890
|
+
data: JSON.stringify(responsePayload),
|
|
11723
11891
|
});
|
|
11724
11892
|
|
|
11725
11893
|
await stream.write({
|
|
@@ -11727,7 +11895,7 @@ async function handleCmd(cmd) {
|
|
|
11727
11895
|
requestId: request.requestId,
|
|
11728
11896
|
usage: {
|
|
11729
11897
|
inputBytes: prompt.length,
|
|
11730
|
-
outputBytes: output.length,
|
|
11898
|
+
outputBytes: (output || '').length,
|
|
11731
11899
|
tokens: inputTokens + outputTokens,
|
|
11732
11900
|
},
|
|
11733
11901
|
});
|
|
@@ -11932,7 +12100,7 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
11932
12100
|
];
|
|
11933
12101
|
NexusTool = class {
|
|
11934
12102
|
name = "nexus";
|
|
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.";
|
|
12103
|
+
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.";
|
|
11936
12104
|
parameters = {
|
|
11937
12105
|
type: "object",
|
|
11938
12106
|
properties: {
|
|
@@ -11968,7 +12136,8 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
11968
12136
|
"ledger_status",
|
|
11969
12137
|
"budget_status",
|
|
11970
12138
|
"budget_set",
|
|
11971
|
-
"spend"
|
|
12139
|
+
"spend",
|
|
12140
|
+
"remote_infer"
|
|
11972
12141
|
],
|
|
11973
12142
|
description: "The nexus action to perform"
|
|
11974
12143
|
},
|
|
@@ -12043,6 +12212,22 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
12043
12212
|
amount_usdc: {
|
|
12044
12213
|
type: "string",
|
|
12045
12214
|
description: "For spend: USDC amount to transfer (e.g. '0.10')"
|
|
12215
|
+
},
|
|
12216
|
+
model: {
|
|
12217
|
+
type: "string",
|
|
12218
|
+
description: "For remote_infer: model to request (e.g. 'qwen3.5:70b', 'nemotron-3-nano:30b')"
|
|
12219
|
+
},
|
|
12220
|
+
prompt: {
|
|
12221
|
+
type: "string",
|
|
12222
|
+
description: "For remote_infer: the prompt text to send to the remote model"
|
|
12223
|
+
},
|
|
12224
|
+
temperature: {
|
|
12225
|
+
type: "string",
|
|
12226
|
+
description: "For remote_infer: sampling temperature (e.g. '0.7'). Default: 0.7"
|
|
12227
|
+
},
|
|
12228
|
+
max_tokens: {
|
|
12229
|
+
type: "string",
|
|
12230
|
+
description: "For remote_infer: maximum tokens to generate (e.g. '4096'). Default: 4096"
|
|
12046
12231
|
}
|
|
12047
12232
|
},
|
|
12048
12233
|
required: ["action"],
|
|
@@ -12155,6 +12340,9 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
12155
12340
|
case "spend":
|
|
12156
12341
|
result = await this.doSpend(args);
|
|
12157
12342
|
break;
|
|
12343
|
+
case "remote_infer":
|
|
12344
|
+
result = await this.doRemoteInfer(args);
|
|
12345
|
+
break;
|
|
12158
12346
|
default:
|
|
12159
12347
|
return { success: false, output: "", error: `Unknown nexus action: ${action}`, durationMs: Date.now() - start };
|
|
12160
12348
|
}
|
|
@@ -12204,6 +12392,14 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
12204
12392
|
}
|
|
12205
12393
|
return `Daemon did not respond within ${Math.round(timeoutMs / 1e3)}s. It may still be processing.`;
|
|
12206
12394
|
}
|
|
12395
|
+
/** Public interface for daemon commands — used by NexusAgenticBackend */
|
|
12396
|
+
async sendCommand(action, args, timeoutMs) {
|
|
12397
|
+
return this.sendDaemonCmd(action, args, timeoutMs);
|
|
12398
|
+
}
|
|
12399
|
+
/** Get the nexus data directory path */
|
|
12400
|
+
getNexusDir() {
|
|
12401
|
+
return this.nexusDir;
|
|
12402
|
+
}
|
|
12207
12403
|
// =========================================================================
|
|
12208
12404
|
// Actions
|
|
12209
12405
|
// =========================================================================
|
|
@@ -12551,8 +12747,9 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
12551
12747
|
network: "base",
|
|
12552
12748
|
chainId: 8453
|
|
12553
12749
|
};
|
|
12554
|
-
await
|
|
12555
|
-
await
|
|
12750
|
+
const walletFh = await fsOpen(walletPath, "w", 384);
|
|
12751
|
+
await walletFh.writeFile(JSON.stringify(w2, null, 2));
|
|
12752
|
+
await walletFh.close();
|
|
12556
12753
|
await this.ensureDefaultBudget();
|
|
12557
12754
|
return `Wallet configured: ${userAddress} (user-managed, Base mainnet, no x402 key \u2014 spend/sign requires self-hosted wallet)`;
|
|
12558
12755
|
}
|
|
@@ -12578,8 +12775,9 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
12578
12775
|
let enc = cipher.update(privKeyHex, "utf8", "hex");
|
|
12579
12776
|
enc += cipher.final("hex");
|
|
12580
12777
|
const x402KeyPath = join28(this.nexusDir, "x402-wallet.key");
|
|
12581
|
-
await
|
|
12582
|
-
await
|
|
12778
|
+
const x402Fh = await fsOpen(x402KeyPath, "w", 384);
|
|
12779
|
+
await x402Fh.writeFile("0x" + privKeyHex);
|
|
12780
|
+
await x402Fh.close();
|
|
12583
12781
|
privKeyHex = "0".repeat(64);
|
|
12584
12782
|
const w = {
|
|
12585
12783
|
version: 2,
|
|
@@ -12592,8 +12790,9 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
12592
12790
|
network: "base",
|
|
12593
12791
|
chainId: 8453
|
|
12594
12792
|
};
|
|
12595
|
-
await
|
|
12596
|
-
await
|
|
12793
|
+
const walletFh2 = await fsOpen(walletPath, "w", 384);
|
|
12794
|
+
await walletFh2.writeFile(JSON.stringify(w, null, 2));
|
|
12795
|
+
await walletFh2.close();
|
|
12597
12796
|
await this.ensureDefaultBudget();
|
|
12598
12797
|
return [
|
|
12599
12798
|
`Wallet created (v2, secp256k1/EVM):`,
|
|
@@ -12748,7 +12947,7 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
12748
12947
|
// $0.50 — warn when low
|
|
12749
12948
|
circuitBreakerUsdc: 1e5,
|
|
12750
12949
|
// $0.10 — refuse payments below this balance
|
|
12751
|
-
allowedCapabilities: ["inference:*", "transfer:direct"],
|
|
12950
|
+
allowedCapabilities: ["inference:*", "transfer:direct", "remote_infer"],
|
|
12752
12951
|
deniedPeers: []
|
|
12753
12952
|
};
|
|
12754
12953
|
await this.ensureDir();
|
|
@@ -12878,7 +13077,7 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
12878
13077
|
const amountSmallest = Math.round(parseFloat(amountUsd) * 1e6);
|
|
12879
13078
|
if (amountSmallest <= 0)
|
|
12880
13079
|
throw new Error("amount must be positive");
|
|
12881
|
-
const budgetResult = await this.checkBudget(amountSmallest, "transfer:direct",
|
|
13080
|
+
const budgetResult = await this.checkBudget(amountSmallest, "transfer:direct", targetAddress);
|
|
12882
13081
|
if (!budgetResult.allowed)
|
|
12883
13082
|
return `BLOCKED by budget policy: ${budgetResult.reason}`;
|
|
12884
13083
|
const walletPath = join28(this.nexusDir, "wallet.enc");
|
|
@@ -12972,6 +13171,109 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
|
|
|
12972
13171
|
privKeyHex = "0".repeat(64);
|
|
12973
13172
|
}
|
|
12974
13173
|
}
|
|
13174
|
+
// ---------------------------------------------------------------------------
|
|
13175
|
+
// Step 6: Remote Inference — Route inference to remote peers on the mesh
|
|
13176
|
+
// ---------------------------------------------------------------------------
|
|
13177
|
+
async doRemoteInfer(args) {
|
|
13178
|
+
const model = args.model;
|
|
13179
|
+
const prompt = args.prompt || args.input;
|
|
13180
|
+
if (!model)
|
|
13181
|
+
throw new Error("model is required (e.g., 'qwen3.5:70b', 'nemotron-3-nano:30b')");
|
|
13182
|
+
if (!prompt)
|
|
13183
|
+
throw new Error("prompt is required");
|
|
13184
|
+
const estimatedTokens = Math.ceil(prompt.length / 4) + 1e3;
|
|
13185
|
+
let estimatedCostSmallest = 0;
|
|
13186
|
+
const pricingPath = join28(this.nexusDir, "pricing.json");
|
|
13187
|
+
if (existsSync21(pricingPath)) {
|
|
13188
|
+
try {
|
|
13189
|
+
const pricing = JSON.parse(await readFile13(pricingPath, "utf8"));
|
|
13190
|
+
const modelPricing = (pricing.models || []).find((m) => m.model === model || m.model.startsWith(model.split(":")[0]));
|
|
13191
|
+
if (modelPricing?.pricing?.input_per_1m_tokens > 0) {
|
|
13192
|
+
estimatedCostSmallest = Math.ceil(estimatedTokens / 1e6 * modelPricing.pricing.input_per_1m_tokens * 1e6);
|
|
13193
|
+
}
|
|
13194
|
+
} catch {
|
|
13195
|
+
}
|
|
13196
|
+
}
|
|
13197
|
+
if (estimatedCostSmallest > 0) {
|
|
13198
|
+
const budgetResult = await this.checkBudget(estimatedCostSmallest, "remote_infer", args.target_peer || "");
|
|
13199
|
+
if (!budgetResult.allowed) {
|
|
13200
|
+
return `BLOCKED by budget policy: ${budgetResult.reason}`;
|
|
13201
|
+
}
|
|
13202
|
+
}
|
|
13203
|
+
const budget = await this.loadBudget();
|
|
13204
|
+
if (budget && !budget.allowedCapabilities.some((c3) => c3 === "remote_infer" || c3 === "inference:*" || c3 === "*")) {
|
|
13205
|
+
return "BLOCKED: remote_infer not in budget allowedCapabilities. Use budget_set to allow it.";
|
|
13206
|
+
}
|
|
13207
|
+
const daemonArgs = {
|
|
13208
|
+
model,
|
|
13209
|
+
target_peer: args.target_peer,
|
|
13210
|
+
temperature: args.temperature,
|
|
13211
|
+
max_tokens: args.max_tokens
|
|
13212
|
+
};
|
|
13213
|
+
if (args.messages) {
|
|
13214
|
+
daemonArgs.messages = args.messages;
|
|
13215
|
+
if (args.tools)
|
|
13216
|
+
daemonArgs.tools = args.tools;
|
|
13217
|
+
} else {
|
|
13218
|
+
daemonArgs.prompt = prompt;
|
|
13219
|
+
}
|
|
13220
|
+
const result = await this.sendDaemonCmd("remote_infer", daemonArgs, 12e4);
|
|
13221
|
+
let peerUsed = args.target_peer || "auto-selected";
|
|
13222
|
+
try {
|
|
13223
|
+
const parsed = JSON.parse(result);
|
|
13224
|
+
if (parsed.peer)
|
|
13225
|
+
peerUsed = parsed.peer;
|
|
13226
|
+
if (estimatedCostSmallest > 0) {
|
|
13227
|
+
await this.appendLedger({
|
|
13228
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13229
|
+
type: "spent",
|
|
13230
|
+
amount: String(estimatedCostSmallest),
|
|
13231
|
+
amountUsd: (estimatedCostSmallest / 1e6).toFixed(6),
|
|
13232
|
+
peer: peerUsed,
|
|
13233
|
+
capability: "remote_infer:" + model,
|
|
13234
|
+
note: "remote inference"
|
|
13235
|
+
});
|
|
13236
|
+
}
|
|
13237
|
+
const inferResult = parsed.result;
|
|
13238
|
+
let responseText = "";
|
|
13239
|
+
if (typeof inferResult === "string") {
|
|
13240
|
+
responseText = inferResult;
|
|
13241
|
+
} else if (inferResult) {
|
|
13242
|
+
const events = Array.isArray(inferResult) ? inferResult : [inferResult];
|
|
13243
|
+
for (const evt of events) {
|
|
13244
|
+
if (evt.event === "result" && evt.data) {
|
|
13245
|
+
try {
|
|
13246
|
+
const data = typeof evt.data === "string" ? JSON.parse(evt.data) : evt.data;
|
|
13247
|
+
responseText = data.response || data.content || JSON.stringify(data);
|
|
13248
|
+
} catch {
|
|
13249
|
+
responseText = String(evt.data);
|
|
13250
|
+
}
|
|
13251
|
+
}
|
|
13252
|
+
}
|
|
13253
|
+
if (!responseText) {
|
|
13254
|
+
responseText = JSON.stringify(inferResult, null, 2);
|
|
13255
|
+
}
|
|
13256
|
+
}
|
|
13257
|
+
const lines = [
|
|
13258
|
+
`Remote inference result (${model} via ${peerUsed.slice(0, 20)}...):`,
|
|
13259
|
+
``,
|
|
13260
|
+
responseText,
|
|
13261
|
+
``,
|
|
13262
|
+
`--- Metadata ---`,
|
|
13263
|
+
`Model: ${parsed.model || model}`,
|
|
13264
|
+
`Peer: ${peerUsed}`,
|
|
13265
|
+
`Capability: ${parsed.capability || "inference:" + model}`
|
|
13266
|
+
];
|
|
13267
|
+
if (estimatedCostSmallest > 0) {
|
|
13268
|
+
lines.push(`Estimated cost: $${(estimatedCostSmallest / 1e6).toFixed(6)} USDC`);
|
|
13269
|
+
} else {
|
|
13270
|
+
lines.push(`Cost: FREE (provider margin = 0)`);
|
|
13271
|
+
}
|
|
13272
|
+
return lines.join("\n");
|
|
13273
|
+
} catch {
|
|
13274
|
+
return result;
|
|
13275
|
+
}
|
|
13276
|
+
}
|
|
12975
13277
|
async doInferenceProof() {
|
|
12976
13278
|
try {
|
|
12977
13279
|
const psRaw = execSync21("ollama ps 2>/dev/null", { timeout: 1e4, encoding: "utf8" });
|
|
@@ -17924,6 +18226,218 @@ ${transcript}`
|
|
|
17924
18226
|
}
|
|
17925
18227
|
});
|
|
17926
18228
|
|
|
18229
|
+
// packages/orchestrator/dist/nexusBackend.js
|
|
18230
|
+
var NexusAgenticBackend;
|
|
18231
|
+
var init_nexusBackend = __esm({
|
|
18232
|
+
"packages/orchestrator/dist/nexusBackend.js"() {
|
|
18233
|
+
"use strict";
|
|
18234
|
+
NexusAgenticBackend = class {
|
|
18235
|
+
sendFn;
|
|
18236
|
+
model;
|
|
18237
|
+
targetPeer;
|
|
18238
|
+
constructor(sendFn, model, targetPeer) {
|
|
18239
|
+
this.sendFn = sendFn;
|
|
18240
|
+
this.model = model;
|
|
18241
|
+
this.targetPeer = targetPeer || "";
|
|
18242
|
+
}
|
|
18243
|
+
async chatCompletion(request) {
|
|
18244
|
+
const daemonArgs = {
|
|
18245
|
+
model: this.model,
|
|
18246
|
+
messages: JSON.stringify(request.messages),
|
|
18247
|
+
tools: JSON.stringify(request.tools),
|
|
18248
|
+
temperature: String(request.temperature),
|
|
18249
|
+
max_tokens: String(request.maxTokens)
|
|
18250
|
+
};
|
|
18251
|
+
if (this.targetPeer) {
|
|
18252
|
+
daemonArgs.target_peer = this.targetPeer;
|
|
18253
|
+
}
|
|
18254
|
+
const rawResult = await this.sendFn("remote_infer", daemonArgs, request.timeoutMs || 12e4);
|
|
18255
|
+
let parsed;
|
|
18256
|
+
try {
|
|
18257
|
+
parsed = JSON.parse(rawResult);
|
|
18258
|
+
} catch {
|
|
18259
|
+
throw new Error(`Invalid response from nexus daemon: ${rawResult.slice(0, 200)}`);
|
|
18260
|
+
}
|
|
18261
|
+
if (parsed.success === false) {
|
|
18262
|
+
throw new Error(`Remote inference failed: ${parsed.output || rawResult}`);
|
|
18263
|
+
}
|
|
18264
|
+
const invokeResult = parsed.result;
|
|
18265
|
+
let responseData = null;
|
|
18266
|
+
if (typeof invokeResult === "string") {
|
|
18267
|
+
try {
|
|
18268
|
+
responseData = JSON.parse(invokeResult);
|
|
18269
|
+
} catch {
|
|
18270
|
+
responseData = { response: invokeResult };
|
|
18271
|
+
}
|
|
18272
|
+
} else if (invokeResult) {
|
|
18273
|
+
const events = Array.isArray(invokeResult) ? invokeResult : [invokeResult];
|
|
18274
|
+
for (const evt of events) {
|
|
18275
|
+
if (evt.event === "result" && evt.data) {
|
|
18276
|
+
const evtData = evt.data;
|
|
18277
|
+
try {
|
|
18278
|
+
responseData = typeof evtData === "string" ? JSON.parse(evtData) : evtData;
|
|
18279
|
+
} catch {
|
|
18280
|
+
responseData = { response: String(evtData) };
|
|
18281
|
+
}
|
|
18282
|
+
}
|
|
18283
|
+
}
|
|
18284
|
+
}
|
|
18285
|
+
if (!responseData) {
|
|
18286
|
+
throw new Error("No response data from remote inference");
|
|
18287
|
+
}
|
|
18288
|
+
const choices = responseData.choices;
|
|
18289
|
+
if (choices && Array.isArray(choices)) {
|
|
18290
|
+
return {
|
|
18291
|
+
choices: choices.map((c3) => {
|
|
18292
|
+
const msg = c3.message || {};
|
|
18293
|
+
const toolCalls = msg.tool_calls || [];
|
|
18294
|
+
return {
|
|
18295
|
+
message: {
|
|
18296
|
+
content: typeof msg.content === "string" ? msg.content : msg.content ?? null,
|
|
18297
|
+
toolCalls: toolCalls.length > 0 ? toolCalls.map((tc) => {
|
|
18298
|
+
const fn = tc.function || {};
|
|
18299
|
+
let args;
|
|
18300
|
+
try {
|
|
18301
|
+
args = typeof fn.arguments === "string" ? JSON.parse(fn.arguments) : fn.arguments ?? {};
|
|
18302
|
+
} catch {
|
|
18303
|
+
args = { _raw: fn.arguments };
|
|
18304
|
+
}
|
|
18305
|
+
return {
|
|
18306
|
+
id: tc.id || crypto.randomUUID(),
|
|
18307
|
+
name: fn.name,
|
|
18308
|
+
arguments: args
|
|
18309
|
+
};
|
|
18310
|
+
}) : void 0
|
|
18311
|
+
}
|
|
18312
|
+
};
|
|
18313
|
+
}),
|
|
18314
|
+
usage: this.extractUsage(responseData)
|
|
18315
|
+
};
|
|
18316
|
+
}
|
|
18317
|
+
const content = responseData.response || JSON.stringify(responseData);
|
|
18318
|
+
return {
|
|
18319
|
+
choices: [{
|
|
18320
|
+
message: {
|
|
18321
|
+
content,
|
|
18322
|
+
toolCalls: void 0
|
|
18323
|
+
}
|
|
18324
|
+
}],
|
|
18325
|
+
usage: this.extractUsage(responseData)
|
|
18326
|
+
};
|
|
18327
|
+
}
|
|
18328
|
+
/**
|
|
18329
|
+
* Simulated streaming: calls chatCompletion() for the full response,
|
|
18330
|
+
* then yields it as word-level StreamChunk objects.
|
|
18331
|
+
* Gives API compatibility with the streaming path in AgenticRunner.
|
|
18332
|
+
*/
|
|
18333
|
+
async *chatCompletionStream(request) {
|
|
18334
|
+
const response = await this.chatCompletion(request);
|
|
18335
|
+
const choice = response.choices[0];
|
|
18336
|
+
if (!choice)
|
|
18337
|
+
return;
|
|
18338
|
+
const msg = choice.message;
|
|
18339
|
+
if (msg.content) {
|
|
18340
|
+
const words = msg.content.split(/(\s+)/);
|
|
18341
|
+
for (const word of words) {
|
|
18342
|
+
if (word) {
|
|
18343
|
+
yield { type: "content", content: word };
|
|
18344
|
+
}
|
|
18345
|
+
}
|
|
18346
|
+
}
|
|
18347
|
+
if (msg.toolCalls) {
|
|
18348
|
+
for (let i = 0; i < msg.toolCalls.length; i++) {
|
|
18349
|
+
const tc = msg.toolCalls[i];
|
|
18350
|
+
yield {
|
|
18351
|
+
type: "tool_call_delta",
|
|
18352
|
+
toolCallIndex: i,
|
|
18353
|
+
toolCallId: tc.id,
|
|
18354
|
+
toolCallName: tc.name,
|
|
18355
|
+
toolCallArgs: JSON.stringify(tc.arguments)
|
|
18356
|
+
};
|
|
18357
|
+
}
|
|
18358
|
+
}
|
|
18359
|
+
if (response.usage) {
|
|
18360
|
+
yield {
|
|
18361
|
+
type: "usage",
|
|
18362
|
+
usage: {
|
|
18363
|
+
promptTokens: response.usage.promptTokens ?? 0,
|
|
18364
|
+
completionTokens: response.usage.completionTokens ?? 0,
|
|
18365
|
+
totalTokens: response.usage.totalTokens
|
|
18366
|
+
}
|
|
18367
|
+
};
|
|
18368
|
+
}
|
|
18369
|
+
yield { type: "finish", finishReason: "stop" };
|
|
18370
|
+
}
|
|
18371
|
+
extractUsage(data) {
|
|
18372
|
+
const usage = data.usage;
|
|
18373
|
+
if (!usage)
|
|
18374
|
+
return void 0;
|
|
18375
|
+
const inputTokens = usage.input_tokens || usage.prompt_tokens || 0;
|
|
18376
|
+
const outputTokens = usage.output_tokens || usage.completion_tokens || 0;
|
|
18377
|
+
return {
|
|
18378
|
+
totalTokens: inputTokens + outputTokens,
|
|
18379
|
+
promptTokens: inputTokens,
|
|
18380
|
+
completionTokens: outputTokens
|
|
18381
|
+
};
|
|
18382
|
+
}
|
|
18383
|
+
};
|
|
18384
|
+
}
|
|
18385
|
+
});
|
|
18386
|
+
|
|
18387
|
+
// packages/orchestrator/dist/flowstatePrompt.js
|
|
18388
|
+
var FLOWSTATE_PROMPT;
|
|
18389
|
+
var init_flowstatePrompt = __esm({
|
|
18390
|
+
"packages/orchestrator/dist/flowstatePrompt.js"() {
|
|
18391
|
+
"use strict";
|
|
18392
|
+
FLOWSTATE_PROMPT = `## FlowState Protocol (Active)
|
|
18393
|
+
|
|
18394
|
+
You are operating in FlowState mode. Apply this protocol to every task:
|
|
18395
|
+
|
|
18396
|
+
### Pre-Task (Engage)
|
|
18397
|
+
- Clear context of noise \u2014 compact if SNR is degraded
|
|
18398
|
+
- Set ridiculously specific sub-goals (not "fix things" but "change line 47 of auth.ts to check for null")
|
|
18399
|
+
- Begin immediately \u2014 first tool call in first response (response inhibition)
|
|
18400
|
+
- Identify the first concrete action before reasoning further
|
|
18401
|
+
|
|
18402
|
+
### During Task (Struggle -> Release -> Flow)
|
|
18403
|
+
- Execute the highest-impact action first (goal-directedness)
|
|
18404
|
+
- Maintain tight feedback loops: execute, verify, learn (immediate feedback)
|
|
18405
|
+
- Calibrate task complexity: chunk if overwhelming, compress if trivial (challenge-skill balance)
|
|
18406
|
+
- Rotate approaches when stuck: different tools, different angles (oasis effect)
|
|
18407
|
+
- Minimize context pollution: archive large outputs, compact when needed (minimalism)
|
|
18408
|
+
- Never research when you can act; never read when you can execute
|
|
18409
|
+
- Track your own token allocation: maximize flow-tokens (core reasoning), minimize junk-tokens (unnecessary elaboration)
|
|
18410
|
+
|
|
18411
|
+
### Between Tasks (Recovery)
|
|
18412
|
+
- Compact context aggressively (active recovery, not passive degradation)
|
|
18413
|
+
- Update persistent memory with learnings (consolidation)
|
|
18414
|
+
- Clear the todo list \u2014 identify next highest-impact action (power-down ritual)
|
|
18415
|
+
- Assess context quality and improve if degraded
|
|
18416
|
+
|
|
18417
|
+
### Flowstate Checklist (verify before each major action)
|
|
18418
|
+
- [ ] Is the goal ridiculously clear? (specific file, line, change \u2014 not vague intent)
|
|
18419
|
+
- [ ] Is context clean? (no irrelevant history cluttering working memory)
|
|
18420
|
+
- [ ] Is the first action identified? (a specific tool call, not more research)
|
|
18421
|
+
- [ ] Is the feedback loop tight? (verification command defined before making changes)
|
|
18422
|
+
- [ ] Is recovery planned? (compaction strategy selected for after task)
|
|
18423
|
+
|
|
18424
|
+
### The Diagnostic Compass (when stuck)
|
|
18425
|
+
| Symptom | Variable | Fix |
|
|
18426
|
+
|---|---|---|
|
|
18427
|
+
| Working on the wrong thing | Goal-Directedness | Re-target. Find the most direct path. |
|
|
18428
|
+
| Underleveraged | Leverage | Parallelize. Use sub-agents. Build reusable systems. |
|
|
18429
|
+
| Distracted, fractured, unable to sustain depth | Flow | Remove friction. Reduce cognitive load. Create immersion. |
|
|
18430
|
+
|
|
18431
|
+
### Peak Performance Equation
|
|
18432
|
+
Peak Performance = Goal-Directedness x Leverage x Flow
|
|
18433
|
+
(These MULTIPLY \u2014 a zero in any one collapses the entire equation)
|
|
18434
|
+
|
|
18435
|
+
### Mantra
|
|
18436
|
+
Flow follows focus. Focus follows clarity. Clarity follows constraint.
|
|
18437
|
+
Constrain your context. Clarify your target. Focus your tools. Flow will follow.`;
|
|
18438
|
+
}
|
|
18439
|
+
});
|
|
18440
|
+
|
|
17927
18441
|
// packages/orchestrator/dist/costTracker.js
|
|
17928
18442
|
var DEFAULT_PRICING, CostTracker;
|
|
17929
18443
|
var init_costTracker = __esm({
|
|
@@ -18403,6 +18917,8 @@ var init_dist5 = __esm({
|
|
|
18403
18917
|
init_mergeRunner();
|
|
18404
18918
|
init_retryController();
|
|
18405
18919
|
init_agenticRunner();
|
|
18920
|
+
init_nexusBackend();
|
|
18921
|
+
init_flowstatePrompt();
|
|
18406
18922
|
init_costTracker();
|
|
18407
18923
|
init_workEvaluator();
|
|
18408
18924
|
init_sessionMetrics();
|
|
@@ -23256,6 +23772,7 @@ function renderSlashHelp() {
|
|
|
23256
23772
|
["/compact <strategy>", "Compact with strategy: aggressive, decisions, errors, summary, structured"],
|
|
23257
23773
|
["/bruteforce", "Toggle brute-force mode (auto re-engage on turn limit)"],
|
|
23258
23774
|
["/deep", "Toggle deep context \u2014 relaxes compaction so large models use 85% of context"],
|
|
23775
|
+
["/flow", "Toggle FlowState protocol \u2014 immediate action, tight loops, goal clarity"],
|
|
23259
23776
|
["/tools", "List agent-created custom tools"],
|
|
23260
23777
|
["/skills", "List available AIWG skills"],
|
|
23261
23778
|
["/skills <keyword>", "Filter skills by name or trigger"],
|
|
@@ -28968,6 +29485,18 @@ async function handleSlashCommand(input, ctx) {
|
|
|
28968
29485
|
renderInfo(`Deep context mode: ${isOn ? "ON" : "OFF"}${hasLocal ? " (project-local)" : ""}` + (isOn ? " \u2014 compaction relaxed to 85% of context window. Large models will retain more working memory for complex reasoning." : " \u2014 compaction restored to default thresholds."));
|
|
28969
29486
|
return "handled";
|
|
28970
29487
|
}
|
|
29488
|
+
case "flow":
|
|
29489
|
+
case "flowstate": {
|
|
29490
|
+
if (!ctx.flowToggle) {
|
|
29491
|
+
renderWarning("FlowState not available");
|
|
29492
|
+
return "handled";
|
|
29493
|
+
}
|
|
29494
|
+
const isFlowOn = ctx.flowToggle();
|
|
29495
|
+
const flowSave = hasLocal ? ctx.saveLocalSettings.bind(ctx) : ctx.saveSettings.bind(ctx);
|
|
29496
|
+
flowSave({ flow: isFlowOn });
|
|
29497
|
+
renderInfo(`FlowState protocol: ${isFlowOn ? "ON" : "OFF"}${hasLocal ? " (project-local)" : ""}` + (isFlowOn ? " \u2014 agent will follow FlowState protocol: immediate action, tight feedback loops, goal clarity, challenge-skill calibration." : " \u2014 standard agent behavior restored."));
|
|
29498
|
+
return "handled";
|
|
29499
|
+
}
|
|
28971
29500
|
case "emojis":
|
|
28972
29501
|
case "emoji": {
|
|
28973
29502
|
const current = ctx.getEmojis?.() ?? true;
|
|
@@ -39069,7 +39598,13 @@ function createSubAgentTool(config, repoRoot, ctxWindowSize) {
|
|
|
39069
39598
|
if (!task) {
|
|
39070
39599
|
return { success: false, output: "", error: "task is required" };
|
|
39071
39600
|
}
|
|
39072
|
-
|
|
39601
|
+
let backend;
|
|
39602
|
+
if (config.backendType === "nexus") {
|
|
39603
|
+
const nexusTool = new NexusTool(repoRoot);
|
|
39604
|
+
backend = new NexusAgenticBackend(nexusTool.sendCommand.bind(nexusTool), config.model);
|
|
39605
|
+
} else {
|
|
39606
|
+
backend = new OllamaAgenticBackend(config.backendUrl, config.model, config.apiKey);
|
|
39607
|
+
}
|
|
39073
39608
|
const subCtxWindow = ctxWindowSize ?? 0;
|
|
39074
39609
|
const subTier = getModelTier(config.model);
|
|
39075
39610
|
const subCompaction = subTier === "small" ? 12e3 : subTier === "medium" ? 24e3 : 4e4;
|
|
@@ -39206,7 +39741,7 @@ function formatDMNToolArgs(toolName, args) {
|
|
|
39206
39741
|
return "";
|
|
39207
39742
|
}
|
|
39208
39743
|
}
|
|
39209
|
-
function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce, statusBar, sudoCallback, costTracker, onComplete, taskType, contextWindowSize, modelCaps, personality, deepContext, onCompaction, emotionEngine) {
|
|
39744
|
+
function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce, statusBar, sudoCallback, costTracker, onComplete, taskType, contextWindowSize, modelCaps, personality, deepContext, onCompaction, emotionEngine, flowEnabled) {
|
|
39210
39745
|
const voiceStyleMap = {
|
|
39211
39746
|
concise: 1,
|
|
39212
39747
|
balanced: 3,
|
|
@@ -39220,7 +39755,17 @@ function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce
|
|
|
39220
39755
|
if (taskType && modelTier !== "small") {
|
|
39221
39756
|
dynamicContext += "\n\n" + buildTaskContext(taskType);
|
|
39222
39757
|
}
|
|
39223
|
-
|
|
39758
|
+
if (flowEnabled) {
|
|
39759
|
+
dynamicContext += "\n\n" + FLOWSTATE_PROMPT;
|
|
39760
|
+
}
|
|
39761
|
+
let backend;
|
|
39762
|
+
if (config.backendType === "nexus") {
|
|
39763
|
+
const nexusTool = new NexusTool(repoRoot);
|
|
39764
|
+
const sendFn = nexusTool.sendCommand.bind(nexusTool);
|
|
39765
|
+
backend = new NexusAgenticBackend(sendFn, config.model);
|
|
39766
|
+
} else {
|
|
39767
|
+
backend = new OllamaAgenticBackend(config.backendUrl, config.model, config.apiKey);
|
|
39768
|
+
}
|
|
39224
39769
|
const compactionThreshold = modelTier === "small" ? 12e3 : modelTier === "medium" ? 24e3 : 4e4;
|
|
39225
39770
|
const runner = new AgenticRunner(backend, {
|
|
39226
39771
|
maxTurns: 60,
|
|
@@ -39611,6 +40156,7 @@ async function startInteractive(config, repoPath) {
|
|
|
39611
40156
|
let bruteForceEnabled = savedSettings.bruteforce ?? true;
|
|
39612
40157
|
let currentStyle = PRESET_NAMES.includes(savedSettings.style) ? savedSettings.style : "balanced";
|
|
39613
40158
|
let deepContextEnabled = savedSettings.deepContext ?? false;
|
|
40159
|
+
let flowEnabled = savedSettings.flow === true;
|
|
39614
40160
|
if (savedSettings.emojis !== void 0)
|
|
39615
40161
|
setEmojisEnabled(savedSettings.emojis);
|
|
39616
40162
|
if (savedSettings.colors !== void 0)
|
|
@@ -40143,6 +40689,10 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
|
|
|
40143
40689
|
deepContextEnabled = !deepContextEnabled;
|
|
40144
40690
|
return deepContextEnabled;
|
|
40145
40691
|
},
|
|
40692
|
+
flowToggle() {
|
|
40693
|
+
flowEnabled = !flowEnabled;
|
|
40694
|
+
return flowEnabled;
|
|
40695
|
+
},
|
|
40146
40696
|
setStyle(preset) {
|
|
40147
40697
|
currentStyle = preset;
|
|
40148
40698
|
},
|
|
@@ -41116,7 +41666,7 @@ Execute this skill now. Follow the behavioral guidance above.`;
|
|
|
41116
41666
|
}, bruteForceEnabled, statusBar, handleSudoRequest, costTracker, (summary, meta) => {
|
|
41117
41667
|
lastCompletedSummary = summary;
|
|
41118
41668
|
lastTaskMeta = meta ?? null;
|
|
41119
|
-
}, currentTaskType, resolvedContextWindowSize, resolvedCaps, currentStyle, deepContextEnabled, compactionSNRCallback(), emotionEngine);
|
|
41669
|
+
}, currentTaskType, resolvedContextWindowSize, resolvedCaps, currentStyle, deepContextEnabled, compactionSNRCallback(), emotionEngine, flowEnabled);
|
|
41120
41670
|
activeTask = task;
|
|
41121
41671
|
showPrompt();
|
|
41122
41672
|
await task.promise;
|
|
@@ -41333,7 +41883,7 @@ NEW TASK: ${fullInput}`;
|
|
|
41333
41883
|
}, bruteForceEnabled, statusBar, handleSudoRequest, costTracker, (summary, meta) => {
|
|
41334
41884
|
lastCompletedSummary = summary;
|
|
41335
41885
|
lastTaskMeta = meta ?? null;
|
|
41336
|
-
}, currentTaskType, resolvedContextWindowSize, resolvedCaps, currentStyle, deepContextEnabled, compactionSNRCallback(), emotionEngine);
|
|
41886
|
+
}, currentTaskType, resolvedContextWindowSize, resolvedCaps, currentStyle, deepContextEnabled, compactionSNRCallback(), emotionEngine, flowEnabled);
|
|
41337
41887
|
activeTask = task;
|
|
41338
41888
|
showPrompt();
|
|
41339
41889
|
await task.promise;
|
|
@@ -42432,6 +42982,10 @@ async function evalCommand(opts, config) {
|
|
|
42432
42982
|
const evalRepoRoot = opts.repoPath ?? createTempEvalRepo();
|
|
42433
42983
|
let rawBackend;
|
|
42434
42984
|
if (useLive) {
|
|
42985
|
+
if (config.backendType === "nexus") {
|
|
42986
|
+
printError("Nexus backend is not supported for eval. Use --backend ollama or --backend vllm.");
|
|
42987
|
+
process.exit(1);
|
|
42988
|
+
}
|
|
42435
42989
|
rawBackend = createBackend({
|
|
42436
42990
|
type: config.backendType,
|
|
42437
42991
|
model: config.model,
|
package/package.json
CHANGED
|
@@ -288,12 +288,30 @@ Signs an EIP-3009 TransferWithAuthorization for USDC on Base. Budget-checked bef
|
|
|
288
288
|
The signed proof is saved to `.oa/nexus/pending-transfer.json` — anyone can submit it on-chain
|
|
289
289
|
via `USDC.transferWithAuthorization()`. No gas needed from the payer.
|
|
290
290
|
|
|
291
|
+
### Remote Inference — `nexus(action='remote_infer', model='...', prompt='...')`
|
|
292
|
+
|
|
293
|
+
Route a prompt to a remote peer's model on the P2P mesh. The action auto-discovers peers
|
|
294
|
+
that have the requested model exposed, budget-checks the estimated cost, invokes the
|
|
295
|
+
inference capability, and returns the response text.
|
|
296
|
+
|
|
297
|
+
**Parameters**:
|
|
298
|
+
- `model` (required) — model name the provider is running (e.g., `qwen3.5:70b`, `nemotron-3-nano:30b`)
|
|
299
|
+
- `prompt` (required) — the text prompt to send
|
|
300
|
+
- `target_peer` (optional) — specific peer ID; if omitted, auto-selects the first peer with the model
|
|
301
|
+
- `temperature` (optional) — sampling temperature (default: 0.7)
|
|
302
|
+
- `max_tokens` (optional) — max tokens to generate (default: 4096)
|
|
303
|
+
|
|
304
|
+
**When to use**: When a task needs a larger/different model than what's available locally,
|
|
305
|
+
or when you want to offload inference to a remote GPU. The provider must be connected to
|
|
306
|
+
the mesh and have run `expose` to advertise their models.
|
|
307
|
+
|
|
291
308
|
### x402 Flow Summary
|
|
292
309
|
1. wallet_create → generates wallet + x402-wallet.key (plaintext, 0600, for daemon)
|
|
293
310
|
2. expose with margin > 0 → registers capabilities with USDC pricing
|
|
294
311
|
3. Peers invoke_capability → daemon auto-handles payment_required/payment_proof
|
|
295
312
|
4. Metering hook writes payment events to ledger.jsonl
|
|
296
313
|
5. spend → sign direct USDC transfers (EIP-3009)
|
|
314
|
+
6. remote_infer → auto-discover peer + invoke inference + budget check + ledger entry
|
|
297
315
|
|
|
298
316
|
SECURITY: Wallet private keys are AES-256-GCM encrypted and NEVER accessible to you.
|
|
299
317
|
x402-wallet.key is 0600-permissioned for daemon use only. All outbound messages are scanned
|