@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.
Files changed (178) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +777 -0
  3. package/dist/index.d.ts +8144 -0
  4. package/dist/index.js +26231 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +73 -0
  7. package/src/cognition/agency/access.grants.ts +59 -0
  8. package/src/cognition/agency/competence.codec.ts +89 -0
  9. package/src/cognition/agency/engines/action.selector.ts +462 -0
  10. package/src/cognition/agency/engines/affordance.synthesizer.ts +384 -0
  11. package/src/cognition/agency/engines/deliberation.engine.ts +241 -0
  12. package/src/cognition/agency/engines/instruction.intake.ts +54 -0
  13. package/src/cognition/agency/engines/motor.schema.executor.ts +532 -0
  14. package/src/cognition/agency/engines/reafference.engine.ts +241 -0
  15. package/src/cognition/agency/execution.primitives.ts +118 -0
  16. package/src/cognition/agency/index.ts +58 -0
  17. package/src/cognition/agency/proactive.communicator.ts +274 -0
  18. package/src/cognition/agency/reconcile.learning.ts +82 -0
  19. package/src/cognition/agency/schemas/external.ts +59 -0
  20. package/src/cognition/agency/schemas/innate.ts +111 -0
  21. package/src/cognition/agency/schemas/repertoire.ts +246 -0
  22. package/src/cognition/agency/selection.scoring.ts +191 -0
  23. package/src/cognition/agency/types.ts +165 -0
  24. package/src/cognition/bus.ts +391 -0
  25. package/src/cognition/completion.inbox.ts +86 -0
  26. package/src/cognition/config.mirror.entities.ts +475 -0
  27. package/src/cognition/conversation.memory.ts +81 -0
  28. package/src/cognition/event.log.ts +116 -0
  29. package/src/cognition/event.schemas.ts +568 -0
  30. package/src/cognition/faculties/aesthetic.evaluator.ts +313 -0
  31. package/src/cognition/faculties/affective.blender.ts +562 -0
  32. package/src/cognition/faculties/attachment.evaluator.ts +432 -0
  33. package/src/cognition/faculties/attention.allocator.ts +422 -0
  34. package/src/cognition/faculties/autobiographical.narrator.ts +300 -0
  35. package/src/cognition/faculties/bias.detector.ts +363 -0
  36. package/src/cognition/faculties/circadian.oscillator.ts +294 -0
  37. package/src/cognition/faculties/confidence.calibrator.ts +324 -0
  38. package/src/cognition/faculties/dream.simulator.ts +251 -0
  39. package/src/cognition/faculties/empathy.simulator.ts +253 -0
  40. package/src/cognition/faculties/energy.regulator.ts +320 -0
  41. package/src/cognition/faculties/episodic.consolidator.ts +728 -0
  42. package/src/cognition/faculties/executive.engine/commands.ts +459 -0
  43. package/src/cognition/faculties/executive.engine/config.ts +46 -0
  44. package/src/cognition/faculties/executive.engine/context.ts +605 -0
  45. package/src/cognition/faculties/executive.engine/deferred.effects.ts +104 -0
  46. package/src/cognition/faculties/executive.engine/deliberate.reasoning.ts +60 -0
  47. package/src/cognition/faculties/executive.engine/effort.gate.ts +114 -0
  48. package/src/cognition/faculties/executive.engine/engine.ts +1028 -0
  49. package/src/cognition/faculties/executive.engine/escalation.buffer.ts +90 -0
  50. package/src/cognition/faculties/executive.engine/facet.supervisor.ts +271 -0
  51. package/src/cognition/faculties/executive.engine/facet.ts +628 -0
  52. package/src/cognition/faculties/executive.engine/gating.ts +222 -0
  53. package/src/cognition/faculties/executive.engine/index.ts +6 -0
  54. package/src/cognition/faculties/executive.engine/messages.ts +102 -0
  55. package/src/cognition/faculties/executive.engine/parser.ts +380 -0
  56. package/src/cognition/faculties/executive.engine/prompt.factory.ts +1053 -0
  57. package/src/cognition/faculties/executive.engine/types.ts +323 -0
  58. package/src/cognition/faculties/exteroception.ts +338 -0
  59. package/src/cognition/faculties/forgetting.curve.ts +202 -0
  60. package/src/cognition/faculties/frustration.evaluator.ts +280 -0
  61. package/src/cognition/faculties/goal.manager.ts +1008 -0
  62. package/src/cognition/faculties/inhibition.controller.ts +351 -0
  63. package/src/cognition/faculties/interoception.ts +417 -0
  64. package/src/cognition/faculties/introspection.engine.ts +246 -0
  65. package/src/cognition/faculties/known.entity.tracker.ts +439 -0
  66. package/src/cognition/faculties/loss.evaluator.ts +224 -0
  67. package/src/cognition/faculties/moral.evaluator.ts +437 -0
  68. package/src/cognition/faculties/novelty.detector.ts +254 -0
  69. package/src/cognition/faculties/persona.consolidator.ts +845 -0
  70. package/src/cognition/faculties/planning.engine/engine.ts +859 -0
  71. package/src/cognition/faculties/planning.engine/plan.frontier.ts +83 -0
  72. package/src/cognition/faculties/planning.engine/plan.store.ts +174 -0
  73. package/src/cognition/faculties/planning.engine/plan.supervision.ts +494 -0
  74. package/src/cognition/faculties/planning.engine/types.ts +143 -0
  75. package/src/cognition/faculties/reputation.tracker.ts +294 -0
  76. package/src/cognition/faculties/reward.evaluator.ts +304 -0
  77. package/src/cognition/faculties/self.model.updater.ts +677 -0
  78. package/src/cognition/faculties/semantic.engine/clustering.ts +673 -0
  79. package/src/cognition/faculties/semantic.engine/index.ts +6 -0
  80. package/src/cognition/faculties/semantic.engine/integrator.ts +750 -0
  81. package/src/cognition/faculties/semantic.engine/types.ts +53 -0
  82. package/src/cognition/faculties/sleep.pressure.regulator.ts +262 -0
  83. package/src/cognition/faculties/social.perception.ts +333 -0
  84. package/src/cognition/faculties/spaced.repetition.ts +605 -0
  85. package/src/cognition/faculties/stress.regulator.ts +375 -0
  86. package/src/cognition/faculties/task.switcher.ts +239 -0
  87. package/src/cognition/faculties/theory.of.mind.ts +333 -0
  88. package/src/cognition/faculties/threat.evaluator.ts +312 -0
  89. package/src/cognition/faculties/working.memory.ts +406 -0
  90. package/src/cognition/generative.model.ts +344 -0
  91. package/src/cognition/heartbeat.ts +70 -0
  92. package/src/cognition/index.ts +291 -0
  93. package/src/cognition/instruction.handler.ts +146 -0
  94. package/src/cognition/memory/index.ts +35 -0
  95. package/src/cognition/memory/vector.adapter.ts +316 -0
  96. package/src/cognition/memory/vector.content.ts +47 -0
  97. package/src/cognition/memory/vector.embedder.ts +212 -0
  98. package/src/cognition/memory/vector.index.ts +459 -0
  99. package/src/cognition/memory/vector.types.ts +65 -0
  100. package/src/cognition/orchestrator.ts +142 -0
  101. package/src/cognition/persona.prior.ts +265 -0
  102. package/src/cognition/schema.registry.ts +145 -0
  103. package/src/cognition/senses/audition.engine/engine.ts +976 -0
  104. package/src/cognition/senses/audition.engine/salience.ts +59 -0
  105. package/src/cognition/senses/base.sense.engine.ts +117 -0
  106. package/src/cognition/senses/gustation.engine.ts +24 -0
  107. package/src/cognition/senses/index.ts +172 -0
  108. package/src/cognition/senses/olfaction.engine.ts +24 -0
  109. package/src/cognition/senses/somatosensation.engine.ts +23 -0
  110. package/src/cognition/senses/vision.engine.ts +23 -0
  111. package/src/cognition/types.ts +63 -0
  112. package/src/cognition/utilities/token.tracker.ts +492 -0
  113. package/src/core/abstracts.ts +60 -0
  114. package/src/core/async.engine.ts +539 -0
  115. package/src/core/clock.ts +196 -0
  116. package/src/core/completion.recorder.ts +122 -0
  117. package/src/core/conflict.detector.ts +140 -0
  118. package/src/core/distributed.ts +626 -0
  119. package/src/core/event.bus.ts +292 -0
  120. package/src/core/inbound.recorder.ts +100 -0
  121. package/src/core/index.ts +167 -0
  122. package/src/core/logger.ts +85 -0
  123. package/src/core/metrics.ts +107 -0
  124. package/src/core/orchestrator.ts +625 -0
  125. package/src/core/replay.ts +589 -0
  126. package/src/core/scenario.ts +120 -0
  127. package/src/core/serialization.ts +448 -0
  128. package/src/core/simulation.ts +197 -0
  129. package/src/core/snapshot.manager.ts +293 -0
  130. package/src/core/state.manager.ts +283 -0
  131. package/src/core/types.ts +274 -0
  132. package/src/core/utils.ts +82 -0
  133. package/src/core/wall.clock.ts +23 -0
  134. package/src/extensions/livestream.ext.ts +570 -0
  135. package/src/extensions/time.ext.ts +339 -0
  136. package/src/index.ts +91 -0
  137. package/src/llm/gate.ts +140 -0
  138. package/src/llm/index.ts +716 -0
  139. package/src/llm/summarizer.ts +170 -0
  140. package/src/llm/wire.contracts.ts +67 -0
  141. package/src/pma/eval.ts +651 -0
  142. package/src/pma/index.ts +1082 -0
  143. package/src/profiles/built-in.ts +7 -0
  144. package/src/profiles/companion.ts +28 -0
  145. package/src/profiles/company-brain.ts +54 -0
  146. package/src/profiles/customer-service.ts +30 -0
  147. package/src/profiles/game-npc.ts +20 -0
  148. package/src/profiles/index.ts +42 -0
  149. package/src/profiles/smart-home.ts +34 -0
  150. package/src/runners/coherence.runner.ts +49 -0
  151. package/src/runners/outreach.runner.ts +158 -0
  152. package/src/runners/social.runner.ts +182 -0
  153. package/src/runners/thin-shim.runner.ts +195 -0
  154. package/src/sdk/will.ts +334 -0
  155. package/src/stem/assembly.audit.ts +88 -0
  156. package/src/stem/distribution.ts +203 -0
  157. package/src/stem/guards/identity.coherence.ts +152 -0
  158. package/src/stem/guards/identity.guard.ts +227 -0
  159. package/src/stem/index.ts +1145 -0
  160. package/src/stem/mind.ts +1090 -0
  161. package/src/stem/tracts/ack.reconciler.ts +55 -0
  162. package/src/stem/tracts/biography.writer.ts +190 -0
  163. package/src/stem/tracts/effector.controller.ts +143 -0
  164. package/src/stem/tracts/health.reporter.ts +113 -0
  165. package/src/stem/tracts/inbound.queue.ts +54 -0
  166. package/src/stem/tracts/outbox.controller.ts +167 -0
  167. package/src/stem/tracts/outbox.writer.ts +133 -0
  168. package/src/stem/tracts/pma.controller.ts +109 -0
  169. package/src/stem/tracts/replay.controller.ts +177 -0
  170. package/src/stem/tracts/sensory.controller.ts +168 -0
  171. package/src/stem/tracts/session.logger.ts +157 -0
  172. package/src/stem/tracts/transport/index.ts +38 -0
  173. package/src/stem/tracts/transport/loopback.transport.ts +86 -0
  174. package/src/stem/tracts/transport/socketio.transport.ts +200 -0
  175. package/src/stem/tracts/transport/stream.transport.ts +152 -0
  176. package/src/stem/tracts/transport/types.ts +193 -0
  177. package/src/stem/tracts/transport.controller.ts +380 -0
  178. package/src/types.ts +116 -0
@@ -0,0 +1,222 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/faculties/executive.engine/gating.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ import type { PendingMessage } from '#faculties/executive.engine/types'
6
+ import type { Tick, ReadonlySimulationState } from '#core/types'
7
+ import type { GenerativeModel } from '#cognition/generative.model'
8
+ import {
9
+ BUFFER_SALIENCE_TRIGGER,
10
+ BUFFER_MAX_AGE_TICKS,
11
+ } from '#faculties/executive.engine/config'
12
+ import type { CognitiveEvent } from '#cognition/bus'
13
+
14
+ function hasPendingInstructions( state: ReadonlySimulationState, pendingMessages: PendingMessage[] ): boolean {
15
+ for( const entity of state.entities.values() ){
16
+ if( entity.type === 'percept' || entity.type === 'percept.social' ){
17
+ const salience = (entity.metadata?.salience as number) ?? 0
18
+ if( salience > 0.7 ) return true
19
+ }
20
+
21
+ if( entity.type === 'communication' && !entity.metadata?.processedByExecutive )
22
+ return true
23
+ }
24
+
25
+ return pendingMessages.length > 0
26
+ }
27
+
28
+ export interface GatingDependencies {
29
+ generativeModel: GenerativeModel
30
+ pendingMessages: PendingMessage[]
31
+ hasPendingWork: boolean
32
+ }
33
+
34
+ export interface SalienceBufferEntry {
35
+ event: CognitiveEvent
36
+ tick: number
37
+ }
38
+
39
+ export interface GatingState {
40
+ executiveInterval: number
41
+ cooldownTicks: number
42
+ lastExecutiveTick: number
43
+ salienceBuffer: SalienceBufferEntry[]
44
+ goallessTickCount: number
45
+ lowValenceTickCount: number
46
+ }
47
+
48
+ export interface GatingResult {
49
+ shouldActivate: boolean
50
+ reason: string
51
+ /** Cleaned buffer with stale entries removed — caller must update GatingState. */
52
+ cleanedBuffer: SalienceBufferEntry[]
53
+ }
54
+
55
+ /**
56
+ * Full gating logic — pure function.
57
+ * Receives all state, returns decision + reason + cleaned buffer.
58
+ * The caller is responsible for updating GatingState with the result.
59
+ */
60
+ export function evaluateGating(
61
+ state: ReadonlySimulationState,
62
+ tick: Tick,
63
+ deps: GatingDependencies,
64
+ gs: GatingState,
65
+ ): GatingResult {
66
+ // ── In-flight work check ──────────────────────────────────
67
+ if( deps.hasPendingWork )
68
+ return {
69
+ shouldActivate: false,
70
+ reason: 'hasPendingWork',
71
+ cleanedBuffer: gs.salienceBuffer
72
+ }
73
+
74
+ // ── Cooldown — always enforced ────────────────────────────
75
+ if( tick - gs.lastExecutiveTick < gs.cooldownTicks )
76
+ return {
77
+ shouldActivate: false,
78
+ reason: 'cooldown',
79
+ cleanedBuffer: gs.salienceBuffer
80
+ }
81
+
82
+ // ── Read metrics ──────────────────────────────────────────
83
+ const energy = state.metrics.get('energy.level') ?? 100
84
+ const isResting = state.metrics.get('state.resting') ?? 0
85
+ const isSleeping = state.metrics.get('state.sleeping') ?? 0
86
+ const isForcedRest = state.metrics.get('state.forced_rest') ?? 0
87
+ const novelty = state.metrics.get('perception.novelty') ?? 0
88
+ const sleepPressure = state.metrics.get('sleep.pressure') ?? 0
89
+ const stressLoad = state.metrics.get('stress.load') ?? 0
90
+ const valence = state.metrics.get('affect.valence') ?? 0
91
+
92
+ // ── Drop stale buffer entries ─────────────────────────────
93
+ const cleanedBuffer = gs.salienceBuffer.filter( e => tick - e.tick < BUFFER_MAX_AGE_TICKS )
94
+
95
+ // ── Accumulated workspace salience (GWT ignition pressure) ─
96
+ // Computed up-front so it both overrides prediction-gating at the interval
97
+ // boundary and drives the pre-interval early trigger further below.
98
+ const totalSalience = cleanedBuffer.reduce( ( s, e ) => s + ( e.event.salience ?? 0 ), 0 )
99
+ const salienceCharged = totalSalience >= BUFFER_SALIENCE_TRIGGER
100
+
101
+ // ── Forced shutdown (energy < 1) ──────────────────────────
102
+ if( isForcedRest > 0 )
103
+ return {
104
+ shouldActivate: false,
105
+ reason: 'forced_rest',
106
+ cleanedBuffer
107
+ }
108
+
109
+ // ── Voluntary rest/sleep — wake on high novelty or pending instructions ─
110
+ if( isResting > 0 || isSleeping > 0 ){
111
+ const
112
+ significantEvent = novelty > 0.8,
113
+ hasPending = hasPendingInstructions( state, deps.pendingMessages )
114
+
115
+ if( significantEvent || hasPending )
116
+ return {
117
+ shouldActivate: true,
118
+ reason: `awakened_from_${isSleeping > 0 ? 'sleep' : 'rest'}`,
119
+ cleanedBuffer
120
+ }
121
+
122
+ return {
123
+ shouldActivate: false,
124
+ reason: 'resting',
125
+ cleanedBuffer
126
+ }
127
+ }
128
+
129
+ // ── Count active goals ────────────────────────────────────
130
+ let activeGoalCount = 0
131
+ for( const entity of state.entities.values() )
132
+ if( entity.type === 'goal' && entity.metadata?.status === 'active')
133
+ activeGoalCount++
134
+
135
+ // ── Physiological crises — bypass executiveInterval ───────
136
+ if( sleepPressure > 65 )
137
+ return { shouldActivate: true, reason: 'sleep_pressure_critical', cleanedBuffer }
138
+
139
+ if( stressLoad > 75 )
140
+ return { shouldActivate: true, reason: 'stress_overload', cleanedBuffer }
141
+
142
+ if( energy < 15 )
143
+ return { shouldActivate: true, reason: 'energy_critical', cleanedBuffer }
144
+
145
+ // ── Pending messages — always fire ────────────────────────
146
+ if( deps.pendingMessages.length > 0 )
147
+ return { shouldActivate: true, reason: 'pending_message', cleanedBuffer }
148
+
149
+ // ── Normal interval scheduling ────────────────────────────
150
+ if( tick - gs.lastExecutiveTick >= gs.executiveInterval ){
151
+ // Prediction-error gating — uses GenerativeModel.observe() which returns { gated }
152
+ const energyErr = deps.generativeModel.observe('energy.level', energy)
153
+ const stressErr = deps.generativeModel.observe('stress.load', stressLoad)
154
+ const noveltyErr = deps.generativeModel.observe('perception.novelty', novelty)
155
+ const valenceErr = deps.generativeModel.observe('affect.valence', valence)
156
+
157
+ const allGated = energyErr.gated && stressErr.gated && noveltyErr.gated && valenceErr.gated
158
+ const overdue = tick - gs.lastExecutiveTick >= gs.executiveInterval * 1.5
159
+
160
+ // A salience-charged workspace ignites even when every signal is well-predicted:
161
+ // accumulated salient coalitions are exactly what GWT ignition is meant to catch,
162
+ // so prediction-gating must not suppress them at the interval boundary.
163
+ if( allGated && !overdue && !salienceCharged )
164
+ return { shouldActivate: false, reason: 'prediction_gated', cleanedBuffer }
165
+
166
+ return {
167
+ shouldActivate: true,
168
+ reason: salienceCharged ? 'salience_charged' : 'interval_elapsed',
169
+ cleanedBuffer
170
+ }
171
+ }
172
+
173
+ // ── Salience competition — early trigger (pre-interval) ───
174
+ if( salienceCharged )
175
+ return { shouldActivate: true, reason: 'salience_charged', cleanedBuffer }
176
+
177
+ // ── Cognitive crises — early trigger ──────────────────────
178
+ // Counter values are computed here (the caller will persist them via updateGatingState)
179
+ const goallessTicks = activeGoalCount === 0 ? gs.goallessTickCount + 1 : 0
180
+ const lowValenceTicks = valence < -0.4 ? gs.lowValenceTickCount + 1 : 0
181
+
182
+ if( goallessTicks > 20 )
183
+ return { shouldActivate: true, reason: 'goalless_crisis', cleanedBuffer }
184
+
185
+ if( lowValenceTicks > 30 )
186
+ return { shouldActivate: true, reason: 'low_valence_crisis', cleanedBuffer }
187
+
188
+ // ── High novelty — early trigger ──────────────────────────
189
+ if( novelty > 0.7 )
190
+ return { shouldActivate: true, reason: 'high_novelty', cleanedBuffer }
191
+
192
+ return { shouldActivate: false, reason: 'no_trigger', cleanedBuffer }
193
+ }
194
+
195
+ /**
196
+ * Update the persistent gating state after a tick.
197
+ * Called every tick regardless of whether the executive activated.
198
+ */
199
+ export function updateGatingState(
200
+ gs: GatingState,
201
+ state: ReadonlySimulationState,
202
+ _tick: Tick,
203
+ didActivate: boolean,
204
+ cleanedBuffer: SalienceBufferEntry[],
205
+ ): void {
206
+ // Apply cleaned buffer
207
+ gs.salienceBuffer = cleanedBuffer
208
+
209
+ // Update crisis counters
210
+ let activeGoalCount = 0
211
+ for( const entity of state.entities.values() )
212
+ if( entity.type === 'goal' && entity.metadata?.status === 'active' )
213
+ activeGoalCount++
214
+
215
+ const valence = state.metrics.get('affect.valence') ?? 0
216
+
217
+ gs.goallessTickCount = activeGoalCount === 0 ? gs.goallessTickCount + 1 : 0
218
+ gs.lowValenceTickCount = valence < -0.4 ? gs.lowValenceTickCount + 1 : 0
219
+
220
+ if( didActivate )
221
+ gs.lastExecutiveTick = _tick
222
+ }
@@ -0,0 +1,6 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/faculties/executive.engine/index.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ export { ExecutiveEngine, effortTargetForActions } from '#faculties/executive.engine/engine'
6
+ export type { ExecutiveEngineConfig, ExecutiveOutputFull } from '#faculties/executive.engine/types'
@@ -0,0 +1,102 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/faculties/executive.engine/messages.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ import { logger } from '#core/logger'
6
+ import type { Tick, ReadonlySimulationState } from '#core/types'
7
+ import type { PendingMessage } from '#faculties/executive.engine/types'
8
+
9
+ /**
10
+ * Message queue state — tracks incoming communication entities,
11
+ * pending messages during in-flight LLM calls, and reply deduplication.
12
+ */
13
+ export class MessageQueue {
14
+ /** Messages buffered while an LLM call is in-flight. */
15
+ pendingMessages: PendingMessage[] = []
16
+
17
+ /** Tick when the current (or most recent) LLM call started. */
18
+ pendingCallStartTick: number = -1
19
+
20
+ /** Entity IDs of communication entities that have been replied to this session. */
21
+ private _repliedEntityIds = new Set<string>()
22
+
23
+ /**
24
+ * Scan the simulation state for unprocessed communication entities
25
+ * and queue them into pendingMessages. Call every tick.
26
+ */
27
+ scanState( state: ReadonlySimulationState, tick: Tick ): void {
28
+ const seenIds = new Set( this.pendingMessages.map( m => m.id ) )
29
+
30
+ for( const [ id, entity ] of state.entities ){
31
+ if( entity.type !== 'communication' ) continue
32
+ if( entity.metadata?.processedByExecutive ) continue
33
+ if( seenIds.has( id ) ) continue
34
+
35
+ const msgTick = ( entity.metadata?.tick as number ) ?? 0
36
+ this.pendingMessages.push({
37
+ id,
38
+ content: (entity.metadata?.content as string) ?? '',
39
+ sender: (entity.metadata?.agentName as string) ?? 'unknown',
40
+ senderId: (entity.metadata?.keid as string) ?? 'unknown',
41
+ tick: msgTick
42
+ })
43
+
44
+ logger.info(`[executive] queued message from ${(entity.metadata?.agentName as string) ?? 'unknown'} (tick=${msgTick})`)
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Clear messages that were included in the most recent LLM call.
50
+ * Any that arrived after the call started remain for the next cycle.
51
+ */
52
+ clearProcessedMessages(): void {
53
+ this.pendingMessages = this.pendingMessages.filter( m => m.tick > this.pendingCallStartTick )
54
+ }
55
+
56
+ /**
57
+ * Check if we've already replied to a specific communication entity this session.
58
+ */
59
+ hasRepliedTo( entityId: string ): boolean {
60
+ return this._repliedEntityIds.has( entityId )
61
+ }
62
+
63
+ /**
64
+ * Mark a communication entity as replied to.
65
+ */
66
+ markReplied( entityId: string ): void {
67
+ this._repliedEntityIds.add( entityId )
68
+ }
69
+
70
+ /**
71
+ * Build the set of communication entity IDs visible in state within the
72
+ * 30-tick window, plus any pending messages. Used to mark them as processed.
73
+ */
74
+ getVisibleMessageIds( state: ReadonlySimulationState, tick: Tick ): Set<string> {
75
+ const ids = new Set<string>( this.pendingMessages.map( m => m.id ) )
76
+ for( const [ id, entity ] of state.entities ){
77
+ if( entity.type !== 'communication' ) continue
78
+
79
+ const msgTick = (entity.metadata?.tick as number) ?? 0
80
+ if( tick - msgTick > 30 ) continue
81
+
82
+ ids.add( id )
83
+ }
84
+
85
+ return ids
86
+ }
87
+
88
+ /**
89
+ * Get stale communication entity IDs (>50 ticks old) for cleanup.
90
+ */
91
+ getStaleMessageIds( state: ReadonlySimulationState, tick: Tick ): string[] {
92
+ const stale: string[] = []
93
+ for( const [ id, entity ] of state.entities ){
94
+ if( entity.type !== 'communication' ) continue
95
+
96
+ const msgTick = (entity.metadata?.tick as number) ?? 0
97
+ tick - msgTick > 50 && stale.push( id )
98
+ }
99
+
100
+ return stale
101
+ }
102
+ }