miravo 0.6.0 → 0.6.2

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 (187) hide show
  1. package/LICENSE +199 -0
  2. package/README.md +29 -59
  3. package/bin/miravo.js +84 -0
  4. package/package.json +21 -37
  5. package/console-dist/assets/activity-DsWeRq4h.js +0 -1
  6. package/console-dist/assets/api-4AqKvsm9.js +0 -1
  7. package/console-dist/assets/arrow-right-Ea_UhTNi.js +0 -1
  8. package/console-dist/assets/arrow-up-down-DFSjzzl2.js +0 -1
  9. package/console-dist/assets/blueprint-card-classes-DBFCDy0M.js +0 -1
  10. package/console-dist/assets/box-C1WaoOMF.js +0 -1
  11. package/console-dist/assets/card-BjeP32PN.js +0 -1
  12. package/console-dist/assets/catalog-BJzyzbLR.js +0 -1
  13. package/console-dist/assets/catalog-DKjEm-c4.js +0 -1
  14. package/console-dist/assets/catalog-template-icons-BrUeHaCY.js +0 -1
  15. package/console-dist/assets/checkbox-DZlJXTE4.js +0 -1
  16. package/console-dist/assets/chunk-OE4NN4TA-CEjpfhS6.js +0 -3
  17. package/console-dist/assets/combobox-BGnuCYxO.js +0 -1
  18. package/console-dist/assets/control-CPhvwx9o.js +0 -1
  19. package/console-dist/assets/control-Cg2up9NA.js +0 -1
  20. package/console-dist/assets/copy-button-WkbNkMcd.js +0 -1
  21. package/console-dist/assets/createLucideIcon-Bwxw859K.js +0 -1
  22. package/console-dist/assets/deploy-tab-JeWDwPoZ.js +0 -1
  23. package/console-dist/assets/dist-BhbDML0a.js +0 -1
  24. package/console-dist/assets/dist-Cy8XTNFT.js +0 -2
  25. package/console-dist/assets/download-DQ6NH16U.js +0 -1
  26. package/console-dist/assets/engine-store-CrLnz692.js +0 -1
  27. package/console-dist/assets/events-CXm29Ok4.js +0 -1
  28. package/console-dist/assets/events-D01IxhnP.js +0 -1
  29. package/console-dist/assets/fira-code-cyrillic-ext-wght-normal-DhYMMuQd.woff2 +0 -0
  30. package/console-dist/assets/fira-code-cyrillic-wght-normal-Y3u8pIsh.woff2 +0 -0
  31. package/console-dist/assets/fira-code-greek-ext-wght-normal-wWus70Ix.woff2 +0 -0
  32. package/console-dist/assets/fira-code-greek-wght-normal-B2SviObF.woff2 +0 -0
  33. package/console-dist/assets/fira-code-latin-ext-wght-normal-Dvfvaomy.woff2 +0 -0
  34. package/console-dist/assets/fira-code-latin-wght-normal-CHoedHDv.woff2 +0 -0
  35. package/console-dist/assets/fira-code-symbols2-wght-normal-CE6EOz_n.woff2 +0 -0
  36. package/console-dist/assets/fleet-CfxbKXt9.js +0 -1
  37. package/console-dist/assets/fleet-lotMGVqe.js +0 -1
  38. package/console-dist/assets/format-kXTonpDJ.js +0 -1
  39. package/console-dist/assets/gauge-Ci8zNZlk.js +0 -1
  40. package/console-dist/assets/get-error-message-3Q5Q-2G5.js +0 -1
  41. package/console-dist/assets/index-CVPKSDvJ.js +0 -12
  42. package/console-dist/assets/index-DJkgDeun.css +0 -2
  43. package/console-dist/assets/input-BWEQFptx.js +0 -1
  44. package/console-dist/assets/instance-detail-C5IVwuh3.js +0 -1
  45. package/console-dist/assets/karla-latin-ext-wght-normal-Dq0rrjgs.woff2 +0 -0
  46. package/console-dist/assets/karla-latin-wght-normal-C3-ma4ov.woff2 +0 -0
  47. package/console-dist/assets/label-uephX5ZG.js +0 -1
  48. package/console-dist/assets/layers-CUsLQ1fq.js +0 -1
  49. package/console-dist/assets/login-C3RJli0m.js +0 -126
  50. package/console-dist/assets/models-B_UioWQ5.js +0 -1
  51. package/console-dist/assets/models-_IPlPiZ7.js +0 -1
  52. package/console-dist/assets/not-found-B8gAm08S.js +0 -1
  53. package/console-dist/assets/platform-targets-R3T-4eJ7.js +0 -1
  54. package/console-dist/assets/platforms-BWh0BQ3Y.js +0 -1
  55. package/console-dist/assets/platforms-DStNbtVc.js +0 -1
  56. package/console-dist/assets/proxy-CsnYqnPp.js +0 -9
  57. package/console-dist/assets/query-keys-BvtE7BMs.js +0 -1
  58. package/console-dist/assets/radio-CoHegOv5.js +0 -1
  59. package/console-dist/assets/rolldown-runtime-COnpUsM8.js +0 -1
  60. package/console-dist/assets/schibsted-grotesk-latin-ext-wght-normal-hsMS0n0O.woff2 +0 -0
  61. package/console-dist/assets/schibsted-grotesk-latin-wght-normal-Bb8VGrTG.woff2 +0 -0
  62. package/console-dist/assets/select-CeO4vP0G.js +0 -1
  63. package/console-dist/assets/select-display-items-CIzY8953.js +0 -1
  64. package/console-dist/assets/server-D9Kfn8fQ.js +0 -1
  65. package/console-dist/assets/shallow-6lGjQhtJ.js +0 -1
  66. package/console-dist/assets/tags-CiXOIHtY.js +0 -4
  67. package/console-dist/assets/tags-FQCXIxrP.js +0 -1
  68. package/console-dist/assets/tooltip-HGLF366Q.js +0 -1
  69. package/console-dist/assets/transition-nav-link-Df4Rk6vV.js +0 -1
  70. package/console-dist/assets/transition-types-CPlGtM8w.js +0 -1
  71. package/console-dist/assets/triangle-alert-adNk_sea.js +0 -1
  72. package/console-dist/assets/use-platform-stream-DGbK1k2L.js +0 -2
  73. package/console-dist/assets/use-transition-navigate-CCpcWXB0.js +0 -2
  74. package/console-dist/assets/validation-Blvud9_O.js +0 -1
  75. package/console-dist/assets/vendor-charts-CAZdaPlq.js +0 -60
  76. package/console-dist/assets/vendor-table-wd8JuMmT.js +0 -4
  77. package/console-dist/assets/vendor-ui-ByahnaY0.js +0 -1
  78. package/console-dist/favicon.svg +0 -37
  79. package/console-dist/index.html +0 -82
  80. package/console-dist/miravo-logo.png +0 -0
  81. package/src/__tests__/integration/executable-methods-runner.ts +0 -262
  82. package/src/__tests__/integration/executable-methods.integration.ts +0 -2348
  83. package/src/__tests__/integration/opcua-concurrent-clients.integration.ts +0 -314
  84. package/src/commands/adapter.ts +0 -249
  85. package/src/commands/apply.test.ts +0 -62
  86. package/src/commands/apply.ts +0 -216
  87. package/src/commands/cleanup.test.ts +0 -34
  88. package/src/commands/cleanup.ts +0 -201
  89. package/src/commands/cli-test-setup.ts +0 -91
  90. package/src/commands/export.test.ts +0 -51
  91. package/src/commands/export.ts +0 -404
  92. package/src/commands/fork.ts +0 -219
  93. package/src/commands/inject.test.ts +0 -57
  94. package/src/commands/inject.ts +0 -212
  95. package/src/commands/load.ts +0 -167
  96. package/src/commands/logs.ts +0 -173
  97. package/src/commands/models.test.ts +0 -162
  98. package/src/commands/models.ts +0 -338
  99. package/src/commands/pause.ts +0 -67
  100. package/src/commands/platforms.test.ts +0 -186
  101. package/src/commands/platforms.ts +0 -371
  102. package/src/commands/ps.ts +0 -116
  103. package/src/commands/remove.ts +0 -38
  104. package/src/commands/reset.ts +0 -44
  105. package/src/commands/rm.ts +0 -97
  106. package/src/commands/spawn.ts +0 -45
  107. package/src/commands/speed.ts +0 -40
  108. package/src/commands/start/adapter-config.ts +0 -157
  109. package/src/commands/start/catalog-le-dt.test.ts +0 -50
  110. package/src/commands/start/catalog-le-dt.ts +0 -187
  111. package/src/commands/start/catalog-model-schema.ts +0 -89
  112. package/src/commands/start/catalog-template-detail.ts +0 -54
  113. package/src/commands/start/daemon.ts +0 -255
  114. package/src/commands/start/display.ts +0 -150
  115. package/src/commands/start/engine-setup.ts +0 -247
  116. package/src/commands/start/events.ts +0 -126
  117. package/src/commands/start/index.ts +0 -897
  118. package/src/commands/start/protocol-plans.ts +0 -239
  119. package/src/commands/start/template-loader.ts +0 -197
  120. package/src/commands/start/template-resolver.ts +0 -185
  121. package/src/commands/start/types.ts +0 -73
  122. package/src/commands/start.test.ts +0 -480
  123. package/src/commands/status.test.ts +0 -20
  124. package/src/commands/status.ts +0 -470
  125. package/src/commands/stop.test.ts +0 -84
  126. package/src/commands/stop.ts +0 -169
  127. package/src/commands/templates.test.ts +0 -58
  128. package/src/commands/templates.ts +0 -277
  129. package/src/index.ts +0 -166
  130. package/src/lib/admin-auth.ts +0 -157
  131. package/src/lib/admin-client.test.ts +0 -51
  132. package/src/lib/admin-client.ts +0 -326
  133. package/src/lib/admin-platform-routes.ts +0 -1017
  134. package/src/lib/admin-platform-stream.test.ts +0 -434
  135. package/src/lib/admin-platform-stream.ts +0 -788
  136. package/src/lib/admin-responses.ts +0 -211
  137. package/src/lib/admin-server.test.ts +0 -781
  138. package/src/lib/admin-server.ts +0 -797
  139. package/src/lib/auth-store.ts +0 -87
  140. package/src/lib/daemon-registry.test.ts +0 -429
  141. package/src/lib/daemon-registry.ts +0 -343
  142. package/src/lib/le/le-apply.test.ts +0 -591
  143. package/src/lib/le/le-apply.ts +0 -832
  144. package/src/lib/le/le-auth.ts +0 -89
  145. package/src/lib/le/le-cleanup.test.ts +0 -168
  146. package/src/lib/le/le-cleanup.ts +0 -399
  147. package/src/lib/le/le-cli-helpers.ts +0 -77
  148. package/src/lib/le/le-client.ts +0 -184
  149. package/src/lib/le/le-concurrency.test.ts +0 -157
  150. package/src/lib/le/le-concurrency.ts +0 -78
  151. package/src/lib/le/le-connection-test.ts +0 -42
  152. package/src/lib/le/le-devicehub-properties.test.ts +0 -53
  153. package/src/lib/le/le-devicehub-properties.ts +0 -33
  154. package/src/lib/le/le-devicehub.ts +0 -213
  155. package/src/lib/le/le-digital-twins.ts +0 -285
  156. package/src/lib/le/le-drivers.ts +0 -23
  157. package/src/lib/le/le-endpoint.ts +0 -40
  158. package/src/lib/le/le-environments.test.ts +0 -73
  159. package/src/lib/le/le-environments.ts +0 -156
  160. package/src/lib/le/le-markers.ts +0 -87
  161. package/src/lib/le/le-plan.test.ts +0 -26
  162. package/src/lib/le/le-plan.ts +0 -109
  163. package/src/lib/le/le-platform-operations.test.ts +0 -198
  164. package/src/lib/le/le-platform-operations.ts +0 -340
  165. package/src/lib/le/le-static-model-schema.ts +0 -78
  166. package/src/lib/le/le-static-planner.test.ts +0 -255
  167. package/src/lib/le/le-static-planner.ts +0 -442
  168. package/src/lib/le/le-template-exporter.test.ts +0 -527
  169. package/src/lib/le/le-template-exporter.ts +0 -459
  170. package/src/lib/le/le-value-types.ts +0 -23
  171. package/src/lib/mqtt-client.ts +0 -115
  172. package/src/lib/parse.test.ts +0 -123
  173. package/src/lib/parse.ts +0 -87
  174. package/src/lib/resolve-simulation.test.ts +0 -118
  175. package/src/lib/resolve-simulation.ts +0 -181
  176. package/src/lib/session-store.ts +0 -110
  177. package/src/lib/shutdown.test.ts +0 -562
  178. package/src/lib/shutdown.ts +0 -621
  179. package/src/lib/template-category.test.ts +0 -71
  180. package/src/lib/template-category.ts +0 -72
  181. package/src/lib/template-locator.ts +0 -55
  182. package/src/lib/template-utils.ts +0 -45
  183. package/src/lib/ws-hub.ts +0 -281
  184. package/src/logger.test.ts +0 -172
  185. package/src/logger.ts +0 -402
  186. package/src/output.test.ts +0 -64
  187. package/src/output.ts +0 -160
@@ -1,247 +0,0 @@
1
- import { resolve } from "node:path";
2
- import type { ContentCatalog, Engine } from "@miravo/core";
3
- import {
4
- createEngine,
5
- loadTemplate,
6
- loadTwinModel,
7
- resolveModelPath,
8
- validateFullTemplate,
9
- } from "@miravo/core";
10
- import type { MiravoTemplate, TwinModel } from "@miravo/shared";
11
- import {
12
- ConfigValidationError,
13
- DEFAULT_TICK_INTERVAL_MS,
14
- DEFAULT_TIME_MULTIPLIER,
15
- SimulationNameSchema,
16
- } from "@miravo/shared";
17
- import {
18
- buildUNSBasePath,
19
- collectReferencedModels,
20
- forEachTwin,
21
- } from "../../lib/template-utils.ts";
22
- import { configLog } from "../../logger.ts";
23
- import {
24
- getCommandOptionSource,
25
- reconcileProtocolBootPlan,
26
- resolveProtocolBootPlans,
27
- } from "./protocol-plans.ts";
28
- import { loadTemplateModels, registerModelFromSearchPaths } from "./template-loader.ts";
29
- import type { StartOptions } from "./types.ts";
30
-
31
- // ---------------------------------------------------------------------------
32
- // Step 2: Load and validate
33
- // ---------------------------------------------------------------------------
34
-
35
- export async function loadAndValidateTemplate(
36
- templatePath: string,
37
- options: StartOptions,
38
- catalog: ContentCatalog,
39
- ): Promise<{
40
- template: MiravoTemplate;
41
- models: Map<string, TwinModel>;
42
- seed: number;
43
- seedExplicit: boolean;
44
- }> {
45
- const template = await loadTemplate(templatePath);
46
-
47
- // Build model search paths from catalog
48
- const catalogSearchPaths = catalog.getTemplateValidationSearchPaths(templatePath);
49
- const searchPaths = [...catalogSearchPaths];
50
- if (options.modelsDir) {
51
- // Insert after template dir but before other paths
52
- searchPaths.splice(1, 0, resolve(options.modelsDir));
53
- }
54
- const uniqueSearchPaths = [...new Set(searchPaths.map((path) => resolve(path)))];
55
-
56
- // Cross-validate
57
- const validation = await validateFullTemplate(templatePath, uniqueSearchPaths);
58
- for (const issue of validation.issues) {
59
- if (issue.severity === "error") {
60
- configLog.error(`${issue.message}\n Fix: ${issue.fix}`);
61
- } else {
62
- configLog.warn(issue.message);
63
- }
64
- }
65
- if (!validation.valid) {
66
- throw new ConfigValidationError({
67
- message: "Template validation failed. See errors above.",
68
- fix: "Fix the reported issues and try again.",
69
- filePath: templatePath,
70
- });
71
- }
72
-
73
- // Build inline models map
74
- const models = new Map<string, TwinModel>();
75
- if (template.models) {
76
- for (const inlineModel of template.models) {
77
- models.set(inlineModel.model.name, inlineModel);
78
- }
79
- }
80
-
81
- // Resolve and load all referenced models not already inline
82
- const referencedModelNames = collectReferencedModels(template);
83
-
84
- for (const modelName of referencedModelNames) {
85
- if (models.has(modelName)) continue;
86
- const modelPath = await resolveModelPath(modelName, uniqueSearchPaths);
87
- const model = await loadTwinModel(modelPath);
88
- models.set(modelName, model);
89
- }
90
-
91
- // Resolve seed: CLI flag > config setting > random
92
- const configSeed = template.settings?.seed;
93
- const cliSeed = options.seed;
94
- const seedExplicit = cliSeed !== undefined || configSeed !== undefined;
95
- const seed = cliSeed ?? configSeed ?? Date.now();
96
-
97
- return { template, models, seed, seedExplicit };
98
- }
99
-
100
- export function resolveSimulationName(input: {
101
- readonly template: MiravoTemplate;
102
- readonly cliName?: string;
103
- readonly envName?: string;
104
- }): string {
105
- const { template, cliName, envName } = input;
106
-
107
- const nameFromNamespace = (): string | undefined => {
108
- const namespace = template.namespace;
109
- if (!namespace) return undefined;
110
- if ("enterprise" in namespace) {
111
- return `${namespace.enterprise}-${namespace.site}`.toLowerCase();
112
- }
113
- return namespace.prefix.toLowerCase();
114
- };
115
-
116
- const rawName = cliName ?? envName ?? nameFromNamespace();
117
- if (!rawName) {
118
- throw new ConfigValidationError({
119
- message: "Template has no namespace.",
120
- fix: "Provide a simulation name via --name or MIRAVO_NAME.",
121
- });
122
- }
123
-
124
- const parsed = SimulationNameSchema.safeParse(rawName);
125
- if (!parsed.success) {
126
- throw new ConfigValidationError({
127
- message: `Invalid simulation name "${rawName}".`,
128
- fix: "Use lowercase letters, numbers, and hyphens only (for example: north-plant).",
129
- });
130
- }
131
-
132
- return parsed.data;
133
- }
134
-
135
- // ---------------------------------------------------------------------------
136
- // Step 4: Engine creation
137
- // ---------------------------------------------------------------------------
138
-
139
- export function createAndPopulateEngine(
140
- template: MiravoTemplate,
141
- models: Map<string, TwinModel>,
142
- catalog: ContentCatalog,
143
- seed: number,
144
- options: StartOptions,
145
- templatePath: string,
146
- simulationName: string,
147
- ): Engine {
148
- const tickIntervalMs =
149
- options.tick ?? template.settings?.tick_interval_ms ?? DEFAULT_TICK_INTERVAL_MS;
150
- const timeMultiplier =
151
- options.speed ?? template.settings?.time_multiplier ?? DEFAULT_TIME_MULTIPLIER;
152
- const overrunThresholdMultiplier = template.settings?.overrun_threshold_multiplier;
153
- const searchPaths = catalog.getTemplateValidationSearchPaths(templatePath);
154
-
155
- let engine!: Engine;
156
- engine = createEngine({
157
- simulationName,
158
- seed,
159
- tickIntervalMs,
160
- timeMultiplier,
161
- overrunThresholdMultiplier,
162
- templateMetadata: {
163
- name: template.name,
164
- path: templatePath,
165
- },
166
- beforeRun: async ({ restoredFromPersistence, persistedDisabledAdapters }) => {
167
- const protocolBootPlans = resolveProtocolBootPlans({
168
- options,
169
- optionSources: {
170
- mqtt: getCommandOptionSource(options.command, "mqtt"),
171
- opcua: getCommandOptionSource(options.command, "opcua"),
172
- mqttPort: getCommandOptionSource(options.command, "mqttPort"),
173
- mqttHost: getCommandOptionSource(options.command, "mqttHost"),
174
- mqttMode: getCommandOptionSource(options.command, "mqttMode"),
175
- mqttUrl: getCommandOptionSource(options.command, "mqttUrl"),
176
- mqttUsername: getCommandOptionSource(options.command, "mqttUsername"),
177
- mqttPassword: getCommandOptionSource(options.command, "mqttPassword"),
178
- opcuaPort: getCommandOptionSource(options.command, "opcuaPort"),
179
- opcuaHost: getCommandOptionSource(options.command, "opcuaHost"),
180
- modbus: getCommandOptionSource(options.command, "modbus"),
181
- modbusPort: getCommandOptionSource(options.command, "modbusPort"),
182
- modbusHost: getCommandOptionSource(options.command, "modbusHost"),
183
- },
184
- restoredFromPersistence,
185
- persistedDisabledAdapters,
186
- templateProtocols: template.protocols,
187
- });
188
-
189
- for (const plan of protocolBootPlans) {
190
- await reconcileProtocolBootPlan(engine, plan);
191
- }
192
- },
193
- persistence: {
194
- enabled: true,
195
- resetOnStart: options.resetState ?? false,
196
- ensureModelRegistered: (modelName) =>
197
- registerModelFromSearchPaths(engine.runtime, modelName, searchPaths),
198
- loadTemplateModels: (persistedTemplatePath) => {
199
- loadTemplateModels(catalog, persistedTemplatePath, engine.runtime);
200
- },
201
- },
202
- });
203
-
204
- // Register template-referenced models
205
- for (const [, model] of models) {
206
- engine.runtime.registerModel(model);
207
- }
208
-
209
- // Register all builtin models so they're available for ad-hoc spawning
210
- const builtinDir = catalog.getBuiltinModelsDir();
211
- for (const path of new Bun.Glob("*.twin.yaml").scanSync({ cwd: builtinDir })) {
212
- const name = path.replace(".twin.yaml", "");
213
- if (!engine.runtime.getModel(name)) {
214
- registerModelFromSearchPaths(engine.runtime, name, searchPaths);
215
- }
216
- }
217
-
218
- // Spawn instances from template with boot-time run ID
219
- const bootRunId = 1;
220
- const allInstanceIds: string[] = [];
221
-
222
- forEachTwin(template, (area, line, twin) => {
223
- const unsBasePath = buildUNSBasePath(template, area.name, line.name);
224
- const spawned = engine.runtime.spawn(twin.model, twin.count, {
225
- variation: twin.variation,
226
- unsBasePath,
227
- ...(twin.parameters !== undefined ? { parameterOverrides: twin.parameters } : {}),
228
- ...(twin.starting_hours !== undefined ? { startingHours: twin.starting_hours } : {}),
229
- templateRunId: bootRunId,
230
- });
231
- for (const node of spawned) {
232
- allInstanceIds.push(node.id);
233
- }
234
- });
235
-
236
- // Register boot-time template as a template run for persistence/unload support
237
- engine.registerTemplateRun({
238
- templateRunId: bootRunId,
239
- templateName: template.name,
240
- templatePath,
241
- prefix: "boot",
242
- instanceIds: allInstanceIds,
243
- models: [...models.keys()],
244
- });
245
-
246
- return engine;
247
- }
@@ -1,126 +0,0 @@
1
- import type { Engine } from "@miravo/core";
2
- import { colors } from "consola/utils";
3
- import { printSummaryLine } from "../../lib/shutdown.ts";
4
- import { engineLog, faultLog, lifecycleLog } from "../../logger.ts";
5
-
6
- /**
7
- * Shorten a full instance ID path to the last two segments for terminal output.
8
- * E.g. "area-1/line-2/pump-003" → "line-2/pump-003".
9
- * Full IDs can be 40+ chars with deep area/line/twin hierarchies.
10
- */
11
- function shortId(instanceId: string): string {
12
- const parts = instanceId.split("/");
13
- return parts.length > 2 ? parts.slice(-2).join("/") : instanceId;
14
- }
15
-
16
- // ---------------------------------------------------------------------------
17
- // Step 7: Event subscriptions + periodic summary
18
- // ---------------------------------------------------------------------------
19
-
20
- const MAX_ERROR_CONTEXT_KEYS = 256;
21
- const ERROR_CONTEXT_OVERFLOW = "__overflow__";
22
-
23
- export function subscribeToEvents(
24
- engine: Engine,
25
- errorContextCounts: Map<string, number>,
26
- ): () => void {
27
- const onLifecycle = (event: {
28
- instanceId: string;
29
- from: string;
30
- to: string;
31
- operatingHours: number;
32
- }): void => {
33
- lifecycleLog.info(
34
- `${shortId(event.instanceId)} ${event.from} ${colors.dim("\u2192")} ${event.to} ${colors.gray(`(${event.operatingHours.toFixed(0)}h)`)}`,
35
- );
36
- };
37
-
38
- const onFaultTriggered = (event: { instanceId: string; fault: string }): void => {
39
- faultLog.warn(`${shortId(event.instanceId)} ${colors.bold(event.fault)} triggered`);
40
- };
41
-
42
- const onFaultCleared = (event: { instanceId: string; fault: string }): void => {
43
- faultLog.info(`${shortId(event.instanceId)} ${colors.dim(event.fault)} cleared`);
44
- };
45
-
46
- const onWarn = (event: { message: string; context: string }): void => {
47
- engineLog.debug(event.message, { context: event.context });
48
- };
49
-
50
- const onReset = (event: {
51
- instanceCount: number;
52
- templateRunCount: number;
53
- clearPersistence: boolean;
54
- }): void => {
55
- const suffix = event.clearPersistence ? " · saved state cleared" : "";
56
- engineLog.info(
57
- `simulation reset · ${colors.bold(String(event.instanceCount))} instances · ${colors.bold(String(event.templateRunCount))} template runs${suffix}`,
58
- );
59
- };
60
-
61
- const onError = (event: { context: string; error: Error }): void => {
62
- incrementErrorContextCount(errorContextCounts, event.context);
63
- engineLog.error(event.error.message, { context: event.context }, event.error);
64
- };
65
-
66
- const onStateChanged = (event: {
67
- from: "idle" | "running" | "paused" | "stopped";
68
- to: "idle" | "running" | "paused" | "stopped";
69
- }): void => {
70
- if (event.to === "paused") {
71
- engineLog.info(`simulation paused`);
72
- } else if (event.to === "running" && event.from === "paused") {
73
- engineLog.info(`simulation resumed`);
74
- }
75
- };
76
-
77
- engine.bus.on("lifecycle:changed", onLifecycle);
78
- engine.bus.on("fault:triggered", onFaultTriggered);
79
- engine.bus.on("fault:cleared", onFaultCleared);
80
- engine.bus.on("engine:warn", onWarn);
81
- engine.bus.on("engine:error", onError);
82
- engine.bus.on("engine:reset", onReset);
83
- engine.bus.on("engine:state-changed", onStateChanged);
84
-
85
- return () => {
86
- engine.bus.off("lifecycle:changed", onLifecycle);
87
- engine.bus.off("fault:triggered", onFaultTriggered);
88
- engine.bus.off("fault:cleared", onFaultCleared);
89
- engine.bus.off("engine:warn", onWarn);
90
- engine.bus.off("engine:error", onError);
91
- engine.bus.off("engine:reset", onReset);
92
- engine.bus.off("engine:state-changed", onStateChanged);
93
- };
94
- }
95
-
96
- export function normalizeErrorContext(raw: string): string {
97
- return raw
98
- .replace(/tick=\d+/g, "tick=*")
99
- .replace(/instance="[^"]+"/g, 'instance="*"')
100
- .replace(/model="[^"]+"/g, 'model="*"');
101
- }
102
-
103
- export function incrementErrorContextCount(counts: Map<string, number>, rawContext: string): void {
104
- const normalizedContext = normalizeErrorContext(rawContext);
105
- if (counts.has(normalizedContext)) {
106
- counts.set(normalizedContext, (counts.get(normalizedContext) ?? 0) + 1);
107
- return;
108
- }
109
-
110
- if (counts.size < MAX_ERROR_CONTEXT_KEYS) {
111
- counts.set(normalizedContext, 1);
112
- return;
113
- }
114
-
115
- counts.set(ERROR_CONTEXT_OVERFLOW, (counts.get(ERROR_CONTEXT_OVERFLOW) ?? 0) + 1);
116
- }
117
-
118
- export function startPeriodicSummary(
119
- engine: Engine,
120
- intervalSeconds: number,
121
- ): ReturnType<typeof setInterval> {
122
- return setInterval(() => {
123
- const metrics = engine.getMetrics();
124
- printSummaryLine(metrics);
125
- }, intervalSeconds * 1_000);
126
- }