@mindot/will 0.1.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 +202 -0
- package/README.md +777 -0
- package/dist/index.d.ts +8144 -0
- package/dist/index.js +26231 -0
- package/dist/index.js.map +1 -0
- package/package.json +73 -0
- package/src/cognition/agency/access.grants.ts +59 -0
- package/src/cognition/agency/competence.codec.ts +89 -0
- package/src/cognition/agency/engines/action.selector.ts +462 -0
- package/src/cognition/agency/engines/affordance.synthesizer.ts +384 -0
- package/src/cognition/agency/engines/deliberation.engine.ts +241 -0
- package/src/cognition/agency/engines/instruction.intake.ts +54 -0
- package/src/cognition/agency/engines/motor.schema.executor.ts +532 -0
- package/src/cognition/agency/engines/reafference.engine.ts +241 -0
- package/src/cognition/agency/execution.primitives.ts +118 -0
- package/src/cognition/agency/index.ts +58 -0
- package/src/cognition/agency/proactive.communicator.ts +274 -0
- package/src/cognition/agency/reconcile.learning.ts +82 -0
- package/src/cognition/agency/schemas/external.ts +59 -0
- package/src/cognition/agency/schemas/innate.ts +111 -0
- package/src/cognition/agency/schemas/repertoire.ts +246 -0
- package/src/cognition/agency/selection.scoring.ts +191 -0
- package/src/cognition/agency/types.ts +165 -0
- package/src/cognition/bus.ts +391 -0
- package/src/cognition/completion.inbox.ts +86 -0
- package/src/cognition/config.mirror.entities.ts +475 -0
- package/src/cognition/conversation.memory.ts +81 -0
- package/src/cognition/event.log.ts +116 -0
- package/src/cognition/event.schemas.ts +568 -0
- package/src/cognition/faculties/aesthetic.evaluator.ts +313 -0
- package/src/cognition/faculties/affective.blender.ts +562 -0
- package/src/cognition/faculties/attachment.evaluator.ts +432 -0
- package/src/cognition/faculties/attention.allocator.ts +422 -0
- package/src/cognition/faculties/autobiographical.narrator.ts +300 -0
- package/src/cognition/faculties/bias.detector.ts +363 -0
- package/src/cognition/faculties/circadian.oscillator.ts +294 -0
- package/src/cognition/faculties/confidence.calibrator.ts +324 -0
- package/src/cognition/faculties/dream.simulator.ts +251 -0
- package/src/cognition/faculties/empathy.simulator.ts +253 -0
- package/src/cognition/faculties/energy.regulator.ts +320 -0
- package/src/cognition/faculties/episodic.consolidator.ts +728 -0
- package/src/cognition/faculties/executive.engine/commands.ts +459 -0
- package/src/cognition/faculties/executive.engine/config.ts +46 -0
- package/src/cognition/faculties/executive.engine/context.ts +605 -0
- package/src/cognition/faculties/executive.engine/deferred.effects.ts +104 -0
- package/src/cognition/faculties/executive.engine/deliberate.reasoning.ts +60 -0
- package/src/cognition/faculties/executive.engine/effort.gate.ts +114 -0
- package/src/cognition/faculties/executive.engine/engine.ts +1028 -0
- package/src/cognition/faculties/executive.engine/escalation.buffer.ts +90 -0
- package/src/cognition/faculties/executive.engine/facet.supervisor.ts +271 -0
- package/src/cognition/faculties/executive.engine/facet.ts +628 -0
- package/src/cognition/faculties/executive.engine/gating.ts +222 -0
- package/src/cognition/faculties/executive.engine/index.ts +6 -0
- package/src/cognition/faculties/executive.engine/messages.ts +102 -0
- package/src/cognition/faculties/executive.engine/parser.ts +380 -0
- package/src/cognition/faculties/executive.engine/prompt.factory.ts +1053 -0
- package/src/cognition/faculties/executive.engine/types.ts +323 -0
- package/src/cognition/faculties/exteroception.ts +338 -0
- package/src/cognition/faculties/forgetting.curve.ts +202 -0
- package/src/cognition/faculties/frustration.evaluator.ts +280 -0
- package/src/cognition/faculties/goal.manager.ts +1008 -0
- package/src/cognition/faculties/inhibition.controller.ts +351 -0
- package/src/cognition/faculties/interoception.ts +417 -0
- package/src/cognition/faculties/introspection.engine.ts +246 -0
- package/src/cognition/faculties/known.entity.tracker.ts +439 -0
- package/src/cognition/faculties/loss.evaluator.ts +224 -0
- package/src/cognition/faculties/moral.evaluator.ts +437 -0
- package/src/cognition/faculties/novelty.detector.ts +254 -0
- package/src/cognition/faculties/persona.consolidator.ts +845 -0
- package/src/cognition/faculties/planning.engine/engine.ts +859 -0
- package/src/cognition/faculties/planning.engine/plan.frontier.ts +83 -0
- package/src/cognition/faculties/planning.engine/plan.store.ts +174 -0
- package/src/cognition/faculties/planning.engine/plan.supervision.ts +494 -0
- package/src/cognition/faculties/planning.engine/types.ts +143 -0
- package/src/cognition/faculties/reputation.tracker.ts +294 -0
- package/src/cognition/faculties/reward.evaluator.ts +304 -0
- package/src/cognition/faculties/self.model.updater.ts +677 -0
- package/src/cognition/faculties/semantic.engine/clustering.ts +673 -0
- package/src/cognition/faculties/semantic.engine/index.ts +6 -0
- package/src/cognition/faculties/semantic.engine/integrator.ts +750 -0
- package/src/cognition/faculties/semantic.engine/types.ts +53 -0
- package/src/cognition/faculties/sleep.pressure.regulator.ts +262 -0
- package/src/cognition/faculties/social.perception.ts +333 -0
- package/src/cognition/faculties/spaced.repetition.ts +605 -0
- package/src/cognition/faculties/stress.regulator.ts +375 -0
- package/src/cognition/faculties/task.switcher.ts +239 -0
- package/src/cognition/faculties/theory.of.mind.ts +333 -0
- package/src/cognition/faculties/threat.evaluator.ts +312 -0
- package/src/cognition/faculties/working.memory.ts +406 -0
- package/src/cognition/generative.model.ts +344 -0
- package/src/cognition/heartbeat.ts +70 -0
- package/src/cognition/index.ts +291 -0
- package/src/cognition/instruction.handler.ts +146 -0
- package/src/cognition/memory/index.ts +35 -0
- package/src/cognition/memory/vector.adapter.ts +316 -0
- package/src/cognition/memory/vector.content.ts +47 -0
- package/src/cognition/memory/vector.embedder.ts +212 -0
- package/src/cognition/memory/vector.index.ts +459 -0
- package/src/cognition/memory/vector.types.ts +65 -0
- package/src/cognition/orchestrator.ts +142 -0
- package/src/cognition/persona.prior.ts +265 -0
- package/src/cognition/schema.registry.ts +145 -0
- package/src/cognition/senses/audition.engine/engine.ts +976 -0
- package/src/cognition/senses/audition.engine/salience.ts +59 -0
- package/src/cognition/senses/base.sense.engine.ts +117 -0
- package/src/cognition/senses/gustation.engine.ts +24 -0
- package/src/cognition/senses/index.ts +172 -0
- package/src/cognition/senses/olfaction.engine.ts +24 -0
- package/src/cognition/senses/somatosensation.engine.ts +23 -0
- package/src/cognition/senses/vision.engine.ts +23 -0
- package/src/cognition/types.ts +63 -0
- package/src/cognition/utilities/token.tracker.ts +492 -0
- package/src/core/abstracts.ts +60 -0
- package/src/core/async.engine.ts +539 -0
- package/src/core/clock.ts +196 -0
- package/src/core/completion.recorder.ts +122 -0
- package/src/core/conflict.detector.ts +140 -0
- package/src/core/distributed.ts +626 -0
- package/src/core/event.bus.ts +292 -0
- package/src/core/inbound.recorder.ts +100 -0
- package/src/core/index.ts +167 -0
- package/src/core/logger.ts +85 -0
- package/src/core/metrics.ts +107 -0
- package/src/core/orchestrator.ts +625 -0
- package/src/core/replay.ts +589 -0
- package/src/core/scenario.ts +120 -0
- package/src/core/serialization.ts +448 -0
- package/src/core/simulation.ts +197 -0
- package/src/core/snapshot.manager.ts +293 -0
- package/src/core/state.manager.ts +283 -0
- package/src/core/types.ts +274 -0
- package/src/core/utils.ts +82 -0
- package/src/core/wall.clock.ts +23 -0
- package/src/extensions/livestream.ext.ts +570 -0
- package/src/extensions/time.ext.ts +339 -0
- package/src/index.ts +91 -0
- package/src/llm/gate.ts +140 -0
- package/src/llm/index.ts +716 -0
- package/src/llm/summarizer.ts +170 -0
- package/src/llm/wire.contracts.ts +67 -0
- package/src/pma/eval.ts +651 -0
- package/src/pma/index.ts +1082 -0
- package/src/profiles/built-in.ts +7 -0
- package/src/profiles/companion.ts +28 -0
- package/src/profiles/company-brain.ts +54 -0
- package/src/profiles/customer-service.ts +30 -0
- package/src/profiles/game-npc.ts +20 -0
- package/src/profiles/index.ts +42 -0
- package/src/profiles/smart-home.ts +34 -0
- package/src/runners/coherence.runner.ts +49 -0
- package/src/runners/outreach.runner.ts +158 -0
- package/src/runners/social.runner.ts +182 -0
- package/src/runners/thin-shim.runner.ts +195 -0
- package/src/sdk/will.ts +334 -0
- package/src/stem/assembly.audit.ts +88 -0
- package/src/stem/distribution.ts +203 -0
- package/src/stem/guards/identity.coherence.ts +152 -0
- package/src/stem/guards/identity.guard.ts +227 -0
- package/src/stem/index.ts +1145 -0
- package/src/stem/mind.ts +1090 -0
- package/src/stem/tracts/ack.reconciler.ts +55 -0
- package/src/stem/tracts/biography.writer.ts +190 -0
- package/src/stem/tracts/effector.controller.ts +143 -0
- package/src/stem/tracts/health.reporter.ts +113 -0
- package/src/stem/tracts/inbound.queue.ts +54 -0
- package/src/stem/tracts/outbox.controller.ts +167 -0
- package/src/stem/tracts/outbox.writer.ts +133 -0
- package/src/stem/tracts/pma.controller.ts +109 -0
- package/src/stem/tracts/replay.controller.ts +177 -0
- package/src/stem/tracts/sensory.controller.ts +168 -0
- package/src/stem/tracts/session.logger.ts +157 -0
- package/src/stem/tracts/transport/index.ts +38 -0
- package/src/stem/tracts/transport/loopback.transport.ts +86 -0
- package/src/stem/tracts/transport/socketio.transport.ts +200 -0
- package/src/stem/tracts/transport/stream.transport.ts +152 -0
- package/src/stem/tracts/transport/types.ts +193 -0
- package/src/stem/tracts/transport.controller.ts +380 -0
- package/src/types.ts +116 -0
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/core/orchestrator.ts
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
import { logger } from '#core/logger'
|
|
6
|
+
import type { EventBus } from '#core/event.bus'
|
|
7
|
+
import type { SimulationClock } from '#core/clock'
|
|
8
|
+
import type { StateManager } from '#core/state.manager'
|
|
9
|
+
import type {
|
|
10
|
+
SimulationContext,
|
|
11
|
+
SimulationEvent,
|
|
12
|
+
StateCommands,
|
|
13
|
+
ReadonlySimulationState,
|
|
14
|
+
Tick,
|
|
15
|
+
Duration,
|
|
16
|
+
MinimalContext,
|
|
17
|
+
} from '#core/types'
|
|
18
|
+
import { createContext } from '#core/utils'
|
|
19
|
+
import { wallClock } from '#core/wall.clock'
|
|
20
|
+
|
|
21
|
+
// ── Engine contract ──────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* What an engine returns from react().
|
|
25
|
+
* Events and commands are collected from all engines, then applied
|
|
26
|
+
* atomically — no engine sees another engine's writes from the same tick.
|
|
27
|
+
*/
|
|
28
|
+
export interface EngineResult {
|
|
29
|
+
/** Events to publish after commands are applied. tick is stamped by Orchestrator. */
|
|
30
|
+
events?: Array<Omit<SimulationEvent, 'id' | 'timestamp' | 'tick'>>
|
|
31
|
+
/** State mutations to apply after all engines have finished reading. */
|
|
32
|
+
commands?: StateCommands
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface SimulationEngine {
|
|
36
|
+
readonly name: string
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Called once per tick with a frozen read-only snapshot.
|
|
40
|
+
* Engines must NOT mutate state directly — return commands instead.
|
|
41
|
+
* Omit entirely on purely event-driven engines — the orchestrator skips them.
|
|
42
|
+
*/
|
|
43
|
+
react?(
|
|
44
|
+
delta: Duration,
|
|
45
|
+
tick: Tick,
|
|
46
|
+
state: ReadonlySimulationState,
|
|
47
|
+
context: SimulationContext
|
|
48
|
+
): Promise<EngineResult>
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Called when this engine's react() throws. Allows the orchestrator
|
|
52
|
+
* to isolate engine failures without crashing the entire tick.
|
|
53
|
+
*
|
|
54
|
+
* Return EngineResult to provide fallback commands/events,
|
|
55
|
+
* or return null to skip this engine's contribution for this tick.
|
|
56
|
+
* If not provided, the error propagates (old behavior).
|
|
57
|
+
*/
|
|
58
|
+
onError?( error: Error, tick: Tick, context: SimulationContext ): Promise<EngineResult | null>
|
|
59
|
+
|
|
60
|
+
onAttach?( state: StateManager, context: SimulationContext ): Promise<void>
|
|
61
|
+
onDetach?( state: StateManager, context: SimulationContext ): Promise<void>
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
// ── Middleware ───────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
export type TickMiddleware = (
|
|
68
|
+
tick: Tick,
|
|
69
|
+
state: ReadonlySimulationState,
|
|
70
|
+
context: SimulationContext
|
|
71
|
+
) => void | Promise<void>
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Pre-commit validator signature.
|
|
75
|
+
* Receives all pending commands for the current tick.
|
|
76
|
+
* Return string[] of validation errors to abort the commit,
|
|
77
|
+
* or void/true to proceed.
|
|
78
|
+
*/
|
|
79
|
+
export type CommitValidator = (
|
|
80
|
+
commands: StateCommands[],
|
|
81
|
+
tick: Tick,
|
|
82
|
+
context: SimulationContext
|
|
83
|
+
) => string[] | true | void | Promise<string[] | true | void>
|
|
84
|
+
|
|
85
|
+
// ── Config ───────────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
export interface OrchestratorConfig {
|
|
88
|
+
tickIntervalMs?: number
|
|
89
|
+
maxTicks?: number
|
|
90
|
+
maxRealTimeMs?: number
|
|
91
|
+
/**
|
|
92
|
+
* Stop the loop when the event bus is empty AND no engine produced
|
|
93
|
+
* events or state commands in the last tick.
|
|
94
|
+
*/
|
|
95
|
+
stopOnEmptyEvents?: boolean
|
|
96
|
+
idleThresholdMs?: number
|
|
97
|
+
/** Fired before engines run — receives the frozen pre-tick snapshot. */
|
|
98
|
+
onBeforeTick?: TickMiddleware | TickMiddleware[]
|
|
99
|
+
/** Fired after commands are applied and events are flushed. */
|
|
100
|
+
onAfterTick?: TickMiddleware | TickMiddleware[]
|
|
101
|
+
/**
|
|
102
|
+
* Pre-commit validation hooks. Called after engines run but
|
|
103
|
+
* before commands are applied. Receives all pending commands.
|
|
104
|
+
* All validators must pass — if any returns string[], the commit
|
|
105
|
+
* is aborted and events are not published for this tick.
|
|
106
|
+
*/
|
|
107
|
+
onBeforeCommit?: CommitValidator[]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ── Interface ────────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
export interface Orchestrator {
|
|
113
|
+
readonly isRunning: boolean
|
|
114
|
+
readonly currentTick: Tick
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Register an onAfterTick handler. Multiple handlers can be registered
|
|
118
|
+
* and all will be called in registration order after each tick.
|
|
119
|
+
* Returns an unsubscribe function.
|
|
120
|
+
*/
|
|
121
|
+
onAfterTick( handler: TickMiddleware ): () => void
|
|
122
|
+
|
|
123
|
+
/** Register an onBeforeTick handler. Returns unsubscribe function. */
|
|
124
|
+
onBeforeTick( handler: TickMiddleware ): () => void
|
|
125
|
+
|
|
126
|
+
/** Register a pre-commit validator. All validators must pass. Returns unsubscribe function. */
|
|
127
|
+
onBeforeCommit( validator: CommitValidator ): () => void
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Update non-middleware config fields at runtime.
|
|
131
|
+
* Accepts a partial config — only provided fields are changed.
|
|
132
|
+
* undefined values are ignored.
|
|
133
|
+
*
|
|
134
|
+
* NOTE: Does NOT accept onBeforeTick/onAfterTick/onBeforeCommit.
|
|
135
|
+
* Use the dedicated registration methods for middleware.
|
|
136
|
+
*/
|
|
137
|
+
updateConfig( config: Partial<Omit<OrchestratorConfig, 'onBeforeTick' | 'onAfterTick' | 'onBeforeCommit'>> ): void
|
|
138
|
+
|
|
139
|
+
addEngine( engine: SimulationEngine ): void
|
|
140
|
+
removeEngine( name: string ): boolean
|
|
141
|
+
hasPendingAsyncWork(): boolean
|
|
142
|
+
/** Engine names in execution order (assembly-order snapshot artifact). */
|
|
143
|
+
readonly engineNames: string[]
|
|
144
|
+
/** Registered engine instances, execution-ordered (assembly wiring audit). */
|
|
145
|
+
readonly engines: readonly SimulationEngine[]
|
|
146
|
+
|
|
147
|
+
start( context: SimulationContext ): Promise<void>
|
|
148
|
+
stop(): void
|
|
149
|
+
pause(): void
|
|
150
|
+
resume(): void
|
|
151
|
+
|
|
152
|
+
step( count?: number, context?: MinimalContext ): Promise<void>
|
|
153
|
+
runUntilTick( targetTick: Tick, context?: MinimalContext ): Promise<void>
|
|
154
|
+
runUntilTime( targetTime: number, context?: MinimalContext ): Promise<void>
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ── Implementation ───────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
export class DefaultOrchestrator implements Orchestrator {
|
|
160
|
+
protected _engines: SimulationEngine[] = []
|
|
161
|
+
|
|
162
|
+
/** Override in subclasses to control which engines participate in the tick loop. */
|
|
163
|
+
protected _enginesTick(): SimulationEngine[] { return this._engines }
|
|
164
|
+
private _isRunning = false
|
|
165
|
+
private _isPaused = false
|
|
166
|
+
private _shouldStop = false
|
|
167
|
+
private _currentTick: Tick = 0
|
|
168
|
+
private _clock: SimulationClock
|
|
169
|
+
private _eventBus: EventBus
|
|
170
|
+
protected _stateManager: StateManager
|
|
171
|
+
private _config: OrchestratorConfig
|
|
172
|
+
private _lastTickHadActivity = false
|
|
173
|
+
private _lastEventTimestamp: number = 0
|
|
174
|
+
private _idleTimer: NodeJS.Timeout | null = null
|
|
175
|
+
private _tickTimer: NodeJS.Timeout | null = null
|
|
176
|
+
// Re-entrancy guard: true while _executeTick is in flight. Prevents an
|
|
177
|
+
// overlapping tick when a tick's async work exceeds tickIntervalMs.
|
|
178
|
+
private _executing = false
|
|
179
|
+
|
|
180
|
+
/** Runtime middleware arrays — the single source of truth for tick execution. */
|
|
181
|
+
private _onBeforeTickHandlers: TickMiddleware[] = []
|
|
182
|
+
private _onAfterTickHandlers: TickMiddleware[] = []
|
|
183
|
+
private _onBeforeCommitHandlers: CommitValidator[] = []
|
|
184
|
+
|
|
185
|
+
// Tick benchmarking — records per-tick latency
|
|
186
|
+
private _tickLatencies: number[] = []
|
|
187
|
+
private _maxLatencySamples = 1000
|
|
188
|
+
|
|
189
|
+
constructor(
|
|
190
|
+
clock: SimulationClock,
|
|
191
|
+
eventBus: EventBus,
|
|
192
|
+
stateManager: StateManager,
|
|
193
|
+
config: OrchestratorConfig = {}
|
|
194
|
+
){
|
|
195
|
+
this._clock = clock
|
|
196
|
+
this._eventBus = eventBus
|
|
197
|
+
this._stateManager = stateManager
|
|
198
|
+
this._config = config
|
|
199
|
+
|
|
200
|
+
// Seed runtime arrays from constructor config
|
|
201
|
+
this._seedMiddleware( config )
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Normalize constructor config into the runtime middleware arrays.
|
|
206
|
+
* Accepts single handler or array for onBeforeTick/onAfterTick.
|
|
207
|
+
* onBeforeCommit is always an array.
|
|
208
|
+
*/
|
|
209
|
+
private _seedMiddleware( config: OrchestratorConfig ): void {
|
|
210
|
+
if( config.onBeforeTick ) {
|
|
211
|
+
if( Array.isArray( config.onBeforeTick ) )
|
|
212
|
+
this._onBeforeTickHandlers.push( ...config.onBeforeTick )
|
|
213
|
+
else
|
|
214
|
+
this._onBeforeTickHandlers.push( config.onBeforeTick )
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if( config.onAfterTick ) {
|
|
218
|
+
if( Array.isArray( config.onAfterTick ) )
|
|
219
|
+
this._onAfterTickHandlers.push( ...config.onAfterTick )
|
|
220
|
+
else
|
|
221
|
+
this._onAfterTickHandlers.push( config.onAfterTick )
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if( config.onBeforeCommit )
|
|
225
|
+
this._onBeforeCommitHandlers.push( ...config.onBeforeCommit )
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
get isRunning(): boolean { return this._isRunning }
|
|
229
|
+
get currentTick(): Tick { return this._currentTick }
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Returns recent tick latency measurements for benchmarking.
|
|
233
|
+
* Array of durations in milliseconds, most recent last.
|
|
234
|
+
*/
|
|
235
|
+
get tickLatencies(): ReadonlyArray<number> {
|
|
236
|
+
return this._tickLatencies
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Returns average tick latency over the sample window.
|
|
241
|
+
*/
|
|
242
|
+
get averageTickLatency(): number {
|
|
243
|
+
if( this._tickLatencies.length === 0 ) return 0
|
|
244
|
+
return this._tickLatencies.reduce( ( a, b ) => a + b, 0 ) / this._tickLatencies.length
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ── Middleware registration (post-construction) ──────────
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Register an onBeforeTick handler. Called before engines run each tick.
|
|
251
|
+
* Returns an unsubscribe function to remove this specific handler.
|
|
252
|
+
*/
|
|
253
|
+
onBeforeTick( handler: TickMiddleware ): () => void {
|
|
254
|
+
this._onBeforeTickHandlers.push( handler )
|
|
255
|
+
return () => {
|
|
256
|
+
const idx = this._onBeforeTickHandlers.indexOf( handler )
|
|
257
|
+
if( idx !== -1 ) this._onBeforeTickHandlers.splice( idx, 1 )
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Register an onAfterTick handler. Called after commands are applied
|
|
263
|
+
* and events are flushed each tick. Multiple handlers are called in
|
|
264
|
+
* registration order.
|
|
265
|
+
* Returns an unsubscribe function.
|
|
266
|
+
*/
|
|
267
|
+
onAfterTick( handler: TickMiddleware ): () => void {
|
|
268
|
+
this._onAfterTickHandlers.push( handler )
|
|
269
|
+
return () => {
|
|
270
|
+
const idx = this._onAfterTickHandlers.indexOf( handler )
|
|
271
|
+
if( idx !== -1 ) this._onAfterTickHandlers.splice( idx, 1 )
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Register a pre-commit validator. All registered validators must pass
|
|
277
|
+
* (return void/true) for commands to be applied. If any validator returns
|
|
278
|
+
* string[], the commit is aborted for this tick.
|
|
279
|
+
* Returns an unsubscribe function.
|
|
280
|
+
*/
|
|
281
|
+
onBeforeCommit( validator: CommitValidator ): () => void {
|
|
282
|
+
this._onBeforeCommitHandlers.push( validator )
|
|
283
|
+
return () => {
|
|
284
|
+
const idx = this._onBeforeCommitHandlers.indexOf( validator )
|
|
285
|
+
if( idx !== -1 ) this._onBeforeCommitHandlers.splice( idx, 1 )
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Update non-middleware configuration at runtime.
|
|
291
|
+
* Only numeric/boolean config fields are accepted — middleware hooks
|
|
292
|
+
* must be registered via onBeforeTick() / onAfterTick() / onBeforeCommit().
|
|
293
|
+
* undefined values are ignored (no un-set support).
|
|
294
|
+
*/
|
|
295
|
+
updateConfig( config: Partial<Omit<OrchestratorConfig, 'onBeforeTick' | 'onAfterTick' | 'onBeforeCommit'>> ): void {
|
|
296
|
+
if( config.tickIntervalMs !== undefined )
|
|
297
|
+
this._config.tickIntervalMs = config.tickIntervalMs
|
|
298
|
+
|
|
299
|
+
if( config.maxTicks !== undefined )
|
|
300
|
+
this._config.maxTicks = config.maxTicks
|
|
301
|
+
|
|
302
|
+
if( config.maxRealTimeMs !== undefined )
|
|
303
|
+
this._config.maxRealTimeMs = config.maxRealTimeMs
|
|
304
|
+
|
|
305
|
+
if( config.stopOnEmptyEvents !== undefined )
|
|
306
|
+
this._config.stopOnEmptyEvents = config.stopOnEmptyEvents
|
|
307
|
+
|
|
308
|
+
if( config.idleThresholdMs !== undefined )
|
|
309
|
+
this._config.idleThresholdMs = config.idleThresholdMs
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// ── Engine registry ──────────────────────────────────────
|
|
313
|
+
|
|
314
|
+
addEngine( engine: SimulationEngine ): void {
|
|
315
|
+
this._engines.push( engine )
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Registered engine names IN EXECUTION ORDER. Registration order = serial
|
|
320
|
+
* tick order = replay determinism — this getter makes the order observable
|
|
321
|
+
* so the assembly-order snapshot test can pin it as a reviewed artifact
|
|
322
|
+
* (the true order comes from priority fields scattered across engine files;
|
|
323
|
+
* without this it is visible nowhere).
|
|
324
|
+
*/
|
|
325
|
+
get engineNames(): string[] {
|
|
326
|
+
return this._engines.map( e => e.name )
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** The registered engine instances, execution-ordered (assembly audit). */
|
|
330
|
+
get engines(): readonly SimulationEngine[] {
|
|
331
|
+
return this._engines
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
removeEngine( name: string ): boolean {
|
|
335
|
+
const index = this._engines.findIndex( e => e.name === name )
|
|
336
|
+
if( index === -1 ) return false
|
|
337
|
+
|
|
338
|
+
this._engines.splice( index, 1 )
|
|
339
|
+
return true
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Returns true if any registered engine is an AsyncEngine
|
|
344
|
+
* with pending reasoning operations. Useful for stopOnEmptyEvents
|
|
345
|
+
* logic — the orchestrator should not stop while engines are thinking.
|
|
346
|
+
*/
|
|
347
|
+
hasPendingAsyncWork(): boolean {
|
|
348
|
+
// AsyncEngine instances maintain internal pending state.
|
|
349
|
+
// This is a conservative check — we check if any engine's react()
|
|
350
|
+
// returned no commands AND no events last tick (indicating waiting).
|
|
351
|
+
// More precise tracking would require the engine to expose pending count.
|
|
352
|
+
return false // Default: orchestrator doesn't introspect engine internals
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Hook called after Phase 1 commands are applied and the EventBus is flushed,
|
|
357
|
+
* but BEFORE after-tick middleware runs. Override in CognitiveOrchestrator to
|
|
358
|
+
* flush the CognitiveBus and apply Phase 2 (event-handler) commands so that
|
|
359
|
+
* metrics and snapshot after-tick handlers see the fully updated state.
|
|
360
|
+
*/
|
|
361
|
+
protected async _onAfterPhase1(
|
|
362
|
+
_tick: Tick,
|
|
363
|
+
_state: ReadonlySimulationState,
|
|
364
|
+
_context: SimulationContext
|
|
365
|
+
): Promise<void> { /* no-op in base orchestrator */ }
|
|
366
|
+
|
|
367
|
+
// ── Lifecycle ────────────────────────────────────────────
|
|
368
|
+
|
|
369
|
+
async start( context: SimulationContext ): Promise<void> {
|
|
370
|
+
if( this._isRunning ) return
|
|
371
|
+
|
|
372
|
+
this._shouldStop = false
|
|
373
|
+
this._isRunning = true
|
|
374
|
+
this._isPaused = false
|
|
375
|
+
|
|
376
|
+
for( const engine of this._engines )
|
|
377
|
+
engine.onAttach && await engine.onAttach( this._stateManager, context )
|
|
378
|
+
|
|
379
|
+
this._runLoop( context )
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Tick-loop scheduling. Uses setInterval for consistent pacing;
|
|
384
|
+
* calls _executeTick() which handles pause/stop checks internally.
|
|
385
|
+
* The orchestrator is the sole driver of ticks.
|
|
386
|
+
*/
|
|
387
|
+
private _runLoop( context: SimulationContext ): void {
|
|
388
|
+
if( this._tickTimer ) return
|
|
389
|
+
|
|
390
|
+
this._tickTimer = setInterval( async () => {
|
|
391
|
+
if( this._shouldStop || this._isPaused ) return
|
|
392
|
+
|
|
393
|
+
// Drop this firing if the previous tick is still in flight. setInterval does
|
|
394
|
+
// not wait for an async callback, so without this guard a tick whose async
|
|
395
|
+
// work (engine reacts, eventBus.flush) exceeds tickIntervalMs would overlap
|
|
396
|
+
// the next one and interleave applyCommands on the double buffer.
|
|
397
|
+
if( this._executing ) return
|
|
398
|
+
|
|
399
|
+
if( this._config.maxTicks && this._currentTick >= this._config.maxTicks ){
|
|
400
|
+
await this._shutdown( context )
|
|
401
|
+
return
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if( this._config.maxRealTimeMs && wallClock() >= this._config.maxRealTimeMs ){
|
|
405
|
+
await this._shutdown( context )
|
|
406
|
+
return
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if( this._config.stopOnEmptyEvents
|
|
410
|
+
&& this._eventBus.getPendingCount() === 0
|
|
411
|
+
&& !this._lastTickHadActivity ){
|
|
412
|
+
await this._shutdown( context )
|
|
413
|
+
return
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
this._executing = true
|
|
417
|
+
try {
|
|
418
|
+
await this._executeTick( context )
|
|
419
|
+
}
|
|
420
|
+
finally {
|
|
421
|
+
this._executing = false
|
|
422
|
+
}
|
|
423
|
+
}, this._config.tickIntervalMs ?? 0 )
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
private async _shutdown( context: SimulationContext ): Promise<void> {
|
|
427
|
+
this._shouldStop = true
|
|
428
|
+
this._tickTimer && clearInterval( this._tickTimer )
|
|
429
|
+
this._tickTimer = null
|
|
430
|
+
|
|
431
|
+
for( const engine of this._engines )
|
|
432
|
+
engine.onDetach && await engine.onDetach( this._stateManager, context )
|
|
433
|
+
|
|
434
|
+
this._isRunning = false
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
stop(): void {
|
|
438
|
+
this._shouldStop = true
|
|
439
|
+
this._tickTimer && clearInterval( this._tickTimer )
|
|
440
|
+
this._tickTimer = null
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
pause(): void { this._isPaused = true; this._clock.pause() }
|
|
444
|
+
resume(): void { this._isPaused = false; this._clock.resume() }
|
|
445
|
+
|
|
446
|
+
// ── Core tick ────────────────────────────────────────────
|
|
447
|
+
|
|
448
|
+
private async _executeTick( context: SimulationContext ): Promise<void> {
|
|
449
|
+
const tickStart = wallClock()
|
|
450
|
+
|
|
451
|
+
// 1. Advance clock
|
|
452
|
+
this._clock.tick()
|
|
453
|
+
this._currentTick = this._clock.currentTick
|
|
454
|
+
|
|
455
|
+
// 2. Sync state manager clock reference
|
|
456
|
+
this._stateManager.updateClock( this._currentTick, this._clock.now )
|
|
457
|
+
|
|
458
|
+
// 3. Drain future events due at this tick into the pending queue
|
|
459
|
+
this._eventBus.prepareTick( this._currentTick )
|
|
460
|
+
|
|
461
|
+
// 4. Frozen read-only snapshot — all engines read this same view
|
|
462
|
+
const snapshot = this._stateManager.snapshot() as ReadonlySimulationState
|
|
463
|
+
|
|
464
|
+
// Allow subclasses to enrich the context (e.g. inject cognitiveBus)
|
|
465
|
+
const engineContext = this._buildEngineContext( context )
|
|
466
|
+
|
|
467
|
+
// 5. Before-tick: iterate all handlers in registration order
|
|
468
|
+
for( const handler of this._onBeforeTickHandlers )
|
|
469
|
+
await handler( this._currentTick, snapshot, engineContext )
|
|
470
|
+
|
|
471
|
+
// 6. Run engines — strictly sequential in priority (registration) order.
|
|
472
|
+
// This serial await chain is DELIBERATE, not an oversight: it fixes the order
|
|
473
|
+
// in which engines emit commands, which is what makes record-and-replay
|
|
474
|
+
// reproducible. The synchronous heuristic engines are mutually independent
|
|
475
|
+
// within a tick and *could* run via Promise.all for throughput — do NOT do
|
|
476
|
+
// that. Parallelizing reintroduces nondeterministic command ordering and
|
|
477
|
+
// silently breaks replay. Throughput is traded for determinism on purpose.
|
|
478
|
+
const allResults: EngineResult[] = []
|
|
479
|
+
|
|
480
|
+
const runEngine = async ( engine: SimulationEngine ): Promise<void> => {
|
|
481
|
+
try {
|
|
482
|
+
const result = await engine.react?.( this._clock.delta, this._currentTick, snapshot, engineContext )
|
|
483
|
+
if( result ) allResults.push( result )
|
|
484
|
+
}
|
|
485
|
+
catch( error ){
|
|
486
|
+
if( engine.onError ){
|
|
487
|
+
const fallback = await engine.onError( error as Error, this._currentTick, engineContext )
|
|
488
|
+
if( fallback ) allResults.push( fallback )
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
logger.error(
|
|
492
|
+
`[Orchestrator] Engine "${engine.name}" threw at tick ${this._currentTick}:`,
|
|
493
|
+
error
|
|
494
|
+
)
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
for( const engine of this._enginesTick() )
|
|
500
|
+
await runEngine( engine )
|
|
501
|
+
|
|
502
|
+
// Track pending async work across engines (check all engines, not just tick engines)
|
|
503
|
+
const hasPendingAsync = this._engines.some( e => ( e as any ).hasPendingWork === true )
|
|
504
|
+
|
|
505
|
+
// 7. Collect all commands for pre-commit validation
|
|
506
|
+
const allCommands: StateCommands[] = []
|
|
507
|
+
for( const result of allResults )
|
|
508
|
+
result.commands && allCommands.push( result.commands )
|
|
509
|
+
|
|
510
|
+
// 8. Pre-commit validation — all validators must pass.
|
|
511
|
+
// If any validator returns string[], abort the commit for this tick.
|
|
512
|
+
if( this._onBeforeCommitHandlers.length > 0 && allCommands.length > 0 ){
|
|
513
|
+
let abort = false
|
|
514
|
+
for( const validator of this._onBeforeCommitHandlers ){
|
|
515
|
+
const result = await validator( allCommands, this._currentTick, engineContext )
|
|
516
|
+
if( Array.isArray( result ) && result.length > 0 ){
|
|
517
|
+
logger.error(
|
|
518
|
+
`[Orchestrator] Pre-commit validation failed at tick ${this._currentTick}:`,
|
|
519
|
+
result.join(', ')
|
|
520
|
+
)
|
|
521
|
+
abort = true
|
|
522
|
+
break // Stop at first failing validator
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if( abort ) return // Skip command application and event publishing
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// 9. Apply all state commands atomically (double-buffer commit)
|
|
530
|
+
for( const commands of allCommands )
|
|
531
|
+
this._stateManager.applyCommands( commands )
|
|
532
|
+
|
|
533
|
+
// 10. Publish engine events with tick stamped
|
|
534
|
+
const allEvents: Array<Omit<SimulationEvent, 'id' | 'timestamp' | 'tick'>> = []
|
|
535
|
+
for( const result of allResults )
|
|
536
|
+
result.events && allEvents.push( ...result.events )
|
|
537
|
+
|
|
538
|
+
for( const event of allEvents )
|
|
539
|
+
this._eventBus.publish( event, context, this._currentTick )
|
|
540
|
+
|
|
541
|
+
// 12. Flush event bus — handlers see the post-command state
|
|
542
|
+
await this._eventBus.flush()
|
|
543
|
+
|
|
544
|
+
// Phase 2: CognitiveBus flush + event-handler command commit (overridden in CognitiveOrchestrator)
|
|
545
|
+
await this._onAfterPhase1( this._currentTick, snapshot, engineContext )
|
|
546
|
+
|
|
547
|
+
// 13. After-tick: iterate all handlers in registration order
|
|
548
|
+
const postSnapshot = this._stateManager.snapshot() as ReadonlySimulationState
|
|
549
|
+
for( const handler of this._onAfterTickHandlers )
|
|
550
|
+
await handler( this._currentTick, postSnapshot, engineContext )
|
|
551
|
+
|
|
552
|
+
// 13. Track activity for stopOnEmptyEvents
|
|
553
|
+
const commandActivity = allResults.some( r => ( r.commands?.set?.length ?? 0 ) + ( r.commands?.delete?.length ?? 0 ) > 0 )
|
|
554
|
+
this._lastTickHadActivity = allEvents.length > 0 || commandActivity || hasPendingAsync
|
|
555
|
+
|
|
556
|
+
// 14. Idle detection
|
|
557
|
+
if( this._lastTickHadActivity ){
|
|
558
|
+
this._lastEventTimestamp = wallClock()
|
|
559
|
+
this._resetIdleTimer()
|
|
560
|
+
}
|
|
561
|
+
else if( this._config.idleThresholdMs )
|
|
562
|
+
this._resetIdleTimer()
|
|
563
|
+
|
|
564
|
+
// 15. Record tick latency for benchmarking
|
|
565
|
+
const tickDuration = wallClock() - tickStart
|
|
566
|
+
this._tickLatencies.push( tickDuration )
|
|
567
|
+
|
|
568
|
+
if( this._tickLatencies.length > this._maxLatencySamples )
|
|
569
|
+
this._tickLatencies.shift()
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Override in subclasses to enrich the SimulationContext passed to engines.
|
|
574
|
+
* Default: returns context unchanged.
|
|
575
|
+
* CognitiveOrchestrator overrides this to inject cognitiveBus.
|
|
576
|
+
*/
|
|
577
|
+
protected _buildEngineContext( context: SimulationContext ): SimulationContext {
|
|
578
|
+
return context
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
private _resetIdleTimer(): void {
|
|
582
|
+
if( !this._config.idleThresholdMs ) return
|
|
583
|
+
|
|
584
|
+
this._idleTimer && clearTimeout( this._idleTimer )
|
|
585
|
+
this._idleTimer = setTimeout( () => {
|
|
586
|
+
if( this._isRunning && !this._isPaused ){
|
|
587
|
+
const idleDuration = wallClock() - this._lastEventTimestamp
|
|
588
|
+
idleDuration >= ( this._config.idleThresholdMs ?? 0 ) && ( this._shouldStop = true )
|
|
589
|
+
}
|
|
590
|
+
}, this._config.idleThresholdMs )
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
private _wait( ms: number ): Promise<void> {
|
|
594
|
+
return new Promise( resolve => setTimeout( resolve, ms ) )
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// ── Utility run modes ────────────────────────────────────
|
|
598
|
+
|
|
599
|
+
async step(
|
|
600
|
+
count: number = 1,
|
|
601
|
+
{ simulationId, runId, seed }: MinimalContext = { simulationId: 'step', runId: 'single' }
|
|
602
|
+
): Promise<void> {
|
|
603
|
+
const fullContext = createContext( simulationId, runId, seed ?? 42 )
|
|
604
|
+
for( let i = 0; i < count; i++ )
|
|
605
|
+
await this._executeTick( fullContext )
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
async runUntilTick(
|
|
609
|
+
targetTick: Tick,
|
|
610
|
+
{ simulationId, runId, seed }: MinimalContext = { simulationId: 'batch', runId: 'until-tick' }
|
|
611
|
+
): Promise<void> {
|
|
612
|
+
const fullContext = createContext( simulationId, runId, seed ?? 42 )
|
|
613
|
+
while( this._currentTick < targetTick && !this._shouldStop )
|
|
614
|
+
await this._executeTick( fullContext )
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
async runUntilTime(
|
|
618
|
+
targetTime: number,
|
|
619
|
+
{ simulationId, runId, seed }: MinimalContext = { simulationId: 'batch', runId: 'until-time' }
|
|
620
|
+
): Promise<void> {
|
|
621
|
+
const fullContext = createContext( simulationId, runId, seed ?? 42 )
|
|
622
|
+
while( this._clock.now < targetTime && !this._shouldStop )
|
|
623
|
+
await this._executeTick( fullContext )
|
|
624
|
+
}
|
|
625
|
+
}
|