@mindot/will 0.7.0 → 0.9.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/README.md +87 -22
- package/dist/channels/discord.d.ts +67 -6
- package/dist/channels/discord.js +112 -6
- package/dist/channels/discord.js.map +1 -1
- package/dist/channels/whatsapp.d.ts +1 -1
- package/dist/channels/whatsapp.js +4 -1
- package/dist/channels/whatsapp.js.map +1 -1
- package/dist/cli.js +13733 -11057
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3729 -1199
- package/dist/index.js.map +1 -1
- package/dist/mcp/effectors.d.ts +1 -1
- package/dist/{will-DAW0l-lY.d.ts → will-DbDj_TEH.d.ts} +1214 -93
- package/package.json +1 -1
- package/src/channels/discord.ts +189 -11
- package/src/channels/types.ts +90 -0
- package/src/channels/whatsapp.ts +13 -4
- package/src/cli.ts +9 -4
- package/src/cognition/agency/consequence.ts +122 -1
- package/src/cognition/agency/conversation.aim.ts +260 -0
- package/src/cognition/agency/engines/action.selector.ts +85 -3
- package/src/cognition/agency/engines/affordance.synthesizer.ts +90 -1
- package/src/cognition/agency/engines/motor.schema.executor.ts +152 -10
- package/src/cognition/agency/engines/reafference.engine.ts +129 -2
- package/src/cognition/agency/proactive.communicator.ts +19 -3
- package/src/cognition/agency/reconcile.learning.ts +16 -2
- package/src/cognition/agency/restart.ts +66 -0
- package/src/cognition/agency/schemas/repertoire.ts +12 -5
- package/src/cognition/agency/selection.scoring.ts +33 -0
- package/src/cognition/agency/types.ts +35 -0
- package/src/cognition/cache/composition.ts +232 -0
- package/src/cognition/cache/deliberation.cache.ts +219 -0
- package/src/cognition/cache/fingerprint.ts +120 -0
- package/src/cognition/cache/types.ts +105 -0
- package/src/cognition/config.mirror.entities.ts +109 -1
- package/src/cognition/event.schemas.ts +22 -0
- package/src/cognition/faculties/autobiographical.narrator.ts +5 -10
- package/src/cognition/faculties/episodic.consolidator.ts +59 -3
- package/src/cognition/faculties/executive.engine/commands.ts +189 -14
- package/src/cognition/faculties/executive.engine/context.ts +67 -13
- package/src/cognition/faculties/executive.engine/deliberate.reasoning.ts +1 -1
- package/src/cognition/faculties/executive.engine/engine.ts +686 -187
- package/src/cognition/faculties/executive.engine/escalation.buffer.ts +162 -44
- package/src/cognition/faculties/executive.engine/facet.supervisor.ts +310 -65
- package/src/cognition/faculties/executive.engine/facet.ts +89 -26
- package/src/cognition/faculties/executive.engine/gating.ts +14 -14
- package/src/cognition/faculties/executive.engine/parser.ts +21 -1
- package/src/cognition/faculties/executive.engine/prompt.factory.ts +169 -20
- package/src/cognition/faculties/executive.engine/types.ts +69 -0
- package/src/cognition/faculties/goal.manager.ts +94 -14
- package/src/cognition/faculties/known.entity.tracker.ts +267 -28
- package/src/cognition/faculties/moral.evaluator.ts +8 -3
- package/src/cognition/faculties/persona.consolidator.ts +141 -0
- package/src/cognition/faculties/reputation.tracker.ts +66 -2
- package/src/cognition/faculties/self.model.updater.ts +19 -12
- package/src/cognition/faculties/social.perception.ts +47 -3
- package/src/cognition/faculties/threat.evaluator.ts +7 -0
- package/src/cognition/faculties/working.memory.ts +10 -20
- package/src/cognition/identity.entity.ts +205 -0
- package/src/cognition/index.ts +11 -0
- package/src/cognition/memory/vector.adapter.ts +12 -3
- package/src/cognition/memory/vector.embedder.ts +54 -7
- package/src/cognition/persona.prior.ts +6 -0
- package/src/cognition/senses/audition.engine/engine.ts +404 -46
- package/src/cognition/senses/base.sense.engine.ts +1 -1
- package/src/cognition/senses/index.ts +12 -0
- package/src/cognition/social.identity.ts +273 -0
- package/src/cognition/utilities/token.tracker.ts +246 -98
- package/src/core/orchestrator.ts +38 -0
- package/src/host/boot.ts +78 -22
- package/src/index.ts +35 -0
- package/src/llm/index.ts +415 -97
- package/src/llm/routing.ts +204 -0
- package/src/llm/summarizer.ts +5 -1
- package/src/llm/wire.contracts.ts +19 -0
- package/src/pma/index.ts +67 -53
- package/src/runners/thin-shim.runner.ts +18 -6
- package/src/sdk/will.ts +121 -22
- package/src/stem/assembly.audit.ts +1 -0
- package/src/stem/guards/identity.coherence.ts +17 -6
- package/src/stem/index.ts +82 -5
- package/src/stem/mind.ts +327 -79
- package/src/stem/policy/arbiter.ts +49 -14
- package/src/stem/policy/rule.table.ts +2 -2
- package/src/stem/tracts/effector.controller.ts +56 -9
- package/src/stem/tracts/outbox.writer.ts +40 -2
- package/src/cognition/faculties/executive.engine/messages.ts +0 -102
|
@@ -37,7 +37,7 @@ export interface ParamConstraint {
|
|
|
37
37
|
*
|
|
38
38
|
* `require` is meaningful with `decision: 'allow'` only: the scope matched, and
|
|
39
39
|
* these constraints must hold for the allow to stand. A violation flips the
|
|
40
|
-
* verdict to deny — carrying the counterfactual — with finality '
|
|
40
|
+
* verdict to deny — carrying the counterfactual — with finality 'parameter',
|
|
41
41
|
* because the ability itself was permitted and only these arguments were not.
|
|
42
42
|
*
|
|
43
43
|
* A rule with `decision: 'deny'` and no `require` is a flat class-level ban;
|
|
@@ -94,7 +94,7 @@ export class RuleTableArbiter implements PolicyArbiter {
|
|
|
94
94
|
return {
|
|
95
95
|
decision: 'deny',
|
|
96
96
|
reasonCode: rule.reasonCode ?? violation.reasonCode,
|
|
97
|
-
finality: rule.finality ?? '
|
|
97
|
+
finality: rule.finality ?? 'parameter',
|
|
98
98
|
counterfactual: violation.counterfactual,
|
|
99
99
|
}
|
|
100
100
|
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
import { logger } from '#core/logger'
|
|
25
25
|
import { reconcileInvocation } from '#agency/reconcile.learning'
|
|
26
26
|
import { NULL_ARBITER, isNullArbiter } from '#stem/policy/arbiter'
|
|
27
|
-
import type { PolicyArbiter, PolicyInvocation, Verdict } from '#stem/policy/arbiter'
|
|
27
|
+
import type { PolicyArbiter, PolicyInvocation, Verdict, DenialFinality, PolicyCounterfactual } from '#stem/policy/arbiter'
|
|
28
|
+
import { finalityOf, asFinality } from '#stem/policy/arbiter'
|
|
28
29
|
import {
|
|
29
30
|
getVerdictRecorder, getVerdictSource, type PolicyVerdictRecord,
|
|
30
31
|
} from '#stem/policy/verdict.recorder'
|
|
@@ -36,9 +37,38 @@ interface PendingRefusal {
|
|
|
36
37
|
intentId: string
|
|
37
38
|
schema: string
|
|
38
39
|
reasonCode: string
|
|
39
|
-
finality:
|
|
40
|
+
finality: DenialFinality
|
|
41
|
+
/** ENVELOPE_NARROWING P0 — what WOULD have been allowed, carried through to
|
|
42
|
+
* the outcome the mind learns from. Absent on refusals that have no bound to
|
|
43
|
+
* report (a flat ban, a fault, an unanswered escalation). */
|
|
44
|
+
counterfactual?: PolicyCounterfactual
|
|
40
45
|
}
|
|
41
46
|
|
|
47
|
+
/**
|
|
48
|
+
* The verdict a fault produces (POLICY_REAFFERENCE P5, conformance S9).
|
|
49
|
+
*
|
|
50
|
+
* An arbiter that throws or rejects has always failed CLOSED — the effect is
|
|
51
|
+
* withheld — but it used to withhold *silently*, queueing no refusal. The held
|
|
52
|
+
* intent then expired at the executor's AWAIT_TIMEOUT and reconciled as a plain
|
|
53
|
+
* failure, landing on COMPETENCE: a PDP outage taught the mind it was unskilled
|
|
54
|
+
* at something it is perfectly capable of. So a fault now yields a real verdict:
|
|
55
|
+
*
|
|
56
|
+
* • 'deny' — still fail-closed, unchanged. The effect never reaches the world.
|
|
57
|
+
* • 'context' — but it teaches NOTHING. The arbiter being unreachable is not a
|
|
58
|
+
* fact about the ability, so nothing about the ability may move.
|
|
59
|
+
*
|
|
60
|
+
* It goes through `_recordAndApply` rather than straight to the refusal queue so
|
|
61
|
+
* the fault lands on the VERDICT TAPE too. That closes a replay hole: an
|
|
62
|
+
* unrecorded fault left the source with nothing to re-feed, and a source miss
|
|
63
|
+
* reproduces a buffered ALLOW — so a live run that withheld the effect would
|
|
64
|
+
* have replayed as one that dispatched it.
|
|
65
|
+
*/
|
|
66
|
+
const ARBITER_FAULT_VERDICT: Readonly<Verdict> = Object.freeze({
|
|
67
|
+
decision: 'deny' as const,
|
|
68
|
+
reasonCode: 'ARBITER_UNAVAILABLE',
|
|
69
|
+
finality: 'context' as const,
|
|
70
|
+
})
|
|
71
|
+
|
|
42
72
|
/** How long an escalated intent is held awaiting a resolution before it degrades
|
|
43
73
|
* to a refusal — 2× the host-ack timeout, so a human has real time to answer. */
|
|
44
74
|
const ESCALATION_TTL_TICKS = 30
|
|
@@ -136,6 +166,7 @@ export class effectorController {
|
|
|
136
166
|
try { verdict = this._arbiter.evaluate( invocation ) }
|
|
137
167
|
catch( err ){
|
|
138
168
|
logger.error(`[policy] arbiter "${this._arbiter.name}" threw for "${invocation.schema}" — failing closed:`, err )
|
|
169
|
+
this._recordAndApply( instance, payload, invocation, ARBITER_FAULT_VERDICT )
|
|
139
170
|
return
|
|
140
171
|
}
|
|
141
172
|
|
|
@@ -145,7 +176,10 @@ export class effectorController {
|
|
|
145
176
|
// queue drains each tick, so a verdict landing a few ticks late still lands.
|
|
146
177
|
void verdict.then(
|
|
147
178
|
v => this._recordAndApply( instance, payload, invocation, v ),
|
|
148
|
-
err =>
|
|
179
|
+
err => {
|
|
180
|
+
logger.error(`[policy] arbiter "${this._arbiter.name}" rejected for "${invocation.schema}" — failing closed:`, err )
|
|
181
|
+
this._recordAndApply( instance, payload, invocation, ARBITER_FAULT_VERDICT )
|
|
182
|
+
},
|
|
149
183
|
)
|
|
150
184
|
return
|
|
151
185
|
}
|
|
@@ -218,7 +252,8 @@ export class effectorController {
|
|
|
218
252
|
intentId: invocation.intentId,
|
|
219
253
|
schema: invocation.schema,
|
|
220
254
|
reasonCode: verdict.reasonCode ?? 'POLICY_DENIED',
|
|
221
|
-
finality: verdict
|
|
255
|
+
finality: finalityOf( verdict ),
|
|
256
|
+
...( verdict.counterfactual ? { counterfactual: verdict.counterfactual } : {} ),
|
|
222
257
|
})
|
|
223
258
|
this._pendingRefusals.set( instance.config.id, queue )
|
|
224
259
|
return
|
|
@@ -272,7 +307,8 @@ export class effectorController {
|
|
|
272
307
|
this.confirmExecution( instance, refusal.intentId, {
|
|
273
308
|
success: false,
|
|
274
309
|
refused: true,
|
|
275
|
-
finality: refusal.finality
|
|
310
|
+
finality: refusal.finality,
|
|
311
|
+
...( refusal.counterfactual ? { counterfactual: refusal.counterfactual } : {} ),
|
|
276
312
|
description: `refused by policy: ${refusal.reasonCode} (${refusal.finality})`,
|
|
277
313
|
} )
|
|
278
314
|
}
|
|
@@ -317,7 +353,16 @@ export class effectorController {
|
|
|
317
353
|
}
|
|
318
354
|
}
|
|
319
355
|
|
|
320
|
-
/**
|
|
356
|
+
/**
|
|
357
|
+
* Degrade escalations no one answered in time into light refusals (P4).
|
|
358
|
+
*
|
|
359
|
+
* Finality 'parameter' is chosen for its BEHAVIOUR, not its name: silence is
|
|
360
|
+
* not literally an argument problem, but the light-dent-with-recovery it
|
|
361
|
+
* produces is exactly right — a Will whose asks go unanswered should ask
|
|
362
|
+
* progressively less, and should resume asking if someone starts answering.
|
|
363
|
+
* 'class' would be a lie (nobody said never) and 'context' would teach
|
|
364
|
+
* nothing, leaving the mind to escalate forever into an empty room.
|
|
365
|
+
*/
|
|
321
366
|
private _expireEscalations( instance: WillInstance, tick: number ): void {
|
|
322
367
|
const active = this._activeEscalations.get( instance.config.id )
|
|
323
368
|
if( !active || active.size === 0 ) return
|
|
@@ -325,14 +370,14 @@ export class effectorController {
|
|
|
325
370
|
if( tick < esc.expiresAt ) continue
|
|
326
371
|
active.delete( intentId )
|
|
327
372
|
this._clearEscalated( instance, intentId )
|
|
328
|
-
this._queueRefusal( instance, esc.intentId, esc.schema, 'ESCALATION_EXPIRED', '
|
|
373
|
+
this._queueRefusal( instance, esc.intentId, esc.schema, 'ESCALATION_EXPIRED', 'parameter')
|
|
329
374
|
logger.info(`[policy] escalation EXPIRED → refusing "${esc.schema}" intent "${intentId}"`)
|
|
330
375
|
}
|
|
331
376
|
}
|
|
332
377
|
|
|
333
378
|
/** Push a refusal onto the queue drained by _applyRefusals this same tick. */
|
|
334
379
|
private _queueRefusal(
|
|
335
|
-
instance: WillInstance, intentId: string, schema: string, reasonCode: string, finality:
|
|
380
|
+
instance: WillInstance, intentId: string, schema: string, reasonCode: string, finality: DenialFinality,
|
|
336
381
|
): void {
|
|
337
382
|
const queue = this._pendingRefusals.get( instance.config.id ) ?? []
|
|
338
383
|
queue.push({ intentId, schema, reasonCode, finality })
|
|
@@ -419,7 +464,9 @@ export class effectorController {
|
|
|
419
464
|
/** POLICY_REAFFERENCE P2 — set when the ack is a policy refusal, so the
|
|
420
465
|
* ReafferenceEngine routes it to availability rather than competence. */
|
|
421
466
|
refused?: boolean
|
|
422
|
-
finality?:
|
|
467
|
+
finality?: DenialFinality
|
|
468
|
+
/** ENVELOPE_NARROWING P0 — the bound that was exceeded, if the arbiter said. */
|
|
469
|
+
counterfactual?: PolicyCounterfactual
|
|
423
470
|
},
|
|
424
471
|
): void {
|
|
425
472
|
const tick = instance.tickCount
|
|
@@ -21,6 +21,19 @@ import { logger } from '#core/logger'
|
|
|
21
21
|
import type { OutboxMessage } from '#types'
|
|
22
22
|
import type { SessionLogger } from './session.logger'
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Referent → a deliverable address, and the room to use when none was chosen.
|
|
26
|
+
*
|
|
27
|
+
* Returns null when the referent is already an address (nothing to translate) or
|
|
28
|
+
* when the mind holds no route at all — in which case the row goes out as-is and
|
|
29
|
+
* the bridge's own roster fallback still applies, so a message is never silently
|
|
30
|
+
* dropped for want of a handle.
|
|
31
|
+
*/
|
|
32
|
+
export type OutboxRouting = (
|
|
33
|
+
targetEntityId: string,
|
|
34
|
+
chosenThread: string | undefined,
|
|
35
|
+
) => { targetEntityId: string; threadId?: string } | null
|
|
36
|
+
|
|
24
37
|
/** The caller-supplied fields of an outbox row; the writer stamps id + defaults. */
|
|
25
38
|
export interface OutboxRow {
|
|
26
39
|
targetEntityId: string
|
|
@@ -43,6 +56,7 @@ export class OutboxWriter {
|
|
|
43
56
|
* which made the embedded ids in `conversation.sent` diverge every run).
|
|
44
57
|
*/
|
|
45
58
|
private _seq = 0
|
|
59
|
+
private _routing: OutboxRouting | null = null
|
|
46
60
|
|
|
47
61
|
constructor( opts: { outbox?: OutboxMessage[]; willId?: string } = {} ){
|
|
48
62
|
this._outbox = opts.outbox ?? []
|
|
@@ -53,6 +67,21 @@ export class OutboxWriter {
|
|
|
53
67
|
this._sessionLogger = logger
|
|
54
68
|
}
|
|
55
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Turn a referent into somewhere the world can actually be spoken to.
|
|
72
|
+
*
|
|
73
|
+
* Injected rather than read here, because this writer is deliberately dumb —
|
|
74
|
+
* it holds no state and must stay replay-safe. Assembly closes over the state
|
|
75
|
+
* manager (the same shape as `attachMemorySink`).
|
|
76
|
+
*
|
|
77
|
+
* This is the ONE seam both send paths cross: ProactiveCommunicator's
|
|
78
|
+
* `enqueue()` and AuditionEngine's `enqueueReply()`. Translating anywhere else
|
|
79
|
+
* would mean doing it twice and getting it wrong once.
|
|
80
|
+
*/
|
|
81
|
+
attachRouting( resolve: OutboxRouting | null ): void {
|
|
82
|
+
this._routing = resolve
|
|
83
|
+
}
|
|
84
|
+
|
|
56
85
|
private _genId( suffix = ''): string {
|
|
57
86
|
return `outbox-${ this._willId }-${ ++this._seq }${ suffix }`
|
|
58
87
|
}
|
|
@@ -63,15 +92,24 @@ export class OutboxWriter {
|
|
|
63
92
|
*/
|
|
64
93
|
enqueue( row: OutboxRow, idSuffix = ''): string {
|
|
65
94
|
const id = this._genId( idSuffix )
|
|
95
|
+
// A `ke:` anchor is who, never where. Resolve it to an address the bridge can
|
|
96
|
+
// deliver to and a room to say it in — and where a room was already chosen
|
|
97
|
+
// (a reply answers into the thread it was asked in), that choice WINS. The
|
|
98
|
+
// mind picking a room is a decision; this is only the fallback for when it
|
|
99
|
+
// made none, and the alternative to the fallback is dropping the message.
|
|
100
|
+
const routed = this._routing?.( row.targetEntityId, row.threadId ) ?? null
|
|
101
|
+
const target = routed?.targetEntityId ?? row.targetEntityId
|
|
102
|
+
const thread = row.threadId ?? routed?.threadId
|
|
103
|
+
|
|
66
104
|
this._outbox.push({
|
|
67
105
|
id,
|
|
68
|
-
targetEntityId:
|
|
106
|
+
targetEntityId: target,
|
|
69
107
|
...( row.targetEntityName !== undefined ? { targetEntityName: row.targetEntityName } : {} ),
|
|
70
108
|
content: row.content,
|
|
71
109
|
effectorName: row.effectorName,
|
|
72
110
|
...( row.gestureType ? { gestureType: row.gestureType } : {} ),
|
|
73
111
|
...( row.replyToMessageId ? { replyToMessageId: row.replyToMessageId } : {} ),
|
|
74
|
-
...(
|
|
112
|
+
...( thread ? { threadId: thread } : {} ),
|
|
75
113
|
deliveryStatus: 'pending',
|
|
76
114
|
createdAtTick: 0,
|
|
77
115
|
createdAt: Date.now(),
|
|
@@ -1,102 +0,0 @@
|
|
|
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
|
-
}
|