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.
Files changed (273) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +224 -0
  3. package/dist/cli/sim-config.d.ts +100 -0
  4. package/dist/cli/sim-config.d.ts.map +1 -0
  5. package/dist/cli/sim-config.js +124 -0
  6. package/dist/cli/sim-config.js.map +1 -0
  7. package/dist/cli/types.d.ts +73 -0
  8. package/dist/cli/types.d.ts.map +1 -0
  9. package/dist/cli/types.js +2 -0
  10. package/dist/cli/types.js.map +1 -0
  11. package/dist/engine/_template/index.d.ts +9 -0
  12. package/dist/engine/_template/index.d.ts.map +1 -0
  13. package/dist/engine/_template/index.js +91 -0
  14. package/dist/engine/_template/index.js.map +1 -0
  15. package/dist/engine/compiler/cache.d.ts +12 -0
  16. package/dist/engine/compiler/cache.d.ts.map +1 -0
  17. package/dist/engine/compiler/cache.js +75 -0
  18. package/dist/engine/compiler/cache.js.map +1 -0
  19. package/dist/engine/compiler/generate-director.d.ts +9 -0
  20. package/dist/engine/compiler/generate-director.d.ts.map +1 -0
  21. package/dist/engine/compiler/generate-director.js +57 -0
  22. package/dist/engine/compiler/generate-director.js.map +1 -0
  23. package/dist/engine/compiler/generate-fingerprint.d.ts +12 -0
  24. package/dist/engine/compiler/generate-fingerprint.d.ts.map +1 -0
  25. package/dist/engine/compiler/generate-fingerprint.js +86 -0
  26. package/dist/engine/compiler/generate-fingerprint.js.map +1 -0
  27. package/dist/engine/compiler/generate-milestones.d.ts +11 -0
  28. package/dist/engine/compiler/generate-milestones.d.ts.map +1 -0
  29. package/dist/engine/compiler/generate-milestones.js +121 -0
  30. package/dist/engine/compiler/generate-milestones.js.map +1 -0
  31. package/dist/engine/compiler/generate-politics.d.ts +11 -0
  32. package/dist/engine/compiler/generate-politics.d.ts.map +1 -0
  33. package/dist/engine/compiler/generate-politics.js +85 -0
  34. package/dist/engine/compiler/generate-politics.js.map +1 -0
  35. package/dist/engine/compiler/generate-progression.d.ts +15 -0
  36. package/dist/engine/compiler/generate-progression.d.ts.map +1 -0
  37. package/dist/engine/compiler/generate-progression.js +104 -0
  38. package/dist/engine/compiler/generate-progression.js.map +1 -0
  39. package/dist/engine/compiler/generate-prompts.d.ts +11 -0
  40. package/dist/engine/compiler/generate-prompts.d.ts.map +1 -0
  41. package/dist/engine/compiler/generate-prompts.js +90 -0
  42. package/dist/engine/compiler/generate-prompts.js.map +1 -0
  43. package/dist/engine/compiler/generate-reactions.d.ts +12 -0
  44. package/dist/engine/compiler/generate-reactions.d.ts.map +1 -0
  45. package/dist/engine/compiler/generate-reactions.js +90 -0
  46. package/dist/engine/compiler/generate-reactions.js.map +1 -0
  47. package/dist/engine/compiler/index.d.ts +34 -0
  48. package/dist/engine/compiler/index.d.ts.map +1 -0
  49. package/dist/engine/compiler/index.js +270 -0
  50. package/dist/engine/compiler/index.js.map +1 -0
  51. package/dist/engine/compiler/seed-ingestion.d.ts +40 -0
  52. package/dist/engine/compiler/seed-ingestion.d.ts.map +1 -0
  53. package/dist/engine/compiler/seed-ingestion.js +232 -0
  54. package/dist/engine/compiler/seed-ingestion.js.map +1 -0
  55. package/dist/engine/compiler/types.d.ts +30 -0
  56. package/dist/engine/compiler/types.d.ts.map +1 -0
  57. package/dist/engine/compiler/types.js +5 -0
  58. package/dist/engine/compiler/types.js.map +1 -0
  59. package/dist/engine/compiler/validate.d.ts +37 -0
  60. package/dist/engine/compiler/validate.d.ts.map +1 -0
  61. package/dist/engine/compiler/validate.js +141 -0
  62. package/dist/engine/compiler/validate.js.map +1 -0
  63. package/dist/engine/core/agent-generator.d.ts +11 -0
  64. package/dist/engine/core/agent-generator.d.ts.map +1 -0
  65. package/dist/engine/core/agent-generator.js +85 -0
  66. package/dist/engine/core/agent-generator.js.map +1 -0
  67. package/dist/engine/core/kernel.d.ts +69 -0
  68. package/dist/engine/core/kernel.d.ts.map +1 -0
  69. package/dist/engine/core/kernel.js +223 -0
  70. package/dist/engine/core/kernel.js.map +1 -0
  71. package/dist/engine/core/progression.d.ts +38 -0
  72. package/dist/engine/core/progression.d.ts.map +1 -0
  73. package/dist/engine/core/progression.js +292 -0
  74. package/dist/engine/core/progression.js.map +1 -0
  75. package/dist/engine/core/rng.d.ts +19 -0
  76. package/dist/engine/core/rng.d.ts.map +1 -0
  77. package/dist/engine/core/rng.js +34 -0
  78. package/dist/engine/core/rng.js.map +1 -0
  79. package/dist/engine/core/state.d.ts +144 -0
  80. package/dist/engine/core/state.d.ts.map +1 -0
  81. package/dist/engine/core/state.js +5 -0
  82. package/dist/engine/core/state.js.map +1 -0
  83. package/dist/engine/effect-registry.d.ts +14 -0
  84. package/dist/engine/effect-registry.d.ts.map +1 -0
  85. package/dist/engine/effect-registry.js +28 -0
  86. package/dist/engine/effect-registry.js.map +1 -0
  87. package/dist/engine/event-taxonomy.d.ts +9 -0
  88. package/dist/engine/event-taxonomy.d.ts.map +1 -0
  89. package/dist/engine/event-taxonomy.js +13 -0
  90. package/dist/engine/event-taxonomy.js.map +1 -0
  91. package/dist/engine/index.d.ts +25 -0
  92. package/dist/engine/index.d.ts.map +1 -0
  93. package/dist/engine/index.js +19 -0
  94. package/dist/engine/index.js.map +1 -0
  95. package/dist/engine/lunar/effects.d.ts +7 -0
  96. package/dist/engine/lunar/effects.d.ts.map +1 -0
  97. package/dist/engine/lunar/effects.js +16 -0
  98. package/dist/engine/lunar/effects.js.map +1 -0
  99. package/dist/engine/lunar/events.d.ts +3 -0
  100. package/dist/engine/lunar/events.d.ts.map +1 -0
  101. package/dist/engine/lunar/events.js +11 -0
  102. package/dist/engine/lunar/events.js.map +1 -0
  103. package/dist/engine/lunar/fingerprint.d.ts +5 -0
  104. package/dist/engine/lunar/fingerprint.d.ts.map +1 -0
  105. package/dist/engine/lunar/fingerprint.js +17 -0
  106. package/dist/engine/lunar/fingerprint.js.map +1 -0
  107. package/dist/engine/lunar/hooks.d.ts +10 -0
  108. package/dist/engine/lunar/hooks.d.ts.map +1 -0
  109. package/dist/engine/lunar/hooks.js +10 -0
  110. package/dist/engine/lunar/hooks.js.map +1 -0
  111. package/dist/engine/lunar/index.d.ts +8 -0
  112. package/dist/engine/lunar/index.d.ts.map +1 -0
  113. package/dist/engine/lunar/index.js +38 -0
  114. package/dist/engine/lunar/index.js.map +1 -0
  115. package/dist/engine/lunar/metrics.d.ts +4 -0
  116. package/dist/engine/lunar/metrics.d.ts.map +1 -0
  117. package/dist/engine/lunar/metrics.js +14 -0
  118. package/dist/engine/lunar/metrics.js.map +1 -0
  119. package/dist/engine/lunar/milestones.d.ts +3 -0
  120. package/dist/engine/lunar/milestones.d.ts.map +1 -0
  121. package/dist/engine/lunar/milestones.js +53 -0
  122. package/dist/engine/lunar/milestones.js.map +1 -0
  123. package/dist/engine/lunar/names.d.ts +4 -0
  124. package/dist/engine/lunar/names.d.ts.map +1 -0
  125. package/dist/engine/lunar/names.js +23 -0
  126. package/dist/engine/lunar/names.js.map +1 -0
  127. package/dist/engine/lunar/politics.d.ts +6 -0
  128. package/dist/engine/lunar/politics.d.ts.map +1 -0
  129. package/dist/engine/lunar/politics.js +13 -0
  130. package/dist/engine/lunar/politics.js.map +1 -0
  131. package/dist/engine/lunar/presets.d.ts +25 -0
  132. package/dist/engine/lunar/presets.d.ts.map +1 -0
  133. package/dist/engine/lunar/presets.js +24 -0
  134. package/dist/engine/lunar/presets.js.map +1 -0
  135. package/dist/engine/lunar/progression-hooks.d.ts +7 -0
  136. package/dist/engine/lunar/progression-hooks.d.ts.map +1 -0
  137. package/dist/engine/lunar/progression-hooks.js +20 -0
  138. package/dist/engine/lunar/progression-hooks.js.map +1 -0
  139. package/dist/engine/lunar/prompts.d.ts +10 -0
  140. package/dist/engine/lunar/prompts.d.ts.map +1 -0
  141. package/dist/engine/lunar/prompts.js +64 -0
  142. package/dist/engine/lunar/prompts.js.map +1 -0
  143. package/dist/engine/lunar/reactions.d.ts +5 -0
  144. package/dist/engine/lunar/reactions.d.ts.map +1 -0
  145. package/dist/engine/lunar/reactions.js +15 -0
  146. package/dist/engine/lunar/reactions.js.map +1 -0
  147. package/dist/engine/lunar/research-bundle.d.ts +8 -0
  148. package/dist/engine/lunar/research-bundle.d.ts.map +1 -0
  149. package/dist/engine/lunar/research-bundle.js +157 -0
  150. package/dist/engine/lunar/research-bundle.js.map +1 -0
  151. package/dist/engine/lunar/scenario.json +117 -0
  152. package/dist/engine/mars/effects.d.ts +18 -0
  153. package/dist/engine/mars/effects.d.ts.map +1 -0
  154. package/dist/engine/mars/effects.js +21 -0
  155. package/dist/engine/mars/effects.js.map +1 -0
  156. package/dist/engine/mars/events.d.ts +9 -0
  157. package/dist/engine/mars/events.d.ts.map +1 -0
  158. package/dist/engine/mars/events.js +12 -0
  159. package/dist/engine/mars/events.js.map +1 -0
  160. package/dist/engine/mars/fingerprint.d.ts +6 -0
  161. package/dist/engine/mars/fingerprint.d.ts.map +1 -0
  162. package/dist/engine/mars/fingerprint.js +21 -0
  163. package/dist/engine/mars/fingerprint.js.map +1 -0
  164. package/dist/engine/mars/hooks.d.ts +11 -0
  165. package/dist/engine/mars/hooks.d.ts.map +1 -0
  166. package/dist/engine/mars/hooks.js +11 -0
  167. package/dist/engine/mars/hooks.js.map +1 -0
  168. package/dist/engine/mars/index.d.ts +8 -0
  169. package/dist/engine/mars/index.d.ts.map +1 -0
  170. package/dist/engine/mars/index.js +44 -0
  171. package/dist/engine/mars/index.js.map +1 -0
  172. package/dist/engine/mars/metrics.d.ts +21 -0
  173. package/dist/engine/mars/metrics.d.ts.map +1 -0
  174. package/dist/engine/mars/metrics.js +25 -0
  175. package/dist/engine/mars/metrics.js.map +1 -0
  176. package/dist/engine/mars/milestones.d.ts +15 -0
  177. package/dist/engine/mars/milestones.d.ts.map +1 -0
  178. package/dist/engine/mars/milestones.js +72 -0
  179. package/dist/engine/mars/milestones.js.map +1 -0
  180. package/dist/engine/mars/names.d.ts +12 -0
  181. package/dist/engine/mars/names.d.ts.map +1 -0
  182. package/dist/engine/mars/names.js +45 -0
  183. package/dist/engine/mars/names.js.map +1 -0
  184. package/dist/engine/mars/politics.d.ts +7 -0
  185. package/dist/engine/mars/politics.d.ts.map +1 -0
  186. package/dist/engine/mars/politics.js +14 -0
  187. package/dist/engine/mars/politics.js.map +1 -0
  188. package/dist/engine/mars/presets.d.ts +29 -0
  189. package/dist/engine/mars/presets.d.ts.map +1 -0
  190. package/dist/engine/mars/presets.js +28 -0
  191. package/dist/engine/mars/presets.js.map +1 -0
  192. package/dist/engine/mars/progression-hooks.d.ts +8 -0
  193. package/dist/engine/mars/progression-hooks.d.ts.map +1 -0
  194. package/dist/engine/mars/progression-hooks.js +21 -0
  195. package/dist/engine/mars/progression-hooks.js.map +1 -0
  196. package/dist/engine/mars/prompts.d.ts +12 -0
  197. package/dist/engine/mars/prompts.d.ts.map +1 -0
  198. package/dist/engine/mars/prompts.js +75 -0
  199. package/dist/engine/mars/prompts.js.map +1 -0
  200. package/dist/engine/mars/reactions.d.ts +7 -0
  201. package/dist/engine/mars/reactions.d.ts.map +1 -0
  202. package/dist/engine/mars/reactions.js +24 -0
  203. package/dist/engine/mars/reactions.js.map +1 -0
  204. package/dist/engine/mars/research-bundle.d.ts +7 -0
  205. package/dist/engine/mars/research-bundle.d.ts.map +1 -0
  206. package/dist/engine/mars/research-bundle.js +174 -0
  207. package/dist/engine/mars/research-bundle.js.map +1 -0
  208. package/dist/engine/mars/scenario.json +118 -0
  209. package/dist/engine/metric-registry.d.ts +12 -0
  210. package/dist/engine/metric-registry.d.ts.map +1 -0
  211. package/dist/engine/metric-registry.js +26 -0
  212. package/dist/engine/metric-registry.js.map +1 -0
  213. package/dist/engine/types.d.ts +349 -0
  214. package/dist/engine/types.d.ts.map +1 -0
  215. package/dist/engine/types.js +7 -0
  216. package/dist/engine/types.js.map +1 -0
  217. package/dist/runtime/agent-memory.d.ts +43 -0
  218. package/dist/runtime/agent-memory.d.ts.map +1 -0
  219. package/dist/runtime/agent-memory.js +201 -0
  220. package/dist/runtime/agent-memory.js.map +1 -0
  221. package/dist/runtime/agent-reactions.d.ts +55 -0
  222. package/dist/runtime/agent-reactions.d.ts.map +1 -0
  223. package/dist/runtime/agent-reactions.js +136 -0
  224. package/dist/runtime/agent-reactions.js.map +1 -0
  225. package/dist/runtime/batch.d.ts +43 -0
  226. package/dist/runtime/batch.d.ts.map +1 -0
  227. package/dist/runtime/batch.js +49 -0
  228. package/dist/runtime/batch.js.map +1 -0
  229. package/dist/runtime/contracts.d.ts +127 -0
  230. package/dist/runtime/contracts.d.ts.map +1 -0
  231. package/dist/runtime/contracts.js +2 -0
  232. package/dist/runtime/contracts.js.map +1 -0
  233. package/dist/runtime/departments.d.ts +28 -0
  234. package/dist/runtime/departments.d.ts.map +1 -0
  235. package/dist/runtime/departments.js +111 -0
  236. package/dist/runtime/departments.js.map +1 -0
  237. package/dist/runtime/director.d.ts +92 -0
  238. package/dist/runtime/director.d.ts.map +1 -0
  239. package/dist/runtime/director.js +220 -0
  240. package/dist/runtime/director.js.map +1 -0
  241. package/dist/runtime/index.d.ts +16 -0
  242. package/dist/runtime/index.d.ts.map +1 -0
  243. package/dist/runtime/index.js +12 -0
  244. package/dist/runtime/index.js.map +1 -0
  245. package/dist/runtime/orchestrator.d.ts +66 -0
  246. package/dist/runtime/orchestrator.d.ts.map +1 -0
  247. package/dist/runtime/orchestrator.js +821 -0
  248. package/dist/runtime/orchestrator.js.map +1 -0
  249. package/dist/runtime/research/knowledge-base.d.ts +14 -0
  250. package/dist/runtime/research/knowledge-base.d.ts.map +1 -0
  251. package/dist/runtime/research/knowledge-base.js +229 -0
  252. package/dist/runtime/research/knowledge-base.js.map +1 -0
  253. package/dist/runtime/research/research-memory.d.ts +21 -0
  254. package/dist/runtime/research/research-memory.d.ts.map +1 -0
  255. package/dist/runtime/research/research-memory.js +118 -0
  256. package/dist/runtime/research/research-memory.js.map +1 -0
  257. package/dist/runtime/research/research.d.ts +4 -0
  258. package/dist/runtime/research/research.d.ts.map +1 -0
  259. package/dist/runtime/research/research.js +138 -0
  260. package/dist/runtime/research/research.js.map +1 -0
  261. package/dist/runtime/research/scenario-research.d.ts +13 -0
  262. package/dist/runtime/research/scenario-research.d.ts.map +1 -0
  263. package/dist/runtime/research/scenario-research.js +58 -0
  264. package/dist/runtime/research/scenario-research.js.map +1 -0
  265. package/dist/runtime/research/scenarios.d.ts +3 -0
  266. package/dist/runtime/research/scenarios.d.ts.map +1 -0
  267. package/dist/runtime/research/scenarios.js +252 -0
  268. package/dist/runtime/research/scenarios.js.map +1 -0
  269. package/dist/runtime/runtime-helpers.d.ts +12 -0
  270. package/dist/runtime/runtime-helpers.d.ts.map +1 -0
  271. package/dist/runtime/runtime-helpers.js +33 -0
  272. package/dist/runtime/runtime-helpers.js.map +1 -0
  273. 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