@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.
Files changed (37) hide show
  1. package/README.md +90 -3
  2. package/connectors/nv_mirofish_wrapper.py +841 -0
  3. package/connectors/nv_scienceclaw_wrapper.py +453 -0
  4. package/dist/adapters/scienceclaw.js +52 -2
  5. package/dist/assets/index-B43_0HyO.css +1 -0
  6. package/dist/assets/index-CdghpsS8.js +595 -0
  7. package/dist/assets/{reportEngine-D2ZrMny8.js → reportEngine-CYSZfooa.js} +1 -1
  8. package/dist/connectors/nv-scienceclaw-post.js +376 -0
  9. package/dist/engine/aiProvider.js +82 -3
  10. package/dist/engine/analyzer.js +12 -24
  11. package/dist/engine/cli.js +89 -114
  12. package/dist/engine/dynamicsGovernance.js +4 -0
  13. package/dist/engine/fullGovernedLoop.js +16 -1
  14. package/dist/engine/goalEngine.js +3 -4
  15. package/dist/engine/governance.js +18 -0
  16. package/dist/engine/index.js +19 -28
  17. package/dist/engine/intentTranslator.js +281 -0
  18. package/dist/engine/liveAdapter.js +100 -18
  19. package/dist/engine/liveVisualizer.js +2071 -1023
  20. package/dist/engine/primeRadiant.js +2 -8
  21. package/dist/engine/reasoningEngine.js +2 -7
  22. package/dist/engine/scenarioCapsule.js +5 -5
  23. package/dist/engine/swarmSimulation.js +1 -9
  24. package/dist/engine/worldBridge.js +22 -8
  25. package/dist/index.html +2 -2
  26. package/dist/lib/reasoningEngine.js +17 -1
  27. package/dist/lib/simulationAdapter.js +11 -11
  28. package/dist/lib/swarmParser.js +1 -1
  29. package/dist/runtime/govern.js +160 -7
  30. package/dist/runtime/index.js +1 -4
  31. package/dist/runtime/types.js +91 -0
  32. package/package.json +23 -6
  33. package/dist/adapters/mirofish.js +0 -461
  34. package/dist/assets/index-B64NuIXu.css +0 -1
  35. package/dist/assets/index-BMkPevVr.js +0 -532
  36. package/dist/assets/mirotir-logo-DUexumBH.svg +0 -185
  37. 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-BMkPevVr.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};
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
- * Auto-detect the best available provider based on environment variables.
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. (none) deterministic (Math.random(), no AI)
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`);
@@ -1,31 +1,21 @@
1
1
  "use strict";
2
2
  /**
3
- * MiroFish Simulation Analyzer
3
+ * Simulation Analyzer
4
4
  *
5
- * THE VIRAL WEDGE: "Explain My MiroFish Simulation"
5
+ * Users paste simulation output and NV-SIM explains it.
6
6
  *
7
- * Users paste MiroFish simulation output → Mirotir explains it.
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.analyzeMiroFishSimulation = analyzeMiroFishSimulation;
12
+ exports.analyzeSimulation = analyzeSimulation;
23
13
  const governance_1 = require("./governance");
24
14
  // ============================================
25
15
  // SIMULATION PARSER
26
16
  // ============================================
27
17
  /**
28
- * Parse raw MiroFish output into structured format.
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 MiroFish simulation and produce structured insights.
409
+ * Analyze a simulation and produce structured insights.
420
410
  *
421
- * This is THE viral feature:
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 analyzeMiroFishSimulation(input) {
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 MiroFish simulation data. Paste the JSON output from your simulation.",
436
- detail: "Expected a MiroFish simulation response object with steps and agent_actions",
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 MiroFish simulation for at least 2 steps before analyzing.",
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: `MiroFish simulation analysis: ${simulation.simulation_id ?? "unknown"}`,
609
+ scenario: `Simulation analysis: ${simulation.simulation_id ?? "unknown"}`,
622
610
  }),
623
611
  authority_level: "ANALYSIS_ONLY",
624
612
  enforced_constraints: [