@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,380 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/stem/tracts/transport.controller.ts
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// TransportController — owns the ExternalTransport ↔ tick-loop boundary.
|
|
6
|
+
//
|
|
7
|
+
// - attach() wires the transport's inbound stream into the instance's
|
|
8
|
+
// tick-stamped InboundQueue (NEVER applies inline)
|
|
9
|
+
// - applyInbound() drains the queue and applies every envelope at a fixed
|
|
10
|
+
// point in the tick (called by WillStem before step())
|
|
11
|
+
// - detach() tears the transport down
|
|
12
|
+
//
|
|
13
|
+
// DETERMINISM (see EXTERNAL_TRANSPORT_TODO.md § guardrails):
|
|
14
|
+
// - Inbound arrival (onInbound) only enqueues — application is deferred to the
|
|
15
|
+
// tick so timing is decoupled from the non-deterministic socket.
|
|
16
|
+
// - Result-acks cross into simulation state via confirmExecution(), applied
|
|
17
|
+
// synchronously before step() so the step sees them.
|
|
18
|
+
// - Messages/percepts are fire-and-forget: their async reasoning is recorded
|
|
19
|
+
// and replayed by the LLM layer, not the deterministic core, so the tick
|
|
20
|
+
// must not block on them.
|
|
21
|
+
// ─────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
import { logger } from '#core/logger'
|
|
24
|
+
import { wallClock } from '#core/wall.clock'
|
|
25
|
+
import { getInboundRecorder, getInboundSource } from '#core/inbound.recorder'
|
|
26
|
+
import type { SensoryInput } from '#senses/index'
|
|
27
|
+
import type { effectorInvocation, OutboxMessage } from '#types'
|
|
28
|
+
import type { WillInstance } from '#stem/index'
|
|
29
|
+
import type { InboundEnvelope, OutboundEnvelope } from './transport/types'
|
|
30
|
+
import type { effectorController } from './effector.controller'
|
|
31
|
+
import type { OutboxController } from './outbox.controller'
|
|
32
|
+
import type { SensoryController } from './sensory.controller'
|
|
33
|
+
import { AckReconciler } from './ack.reconciler'
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Cap on un-acked outbound envelopes buffered per Will for reconnect re-emit.
|
|
37
|
+
* A peer that never acks would otherwise grow `_pending` without bound; past the
|
|
38
|
+
* cap the oldest un-acked envelope is dropped (FIFO), matching the outbox TTL's
|
|
39
|
+
* "stale outbound eventually gives up" stance.
|
|
40
|
+
*/
|
|
41
|
+
const MAX_PENDING_PER_WILL = 1_000
|
|
42
|
+
|
|
43
|
+
/** Collaborators applyInbound() needs to route each inbound channel. */
|
|
44
|
+
export interface InboundApplyDeps {
|
|
45
|
+
effector: effectorController
|
|
46
|
+
outbox: OutboxController
|
|
47
|
+
sensory: SensoryController
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export class TransportController {
|
|
51
|
+
/** Monotonic outbound sequence number per Will — for peer ordering / dedup. */
|
|
52
|
+
private readonly _seq = new Map<string, number>()
|
|
53
|
+
/** Unsubscribe for the engine chunk-stream subscription, per Will. */
|
|
54
|
+
private readonly _chunkUnsub = new Map<string, () => void>()
|
|
55
|
+
/** Per-Will ack deduper for the dual (callback + discrete-event) ack paths. */
|
|
56
|
+
private readonly _reconcilers = new Map<string, AckReconciler>()
|
|
57
|
+
/** Activity-listener + status-listener unsubscribes, per Will. */
|
|
58
|
+
private readonly _activityUnsub = new Map<string, () => void>()
|
|
59
|
+
private readonly _statusUnsub = new Map<string, () => void>()
|
|
60
|
+
/**
|
|
61
|
+
* Un-acked outbound envelopes per Will (correlationId → envelope), for
|
|
62
|
+
* messages/invocations only — re-emitted when the transport reconnects.
|
|
63
|
+
* Replies/chunks/activity are ephemeral ('none') and not buffered.
|
|
64
|
+
*/
|
|
65
|
+
private readonly _pending = new Map<string, Map<string, OutboundEnvelope>>()
|
|
66
|
+
|
|
67
|
+
private _pendingFor( id: string ): Map<string, OutboundEnvelope> {
|
|
68
|
+
let m = this._pending.get( id )
|
|
69
|
+
if( !m ){ m = new Map(); this._pending.set( id, m ) }
|
|
70
|
+
return m
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private _nextSeq( id: string ): number {
|
|
74
|
+
const n = ( this._seq.get( id ) ?? 0 ) + 1
|
|
75
|
+
this._seq.set( id, n )
|
|
76
|
+
return n
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private _reconciler( id: string ): AckReconciler {
|
|
80
|
+
let r = this._reconcilers.get( id )
|
|
81
|
+
if( !r ){ r = new AckReconciler(); this._reconcilers.set( id, r ) }
|
|
82
|
+
return r
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Emit an outbound envelope. When `reconcileAs` is set, the socket.io emit-callback
|
|
87
|
+
* ack is marshaled back onto the tick-stamped InboundQueue as an `ack` envelope —
|
|
88
|
+
* the SAME path discrete ack events take — so applyInbound() reconciles both
|
|
89
|
+
* idempotently. 'none' = fire-and-forget (chunks, replies: no outbox entry to mark).
|
|
90
|
+
*/
|
|
91
|
+
private _emit( instance: WillInstance, env: OutboundEnvelope, reconcileAs: 'delivery' | 'result' | 'none' ): void {
|
|
92
|
+
const transport = instance.transport
|
|
93
|
+
if( !transport ) return
|
|
94
|
+
|
|
95
|
+
// Buffer state-affecting outbound until acked, so a reconnect can re-emit it.
|
|
96
|
+
if( reconcileAs !== 'none' ){
|
|
97
|
+
const pending = this._pendingFor( instance.config.id )
|
|
98
|
+
pending.set( env.correlationId, env )
|
|
99
|
+
// Bound the buffer — drop the oldest un-acked envelope past the cap (FIFO).
|
|
100
|
+
while( pending.size > MAX_PENDING_PER_WILL ){
|
|
101
|
+
const oldest = pending.keys().next().value
|
|
102
|
+
if( oldest === undefined ) break
|
|
103
|
+
pending.delete( oldest )
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const pending = transport.emit( env )
|
|
108
|
+
if( reconcileAs === 'none' ){ void pending; return }
|
|
109
|
+
|
|
110
|
+
void pending.then( ack => {
|
|
111
|
+
if( !ack.acked ) return
|
|
112
|
+
const result = reconcileAs === 'result' ? this._asResult( ack.payload ) : null
|
|
113
|
+
instance.inbound.enqueue(
|
|
114
|
+
result
|
|
115
|
+
? { channel: 'ack', ackKind: 'result', result, willId: instance.config.id, correlationId: env.correlationId, seq: 0, wallTime: wallClock() }
|
|
116
|
+
: { channel: 'ack', ackKind: 'delivery', delivered: true, willId: instance.config.id, correlationId: env.correlationId, seq: 0, wallTime: wallClock() }
|
|
117
|
+
)
|
|
118
|
+
} ).catch( () => {} )
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Coerce an emit-callback ack payload into a confirmExecution result, or null. */
|
|
122
|
+
private _asResult( payload: unknown ): { success: boolean; description: string; metrics?: Record<string, number> } | null {
|
|
123
|
+
if( payload && typeof payload === 'object' && typeof ( payload as { success?: unknown } ).success === 'boolean' ){
|
|
124
|
+
const p = payload as { success: boolean; description?: unknown; metrics?: Record<string, number> }
|
|
125
|
+
return { success: p.success, description: String( p.description ?? '' ), ...( p.metrics ? { metrics: p.metrics } : {} ) }
|
|
126
|
+
}
|
|
127
|
+
return null
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Emit a SessionLogger entry as a `session_log` observability envelope.
|
|
132
|
+
* Fire-and-forget — the consumer's transport owns persistence. No-op without a
|
|
133
|
+
* transport (dev file logging still happens inside SessionLogger).
|
|
134
|
+
*/
|
|
135
|
+
emitSessionLog( instance: WillInstance, entry: Record<string, unknown> ): void {
|
|
136
|
+
if( !instance.transport ) return
|
|
137
|
+
const seq = this._nextSeq( instance.config.id )
|
|
138
|
+
this._emit( instance, {
|
|
139
|
+
channel: 'session_log',
|
|
140
|
+
willId: instance.config.id,
|
|
141
|
+
correlationId: `session_log-${seq}`,
|
|
142
|
+
seq,
|
|
143
|
+
wallTime: wallClock(),
|
|
144
|
+
entry,
|
|
145
|
+
}, 'none' )
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Emit an attributed token/cost ledger record as a `token_report` envelope so
|
|
150
|
+
* the consumer can meter/re-bill from the stream. Fire-and-forget.
|
|
151
|
+
*/
|
|
152
|
+
emitTokenReport( instance: WillInstance, report: Record<string, unknown> ): void {
|
|
153
|
+
if( !instance.transport ) return
|
|
154
|
+
const seq = this._nextSeq( instance.config.id )
|
|
155
|
+
this._emit( instance, {
|
|
156
|
+
channel: 'token_report',
|
|
157
|
+
willId: instance.config.id,
|
|
158
|
+
correlationId: `token_report-${seq}`,
|
|
159
|
+
seq,
|
|
160
|
+
wallTime: wallClock(),
|
|
161
|
+
report,
|
|
162
|
+
}, 'none' )
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Wire the transport's inbound stream into the instance's tick-stamped queue,
|
|
167
|
+
* and the engines' outbound fast-paths (reply) into transport emits.
|
|
168
|
+
*/
|
|
169
|
+
attach( instance: WillInstance ): void {
|
|
170
|
+
const transport = instance.transport
|
|
171
|
+
if( !transport ) return
|
|
172
|
+
|
|
173
|
+
instance._transportUnsub = transport.onInbound( env => instance.inbound.enqueue( env ) )
|
|
174
|
+
|
|
175
|
+
// Outbound reply fast-path (2.1): emit the instant the facet decides — off-tick.
|
|
176
|
+
// 'none': the reply bubbles aren't in the outbox when a transport is present
|
|
177
|
+
// (AuditionEngine skips the outbox push), so there's no delivery-ack to apply.
|
|
178
|
+
instance.cognition.auditionEngine.attachReplyCallback( ( entityId, threadId, bubbles ) => {
|
|
179
|
+
this._emit( instance, {
|
|
180
|
+
channel: 'reply',
|
|
181
|
+
willId: instance.config.id,
|
|
182
|
+
correlationId: `reply-${entityId}-${wallClock()}`,
|
|
183
|
+
seq: this._nextSeq( instance.config.id ),
|
|
184
|
+
wallTime: wallClock(),
|
|
185
|
+
entityId,
|
|
186
|
+
threadId,
|
|
187
|
+
bubbles,
|
|
188
|
+
}, 'none' )
|
|
189
|
+
} )
|
|
190
|
+
|
|
191
|
+
// Outbound chunk fast-path (2.2): stream each filtered reply token off-tick.
|
|
192
|
+
// Multi-subscriber chunk callback — coexists with the SSE fan-out.
|
|
193
|
+
const chunkUnsub = instance.cognition.auditionEngine.addChunkCallback( ( entityId, threadId, content ) => {
|
|
194
|
+
this._emit( instance, {
|
|
195
|
+
channel: 'chunk',
|
|
196
|
+
willId: instance.config.id,
|
|
197
|
+
correlationId: `chunk-${entityId}`,
|
|
198
|
+
seq: this._nextSeq( instance.config.id ),
|
|
199
|
+
wallTime: wallClock(),
|
|
200
|
+
entityId,
|
|
201
|
+
threadId,
|
|
202
|
+
content,
|
|
203
|
+
}, 'none' )
|
|
204
|
+
} )
|
|
205
|
+
this._chunkUnsub.set( instance.config.id, chunkUnsub )
|
|
206
|
+
|
|
207
|
+
// Outbound activity projection (2.5): forward all plan/activity events
|
|
208
|
+
// ('*' wildcard = no entity filter). Observability only — fire-and-forget.
|
|
209
|
+
const activityUnsub = instance.cognition.planningEngine.addActivityListener( '*', event => {
|
|
210
|
+
this._emit( instance, {
|
|
211
|
+
channel: 'activity',
|
|
212
|
+
willId: instance.config.id,
|
|
213
|
+
correlationId: `activity-${event.planId}-${event.type}-${wallClock()}`,
|
|
214
|
+
seq: this._nextSeq( instance.config.id ),
|
|
215
|
+
wallTime: wallClock(),
|
|
216
|
+
entityId: event.requestingEntityId ?? '',
|
|
217
|
+
eventType: event.type,
|
|
218
|
+
payload: { ...event },
|
|
219
|
+
}, 'none' )
|
|
220
|
+
} )
|
|
221
|
+
this._activityUnsub.set( instance.config.id, activityUnsub )
|
|
222
|
+
|
|
223
|
+
// Reconnect: re-emit un-acked messages/invocations when the link returns.
|
|
224
|
+
const statusUnsub = transport.onStatus( status => {
|
|
225
|
+
if( status === 'connected' ) this._reemitPending( instance )
|
|
226
|
+
} )
|
|
227
|
+
this._statusUnsub.set( instance.config.id, statusUnsub )
|
|
228
|
+
|
|
229
|
+
logger.info(`[transport] attached inbound + outbound streams for ${instance.config.id}`)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Re-emit un-acked outbound envelopes after the transport reconnects. */
|
|
233
|
+
private _reemitPending( instance: WillInstance ): void {
|
|
234
|
+
const pending = this._pending.get( instance.config.id )
|
|
235
|
+
if( !pending || pending.size === 0 ) return
|
|
236
|
+
|
|
237
|
+
logger.info(`[transport] reconnect — re-emitting ${pending.size} un-acked envelope(s) for ${instance.config.id}`)
|
|
238
|
+
for( const env of [ ...pending.values() ] )
|
|
239
|
+
this._emit( instance, env, env.channel === 'effector_invocation' ? 'result' : 'delivery' )
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Emit drained external effector invocations over the transport (2.4). Called
|
|
244
|
+
* by the tick loop after `pendingEffectorInvocations` is spliced. The peer
|
|
245
|
+
* executes each and returns a result-ack (correlationId = decisionRecordId),
|
|
246
|
+
* which inbound dispatch routes to `confirmExecution`.
|
|
247
|
+
*/
|
|
248
|
+
emitInvocations( instance: WillInstance, invocations: effectorInvocation[] ): void {
|
|
249
|
+
if( !instance.transport || invocations.length === 0 ) return
|
|
250
|
+
|
|
251
|
+
for( const invocation of invocations )
|
|
252
|
+
// 'result': the peer's ack (callback or discrete event) carries the execution
|
|
253
|
+
// result → reconciled once → confirmExecution.
|
|
254
|
+
this._emit( instance, {
|
|
255
|
+
channel: 'effector_invocation',
|
|
256
|
+
willId: instance.config.id,
|
|
257
|
+
correlationId: invocation.decisionRecordId,
|
|
258
|
+
seq: this._nextSeq( instance.config.id ),
|
|
259
|
+
wallTime: wallClock(),
|
|
260
|
+
invocation,
|
|
261
|
+
}, 'result' )
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Emit drained outbox messages over the transport (2.3). Called by the tick loop
|
|
266
|
+
* after the outbox is spliced. Facet replies are NOT here — they go via the 2.1
|
|
267
|
+
* fast-path and skip the outbox when a transport is present, so this carries only
|
|
268
|
+
* master/action messages (talk/broadcast/gesture). correlationId = message id, so
|
|
269
|
+
* a delivery-ack marks the right outbox row via confirmDelivery.
|
|
270
|
+
*/
|
|
271
|
+
emitOutbox( instance: WillInstance, messages: OutboxMessage[] ): void {
|
|
272
|
+
if( !instance.transport || messages.length === 0 ) return
|
|
273
|
+
|
|
274
|
+
for( const message of messages )
|
|
275
|
+
this._emit( instance, {
|
|
276
|
+
channel: 'message',
|
|
277
|
+
willId: instance.config.id,
|
|
278
|
+
correlationId: message.id,
|
|
279
|
+
seq: this._nextSeq( instance.config.id ),
|
|
280
|
+
wallTime: wallClock(),
|
|
281
|
+
message,
|
|
282
|
+
}, 'delivery' )
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Tear down the transport and discard any un-applied inbound. */
|
|
286
|
+
detach( instance: WillInstance ): void {
|
|
287
|
+
instance._transportUnsub?.()
|
|
288
|
+
instance._transportUnsub = null
|
|
289
|
+
this._chunkUnsub.get( instance.config.id )?.()
|
|
290
|
+
this._chunkUnsub.delete( instance.config.id )
|
|
291
|
+
this._activityUnsub.get( instance.config.id )?.()
|
|
292
|
+
this._activityUnsub.delete( instance.config.id )
|
|
293
|
+
this._statusUnsub.get( instance.config.id )?.()
|
|
294
|
+
this._statusUnsub.delete( instance.config.id )
|
|
295
|
+
instance.transport?.close()
|
|
296
|
+
// Null the handle so any engine callback that still holds this instance (e.g.
|
|
297
|
+
// the AuditionEngine single-slot reply callback, which has no unsubscribe) sees
|
|
298
|
+
// no transport and its _emit() becomes a no-op instead of emitting on a closed link.
|
|
299
|
+
instance.transport = null
|
|
300
|
+
instance.inbound.clear()
|
|
301
|
+
this._seq.delete( instance.config.id )
|
|
302
|
+
this._reconcilers.delete( instance.config.id )
|
|
303
|
+
this._pending.delete( instance.config.id )
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Drain the inbound queue and apply every envelope deterministically at `tick`.
|
|
308
|
+
* Called once per tick BEFORE simulation.step().
|
|
309
|
+
*/
|
|
310
|
+
applyInbound( instance: WillInstance, tick: number, deps: InboundApplyDeps ): void {
|
|
311
|
+
const willId = instance.config.id
|
|
312
|
+
const source = getInboundSource( willId )
|
|
313
|
+
|
|
314
|
+
// Replay re-feed (1.3): when a source is registered, pull the recorded
|
|
315
|
+
// envelopes for this tick instead of draining the live socket queue — the
|
|
316
|
+
// transport analog of the LLM completion re-feed. A live run drains the queue.
|
|
317
|
+
const envelopes: InboundEnvelope[] = source
|
|
318
|
+
? ( source.envelopesAt( tick ) as InboundEnvelope[] )
|
|
319
|
+
: instance.inbound.drain( tick ).map( s => s.envelope )
|
|
320
|
+
|
|
321
|
+
if( envelopes.length === 0 ) return
|
|
322
|
+
|
|
323
|
+
// Record the batch (live runs only — a source means we are replaying). The
|
|
324
|
+
// transport is a non-deterministic input; capturing each envelope at its
|
|
325
|
+
// applied tick lets a deterministic re-execution re-inject them.
|
|
326
|
+
if( !source ){
|
|
327
|
+
const recorder = getInboundRecorder( willId )
|
|
328
|
+
if( recorder )
|
|
329
|
+
for( const envelope of envelopes )
|
|
330
|
+
recorder.recordInbound({ tick, willId, envelope, timestamp: wallClock() })
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
for( const envelope of envelopes ){
|
|
334
|
+
try { this._dispatch( instance, envelope, deps ) }
|
|
335
|
+
catch( err ){ logger.error(`[transport] inbound dispatch failed (${envelope.channel}):`, err ) }
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
private _dispatch( instance: WillInstance, env: InboundEnvelope, deps: InboundApplyDeps ): void {
|
|
340
|
+
switch( env.channel ){
|
|
341
|
+
case 'inbound_message': {
|
|
342
|
+
// Fire-and-forget: AuditionEngine.ingest() serializes per-entity
|
|
343
|
+
// internally; the tick must not block on the LLM reply.
|
|
344
|
+
const input: SensoryInput = env.kind === 'voice'
|
|
345
|
+
? { kind: 'voice', entityId: env.entityId, threadId: env.threadId, transcription: env.content }
|
|
346
|
+
: {
|
|
347
|
+
kind: 'text',
|
|
348
|
+
entityId: env.entityId,
|
|
349
|
+
threadId: env.threadId,
|
|
350
|
+
content: env.content,
|
|
351
|
+
...( env.speakerName ? { speakerName: env.speakerName } : {} ),
|
|
352
|
+
}
|
|
353
|
+
void instance.cognition.auditionEngine.ingest( input )
|
|
354
|
+
break
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
case 'inbound_percept':
|
|
358
|
+
// Materialize as an external event entity; perceptual engines pick it up next tick.
|
|
359
|
+
deps.sensory.injectEvent( instance, { type: `senses.${env.domain}`, payload: env.payload } )
|
|
360
|
+
break
|
|
361
|
+
|
|
362
|
+
case 'ack': {
|
|
363
|
+
// Idempotent across the dual paths: the emit-callback ack and the discrete
|
|
364
|
+
// ack event share a correlationId — apply only the first.
|
|
365
|
+
if( !this._reconciler( instance.config.id ).shouldApply( env.correlationId ) ) break
|
|
366
|
+
|
|
367
|
+
// Acked — drop it from the reconnect re-emit buffer.
|
|
368
|
+
this._pending.get( instance.config.id )?.delete( env.correlationId )
|
|
369
|
+
|
|
370
|
+
if( env.ackKind === 'result' && env.result )
|
|
371
|
+
// Reafference — crosses into deterministic state via confirmExecution().
|
|
372
|
+
deps.effector.confirmExecution( instance, env.correlationId, env.result )
|
|
373
|
+
else
|
|
374
|
+
// Delivery receipt — outbox status update.
|
|
375
|
+
deps.outbox.confirmDelivery( instance, env.correlationId, env.delivered ?? true )
|
|
376
|
+
break
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { EngineRegistry } from '#cognition/index'
|
|
2
|
+
import type { ReadonlySimulationState, StateCommands } from '#core/types'
|
|
3
|
+
import type { OutboxWriter } from '#stem/tracts/outbox.writer'
|
|
4
|
+
import type { SchemaRepertoire } from '#agency/schemas/repertoire'
|
|
5
|
+
import type { AccessGrants } from '#agency/access.grants'
|
|
6
|
+
|
|
7
|
+
export interface Cognition extends EngineRegistry {
|
|
8
|
+
/** Shared outbox producer (row shape + reply audit); see #stem/tracts/outbox.writer. */
|
|
9
|
+
outboxWriter: OutboxWriter
|
|
10
|
+
/** Agency pipeline competence layer (learned schemas + skills) — see #agency. */
|
|
11
|
+
schemaRepertoire: SchemaRepertoire
|
|
12
|
+
/** Permission / sense-gate authority (replaces effectorRegistry's grant role). */
|
|
13
|
+
accessGrants: AccessGrants
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Emitted when Will decides to use an effector that has no internal handler.
|
|
18
|
+
* The host system receives this via SSE and is responsible for execution.
|
|
19
|
+
* When done, the host calls injectEvent() with the result.
|
|
20
|
+
*/
|
|
21
|
+
export interface effectorInvocation {
|
|
22
|
+
id: string
|
|
23
|
+
/** Correlation handle — the awaiting `agency.intent` id. Echo it when POSTing to
|
|
24
|
+
* `POST /v1/wills/:id/effectors/invoked/ack`; the Will reconciles the result onto
|
|
25
|
+
* that intent. (Field name kept for wire-contract stability; no longer a
|
|
26
|
+
* `decision.record` id since the agency cutover.) */
|
|
27
|
+
decisionRecordId: string
|
|
28
|
+
effectorName: string
|
|
29
|
+
parameters: Record<string, unknown>
|
|
30
|
+
targetEntityId: string | undefined
|
|
31
|
+
reasoning: string
|
|
32
|
+
tick: number
|
|
33
|
+
timestamp: number
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface WorldEntity {
|
|
37
|
+
id: string
|
|
38
|
+
type: string
|
|
39
|
+
name: string
|
|
40
|
+
description: string
|
|
41
|
+
/** What effectors can be used on this entity */
|
|
42
|
+
affordances: string[] // Effector names
|
|
43
|
+
/** Current state */
|
|
44
|
+
state: Record<string, unknown>
|
|
45
|
+
/** Is this entity currently reachable/interactable */
|
|
46
|
+
reachable: boolean
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface ActionRequest {
|
|
50
|
+
/** The effector being invoked */
|
|
51
|
+
effector: string
|
|
52
|
+
/** Parameters for the effector */
|
|
53
|
+
parameters: Record<string, unknown>
|
|
54
|
+
/** Target entity (if any) */
|
|
55
|
+
targetEntityId?: string
|
|
56
|
+
/** The Will's reasoning for this action */
|
|
57
|
+
reasoning: string
|
|
58
|
+
/** Expected outcome description */
|
|
59
|
+
expectedOutcome: string
|
|
60
|
+
/** The tick when this was decided */
|
|
61
|
+
decidedAt: number
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface ActionResult {
|
|
65
|
+
/** Whether the action succeeded */
|
|
66
|
+
success: boolean
|
|
67
|
+
/** Human-readable description of what happened */
|
|
68
|
+
description: string
|
|
69
|
+
/** State changes that occurred as a result */
|
|
70
|
+
commands: StateCommands
|
|
71
|
+
/** New entities created by this action */
|
|
72
|
+
createdEntities?: WorldEntity[]
|
|
73
|
+
/** Feedback for the Will to learn from */
|
|
74
|
+
feedback: {
|
|
75
|
+
outcomeQuality: number // 0-1: how good was the outcome
|
|
76
|
+
surprise: number // 0-1: how unexpected was the outcome
|
|
77
|
+
lessons: string[] // What can be learned
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface WorldInterface {
|
|
82
|
+
/** Get all entities the Will can perceive right now */
|
|
83
|
+
getPerceptibleEntities( state: ReadonlySimulationState ): WorldEntity[]
|
|
84
|
+
|
|
85
|
+
/** Get entities the Will can interact with */
|
|
86
|
+
getInteractableEntities( state: ReadonlySimulationState ): WorldEntity[]
|
|
87
|
+
|
|
88
|
+
/** Returns true if this world can handle the given effector name. */
|
|
89
|
+
canHandle( effectorName: string ): boolean
|
|
90
|
+
|
|
91
|
+
/** Execute a Will's action in the world */
|
|
92
|
+
executeAction( request: ActionRequest, state: ReadonlySimulationState ): Promise<ActionResult>
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface OutboxMessage {
|
|
96
|
+
id: string
|
|
97
|
+
targetEntityId: string
|
|
98
|
+
targetEntityName?: string
|
|
99
|
+
content: string
|
|
100
|
+
effectorName: 'talk' | 'text' | 'gesture' | 'broadcast'
|
|
101
|
+
gestureType?: string // for gesture effector
|
|
102
|
+
createdAtTick: number
|
|
103
|
+
createdAt: number
|
|
104
|
+
/** If this message is a reply, the incoming message ID it is replying to. */
|
|
105
|
+
replyToMessageId?: string
|
|
106
|
+
/**
|
|
107
|
+
* Conversation thread this message belongs to.
|
|
108
|
+
* Set by AuditionEngine/OutboxWriter.enqueueReply() — ties all bubbles from one exchange
|
|
109
|
+
* to the same thread so the SSE consumer can group them into a single reply_complete.
|
|
110
|
+
*/
|
|
111
|
+
threadId?: string
|
|
112
|
+
/** Delivery lifecycle: 'pending' until the SSE consumer confirms receipt. */
|
|
113
|
+
deliveryStatus: 'pending' | 'delivered' | 'failed'
|
|
114
|
+
/** Token usage from the outbound() composition call — used for exchange billing. */
|
|
115
|
+
usage?: { promptTokens: number; completionTokens: number }
|
|
116
|
+
}
|