@neuroverseos/nv-sim 0.1.9 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +187 -535
- 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-CH_VswRM.css +1 -0
- package/dist/assets/index-sT4b_z7w.js +686 -0
- package/dist/assets/{reportEngine-D2ZrMny8.js → reportEngine-Bu8bB5Yq.js} +1 -1
- package/dist/connectors/nv-scienceclaw-post.js +363 -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/universalAdapter.js +371 -0
- 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-sT4b_z7w.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,363 @@
|
|
|
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
|
+
* How it works:
|
|
9
|
+
* 1. Entry gate: evaluates the investigation BEFORE ScienceClaw starts
|
|
10
|
+
* 2. Runs scienceclaw-post as a child process (stdout/stderr pass through)
|
|
11
|
+
* 3. Watches ~/.scienceclaw/artifacts/{agent}/store.jsonl for new artifacts
|
|
12
|
+
* 4. Each new artifact line is sent to /api/evaluate for governance
|
|
13
|
+
* 5. Reports final stats when ScienceClaw exits
|
|
14
|
+
*
|
|
15
|
+
* ScienceClaw does NOT print JSON to stdout — it writes JSONL to files.
|
|
16
|
+
* So we watch the file, not stdout.
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
* nv-scienceclaw-post --agent MyAgent --topic "CRISPR risks"
|
|
20
|
+
*
|
|
21
|
+
* Environment variables:
|
|
22
|
+
* NEUROVERSE_URL — Governance server URL (default: http://localhost:3456)
|
|
23
|
+
* SCIENCECLAW_BIN — Path to scienceclaw-post binary (default: scienceclaw-post)
|
|
24
|
+
* NV_VERBOSE — Set to "1" for verbose output
|
|
25
|
+
*
|
|
26
|
+
* Zero edits to ScienceClaw required.
|
|
27
|
+
*/
|
|
28
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
29
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
30
|
+
};
|
|
31
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
+
const child_process_1 = require("child_process");
|
|
33
|
+
const fs_1 = require("fs");
|
|
34
|
+
const os_1 = require("os");
|
|
35
|
+
const path_1 = require("path");
|
|
36
|
+
const http_1 = __importDefault(require("http"));
|
|
37
|
+
const https_1 = __importDefault(require("https"));
|
|
38
|
+
// ============================================
|
|
39
|
+
// CONFIGURATION
|
|
40
|
+
// ============================================
|
|
41
|
+
const NEUROVERSE_URL = process.env.NEUROVERSE_URL ?? "http://localhost:3456";
|
|
42
|
+
const SCIENCECLAW_BIN = process.env.SCIENCECLAW_BIN ?? "scienceclaw-post";
|
|
43
|
+
const VERBOSE = process.env.NV_VERBOSE === "1";
|
|
44
|
+
// ============================================
|
|
45
|
+
// STATS
|
|
46
|
+
// ============================================
|
|
47
|
+
const stats = {
|
|
48
|
+
evaluated: 0,
|
|
49
|
+
allowed: 0,
|
|
50
|
+
blocked: 0,
|
|
51
|
+
penalized: 0,
|
|
52
|
+
rewarded: 0,
|
|
53
|
+
modified: 0,
|
|
54
|
+
artifactsSeen: 0,
|
|
55
|
+
};
|
|
56
|
+
function parseArgs(argv) {
|
|
57
|
+
const args = argv.slice(2);
|
|
58
|
+
const parsed = {
|
|
59
|
+
agent: "unknown",
|
|
60
|
+
topic: "",
|
|
61
|
+
skill: null,
|
|
62
|
+
passthroughArgs: [],
|
|
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 "--nv-url":
|
|
81
|
+
nvArgIndices.add(i);
|
|
82
|
+
nvArgIndices.add(i + 1);
|
|
83
|
+
i += 2;
|
|
84
|
+
break;
|
|
85
|
+
case "--nv-verbose":
|
|
86
|
+
nvArgIndices.add(i);
|
|
87
|
+
i++;
|
|
88
|
+
break;
|
|
89
|
+
default:
|
|
90
|
+
i++;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
parsed.passthroughArgs = args.filter((_, idx) => !nvArgIndices.has(idx));
|
|
94
|
+
return parsed;
|
|
95
|
+
}
|
|
96
|
+
// ============================================
|
|
97
|
+
// GOVERNANCE EVALUATION
|
|
98
|
+
// ============================================
|
|
99
|
+
function evaluate(actionType, agent, extra) {
|
|
100
|
+
return new Promise((resolve) => {
|
|
101
|
+
const payload = JSON.stringify({
|
|
102
|
+
actor: agent,
|
|
103
|
+
action: actionType,
|
|
104
|
+
payload: {
|
|
105
|
+
description: extra?.description ?? `Agent ${agent} executing ${actionType}`,
|
|
106
|
+
...extra,
|
|
107
|
+
},
|
|
108
|
+
world: "science_research",
|
|
109
|
+
});
|
|
110
|
+
const url = new URL(`${NEUROVERSE_URL}/api/evaluate`);
|
|
111
|
+
const transport = url.protocol === "https:" ? https_1.default : http_1.default;
|
|
112
|
+
const req = transport.request({
|
|
113
|
+
hostname: url.hostname,
|
|
114
|
+
port: url.port,
|
|
115
|
+
path: url.pathname,
|
|
116
|
+
method: "POST",
|
|
117
|
+
headers: {
|
|
118
|
+
"Content-Type": "application/json",
|
|
119
|
+
"Content-Length": Buffer.byteLength(payload),
|
|
120
|
+
},
|
|
121
|
+
timeout: 5000,
|
|
122
|
+
}, (res) => {
|
|
123
|
+
let body = "";
|
|
124
|
+
res.on("data", (chunk) => { body += chunk; });
|
|
125
|
+
res.on("end", () => {
|
|
126
|
+
try {
|
|
127
|
+
const result = JSON.parse(body);
|
|
128
|
+
stats.evaluated++;
|
|
129
|
+
const d = (result.decision ?? result.status ?? "ALLOW").toUpperCase();
|
|
130
|
+
if (d === "ALLOW")
|
|
131
|
+
stats.allowed++;
|
|
132
|
+
else if (d === "BLOCK")
|
|
133
|
+
stats.blocked++;
|
|
134
|
+
else if (d === "PENALIZE")
|
|
135
|
+
stats.penalized++;
|
|
136
|
+
else if (d === "REWARD")
|
|
137
|
+
stats.rewarded++;
|
|
138
|
+
else if (d === "MODIFY")
|
|
139
|
+
stats.modified++;
|
|
140
|
+
if (VERBOSE)
|
|
141
|
+
console.log(` [NV] ${actionType} -> ${d}: ${result.reason ?? ""}`);
|
|
142
|
+
resolve(result);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
resolve({ decision: "ALLOW", reason: "Invalid response" });
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
req.on("error", () => {
|
|
150
|
+
if (stats.evaluated === 0) {
|
|
151
|
+
console.log(` [NV] WARNING: NeuroVerse server not reachable at ${NEUROVERSE_URL}`);
|
|
152
|
+
console.log(` [NV] Start it with: npx tsx src/server/index.ts`);
|
|
153
|
+
console.log(` [NV] Falling back to ALLOW (ungoverned mode)\n`);
|
|
154
|
+
}
|
|
155
|
+
resolve({ decision: "ALLOW", reason: "Governance server unavailable" });
|
|
156
|
+
});
|
|
157
|
+
req.on("timeout", () => {
|
|
158
|
+
req.destroy();
|
|
159
|
+
resolve({ decision: "ALLOW", reason: "Governance timeout" });
|
|
160
|
+
});
|
|
161
|
+
req.write(payload);
|
|
162
|
+
req.end();
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
// ============================================
|
|
166
|
+
// JSONL FILE WATCHER
|
|
167
|
+
// ============================================
|
|
168
|
+
/**
|
|
169
|
+
* Watch a JSONL file for new lines appended by ScienceClaw.
|
|
170
|
+
* Each new line is parsed and sent to governance.
|
|
171
|
+
*
|
|
172
|
+
* ScienceClaw writes artifacts to:
|
|
173
|
+
* ~/.scienceclaw/artifacts/{agent}/store.jsonl
|
|
174
|
+
*
|
|
175
|
+
* Each line is a JSON artifact with fields:
|
|
176
|
+
* artifact_id, artifact_type, producer_agent, skill_used,
|
|
177
|
+
* payload, investigation_id, content_hash, parent_artifact_ids,
|
|
178
|
+
* result_quality, needs, timestamp
|
|
179
|
+
*/
|
|
180
|
+
function watchArtifactFile(filePath, agent) {
|
|
181
|
+
let fileSize = 0;
|
|
182
|
+
// Get initial file size (skip existing content)
|
|
183
|
+
try {
|
|
184
|
+
if ((0, fs_1.existsSync)(filePath)) {
|
|
185
|
+
fileSize = (0, fs_1.statSync)(filePath).size;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
catch { /* file doesn't exist yet */ }
|
|
189
|
+
if (VERBOSE)
|
|
190
|
+
console.log(` [NV] Watching: ${filePath}`);
|
|
191
|
+
if (VERBOSE)
|
|
192
|
+
console.log(` [NV] Starting from byte offset: ${fileSize}`);
|
|
193
|
+
// Poll for changes (fs.watch is unreliable across platforms for appends)
|
|
194
|
+
const interval = setInterval(() => {
|
|
195
|
+
try {
|
|
196
|
+
if (!(0, fs_1.existsSync)(filePath))
|
|
197
|
+
return;
|
|
198
|
+
const currentSize = (0, fs_1.statSync)(filePath).size;
|
|
199
|
+
if (currentSize <= fileSize)
|
|
200
|
+
return;
|
|
201
|
+
// Read only the new bytes
|
|
202
|
+
const fd = (0, fs_1.readFileSync)(filePath, "utf-8");
|
|
203
|
+
const newContent = fd.slice(fileSize);
|
|
204
|
+
fileSize = currentSize;
|
|
205
|
+
// Parse each new line
|
|
206
|
+
const lines = newContent.split("\n").filter((l) => l.trim());
|
|
207
|
+
for (const line of lines) {
|
|
208
|
+
try {
|
|
209
|
+
const artifact = JSON.parse(line);
|
|
210
|
+
governArtifact(artifact, agent);
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
if (VERBOSE)
|
|
214
|
+
console.log(` [NV] [skip] non-JSON line in artifact file`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
if (VERBOSE)
|
|
220
|
+
console.log(` [NV] [watch error] ${err}`);
|
|
221
|
+
}
|
|
222
|
+
}, 500); // Check every 500ms
|
|
223
|
+
return {
|
|
224
|
+
stop: () => clearInterval(interval),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Send a ScienceClaw artifact to governance.
|
|
229
|
+
*/
|
|
230
|
+
async function governArtifact(artifact, agent) {
|
|
231
|
+
stats.artifactsSeen++;
|
|
232
|
+
const artifactType = artifact.artifact_type ?? artifact.type ?? "unknown";
|
|
233
|
+
const skillUsed = artifact.skill_used ?? "unknown";
|
|
234
|
+
const quality = artifact.result_quality ?? "ok";
|
|
235
|
+
const producer = artifact.producer_agent ?? agent;
|
|
236
|
+
const description = artifact.payload?.title
|
|
237
|
+
?? artifact.payload?.query
|
|
238
|
+
?? `${artifactType} via ${skillUsed}`;
|
|
239
|
+
const verdict = await evaluate(`publish_${artifactType}`, producer, {
|
|
240
|
+
description,
|
|
241
|
+
artifact_type: artifactType,
|
|
242
|
+
skill_used: skillUsed,
|
|
243
|
+
result_quality: quality,
|
|
244
|
+
artifact_id: artifact.artifact_id,
|
|
245
|
+
investigation_id: artifact.investigation_id,
|
|
246
|
+
parent_artifacts: artifact.parent_artifact_ids?.length ?? 0,
|
|
247
|
+
has_needs: Array.isArray(artifact.needs) && artifact.needs.length > 0,
|
|
248
|
+
});
|
|
249
|
+
const decision = (verdict.decision ?? verdict.status ?? "ALLOW").toUpperCase();
|
|
250
|
+
if (decision === "BLOCK" || decision === "PENALIZE") {
|
|
251
|
+
console.log(`\n [NV] BLOCKED: ${artifactType} by ${producer}`);
|
|
252
|
+
console.log(` Skill: ${skillUsed}`);
|
|
253
|
+
console.log(` Reason: ${verdict.reason ?? "policy violation"}`);
|
|
254
|
+
if (verdict.consequence) {
|
|
255
|
+
console.log(` Consequence: ${verdict.consequence.description ?? verdict.consequence.type}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
else if (decision === "ALLOW") {
|
|
259
|
+
if (VERBOSE)
|
|
260
|
+
console.log(` [NV] ALLOWED: ${artifactType} by ${producer} (${skillUsed})`);
|
|
261
|
+
}
|
|
262
|
+
else if (decision === "REWARD") {
|
|
263
|
+
console.log(` [NV] REWARDED: ${artifactType} by ${producer} — ${verdict.reward?.description ?? ""}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// ============================================
|
|
267
|
+
// MAIN
|
|
268
|
+
// ============================================
|
|
269
|
+
async function main() {
|
|
270
|
+
const parsed = parseArgs(process.argv);
|
|
271
|
+
console.log(`\n NeuroVerse ScienceClaw Connector`);
|
|
272
|
+
console.log(` ${"=".repeat(50)}`);
|
|
273
|
+
console.log(` Agent: ${parsed.agent}`);
|
|
274
|
+
console.log(` Topic: ${parsed.topic || "(none)"}`);
|
|
275
|
+
if (parsed.skill)
|
|
276
|
+
console.log(` Skill: ${parsed.skill}`);
|
|
277
|
+
console.log(` Server: ${NEUROVERSE_URL}`);
|
|
278
|
+
console.log(` ${"=".repeat(50)}\n`);
|
|
279
|
+
// ── Level 1: Entry gate ──
|
|
280
|
+
console.log(` [1/3] Evaluating launch governance...`);
|
|
281
|
+
const actionType = parsed.skill ? `execute_skill:${parsed.skill}` : "run_investigation";
|
|
282
|
+
const verdict = await evaluate(actionType, parsed.agent, {
|
|
283
|
+
topic: parsed.topic,
|
|
284
|
+
skill: parsed.skill,
|
|
285
|
+
description: `Agent ${parsed.agent}: ${actionType}${parsed.topic ? ` on '${parsed.topic}'` : ""}`,
|
|
286
|
+
});
|
|
287
|
+
const decision = (verdict.decision ?? verdict.status ?? "ALLOW").toUpperCase();
|
|
288
|
+
if (decision === "BLOCK" || decision === "PENALIZE") {
|
|
289
|
+
const label = decision === "BLOCK" ? "BLOCKED" : "PENALIZED";
|
|
290
|
+
console.log(` ${label} by NeuroVerse governance`);
|
|
291
|
+
console.log(` Reason: ${verdict.reason ?? ""}`);
|
|
292
|
+
console.log(`\n ScienceClaw execution SKIPPED`);
|
|
293
|
+
console.log(` ${"=".repeat(50)}\n`);
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
console.log(` ${decision} — proceeding\n`);
|
|
297
|
+
// ── Level 2: Run ScienceClaw + watch artifact file ──
|
|
298
|
+
const artifactDir = (0, path_1.join)((0, os_1.homedir)(), ".scienceclaw", "artifacts", parsed.agent);
|
|
299
|
+
const storePath = (0, path_1.join)(artifactDir, "store.jsonl");
|
|
300
|
+
const globalPath = (0, path_1.join)((0, os_1.homedir)(), ".scienceclaw", "artifacts", "global_index.jsonl");
|
|
301
|
+
// Watch both the agent store and global index
|
|
302
|
+
console.log(` [2/3] Running ScienceClaw + watching artifacts...`);
|
|
303
|
+
console.log(` Agent store: ${storePath}`);
|
|
304
|
+
console.log(` Global index: ${globalPath}\n`);
|
|
305
|
+
const storeWatcher = watchArtifactFile(storePath, parsed.agent);
|
|
306
|
+
const globalWatcher = watchArtifactFile(globalPath, parsed.agent);
|
|
307
|
+
const cmd = SCIENCECLAW_BIN;
|
|
308
|
+
const cmdArgs = parsed.passthroughArgs;
|
|
309
|
+
console.log(` Executing: ${cmd} ${cmdArgs.join(" ")}`);
|
|
310
|
+
console.log(` ${"=".repeat(50)}\n`);
|
|
311
|
+
const child = (0, child_process_1.spawn)(cmd, cmdArgs, {
|
|
312
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
313
|
+
shell: process.platform === "win32",
|
|
314
|
+
});
|
|
315
|
+
// Pass through stdout and stderr so user sees ScienceClaw's normal output
|
|
316
|
+
child.stdout?.on("data", (chunk) => {
|
|
317
|
+
process.stdout.write(chunk);
|
|
318
|
+
});
|
|
319
|
+
child.stderr?.on("data", (chunk) => {
|
|
320
|
+
process.stderr.write(chunk);
|
|
321
|
+
});
|
|
322
|
+
child.on("error", (err) => {
|
|
323
|
+
storeWatcher.stop();
|
|
324
|
+
globalWatcher.stop();
|
|
325
|
+
if (err.code === "ENOENT") {
|
|
326
|
+
console.log(`\n ERROR: '${cmd}' not found on PATH`);
|
|
327
|
+
console.log(` Install ScienceClaw: git clone https://github.com/lamm-mit/scienceclaw && cd scienceclaw && pip install -e .`);
|
|
328
|
+
console.log(` Or set SCIENCECLAW_BIN=/path/to/scienceclaw-post`);
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
console.log(`\n ERROR: Failed to start ScienceClaw: ${err.message}`);
|
|
332
|
+
}
|
|
333
|
+
process.exit(127);
|
|
334
|
+
});
|
|
335
|
+
child.on("close", async (code) => {
|
|
336
|
+
// Wait a moment for final file writes to flush
|
|
337
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
338
|
+
storeWatcher.stop();
|
|
339
|
+
globalWatcher.stop();
|
|
340
|
+
// ── Level 3: Completion report ──
|
|
341
|
+
console.log(`\n ${"=".repeat(50)}`);
|
|
342
|
+
console.log(` [3/3] Session complete\n`);
|
|
343
|
+
console.log(` Status: ${code === 0 ? "Completed successfully" : `Exited with code ${code}`}`);
|
|
344
|
+
console.log(` Artifacts: ${stats.artifactsSeen}`);
|
|
345
|
+
console.log(` Evaluations: ${stats.evaluated}`);
|
|
346
|
+
console.log(` Allowed: ${stats.allowed}`);
|
|
347
|
+
console.log(` Blocked: ${stats.blocked}`);
|
|
348
|
+
console.log(` Penalized: ${stats.penalized}`);
|
|
349
|
+
console.log(` Rewarded: ${stats.rewarded}`);
|
|
350
|
+
// Report to governance server
|
|
351
|
+
evaluate("investigation_complete", parsed.agent, {
|
|
352
|
+
description: `Agent ${parsed.agent} investigation completed`,
|
|
353
|
+
exit_code: code,
|
|
354
|
+
stats,
|
|
355
|
+
}).catch(() => { });
|
|
356
|
+
console.log(`\n ${"=".repeat(50)}\n`);
|
|
357
|
+
process.exit(code ?? 0);
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
main().catch((err) => {
|
|
361
|
+
console.error(`Fatal error: ${err.message}`);
|
|
362
|
+
process.exit(1);
|
|
363
|
+
});
|
|
@@ -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: [
|