@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.
Files changed (38) hide show
  1. package/README.md +187 -535
  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-CH_VswRM.css +1 -0
  6. package/dist/assets/index-sT4b_z7w.js +686 -0
  7. package/dist/assets/{reportEngine-D2ZrMny8.js → reportEngine-Bu8bB5Yq.js} +1 -1
  8. package/dist/connectors/nv-scienceclaw-post.js +363 -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/universalAdapter.js +371 -0
  25. package/dist/engine/worldBridge.js +22 -8
  26. package/dist/index.html +2 -2
  27. package/dist/lib/reasoningEngine.js +17 -1
  28. package/dist/lib/simulationAdapter.js +11 -11
  29. package/dist/lib/swarmParser.js +1 -1
  30. package/dist/runtime/govern.js +160 -7
  31. package/dist/runtime/index.js +1 -4
  32. package/dist/runtime/types.js +91 -0
  33. package/package.json +23 -6
  34. package/dist/adapters/mirofish.js +0 -461
  35. package/dist/assets/index-B64NuIXu.css +0 -1
  36. package/dist/assets/index-BMkPevVr.js +0 -532
  37. package/dist/assets/mirotir-logo-DUexumBH.svg +0 -185
  38. 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-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
- * 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: [