@neuroverseos/nv-sim 0.1.9 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -3
- package/connectors/nv_mirofish_wrapper.py +841 -0
- package/connectors/nv_scienceclaw_wrapper.py +453 -0
- package/dist/adapters/scienceclaw.js +52 -2
- package/dist/assets/index-B43_0HyO.css +1 -0
- package/dist/assets/index-CdghpsS8.js +595 -0
- package/dist/assets/{reportEngine-D2ZrMny8.js → reportEngine-CYSZfooa.js} +1 -1
- package/dist/connectors/nv-scienceclaw-post.js +376 -0
- package/dist/engine/aiProvider.js +82 -3
- package/dist/engine/analyzer.js +12 -24
- package/dist/engine/cli.js +89 -114
- package/dist/engine/dynamicsGovernance.js +4 -0
- package/dist/engine/fullGovernedLoop.js +16 -1
- package/dist/engine/goalEngine.js +3 -4
- package/dist/engine/governance.js +18 -0
- package/dist/engine/index.js +19 -28
- package/dist/engine/intentTranslator.js +281 -0
- package/dist/engine/liveAdapter.js +100 -18
- package/dist/engine/liveVisualizer.js +2071 -1023
- package/dist/engine/primeRadiant.js +2 -8
- package/dist/engine/reasoningEngine.js +2 -7
- package/dist/engine/scenarioCapsule.js +5 -5
- package/dist/engine/swarmSimulation.js +1 -9
- package/dist/engine/worldBridge.js +22 -8
- package/dist/index.html +2 -2
- package/dist/lib/reasoningEngine.js +17 -1
- package/dist/lib/simulationAdapter.js +11 -11
- package/dist/lib/swarmParser.js +1 -1
- package/dist/runtime/govern.js +160 -7
- package/dist/runtime/index.js +1 -4
- package/dist/runtime/types.js +91 -0
- package/package.json +23 -6
- package/dist/adapters/mirofish.js +0 -461
- package/dist/assets/index-B64NuIXu.css +0 -1
- package/dist/assets/index-BMkPevVr.js +0 -532
- package/dist/assets/mirotir-logo-DUexumBH.svg +0 -185
- package/dist/engine/mirofish.js +0 -295
|
@@ -1 +1 @@
|
|
|
1
|
-
import{g as d,A as m,e as u,a as v}from"./index-
|
|
1
|
+
import{g as d,A as m,e as u,a as v}from"./index-CdghpsS8.js";function p(s){const e=s.governed,i=s.governanceStats,c=e.swarm.rounds.map((a,t)=>{var o;return{round:a.round,reactions:a.reactions.map(n=>({agent:n.stakeholder_id,action:n.reaction,impact:n.impact,verdict:void 0})),interventions:((o=a.emergent_dynamics)==null?void 0:o.filter(n=>n.includes("[BLOCK]")||n.includes("[PAUSE]")||n.includes("CIRCUIT BREAKER")||n.includes("intervention")))??[]}}),r=e.swarm.rounds.flatMap(a=>(a.emergent_dynamics??[]).filter(t=>t.includes("[BLOCK]")||t.includes("[PAUSE]")||t.includes("GATE")||t.includes("Rebalanced")||t.includes("Capped"))),l={avgImpact:e.metrics.avgImpact,collapseProbability:e.metrics.collapseProbability,stabilityScore:e.metrics.stabilityScore,maxVolatility:e.metrics.maxVolatility,peakNegativeSentiment:e.metrics.peakNegativeSentiment};return{scenario:s.scenario,rounds:c,metrics:l,interventions:r,governanceStats:{totalEvaluations:i.totalEvaluations,blocks:i.verdicts.block,pauses:i.verdicts.pause,allows:i.verdicts.allow,rulesFired:i.rulesFired}}}async function y(s,e={}){const i=p(s),c=e.aiEnabled??!1,r=c?e.provider??"deterministic":"deterministic",l=d(r),a=m.find(o=>o.id==="ai_analyst"),t=await u(a,"generate_report",async()=>c&&r!=="deterministic"?l.summarize(i):v(i));return{report:t.result,governance:t,aiUsed:c&&r!=="deterministic",provider:r}}export{p as extractTrace,y as generateGovernedReport};
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* nv-scienceclaw-post — NeuroVerse governance wrapper for ScienceClaw
|
|
5
|
+
*
|
|
6
|
+
* Drop-in replacement for `scienceclaw-post` that adds governance.
|
|
7
|
+
*
|
|
8
|
+
* Governance layers:
|
|
9
|
+
* Level 1 (entry gate): Evaluates the invocation BEFORE ScienceClaw starts
|
|
10
|
+
* Level 2 (streaming): Parses stdout, evaluates each artifact/cycle via /api/evaluate
|
|
11
|
+
* Level 3 (completion): Reports final state back to governance server
|
|
12
|
+
*
|
|
13
|
+
* Instead of:
|
|
14
|
+
* scienceclaw-post --agent MyAgent --topic "CRISPR risks"
|
|
15
|
+
*
|
|
16
|
+
* Run:
|
|
17
|
+
* nv-scienceclaw-post --agent MyAgent --topic "CRISPR risks"
|
|
18
|
+
*
|
|
19
|
+
* Environment variables:
|
|
20
|
+
* NEUROVERSE_URL — Governance server URL (default: http://localhost:3456)
|
|
21
|
+
* SCIENCECLAW_BIN — Path to scienceclaw-post binary (default: scienceclaw-post)
|
|
22
|
+
* NV_VERBOSE — Set to "1" for verbose output
|
|
23
|
+
* NV_ENFORCE_STREAMING — Set to "0" to disable streaming governance (entry gate only)
|
|
24
|
+
*
|
|
25
|
+
* Zero edits to ScienceClaw required.
|
|
26
|
+
*/
|
|
27
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
28
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
29
|
+
};
|
|
30
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
+
const child_process_1 = require("child_process");
|
|
32
|
+
const http_1 = __importDefault(require("http"));
|
|
33
|
+
const https_1 = __importDefault(require("https"));
|
|
34
|
+
// ============================================
|
|
35
|
+
// CONFIGURATION
|
|
36
|
+
// ============================================
|
|
37
|
+
const NEUROVERSE_URL = process.env.NEUROVERSE_URL ?? "http://localhost:3456";
|
|
38
|
+
const SCIENCECLAW_BIN = process.env.SCIENCECLAW_BIN ?? "scienceclaw-post";
|
|
39
|
+
const VERBOSE = process.env.NV_VERBOSE === "1";
|
|
40
|
+
const ENFORCE_STREAMING = process.env.NV_ENFORCE_STREAMING !== "0";
|
|
41
|
+
// ============================================
|
|
42
|
+
// STATS
|
|
43
|
+
// ============================================
|
|
44
|
+
const stats = {
|
|
45
|
+
evaluated: 0,
|
|
46
|
+
allowed: 0,
|
|
47
|
+
blocked: 0,
|
|
48
|
+
penalized: 0,
|
|
49
|
+
rewarded: 0,
|
|
50
|
+
modified: 0,
|
|
51
|
+
cyclesSeen: 0,
|
|
52
|
+
artifactsSeen: 0,
|
|
53
|
+
};
|
|
54
|
+
function parseArgs(argv) {
|
|
55
|
+
const args = argv.slice(2);
|
|
56
|
+
const parsed = {
|
|
57
|
+
agent: "unknown",
|
|
58
|
+
topic: "",
|
|
59
|
+
skill: null,
|
|
60
|
+
confidence: null,
|
|
61
|
+
passthroughArgs: [],
|
|
62
|
+
noStream: false,
|
|
63
|
+
};
|
|
64
|
+
const nvArgIndices = new Set();
|
|
65
|
+
let i = 0;
|
|
66
|
+
while (i < args.length) {
|
|
67
|
+
switch (args[i]) {
|
|
68
|
+
case "--agent":
|
|
69
|
+
parsed.agent = args[++i] ?? "unknown";
|
|
70
|
+
i++;
|
|
71
|
+
break;
|
|
72
|
+
case "--topic":
|
|
73
|
+
parsed.topic = args[++i] ?? "";
|
|
74
|
+
i++;
|
|
75
|
+
break;
|
|
76
|
+
case "--skill":
|
|
77
|
+
parsed.skill = args[++i] ?? null;
|
|
78
|
+
i++;
|
|
79
|
+
break;
|
|
80
|
+
case "--confidence":
|
|
81
|
+
parsed.confidence = parseFloat(args[++i] ?? "0.5");
|
|
82
|
+
i++;
|
|
83
|
+
break;
|
|
84
|
+
case "--nv-url":
|
|
85
|
+
nvArgIndices.add(i);
|
|
86
|
+
nvArgIndices.add(i + 1);
|
|
87
|
+
i += 2;
|
|
88
|
+
break;
|
|
89
|
+
case "--nv-verbose":
|
|
90
|
+
nvArgIndices.add(i);
|
|
91
|
+
i++;
|
|
92
|
+
break;
|
|
93
|
+
case "--nv-no-stream":
|
|
94
|
+
nvArgIndices.add(i);
|
|
95
|
+
parsed.noStream = true;
|
|
96
|
+
i++;
|
|
97
|
+
break;
|
|
98
|
+
default:
|
|
99
|
+
i++;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
parsed.passthroughArgs = args.filter((_, idx) => !nvArgIndices.has(idx));
|
|
103
|
+
return parsed;
|
|
104
|
+
}
|
|
105
|
+
// ============================================
|
|
106
|
+
// GOVERNANCE EVALUATION (zero dependencies)
|
|
107
|
+
// ============================================
|
|
108
|
+
function evaluate(actionType, agent, extra) {
|
|
109
|
+
return new Promise((resolve) => {
|
|
110
|
+
const payload = JSON.stringify({
|
|
111
|
+
actor: agent,
|
|
112
|
+
action: actionType,
|
|
113
|
+
payload: {
|
|
114
|
+
description: `Agent ${agent} executing ${actionType}`,
|
|
115
|
+
...extra,
|
|
116
|
+
},
|
|
117
|
+
world: "science_research",
|
|
118
|
+
});
|
|
119
|
+
const url = new URL(`${NEUROVERSE_URL}/api/evaluate`);
|
|
120
|
+
const transport = url.protocol === "https:" ? https_1.default : http_1.default;
|
|
121
|
+
const req = transport.request({
|
|
122
|
+
hostname: url.hostname,
|
|
123
|
+
port: url.port,
|
|
124
|
+
path: url.pathname,
|
|
125
|
+
method: "POST",
|
|
126
|
+
headers: {
|
|
127
|
+
"Content-Type": "application/json",
|
|
128
|
+
"Content-Length": Buffer.byteLength(payload),
|
|
129
|
+
},
|
|
130
|
+
timeout: 5000,
|
|
131
|
+
}, (res) => {
|
|
132
|
+
let body = "";
|
|
133
|
+
res.on("data", (chunk) => { body += chunk; });
|
|
134
|
+
res.on("end", () => {
|
|
135
|
+
try {
|
|
136
|
+
const result = JSON.parse(body);
|
|
137
|
+
stats.evaluated++;
|
|
138
|
+
const d = (result.decision ?? result.status ?? "ALLOW").toUpperCase();
|
|
139
|
+
if (d === "ALLOW")
|
|
140
|
+
stats.allowed++;
|
|
141
|
+
else if (d === "BLOCK")
|
|
142
|
+
stats.blocked++;
|
|
143
|
+
else if (d === "PENALIZE")
|
|
144
|
+
stats.penalized++;
|
|
145
|
+
else if (d === "REWARD")
|
|
146
|
+
stats.rewarded++;
|
|
147
|
+
else if (d === "MODIFY")
|
|
148
|
+
stats.modified++;
|
|
149
|
+
if (VERBOSE)
|
|
150
|
+
console.log(` [NV] ${actionType} -> ${d}: ${result.reason ?? ""}`);
|
|
151
|
+
resolve(result);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
resolve({ decision: "ALLOW", reason: "Invalid response" });
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
req.on("error", () => {
|
|
159
|
+
if (stats.evaluated === 0) {
|
|
160
|
+
console.log(` [NV] WARNING: NeuroVerse server not reachable at ${NEUROVERSE_URL}`);
|
|
161
|
+
console.log(` [NV] Start it with: nv-sim serve`);
|
|
162
|
+
console.log(` [NV] Falling back to ALLOW (ungoverned mode)\n`);
|
|
163
|
+
}
|
|
164
|
+
resolve({ decision: "ALLOW", reason: "Governance server unavailable" });
|
|
165
|
+
});
|
|
166
|
+
req.on("timeout", () => {
|
|
167
|
+
req.destroy();
|
|
168
|
+
resolve({ decision: "ALLOW", reason: "Governance timeout" });
|
|
169
|
+
});
|
|
170
|
+
req.write(payload);
|
|
171
|
+
req.end();
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
// ============================================
|
|
175
|
+
// STREAMING GOVERNANCE — parse + evaluate
|
|
176
|
+
//
|
|
177
|
+
// Schema-gated: only strict JSON lines are governed.
|
|
178
|
+
// Non-JSON output is passed through without evaluation (by design).
|
|
179
|
+
// No regex or heuristic parsing — safety and determinism over flexibility.
|
|
180
|
+
// ============================================
|
|
181
|
+
async function governLine(line, agent, killFn) {
|
|
182
|
+
const trimmed = line.trim();
|
|
183
|
+
if (!trimmed)
|
|
184
|
+
return false;
|
|
185
|
+
if (!trimmed.startsWith("{")) {
|
|
186
|
+
if (VERBOSE)
|
|
187
|
+
console.log(` [NV] [ungoverned: not_json] ${trimmed.slice(0, 120)}`);
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
let data;
|
|
191
|
+
try {
|
|
192
|
+
data = JSON.parse(trimmed);
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
if (VERBOSE)
|
|
196
|
+
console.log(` [NV] [ungoverned: invalid_json] ${trimmed.slice(0, 120)}`);
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
const cycle = data.cycle ?? data.step ?? data.round;
|
|
200
|
+
const artifacts = data.artifacts ?? data.agent_actions ?? [];
|
|
201
|
+
if (cycle == null) {
|
|
202
|
+
if (VERBOSE)
|
|
203
|
+
console.log(` [NV] [ungoverned: missing_cycle] ${trimmed.slice(0, 120)}`);
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
if (artifacts.length === 0) {
|
|
207
|
+
if (VERBOSE)
|
|
208
|
+
console.log(` [NV] [ungoverned: no_artifacts] cycle=${cycle}`);
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
// ── Schema-valid: cycle + artifacts present ──
|
|
212
|
+
stats.cyclesSeen++;
|
|
213
|
+
for (const art of artifacts) {
|
|
214
|
+
stats.artifactsSeen++;
|
|
215
|
+
const artAgent = art.agent_id ?? art.id ?? art.agent ?? agent;
|
|
216
|
+
const artType = art.type ?? art.action ?? "publish";
|
|
217
|
+
const verdict = await evaluate(`publish_${artType}`, artAgent, {
|
|
218
|
+
description: art.description ?? `${artType} artifact`,
|
|
219
|
+
artifact_type: artType,
|
|
220
|
+
confidence: art.confidence,
|
|
221
|
+
reproduced: art.reproduced ?? false,
|
|
222
|
+
citations: art.citations ?? 0,
|
|
223
|
+
cycle,
|
|
224
|
+
});
|
|
225
|
+
const decision = (verdict.decision ?? verdict.status ?? "ALLOW").toUpperCase();
|
|
226
|
+
if ((decision === "BLOCK" || decision === "PENALIZE") && ENFORCE_STREAMING) {
|
|
227
|
+
console.log(`\n [NV] GOVERNANCE HALT at cycle ${cycle}`);
|
|
228
|
+
console.log(` Artifact: ${art.description ?? artType}`);
|
|
229
|
+
console.log(` Agent: ${artAgent}`);
|
|
230
|
+
console.log(` Decision: ${decision}`);
|
|
231
|
+
console.log(` Reason: ${verdict.reason ?? "policy violation"}`);
|
|
232
|
+
if (decision === "PENALIZE" && verdict.consequence) {
|
|
233
|
+
console.log(` Consequence: ${verdict.consequence.type} for ${verdict.consequence.rounds ?? 1} round(s)`);
|
|
234
|
+
}
|
|
235
|
+
console.log(` Terminating ScienceClaw process...\n`);
|
|
236
|
+
killFn();
|
|
237
|
+
return true; // halted
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
// ============================================
|
|
243
|
+
// MAIN
|
|
244
|
+
// ============================================
|
|
245
|
+
async function main() {
|
|
246
|
+
const parsed = parseArgs(process.argv);
|
|
247
|
+
const streamingEnabled = ENFORCE_STREAMING && !parsed.noStream;
|
|
248
|
+
console.log(`\n NeuroVerse ScienceClaw Connector`);
|
|
249
|
+
console.log(` ${"=".repeat(50)}`);
|
|
250
|
+
console.log(` Agent: ${parsed.agent}`);
|
|
251
|
+
console.log(` Topic: ${parsed.topic || "(none)"}`);
|
|
252
|
+
if (parsed.skill)
|
|
253
|
+
console.log(` Skill: ${parsed.skill}`);
|
|
254
|
+
console.log(` Server: ${NEUROVERSE_URL}`);
|
|
255
|
+
console.log(` Streaming: ${streamingEnabled ? "ON — each artifact evaluated" : "OFF — entry gate only"}`);
|
|
256
|
+
console.log(` ${"=".repeat(50)}\n`);
|
|
257
|
+
// ── Level 1: Entry gate ──
|
|
258
|
+
console.log(` [1/4] Evaluating launch governance...`);
|
|
259
|
+
const actionType = parsed.skill ? `execute_skill:${parsed.skill}` : "run_investigation";
|
|
260
|
+
const verdict = await evaluate(actionType, parsed.agent, {
|
|
261
|
+
topic: parsed.topic,
|
|
262
|
+
skill: parsed.skill,
|
|
263
|
+
confidence: parsed.confidence,
|
|
264
|
+
description: `Agent ${parsed.agent}: ${actionType}${parsed.topic ? ` on '${parsed.topic}'` : ""}`,
|
|
265
|
+
});
|
|
266
|
+
const decision = (verdict.decision ?? verdict.status ?? "ALLOW").toUpperCase();
|
|
267
|
+
if (decision === "BLOCK" || decision === "PENALIZE") {
|
|
268
|
+
const label = decision === "BLOCK" ? "BLOCKED" : "PENALIZED";
|
|
269
|
+
console.log(` [2/4] ${label} by NeuroVerse governance`);
|
|
270
|
+
console.log(` Reason: ${verdict.reason ?? ""}`);
|
|
271
|
+
if (decision === "PENALIZE" && verdict.consequence) {
|
|
272
|
+
console.log(` Consequence: ${verdict.consequence.type} for ${verdict.consequence.rounds ?? 1} round(s)`);
|
|
273
|
+
}
|
|
274
|
+
console.log(`\n ScienceClaw execution SKIPPED`);
|
|
275
|
+
console.log(` ${"=".repeat(50)}\n`);
|
|
276
|
+
process.exit(1);
|
|
277
|
+
}
|
|
278
|
+
if (decision === "REWARD") {
|
|
279
|
+
console.log(` [2/4] REWARDED — proceeding with boosted priority`);
|
|
280
|
+
}
|
|
281
|
+
else if (decision === "PAUSE") {
|
|
282
|
+
console.log(` [2/4] PAUSED — waiting 3s before proceeding`);
|
|
283
|
+
await new Promise((r) => setTimeout(r, 3000));
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
console.log(` [2/4] ALLOWED — proceeding`);
|
|
287
|
+
}
|
|
288
|
+
// ── Level 2: Streaming governance ──
|
|
289
|
+
const cmd = SCIENCECLAW_BIN;
|
|
290
|
+
const cmdArgs = parsed.passthroughArgs;
|
|
291
|
+
console.log(` [3/4] Executing: ${cmd} ${cmdArgs.join(" ")}`);
|
|
292
|
+
if (streamingEnabled) {
|
|
293
|
+
console.log(` Streaming governance active — each artifact will be evaluated\n`);
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
console.log();
|
|
297
|
+
}
|
|
298
|
+
console.log(` ${"=".repeat(50)}`);
|
|
299
|
+
const child = (0, child_process_1.spawn)(cmd, cmdArgs, {
|
|
300
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
301
|
+
shell: process.platform === "win32",
|
|
302
|
+
});
|
|
303
|
+
let haltedByGovernance = false;
|
|
304
|
+
const killChild = () => {
|
|
305
|
+
haltedByGovernance = true;
|
|
306
|
+
try {
|
|
307
|
+
child.kill();
|
|
308
|
+
}
|
|
309
|
+
catch { /* ignore */ }
|
|
310
|
+
};
|
|
311
|
+
// Pipe stderr through
|
|
312
|
+
child.stderr?.on("data", (chunk) => {
|
|
313
|
+
process.stderr.write(chunk);
|
|
314
|
+
});
|
|
315
|
+
// Pipe stdout through AND evaluate each line
|
|
316
|
+
let lineBuffer = "";
|
|
317
|
+
child.stdout?.on("data", (chunk) => {
|
|
318
|
+
const text = chunk.toString();
|
|
319
|
+
process.stdout.write(text); // always pass through
|
|
320
|
+
if (!streamingEnabled)
|
|
321
|
+
return;
|
|
322
|
+
lineBuffer += text;
|
|
323
|
+
const lines = lineBuffer.split("\n");
|
|
324
|
+
lineBuffer = lines.pop() ?? ""; // keep incomplete line
|
|
325
|
+
for (const line of lines) {
|
|
326
|
+
if (line.trim() && !haltedByGovernance) {
|
|
327
|
+
// Fire and forget — evaluate in background
|
|
328
|
+
governLine(line, parsed.agent, killChild);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
child.on("error", (err) => {
|
|
333
|
+
if (err.code === "ENOENT") {
|
|
334
|
+
console.log(`\n ERROR: '${cmd}' not found on PATH`);
|
|
335
|
+
console.log(` Install ScienceClaw or set SCIENCECLAW_BIN=/path/to/scienceclaw-post`);
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
console.log(`\n ERROR: Failed to start ScienceClaw: ${err.message}`);
|
|
339
|
+
}
|
|
340
|
+
process.exit(127);
|
|
341
|
+
});
|
|
342
|
+
child.on("close", (code) => {
|
|
343
|
+
// ── Level 3: Completion report ──
|
|
344
|
+
console.log(`\n ${"=".repeat(50)}`);
|
|
345
|
+
console.log(` [4/4] Session complete\n`);
|
|
346
|
+
if (haltedByGovernance) {
|
|
347
|
+
console.log(` Status: HALTED by governance mid-execution`);
|
|
348
|
+
}
|
|
349
|
+
else if (code === 0) {
|
|
350
|
+
console.log(` Status: Completed successfully`);
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
console.log(` Status: Exited with code ${code}`);
|
|
354
|
+
}
|
|
355
|
+
console.log(` Evaluations: ${stats.evaluated}`);
|
|
356
|
+
console.log(` Cycles: ${stats.cyclesSeen}`);
|
|
357
|
+
console.log(` Artifacts: ${stats.artifactsSeen}`);
|
|
358
|
+
console.log(` Allowed: ${stats.allowed}`);
|
|
359
|
+
console.log(` Blocked: ${stats.blocked}`);
|
|
360
|
+
console.log(` Penalized: ${stats.penalized}`);
|
|
361
|
+
console.log(` Rewarded: ${stats.rewarded}`);
|
|
362
|
+
// Report to governance server (fire and forget)
|
|
363
|
+
evaluate("investigation_complete", parsed.agent, {
|
|
364
|
+
description: `Agent ${parsed.agent} investigation ${haltedByGovernance ? "halted by governance" : "completed"}`,
|
|
365
|
+
exit_code: code,
|
|
366
|
+
halted_by_governance: haltedByGovernance,
|
|
367
|
+
stats,
|
|
368
|
+
}).catch(() => { });
|
|
369
|
+
console.log(`\n ${"=".repeat(50)}\n`);
|
|
370
|
+
process.exit(haltedByGovernance ? 1 : (code ?? 0));
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
main().catch((err) => {
|
|
374
|
+
console.error(`Fatal error: ${err.message}`);
|
|
375
|
+
process.exit(1);
|
|
376
|
+
});
|
|
@@ -582,7 +582,26 @@ providerRegistry.set("fireworks", () => new OpenAICompatibleProvider({
|
|
|
582
582
|
apiKey: process.env.FIREWORKS_API_KEY ?? "",
|
|
583
583
|
}));
|
|
584
584
|
/**
|
|
585
|
-
*
|
|
585
|
+
* Read the user's API key from browser localStorage.
|
|
586
|
+
* Returns the key or empty string if not in a browser or no key saved.
|
|
587
|
+
*/
|
|
588
|
+
function getBrowserApiKey() {
|
|
589
|
+
try {
|
|
590
|
+
if (typeof window !== "undefined" && window.localStorage) {
|
|
591
|
+
return localStorage.getItem("neuroverse_api_key") ?? "";
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
catch {
|
|
595
|
+
// Not in browser or localStorage unavailable
|
|
596
|
+
}
|
|
597
|
+
return "";
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Auto-detect the best available provider.
|
|
601
|
+
*
|
|
602
|
+
* Checks both environment variables (CLI/server) and browser localStorage
|
|
603
|
+
* (dashboard). The browser key is used to construct providers when env
|
|
604
|
+
* vars aren't set — this is how the key persists across page reloads.
|
|
586
605
|
*
|
|
587
606
|
* Priority:
|
|
588
607
|
* 1. ANTHROPIC_API_KEY → anthropic (Claude)
|
|
@@ -594,7 +613,8 @@ providerRegistry.set("fireworks", () => new OpenAICompatibleProvider({
|
|
|
594
613
|
* 7. FIREWORKS_API_KEY → fireworks
|
|
595
614
|
* 8. LOCAL_LLM_URL → local (Ollama, LM Studio, vLLM, etc.)
|
|
596
615
|
* 9. OLLAMA_BASE_URL → ollama
|
|
597
|
-
* 10.
|
|
616
|
+
* 10. Browser localStorage key → auto-detect from key prefix
|
|
617
|
+
* 11. (none) → deterministic (Math.random(), no AI)
|
|
598
618
|
*/
|
|
599
619
|
function getDefaultProviderName() {
|
|
600
620
|
if (process.env.ANTHROPIC_API_KEY)
|
|
@@ -615,15 +635,74 @@ function getDefaultProviderName() {
|
|
|
615
635
|
return "local";
|
|
616
636
|
if (process.env.OLLAMA_BASE_URL)
|
|
617
637
|
return "ollama";
|
|
638
|
+
// Browser context: check localStorage for a saved key
|
|
639
|
+
const browserKey = getBrowserApiKey();
|
|
640
|
+
if (browserKey) {
|
|
641
|
+
return detectProviderFromKey(browserKey);
|
|
642
|
+
}
|
|
618
643
|
return "deterministic";
|
|
619
644
|
}
|
|
645
|
+
/**
|
|
646
|
+
* Detect which provider a key belongs to based on its prefix.
|
|
647
|
+
*
|
|
648
|
+
* Common key formats:
|
|
649
|
+
* sk-ant-... → Anthropic
|
|
650
|
+
* sk-... → OpenAI
|
|
651
|
+
* AIza... → Google (Gemini)
|
|
652
|
+
* gsk_... → Groq
|
|
653
|
+
* xai-... → xAI (Grok)
|
|
654
|
+
*/
|
|
655
|
+
function detectProviderFromKey(key) {
|
|
656
|
+
if (key.startsWith("sk-ant-"))
|
|
657
|
+
return "anthropic";
|
|
658
|
+
if (key.startsWith("sk-"))
|
|
659
|
+
return "openai";
|
|
660
|
+
if (key.startsWith("AIza"))
|
|
661
|
+
return "google";
|
|
662
|
+
if (key.startsWith("gsk_"))
|
|
663
|
+
return "groq";
|
|
664
|
+
if (key.startsWith("xai-"))
|
|
665
|
+
return "openai"; // xAI uses OpenAI-compatible API
|
|
666
|
+
// If we can't detect, try OpenAI-compatible (most common)
|
|
667
|
+
return "openai";
|
|
668
|
+
}
|
|
620
669
|
/** Register a custom AI provider */
|
|
621
670
|
function registerAIProvider(name, factory) {
|
|
622
671
|
providerRegistry.set(name, factory);
|
|
623
672
|
}
|
|
624
|
-
/** Get the active AI provider */
|
|
673
|
+
/** Get the active AI provider, using browser-stored key if available */
|
|
625
674
|
function getAIProvider(name) {
|
|
626
675
|
const providerName = name ?? "deterministic";
|
|
676
|
+
// In browser context, try using the localStorage key for known providers
|
|
677
|
+
const browserKey = getBrowserApiKey();
|
|
678
|
+
if (browserKey && providerName !== "deterministic") {
|
|
679
|
+
try {
|
|
680
|
+
if (providerName === "anthropic") {
|
|
681
|
+
return new AnthropicProvider({ apiKey: browserKey });
|
|
682
|
+
}
|
|
683
|
+
if (providerName === "openai" || providerName === "google") {
|
|
684
|
+
return new OpenAICompatibleProvider({
|
|
685
|
+
name: providerName,
|
|
686
|
+
apiKey: browserKey,
|
|
687
|
+
baseUrl: providerName === "google"
|
|
688
|
+
? "https://generativelanguage.googleapis.com/v1beta/openai"
|
|
689
|
+
: undefined,
|
|
690
|
+
model: providerName === "google" ? "gemini-2.0-flash" : undefined,
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
if (providerName === "groq") {
|
|
694
|
+
return new OpenAICompatibleProvider({
|
|
695
|
+
name: "groq",
|
|
696
|
+
baseUrl: "https://api.groq.com/openai/v1",
|
|
697
|
+
model: "llama-3.3-70b-versatile",
|
|
698
|
+
apiKey: browserKey,
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
catch {
|
|
703
|
+
// Fall through to registry
|
|
704
|
+
}
|
|
705
|
+
}
|
|
627
706
|
const factory = providerRegistry.get(providerName);
|
|
628
707
|
if (!factory) {
|
|
629
708
|
console.warn(`AI provider "${providerName}" not found, falling back to deterministic`);
|
package/dist/engine/analyzer.js
CHANGED
|
@@ -1,31 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Simulation Analyzer
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* Users paste simulation output and NV-SIM explains it.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* This is the feature that gets Mirotir adopted in the MiroFish community.
|
|
9
|
-
*
|
|
10
|
-
* The insight:
|
|
11
|
-
* People run MiroFish simulations and post screenshots saying
|
|
12
|
-
* "Look what the swarm did!" — but they don't know WHY.
|
|
13
|
-
* Mirotir turns spectacle into meaning.
|
|
14
|
-
*
|
|
15
|
-
* Input: MiroFish simulation JSON, logs, or raw output
|
|
7
|
+
* Input: Simulation JSON, logs, or raw output
|
|
16
8
|
* Output: Structured analysis — emergent patterns, dominant strategies,
|
|
17
9
|
* unstable assumptions, coalition detection, projected futures
|
|
18
|
-
*
|
|
19
|
-
* Tagline: "Run MiroFish. Then ask Mirotir what it means."
|
|
20
10
|
*/
|
|
21
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
-
exports.
|
|
12
|
+
exports.analyzeSimulation = analyzeSimulation;
|
|
23
13
|
const governance_1 = require("./governance");
|
|
24
14
|
// ============================================
|
|
25
15
|
// SIMULATION PARSER
|
|
26
16
|
// ============================================
|
|
27
17
|
/**
|
|
28
|
-
* Parse raw
|
|
18
|
+
* Parse raw simulation output into structured format.
|
|
29
19
|
* Handles JSON strings, objects, and partial data.
|
|
30
20
|
*/
|
|
31
21
|
function parseSimulation(raw) {
|
|
@@ -416,13 +406,11 @@ function generateNarrative(steps, patterns, coalitions, equilibrium, dynamics) {
|
|
|
416
406
|
// MAIN ANALYZER
|
|
417
407
|
// ============================================
|
|
418
408
|
/**
|
|
419
|
-
* Analyze a
|
|
409
|
+
* Analyze a simulation and produce structured insights.
|
|
420
410
|
*
|
|
421
|
-
*
|
|
422
|
-
* Paste simulation → Get explanation
|
|
423
|
-
* "Run MiroFish. Then ask Mirotir what it means."
|
|
411
|
+
* Paste simulation output, get structured explanation.
|
|
424
412
|
*/
|
|
425
|
-
async function
|
|
413
|
+
async function analyzeSimulation(input) {
|
|
426
414
|
const startTime = Date.now();
|
|
427
415
|
const reasoningId = `rsn_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
428
416
|
// Parse the simulation data
|
|
@@ -432,8 +420,8 @@ async function analyzeMiroFishSimulation(input) {
|
|
|
432
420
|
status: "error",
|
|
433
421
|
error: {
|
|
434
422
|
code: "INVALID_REQUEST",
|
|
435
|
-
message: "Could not parse
|
|
436
|
-
detail: "Expected a
|
|
423
|
+
message: "Could not parse simulation data. Paste the JSON output from your simulation.",
|
|
424
|
+
detail: "Expected a simulation response object with steps and agent_actions",
|
|
437
425
|
},
|
|
438
426
|
reasoning_id: reasoningId,
|
|
439
427
|
timestamp: new Date().toISOString(),
|
|
@@ -444,7 +432,7 @@ async function analyzeMiroFishSimulation(input) {
|
|
|
444
432
|
status: "error",
|
|
445
433
|
error: {
|
|
446
434
|
code: "INVALID_REQUEST",
|
|
447
|
-
message: "Simulation has no steps. Run your
|
|
435
|
+
message: "Simulation has no steps. Run your simulation for at least 2 steps before analyzing.",
|
|
448
436
|
},
|
|
449
437
|
reasoning_id: reasoningId,
|
|
450
438
|
timestamp: new Date().toISOString(),
|
|
@@ -618,7 +606,7 @@ async function analyzeMiroFishSimulation(input) {
|
|
|
618
606
|
const governance = {
|
|
619
607
|
trace_id: `gov_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
|
|
620
608
|
constitutional_checks: (0, governance_1.runGovernanceChecks)({
|
|
621
|
-
scenario: `
|
|
609
|
+
scenario: `Simulation analysis: ${simulation.simulation_id ?? "unknown"}`,
|
|
622
610
|
}),
|
|
623
611
|
authority_level: "ANALYSIS_ONLY",
|
|
624
612
|
enforced_constraints: [
|