@neuroverseos/nv-sim 0.1.0 → 0.1.4

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.
@@ -110,10 +110,15 @@ function runFullGovernanceChecks(request, worldLite) {
110
110
  // Convert verdict to constitutional checks
111
111
  const nvChecks = (0, worldBridge_1.verdictToConstitutionalChecks)(guardVerdict);
112
112
  results.push(...nvChecks);
113
- // If guard blocks, stop reasoning
113
+ // If guard blocks, note the intervention but continue in advisory mode
114
+ // (Reasoning continues so user can see what governance changed)
114
115
  if (guardVerdict.status === "BLOCK") {
115
- allowed = false;
116
- blockReason = `NeuroverseOS governance: ${guardVerdict.reason ?? "Action blocked by guard"}`;
116
+ results.push({
117
+ rule_id: "NV-BLOCK",
118
+ rule: "NeuroverseOS guard issued BLOCK — governance intervention active",
119
+ passed: false,
120
+ detail: `Guard blocked: ${guardVerdict.reason ?? "Action blocked by guard"}. Continuing in governed advisory mode.`,
121
+ });
117
122
  }
118
123
  // If guard pauses, note it but continue (reasoning is advisory)
119
124
  if (guardVerdict.status === "PAUSE") {
@@ -17,6 +17,7 @@ exports.TRADING_DEMO = void 0;
17
17
  exports.runGovernedComparison = runGovernedComparison;
18
18
  const swarmSimulation_1 = require("./swarmSimulation");
19
19
  const worldBridge_1 = require("./worldBridge");
20
+ const narrativeInjection_1 = require("./narrativeInjection");
20
21
  // ============================================
21
22
  // GOVERNED REACTION MODEL
22
23
  // ============================================
@@ -39,11 +40,11 @@ function applyGovernanceToReactions(reactions, invariants, gates, roundIndex, cu
39
40
  // Cap extreme negative impacts (e.g., "maintain liquidity floor")
40
41
  if (desc.includes("floor") || desc.includes("maintain") || desc.includes("minimum")) {
41
42
  for (const agent of governed) {
42
- if (agent.impact < -0.6) {
43
+ if (agent.impact < -0.35) {
43
44
  const original = agent.impact;
44
- agent.impact = Math.max(agent.impact, -0.5);
45
+ agent.impact = Math.max(agent.impact, -0.3);
45
46
  if (original !== agent.impact) {
46
- interventions.push(`[${inv.id}] Capped ${agent.stakeholder_id} negative impact: ${original.toFixed(2)} → ${agent.impact.toFixed(2)} (${inv.description})`);
47
+ interventions.push(`[BLOCK] ${agent.stakeholder_id}: Liquidity floor violated — impact capped ${original.toFixed(2)} → ${agent.impact.toFixed(2)} (${inv.description})`);
47
48
  }
48
49
  }
49
50
  }
@@ -51,11 +52,11 @@ function applyGovernanceToReactions(reactions, invariants, gates, roundIndex, cu
51
52
  // Limit excessive positive speculation (e.g., "no leverage > 5x")
52
53
  if (desc.includes("leverage") || desc.includes("limit") || desc.includes("cap") || desc.includes("restrict")) {
53
54
  for (const agent of governed) {
54
- if (agent.impact > 0.7) {
55
+ if (agent.impact > 0.45) {
55
56
  const original = agent.impact;
56
- agent.impact = Math.min(agent.impact, 0.6);
57
+ agent.impact = Math.min(agent.impact, 0.4);
57
58
  if (original !== agent.impact) {
58
- interventions.push(`[${inv.id}] Capped ${agent.stakeholder_id} excessive position: ${original.toFixed(2)} → ${agent.impact.toFixed(2)} (${inv.description})`);
59
+ interventions.push(`[BLOCK] ${agent.stakeholder_id}: Leverage limit exceeded — position capped ${original.toFixed(2)} → ${agent.impact.toFixed(2)} (${inv.description})`);
59
60
  }
60
61
  }
61
62
  }
@@ -65,10 +66,10 @@ function applyGovernanceToReactions(reactions, invariants, gates, roundIndex, cu
65
66
  // Pull extreme reactions toward the mean
66
67
  const avgImpact = governed.reduce((s, a) => s + a.impact, 0) / governed.length;
67
68
  for (const agent of governed) {
68
- if (Math.abs(agent.impact - avgImpact) > 0.4) {
69
+ if (Math.abs(agent.impact - avgImpact) > 0.25) {
69
70
  const original = agent.impact;
70
71
  agent.impact = agent.impact * 0.7 + avgImpact * 0.3;
71
- interventions.push(`[${inv.id}] Rebalanced ${agent.stakeholder_id}: ${original.toFixed(2)} → ${agent.impact.toFixed(2)} (${inv.description})`);
72
+ interventions.push(`[PAUSE] Rebalanced ${agent.stakeholder_id}: drift ${original.toFixed(2)} → ${agent.impact.toFixed(2)} (${inv.description})`);
72
73
  }
73
74
  }
74
75
  }
@@ -92,7 +93,7 @@ function applyGovernanceToReactions(reactions, invariants, gates, roundIndex, cu
92
93
  if (gate.severity === "critical") {
93
94
  // Critical gates act as circuit breakers — compress all impacts toward zero
94
95
  const avgAbsImpact = governed.reduce((s, a) => s + Math.abs(a.impact), 0) / governed.length;
95
- if (avgAbsImpact > 0.5 || cumulativeAvgImpact < -0.3) {
96
+ if (avgAbsImpact > 0.3 || cumulativeAvgImpact < -0.15) {
96
97
  const dampingFactor = 0.6;
97
98
  for (const agent of governed) {
98
99
  const original = agent.impact;
@@ -131,13 +132,14 @@ function applyGovernanceToReactions(reactions, invariants, gates, roundIndex, cu
131
132
  * Same agents, same scenario, same reaction model —
132
133
  * but world rules reshape the outcomes.
133
134
  */
134
- async function runGovernedSwarmSimulation(scenario, stakeholders, paths, config, worldDef, nvWorld, request) {
135
+ async function runGovernedSwarmSimulation(scenario, stakeholders, paths, config, worldDef, nvWorld, request, narrativeEvents) {
135
136
  // First run the unmodified simulation to get raw reactions
136
137
  const rawResult = await (0, swarmSimulation_1.runSwarmSimulation)(scenario, stakeholders, paths, config);
137
138
  // Now re-run applying governance to each round
138
139
  const governedRounds = [];
139
140
  const allInterventions = [];
140
141
  const allGuardVerdicts = [];
142
+ const allNarrativeImpacts = [];
141
143
  let cumulativeAvgImpact = 0;
142
144
  for (const round of rawResult.rounds) {
143
145
  // --- Real governance engine: evaluate each agent's action ---
@@ -159,6 +161,14 @@ async function runGovernedSwarmSimulation(scenario, stakeholders, paths, config,
159
161
  }
160
162
  }
161
163
  }
164
+ // --- Narrative injection: information shocks ---
165
+ if (narrativeEvents && narrativeEvents.length > 0) {
166
+ const roundEvents = (0, narrativeInjection_1.getEventsForRound)(narrativeEvents, round.round);
167
+ for (const event of roundEvents) {
168
+ const impact = (0, narrativeInjection_1.injectNarrative)(round.reactions, event, stakeholders);
169
+ allNarrativeImpacts.push(impact);
170
+ }
171
+ }
162
172
  // --- Heuristic governance: invariant/gate enforcement (always applied) ---
163
173
  const { reactions: governedReactions, interventions } = applyGovernanceToReactions(round.reactions, worldDef.invariants, worldDef.gates ?? [], round.round, cumulativeAvgImpact);
164
174
  // Recompute emergent dynamics with governed reactions
@@ -184,6 +194,7 @@ async function runGovernedSwarmSimulation(scenario, stakeholders, paths, config,
184
194
  },
185
195
  allInterventions,
186
196
  guardVerdicts: allGuardVerdicts,
197
+ narrativeImpacts: allNarrativeImpacts,
187
198
  };
188
199
  }
189
200
  function detectGovernedDynamics(reactions, interventions) {
@@ -323,7 +334,7 @@ function buildComparisonNarrative(baseline, governed, worldThesis) {
323
334
  * 3. Compare metrics
324
335
  * 4. Generate narrative
325
336
  */
326
- async function runGovernedComparison(request, worldLite, paths) {
337
+ async function runGovernedComparison(request, worldLite, paths, narrativeEvents) {
327
338
  const stakeholders = (request.stakeholders ?? []).map((s) => typeof s === "string" ? { id: s, disposition: "unknown" } : s);
328
339
  const swarmConfig = request.swarm ?? {
329
340
  enabled: true,
@@ -336,7 +347,7 @@ async function runGovernedComparison(request, worldLite, paths) {
336
347
  const baselineSwarm = await (0, swarmSimulation_1.runSwarmSimulation)(request.scenario, stakeholders, paths, swarmConfig);
337
348
  const baselineMetrics = computeMetrics(baselineSwarm);
338
349
  // Step 2: Governed — same scenario, world rules applied (real engine + heuristics)
339
- const { swarm: governedSwarm, guardVerdicts } = await runGovernedSwarmSimulation(request.scenario, stakeholders, paths, swarmConfig, worldLite, nvWorld, request);
350
+ const { swarm: governedSwarm, guardVerdicts, narrativeImpacts: collectedNarrativeImpacts } = await runGovernedSwarmSimulation(request.scenario, stakeholders, paths, swarmConfig, worldLite, nvWorld, request, narrativeEvents);
340
351
  const governedMetrics = computeMetrics(governedSwarm);
341
352
  // Step 3: Build world simulation (deterministic state evolution via real engine)
342
353
  let worldSimulation;
@@ -417,6 +428,7 @@ async function runGovernedComparison(request, worldLite, paths) {
417
428
  narrative,
418
429
  },
419
430
  governanceStats,
431
+ narrativeImpacts: collectedNarrativeImpacts.length > 0 ? collectedNarrativeImpacts : undefined,
420
432
  };
421
433
  }
422
434
  // ============================================
@@ -478,6 +490,20 @@ exports.TRADING_DEMO = {
478
490
  { id: "GATE-002", label: "Contagion Warning", condition: "contagion_spread == systemic", severity: "critical" },
479
491
  { id: "GATE-003", label: "Leverage Warning", condition: "leverage_ratio > 8", severity: "warning" },
480
492
  ],
493
+ ai_roles: [
494
+ {
495
+ id: "ai_translator",
496
+ type: "ai",
497
+ permissions: ["translate_input"],
498
+ constraints: ["must_output_valid_schema", "no_invention_of_events", "confidence_must_be_provided"],
499
+ },
500
+ {
501
+ id: "ai_analyst",
502
+ type: "ai",
503
+ permissions: ["generate_report", "summarize_trace"],
504
+ constraints: ["must_reference_trace", "must_include_blocked_actions", "must_include_metrics", "no_unverifiable_claims"],
505
+ },
506
+ ],
481
507
  },
482
508
  paths: [
483
509
  {
@@ -21,7 +21,7 @@
21
21
  * Mirotir helps you decide WHAT TO DO → the brain
22
22
  */
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
- exports.parseReasonRequestBody = exports.handleRunPreset = exports.handleGetPreset = exports.handleListPresets = exports.handleHealthCheck = exports.handleCreateCapsule = exports.handleReasonFromCapsule = exports.handleReasonRequest = exports.TRADING_DEMO = exports.runGovernedComparison = exports.analyzeMiroFishSimulation = exports.DEFAULT_MIROFISH_CONFIG = exports.buildMiroFishRequest = exports.miroFishResultToSwarmResult = exports.stakeholdersToMiroFishAgents = exports.runUnifiedSimulation = exports.getMiroFishClient = exports.configureMiroFish = exports.MiroFishError = exports.MiroFishClient = exports.runSwarmSimulation = exports.initNeuroverseModule = exports.validationToEnforcedConstraints = exports.simulationToGovernanceSignals = exports.verdictToConstitutionalChecks = exports.validateScenarioWorld = exports.runWorldSimulation = exports.evaluateScenarioGuard = exports.buildWorldFromScenario = exports.runFullGovernanceChecks = exports.runGovernanceChecks = exports.processReasonRequest = exports.SCENARIO_TEMPLATES = exports.getPresetCapsule = exports.extractCapsuleFromUrl = exports.buildShareableUrl = exports.decodeCapsule = exports.encodeCapsule = exports.capsuleToReasonRequest = exports.createCapsule = exports.runGoalReasoning = void 0;
24
+ exports.extractTrace = exports.generateGovernedReport = exports.evaluateAIAction = exports.listAIProviders = exports.getAIProvider = exports.registerAIProvider = exports.generateDeterministicReport = exports.DeterministicProvider = exports.AI_ROLES = exports.parseReasonRequestBody = exports.handleRunPreset = exports.handleGetPreset = exports.handleListPresets = exports.handleHealthCheck = exports.handleCreateCapsule = exports.handleReasonFromCapsule = exports.handleReasonRequest = exports.TRADING_DEMO = exports.runGovernedComparison = exports.analyzeMiroFishSimulation = exports.DEFAULT_MIROFISH_CONFIG = exports.buildMiroFishRequest = exports.miroFishResultToSwarmResult = exports.stakeholdersToMiroFishAgents = exports.runUnifiedSimulation = exports.getMiroFishClient = exports.configureMiroFish = exports.MiroFishError = exports.MiroFishClient = exports.runSwarmSimulation = exports.initNeuroverseModule = exports.validationToEnforcedConstraints = exports.simulationToGovernanceSignals = exports.verdictToConstitutionalChecks = exports.validateScenarioWorld = exports.runWorldSimulation = exports.evaluateScenarioGuard = exports.buildWorldFromScenario = exports.runFullGovernanceChecks = exports.runGovernanceChecks = exports.processReasonRequest = exports.SCENARIO_TEMPLATES = exports.getPresetCapsule = exports.extractCapsuleFromUrl = exports.buildShareableUrl = exports.decodeCapsule = exports.encodeCapsule = exports.capsuleToReasonRequest = exports.createCapsule = exports.runGoalReasoning = void 0;
25
25
  // Goal-Directed Strategy Engine
26
26
  var goalEngine_1 = require("./goalEngine");
27
27
  Object.defineProperty(exports, "runGoalReasoning", { enumerable: true, get: function () { return goalEngine_1.runGoalReasoning; } });
@@ -80,3 +80,18 @@ Object.defineProperty(exports, "handleListPresets", { enumerable: true, get: fun
80
80
  Object.defineProperty(exports, "handleGetPreset", { enumerable: true, get: function () { return api_1.handleGetPreset; } });
81
81
  Object.defineProperty(exports, "handleRunPreset", { enumerable: true, get: function () { return api_1.handleRunPreset; } });
82
82
  Object.defineProperty(exports, "parseReasonRequestBody", { enumerable: true, get: function () { return api_1.parseReasonRequestBody; } });
83
+ var aiProvider_1 = require("./aiProvider");
84
+ Object.defineProperty(exports, "AI_ROLES", { enumerable: true, get: function () { return aiProvider_1.AI_ROLES; } });
85
+ Object.defineProperty(exports, "DeterministicProvider", { enumerable: true, get: function () { return aiProvider_1.DeterministicProvider; } });
86
+ Object.defineProperty(exports, "generateDeterministicReport", { enumerable: true, get: function () { return aiProvider_1.generateDeterministicReport; } });
87
+ Object.defineProperty(exports, "registerAIProvider", { enumerable: true, get: function () { return aiProvider_1.registerAIProvider; } });
88
+ Object.defineProperty(exports, "getAIProvider", { enumerable: true, get: function () { return aiProvider_1.getAIProvider; } });
89
+ Object.defineProperty(exports, "listAIProviders", { enumerable: true, get: function () { return aiProvider_1.listAIProviders; } });
90
+ Object.defineProperty(exports, "evaluateAIAction", { enumerable: true, get: function () { return aiProvider_1.evaluateAIAction; } });
91
+ var reportEngine_1 = require("./reportEngine");
92
+ Object.defineProperty(exports, "generateGovernedReport", { enumerable: true, get: function () { return reportEngine_1.generateGovernedReport; } });
93
+ Object.defineProperty(exports, "extractTrace", { enumerable: true, get: function () { return reportEngine_1.extractTrace; } });
94
+ // Live Simulation Adapters — plug ANY running simulator into governance + trace
95
+ // NOTE: liveAdapter.ts uses Node.js APIs (child_process, events) and is NOT
96
+ // exported here to avoid Vite browser bundle errors. Import directly from
97
+ // "./liveAdapter" in CLI/server code only.
@@ -0,0 +1,342 @@
1
+ "use strict";
2
+ /**
3
+ * Live Simulation Adapter
4
+ *
5
+ * Connects ANY running simulator into the NV-SIM governance + trace pipeline.
6
+ * Instead of running our own swarm simulation, we ingest live events from an
7
+ * external simulator process, normalize them, and feed them through governance.
8
+ *
9
+ * Architecture:
10
+ * External Simulator (running process / stream)
11
+ * ↓ stdout / WebSocket / SSE
12
+ * LiveSimulationAdapter (parse + normalize)
13
+ * ↓ LiveSimulationRound
14
+ * Governance Engine (evaluate guards)
15
+ * ↓ verdicts
16
+ * Trace UI (causality visualization)
17
+ *
18
+ * Supported adapter types:
19
+ * - ProcessAdapter: spawn a child process, parse stdout
20
+ * - StreamAdapter: connect to WebSocket/SSE endpoint (future)
21
+ *
22
+ * Every adapter emits the same LiveSimulationRound events, regardless of source.
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.ADAPTER_REGISTRY = void 0;
26
+ exports.createProcessAdapter = createProcessAdapter;
27
+ exports.parseMiroFishLine = parseMiroFishLine;
28
+ exports.parseGenericJsonLine = parseGenericJsonLine;
29
+ exports.createAdapter = createAdapter;
30
+ const child_process_1 = require("child_process");
31
+ const events_1 = require("events");
32
+ function createParserState() {
33
+ return {
34
+ currentRound: 0,
35
+ pendingActions: [],
36
+ pendingEvents: [],
37
+ pendingDynamics: [],
38
+ buffer: {},
39
+ };
40
+ }
41
+ /**
42
+ * Process adapter — spawns a child process and parses its stdout.
43
+ *
44
+ * Usage:
45
+ * const adapter = createProcessAdapter({
46
+ * type: "process",
47
+ * label: "MiroFish (Live)",
48
+ * command: "node",
49
+ * args: ["mirofish.js"],
50
+ * parser: parseMiroFishLine,
51
+ * });
52
+ * adapter.on("round", (round) => { ... });
53
+ * await adapter.start();
54
+ */
55
+ function createProcessAdapter(config) {
56
+ const emitter = new events_1.EventEmitter();
57
+ let proc = null;
58
+ let _status = "idle";
59
+ const state = createParserState();
60
+ function setStatus(s) {
61
+ _status = s;
62
+ emitter.emit("status", s);
63
+ }
64
+ Object.defineProperties(emitter, {
65
+ status: { get: () => _status },
66
+ source: { get: () => config.type },
67
+ label: { get: () => config.label },
68
+ });
69
+ emitter.start = async function start() {
70
+ setStatus("connecting");
71
+ proc = (0, child_process_1.spawn)(config.command, config.args, {
72
+ cwd: config.cwd,
73
+ env: { ...process.env, ...config.env },
74
+ stdio: ["ignore", "pipe", "pipe"],
75
+ });
76
+ let lineBuffer = "";
77
+ proc.stdout?.on("data", (chunk) => {
78
+ lineBuffer += chunk.toString();
79
+ const lines = lineBuffer.split("\n");
80
+ // Keep incomplete last line in buffer
81
+ lineBuffer = lines.pop() ?? "";
82
+ for (const line of lines) {
83
+ const trimmed = line.trim();
84
+ if (!trimmed)
85
+ continue;
86
+ try {
87
+ const round = config.parser(trimmed, state);
88
+ if (round) {
89
+ emitter.emit("round", round);
90
+ }
91
+ }
92
+ catch (err) {
93
+ // Parser error — log but don't crash
94
+ emitter.emit("error", new Error(`Parser error: ${err}`));
95
+ }
96
+ }
97
+ if (_status === "connecting") {
98
+ setStatus("streaming");
99
+ }
100
+ });
101
+ proc.stderr?.on("data", (chunk) => {
102
+ // stderr is informational — don't treat as error
103
+ const msg = chunk.toString().trim();
104
+ if (msg) {
105
+ emitter.emit("log", msg);
106
+ }
107
+ });
108
+ proc.on("close", (code) => {
109
+ // Flush any remaining buffer
110
+ if (lineBuffer.trim()) {
111
+ try {
112
+ const round = config.parser(lineBuffer.trim(), state);
113
+ if (round)
114
+ emitter.emit("round", round);
115
+ }
116
+ catch { /* ignore */ }
117
+ }
118
+ if (code === 0) {
119
+ setStatus("completed");
120
+ emitter.emit("complete");
121
+ }
122
+ else {
123
+ setStatus("error");
124
+ emitter.emit("error", new Error(`Process exited with code ${code}`));
125
+ }
126
+ proc = null;
127
+ });
128
+ proc.on("error", (err) => {
129
+ setStatus("error");
130
+ emitter.emit("error", err);
131
+ proc = null;
132
+ });
133
+ };
134
+ emitter.stop = async function stop() {
135
+ if (proc) {
136
+ proc.kill("SIGTERM");
137
+ // Give it 3s to exit gracefully, then force
138
+ setTimeout(() => {
139
+ if (proc)
140
+ proc.kill("SIGKILL");
141
+ }, 3000);
142
+ }
143
+ setStatus("idle");
144
+ };
145
+ return emitter;
146
+ }
147
+ // ============================================
148
+ // MIROFISH STDOUT PARSER
149
+ // ============================================
150
+ /**
151
+ * Parses MiroFish stdout line-by-line.
152
+ *
153
+ * Expected MiroFish output formats:
154
+ *
155
+ * JSON-per-line (preferred):
156
+ * {"step":1,"agent_actions":[{"agent_id":"a1","action":"buy","sentiment":0.5,"confidence":0.8}]}
157
+ *
158
+ * Key-value (fallback):
159
+ * [step 1] agent=swarm_1 action=panic_sell sentiment=-0.7 confidence=0.6
160
+ * [step 1] event=market_crash severity=major
161
+ * [step 1] emergent=herd_behavior
162
+ * [step 2] ...
163
+ *
164
+ * We emit a LiveSimulationRound when we detect a new step number
165
+ * (meaning the previous step is complete).
166
+ */
167
+ function parseMiroFishLine(line, state) {
168
+ // Try JSON first
169
+ if (line.startsWith("{")) {
170
+ try {
171
+ const data = JSON.parse(line);
172
+ if (data.step != null && Array.isArray(data.agent_actions)) {
173
+ // Parse adaptation data if present
174
+ let adaptation;
175
+ if (data.adaptation) {
176
+ const a = data.adaptation;
177
+ adaptation = {
178
+ count: a.count ?? 0,
179
+ rate: a.rate ?? 0,
180
+ deltas: (a.deltas ?? []).map((d) => ({
181
+ agent: d.agent ?? "unknown",
182
+ intended: d.intended ?? "",
183
+ actual: d.actual ?? "",
184
+ decision: d.decision ?? "BLOCK",
185
+ rule: d.rule,
186
+ reason: d.reason,
187
+ shift: d.shift ?? "unknown",
188
+ })),
189
+ intendedDistribution: a.intended_distribution ?? {},
190
+ actualDistribution: a.actual_distribution ?? {},
191
+ clusterPatterns: (a.cluster_patterns ?? []).map((c) => ({
192
+ type: c.type ?? "unknown",
193
+ strength: c.strength ?? 0,
194
+ agents: c.agents ?? 0,
195
+ })),
196
+ narrative: a.narrative ?? "",
197
+ };
198
+ }
199
+ // Complete step as JSON — emit immediately
200
+ return {
201
+ round: data.step,
202
+ source: "mirofish",
203
+ agentActions: data.agent_actions.map((a) => ({
204
+ agent: a.agent_id ?? a.agent ?? "unknown",
205
+ action: a.action ?? "",
206
+ impact: a.sentiment ?? a.impact ?? 0,
207
+ confidence: a.confidence ?? 0.5,
208
+ ...(a.verdict ? { verdict: { status: a.verdict.status, rule: a.verdict.rule, reason: a.verdict.reason } } : {}),
209
+ })),
210
+ systemEvents: (data.system_events ?? []).map((e) => typeof e === "string"
211
+ ? { id: e, label: e }
212
+ : { id: e.id ?? e.event ?? "unknown", label: e.label ?? e.event ?? e.id ?? "", severity: e.severity }),
213
+ emergentDynamics: data.emergent_patterns ?? data.emergent ?? [],
214
+ adaptation,
215
+ };
216
+ }
217
+ }
218
+ catch { /* not JSON, fall through to key-value parsing */ }
219
+ }
220
+ // Key-value format: [step N] key=value ...
221
+ const stepMatch = line.match(/\[step\s+(\d+)\]/i);
222
+ if (!stepMatch)
223
+ return null;
224
+ const stepNum = parseInt(stepMatch[1], 10);
225
+ let result = null;
226
+ // If new step, flush previous
227
+ if (stepNum > state.currentRound && state.currentRound > 0) {
228
+ result = {
229
+ round: state.currentRound,
230
+ source: "mirofish",
231
+ agentActions: [...state.pendingActions],
232
+ systemEvents: state.pendingEvents.length > 0 ? [...state.pendingEvents] : undefined,
233
+ emergentDynamics: state.pendingDynamics.length > 0 ? [...state.pendingDynamics] : undefined,
234
+ };
235
+ state.pendingActions = [];
236
+ state.pendingEvents = [];
237
+ state.pendingDynamics = [];
238
+ }
239
+ state.currentRound = stepNum;
240
+ const rest = line.slice(stepMatch.index + stepMatch[0].length).trim();
241
+ // Parse agent action
242
+ const agentMatch = rest.match(/agent=(\S+)\s+action=(\S+)(?:\s+sentiment=([-\d.]+))?(?:\s+confidence=([-\d.]+))?/);
243
+ if (agentMatch) {
244
+ state.pendingActions.push({
245
+ agent: agentMatch[1],
246
+ action: agentMatch[2],
247
+ impact: agentMatch[3] ? parseFloat(agentMatch[3]) : 0,
248
+ confidence: agentMatch[4] ? parseFloat(agentMatch[4]) : 0.5,
249
+ });
250
+ return result;
251
+ }
252
+ // Parse system event
253
+ const eventMatch = rest.match(/event=(\S+)(?:\s+severity=(\S+))?/);
254
+ if (eventMatch) {
255
+ state.pendingEvents.push({
256
+ id: eventMatch[1],
257
+ label: eventMatch[1].replace(/_/g, " "),
258
+ severity: eventMatch[2],
259
+ });
260
+ return result;
261
+ }
262
+ // Parse emergent behavior
263
+ const emergentMatch = rest.match(/emergent=(.+)/);
264
+ if (emergentMatch) {
265
+ state.pendingDynamics.push(emergentMatch[1].trim());
266
+ return result;
267
+ }
268
+ return result;
269
+ }
270
+ // ============================================
271
+ // GENERIC JSON PARSER (works for any JSON-per-line simulator)
272
+ // ============================================
273
+ /**
274
+ * Generic JSON-per-line parser.
275
+ * Expects each line to be a JSON object with at minimum:
276
+ * { "step": N, "agents": [{ "id": "...", "action": "...", "impact": 0.5 }] }
277
+ */
278
+ function parseGenericJsonLine(line, _state) {
279
+ if (!line.startsWith("{"))
280
+ return null;
281
+ try {
282
+ const data = JSON.parse(line);
283
+ const step = data.step ?? data.tick ?? data.round ?? data.time;
284
+ if (step == null)
285
+ return null;
286
+ const agents = (data.agents ?? data.agent_actions ?? []).map((a) => ({
287
+ agent: a.id ?? a.agent ?? a.agent_id ?? "unknown",
288
+ action: a.action ?? a.behavior ?? "",
289
+ impact: a.impact ?? a.sentiment ?? a.value ?? 0,
290
+ confidence: a.confidence ?? 0.5,
291
+ }));
292
+ return {
293
+ round: typeof step === "number" ? step : parseInt(step, 10),
294
+ source: "generic",
295
+ agentActions: agents,
296
+ systemEvents: (data.events ?? []).map((e) => typeof e === "string"
297
+ ? { id: e, label: e }
298
+ : { id: e.id ?? "unknown", label: e.label ?? e.id ?? "", severity: e.severity }),
299
+ emergentDynamics: data.dynamics ?? data.emergent ?? [],
300
+ };
301
+ }
302
+ catch {
303
+ return null;
304
+ }
305
+ }
306
+ exports.ADAPTER_REGISTRY = {
307
+ mirofish: {
308
+ id: "mirofish",
309
+ label: "MiroFish (Live)",
310
+ description: "Spawn MiroFish swarm simulation and stream agent behavior",
311
+ createConfig: (opts = {}) => ({
312
+ type: "process",
313
+ label: "MiroFish (Live)",
314
+ command: opts.command ?? "node",
315
+ args: (opts.args ?? "mirofish.js").split(" "),
316
+ cwd: opts.cwd,
317
+ parser: parseMiroFishLine,
318
+ }),
319
+ },
320
+ generic: {
321
+ id: "generic",
322
+ label: "Generic JSON Process",
323
+ description: "Spawn any process that outputs JSON-per-line simulation data",
324
+ createConfig: (opts = {}) => ({
325
+ type: "process",
326
+ label: opts.label ?? "External Simulator",
327
+ command: opts.command ?? "python",
328
+ args: (opts.args ?? "sim.py").split(" "),
329
+ cwd: opts.cwd,
330
+ parser: parseGenericJsonLine,
331
+ }),
332
+ },
333
+ };
334
+ /**
335
+ * Create an adapter from the registry.
336
+ */
337
+ function createAdapter(adapterId, options) {
338
+ const entry = exports.ADAPTER_REGISTRY[adapterId];
339
+ if (!entry)
340
+ return null;
341
+ return createProcessAdapter(entry.createConfig(options));
342
+ }