@neuroverseos/nv-sim 0.1.4 → 0.1.7

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 (44) hide show
  1. package/README.md +260 -6
  2. package/dist/adapters/mirofish.js +461 -0
  3. package/dist/adapters/scienceclaw.js +750 -0
  4. package/dist/assets/index-CHmUN8s0.js +532 -0
  5. package/dist/assets/index-DWgMnB7I.css +1 -0
  6. package/dist/assets/{reportEngine-BfteK4MN.js → reportEngine-BVdQ2_nW.js} +1 -1
  7. package/dist/components/ConstraintsPanel.js +11 -0
  8. package/dist/components/StakeholderBuilder.js +32 -0
  9. package/dist/components/ui/badge.js +24 -0
  10. package/dist/components/ui/button.js +70 -0
  11. package/dist/components/ui/card.js +57 -0
  12. package/dist/components/ui/input.js +44 -0
  13. package/dist/components/ui/label.js +45 -0
  14. package/dist/components/ui/select.js +70 -0
  15. package/dist/engine/aiProvider.js +427 -2
  16. package/dist/engine/auditTrace.js +352 -0
  17. package/dist/engine/behavioralAnalysis.js +605 -0
  18. package/dist/engine/cli.js +1087 -13
  19. package/dist/engine/dynamicsGovernance.js +588 -0
  20. package/dist/engine/fullGovernedLoop.js +367 -0
  21. package/dist/engine/governedSimulation.js +77 -6
  22. package/dist/engine/index.js +41 -1
  23. package/dist/engine/liveVisualizer.js +1961 -197
  24. package/dist/engine/metrics/science.metrics.js +335 -0
  25. package/dist/engine/policyEnforcement.js +1611 -0
  26. package/dist/engine/policyEngine.js +799 -0
  27. package/dist/engine/primeRadiant.js +540 -0
  28. package/dist/engine/scenarioComparison.js +463 -0
  29. package/dist/engine/swarmSimulation.js +54 -1
  30. package/dist/engine/worldComparison.js +164 -0
  31. package/dist/engine/worldStorage.js +232 -0
  32. package/dist/index.html +2 -2
  33. package/dist/lib/reasoningEngine.js +290 -0
  34. package/dist/lib/simulationAdapter.js +686 -0
  35. package/dist/lib/swarmParser.js +291 -0
  36. package/dist/lib/types.js +2 -0
  37. package/dist/lib/utils.js +8 -0
  38. package/dist/runtime/govern.js +473 -0
  39. package/dist/runtime/index.js +75 -0
  40. package/dist/runtime/types.js +11 -0
  41. package/package.json +5 -2
  42. package/dist/assets/index-DHKd4rcV.js +0 -338
  43. package/dist/assets/index-SyyA3z3U.css +0 -1
  44. package/dist/assets/swarmSimulation-DHDqjfMa.js +0 -1
@@ -0,0 +1,463 @@
1
+ "use strict";
2
+ /**
3
+ * Multi-Scenario Comparison Runner — N Scenarios × M Policies
4
+ *
5
+ * The decision intelligence engine. This is MODE 3 of the Prime Radiant.
6
+ *
7
+ * Instead of:
8
+ * "Here's what happened"
9
+ * We produce:
10
+ * "Here's what you should do"
11
+ *
12
+ * Usage:
13
+ * const result = await runScenarioMatrix({
14
+ * scenario: "University disciplinary crisis",
15
+ * stakeholders: [...],
16
+ * paths: [...],
17
+ * policyOptions: [
18
+ * { id: "revoke", label: "Revoke Decision", policyText: "..." },
19
+ * { id: "partial", label: "Partial Apology", policyText: "..." },
20
+ * { id: "maintain", label: "Maintain Decision", policyText: "..." },
21
+ * ],
22
+ * evaluationMetrics: [
23
+ * { id: "trust", label: "Public Trust", weight: 0.3 },
24
+ * { id: "polarization", label: "Polarization Index", weight: 0.25, lowerIsBetter: true },
25
+ * ...
26
+ * ],
27
+ * })
28
+ */
29
+ Object.defineProperty(exports, "__esModule", { value: true });
30
+ exports.DEFAULT_METRICS = void 0;
31
+ exports.runScenarioMatrix = runScenarioMatrix;
32
+ const fullGovernedLoop_1 = require("./fullGovernedLoop");
33
+ // ============================================
34
+ // DEFAULT METRICS
35
+ // ============================================
36
+ exports.DEFAULT_METRICS = [
37
+ {
38
+ id: "trust",
39
+ label: "Public Trust",
40
+ weight: 0.25,
41
+ extractor: (r) => r.finalState.trust,
42
+ },
43
+ {
44
+ id: "polarization",
45
+ label: "Polarization Index",
46
+ weight: 0.20,
47
+ lowerIsBetter: true,
48
+ extractor: (r) => r.finalState.polarization,
49
+ },
50
+ {
51
+ id: "outrage",
52
+ label: "Outrage Level",
53
+ weight: 0.15,
54
+ lowerIsBetter: true,
55
+ extractor: (r) => r.finalState.outrage,
56
+ },
57
+ {
58
+ id: "cascade_risk",
59
+ label: "Cascade Risk",
60
+ weight: 0.15,
61
+ lowerIsBetter: true,
62
+ extractor: (r) => r.finalState.cascadeRisk,
63
+ },
64
+ {
65
+ id: "system_health",
66
+ label: "System Health",
67
+ weight: 0.15,
68
+ extractor: (r) => r.systemHealthScore / 100,
69
+ },
70
+ {
71
+ id: "governance_effectiveness",
72
+ label: "Governance Effectiveness",
73
+ weight: 0.10,
74
+ extractor: (r) => {
75
+ const blocked = r.actionGovernance.blocked;
76
+ const total = r.actionGovernance.totalEvaluations;
77
+ const blockRate = total > 0 ? blocked / total : 0;
78
+ // Sweet spot: some blocking is good, too much is bad
79
+ if (blockRate < 0.1)
80
+ return 0.3; // too lenient
81
+ if (blockRate > 0.7)
82
+ return 0.2; // over-constrained
83
+ return 0.7 + (1 - Math.abs(blockRate - 0.3) * 3) * 0.3;
84
+ },
85
+ },
86
+ ];
87
+ // ============================================
88
+ // METRIC EXTRACTION
89
+ // ============================================
90
+ function extractMetric(metric, result) {
91
+ if (metric.extractor) {
92
+ return metric.extractor(result);
93
+ }
94
+ // Default extractors based on metric ID
95
+ switch (metric.id) {
96
+ case "trust": return result.finalState.trust;
97
+ case "polarization": return result.finalState.polarization;
98
+ case "outrage": return result.finalState.outrage;
99
+ case "cascade_risk": return result.finalState.cascadeRisk;
100
+ case "system_health": return result.systemHealthScore / 100;
101
+ case "amplification": return result.finalState.amplification;
102
+ default: return 0.5;
103
+ }
104
+ }
105
+ function normalizeScore(value, lowerIsBetter) {
106
+ if (lowerIsBetter) {
107
+ return Math.max(0, Math.min(1, 1 - value));
108
+ }
109
+ return Math.max(0, Math.min(1, value));
110
+ }
111
+ function rateScore(normalized) {
112
+ if (normalized >= 0.8)
113
+ return "excellent";
114
+ if (normalized >= 0.6)
115
+ return "good";
116
+ if (normalized >= 0.4)
117
+ return "moderate";
118
+ if (normalized >= 0.2)
119
+ return "poor";
120
+ return "critical";
121
+ }
122
+ function formatMetricValue(value, metricId) {
123
+ if (metricId === "system_health")
124
+ return `${(value * 100).toFixed(0)}/100`;
125
+ if (metricId.includes("risk") || metricId.includes("probability"))
126
+ return `${(value * 100).toFixed(0)}%`;
127
+ return value.toFixed(2);
128
+ }
129
+ // ============================================
130
+ // MAIN: runScenarioMatrix()
131
+ // ============================================
132
+ async function runScenarioMatrix(config) {
133
+ const metrics = config.evaluationMetrics.length > 0
134
+ ? config.evaluationMetrics
135
+ : exports.DEFAULT_METRICS;
136
+ const optionResults = [];
137
+ // --- Run simulation for each policy option ---
138
+ for (const option of config.policyOptions) {
139
+ config.onProgress?.(option.id, "simulating");
140
+ const worldDef = option.worldDef ?? config.defaultWorldDef;
141
+ const simulation = await (0, fullGovernedLoop_1.runFullGovernedSimulation)({
142
+ scenario: config.scenario,
143
+ stakeholders: config.stakeholders,
144
+ paths: config.paths,
145
+ swarmConfig: config.swarmConfig,
146
+ policyText: option.policyText,
147
+ worldDef,
148
+ narrativeEvents: config.narrativeEvents,
149
+ agentTypes: config.agentTypes,
150
+ });
151
+ config.onProgress?.(option.id, "evaluating");
152
+ // Score each metric
153
+ const metricScores = metrics.map((metric) => {
154
+ const value = extractMetric(metric, simulation);
155
+ const normalized = normalizeScore(value, metric.lowerIsBetter ?? false);
156
+ const weighted = normalized * metric.weight;
157
+ return {
158
+ metricId: metric.id,
159
+ metricLabel: metric.label,
160
+ value: Number(value.toFixed(3)),
161
+ normalizedScore: Number(normalized.toFixed(3)),
162
+ weightedScore: Number(weighted.toFixed(3)),
163
+ display: formatMetricValue(value, metric.id),
164
+ rating: rateScore(normalized),
165
+ };
166
+ });
167
+ const overallScore = metricScores.reduce((s, m) => s + m.weightedScore, 0);
168
+ // Extract outcomes
169
+ const outcomes = {
170
+ finalTrust: Number(simulation.finalState.trust.toFixed(3)),
171
+ finalPolarization: Number(simulation.finalState.polarization.toFixed(3)),
172
+ finalOutrage: Number(simulation.finalState.outrage.toFixed(3)),
173
+ finalCascadeRisk: Number(simulation.finalState.cascadeRisk.toFixed(3)),
174
+ peakOutrage: simulation.dynamicsGovernance.peakOutrage,
175
+ totalInterventions: simulation.dynamicsGovernance.totalInterventions,
176
+ actionsBlocked: simulation.actionGovernance.blocked,
177
+ systemHealth: simulation.systemHealthScore,
178
+ trajectory: simulation.trajectory,
179
+ cascadeBreakerFired: simulation.dynamicsGovernance.cascadeBreakers > 0,
180
+ coolingActivated: simulation.dynamicsGovernance.coolingPeriods > 0,
181
+ };
182
+ optionResults.push({
183
+ optionId: option.id,
184
+ optionLabel: option.label,
185
+ simulation,
186
+ metricScores,
187
+ overallScore: Number(overallScore.toFixed(3)),
188
+ outcomes,
189
+ });
190
+ }
191
+ // --- Build comparison matrix ---
192
+ const sortedResults = [...optionResults].sort((a, b) => b.overallScore - a.overallScore);
193
+ const comparison = buildComparisonMatrix(optionResults, metrics);
194
+ const rankings = buildRankings(sortedResults);
195
+ const riskAssessment = buildRiskAssessment(optionResults);
196
+ const recommendation = buildRecommendation(sortedResults, riskAssessment, config.policyOptions);
197
+ const decisionBrief = buildDecisionBrief(sortedResults, recommendation, riskAssessment);
198
+ return {
199
+ scenario: config.scenario,
200
+ optionResults,
201
+ comparison,
202
+ recommendation,
203
+ rankings,
204
+ riskAssessment,
205
+ decisionBrief,
206
+ };
207
+ }
208
+ // ============================================
209
+ // COMPARISON MATRIX BUILDER
210
+ // ============================================
211
+ function buildComparisonMatrix(results, metrics) {
212
+ const metricIds = metrics.map((m) => m.id);
213
+ // Find best/worst per metric
214
+ const bestPerMetric = {};
215
+ const worstPerMetric = {};
216
+ for (const metric of metrics) {
217
+ const values = results.map((r) => {
218
+ const score = r.metricScores.find((s) => s.metricId === metric.id);
219
+ return score?.normalizedScore ?? 0;
220
+ });
221
+ bestPerMetric[metric.id] = Math.max(...values);
222
+ worstPerMetric[metric.id] = Math.min(...values);
223
+ }
224
+ const sorted = [...results].sort((a, b) => b.overallScore - a.overallScore);
225
+ const rows = sorted.map((result, idx) => ({
226
+ optionId: result.optionId,
227
+ optionLabel: result.optionLabel,
228
+ values: metrics.map((metric) => {
229
+ const score = result.metricScores.find((s) => s.metricId === metric.id);
230
+ return {
231
+ metricId: metric.id,
232
+ value: score.value,
233
+ display: score.display,
234
+ isBest: score.normalizedScore === bestPerMetric[metric.id],
235
+ isWorst: score.normalizedScore === worstPerMetric[metric.id],
236
+ };
237
+ }),
238
+ overallScore: result.overallScore,
239
+ rank: idx + 1,
240
+ }));
241
+ return {
242
+ metrics: metricIds,
243
+ rows,
244
+ };
245
+ }
246
+ // ============================================
247
+ // RANKINGS
248
+ // ============================================
249
+ function buildRankings(sorted) {
250
+ return sorted.map((result, idx) => {
251
+ let verdict;
252
+ if (idx === 0)
253
+ verdict = "BEST";
254
+ else if (result.overallScore > 0.5)
255
+ verdict = "VIABLE";
256
+ else if (result.overallScore > 0.3)
257
+ verdict = "RISKY";
258
+ else
259
+ verdict = "AVOID";
260
+ const oneLiner = buildOneLiner(result, idx);
261
+ return {
262
+ rank: idx + 1,
263
+ optionId: result.optionId,
264
+ optionLabel: result.optionLabel,
265
+ overallScore: result.overallScore,
266
+ verdict,
267
+ oneLiner,
268
+ };
269
+ });
270
+ }
271
+ function buildOneLiner(result, rank) {
272
+ const outcomes = result.outcomes;
273
+ if (outcomes.cascadeBreakerFired) {
274
+ return "Required emergency circuit breaker — high risk scenario.";
275
+ }
276
+ if (outcomes.finalTrust > 0.6 && outcomes.finalPolarization < 0.3) {
277
+ return "Maintains trust and minimizes polarization.";
278
+ }
279
+ if (outcomes.finalOutrage > 0.6) {
280
+ return "Outrage remains elevated — risk of sustained negative dynamics.";
281
+ }
282
+ if (outcomes.finalCascadeRisk > 0.5) {
283
+ return "Cascade risk remains dangerous — structural instability.";
284
+ }
285
+ if (outcomes.systemHealth > 70) {
286
+ return "System stabilizes with good overall health.";
287
+ }
288
+ if (outcomes.totalInterventions > 10) {
289
+ return "Requires heavy governance intervention to maintain stability.";
290
+ }
291
+ return `Overall score: ${result.overallScore.toFixed(2)} — ${outcomes.trajectory} trajectory.`;
292
+ }
293
+ // ============================================
294
+ // RISK ASSESSMENT
295
+ // ============================================
296
+ function buildRiskAssessment(results) {
297
+ return results.map((result) => {
298
+ const risks = [];
299
+ const outcomes = result.outcomes;
300
+ if (outcomes.finalCascadeRisk > 0.5) {
301
+ risks.push({
302
+ risk: "Cascading failure — actions compound into system-wide instability",
303
+ probability: outcomes.finalCascadeRisk,
304
+ severity: outcomes.finalCascadeRisk > 0.7 ? "critical" : "high",
305
+ });
306
+ }
307
+ if (outcomes.finalPolarization > 0.5) {
308
+ risks.push({
309
+ risk: "Entrenched polarization — stakeholder base splits into opposing camps",
310
+ probability: outcomes.finalPolarization,
311
+ severity: outcomes.finalPolarization > 0.7 ? "high" : "moderate",
312
+ });
313
+ }
314
+ if (outcomes.finalTrust < 0.3) {
315
+ risks.push({
316
+ risk: "Institutional trust collapse — credibility cannot be recovered",
317
+ probability: 1 - outcomes.finalTrust,
318
+ severity: outcomes.finalTrust < 0.15 ? "critical" : "high",
319
+ });
320
+ }
321
+ if (outcomes.peakOutrage > 0.7) {
322
+ risks.push({
323
+ risk: "Outrage spike — may trigger media cascade or coordinated backlash",
324
+ probability: outcomes.peakOutrage,
325
+ severity: outcomes.peakOutrage > 0.85 ? "critical" : "high",
326
+ });
327
+ }
328
+ if (outcomes.actionsBlocked > outcomes.systemHealth * 0.3) {
329
+ risks.push({
330
+ risk: "Over-governance — too many blocked actions may freeze legitimate response",
331
+ probability: 0.5,
332
+ severity: "moderate",
333
+ });
334
+ }
335
+ let overallRisk;
336
+ const criticalRisks = risks.filter((r) => r.severity === "critical").length;
337
+ const highRisks = risks.filter((r) => r.severity === "high").length;
338
+ if (criticalRisks > 0)
339
+ overallRisk = "CRITICAL";
340
+ else if (highRisks > 1)
341
+ overallRisk = "HIGH";
342
+ else if (highRisks > 0 || risks.length > 2)
343
+ overallRisk = "MEDIUM";
344
+ else
345
+ overallRisk = "LOW";
346
+ return {
347
+ optionId: result.optionId,
348
+ optionLabel: result.optionLabel,
349
+ overallRisk,
350
+ risks,
351
+ };
352
+ });
353
+ }
354
+ // ============================================
355
+ // RECOMMENDATION ENGINE
356
+ // ============================================
357
+ function buildRecommendation(sorted, risks, options) {
358
+ const best = sorted[0];
359
+ const bestRisk = risks.find((r) => r.optionId === best.optionId);
360
+ const secondBest = sorted.length > 1 ? sorted[1] : null;
361
+ // Confidence: based on score gap and risk level
362
+ let confidence = 0.5;
363
+ if (secondBest) {
364
+ const gap = best.overallScore - secondBest.overallScore;
365
+ confidence += gap * 2; // bigger gap = more confidence
366
+ }
367
+ if (bestRisk?.overallRisk === "LOW")
368
+ confidence += 0.15;
369
+ else if (bestRisk?.overallRisk === "CRITICAL")
370
+ confidence -= 0.2;
371
+ confidence = Math.max(0.2, Math.min(0.95, confidence));
372
+ // Rationale
373
+ const parts = [];
374
+ parts.push(`"${best.optionLabel}" scores highest across the evaluation metrics (${best.overallScore.toFixed(2)}).`);
375
+ if (best.outcomes.finalTrust > 0.5) {
376
+ parts.push(`Preserves institutional trust at ${(best.outcomes.finalTrust * 100).toFixed(0)}%.`);
377
+ }
378
+ if (best.outcomes.finalPolarization < 0.3) {
379
+ parts.push(`Keeps polarization contained at ${(best.outcomes.finalPolarization * 100).toFixed(0)}%.`);
380
+ }
381
+ if (secondBest) {
382
+ parts.push(`Outperforms "${secondBest.optionLabel}" by ${((best.overallScore - secondBest.overallScore) * 100).toFixed(0)} points.`);
383
+ }
384
+ // Caveats
385
+ const caveats = [];
386
+ if (bestRisk?.overallRisk === "HIGH" || bestRisk?.overallRisk === "CRITICAL") {
387
+ caveats.push(`This option still carries ${bestRisk.overallRisk} risk — ${bestRisk.risks[0]?.risk}`);
388
+ }
389
+ if (best.outcomes.coolingActivated) {
390
+ caveats.push("Required a cooling period — expect delayed response dynamics.");
391
+ }
392
+ if (best.outcomes.cascadeBreakerFired) {
393
+ caveats.push("Circuit breaker was triggered — the system reached critical thresholds before stabilizing.");
394
+ }
395
+ if (confidence < 0.5) {
396
+ caveats.push("Score differences between options are small — the choice is not clear-cut.");
397
+ }
398
+ // Next steps
399
+ const nextSteps = [
400
+ `Implement the "${best.optionLabel}" policy as the primary approach.`,
401
+ ];
402
+ if (best.outcomes.totalInterventions > 5) {
403
+ nextSteps.push("Prepare governance mechanisms for active intervention — this option requires ongoing management.");
404
+ }
405
+ if (secondBest && secondBest.overallScore > best.overallScore - 0.1) {
406
+ nextSteps.push(`Keep "${secondBest.optionLabel}" as a fallback — score difference is marginal.`);
407
+ }
408
+ nextSteps.push("Monitor the system state metrics (trust, polarization, outrage) in real-time.");
409
+ return {
410
+ optionId: best.optionId,
411
+ optionLabel: best.optionLabel,
412
+ confidence: Number(confidence.toFixed(2)),
413
+ rationale: parts.join(" "),
414
+ caveats,
415
+ nextSteps,
416
+ };
417
+ }
418
+ // ============================================
419
+ // DECISION BRIEF
420
+ // ============================================
421
+ function buildDecisionBrief(sorted, recommendation, risks) {
422
+ const lines = [];
423
+ lines.push("DECISION INTELLIGENCE BRIEF");
424
+ lines.push("=".repeat(50));
425
+ lines.push("");
426
+ // Rankings table
427
+ lines.push("SCENARIO COMPARISON:");
428
+ lines.push("-".repeat(50));
429
+ const headers = ["Option", "Score", "Trust", "Polar.", "Risk", "Verdict"];
430
+ lines.push(` ${headers.map((h) => h.padEnd(12)).join("")}`);
431
+ lines.push(` ${"-".repeat(72)}`);
432
+ for (const result of sorted) {
433
+ const risk = risks.find((r) => r.optionId === result.optionId);
434
+ const rank = sorted.indexOf(result);
435
+ const verdict = rank === 0 ? "BEST" : result.overallScore > 0.5 ? "VIABLE" : result.overallScore > 0.3 ? "RISKY" : "AVOID";
436
+ const prefix = rank === 0 ? ">>> " : " ";
437
+ const row = [
438
+ result.optionLabel.slice(0, 11),
439
+ result.overallScore.toFixed(2),
440
+ `${(result.outcomes.finalTrust * 100).toFixed(0)}%`,
441
+ `${(result.outcomes.finalPolarization * 100).toFixed(0)}%`,
442
+ risk?.overallRisk ?? "?",
443
+ verdict,
444
+ ];
445
+ lines.push(`${prefix}${row.map((v) => v.padEnd(12)).join("")}`);
446
+ }
447
+ lines.push("");
448
+ lines.push("RECOMMENDATION:");
449
+ lines.push(` ${recommendation.rationale}`);
450
+ if (recommendation.caveats.length > 0) {
451
+ lines.push("");
452
+ lines.push("CAVEATS:");
453
+ for (const caveat of recommendation.caveats) {
454
+ lines.push(` - ${caveat}`);
455
+ }
456
+ }
457
+ lines.push("");
458
+ lines.push("NEXT STEPS:");
459
+ for (const step of recommendation.nextSteps) {
460
+ lines.push(` ${sorted.indexOf(sorted[0]) + 1}. ${step}`);
461
+ }
462
+ return lines.join("\n");
463
+ }
@@ -19,6 +19,7 @@
19
19
  */
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
21
  exports.runSwarmSimulation = runSwarmSimulation;
22
+ const aiProvider_1 = require("./aiProvider");
22
23
  // ============================================
23
24
  // REACTION MODELS
24
25
  // ============================================
@@ -205,10 +206,16 @@ function findInflectionPoints(rounds) {
205
206
  * This is Echelon's native reaction model — NOT MiroFish.
206
207
  * It simulates how stakeholders react to different reasoning paths
207
208
  * over multiple rounds, detecting emergent dynamics.
209
+ *
210
+ * When ANTHROPIC_API_KEY is set, agents use Claude for real reasoning.
211
+ * Otherwise, falls back to the deterministic Math.random() model.
208
212
  */
209
213
  async function runSwarmSimulation(scenario, stakeholders, paths, config) {
210
214
  const roundCount = config.rounds ?? 3;
211
215
  const model = config.reaction_model ?? "mixed";
216
+ // Detect if AI reasoning is available and requested
217
+ const defaultProvider = (0, aiProvider_1.getDefaultProviderName)();
218
+ const useAI = config.ai_reasoning !== false && defaultProvider !== "deterministic";
212
219
  // Filter stakeholders if specific ones requested
213
220
  const activeStakeholders = config.simulate_stakeholders
214
221
  ? stakeholders.filter((s) => config.simulate_stakeholders.includes(s.id))
@@ -224,8 +231,54 @@ async function runSwarmSimulation(scenario, stakeholders, paths, config) {
224
231
  }
225
232
  // Run simulation rounds
226
233
  const rounds = [];
234
+ let aiProvider;
235
+ if (useAI) {
236
+ try {
237
+ aiProvider = (0, aiProvider_1.getAIProvider)(defaultProvider);
238
+ }
239
+ catch {
240
+ // No API key or provider unavailable — fall back silently
241
+ }
242
+ }
227
243
  for (let round = 0; round < roundCount; round++) {
228
- const reactions = activeStakeholders.map((stakeholder) => modelReaction(stakeholder, primaryPath, model, round));
244
+ let reactions;
245
+ if (aiProvider) {
246
+ // AI-POWERED REASONING: Each agent gets a Claude call
247
+ const previousReactions = round > 0
248
+ ? rounds[round - 1].reactions.map(r => `${r.stakeholder_id}: ${r.reaction}`)
249
+ : [];
250
+ reactions = await Promise.all(activeStakeholders.map(async (stakeholder) => {
251
+ try {
252
+ const aiResult = await (0, aiProvider_1.generateAIReaction)({
253
+ stakeholderId: stakeholder.id,
254
+ stakeholderDescription: stakeholder.description,
255
+ stakeholderDisposition: stakeholder.disposition,
256
+ stakeholderPriorities: stakeholder.priorities,
257
+ scenario,
258
+ pathDescription: primaryPath.description,
259
+ pathRisk: primaryPath.risk,
260
+ round,
261
+ previousReactions,
262
+ provider: aiProvider,
263
+ });
264
+ return {
265
+ stakeholder_id: stakeholder.id,
266
+ reaction: aiResult.reaction,
267
+ confidence: aiResult.confidence,
268
+ impact: aiResult.impact,
269
+ trigger: aiResult.trigger,
270
+ };
271
+ }
272
+ catch {
273
+ // Individual agent fallback to deterministic
274
+ return modelReaction(stakeholder, primaryPath, model, round);
275
+ }
276
+ }));
277
+ }
278
+ else {
279
+ // DETERMINISTIC: Original Math.random() model
280
+ reactions = activeStakeholders.map((stakeholder) => modelReaction(stakeholder, primaryPath, model, round));
281
+ }
229
282
  const emergentDynamics = detectEmergentDynamics(reactions);
230
283
  rounds.push({
231
284
  round,