@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,168 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/stem/tracts/sensory.controller.ts — per-Will senses I/O subsystem
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// SensoryController owns the senses input + LLM chunk-streaming output
|
|
6
|
+
// boundary extracted from WillStem (R5-e):
|
|
7
|
+
// - input ingestion: ingestText / ingestSensory / injectEvent
|
|
8
|
+
// - real-time chunk streaming: addChunkListener / addSensoryChunkListener
|
|
9
|
+
// and their sync helpers (syncChunkBroadcaster + the addChunkCallback fan-out)
|
|
10
|
+
//
|
|
11
|
+
// WillStem delegates the matching public methods here, so the god-object no
|
|
12
|
+
// longer owns this concern. The ops touch only WillInstance fields (no Will
|
|
13
|
+
// id needed), so these methods take the resolved instance directly; WillStem
|
|
14
|
+
// still validates existence via _get(id) before delegating.
|
|
15
|
+
//
|
|
16
|
+
// Behaviour is preserved verbatim from the original WillStem methods;
|
|
17
|
+
// this is a pure extract-collaborator refactor.
|
|
18
|
+
// ─────────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
import type { TextMessage, SensoryInput } from '#senses/index'
|
|
21
|
+
import type { WillInstance } from '#stem/index'
|
|
22
|
+
|
|
23
|
+
export class SensoryController {
|
|
24
|
+
/** Instances whose SSE chunk fan-out has already been registered on the engine. */
|
|
25
|
+
private readonly _chunkWired = new WeakSet<WillInstance>()
|
|
26
|
+
/** Per-Will monotonic counter for deterministic injected-event ids (replay). */
|
|
27
|
+
private readonly _eventSeq = new WeakMap<WillInstance, number>()
|
|
28
|
+
|
|
29
|
+
// ── Input ingestion ──────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Route an external text message through the Will's AuditionEngine.
|
|
33
|
+
* The senses-aware AuditionEngine handles salience, thread digests,
|
|
34
|
+
* LanguagePercept publication, conversation-facet spawn/reuse, and LLM
|
|
35
|
+
* reply generation. Reply delivery is async via the outbox / SSE channel.
|
|
36
|
+
*/
|
|
37
|
+
async ingestText( instance: WillInstance, input: TextMessage ): Promise<void> {
|
|
38
|
+
await instance.cognition.auditionEngine.ingest( input )
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Route a raw SensoryInput to the appropriate sense engine by domain.
|
|
43
|
+
* Used by the debug `POST /senses/:domain/ingest` route.
|
|
44
|
+
*/
|
|
45
|
+
async ingestSensory( instance: WillInstance, domain: string, input: SensoryInput ): Promise<void> {
|
|
46
|
+
const cog = instance.cognition
|
|
47
|
+
const engineMap: Record<string, { ingest: (i: SensoryInput) => Promise<void> }> = {
|
|
48
|
+
audition: cog.auditionEngine,
|
|
49
|
+
vision: cog.visionEngine,
|
|
50
|
+
somatosensation: cog.somatosensationEngine,
|
|
51
|
+
olfaction: cog.olfactionEngine,
|
|
52
|
+
gustation: cog.gustationEngine,
|
|
53
|
+
}
|
|
54
|
+
const engine = engineMap[ domain ]
|
|
55
|
+
if( !engine ) throw Object.assign( new Error( `Unknown sense domain: ${domain}` ), { code: 400 } )
|
|
56
|
+
await engine.ingest( input )
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Inject a percept or external event into the Will's world.
|
|
61
|
+
* The event is picked up by perceptual engines on the next tick.
|
|
62
|
+
*/
|
|
63
|
+
injectEvent( instance: WillInstance, event: { type: string; payload: Record<string, unknown> } ): void {
|
|
64
|
+
// Deterministic id (tick + per-Will monotonic counter) so injected-event
|
|
65
|
+
// entities reproduce on replay — they're compared in ReplayComparison, and
|
|
66
|
+
// Date.now()+random made them diverge every run. createdAt/updatedAt are left
|
|
67
|
+
// to StateManager.setEntity, which stamps the (deterministic) sim clock time.
|
|
68
|
+
const seq = ( this._eventSeq.get( instance ) ?? 0 ) + 1
|
|
69
|
+
this._eventSeq.set( instance, seq )
|
|
70
|
+
|
|
71
|
+
instance.simulation.stateManager.setEntity({
|
|
72
|
+
id: `external-event-${instance.tickCount}-${seq}`,
|
|
73
|
+
type: event.type,
|
|
74
|
+
metadata: event.payload,
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ── Real-time LLM chunk streaming (F3) ───────────────────────
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Subscribe to real-time LLM token chunks for this Will (Will-wide).
|
|
82
|
+
* The callback fires for each text token during executive LLM generation.
|
|
83
|
+
* Returns an unsubscribe function — call it when the SSE connection closes.
|
|
84
|
+
*/
|
|
85
|
+
addChunkListener( instance: WillInstance, fn: ( chunk: string ) => void ): () => void {
|
|
86
|
+
instance.chunkListeners.add( fn )
|
|
87
|
+
this.syncChunkBroadcaster( instance )
|
|
88
|
+
|
|
89
|
+
return () => {
|
|
90
|
+
instance.chunkListeners.delete( fn )
|
|
91
|
+
this.syncChunkBroadcaster( instance )
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Subscribe to real-time LLM token chunks for a specific conversation entity.
|
|
97
|
+
*
|
|
98
|
+
* Unlike addChunkListener() (Will-wide), this fires only when the AuditionEngine
|
|
99
|
+
* conversation facet for `entityId` is actively streaming — isolating chunks to the
|
|
100
|
+
* relevant SSE/WS client connection.
|
|
101
|
+
*
|
|
102
|
+
* On the first listener for any entity, wires addChunkCallback() on the
|
|
103
|
+
* AuditionEngine to fan out chunks by entityId. Subsequent listeners reuse the same
|
|
104
|
+
* callback — no duplicate registration occurs.
|
|
105
|
+
*
|
|
106
|
+
* Returns an unsubscribe function — call it when the SSE/WS connection closes.
|
|
107
|
+
*/
|
|
108
|
+
addSensoryChunkListener(
|
|
109
|
+
instance: WillInstance,
|
|
110
|
+
entityId: string,
|
|
111
|
+
fn: ( chunk: string ) => void,
|
|
112
|
+
): () => void {
|
|
113
|
+
let listeners = instance.sensoryChunkListeners.get( entityId )
|
|
114
|
+
if( !listeners ){
|
|
115
|
+
listeners = new Set()
|
|
116
|
+
instance.sensoryChunkListeners.set( entityId, listeners )
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
listeners.add( fn )
|
|
120
|
+
|
|
121
|
+
// Register the SSE fan-out on the engine once per instance. It reads the live
|
|
122
|
+
// sensoryChunkListeners map, so it stays correct as listeners come and go, and
|
|
123
|
+
// coexists with other chunk subscribers (e.g. the transport emit) — the engine
|
|
124
|
+
// chunk callback is multi-subscriber.
|
|
125
|
+
this._ensureChunkFanout( instance )
|
|
126
|
+
|
|
127
|
+
return () => {
|
|
128
|
+
listeners!.delete( fn )
|
|
129
|
+
if( listeners!.size === 0 )
|
|
130
|
+
instance.sensoryChunkListeners.delete( entityId )
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Register the per-instance SSE chunk fan-out on the AuditionEngine (idempotent). */
|
|
135
|
+
private _ensureChunkFanout( instance: WillInstance ): void {
|
|
136
|
+
if( this._chunkWired.has( instance ) ) return
|
|
137
|
+
const audition = instance.cognition.auditionEngine
|
|
138
|
+
if( typeof ( audition as { addChunkCallback?: unknown } ).addChunkCallback !== 'function' ) return
|
|
139
|
+
|
|
140
|
+
this._chunkWired.add( instance )
|
|
141
|
+
audition.addChunkCallback( ( entityId: string, _threadId: string, chunk: string ) => {
|
|
142
|
+
const fns = instance.sensoryChunkListeners.get( entityId )
|
|
143
|
+
if( !fns ) return
|
|
144
|
+
for( const fn of fns ){
|
|
145
|
+
try { fn( chunk ) } catch {}
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
syncChunkBroadcaster( instance: WillInstance ): void {
|
|
151
|
+
// Get the executive engine (typed loosely to avoid circular imports)
|
|
152
|
+
const exec = ( instance.cognition as unknown as Record<string, unknown> )?.executiveEngine as
|
|
153
|
+
{ setChunkBroadcaster?: ( fn: (( c: string ) => void) | null ) => void } | undefined
|
|
154
|
+
if( !exec?.setChunkBroadcaster ) return
|
|
155
|
+
|
|
156
|
+
if( instance.chunkListeners.size === 0 ){
|
|
157
|
+
exec.setChunkBroadcaster( null )
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
// Single broadcaster that fans out to all listeners
|
|
161
|
+
exec.setChunkBroadcaster(( chunk: string ) => {
|
|
162
|
+
for( const fn of instance.chunkListeners ){
|
|
163
|
+
try { fn( chunk ) } catch {}
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/stem/session.logger.ts
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// Captures every significant event in a Will's cognitive cycle:
|
|
6
|
+
//
|
|
7
|
+
// tick — per-tick metrics snapshot + entity counts
|
|
8
|
+
// event — every semantic event from the EventBus
|
|
9
|
+
// executive.call — LLM call metadata (model, prompt size, path)
|
|
10
|
+
// executive.response — LLM response (tokens, latency, raw excerpt)
|
|
11
|
+
// executive.output — parsed structured output (actions, beliefs, ...)
|
|
12
|
+
// action.execute — effector execution result and feedback
|
|
13
|
+
// belief.integrate — belief written to store (pre/post confidence cap)
|
|
14
|
+
// conversation.in — inbound message from an external entity
|
|
15
|
+
// conversation.out — outbound message composed by the Will
|
|
16
|
+
// session.start — written once on logger creation
|
|
17
|
+
// session.end — written on close()
|
|
18
|
+
//
|
|
19
|
+
// Transport: every entry is forwarded (via `emit`/`attachEmit`) to the stem,
|
|
20
|
+
// which emits it as a `session_log` envelope on the Will's ExternalTransport —
|
|
21
|
+
// so the product wrapper / consumer owns persistence (Postgres, S3, Kafka,
|
|
22
|
+
// SSE …). The Will itself no longer uploads to S3.
|
|
23
|
+
//
|
|
24
|
+
// Files: in DEVELOPMENT only (see `fileLoggingEnabled()`), the same NDJSON is
|
|
25
|
+
// also written to data/wills/{willId}/sessions/{sessionId}.jsonl. One JSON
|
|
26
|
+
// object per line; parse with `jq`, Python, or any NDJSON reader.
|
|
27
|
+
// ─────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
import { createWriteStream, mkdirSync } from 'node:fs'
|
|
30
|
+
import { join } from 'node:path'
|
|
31
|
+
import type { WriteStream } from 'node:fs'
|
|
32
|
+
|
|
33
|
+
/** Forwards each entry to the consumer (the stem bridges it onto the transport). */
|
|
34
|
+
export type SessionLogEmit = ( record: Record<string, unknown> ) => void
|
|
35
|
+
|
|
36
|
+
// ── Entry type union ──────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
export type LogEntryType =
|
|
39
|
+
| 'session.start'
|
|
40
|
+
| 'session.end'
|
|
41
|
+
| 'tick'
|
|
42
|
+
| 'event'
|
|
43
|
+
| 'executive.call'
|
|
44
|
+
| 'executive.response'
|
|
45
|
+
| 'executive.output'
|
|
46
|
+
| 'executive.facet.spawn'
|
|
47
|
+
| 'executive.facet.call'
|
|
48
|
+
| 'executive.facet.response'
|
|
49
|
+
| 'executive.facet.output'
|
|
50
|
+
| 'executive.facet.destroy'
|
|
51
|
+
| 'action.execute'
|
|
52
|
+
| 'action.error'
|
|
53
|
+
| 'action.outcome'
|
|
54
|
+
| 'belief.integrate'
|
|
55
|
+
| 'conversation.in'
|
|
56
|
+
| 'conversation.out'
|
|
57
|
+
| 'plan.step.activated'
|
|
58
|
+
| 'plan.step.outcome'
|
|
59
|
+
| 'goal.progress'
|
|
60
|
+
| 'goal.achieved'
|
|
61
|
+
| 'goal.blocked'
|
|
62
|
+
| 'goal.abandoned'
|
|
63
|
+
| 'outbox.push'
|
|
64
|
+
| 'outbox.expire'
|
|
65
|
+
|
|
66
|
+
interface BaseEntry {
|
|
67
|
+
type: LogEntryType
|
|
68
|
+
wallTime: number
|
|
69
|
+
tick?: number
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type LogEntry = BaseEntry & Record<string, unknown>
|
|
73
|
+
|
|
74
|
+
export interface SessionLoggerOptions {
|
|
75
|
+
/**
|
|
76
|
+
* Forward every entry to the consumer (typically the stem, which emits it as a
|
|
77
|
+
* `session_log` envelope on the transport). May also be set later via
|
|
78
|
+
* {@link SessionLogger.attachEmit} once the Will instance exists.
|
|
79
|
+
*/
|
|
80
|
+
emit?: SessionLogEmit | null
|
|
81
|
+
/** Also write a local NDJSON file. Dev convenience only; off in production. */
|
|
82
|
+
fileLogging?: boolean
|
|
83
|
+
/** Override the data directory for the dev file (default `./data`). */
|
|
84
|
+
dataDir?: string
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── SessionLogger ─────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
export class SessionLogger {
|
|
90
|
+
readonly sessionId: string
|
|
91
|
+
/** Local file path — empty string when file logging is disabled. */
|
|
92
|
+
readonly filePath: string
|
|
93
|
+
|
|
94
|
+
private readonly _stream: WriteStream | null
|
|
95
|
+
private _emit: SessionLogEmit | null
|
|
96
|
+
private _closed = false
|
|
97
|
+
private _count = 0
|
|
98
|
+
|
|
99
|
+
constructor( willId: string, dataDir: string = './data', opts: SessionLoggerOptions = {} ) {
|
|
100
|
+
const ts = new Date().toISOString().replace( /[:.]/g, '-' ).slice( 0, 19 )
|
|
101
|
+
const rnd = Math.random().toString( 36 ).slice( 2, 8 )
|
|
102
|
+
this.sessionId = `${ts}-${rnd}`
|
|
103
|
+
|
|
104
|
+
this._emit = opts.emit ?? null
|
|
105
|
+
|
|
106
|
+
const fileLogging = opts.fileLogging ?? false
|
|
107
|
+
if( fileLogging ){
|
|
108
|
+
const dir = join( opts.dataDir ?? dataDir, 'wills', willId, 'sessions' )
|
|
109
|
+
mkdirSync( dir, { recursive: true } )
|
|
110
|
+
this.filePath = join( dir, `${this.sessionId}.jsonl` )
|
|
111
|
+
this._stream = createWriteStream( this.filePath, { flags: 'a' } )
|
|
112
|
+
} else {
|
|
113
|
+
this.filePath = ''
|
|
114
|
+
this._stream = null
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Late-bind the consumer forwarder (the stem sets this once the Will instance
|
|
120
|
+
* exists, so it can emit a `session_log` envelope on the instance's transport).
|
|
121
|
+
*/
|
|
122
|
+
attachEmit( emit: SessionLogEmit ): void {
|
|
123
|
+
this._emit = emit
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
write( entry: Omit<LogEntry, 'wallTime'> ): void {
|
|
127
|
+
if( this._closed ) return
|
|
128
|
+
const record = { wallTime: Date.now(), ...entry }
|
|
129
|
+
|
|
130
|
+
// Forward first — this is the production transport; the consumer persists it.
|
|
131
|
+
try { this._emit?.( record ) }
|
|
132
|
+
catch { /* a listener fault must never block the tick loop */ }
|
|
133
|
+
|
|
134
|
+
// File is a dev-only mirror.
|
|
135
|
+
if( this._stream ){
|
|
136
|
+
try { this._stream.write( JSON.stringify( record ) + '\n' ) }
|
|
137
|
+
catch { /* never block the tick loop on a log failure */ }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
this._count++
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Emits `session.end` and closes the local file stream (if any). Persistence
|
|
145
|
+
* of the streamed entries is the consumer's responsibility — the Will no longer
|
|
146
|
+
* uploads anywhere itself.
|
|
147
|
+
*/
|
|
148
|
+
close(): void {
|
|
149
|
+
if( this._closed ) return
|
|
150
|
+
this.write({ type: 'session.end', totalEntries: this._count + 1 })
|
|
151
|
+
this._closed = true
|
|
152
|
+
this._stream?.end()
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
get entryCount(): number { return this._count }
|
|
156
|
+
get isClosed(): boolean { return this._closed }
|
|
157
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/stem/tracts/transport/index.ts — ExternalTransport barrel
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
export type {
|
|
6
|
+
ExternalTransport,
|
|
7
|
+
Envelope,
|
|
8
|
+
OutboundEnvelope,
|
|
9
|
+
InboundEnvelope,
|
|
10
|
+
ReplyEnvelope,
|
|
11
|
+
ChunkEnvelope,
|
|
12
|
+
MessageEnvelope,
|
|
13
|
+
effectorInvocationEnvelope,
|
|
14
|
+
PerceptEnvelope,
|
|
15
|
+
ActivityEnvelope,
|
|
16
|
+
SessionLogEnvelope,
|
|
17
|
+
TokenReportEnvelope,
|
|
18
|
+
InboundMessageEnvelope,
|
|
19
|
+
InboundPerceptEnvelope,
|
|
20
|
+
AckEnvelope,
|
|
21
|
+
AckResult,
|
|
22
|
+
TransportStatus,
|
|
23
|
+
} from '#stem/tracts/transport/types'
|
|
24
|
+
|
|
25
|
+
export { LoopbackTransport, type AckPolicy } from '#stem/tracts/transport/loopback.transport'
|
|
26
|
+
export {
|
|
27
|
+
SocketIoTransport,
|
|
28
|
+
type SocketIoTransportOptions,
|
|
29
|
+
type SocketLike,
|
|
30
|
+
} from '#stem/tracts/transport/socketio.transport'
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
type StreamChannel,
|
|
35
|
+
StreamTransport,
|
|
36
|
+
type OutboundListener,
|
|
37
|
+
fileLoggingEnabled,
|
|
38
|
+
} from '#stem/tracts/transport/stream.transport'
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/stem/tracts/transport/loopback.transport.ts
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// LoopbackTransport — in-memory, deterministic ExternalTransport for tests,
|
|
6
|
+
// replay, and any configuration without a real peer.
|
|
7
|
+
//
|
|
8
|
+
// No sockets, no timers: emit() resolves its ack synchronously per a scripted
|
|
9
|
+
// policy, and inbound is driven by injectInbound(). Mirrors the role of
|
|
10
|
+
// InProcessCognitiveTransport for the cognitive bus.
|
|
11
|
+
// ─────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
ExternalTransport,
|
|
15
|
+
OutboundEnvelope,
|
|
16
|
+
InboundEnvelope,
|
|
17
|
+
AckResult,
|
|
18
|
+
TransportStatus,
|
|
19
|
+
} from './types'
|
|
20
|
+
|
|
21
|
+
/** Decides how emit() resolves an ack for a given envelope. Default: acked via callback. */
|
|
22
|
+
export type AckPolicy = ( env: OutboundEnvelope ) => AckResult
|
|
23
|
+
|
|
24
|
+
const DEFAULT_ACK: AckPolicy = () => ({ acked: true, via: 'callback' })
|
|
25
|
+
|
|
26
|
+
export class LoopbackTransport implements ExternalTransport {
|
|
27
|
+
/** Every envelope passed to emit(), in order — inspect in tests. */
|
|
28
|
+
readonly sent: OutboundEnvelope[] = []
|
|
29
|
+
|
|
30
|
+
private _connected = true
|
|
31
|
+
private _ackPolicy: AckPolicy
|
|
32
|
+
private readonly _inboundHandlers = new Set<( env: InboundEnvelope ) => void>()
|
|
33
|
+
private readonly _statusHandlers = new Set<( s: TransportStatus ) => void>()
|
|
34
|
+
|
|
35
|
+
constructor( ackPolicy: AckPolicy = DEFAULT_ACK ){
|
|
36
|
+
this._ackPolicy = ackPolicy
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get connected(): boolean { return this._connected }
|
|
40
|
+
|
|
41
|
+
async emit( env: OutboundEnvelope ): Promise<AckResult> {
|
|
42
|
+
this.sent.push( env )
|
|
43
|
+
return this._ackPolicy( env )
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
onInbound( handler: ( env: InboundEnvelope ) => void ): () => void {
|
|
47
|
+
this._inboundHandlers.add( handler )
|
|
48
|
+
return () => { this._inboundHandlers.delete( handler ) }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
onStatus( handler: ( s: TransportStatus ) => void ): () => void {
|
|
52
|
+
this._statusHandlers.add( handler )
|
|
53
|
+
return () => { this._statusHandlers.delete( handler ) }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
close(): void {
|
|
57
|
+
this._connected = false
|
|
58
|
+
this._inboundHandlers.clear()
|
|
59
|
+
this._statusHandlers.clear()
|
|
60
|
+
this.sent.length = 0
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── Test / harness controls ──────────────────────────────────
|
|
64
|
+
|
|
65
|
+
/** Simulate the peer sending an inbound envelope to the Will. */
|
|
66
|
+
injectInbound( env: InboundEnvelope ): void {
|
|
67
|
+
for( const h of this._inboundHandlers ) h( env )
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Flip connection state and notify status handlers. */
|
|
71
|
+
setConnected( connected: boolean ): void {
|
|
72
|
+
this._connected = connected
|
|
73
|
+
const status: TransportStatus = connected ? 'connected' : 'disconnected'
|
|
74
|
+
for( const h of this._statusHandlers ) h( status )
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Swap the ack policy mid-test (e.g. to simulate timeouts then recovery). */
|
|
78
|
+
setAckPolicy( policy: AckPolicy ): void {
|
|
79
|
+
this._ackPolicy = policy
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Convenience: only the envelopes on a given channel. */
|
|
83
|
+
sentOn<T extends OutboundEnvelope['channel']>( channel: T ): Extract<OutboundEnvelope, { channel: T }>[] {
|
|
84
|
+
return this.sent.filter( e => e.channel === channel ) as Extract<OutboundEnvelope, { channel: T }>[]
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/stem/tracts/transport/socketio.transport.ts
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// SocketIoTransport — production ExternalTransport over socket.io-client.
|
|
6
|
+
//
|
|
7
|
+
// The peer (backend) hosts the socket.io server; this is Will's client side.
|
|
8
|
+
// It is constructed by the CALLER and injected via WillConfig.transport, so the
|
|
9
|
+
// `will` package never imports socket.io-client at module load — the dependency
|
|
10
|
+
// is OPTIONAL and loaded lazily via a dynamic import only when a transport is
|
|
11
|
+
// actually instantiated. The import specifier is indirected through a variable
|
|
12
|
+
// so the type checker does not require the module to be installed.
|
|
13
|
+
//
|
|
14
|
+
// Outbound: emit('envelope', env, ack) — socket.io ack callback gives the fast
|
|
15
|
+
// best-effort delivery/result ack (Section 3 reconciles with the
|
|
16
|
+
// durable discrete-event path).
|
|
17
|
+
// Inbound: 'envelope' events plus discrete 'message.delivered' /
|
|
18
|
+
// 'effector.invoked.ack' events (synthesized into ack envelopes), in
|
|
19
|
+
// case the peer emits them independently. All inbound is handed to
|
|
20
|
+
// onInbound() handlers — the stem enqueues them onto the tick queue.
|
|
21
|
+
// ─────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
import { logger } from '#core/logger'
|
|
24
|
+
import { wallClock } from '#core/wall.clock'
|
|
25
|
+
import type {
|
|
26
|
+
ExternalTransport,
|
|
27
|
+
OutboundEnvelope,
|
|
28
|
+
InboundEnvelope,
|
|
29
|
+
AckEnvelope,
|
|
30
|
+
AckResult,
|
|
31
|
+
TransportStatus,
|
|
32
|
+
} from './types'
|
|
33
|
+
|
|
34
|
+
/** Minimal surface of a socket.io client we depend on — keeps us decoupled from the dep's types. */
|
|
35
|
+
export interface SocketLike {
|
|
36
|
+
connected: boolean
|
|
37
|
+
on( event: string, handler: ( ...args: any[] ) => void ): void
|
|
38
|
+
emit( event: string, payload: unknown, ack?: ( response: unknown ) => void ): void
|
|
39
|
+
disconnect(): void
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface SocketIoTransportOptions {
|
|
43
|
+
/** socket.io server URL (e.g. wss://host or http://host:port). */
|
|
44
|
+
url: string
|
|
45
|
+
/** This Will's id — sent as auth + routing key. */
|
|
46
|
+
willId: string
|
|
47
|
+
/** Optional auth token sent in the socket.io handshake. */
|
|
48
|
+
token?: string
|
|
49
|
+
/** Default ack timeout for emit() in ms. Default 5000. */
|
|
50
|
+
ackTimeoutMs?: number
|
|
51
|
+
/**
|
|
52
|
+
* Override the socket factory — inject a fake in tests, or customize the
|
|
53
|
+
* socket.io connection. When omitted, `socket.io-client` is dynamically
|
|
54
|
+
* imported and `io(url, { auth })` is called.
|
|
55
|
+
*/
|
|
56
|
+
socketFactory?: ( url: string, opts: { auth: { willId: string; token?: string } } ) => SocketLike | Promise<SocketLike>
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const DEFAULT_ACK_TIMEOUT_MS = 5_000
|
|
60
|
+
|
|
61
|
+
export class SocketIoTransport implements ExternalTransport {
|
|
62
|
+
private _socket: SocketLike | null = null
|
|
63
|
+
private _connecting: Promise<SocketLike | null> | null = null
|
|
64
|
+
private _seq = 0
|
|
65
|
+
|
|
66
|
+
private readonly _inboundHandlers = new Set<( env: InboundEnvelope ) => void>()
|
|
67
|
+
private readonly _statusHandlers = new Set<( s: TransportStatus ) => void>()
|
|
68
|
+
|
|
69
|
+
constructor( private readonly _opts: SocketIoTransportOptions ){
|
|
70
|
+
// Begin connecting eagerly; emit()/inbound wiring await the same promise.
|
|
71
|
+
void this._ensureSocket()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
get connected(): boolean { return this._socket?.connected ?? false }
|
|
75
|
+
|
|
76
|
+
// ── Outbound ─────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
async emit( env: OutboundEnvelope, opts?: { ackTimeoutMs?: number } ): Promise<AckResult> {
|
|
79
|
+
const socket = await this._ensureSocket()
|
|
80
|
+
if( !socket || !socket.connected )
|
|
81
|
+
// No connection — caller keeps the message buffered (outbox) and retries.
|
|
82
|
+
return { acked: false, via: 'timeout' }
|
|
83
|
+
|
|
84
|
+
const timeoutMs = opts?.ackTimeoutMs ?? this._opts.ackTimeoutMs ?? DEFAULT_ACK_TIMEOUT_MS
|
|
85
|
+
const stamped: OutboundEnvelope = { ...env, seq: env.seq || ++this._seq }
|
|
86
|
+
|
|
87
|
+
return new Promise<AckResult>( resolve => {
|
|
88
|
+
let settled = false
|
|
89
|
+
const done = ( r: AckResult ) => { if( !settled ){ settled = true; clearTimeout( timer ); resolve( r ) } }
|
|
90
|
+
|
|
91
|
+
const timer = setTimeout( () => done({ acked: false, via: 'timeout' }), timeoutMs )
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
socket.emit( 'envelope', stamped, ( response: unknown ) => done({ acked: true, via: 'callback', payload: response }) )
|
|
95
|
+
}
|
|
96
|
+
catch( err ){
|
|
97
|
+
logger.warn(`[socketio-transport] emit failed: ${( err as Error ).message}`)
|
|
98
|
+
done({ acked: false, via: 'timeout' })
|
|
99
|
+
}
|
|
100
|
+
} )
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ── Inbound + status subscriptions ───────────────────────────
|
|
104
|
+
|
|
105
|
+
onInbound( handler: ( env: InboundEnvelope ) => void ): () => void {
|
|
106
|
+
this._inboundHandlers.add( handler )
|
|
107
|
+
return () => { this._inboundHandlers.delete( handler ) }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
onStatus( handler: ( s: TransportStatus ) => void ): () => void {
|
|
111
|
+
this._statusHandlers.add( handler )
|
|
112
|
+
return () => { this._statusHandlers.delete( handler ) }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
close(): void {
|
|
116
|
+
try { this._socket?.disconnect() } catch { /* ignore */ }
|
|
117
|
+
this._socket = null
|
|
118
|
+
this._connecting = null
|
|
119
|
+
this._inboundHandlers.clear()
|
|
120
|
+
this._statusHandlers.clear()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ── Internal ─────────────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
private async _ensureSocket(): Promise<SocketLike | null> {
|
|
126
|
+
if( this._socket ) return this._socket
|
|
127
|
+
if( !this._connecting ) this._connecting = this._connect()
|
|
128
|
+
return this._connecting
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private async _connect(): Promise<SocketLike | null> {
|
|
132
|
+
try {
|
|
133
|
+
const socket = this._opts.socketFactory
|
|
134
|
+
? await this._opts.socketFactory( this._opts.url, { auth: { willId: this._opts.willId, token: this._opts.token } } )
|
|
135
|
+
: await this._defaultFactory()
|
|
136
|
+
|
|
137
|
+
this._wire( socket )
|
|
138
|
+
this._socket = socket
|
|
139
|
+
return socket
|
|
140
|
+
}
|
|
141
|
+
catch( err ){
|
|
142
|
+
logger.error(`[socketio-transport] connect failed: ${( err as Error ).message}`)
|
|
143
|
+
this._connecting = null // allow a later retry
|
|
144
|
+
return null
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private async _defaultFactory(): Promise<SocketLike> {
|
|
149
|
+
// Indirect the specifier so the type checker does not require the optional
|
|
150
|
+
// dependency to be installed for the core package to build.
|
|
151
|
+
const spec = 'socket.io-client'
|
|
152
|
+
const mod = await import( /* @vite-ignore */ spec ) as any
|
|
153
|
+
const io = mod.io ?? mod.default ?? mod
|
|
154
|
+
return io( this._opts.url, {
|
|
155
|
+
auth: { willId: this._opts.willId, token: this._opts.token },
|
|
156
|
+
transports: ['websocket'],
|
|
157
|
+
} ) as SocketLike
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Attach socket.io event handlers that feed the inbound + status streams. */
|
|
161
|
+
private _wire( socket: SocketLike ): void {
|
|
162
|
+
socket.on( 'connect', () => this._notifyStatus( 'connected' ) )
|
|
163
|
+
socket.on( 'disconnect', () => this._notifyStatus( 'disconnected' ) )
|
|
164
|
+
socket.on( 'reconnect_attempt', () => this._notifyStatus( 'reconnecting' ) )
|
|
165
|
+
|
|
166
|
+
// Primary inbound channel — already-typed envelopes from the peer.
|
|
167
|
+
socket.on( 'envelope', ( env: InboundEnvelope ) => this._emitInbound( env ) )
|
|
168
|
+
|
|
169
|
+
// Discrete ack events (Section 2): in case the peer emits acks independently
|
|
170
|
+
// rather than via the emit() callback. Synthesized into ack envelopes; the
|
|
171
|
+
// AckReconciler (Section 3) dedups against the callback path by correlationId.
|
|
172
|
+
socket.on( 'message.delivered', ( m: { correlationId: string; delivered?: boolean } ) =>
|
|
173
|
+
this._emitInbound( this._deliveryAck( m.correlationId, m.delivered ?? true ) ) )
|
|
174
|
+
|
|
175
|
+
socket.on( 'effector.invoked.ack', ( m: { correlationId: string; result: AckEnvelope['result'] } ) =>
|
|
176
|
+
this._emitInbound( this._resultAck( m.correlationId, m.result ) ) )
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private _emitInbound( env: InboundEnvelope ): void {
|
|
180
|
+
for( const h of this._inboundHandlers ){
|
|
181
|
+
try { h( env ) }
|
|
182
|
+
catch( err ){ logger.error(`[socketio-transport] inbound handler error: ${( err as Error ).message}`) }
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private _notifyStatus( status: TransportStatus ): void {
|
|
187
|
+
for( const h of this._statusHandlers ){
|
|
188
|
+
try { h( status ) }
|
|
189
|
+
catch { /* ignore */ }
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private _deliveryAck( correlationId: string, delivered: boolean ): AckEnvelope {
|
|
194
|
+
return { channel: 'ack', ackKind: 'delivery', delivered, willId: this._opts.willId, correlationId, seq: 0, wallTime: wallClock() }
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private _resultAck( correlationId: string, result: AckEnvelope['result'] ): AckEnvelope {
|
|
198
|
+
return { channel: 'ack', ackKind: 'result', result, willId: this._opts.willId, correlationId, seq: 0, wallTime: wallClock() }
|
|
199
|
+
}
|
|
200
|
+
}
|