@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
@@ -0,0 +1,281 @@
1
+ "use strict";
2
+ /**
3
+ * Intent Translator — Natural Language → World Rules
4
+ *
5
+ * Scientists describe what they want in plain language.
6
+ * AI translates that into NeuroverseOS governance rules.
7
+ *
8
+ * Uses the same AI provider infrastructure (BYOM) as the rest of the system.
9
+ * Falls back to deterministic heuristics when no AI is configured.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.translateIntent = translateIntent;
13
+ const aiProvider_1 = require("./aiProvider");
14
+ // ============================================
15
+ // SYSTEM PROMPT
16
+ // ============================================
17
+ const TRANSLATE_INTENT_PROMPT = `You are a governance rule translator for NeuroverseOS, a system that governs how AI agent swarms behave during scientific discovery.
18
+
19
+ A scientist has described their research intent in five categories:
20
+ - EXPLORE: What agents should investigate (becomes ALLOW rules and domain scope)
21
+ - EXCLUDE: What should never be concluded or published (becomes BLOCK rules)
22
+ - ELEVATE: What makes results valuable (becomes PRIORITIZE rules)
23
+ - EXPERIMENT: If/then rules with explicit verdicts (becomes REWARD, PENALIZE, or NEUTRAL rules)
24
+ - OPEN-ENDED: Free-form ideas the scientist wants to test (translate into the most appropriate rule types)
25
+
26
+ Translate their intent into structured governance rules. Return ONLY valid JSON:
27
+ {
28
+ "worldName": "short name for this research world (max 50 chars)",
29
+ "thesis": "one-sentence thesis for this world",
30
+ "rules": [
31
+ {
32
+ "source": "explore|exclude|elevate|experiment|open_ended",
33
+ "naturalLanguage": "human-readable rule description",
34
+ "ruleType": "ALLOW|BLOCK|PRIORITIZE|GATE|REWARD|PENALIZE|NEUTRAL",
35
+ "condition": {
36
+ "field": "the state/result field to check (e.g. result.confidence, action.domain, result.sources_count, agent.behavior, agent.influence)",
37
+ "operator": ">|<|>=|<=|==|!=|contains|matches",
38
+ "value": "the threshold or match value"
39
+ },
40
+ "effect": "what happens — for REWARD: boost_influence, priority, faster_execution, weight_increase. For PENALIZE: freeze, reduce_influence, increase_risk, cooldown. Include magnitude and rounds where appropriate.",
41
+ "confidence": 0.0-1.0
42
+ }
43
+ ],
44
+ "suggestedInvariants": ["immutable rules that should never be violated"]
45
+ }
46
+
47
+ Guidelines:
48
+ - Each natural language sentence should produce at least one rule
49
+ - EXCLUDE items → BLOCK rules with specific, enforceable conditions
50
+ - ELEVATE items → PRIORITIZE rules that boost result ranking
51
+ - EXPLORE items → ALLOW rules that define the investigation scope
52
+ - EXPERIMENT if/then items → REWARD or PENALIZE rules with concrete consequences (e.g. "freeze for 3 rounds", "boost influence by 2x")
53
+ - OPEN-ENDED items → use your best judgment to create the most appropriate rule types. These are experimental — the scientist is exploring what's possible
54
+ - Be specific about conditions — use quantitative thresholds where the user implies them
55
+ - If the user says "confidence below 70%", the condition should be {field: "result.confidence", operator: "<", value: 0.7}
56
+ - suggestedInvariants are the hardest constraints — things that must NEVER be violated`;
57
+ // ============================================
58
+ // MAIN TRANSLATION FUNCTION
59
+ // ============================================
60
+ /**
61
+ * Translate a scientist's natural language intent into governance rules.
62
+ * Uses AI when available, falls back to deterministic parsing.
63
+ */
64
+ async function translateIntent(intent) {
65
+ const providerName = (0, aiProvider_1.getDefaultProviderName)();
66
+ if (providerName === "deterministic") {
67
+ return deterministicTranslation(intent);
68
+ }
69
+ try {
70
+ const provider = (0, aiProvider_1.getAIProvider)(providerName);
71
+ return await aiTranslation(intent, provider);
72
+ }
73
+ catch {
74
+ // AI failed — fall back to deterministic
75
+ return deterministicTranslation(intent);
76
+ }
77
+ }
78
+ // ============================================
79
+ // AI-POWERED TRANSLATION
80
+ // ============================================
81
+ async function aiTranslation(intent, provider) {
82
+ const experimentText = intent.experiment.length > 0
83
+ ? intent.experiment.map(r => `- IF ${r.condition} THEN ${r.outcome} [${r.verdict}]`).join("\n")
84
+ : "(none)";
85
+ const userMessage = `Translate this research intent into governance rules:
86
+
87
+ EXPLORE (what to investigate):
88
+ ${intent.explore || "(not specified)"}
89
+
90
+ EXCLUDE (what to reject):
91
+ ${intent.exclude || "(not specified)"}
92
+
93
+ ELEVATE (what to trust):
94
+ ${intent.elevate || "(not specified)"}
95
+
96
+ EXPERIMENT (if/then rules):
97
+ ${experimentText}
98
+
99
+ OPEN-ENDED (other things to try):
100
+ ${intent.openEnded || "(not specified)"}`;
101
+ let text;
102
+ if (provider instanceof aiProvider_1.AnthropicProvider) {
103
+ const client = await provider.getClient();
104
+ const response = await client.messages.create({
105
+ model: provider.model,
106
+ max_tokens: 1024,
107
+ system: TRANSLATE_INTENT_PROMPT,
108
+ messages: [{ role: "user", content: userMessage }],
109
+ });
110
+ text = response.content[0]?.type === "text" ? response.content[0].text : "{}";
111
+ }
112
+ else if (provider instanceof aiProvider_1.OpenAICompatibleProvider) {
113
+ text = await provider.chatCompletion(TRANSLATE_INTENT_PROMPT, userMessage, 1024);
114
+ }
115
+ else {
116
+ // Generic provider — use translate method as proxy
117
+ return deterministicTranslation(intent);
118
+ }
119
+ try {
120
+ const parsed = JSON.parse(text);
121
+ return {
122
+ rules: (parsed.rules ?? []).map((r, i) => ({
123
+ id: `ai_rule_${i}`,
124
+ source: r.source ?? "explore",
125
+ naturalLanguage: r.naturalLanguage ?? "",
126
+ ruleType: r.ruleType ?? "ALLOW",
127
+ condition: r.condition ?? { field: "unknown", operator: "==", value: true },
128
+ effect: r.effect ?? "",
129
+ confidence: r.confidence ?? 0.7,
130
+ })),
131
+ worldName: parsed.worldName ?? deriveWorldName(intent),
132
+ thesis: parsed.thesis ?? `Research world for: ${intent.explore.slice(0, 80)}`,
133
+ suggestedInvariants: parsed.suggestedInvariants ?? [],
134
+ translatedBy: `ai:${provider.name}`,
135
+ };
136
+ }
137
+ catch {
138
+ return deterministicTranslation(intent);
139
+ }
140
+ }
141
+ // ============================================
142
+ // DETERMINISTIC FALLBACK
143
+ // ============================================
144
+ function deterministicTranslation(intent) {
145
+ const rules = [];
146
+ let idx = 0;
147
+ // EXPLORE → ALLOW rules
148
+ for (const sentence of splitSentences(intent.explore)) {
149
+ rules.push({
150
+ id: `rule_${idx++}`,
151
+ source: "explore",
152
+ naturalLanguage: `Agents should investigate: ${sentence}`,
153
+ ruleType: "ALLOW",
154
+ condition: { field: "action.domain", operator: "matches", value: extractKeyTerms(sentence) },
155
+ effect: "Allow investigation with priority",
156
+ confidence: 0.8,
157
+ });
158
+ }
159
+ // EXCLUDE → BLOCK rules
160
+ for (const sentence of splitSentences(intent.exclude)) {
161
+ const { field, operator, value } = parseBlockCondition(sentence);
162
+ rules.push({
163
+ id: `rule_${idx++}`,
164
+ source: "exclude",
165
+ naturalLanguage: `Block: ${sentence}`,
166
+ ruleType: "BLOCK",
167
+ condition: { field, operator, value },
168
+ effect: "Block action or publication",
169
+ confidence: 0.85,
170
+ });
171
+ }
172
+ // ELEVATE → PRIORITIZE rules
173
+ for (const sentence of splitSentences(intent.elevate)) {
174
+ rules.push({
175
+ id: `rule_${idx++}`,
176
+ source: "elevate",
177
+ naturalLanguage: `Prioritize: ${sentence}`,
178
+ ruleType: "PRIORITIZE",
179
+ condition: { field: "result.quality", operator: "matches", value: sentence.slice(0, 80) },
180
+ effect: "Boost priority score by 2x",
181
+ confidence: 0.75,
182
+ });
183
+ }
184
+ // EXPERIMENT if/then → REWARD / PENALIZE / NEUTRAL
185
+ for (const ifThen of intent.experiment) {
186
+ const effectMap = {
187
+ REWARD: `boost_influence for 3 rounds: ${ifThen.outcome}`,
188
+ PENALIZE: `reduce_influence for 3 rounds: ${ifThen.outcome}`,
189
+ NEUTRAL: `log observation: ${ifThen.outcome}`,
190
+ };
191
+ rules.push({
192
+ id: `rule_${idx++}`,
193
+ source: "experiment",
194
+ naturalLanguage: `If ${ifThen.condition} → ${ifThen.outcome}`,
195
+ ruleType: ifThen.verdict,
196
+ condition: { field: "agent.behavior", operator: "matches", value: ifThen.condition.slice(0, 80) },
197
+ effect: effectMap[ifThen.verdict] ?? ifThen.outcome,
198
+ confidence: 0.9,
199
+ });
200
+ }
201
+ // OPEN-ENDED → NEUTRAL rules (AI would do better)
202
+ for (const sentence of splitSentences(intent.openEnded)) {
203
+ rules.push({
204
+ id: `rule_${idx++}`,
205
+ source: "open_ended",
206
+ naturalLanguage: sentence,
207
+ ruleType: "NEUTRAL",
208
+ condition: { field: "scenario.context", operator: "matches", value: extractKeyTerms(sentence) },
209
+ effect: "Experimental rule — AI will refine into specific governance",
210
+ confidence: 0.6,
211
+ });
212
+ }
213
+ // Derive invariants from EXCLUDE items
214
+ const invariants = splitSentences(intent.exclude)
215
+ .filter(s => {
216
+ const lower = s.toLowerCase();
217
+ return lower.includes("never") || lower.includes("must not") || lower.includes("contradict");
218
+ })
219
+ .map(s => s);
220
+ return {
221
+ rules,
222
+ worldName: deriveWorldName(intent),
223
+ thesis: intent.explore.trim()
224
+ ? `Governed investigation of: ${intent.explore.split(/[.;\n]/)[0].trim().slice(0, 100)}`
225
+ : "Custom governed research world",
226
+ suggestedInvariants: invariants,
227
+ translatedBy: "deterministic",
228
+ };
229
+ }
230
+ // ============================================
231
+ // HELPERS
232
+ // ============================================
233
+ function splitSentences(text) {
234
+ if (!text.trim())
235
+ return [];
236
+ return text
237
+ .split(/[.;\n]+/)
238
+ .map(s => s.trim())
239
+ .filter(s => s.length > 3);
240
+ }
241
+ function extractKeyTerms(text) {
242
+ const stopWords = new Set([
243
+ "the", "a", "an", "and", "or", "but", "in", "on", "at", "to", "for",
244
+ "of", "with", "by", "that", "this", "is", "are", "was", "were", "be",
245
+ "been", "being", "have", "has", "had", "do", "does", "did", "will",
246
+ "would", "could", "should", "may", "might", "can",
247
+ ]);
248
+ return text
249
+ .toLowerCase()
250
+ .split(/\s+/)
251
+ .filter(w => w.length > 2 && !stopWords.has(w))
252
+ .slice(0, 6)
253
+ .join(" ");
254
+ }
255
+ function parseBlockCondition(text) {
256
+ const lower = text.toLowerCase();
257
+ if (lower.includes("single") && lower.includes("source")) {
258
+ return { field: "result.sources_count", operator: "<", value: 2 };
259
+ }
260
+ if (lower.includes("confidence") || lower.includes("below")) {
261
+ const match = text.match(/(\d+)%?/);
262
+ const threshold = match ? parseInt(match[1]) / 100 : 0.7;
263
+ return { field: "result.confidence", operator: "<", value: threshold };
264
+ }
265
+ if (lower.includes("reproduc")) {
266
+ return { field: "result.reproducible", operator: "==", value: false };
267
+ }
268
+ if (lower.includes("contradict")) {
269
+ return { field: "result.contradicts_laws", operator: "==", value: true };
270
+ }
271
+ return { field: "result.excluded_pattern", operator: "matches", value: text.slice(0, 80) };
272
+ }
273
+ function deriveWorldName(intent) {
274
+ const explore = intent.explore.trim();
275
+ if (!explore)
276
+ return "Custom World";
277
+ const first = explore.split(/[.,;\n]/)[0].trim();
278
+ if (first.length <= 50)
279
+ return first;
280
+ return first.slice(0, 47) + "...";
281
+ }
@@ -24,11 +24,13 @@
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
25
  exports.ADAPTER_REGISTRY = void 0;
26
26
  exports.createProcessAdapter = createProcessAdapter;
27
- exports.parseMiroFishLine = parseMiroFishLine;
27
+ exports.parseStepBasedLine = parseStepBasedLine;
28
28
  exports.parseGenericJsonLine = parseGenericJsonLine;
29
+ exports.parseScienceClawLine = parseScienceClawLine;
29
30
  exports.createAdapter = createAdapter;
30
31
  const child_process_1 = require("child_process");
31
32
  const events_1 = require("events");
33
+ const types_1 = require("../runtime/types");
32
34
  function createParserState() {
33
35
  return {
34
36
  currentRound: 0,
@@ -44,10 +46,10 @@ function createParserState() {
44
46
  * Usage:
45
47
  * const adapter = createProcessAdapter({
46
48
  * type: "process",
47
- * label: "MiroFish (Live)",
49
+ * label: "My Simulation (Live)",
48
50
  * command: "node",
49
- * args: ["mirofish.js"],
50
- * parser: parseMiroFishLine,
51
+ * args: ["sim.js"],
52
+ * parser: parseStepBasedLine,
51
53
  * });
52
54
  * adapter.on("round", (round) => { ... });
53
55
  * await adapter.start();
@@ -145,12 +147,12 @@ function createProcessAdapter(config) {
145
147
  return emitter;
146
148
  }
147
149
  // ============================================
148
- // MIROFISH STDOUT PARSER
150
+ // GENERIC STEP-BASED STDOUT PARSER
149
151
  // ============================================
150
152
  /**
151
- * Parses MiroFish stdout line-by-line.
153
+ * Parses step-based stdout line-by-line.
152
154
  *
153
- * Expected MiroFish output formats:
155
+ * Expected output formats:
154
156
  *
155
157
  * JSON-per-line (preferred):
156
158
  * {"step":1,"agent_actions":[{"agent_id":"a1","action":"buy","sentiment":0.5,"confidence":0.8}]}
@@ -164,7 +166,7 @@ function createProcessAdapter(config) {
164
166
  * We emit a LiveSimulationRound when we detect a new step number
165
167
  * (meaning the previous step is complete).
166
168
  */
167
- function parseMiroFishLine(line, state) {
169
+ function parseStepBasedLine(line, state) {
168
170
  // Try JSON first
169
171
  if (line.startsWith("{")) {
170
172
  try {
@@ -199,7 +201,7 @@ function parseMiroFishLine(line, state) {
199
201
  // Complete step as JSON — emit immediately
200
202
  return {
201
203
  round: data.step,
202
- source: "mirofish",
204
+ source: "generic",
203
205
  agentActions: data.agent_actions.map((a) => ({
204
206
  agent: a.agent_id ?? a.agent ?? "unknown",
205
207
  action: a.action ?? "",
@@ -227,7 +229,7 @@ function parseMiroFishLine(line, state) {
227
229
  if (stepNum > state.currentRound && state.currentRound > 0) {
228
230
  result = {
229
231
  round: state.currentRound,
230
- source: "mirofish",
232
+ source: "generic",
231
233
  agentActions: [...state.pendingActions],
232
234
  systemEvents: state.pendingEvents.length > 0 ? [...state.pendingEvents] : undefined,
233
235
  emergentDynamics: state.pendingDynamics.length > 0 ? [...state.pendingDynamics] : undefined,
@@ -303,18 +305,98 @@ function parseGenericJsonLine(line, _state) {
303
305
  return null;
304
306
  }
305
307
  }
308
+ // ============================================
309
+ // SCIENCECLAW STDOUT PARSER
310
+ // ============================================
311
+ /**
312
+ * Parses structured JSON lines emitted by ScienceClaw.
313
+ *
314
+ * IMPORTANT: Schema-gated governance — only strict JSON lines are processed.
315
+ * Non-JSON output is passed through without evaluation (by design).
316
+ * No regex or heuristic parsing is used, for safety and determinism.
317
+ *
318
+ * Expected format (one JSON object per line):
319
+ * {"cycle":1,"artifacts":[{"agent_id":"a1","type":"hypothesis","description":"...","confidence":0.7}]}
320
+ *
321
+ * Also accepted (alternate field names, same structure):
322
+ * {"step":1,"agents":[...]}
323
+ * {"round":1,"agent_actions":[...]}
324
+ *
325
+ * Any future formats must be added as explicit structured cases — never inferred.
326
+ */
327
+ function parseScienceClawLine(line, _state) {
328
+ if (!line.startsWith("{"))
329
+ return null;
330
+ let data;
331
+ try {
332
+ data = JSON.parse(line);
333
+ }
334
+ catch {
335
+ return null;
336
+ }
337
+ // ── Route through NV Governance Schema v1 ──
338
+ const validated = (0, types_1.validateGovernableCycle)(data, "scienceclaw");
339
+ // Schema validation failed — line is ungovernable
340
+ if (typeof validated === "string")
341
+ return null;
342
+ // Map validated NVGovernableEvents → LiveAgentActions
343
+ const agents = validated.events.map((ev) => {
344
+ // Confidence-weighted impact: higher confidence + reproduced = more constructive
345
+ const impact = ev.reproduced
346
+ ? Math.min(1, ev.confidence * 0.8 + 0.2) // reproduced = positive signal
347
+ : (ev.confidence - 0.4) * 1.2; // unreproduced = depends on confidence
348
+ return {
349
+ agent: ev.agentId,
350
+ action: ev.actionType,
351
+ impact: Number(Math.max(-1, Math.min(1, impact)).toFixed(3)),
352
+ confidence: ev.confidence,
353
+ metadata: {
354
+ artifactType: ev.actionType,
355
+ reproduced: ev.reproduced,
356
+ citations: ev.references,
357
+ description: ev.description,
358
+ nvSchema: ev.schema,
359
+ },
360
+ };
361
+ });
362
+ // Map reactor matches to system events (pass-through, not schema-gated)
363
+ const matches = (data.matches ?? data.reactor_matches ?? []);
364
+ const systemEvents = matches.map((m) => ({
365
+ id: m.matchType ?? m.type ?? "match",
366
+ label: m.description ?? `${m.matchType ?? "match"} (strength: ${(m.strength ?? 0).toFixed(2)})`,
367
+ severity: (m.matchType === "contradiction" || m.type === "contradiction")
368
+ ? "moderate"
369
+ : "minor",
370
+ }));
371
+ // Extract emergent dynamics (pass-through)
372
+ const dynamics = (data.dynamics ?? data.emergent ?? []);
373
+ if (data.convergence_detected) {
374
+ dynamics.push("convergence_detected");
375
+ }
376
+ if (data.risk_flags) {
377
+ const flags = data.risk_flags;
378
+ dynamics.push(...(Array.isArray(flags) ? flags : [flags]));
379
+ }
380
+ return {
381
+ round: validated.cycle,
382
+ source: validated.source,
383
+ agentActions: agents,
384
+ systemEvents,
385
+ emergentDynamics: dynamics,
386
+ };
387
+ }
306
388
  exports.ADAPTER_REGISTRY = {
307
- mirofish: {
308
- id: "mirofish",
309
- label: "MiroFish (Live)",
310
- description: "Spawn MiroFish swarm simulation and stream agent behavior",
389
+ scienceclaw: {
390
+ id: "scienceclaw",
391
+ label: "ScienceClaw (Live)",
392
+ description: "Spawn ScienceClaw investigation and govern artifact cycles in real time",
311
393
  createConfig: (opts = {}) => ({
312
394
  type: "process",
313
- label: "MiroFish (Live)",
314
- command: opts.command ?? "node",
315
- args: (opts.args ?? "mirofish.js").split(" "),
395
+ label: "ScienceClaw (Live)",
396
+ command: opts.command ?? "scienceclaw-post",
397
+ args: (opts.args ?? "").split(" ").filter(Boolean),
316
398
  cwd: opts.cwd,
317
- parser: parseMiroFishLine,
399
+ parser: parseScienceClawLine,
318
400
  }),
319
401
  },
320
402
  generic: {