paracosm 0.0.1 → 0.2.0
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.
- package/LICENSE +190 -0
- package/README.md +224 -0
- package/dist/cli/sim-config.d.ts +100 -0
- package/dist/cli/sim-config.d.ts.map +1 -0
- package/dist/cli/sim-config.js +124 -0
- package/dist/cli/sim-config.js.map +1 -0
- package/dist/cli/types.d.ts +73 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/cli/types.js +2 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/engine/_template/index.d.ts +9 -0
- package/dist/engine/_template/index.d.ts.map +1 -0
- package/dist/engine/_template/index.js +91 -0
- package/dist/engine/_template/index.js.map +1 -0
- package/dist/engine/compiler/cache.d.ts +12 -0
- package/dist/engine/compiler/cache.d.ts.map +1 -0
- package/dist/engine/compiler/cache.js +75 -0
- package/dist/engine/compiler/cache.js.map +1 -0
- package/dist/engine/compiler/generate-director.d.ts +9 -0
- package/dist/engine/compiler/generate-director.d.ts.map +1 -0
- package/dist/engine/compiler/generate-director.js +57 -0
- package/dist/engine/compiler/generate-director.js.map +1 -0
- package/dist/engine/compiler/generate-fingerprint.d.ts +12 -0
- package/dist/engine/compiler/generate-fingerprint.d.ts.map +1 -0
- package/dist/engine/compiler/generate-fingerprint.js +86 -0
- package/dist/engine/compiler/generate-fingerprint.js.map +1 -0
- package/dist/engine/compiler/generate-milestones.d.ts +11 -0
- package/dist/engine/compiler/generate-milestones.d.ts.map +1 -0
- package/dist/engine/compiler/generate-milestones.js +121 -0
- package/dist/engine/compiler/generate-milestones.js.map +1 -0
- package/dist/engine/compiler/generate-politics.d.ts +11 -0
- package/dist/engine/compiler/generate-politics.d.ts.map +1 -0
- package/dist/engine/compiler/generate-politics.js +85 -0
- package/dist/engine/compiler/generate-politics.js.map +1 -0
- package/dist/engine/compiler/generate-progression.d.ts +15 -0
- package/dist/engine/compiler/generate-progression.d.ts.map +1 -0
- package/dist/engine/compiler/generate-progression.js +104 -0
- package/dist/engine/compiler/generate-progression.js.map +1 -0
- package/dist/engine/compiler/generate-prompts.d.ts +11 -0
- package/dist/engine/compiler/generate-prompts.d.ts.map +1 -0
- package/dist/engine/compiler/generate-prompts.js +90 -0
- package/dist/engine/compiler/generate-prompts.js.map +1 -0
- package/dist/engine/compiler/generate-reactions.d.ts +12 -0
- package/dist/engine/compiler/generate-reactions.d.ts.map +1 -0
- package/dist/engine/compiler/generate-reactions.js +90 -0
- package/dist/engine/compiler/generate-reactions.js.map +1 -0
- package/dist/engine/compiler/index.d.ts +34 -0
- package/dist/engine/compiler/index.d.ts.map +1 -0
- package/dist/engine/compiler/index.js +270 -0
- package/dist/engine/compiler/index.js.map +1 -0
- package/dist/engine/compiler/seed-ingestion.d.ts +40 -0
- package/dist/engine/compiler/seed-ingestion.d.ts.map +1 -0
- package/dist/engine/compiler/seed-ingestion.js +232 -0
- package/dist/engine/compiler/seed-ingestion.js.map +1 -0
- package/dist/engine/compiler/types.d.ts +30 -0
- package/dist/engine/compiler/types.d.ts.map +1 -0
- package/dist/engine/compiler/types.js +5 -0
- package/dist/engine/compiler/types.js.map +1 -0
- package/dist/engine/compiler/validate.d.ts +37 -0
- package/dist/engine/compiler/validate.d.ts.map +1 -0
- package/dist/engine/compiler/validate.js +141 -0
- package/dist/engine/compiler/validate.js.map +1 -0
- package/dist/engine/core/agent-generator.d.ts +11 -0
- package/dist/engine/core/agent-generator.d.ts.map +1 -0
- package/dist/engine/core/agent-generator.js +85 -0
- package/dist/engine/core/agent-generator.js.map +1 -0
- package/dist/engine/core/kernel.d.ts +69 -0
- package/dist/engine/core/kernel.d.ts.map +1 -0
- package/dist/engine/core/kernel.js +223 -0
- package/dist/engine/core/kernel.js.map +1 -0
- package/dist/engine/core/progression.d.ts +38 -0
- package/dist/engine/core/progression.d.ts.map +1 -0
- package/dist/engine/core/progression.js +292 -0
- package/dist/engine/core/progression.js.map +1 -0
- package/dist/engine/core/rng.d.ts +19 -0
- package/dist/engine/core/rng.d.ts.map +1 -0
- package/dist/engine/core/rng.js +34 -0
- package/dist/engine/core/rng.js.map +1 -0
- package/dist/engine/core/state.d.ts +144 -0
- package/dist/engine/core/state.d.ts.map +1 -0
- package/dist/engine/core/state.js +5 -0
- package/dist/engine/core/state.js.map +1 -0
- package/dist/engine/effect-registry.d.ts +14 -0
- package/dist/engine/effect-registry.d.ts.map +1 -0
- package/dist/engine/effect-registry.js +28 -0
- package/dist/engine/effect-registry.js.map +1 -0
- package/dist/engine/event-taxonomy.d.ts +9 -0
- package/dist/engine/event-taxonomy.d.ts.map +1 -0
- package/dist/engine/event-taxonomy.js +13 -0
- package/dist/engine/event-taxonomy.js.map +1 -0
- package/dist/engine/index.d.ts +25 -0
- package/dist/engine/index.d.ts.map +1 -0
- package/dist/engine/index.js +19 -0
- package/dist/engine/index.js.map +1 -0
- package/dist/engine/lunar/effects.d.ts +7 -0
- package/dist/engine/lunar/effects.d.ts.map +1 -0
- package/dist/engine/lunar/effects.js +16 -0
- package/dist/engine/lunar/effects.js.map +1 -0
- package/dist/engine/lunar/events.d.ts +3 -0
- package/dist/engine/lunar/events.d.ts.map +1 -0
- package/dist/engine/lunar/events.js +11 -0
- package/dist/engine/lunar/events.js.map +1 -0
- package/dist/engine/lunar/fingerprint.d.ts +5 -0
- package/dist/engine/lunar/fingerprint.d.ts.map +1 -0
- package/dist/engine/lunar/fingerprint.js +17 -0
- package/dist/engine/lunar/fingerprint.js.map +1 -0
- package/dist/engine/lunar/hooks.d.ts +10 -0
- package/dist/engine/lunar/hooks.d.ts.map +1 -0
- package/dist/engine/lunar/hooks.js +10 -0
- package/dist/engine/lunar/hooks.js.map +1 -0
- package/dist/engine/lunar/index.d.ts +8 -0
- package/dist/engine/lunar/index.d.ts.map +1 -0
- package/dist/engine/lunar/index.js +38 -0
- package/dist/engine/lunar/index.js.map +1 -0
- package/dist/engine/lunar/metrics.d.ts +4 -0
- package/dist/engine/lunar/metrics.d.ts.map +1 -0
- package/dist/engine/lunar/metrics.js +14 -0
- package/dist/engine/lunar/metrics.js.map +1 -0
- package/dist/engine/lunar/milestones.d.ts +3 -0
- package/dist/engine/lunar/milestones.d.ts.map +1 -0
- package/dist/engine/lunar/milestones.js +53 -0
- package/dist/engine/lunar/milestones.js.map +1 -0
- package/dist/engine/lunar/names.d.ts +4 -0
- package/dist/engine/lunar/names.d.ts.map +1 -0
- package/dist/engine/lunar/names.js +23 -0
- package/dist/engine/lunar/names.js.map +1 -0
- package/dist/engine/lunar/politics.d.ts +6 -0
- package/dist/engine/lunar/politics.d.ts.map +1 -0
- package/dist/engine/lunar/politics.js +13 -0
- package/dist/engine/lunar/politics.js.map +1 -0
- package/dist/engine/lunar/presets.d.ts +25 -0
- package/dist/engine/lunar/presets.d.ts.map +1 -0
- package/dist/engine/lunar/presets.js +24 -0
- package/dist/engine/lunar/presets.js.map +1 -0
- package/dist/engine/lunar/progression-hooks.d.ts +7 -0
- package/dist/engine/lunar/progression-hooks.d.ts.map +1 -0
- package/dist/engine/lunar/progression-hooks.js +20 -0
- package/dist/engine/lunar/progression-hooks.js.map +1 -0
- package/dist/engine/lunar/prompts.d.ts +10 -0
- package/dist/engine/lunar/prompts.d.ts.map +1 -0
- package/dist/engine/lunar/prompts.js +64 -0
- package/dist/engine/lunar/prompts.js.map +1 -0
- package/dist/engine/lunar/reactions.d.ts +5 -0
- package/dist/engine/lunar/reactions.d.ts.map +1 -0
- package/dist/engine/lunar/reactions.js +15 -0
- package/dist/engine/lunar/reactions.js.map +1 -0
- package/dist/engine/lunar/research-bundle.d.ts +8 -0
- package/dist/engine/lunar/research-bundle.d.ts.map +1 -0
- package/dist/engine/lunar/research-bundle.js +157 -0
- package/dist/engine/lunar/research-bundle.js.map +1 -0
- package/dist/engine/lunar/scenario.json +117 -0
- package/dist/engine/mars/effects.d.ts +18 -0
- package/dist/engine/mars/effects.d.ts.map +1 -0
- package/dist/engine/mars/effects.js +21 -0
- package/dist/engine/mars/effects.js.map +1 -0
- package/dist/engine/mars/events.d.ts +9 -0
- package/dist/engine/mars/events.d.ts.map +1 -0
- package/dist/engine/mars/events.js +12 -0
- package/dist/engine/mars/events.js.map +1 -0
- package/dist/engine/mars/fingerprint.d.ts +6 -0
- package/dist/engine/mars/fingerprint.d.ts.map +1 -0
- package/dist/engine/mars/fingerprint.js +21 -0
- package/dist/engine/mars/fingerprint.js.map +1 -0
- package/dist/engine/mars/hooks.d.ts +11 -0
- package/dist/engine/mars/hooks.d.ts.map +1 -0
- package/dist/engine/mars/hooks.js +11 -0
- package/dist/engine/mars/hooks.js.map +1 -0
- package/dist/engine/mars/index.d.ts +8 -0
- package/dist/engine/mars/index.d.ts.map +1 -0
- package/dist/engine/mars/index.js +44 -0
- package/dist/engine/mars/index.js.map +1 -0
- package/dist/engine/mars/metrics.d.ts +21 -0
- package/dist/engine/mars/metrics.d.ts.map +1 -0
- package/dist/engine/mars/metrics.js +25 -0
- package/dist/engine/mars/metrics.js.map +1 -0
- package/dist/engine/mars/milestones.d.ts +15 -0
- package/dist/engine/mars/milestones.d.ts.map +1 -0
- package/dist/engine/mars/milestones.js +72 -0
- package/dist/engine/mars/milestones.js.map +1 -0
- package/dist/engine/mars/names.d.ts +12 -0
- package/dist/engine/mars/names.d.ts.map +1 -0
- package/dist/engine/mars/names.js +45 -0
- package/dist/engine/mars/names.js.map +1 -0
- package/dist/engine/mars/politics.d.ts +7 -0
- package/dist/engine/mars/politics.d.ts.map +1 -0
- package/dist/engine/mars/politics.js +14 -0
- package/dist/engine/mars/politics.js.map +1 -0
- package/dist/engine/mars/presets.d.ts +29 -0
- package/dist/engine/mars/presets.d.ts.map +1 -0
- package/dist/engine/mars/presets.js +28 -0
- package/dist/engine/mars/presets.js.map +1 -0
- package/dist/engine/mars/progression-hooks.d.ts +8 -0
- package/dist/engine/mars/progression-hooks.d.ts.map +1 -0
- package/dist/engine/mars/progression-hooks.js +21 -0
- package/dist/engine/mars/progression-hooks.js.map +1 -0
- package/dist/engine/mars/prompts.d.ts +12 -0
- package/dist/engine/mars/prompts.d.ts.map +1 -0
- package/dist/engine/mars/prompts.js +75 -0
- package/dist/engine/mars/prompts.js.map +1 -0
- package/dist/engine/mars/reactions.d.ts +7 -0
- package/dist/engine/mars/reactions.d.ts.map +1 -0
- package/dist/engine/mars/reactions.js +24 -0
- package/dist/engine/mars/reactions.js.map +1 -0
- package/dist/engine/mars/research-bundle.d.ts +7 -0
- package/dist/engine/mars/research-bundle.d.ts.map +1 -0
- package/dist/engine/mars/research-bundle.js +174 -0
- package/dist/engine/mars/research-bundle.js.map +1 -0
- package/dist/engine/mars/scenario.json +118 -0
- package/dist/engine/metric-registry.d.ts +12 -0
- package/dist/engine/metric-registry.d.ts.map +1 -0
- package/dist/engine/metric-registry.js +26 -0
- package/dist/engine/metric-registry.js.map +1 -0
- package/dist/engine/types.d.ts +349 -0
- package/dist/engine/types.d.ts.map +1 -0
- package/dist/engine/types.js +7 -0
- package/dist/engine/types.js.map +1 -0
- package/dist/runtime/agent-memory.d.ts +43 -0
- package/dist/runtime/agent-memory.d.ts.map +1 -0
- package/dist/runtime/agent-memory.js +201 -0
- package/dist/runtime/agent-memory.js.map +1 -0
- package/dist/runtime/agent-reactions.d.ts +55 -0
- package/dist/runtime/agent-reactions.d.ts.map +1 -0
- package/dist/runtime/agent-reactions.js +136 -0
- package/dist/runtime/agent-reactions.js.map +1 -0
- package/dist/runtime/batch.d.ts +43 -0
- package/dist/runtime/batch.d.ts.map +1 -0
- package/dist/runtime/batch.js +49 -0
- package/dist/runtime/batch.js.map +1 -0
- package/dist/runtime/contracts.d.ts +127 -0
- package/dist/runtime/contracts.d.ts.map +1 -0
- package/dist/runtime/contracts.js +2 -0
- package/dist/runtime/contracts.js.map +1 -0
- package/dist/runtime/departments.d.ts +28 -0
- package/dist/runtime/departments.d.ts.map +1 -0
- package/dist/runtime/departments.js +111 -0
- package/dist/runtime/departments.js.map +1 -0
- package/dist/runtime/director.d.ts +92 -0
- package/dist/runtime/director.d.ts.map +1 -0
- package/dist/runtime/director.js +220 -0
- package/dist/runtime/director.js.map +1 -0
- package/dist/runtime/index.d.ts +16 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +12 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/orchestrator.d.ts +66 -0
- package/dist/runtime/orchestrator.d.ts.map +1 -0
- package/dist/runtime/orchestrator.js +821 -0
- package/dist/runtime/orchestrator.js.map +1 -0
- package/dist/runtime/research/knowledge-base.d.ts +14 -0
- package/dist/runtime/research/knowledge-base.d.ts.map +1 -0
- package/dist/runtime/research/knowledge-base.js +229 -0
- package/dist/runtime/research/knowledge-base.js.map +1 -0
- package/dist/runtime/research/research-memory.d.ts +21 -0
- package/dist/runtime/research/research-memory.d.ts.map +1 -0
- package/dist/runtime/research/research-memory.js +118 -0
- package/dist/runtime/research/research-memory.js.map +1 -0
- package/dist/runtime/research/research.d.ts +4 -0
- package/dist/runtime/research/research.d.ts.map +1 -0
- package/dist/runtime/research/research.js +138 -0
- package/dist/runtime/research/research.js.map +1 -0
- package/dist/runtime/research/scenario-research.d.ts +13 -0
- package/dist/runtime/research/scenario-research.d.ts.map +1 -0
- package/dist/runtime/research/scenario-research.js +58 -0
- package/dist/runtime/research/scenario-research.js.map +1 -0
- package/dist/runtime/research/scenarios.d.ts +3 -0
- package/dist/runtime/research/scenarios.d.ts.map +1 -0
- package/dist/runtime/research/scenarios.js +252 -0
- package/dist/runtime/research/scenarios.js.map +1 -0
- package/dist/runtime/runtime-helpers.d.ts +12 -0
- package/dist/runtime/runtime-helpers.d.ts.map +1 -0
- package/dist/runtime/runtime-helpers.js +33 -0
- package/dist/runtime/runtime-helpers.js.map +1 -0
- package/package.json +78 -4
|
@@ -0,0 +1,821 @@
|
|
|
1
|
+
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { resolve, dirname } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { EmergentCapabilityEngine, EmergentJudge, EmergentToolRegistry, ComposableToolBuilder, SandboxedToolForge, ForgeToolMetaTool, generateText, } from '@framers/agentos';
|
|
5
|
+
import { SeededRng } from '../engine/core/rng.js';
|
|
6
|
+
import { classifyOutcome, classifyOutcomeById } from '../engine/core/progression.js';
|
|
7
|
+
import { SimulationKernel } from '../engine/core/kernel.js';
|
|
8
|
+
import { getResearchPacket } from './research/research.js';
|
|
9
|
+
import { getResearchFromBundle } from './research/scenario-research.js';
|
|
10
|
+
import { initResearchMemory, recallResearch, closeResearchMemory } from './research/research-memory.js';
|
|
11
|
+
import { buildDepartmentContext, getDepartmentsForTurn } from './departments.js';
|
|
12
|
+
import { EventDirector } from './director.js';
|
|
13
|
+
import { generateAgentReactions } from './agent-reactions.js';
|
|
14
|
+
import { recordReactionMemory, consolidateMemory, updateRelationshipsFromReactions } from './agent-memory.js';
|
|
15
|
+
import { DEFAULT_EXECUTION, resolveSimulationModels, } from '../cli/sim-config.js';
|
|
16
|
+
import { applyCustomEventToCrisis, buildPromotionPrompt, buildYearSchedule } from './runtime-helpers.js';
|
|
17
|
+
import { EffectRegistry } from '../engine/effect-registry.js';
|
|
18
|
+
import { marsScenario } from '../engine/mars/index.js';
|
|
19
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Web search tool
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
const webSearchTool = {
|
|
24
|
+
id: 'tool.web_search', name: 'web_search', displayName: 'Multi-Provider Web Search',
|
|
25
|
+
description: 'Search for scientific papers, NASA data, and Mars research using AgentOS WebSearchService with multi-provider fusion (Serper, Tavily, Firecrawl, Brave) and Cohere neural reranking.',
|
|
26
|
+
inputSchema: { type: 'object', properties: { query: { type: 'string' } }, required: ['query'] },
|
|
27
|
+
hasSideEffects: false,
|
|
28
|
+
async execute(args) {
|
|
29
|
+
const query = String(args.query || '');
|
|
30
|
+
// Try AgentOS WebSearchService first (multi-provider with RRF fusion)
|
|
31
|
+
try {
|
|
32
|
+
const { WebSearchService, FirecrawlProvider, TavilyProvider, SerperProvider, BraveProvider } = await import('@framers/agentos/web-search');
|
|
33
|
+
const service = new WebSearchService();
|
|
34
|
+
if (process.env.FIRECRAWL_API_KEY)
|
|
35
|
+
service.registerProvider(new FirecrawlProvider(process.env.FIRECRAWL_API_KEY));
|
|
36
|
+
if (process.env.TAVILY_API_KEY)
|
|
37
|
+
service.registerProvider(new TavilyProvider(process.env.TAVILY_API_KEY));
|
|
38
|
+
if (process.env.SERPER_API_KEY)
|
|
39
|
+
service.registerProvider(new SerperProvider(process.env.SERPER_API_KEY));
|
|
40
|
+
if (process.env.BRAVE_API_KEY)
|
|
41
|
+
service.registerProvider(new BraveProvider(process.env.BRAVE_API_KEY));
|
|
42
|
+
if (!service.hasProviders()) {
|
|
43
|
+
return { success: false, error: 'No search API keys configured. Set SERPER_API_KEY, TAVILY_API_KEY, FIRECRAWL_API_KEY, or BRAVE_API_KEY.' };
|
|
44
|
+
}
|
|
45
|
+
const results = await service.search(query, { maxResults: 5, rerank: !!process.env.COHERE_API_KEY });
|
|
46
|
+
return {
|
|
47
|
+
success: true,
|
|
48
|
+
output: {
|
|
49
|
+
results: results.map(r => ({
|
|
50
|
+
title: r.title, url: r.url, snippet: r.snippet,
|
|
51
|
+
providers: r.providerSources || [],
|
|
52
|
+
relevance: r.rerankScore || r.rrfScore || r.relevanceScore,
|
|
53
|
+
})),
|
|
54
|
+
query,
|
|
55
|
+
reranked: !!process.env.COHERE_API_KEY,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Fallback to direct Serper if AgentOS web-search module not available
|
|
61
|
+
try {
|
|
62
|
+
const key = process.env.SERPER_API_KEY;
|
|
63
|
+
if (!key)
|
|
64
|
+
return { success: false, error: 'No search API keys configured' };
|
|
65
|
+
const res = await fetch('https://google.serper.dev/search', {
|
|
66
|
+
method: 'POST', headers: { 'X-API-KEY': key, 'Content-Type': 'application/json' },
|
|
67
|
+
body: JSON.stringify({ q: query, num: 5 }),
|
|
68
|
+
});
|
|
69
|
+
if (!res.ok)
|
|
70
|
+
return { success: false, error: `Search ${res.status}` };
|
|
71
|
+
const data = await res.json();
|
|
72
|
+
return { success: true, output: { results: (data.organic || []).slice(0, 5).map((r) => ({ title: r.title, url: r.link, snippet: r.snippet })), query } };
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
return { success: false, error: String(err) };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// Emergent engine
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
function createEmergentEngine(toolMap, provider, judgeModel, execution = {}) {
|
|
84
|
+
const llmCb = async (model, prompt) => {
|
|
85
|
+
const r = await generateText({ provider, model: model || judgeModel, prompt });
|
|
86
|
+
return r.text;
|
|
87
|
+
};
|
|
88
|
+
const registry = new EmergentToolRegistry();
|
|
89
|
+
const judge = new EmergentJudge({ judgeModel, promotionModel: judgeModel, generateText: llmCb });
|
|
90
|
+
const executor = async (name, args, ctx) => {
|
|
91
|
+
const t = toolMap.get(name);
|
|
92
|
+
return t ? t.execute(args, ctx) : { success: false, error: `Tool "${name}" not found` };
|
|
93
|
+
};
|
|
94
|
+
const engine = new EmergentCapabilityEngine({
|
|
95
|
+
config: {
|
|
96
|
+
enabled: true, maxSessionTools: 20, maxAgentTools: 50,
|
|
97
|
+
sandboxTimeoutMs: execution.sandboxTimeoutMs ?? DEFAULT_EXECUTION.sandboxTimeoutMs,
|
|
98
|
+
sandboxMemoryMB: execution.sandboxMemoryMB ?? DEFAULT_EXECUTION.sandboxMemoryMB,
|
|
99
|
+
promotionThreshold: { uses: 5, confidence: 0.8 },
|
|
100
|
+
allowSandboxTools: true, persistSandboxSource: true,
|
|
101
|
+
judgeModel, promotionJudgeModel: judgeModel,
|
|
102
|
+
},
|
|
103
|
+
composableBuilder: new ComposableToolBuilder(executor),
|
|
104
|
+
sandboxForge: new SandboxedToolForge(),
|
|
105
|
+
judge, registry,
|
|
106
|
+
});
|
|
107
|
+
return { engine, forgeTool: new ForgeToolMetaTool(engine) };
|
|
108
|
+
}
|
|
109
|
+
function wrapForgeTool(raw, agentId, sessionId, dept) {
|
|
110
|
+
return {
|
|
111
|
+
...raw,
|
|
112
|
+
async execute(args, ctx) {
|
|
113
|
+
const fixed = { ...args };
|
|
114
|
+
// Parse stringified nested JSON from tool call serialization
|
|
115
|
+
for (const k of ['implementation', 'inputSchema', 'outputSchema', 'testCases']) {
|
|
116
|
+
if (typeof fixed[k] === 'string')
|
|
117
|
+
try {
|
|
118
|
+
fixed[k] = JSON.parse(fixed[k]);
|
|
119
|
+
}
|
|
120
|
+
catch { }
|
|
121
|
+
}
|
|
122
|
+
// Normalize implementation: OpenAI models send "code" instead of "sandbox", may omit allowlist
|
|
123
|
+
if (fixed.implementation && typeof fixed.implementation === 'object') {
|
|
124
|
+
const impl = fixed.implementation;
|
|
125
|
+
if (impl.mode === 'code')
|
|
126
|
+
impl.mode = 'sandbox';
|
|
127
|
+
if (impl.mode === 'sandbox' && !Array.isArray(impl.allowlist))
|
|
128
|
+
impl.allowlist = [];
|
|
129
|
+
if (impl.code && typeof impl.code !== 'string')
|
|
130
|
+
impl.code = String(impl.code);
|
|
131
|
+
// Ensure code wraps in function execute(input) if missing
|
|
132
|
+
if (impl.mode === 'sandbox' && impl.code && !impl.code.includes('function execute')) {
|
|
133
|
+
impl.code = `function execute(input) {\n${impl.code}\n}`;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Ensure schemas are valid objects — use permissive schemas so the judge
|
|
137
|
+
// doesn't reject on schema conformance mismatch
|
|
138
|
+
if (!fixed.inputSchema || typeof fixed.inputSchema !== 'object') {
|
|
139
|
+
fixed.inputSchema = { type: 'object', additionalProperties: true };
|
|
140
|
+
}
|
|
141
|
+
if (!fixed.outputSchema || typeof fixed.outputSchema !== 'object') {
|
|
142
|
+
fixed.outputSchema = { type: 'object', additionalProperties: true };
|
|
143
|
+
}
|
|
144
|
+
// Ensure testCases is a non-empty array with valid structure
|
|
145
|
+
if (!Array.isArray(fixed.testCases) || fixed.testCases.length === 0) {
|
|
146
|
+
fixed.testCases = [{ input: {}, expectedOutput: {} }];
|
|
147
|
+
}
|
|
148
|
+
for (const tc of fixed.testCases) {
|
|
149
|
+
if (!tc.input || typeof tc.input !== 'object')
|
|
150
|
+
tc.input = {};
|
|
151
|
+
if (tc.expectedOutput === undefined)
|
|
152
|
+
tc.expectedOutput = {};
|
|
153
|
+
}
|
|
154
|
+
const mode = fixed.implementation?.mode || '?';
|
|
155
|
+
console.log(` 🔧 [${dept}] Forging "${fixed.name}" (${mode})...`);
|
|
156
|
+
const patched = { ...ctx, gmiId: agentId, sessionData: { ...(ctx?.sessionData ?? {}), sessionId } };
|
|
157
|
+
try {
|
|
158
|
+
const r = await raw.execute(fixed, patched);
|
|
159
|
+
if (r.success) {
|
|
160
|
+
console.log(` 🔧 [${dept}] ✓ "${fixed.name}" approved`);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
const reason = r.error || r.output?.verdict?.reasoning || r.output?.error || '';
|
|
164
|
+
console.log(` 🔧 [${dept}] ✗ "${fixed.name}" — ${String(reason).slice(0, 150)}`);
|
|
165
|
+
}
|
|
166
|
+
return r;
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
console.log(` 🔧 [${dept}] ERR: ${err}`);
|
|
170
|
+
return { success: false, error: String(err) };
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
// Parsing
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
function humanizeToolName(name) {
|
|
179
|
+
return name.replace(/_v\d+$/, '').replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
180
|
+
}
|
|
181
|
+
/** Extract all top-level JSON objects from a string using balanced brace counting. */
|
|
182
|
+
function extractJsonBlocks(text) {
|
|
183
|
+
const blocks = [];
|
|
184
|
+
let depth = 0, start = -1;
|
|
185
|
+
for (let i = 0; i < text.length; i++) {
|
|
186
|
+
if (text[i] === '{') {
|
|
187
|
+
if (depth === 0)
|
|
188
|
+
start = i;
|
|
189
|
+
depth++;
|
|
190
|
+
}
|
|
191
|
+
if (text[i] === '}') {
|
|
192
|
+
depth--;
|
|
193
|
+
if (depth === 0 && start >= 0) {
|
|
194
|
+
blocks.push(text.slice(start, i + 1));
|
|
195
|
+
start = -1;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return blocks;
|
|
200
|
+
}
|
|
201
|
+
function cleanSummary(raw) {
|
|
202
|
+
let s = raw
|
|
203
|
+
.replace(/^#{1,4}\s+/gm, '')
|
|
204
|
+
.replace(/\*\*/g, '')
|
|
205
|
+
.replace(/^[-*]\s+/gm, '')
|
|
206
|
+
.replace(/^\d+\.\s+/gm, '')
|
|
207
|
+
.replace(/^(Decision|Recommendation|Summary|Analysis|Conclusion|I recommend|My analysis|Based on|After careful|Given the|Looking at|The data|In conclusion|Therefore|Overall|To summarize|As a result|In summary|Considering|Upon review|Having analyzed)\s*:?\s*/gim, '')
|
|
208
|
+
.replace(/^(choose|select|go with|opt for|approve|we should|I suggest|I propose)\s+/i, '')
|
|
209
|
+
.replace(/^Option [A-C][.:,]\s*/i, '')
|
|
210
|
+
.replace(/\n+/g, ' ')
|
|
211
|
+
.replace(/\s{2,}/g, ' ')
|
|
212
|
+
.trim();
|
|
213
|
+
if (s.startsWith('{') || s.startsWith('['))
|
|
214
|
+
return '';
|
|
215
|
+
const sentences = s.match(/[^.!?]+[.!?]/g) || [];
|
|
216
|
+
const result = sentences.slice(0, 2).join(' ').trim();
|
|
217
|
+
return result || s.slice(0, 150);
|
|
218
|
+
}
|
|
219
|
+
function buildReadableSummary(raw, dept) {
|
|
220
|
+
const summaryText = raw.summary || raw.decision || raw.recommendation || '';
|
|
221
|
+
const cleaned = cleanSummary(summaryText);
|
|
222
|
+
if (cleaned && cleaned.length >= 20)
|
|
223
|
+
return cleaned;
|
|
224
|
+
const recs = (raw.recommendedActions || []).slice(0, 2).join('. ');
|
|
225
|
+
if (recs)
|
|
226
|
+
return cleanSummary(recs);
|
|
227
|
+
const risks = (raw.risks || []).map((r) => r.description).slice(0, 2).join('. ');
|
|
228
|
+
if (risks)
|
|
229
|
+
return cleanSummary(risks);
|
|
230
|
+
return `${dept.charAt(0).toUpperCase() + dept.slice(1)} department analysis complete.`;
|
|
231
|
+
}
|
|
232
|
+
function parseDeptReport(text, dept) {
|
|
233
|
+
const jsonBlocks = extractJsonBlocks(text);
|
|
234
|
+
for (const block of jsonBlocks) {
|
|
235
|
+
try {
|
|
236
|
+
const raw = JSON.parse(block);
|
|
237
|
+
if (raw.department || raw.summary || raw.risks || raw.recommendedActions) {
|
|
238
|
+
const report = { ...emptyReport(dept), ...raw, department: dept };
|
|
239
|
+
report.summary = buildReadableSummary(raw, dept);
|
|
240
|
+
if (typeof report.confidence !== 'number' || report.confidence < 0.1)
|
|
241
|
+
report.confidence = 0.8;
|
|
242
|
+
return report;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
catch { /* try next block */ }
|
|
246
|
+
}
|
|
247
|
+
const cites = [];
|
|
248
|
+
let m;
|
|
249
|
+
const re = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
250
|
+
while ((m = re.exec(text)))
|
|
251
|
+
if (m[2].startsWith('http'))
|
|
252
|
+
cites.push({ text: m[1], url: m[2], context: m[1] });
|
|
253
|
+
return { ...emptyReport(dept), summary: cleanSummary(text), citations: cites };
|
|
254
|
+
}
|
|
255
|
+
function parseCmdDecision(text, depts) {
|
|
256
|
+
const jsonBlocks = extractJsonBlocks(text);
|
|
257
|
+
for (const block of jsonBlocks) {
|
|
258
|
+
try {
|
|
259
|
+
const raw = JSON.parse(block);
|
|
260
|
+
if (raw.decision || raw.selectedOptionId) {
|
|
261
|
+
return { ...emptyDecision(depts), ...raw };
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
catch { /* try next block */ }
|
|
265
|
+
}
|
|
266
|
+
return { ...emptyDecision(depts), decision: text.slice(0, 500), rationale: text };
|
|
267
|
+
}
|
|
268
|
+
function emptyReport(d) {
|
|
269
|
+
return { department: d, summary: '', citations: [], risks: [], opportunities: [], recommendedActions: [], proposedPatches: {}, forgedToolsUsed: [], featuredAgentUpdates: [], confidence: 0.7, openQuestions: [], recommendedEffects: [] };
|
|
270
|
+
}
|
|
271
|
+
function emptyDecision(d) {
|
|
272
|
+
return { decision: '', rationale: '', departmentsConsulted: d, selectedPolicies: [], rejectedPolicies: [], expectedTradeoffs: [], watchMetricsNextTurn: [] };
|
|
273
|
+
}
|
|
274
|
+
function decisionToPolicy(decision, reports, turn, year) {
|
|
275
|
+
const patches = {};
|
|
276
|
+
// Apply legacy proposedPatches (backward compat)
|
|
277
|
+
for (const r of reports) {
|
|
278
|
+
if (r.proposedPatches.colony)
|
|
279
|
+
patches.colony = { ...patches.colony, ...r.proposedPatches.colony };
|
|
280
|
+
if (r.proposedPatches.politics)
|
|
281
|
+
patches.politics = { ...patches.politics, ...r.proposedPatches.politics };
|
|
282
|
+
if (r.proposedPatches.agentUpdates)
|
|
283
|
+
patches.agentUpdates = [...(patches.agentUpdates || []), ...r.proposedPatches.agentUpdates];
|
|
284
|
+
}
|
|
285
|
+
// Apply typed effects selected by commander
|
|
286
|
+
if (decision.selectedEffectIds?.length) {
|
|
287
|
+
const allEffects = reports.flatMap(r => r.recommendedEffects || []);
|
|
288
|
+
for (const effectId of decision.selectedEffectIds) {
|
|
289
|
+
const effect = allEffects.find(e => e.id === effectId);
|
|
290
|
+
if (!effect)
|
|
291
|
+
continue;
|
|
292
|
+
if (effect.colonyDelta) {
|
|
293
|
+
patches.colony = patches.colony || {};
|
|
294
|
+
for (const [key, delta] of Object.entries(effect.colonyDelta)) {
|
|
295
|
+
const current = patches.colony[key] ?? 0;
|
|
296
|
+
patches.colony[key] = current + delta;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (effect.politicsDelta) {
|
|
300
|
+
patches.politics = patches.politics || {};
|
|
301
|
+
for (const [key, delta] of Object.entries(effect.politicsDelta)) {
|
|
302
|
+
const current = patches.politics[key] ?? 0;
|
|
303
|
+
patches.politics[key] = current + delta;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
description: decision.decision,
|
|
310
|
+
patches,
|
|
311
|
+
events: [{ turn, year, type: 'decision', description: decision.decision.slice(0, 200), data: { policies: decision.selectedPolicies } }],
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
export async function runSimulation(leader, keyPersonnel, opts = {}) {
|
|
315
|
+
const { agent } = await import('@framers/agentos');
|
|
316
|
+
const sc = opts.scenario ?? marsScenario;
|
|
317
|
+
const maxTurns = opts.maxTurns ?? 12;
|
|
318
|
+
const startYear = opts.startYear ?? 2035;
|
|
319
|
+
const provider = opts.provider ?? 'openai';
|
|
320
|
+
const sid = `${sc.labels.shortName}-v2-${leader.archetype.toLowerCase().replace(/\s+/g, '-')}`;
|
|
321
|
+
const modelConfig = resolveSimulationModels(provider, opts.models);
|
|
322
|
+
const emit = (type, data) => {
|
|
323
|
+
opts.onEvent?.({ type, leader: leader.name, data });
|
|
324
|
+
};
|
|
325
|
+
console.log(`\n${'═'.repeat(60)}`);
|
|
326
|
+
console.log(` ${sc.labels.name.toUpperCase()} v2`);
|
|
327
|
+
console.log(` Commander: ${leader.name} — "${leader.archetype}"`);
|
|
328
|
+
console.log(` Turns: ${maxTurns} | Live search: ${opts.liveSearch ? 'yes' : 'no'}`);
|
|
329
|
+
console.log(`${'═'.repeat(60)}\n`);
|
|
330
|
+
// Initialize research memory from scenario knowledge bundle
|
|
331
|
+
await initResearchMemory(sc.knowledge);
|
|
332
|
+
const seed = opts.seed ?? 950;
|
|
333
|
+
const kernel = new SimulationKernel(seed, leader.name, keyPersonnel, {
|
|
334
|
+
startYear,
|
|
335
|
+
initialPopulation: opts.initialPopulation,
|
|
336
|
+
startingResources: opts.startingResources,
|
|
337
|
+
startingPolitics: opts.startingPolitics,
|
|
338
|
+
});
|
|
339
|
+
const toolMap = new Map();
|
|
340
|
+
toolMap.set('web_search', webSearchTool);
|
|
341
|
+
const { engine, forgeTool } = createEmergentEngine(toolMap, provider, modelConfig.judge, opts.execution);
|
|
342
|
+
const toolRegs = {};
|
|
343
|
+
const commander = agent({
|
|
344
|
+
provider, model: modelConfig.commander,
|
|
345
|
+
instructions: leader.instructions,
|
|
346
|
+
personality: { openness: leader.hexaco.openness, conscientiousness: leader.hexaco.conscientiousness, extraversion: leader.hexaco.extraversion, agreeableness: leader.hexaco.agreeableness, emotionality: leader.hexaco.emotionality, honesty: leader.hexaco.honestyHumility },
|
|
347
|
+
maxSteps: opts.execution?.commanderMaxSteps ?? DEFAULT_EXECUTION.commanderMaxSteps,
|
|
348
|
+
});
|
|
349
|
+
const cmdSess = commander.session(`${sid}-cmd`);
|
|
350
|
+
await cmdSess.send('You are the colony commander. You receive department reports and make strategic decisions. When the crisis includes options with IDs, you MUST include selectedOptionId in your JSON response. Return JSON with selectedOptionId, decision, rationale, selectedPolicies, rejectedPolicies, expectedTradeoffs, watchMetricsNextTurn. Acknowledge.');
|
|
351
|
+
// Turn 0: Commander promotes department heads from agent roster
|
|
352
|
+
console.log(' [Turn 0] Commander evaluating roster for promotions...');
|
|
353
|
+
const promotionDepts = sc.departments.map(d => d.id);
|
|
354
|
+
const roleNames = Object.fromEntries(sc.departments.map(d => [d.id, d.role]));
|
|
355
|
+
const candidateSummaries = promotionDepts.map(dept => {
|
|
356
|
+
const candidates = kernel.getCandidates(dept, 5);
|
|
357
|
+
return `## ${dept.toUpperCase()} — Top 5 Candidates:\n${candidates.map(c => {
|
|
358
|
+
const age = startYear - c.core.birthYear;
|
|
359
|
+
const h = c.hexaco;
|
|
360
|
+
return `- ${c.core.name} (${c.core.id}), age ${age}, spec: ${c.career.specialization}, O:${h.openness.toFixed(2)} C:${h.conscientiousness.toFixed(2)} E:${h.extraversion.toFixed(2)} A:${h.agreeableness.toFixed(2)} Em:${h.emotionality.toFixed(2)} HH:${h.honestyHumility.toFixed(2)}`;
|
|
361
|
+
}).join('\n')}`;
|
|
362
|
+
}).join('\n\n');
|
|
363
|
+
const promoResult = await cmdSess.send(buildPromotionPrompt(candidateSummaries));
|
|
364
|
+
const promoMatch = promoResult.text.match(/\{[\s\S]*"promotions"[\s\S]*\}/);
|
|
365
|
+
if (promoMatch) {
|
|
366
|
+
try {
|
|
367
|
+
const pd = JSON.parse(promoMatch[0]);
|
|
368
|
+
for (const p of pd.promotions || []) {
|
|
369
|
+
try {
|
|
370
|
+
kernel.promoteAgent(p.agentId, p.department, p.role, leader.name);
|
|
371
|
+
console.log(` ✦ ${p.agentId} → ${p.role}: ${p.reason?.slice(0, 80)}`);
|
|
372
|
+
emit('promotion', { agentId: p.agentId, department: p.department, role: p.role, reason: p.reason?.slice(0, 120) });
|
|
373
|
+
}
|
|
374
|
+
catch (err) {
|
|
375
|
+
console.log(` ✦ Promotion failed: ${err}`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
catch { /* fallback below */ }
|
|
380
|
+
}
|
|
381
|
+
// Fallback: promote top candidate per dept if commander didn't produce valid JSON
|
|
382
|
+
for (const dept of promotionDepts) {
|
|
383
|
+
const hasLeader = kernel.getState().agents.some(c => c.promotion?.department === dept);
|
|
384
|
+
if (!hasLeader) {
|
|
385
|
+
const top = kernel.getCandidates(dept, 1)[0];
|
|
386
|
+
if (top) {
|
|
387
|
+
kernel.promoteAgent(top.core.id, dept, roleNames[dept] || `Head of ${dept}`, leader.name);
|
|
388
|
+
console.log(` ✦ [fallback] ${top.core.name} → ${roleNames[dept]}`);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
// Create department agent sessions from promoted agents
|
|
393
|
+
const deptAgents = new Map();
|
|
394
|
+
const deptSess = new Map();
|
|
395
|
+
const promoted = kernel.getState().agents.filter(c => c.promotion);
|
|
396
|
+
for (const p of promoted) {
|
|
397
|
+
const dept = p.promotion.department;
|
|
398
|
+
const cfg = sc.departments.find(c => c.id === dept);
|
|
399
|
+
if (!cfg)
|
|
400
|
+
continue;
|
|
401
|
+
const wrapped = wrapForgeTool(forgeTool, `${sid}-${dept}`, sid, dept);
|
|
402
|
+
const tools = opts.liveSearch ? [webSearchTool, wrapped] : [wrapped];
|
|
403
|
+
const a = agent({
|
|
404
|
+
provider,
|
|
405
|
+
model: modelConfig.departments || cfg.defaultModel,
|
|
406
|
+
instructions: cfg.instructions + '\n\nIMPORTANT: You MUST respond with valid JSON only. No markdown, no prose, no explanation outside the JSON object.',
|
|
407
|
+
tools,
|
|
408
|
+
maxSteps: opts.execution?.departmentMaxSteps ?? DEFAULT_EXECUTION.departmentMaxSteps,
|
|
409
|
+
});
|
|
410
|
+
deptAgents.set(dept, a);
|
|
411
|
+
deptSess.set(dept, a.session(`${sid}-${dept}`));
|
|
412
|
+
}
|
|
413
|
+
console.log(` Promoted ${promoted.length} department heads. Agents created.\n`);
|
|
414
|
+
const artifacts = [];
|
|
415
|
+
const yearSchedule = buildYearSchedule(startYear, maxTurns, opts.yearsPerTurn);
|
|
416
|
+
const outcomeLog = [];
|
|
417
|
+
const eventHistory = [];
|
|
418
|
+
let lastTurnToolOutputs = [];
|
|
419
|
+
let lastTurnMoodSummary;
|
|
420
|
+
const director = new EventDirector();
|
|
421
|
+
const effectRegistry = new EffectRegistry(sc.effects[0]?.categoryDefaults ?? {});
|
|
422
|
+
// Department memory: stores previous turn summaries per department for session continuity
|
|
423
|
+
const deptMemory = new Map();
|
|
424
|
+
const activeDepartments = new Set(opts.activeDepartments ?? sc.departments.map(d => d.id));
|
|
425
|
+
for (let turn = 1; turn <= maxTurns; turn++) {
|
|
426
|
+
const year = yearSchedule[turn - 1] ?? (yearSchedule[yearSchedule.length - 1] + (turn - yearSchedule.length) * 5);
|
|
427
|
+
// Get crisis: milestone (turn 1 / final) or emergent (director)
|
|
428
|
+
let event;
|
|
429
|
+
const getMilestone = sc.hooks.getMilestoneEvent ?? sc.hooks.getMilestoneEvent;
|
|
430
|
+
const milestone = getMilestone?.(turn, maxTurns);
|
|
431
|
+
if (milestone) {
|
|
432
|
+
event = { ...milestone, description: milestone.description || milestone.crisis || '' };
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
// Build director context from current colony state
|
|
436
|
+
const preState = kernel.getState();
|
|
437
|
+
const alive = preState.agents.filter(c => c.health.alive);
|
|
438
|
+
const dirCtx = {
|
|
439
|
+
turn, year,
|
|
440
|
+
leaderName: leader.name, leaderArchetype: leader.archetype, leaderHexaco: leader.hexaco,
|
|
441
|
+
state: preState.colony,
|
|
442
|
+
politics: preState.politics,
|
|
443
|
+
colony: preState.colony,
|
|
444
|
+
aliveCount: alive.length,
|
|
445
|
+
nativeBornCount: alive.filter(c => c.core.marsborn).length,
|
|
446
|
+
marsBornCount: alive.filter(c => c.core.marsborn).length,
|
|
447
|
+
recentDeaths: preState.eventLog.filter(e => e.turn === turn - 1 && e.type === 'death').length,
|
|
448
|
+
recentBirths: preState.eventLog.filter(e => e.turn === turn - 1 && e.type === 'birth').length,
|
|
449
|
+
previousEvents: eventHistory,
|
|
450
|
+
previousCrises: eventHistory,
|
|
451
|
+
toolsForged: Object.values(toolRegs).flat(),
|
|
452
|
+
driftSummary: preState.agents.filter(c => c.promotion && c.health.alive).slice(0, 4)
|
|
453
|
+
.map(c => ({ name: c.core.name, role: c.core.role, openness: c.hexaco.openness, conscientiousness: c.hexaco.conscientiousness })),
|
|
454
|
+
recentToolOutputs: lastTurnToolOutputs,
|
|
455
|
+
agentMoodSummary: lastTurnMoodSummary,
|
|
456
|
+
};
|
|
457
|
+
emit('turn_start', { turn, year, title: 'Director generating...', crisis: '', births: 0, deaths: 0, colony: preState.colony });
|
|
458
|
+
const dirInstructions = sc.hooks.directorInstructions?.();
|
|
459
|
+
event = await director.generateEvent(dirCtx, provider, modelConfig.director, dirInstructions);
|
|
460
|
+
}
|
|
461
|
+
event = applyCustomEventToCrisis(event, opts.customEvents ?? [], turn);
|
|
462
|
+
console.log(`\n${'─'.repeat(50)}`);
|
|
463
|
+
console.log(` Turn ${turn}/${maxTurns} — Year ${year}: ${event.title} [${milestone ? 'MILESTONE' : 'EMERGENT'}]`);
|
|
464
|
+
console.log(`${'─'.repeat(50)}`);
|
|
465
|
+
const state = kernel.advanceTurn(turn, year, sc.hooks.progressionHook);
|
|
466
|
+
const births = state.eventLog.filter(e => e.turn === turn && e.type === 'birth').length;
|
|
467
|
+
const deaths = state.eventLog.filter(e => e.turn === turn && e.type === 'death').length;
|
|
468
|
+
console.log(` Kernel: +${births} births, -${deaths} deaths → pop ${state.colony.population}`);
|
|
469
|
+
emit('turn_start', { turn, year, title: event.title, crisis: event.description.slice(0, 200), category: event.category, births, deaths, colony: state.colony, emergent: !milestone, turnSummary: event.turnSummary });
|
|
470
|
+
// Get research: memory recall > live web search > static fallback
|
|
471
|
+
let packet;
|
|
472
|
+
if (milestone) {
|
|
473
|
+
// Milestone research: try scenario knowledge bundle first, fall back to legacy static
|
|
474
|
+
packet = getResearchFromBundle(sc.knowledge, event.category, event.researchKeywords);
|
|
475
|
+
if (packet.canonicalFacts.length === 0) {
|
|
476
|
+
packet = getResearchPacket(turn);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
// Try research memory first (semantic recall from ingested scenario citations)
|
|
481
|
+
const memPacket = await recallResearch(event.title + ' ' + event.description.slice(0, 100), event.researchKeywords, event.category);
|
|
482
|
+
if (memPacket.canonicalFacts.length >= 2) {
|
|
483
|
+
packet = memPacket;
|
|
484
|
+
console.log(` [research] Memory recall: ${packet.canonicalFacts.length} citations`);
|
|
485
|
+
}
|
|
486
|
+
else if (opts.liveSearch && event.researchKeywords.length) {
|
|
487
|
+
// Live web search using AgentOS WebSearchService
|
|
488
|
+
try {
|
|
489
|
+
const query = event.researchKeywords.slice(0, 3).join(' ') + ' ' + sc.labels.settlementNoun + ' science';
|
|
490
|
+
console.log(` [research] Live search: "${query}"`);
|
|
491
|
+
const searchResult = await webSearchTool.execute({ query }, { gmiId: sid, personaId: sid, userContext: {} });
|
|
492
|
+
const results = searchResult?.output?.results || [];
|
|
493
|
+
packet = {
|
|
494
|
+
canonicalFacts: results.slice(0, 5).map((r) => ({
|
|
495
|
+
claim: r.snippet || r.title || '',
|
|
496
|
+
source: r.title || 'web search',
|
|
497
|
+
url: r.url || '',
|
|
498
|
+
})),
|
|
499
|
+
counterpoints: [],
|
|
500
|
+
departmentNotes: {},
|
|
501
|
+
};
|
|
502
|
+
console.log(` [research] ${packet.canonicalFacts.length} live results`);
|
|
503
|
+
}
|
|
504
|
+
catch (err) {
|
|
505
|
+
console.log(` [research] Live search failed, using scenario bundle: ${err}`);
|
|
506
|
+
packet = getResearchFromBundle(sc.knowledge, event.category, event.researchKeywords);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
// Fallback to scenario knowledge bundle (not hardcoded Mars)
|
|
511
|
+
packet = getResearchFromBundle(sc.knowledge, event.category, event.researchKeywords);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
// Departments: from director for emergent, from schedule for milestones
|
|
515
|
+
const validDepts = ['medical', 'engineering', 'agriculture', 'psychology', 'governance'];
|
|
516
|
+
const rawDepts = milestone ? getDepartmentsForTurn(turn) : event.relevantDepartments;
|
|
517
|
+
const depts = rawDepts.filter(d => validDepts.includes(d) && activeDepartments.has(d));
|
|
518
|
+
if (!depts.length)
|
|
519
|
+
depts.push('medical', 'engineering');
|
|
520
|
+
console.log(` Departments: ${depts.join(', ')}`);
|
|
521
|
+
// Build a Scenario-compatible object for department context builder
|
|
522
|
+
const scenario = {
|
|
523
|
+
turn, year, title: event.title, crisis: event.description,
|
|
524
|
+
researchKeywords: event.researchKeywords, snapshotHints: {},
|
|
525
|
+
riskyOption: event.options.find(o => o.isRisky)?.label || '',
|
|
526
|
+
riskSuccessProbability: event.riskSuccessProbability,
|
|
527
|
+
options: event.options,
|
|
528
|
+
};
|
|
529
|
+
const reports = [];
|
|
530
|
+
for (const dept of depts) {
|
|
531
|
+
const sess = deptSess.get(dept);
|
|
532
|
+
if (!sess)
|
|
533
|
+
continue;
|
|
534
|
+
const ctx = buildDepartmentContext(dept, state, scenario, packet, deptMemory.get(dept), sc.hooks.departmentPromptHook);
|
|
535
|
+
console.log(` [${dept}] Analyzing...`);
|
|
536
|
+
emit('dept_start', { turn, year, department: dept });
|
|
537
|
+
try {
|
|
538
|
+
const r = await sess.send(ctx);
|
|
539
|
+
const report = parseDeptReport(r.text, dept);
|
|
540
|
+
reports.push(report);
|
|
541
|
+
console.log(` [${dept}] Done: ${report.citations.length} citations, ${report.risks.length} risks, ${report.forgedToolsUsed.length} tools`);
|
|
542
|
+
const validTools = report.forgedToolsUsed
|
|
543
|
+
.filter(t => t && (t.name || t.description))
|
|
544
|
+
.map(t => {
|
|
545
|
+
const rawOutput = t.output ? (typeof t.output === 'string' ? t.output : JSON.stringify(t.output)) : null;
|
|
546
|
+
// Extract input/output field names from output JSON
|
|
547
|
+
let inputFields = [];
|
|
548
|
+
let outputFields = [];
|
|
549
|
+
if (rawOutput) {
|
|
550
|
+
try {
|
|
551
|
+
const parsed = JSON.parse(rawOutput);
|
|
552
|
+
if (parsed && typeof parsed === 'object') {
|
|
553
|
+
const keys = Object.keys(parsed);
|
|
554
|
+
const inKey = keys.find(k => ['inputs', 'input', 'parameters', 'params'].includes(k));
|
|
555
|
+
if (inKey && parsed[inKey] && typeof parsed[inKey] === 'object') {
|
|
556
|
+
inputFields = Object.keys(parsed[inKey]);
|
|
557
|
+
outputFields = keys.filter(k => k !== inKey);
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
outputFields = keys;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
catch { }
|
|
565
|
+
}
|
|
566
|
+
return {
|
|
567
|
+
name: t.name || t.description || 'tool',
|
|
568
|
+
mode: t.mode || 'sandbox',
|
|
569
|
+
confidence: t.confidence ?? 0.85,
|
|
570
|
+
description: t.description || humanizeToolName(t.name || ''),
|
|
571
|
+
output: rawOutput?.slice(0, 400) || null,
|
|
572
|
+
inputFields: inputFields.slice(0, 8),
|
|
573
|
+
outputFields: outputFields.slice(0, 8),
|
|
574
|
+
department: dept,
|
|
575
|
+
crisis: event.title,
|
|
576
|
+
};
|
|
577
|
+
});
|
|
578
|
+
emit('dept_done', {
|
|
579
|
+
turn, year, department: dept, summary: report.summary,
|
|
580
|
+
citations: report.citations.length,
|
|
581
|
+
citationList: report.citations.slice(0, 5).map(c => ({ text: c.text, url: c.url, doi: c.doi })),
|
|
582
|
+
risks: report.risks, forgedTools: validTools,
|
|
583
|
+
recommendedActions: report.recommendedActions?.slice(0, 2),
|
|
584
|
+
});
|
|
585
|
+
if (report.forgedToolsUsed.length) {
|
|
586
|
+
const names = report.forgedToolsUsed.map(t => t?.name || t?.description || 'unnamed').filter(Boolean);
|
|
587
|
+
if (names.length)
|
|
588
|
+
toolRegs[dept] = [...(toolRegs[dept] || []), ...names];
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
catch (err) {
|
|
592
|
+
console.log(` [${dept}] ERROR: ${err}`);
|
|
593
|
+
reports.push(emptyReport(dept));
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
// Collect tool outputs for next turn's director context
|
|
597
|
+
lastTurnToolOutputs = reports.flatMap(r => (r.forgedToolsUsed || []).filter(t => t?.output).map(t => ({
|
|
598
|
+
name: t.name || 'unnamed',
|
|
599
|
+
department: r.department,
|
|
600
|
+
output: typeof t.output === 'string' ? t.output.slice(0, 200) : JSON.stringify(t.output).slice(0, 200),
|
|
601
|
+
})));
|
|
602
|
+
// Store department memories for session continuity (will be completed with outcome after decision)
|
|
603
|
+
const pendingDeptMemories = new Map();
|
|
604
|
+
for (const r of reports) {
|
|
605
|
+
pendingDeptMemories.set(r.department, {
|
|
606
|
+
turn, year, crisis: event.title,
|
|
607
|
+
summary: r.summary,
|
|
608
|
+
recommendedActions: r.recommendedActions?.slice(0, 3) || [],
|
|
609
|
+
outcome: '', // filled after outcome classification
|
|
610
|
+
toolsForged: (r.forgedToolsUsed || []).map(t => t?.name || '').filter(Boolean),
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
const summaries = reports.map(r => `## ${r.department.toUpperCase()} (conf: ${r.confidence})\n${r.summary}\nRisks: ${r.risks.map(x => `[${x.severity}] ${x.description}`).join('; ') || 'none'}\nRecs: ${r.recommendedActions.join('; ') || 'none'}`).join('\n\n');
|
|
614
|
+
const optionText = event.options.length
|
|
615
|
+
? '\n\nOPTIONS:\n' + event.options.map(o => `- ${o.id}: ${o.label} — ${o.description}${o.isRisky ? ' [RISKY]' : ''}`).join('\n') + '\n\nYou MUST include "selectedOptionId" in your JSON response.'
|
|
616
|
+
: '';
|
|
617
|
+
const effectsList = reports.flatMap(r => (r.recommendedEffects || []).map(e => ` - ${e.id} (${e.type}): ${e.description}${e.colonyDelta ? ' | Delta: ' + JSON.stringify(e.colonyDelta) : ''}`));
|
|
618
|
+
const effectsText = effectsList.length
|
|
619
|
+
? '\n\nAVAILABLE POLICY EFFECTS (include "selectedEffectIds" array in your JSON to apply):\n' + effectsList.join('\n')
|
|
620
|
+
: '';
|
|
621
|
+
const cmdPrompt = `TURN ${turn} — ${year}: ${event.title}\n\n${event.description}\n\nDEPARTMENT REPORTS:\n${summaries}\n\nColony: Pop ${state.colony.population} | Morale ${Math.round(state.colony.morale * 100)}% | Food ${state.colony.foodMonthsReserve.toFixed(1)}mo${optionText}${effectsText}\n\nDecide. Return JSON.`;
|
|
622
|
+
console.log(` [commander] Deciding...`);
|
|
623
|
+
emit('commander_deciding', { turn, year });
|
|
624
|
+
const cmdR = await cmdSess.send(cmdPrompt);
|
|
625
|
+
const decision = parseCmdDecision(cmdR.text, depts);
|
|
626
|
+
console.log(` [commander] ${decision.decision.slice(0, 120)}...`);
|
|
627
|
+
emit('commander_decided', { turn, year, decision: decision.decision, rationale: decision.rationale, selectedPolicies: decision.selectedPolicies });
|
|
628
|
+
kernel.applyPolicy(decisionToPolicy(decision, reports, turn, year));
|
|
629
|
+
// Apply featured agent updates
|
|
630
|
+
const agentUpdates = reports.flatMap(r => (r.featuredAgentUpdates || []).filter(u => u && u.agentId && u.updates).map(u => ({
|
|
631
|
+
agentId: u.agentId,
|
|
632
|
+
health: u.updates?.health,
|
|
633
|
+
career: u.updates?.career,
|
|
634
|
+
narrativeEvent: u.updates?.narrative?.event,
|
|
635
|
+
})));
|
|
636
|
+
if (agentUpdates.length)
|
|
637
|
+
kernel.applyAgentUpdates(agentUpdates);
|
|
638
|
+
// Classify outcome using structured option IDs
|
|
639
|
+
const outcomeRng = new SeededRng(seed).turnSeed(turn + 1000);
|
|
640
|
+
// Determine selected option: prefer explicit ID, then try to infer from decision text matching option labels
|
|
641
|
+
let resolvedOptionId = decision.selectedOptionId;
|
|
642
|
+
if (!resolvedOptionId && event.options.length) {
|
|
643
|
+
const decLower = (decision.decision || '').toLowerCase();
|
|
644
|
+
for (const opt of event.options) {
|
|
645
|
+
if (decLower.includes(opt.id) || decLower.includes(opt.label.toLowerCase())) {
|
|
646
|
+
resolvedOptionId = opt.id;
|
|
647
|
+
break;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
const outcome = resolvedOptionId
|
|
652
|
+
? classifyOutcomeById(resolvedOptionId, event.options, event.riskSuccessProbability, kernel.getState().colony, outcomeRng)
|
|
653
|
+
: classifyOutcome(decision.decision, scenario.riskyOption, event.riskSuccessProbability, kernel.getState().colony, outcomeRng);
|
|
654
|
+
// Apply outcome-driven colony effects via EffectRegistry
|
|
655
|
+
const outcomeEffectRng = new SeededRng(seed).turnSeed(turn + 2000);
|
|
656
|
+
const personalityBonus = (leader.hexaco.openness - 0.5) * 0.08 + (leader.hexaco.conscientiousness - 0.5) * 0.04;
|
|
657
|
+
const colonyDeltas = effectRegistry.applyOutcome(event.category, outcome, {
|
|
658
|
+
personalityBonus,
|
|
659
|
+
noise: outcomeEffectRng.next() * 0.2 - 0.1,
|
|
660
|
+
});
|
|
661
|
+
kernel.applyColonyDeltas(colonyDeltas, [{
|
|
662
|
+
turn, year, type: 'system',
|
|
663
|
+
description: `Outcome effect (${outcome}): ${Object.entries(colonyDeltas).map(([k, v]) => `${k} ${v >= 0 ? '+' : ''}${v}`).join(', ')}`,
|
|
664
|
+
}]);
|
|
665
|
+
// Apply politics deltas via scenario hook
|
|
666
|
+
const polDelta = sc.hooks.politicsHook?.(event.category, outcome);
|
|
667
|
+
if (polDelta) {
|
|
668
|
+
kernel.applyPoliticsDeltas(polDelta);
|
|
669
|
+
}
|
|
670
|
+
const prevYear = turn === 1 ? startYear : yearSchedule[turn - 2] ?? startYear;
|
|
671
|
+
kernel.applyDrift(leader.hexaco, outcome, Math.max(1, year - prevYear));
|
|
672
|
+
outcomeLog.push({ turn, year, outcome });
|
|
673
|
+
// Track crisis history for director context
|
|
674
|
+
eventHistory.push({ turn, title: event.title, category: event.category, selectedOptionId: decision.selectedOptionId, decision: decision.decision.slice(0, 100), outcome });
|
|
675
|
+
// Finalize department memories with outcome and store
|
|
676
|
+
for (const [dept, mem] of pendingDeptMemories) {
|
|
677
|
+
mem.outcome = outcome;
|
|
678
|
+
const existing = deptMemory.get(dept) || [];
|
|
679
|
+
existing.push(mem);
|
|
680
|
+
deptMemory.set(dept, existing);
|
|
681
|
+
}
|
|
682
|
+
console.log(` [outcome] ${outcome} (${event.category}${milestone ? ', milestone' : ', emergent'}) effects: ${JSON.stringify(colonyDeltas)}`);
|
|
683
|
+
emit('outcome', { turn, year, outcome, riskyOption: scenario.riskyOption, category: event.category, emergent: !milestone, colonyDeltas });
|
|
684
|
+
// Log drift
|
|
685
|
+
const drifted = kernel.getState().agents.filter(c => c.promotion && c.health.alive);
|
|
686
|
+
const driftData = {};
|
|
687
|
+
for (const p of drifted.slice(0, 5)) {
|
|
688
|
+
const h = p.hexaco;
|
|
689
|
+
driftData[p.core.id] = { name: p.core.name, hexaco: { O: +h.openness.toFixed(2), C: +h.conscientiousness.toFixed(2), E: +h.extraversion.toFixed(2), A: +h.agreeableness.toFixed(2) } };
|
|
690
|
+
}
|
|
691
|
+
emit('drift', { turn, year, agents: driftData });
|
|
692
|
+
// Generate colonist reactions in parallel (all alive agents, cheap model)
|
|
693
|
+
const reactionCtx = {
|
|
694
|
+
crisisTitle: event.title, crisisCategory: event.category,
|
|
695
|
+
outcome, decision: decision.decision.slice(0, 200),
|
|
696
|
+
year, turn, colonyMorale: kernel.getState().colony.morale,
|
|
697
|
+
colonyPopulation: kernel.getState().colony.population,
|
|
698
|
+
};
|
|
699
|
+
try {
|
|
700
|
+
const reactions = await generateAgentReactions(kernel.getState().agents, reactionCtx, { provider, model: modelConfig.agentReactions || 'gpt-4o-mini', maxConcurrent: 25, reactionContextHook: sc.hooks.reactionContextHook });
|
|
701
|
+
if (reactions.length) {
|
|
702
|
+
// Emit top 8 most intense reactions for dashboard display
|
|
703
|
+
// Build memory summaries for display
|
|
704
|
+
const agentMap = new Map(kernel.getState().agents.map(c => [c.core.id, c]));
|
|
705
|
+
emit('agent_reactions', {
|
|
706
|
+
turn, year,
|
|
707
|
+
reactions: reactions.slice(0, 8).map(r => {
|
|
708
|
+
const agent = agentMap.get(r.agentId);
|
|
709
|
+
const mem = agent?.memory;
|
|
710
|
+
return {
|
|
711
|
+
name: r.name, age: r.age, department: r.department, role: r.role,
|
|
712
|
+
specialization: r.specialization, marsborn: r.marsborn,
|
|
713
|
+
quote: r.quote, mood: r.mood, intensity: r.intensity,
|
|
714
|
+
hexaco: r.hexaco, psychScore: r.psychScore, boneDensity: r.boneDensity, radiation: r.radiation,
|
|
715
|
+
agentId: r.agentId,
|
|
716
|
+
memory: mem ? {
|
|
717
|
+
recentMemories: mem.shortTerm.slice(-3).map(m => ({ year: m.year, content: m.content, valence: m.valence })),
|
|
718
|
+
beliefs: mem.longTerm.slice(-3),
|
|
719
|
+
stances: Object.entries(mem.stances).filter(([, v]) => Math.abs(v) > 0.2).map(([k, v]) => ({ topic: k, value: v })),
|
|
720
|
+
relationships: Object.entries(mem.relationships)
|
|
721
|
+
.filter(([, v]) => Math.abs(v) > 0.2)
|
|
722
|
+
.sort((a, b) => Math.abs(b[1]) - Math.abs(a[1]))
|
|
723
|
+
.slice(0, 3)
|
|
724
|
+
.map(([id, v]) => ({ name: agentMap.get(id)?.core.name || id, sentiment: v })),
|
|
725
|
+
} : null,
|
|
726
|
+
};
|
|
727
|
+
}),
|
|
728
|
+
totalReactions: reactions.length,
|
|
729
|
+
});
|
|
730
|
+
// Record reactions into persistent memory
|
|
731
|
+
for (const r of reactions) {
|
|
732
|
+
const c = agentMap.get(r.agentId);
|
|
733
|
+
if (c)
|
|
734
|
+
recordReactionMemory(c, r, event.title, event.category, outcome, turn, year);
|
|
735
|
+
}
|
|
736
|
+
updateRelationshipsFromReactions(kernel.getState().agents, reactions);
|
|
737
|
+
// Consolidate any overflowing short-term memories
|
|
738
|
+
for (const c of kernel.getState().agents) {
|
|
739
|
+
if (c.health.alive)
|
|
740
|
+
consolidateMemory(c);
|
|
741
|
+
}
|
|
742
|
+
// Summarize mood for next turn's director
|
|
743
|
+
const moodCounts = {};
|
|
744
|
+
for (const r of reactions)
|
|
745
|
+
moodCounts[r.mood] = (moodCounts[r.mood] || 0) + 1;
|
|
746
|
+
const moodParts = Object.entries(moodCounts).sort((a, b) => b[1] - a[1]).map(([m, c]) => `${Math.round(c / reactions.length * 100)}% ${m}`);
|
|
747
|
+
lastTurnMoodSummary = `${reactions.length} colonists: ${moodParts.join(', ')}`;
|
|
748
|
+
// Colony bulletin board: top 4 most intense reactions as public posts
|
|
749
|
+
const bulletinPosts = reactions.slice(0, 4).map(r => ({
|
|
750
|
+
name: r.name, department: r.department, role: r.role,
|
|
751
|
+
marsborn: r.marsborn, age: r.age,
|
|
752
|
+
post: r.quote.length > 140 ? r.quote.slice(0, 137) + '...' : r.quote,
|
|
753
|
+
mood: r.mood, intensity: r.intensity,
|
|
754
|
+
likes: Math.floor(r.intensity * 20 + outcomeEffectRng.next() * 10),
|
|
755
|
+
replies: Math.floor(r.intensity * 5 + outcomeEffectRng.next() * 3),
|
|
756
|
+
}));
|
|
757
|
+
emit('bulletin', { turn, year, posts: bulletinPosts });
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
catch (err) {
|
|
761
|
+
console.log(` [agents] Reaction generation failed: ${err}`);
|
|
762
|
+
}
|
|
763
|
+
const after = kernel.getState();
|
|
764
|
+
artifacts.push({
|
|
765
|
+
turn, year, crisis: event.title,
|
|
766
|
+
departmentReports: reports, commanderDecision: decision,
|
|
767
|
+
policyEffectsApplied: decision.selectedPolicies,
|
|
768
|
+
stateSnapshotAfter: {
|
|
769
|
+
population: after.colony.population, morale: after.colony.morale,
|
|
770
|
+
foodMonthsReserve: after.colony.foodMonthsReserve, infrastructureModules: after.colony.infrastructureModules,
|
|
771
|
+
scienceOutput: after.colony.scienceOutput, births, deaths,
|
|
772
|
+
},
|
|
773
|
+
});
|
|
774
|
+
console.log(` State: Pop ${after.colony.population} | Morale ${Math.round(after.colony.morale * 100)}% | Food ${after.colony.foodMonthsReserve.toFixed(1)}mo`);
|
|
775
|
+
emit('turn_done', { turn, year, colony: after.colony, toolsForged: Object.values(toolRegs).flat().length });
|
|
776
|
+
}
|
|
777
|
+
const final = kernel.export();
|
|
778
|
+
// Build colonist trajectories for promoted leaders
|
|
779
|
+
const trajectories = Object.fromEntries(final.agents
|
|
780
|
+
.filter(c => c.promotion && c.hexacoHistory.length > 1)
|
|
781
|
+
.map(c => [c.core.id, {
|
|
782
|
+
name: c.core.name,
|
|
783
|
+
promotedTurn: c.promotion.turnPromoted,
|
|
784
|
+
promotedAs: c.promotion.role,
|
|
785
|
+
promotedBy: c.promotion.promotedBy,
|
|
786
|
+
hexacoTrajectory: c.hexacoHistory,
|
|
787
|
+
}]));
|
|
788
|
+
// Compute timeline fingerprint via scenario hook
|
|
789
|
+
const fingerprint = sc.hooks.fingerprintHook
|
|
790
|
+
? sc.hooks.fingerprintHook(final, outcomeLog, leader, toolRegs, maxTurns)
|
|
791
|
+
: { summary: 'no fingerprint hook' };
|
|
792
|
+
const output = {
|
|
793
|
+
simulation: `${sc.id}-v3`, leader: { name: leader.name, archetype: leader.archetype, colony: leader.colony, hexaco: leader.hexaco },
|
|
794
|
+
turnArtifacts: artifacts, finalState: final, toolRegistries: toolRegs,
|
|
795
|
+
agentTrajectories: trajectories,
|
|
796
|
+
outcomeClassifications: outcomeLog,
|
|
797
|
+
fingerprint,
|
|
798
|
+
totalCitations: artifacts.reduce((s, t) => s + t.departmentReports.reduce((s2, r) => s2 + r.citations.length, 0), 0),
|
|
799
|
+
totalToolsForged: Object.values(toolRegs).flat().length,
|
|
800
|
+
};
|
|
801
|
+
const outDir = resolve(__dirname, '..', '..', 'output');
|
|
802
|
+
mkdirSync(outDir, { recursive: true });
|
|
803
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
804
|
+
const tag = leader.archetype.toLowerCase().replace(/\s+/g, '-');
|
|
805
|
+
const path = resolve(outDir, `v3-${tag}-${ts}.json`);
|
|
806
|
+
writeFileSync(path, JSON.stringify(output, null, 2));
|
|
807
|
+
console.log(`\n${'═'.repeat(60)}`);
|
|
808
|
+
console.log(` COMPLETE — ${leader.name}`);
|
|
809
|
+
console.log(` Output: ${path}`);
|
|
810
|
+
console.log(` Turns: ${artifacts.length} | Citations: ${output.totalCitations} | Tools: ${output.totalToolsForged}`);
|
|
811
|
+
console.log(` Final: Pop ${final.colony.population} | Morale ${Math.round(final.colony.morale * 100)}%`);
|
|
812
|
+
console.log(` Registries: ${JSON.stringify(toolRegs)}`);
|
|
813
|
+
console.log(`${'═'.repeat(60)}\n`);
|
|
814
|
+
engine.cleanupSession(sid);
|
|
815
|
+
await closeResearchMemory();
|
|
816
|
+
await commander.close();
|
|
817
|
+
for (const a of deptAgents.values())
|
|
818
|
+
await a.close();
|
|
819
|
+
return output;
|
|
820
|
+
}
|
|
821
|
+
//# sourceMappingURL=orchestrator.js.map
|