@mindot/will 0.8.0 → 0.10.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 +15 -11
- package/dist/index.d.ts +188 -141
- package/dist/index.js +6985 -3337
- package/dist/index.js.map +1 -1
- package/dist/surface/channels/discord.d.ts +177 -0
- package/dist/surface/channels/discord.js +394 -0
- package/dist/surface/channels/discord.js.map +1 -0
- package/dist/{channels → surface/channels}/whatsapp.d.ts +2 -2
- package/dist/{channels → surface/channels}/whatsapp.js +11 -5
- package/dist/surface/channels/whatsapp.js.map +1 -0
- package/dist/{cli.js → surface/cli.js} +5382 -1573
- package/dist/surface/cli.js.map +1 -0
- package/dist/{mcp → surface/mcp}/effectors.d.ts +1 -1
- package/dist/{mcp → surface/mcp}/effectors.js +10 -4
- package/dist/surface/mcp/effectors.js.map +1 -0
- package/dist/{will-cS6k4uiJ.d.ts → will-evj9_vrd.d.ts} +6658 -5078
- package/package.json +11 -12
- package/src/cognition/agency/consequence.ts +356 -1
- package/src/cognition/agency/conversation.aim.ts +292 -0
- package/src/cognition/agency/engines/action.selector.ts +142 -5
- package/src/cognition/agency/engines/affordance.synthesizer.ts +200 -7
- package/src/cognition/agency/engines/deliberation.engine.ts +76 -3
- package/src/cognition/agency/engines/motor.schema.executor.ts +509 -32
- package/src/cognition/agency/engines/reafference.engine.ts +160 -4
- package/src/cognition/agency/execution.primitives.ts +17 -4
- package/src/cognition/agency/proactive.communicator.ts +19 -3
- package/src/cognition/agency/reconcile.learning.ts +5 -4
- package/src/cognition/agency/restart.ts +66 -0
- package/src/cognition/agency/schemas/innate.ts +96 -1
- package/src/cognition/agency/schemas/repertoire.ts +19 -1
- package/src/cognition/agency/selection.scoring.ts +49 -0
- package/src/cognition/agency/settlement.ts +203 -0
- package/src/cognition/agency/types.ts +73 -1
- 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 +108 -1
- package/src/cognition/event.schemas.ts +22 -8
- package/src/cognition/faculties/affective.blender.ts +21 -4
- package/src/cognition/faculties/autobiographical.narrator.ts +5 -10
- package/src/cognition/faculties/circadian.oscillator.ts +36 -7
- package/src/cognition/faculties/episodic.consolidator.ts +59 -3
- package/src/cognition/faculties/executive.engine/action.record.ts +129 -0
- package/src/cognition/faculties/executive.engine/commands.ts +189 -14
- package/src/cognition/faculties/executive.engine/context.ts +106 -46
- package/src/cognition/faculties/executive.engine/deliberate.reasoning.ts +1 -1
- package/src/cognition/faculties/executive.engine/engine.ts +714 -136
- package/src/cognition/faculties/executive.engine/escalation.buffer.ts +230 -45
- package/src/cognition/faculties/executive.engine/facet.supervisor.ts +320 -65
- package/src/cognition/faculties/executive.engine/facet.ts +81 -26
- package/src/cognition/faculties/executive.engine/gating.ts +14 -14
- package/src/cognition/faculties/executive.engine/parser.ts +120 -38
- package/src/cognition/faculties/executive.engine/prompt.factory.ts +331 -38
- package/src/cognition/faculties/executive.engine/types.ts +84 -12
- package/src/cognition/faculties/exteroception.ts +99 -98
- package/src/cognition/faculties/goal.manager.ts +144 -16
- package/src/cognition/faculties/known.entity.tracker.ts +287 -29
- package/src/cognition/faculties/moral.evaluator.ts +8 -3
- package/src/cognition/faculties/persona.consolidator.ts +141 -0
- package/src/cognition/faculties/planning.engine/engine.ts +4 -4
- package/src/cognition/faculties/planning.engine/plan.frontier.ts +1 -1
- package/src/cognition/faculties/reputation.tracker.ts +66 -2
- package/src/cognition/faculties/self.model.updater.ts +19 -12
- package/src/cognition/faculties/semantic.engine/integrator.ts +1 -1
- 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 +16 -21
- package/src/cognition/identity.entity.ts +205 -0
- package/src/cognition/index.ts +7 -0
- package/src/cognition/instruction.handler.ts +1 -1
- package/src/cognition/memory/vector.adapter.ts +12 -3
- package/src/cognition/memory/vector.embedder.ts +45 -4
- package/src/cognition/percept.entity.ts +122 -0
- package/src/cognition/persona.prior.ts +6 -0
- package/src/cognition/sense.boundary.ts +176 -0
- package/src/cognition/senses/audition.engine/engine.ts +492 -53
- package/src/cognition/senses/base.sense.engine.ts +105 -7
- package/src/cognition/senses/index.ts +83 -13
- package/src/cognition/senses/provenance.ts +128 -0
- package/src/cognition/senses/somatosensation.engine.ts +117 -9
- package/src/cognition/social.identity.ts +296 -0
- package/src/cognition/utilities/token.tracker.ts +94 -10
- package/src/core/orchestrator.ts +51 -0
- package/src/index.ts +39 -5
- package/src/llm/gate.ts +48 -0
- package/src/llm/index.ts +127 -33
- package/src/llm/routing.ts +6 -0
- package/src/llm/summarizer.ts +1 -1
- package/src/llm/wire.contracts.ts +57 -0
- package/src/pma/index.ts +68 -54
- package/src/stem/assembly.audit.ts +1 -0
- package/src/stem/guards/identity.coherence.ts +1 -1
- package/src/stem/index.ts +171 -23
- package/src/stem/mind.ts +198 -57
- package/src/stem/policy/arbiter.ts +10 -0
- package/src/stem/profiles/built-in.ts +7 -0
- package/src/{profiles → stem/profiles}/companion.ts +1 -1
- package/src/{profiles → stem/profiles}/company-brain.ts +1 -1
- package/src/{profiles → stem/profiles}/customer-service.ts +1 -1
- package/src/{profiles → stem/profiles}/game-npc.ts +1 -1
- package/src/{profiles → stem/profiles}/index.ts +1 -1
- package/src/{profiles → stem/profiles}/smart-home.ts +1 -1
- package/src/stem/tracts/effector/escalation.lifecycle.ts +239 -0
- package/src/stem/tracts/effector/policy.enforcement.ts +261 -0
- package/src/stem/tracts/effector/types.ts +110 -0
- package/src/stem/tracts/effector.controller.ts +112 -401
- package/src/stem/tracts/outbox.controller.ts +30 -17
- package/src/stem/tracts/outbox.writer.ts +40 -2
- package/src/stem/tracts/sensory.controller.ts +6 -6
- package/src/stem/tracts/session.logger.ts +6 -1
- package/src/stem/tracts/transport/types.ts +20 -1
- package/src/stem/tracts/transport.controller.ts +33 -9
- package/src/surface/channels/discord.ts +609 -0
- package/src/{channels → surface/channels}/roster.ts +1 -1
- package/src/surface/channels/types.ts +136 -0
- package/src/{channels → surface/channels}/whatsapp.ts +22 -10
- package/src/{cli.ts → surface/cli.ts} +14 -9
- package/src/{host → surface/host}/boot.ts +3 -3
- package/src/{host → surface/host}/utterances.ts +2 -2
- package/src/{mcp → surface/mcp}/effectors.ts +19 -6
- package/src/{mcp → surface/mcp}/server.ts +19 -5
- package/src/{sdk → surface/sdk}/will.ts +194 -33
- package/src/{serve → surface/serve}/server.ts +11 -4
- package/src/types.ts +24 -3
- package/dist/channels/discord.d.ts +0 -69
- package/dist/channels/discord.js +0 -193
- package/dist/channels/discord.js.map +0 -1
- package/dist/channels/whatsapp.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/mcp/effectors.js.map +0 -1
- package/src/channels/discord.ts +0 -214
- package/src/channels/types.ts +0 -46
- package/src/cognition/faculties/executive.engine/messages.ts +0 -102
- package/src/extensions/livestream.ext.ts +0 -570
- package/src/extensions/time.ext.ts +0 -339
- package/src/profiles/built-in.ts +0 -7
- package/src/runners/coherence.runner.ts +0 -49
- package/src/runners/outreach.runner.ts +0 -155
- package/src/runners/social.runner.ts +0 -179
- package/src/runners/thin-shim.runner.ts +0 -205
- /package/dist/{cli.d.ts → surface/cli.d.ts} +0 -0
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* domains (see §6 cross-modal note).
|
|
21
21
|
*
|
|
22
22
|
* - `AuditionEngine` extends this and overrides `publishes()`/`snapshot()` for its
|
|
23
|
-
* extra `
|
|
23
|
+
* extra `executive.facet.handoff` schema and session snapshot.
|
|
24
24
|
* - The four shell engines extend `ShellSenseEngine` (below) and are ~6 lines each.
|
|
25
25
|
*/
|
|
26
26
|
|
|
@@ -29,7 +29,10 @@ import type { CognitiveEvent, CognitiveBus } from '#cognition/bus'
|
|
|
29
29
|
import type { CognitiveEventSchema } from '#cognition/schema.registry'
|
|
30
30
|
import type { StateCommands } from '#core/types'
|
|
31
31
|
import type { AccessGrants } from '#agency/access.grants'
|
|
32
|
-
import type {
|
|
32
|
+
import type { SensorySignal } from '#senses/provenance'
|
|
33
|
+
import { perceptEntity, type PerceptEntity } from '#cognition/percept.entity'
|
|
34
|
+
import { fnv1a } from '#agency/consequence'
|
|
35
|
+
import type { SenseEngine, SensoryInput, SenseDomain, Percept, Transduced } from '#senses/index'
|
|
33
36
|
|
|
34
37
|
export abstract class BaseSenseEngine implements SenseEngine {
|
|
35
38
|
abstract readonly name: string
|
|
@@ -38,7 +41,7 @@ export abstract class BaseSenseEngine implements SenseEngine {
|
|
|
38
41
|
/**
|
|
39
42
|
* The `SensoryInput.kind` values this engine consumes. Inputs of any other
|
|
40
43
|
* kind are ignored silently by `ingest()` (no warning, no work) — this is how
|
|
41
|
-
* a single `
|
|
44
|
+
* a single `senseSignal(domain, input)` call can be routed leniently.
|
|
42
45
|
*/
|
|
43
46
|
protected abstract readonly acceptedKinds: ReadonlySet<SensoryInput['kind']>
|
|
44
47
|
|
|
@@ -51,12 +54,45 @@ export abstract class BaseSenseEngine implements SenseEngine {
|
|
|
51
54
|
|
|
52
55
|
protected _bus: CognitiveBus | null = null
|
|
53
56
|
protected _grants: AccessGrants | null = null
|
|
57
|
+
protected _trace: (( e: PerceptEntity ) => void) | null = null
|
|
58
|
+
protected _now: (() => number) | null = null
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Whether this sense lays down a `percept` trace in state (SIGNAL_BOUNDARY P0).
|
|
62
|
+
*
|
|
63
|
+
* ON by default, because that is the contract a host is owed: implement a
|
|
64
|
+
* sense, and what it senses reaches the five things that read percepts — the
|
|
65
|
+
* rupture gate, reafference credit, working memory, the executive prompt, and
|
|
66
|
+
* novelty. Before this, `publishPercept()` emitted a bus event three
|
|
67
|
+
* subscribers glanced at for one tick and nothing else, so a robot host
|
|
68
|
+
* ingesting frames could never remember having SEEN anything.
|
|
69
|
+
*
|
|
70
|
+
* `AuditionEngine` overrides it to `false` — see the comment there. It is the
|
|
71
|
+
* documented exception, not the template.
|
|
72
|
+
*/
|
|
73
|
+
protected readonly tracesPercepts: boolean = true
|
|
54
74
|
|
|
55
75
|
// ── Wiring ───────────────────────────────────────────────
|
|
56
76
|
attachBus( bus: CognitiveBus ): void { this._bus = bus }
|
|
57
77
|
/** Inject the AccessGrants so `ingest()` honours `gateEffector` (the permission gate). */
|
|
58
78
|
attachGrants( g: AccessGrants ): void { this._grants = g }
|
|
59
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Wire the sense to state: where a percept goes, and what tick it is now.
|
|
82
|
+
*
|
|
83
|
+
* Both together, never one: a percept without a tick is uncollectable — the
|
|
84
|
+
* sweeper reads `metadata.tick` and nothing else — which is the leak P0 step 2
|
|
85
|
+
* closed in two other writers. And the tick has to be injected rather than
|
|
86
|
+
* remembered, because a sense is INGEST-DRIVEN and off-tick: it has no `react()`
|
|
87
|
+
* to be handed one in. Audition's own `_lastDecisionTick` is the cautionary
|
|
88
|
+
* case — it lags to whenever the executive last decided, so a message arriving
|
|
89
|
+
* forty ticks later would be stamped forty ticks stale and swept on arrival.
|
|
90
|
+
*/
|
|
91
|
+
attachPerceptTrace( write: ( e: PerceptEntity ) => void, currentTick: () => number ): void {
|
|
92
|
+
this._trace = write
|
|
93
|
+
this._now = currentTick
|
|
94
|
+
}
|
|
95
|
+
|
|
60
96
|
// ── CognitiveEngine defaults ─────────────────────────────
|
|
61
97
|
publishes(): CognitiveEventSchema[] {
|
|
62
98
|
return [ { type: `senses.${this.domain}.percept`, version: 1, validate: () => null } ]
|
|
@@ -71,7 +107,7 @@ export abstract class BaseSenseEngine implements SenseEngine {
|
|
|
71
107
|
* the domain-specific work to `_perceive()`. Subclasses never re-handle gating
|
|
72
108
|
* or filtering — they only implement `_perceive()`.
|
|
73
109
|
*/
|
|
74
|
-
async
|
|
110
|
+
async sense( input: SensoryInput ): Promise<void> {
|
|
75
111
|
// Permission gate — AccessGrants decides whether this sense may ingest.
|
|
76
112
|
if( this.gateEffector && this._grants && !this._grants.isAllowed( this.gateEffector ) ) return
|
|
77
113
|
if( !this.acceptedKinds.has( input.kind ) ) return
|
|
@@ -86,15 +122,77 @@ export abstract class BaseSenseEngine implements SenseEngine {
|
|
|
86
122
|
* Publish a percept on this engine's `senses.<domain>.percept` topic. The
|
|
87
123
|
* single emit chokepoint — the AttentionAllocator (and, in future, a
|
|
88
124
|
* cross-modal binder) observes percepts here.
|
|
125
|
+
*
|
|
126
|
+
* `from` is the signal this percept was transduced FROM, and it is required
|
|
127
|
+
* rather than optional on purpose: every percept has a cause, and the one
|
|
128
|
+
* fact transduction must not lose is whose doing that cause was. Taking it
|
|
129
|
+
* here — instead of stashing the in-flight input on a field — is what keeps
|
|
130
|
+
* the stamp correct while `_perceive()` is async and two ingests overlap.
|
|
131
|
+
*
|
|
132
|
+
* The percept arrives as `Transduced`, i.e. WITHOUT the two fields, so a
|
|
133
|
+
* sense engine cannot supply them at all. An earlier cut let one through: the
|
|
134
|
+
* stamp overwrote `provenance` unconditionally but `sourceIntentId` only when
|
|
135
|
+
* the host supplied one, so an engine could fabricate an intent id and it
|
|
136
|
+
* survived — provenance the mind would later trust, laundered by the very
|
|
137
|
+
* step that exists to establish it. The type now refuses it outright; the
|
|
138
|
+
* host's assertion is the only authority.
|
|
139
|
+
*
|
|
140
|
+
* Returns the stamped percept so a sense engine that needs it downstream (as
|
|
141
|
+
* audition does, to route the turn) uses the SAME object the bus saw, rather
|
|
142
|
+
* than a second one that could drift from it.
|
|
89
143
|
*/
|
|
90
|
-
protected publishPercept( percept:
|
|
144
|
+
protected publishPercept<P extends Percept>( percept: Transduced<P>, from: SensorySignal ): P {
|
|
145
|
+
// Stripped, then re-applied — belt AND braces. `Transduced` makes forging a
|
|
146
|
+
// compile error, but a type is only as strong as the compiler that saw it:
|
|
147
|
+
// a JS host, or a consumer built against an older .d.ts, hands over whatever
|
|
148
|
+
// it likes. The strip is what holds at runtime.
|
|
149
|
+
const { provenance: _stale, sourceIntentId: _staleIntent, ...rest } =
|
|
150
|
+
percept as Transduced<P> & Partial<SensorySignal>
|
|
151
|
+
|
|
152
|
+
const stamped = {
|
|
153
|
+
...rest,
|
|
154
|
+
provenance: from.provenance,
|
|
155
|
+
...( from.sourceIntentId ? { sourceIntentId: from.sourceIntentId } : {} ),
|
|
156
|
+
} as P
|
|
157
|
+
|
|
91
158
|
this._bus?.publish({
|
|
92
159
|
type: `senses.${this.domain}.percept`,
|
|
93
160
|
version: 1,
|
|
94
161
|
sourceEngine: this.name,
|
|
95
|
-
salience:
|
|
96
|
-
payload:
|
|
162
|
+
salience: stamped.salience,
|
|
163
|
+
payload: stamped,
|
|
97
164
|
})
|
|
165
|
+
|
|
166
|
+
this._writeTrace( stamped )
|
|
167
|
+
return stamped
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Lay the percept down in state, so it reaches the faculties that read
|
|
172
|
+
* percepts rather than only the three that were listening on the bus this
|
|
173
|
+
* tick. Silent when the sense opts out or the host wired no sink.
|
|
174
|
+
*
|
|
175
|
+
* The id is content-derived and tick-stamped — never `wallClock()` — because
|
|
176
|
+
* this entity lives in state, and a wall-clock id makes a recorded and a
|
|
177
|
+
* replayed run diverge (R2). Two identical signals from one entity on one
|
|
178
|
+
* tick collapse to one percept, which is the same coalescing audition already
|
|
179
|
+
* applies to a burst of identical messages.
|
|
180
|
+
*/
|
|
181
|
+
private _writeTrace( p: Percept ): void {
|
|
182
|
+
if( !this.tracesPercepts || !this._trace || !this._now ) return
|
|
183
|
+
|
|
184
|
+
const tick = this._now()
|
|
185
|
+
this._trace( perceptEntity( {
|
|
186
|
+
id: `sense-${ this.domain }-${ tick }-${ fnv1a( `${ p.sourceEntityId }\u0000${ p.summary }` ) }`,
|
|
187
|
+
tick,
|
|
188
|
+
salience: p.salience,
|
|
189
|
+
category: this.domain,
|
|
190
|
+
summary: p.summary,
|
|
191
|
+
provenance: p.provenance,
|
|
192
|
+
entityId: p.sourceEntityId,
|
|
193
|
+
...( p.sourceIntentId !== undefined ? { sourceIntentId: p.sourceIntentId } : {} ),
|
|
194
|
+
...( p.data !== undefined ? { data: p.data } : {} ),
|
|
195
|
+
} ) )
|
|
98
196
|
}
|
|
99
197
|
}
|
|
100
198
|
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
import type { CognitiveEngine } from '#cognition/types'
|
|
23
23
|
import type { CognitiveBus } from '#cognition/bus'
|
|
24
|
+
import type { SensorySignal } from './provenance'
|
|
24
25
|
|
|
25
26
|
// ── Domain ────────────────────────────────────────────────────
|
|
26
27
|
|
|
@@ -31,15 +32,57 @@ export type SenseDomain =
|
|
|
31
32
|
| 'olfaction' // ambient signals — OlfactionEngine (shell)
|
|
32
33
|
| 'gustation' // self-evaluation — GustationEngine (shell)
|
|
33
34
|
|
|
35
|
+
// ── Provenance (re-exported from ./provenance) ────────────────
|
|
36
|
+
|
|
37
|
+
export type { SignalProvenance, SensorySignal, Transduced } from './provenance'
|
|
38
|
+
export { asProvenance } from './provenance'
|
|
39
|
+
|
|
34
40
|
// ── Percept types ─────────────────────────────────────────────
|
|
35
41
|
|
|
36
|
-
/**
|
|
37
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Base percept published on the CognitiveBus.
|
|
44
|
+
*
|
|
45
|
+
* It extends `SensorySignal` because transduction does not change whose doing a
|
|
46
|
+
* signal was: what the host stamped on the input is what the percept carries.
|
|
47
|
+
* `BaseSenseEngine.publishPercept()` applies both from the input, so a percept
|
|
48
|
+
* ALWAYS carries provenance — no consumer downstream ever writes a fallback.
|
|
49
|
+
*/
|
|
50
|
+
export interface Percept extends SensorySignal {
|
|
38
51
|
domain: SenseDomain
|
|
39
52
|
sourceEntityId: string
|
|
40
53
|
timestamp: number
|
|
41
54
|
salience: number // 0–1, computed by the sense engine
|
|
42
55
|
raw: unknown // original input object
|
|
56
|
+
/**
|
|
57
|
+
* A LABEL for what was sensed. Required, so a percept is never invisible —
|
|
58
|
+
* `extractPercepts` skips one without it.
|
|
59
|
+
*
|
|
60
|
+
* A label, not the payload, and not a meaning. The engine composes it from
|
|
61
|
+
* what it has (the signal's name, a compact rendering) — A HOST IS NEVER
|
|
62
|
+
* ASKED TO WRITE ONE. Demanding prose from an integration puts the mind's own
|
|
63
|
+
* work on the wrong side of the boundary: a robot's vision system reports
|
|
64
|
+
* `object_detected, confidence 0.9, bbox […]`, and making it also write
|
|
65
|
+
* "I see a red ball on the table" is asking the arm to do the thinking.
|
|
66
|
+
*
|
|
67
|
+
* Because the ENGINE composes this, `PERCEPT_SUMMARY_CAP` may bound it —
|
|
68
|
+
* bounding its own words destroys nobody's only copy. `data` beside it is the
|
|
69
|
+
* host's, and is never bounded.
|
|
70
|
+
*/
|
|
71
|
+
summary: string
|
|
72
|
+
/**
|
|
73
|
+
* What the host actually sent, in the shape it had it — whole, unreshaped,
|
|
74
|
+
* uncapped.
|
|
75
|
+
*
|
|
76
|
+
* The mind's job is to make meaning by connecting pieces of information, and
|
|
77
|
+
* it cannot do that from a sentence somebody else wrote about the pieces. A
|
|
78
|
+
* host reporting `memberCount: 47` under a summary saying "a few people"
|
|
79
|
+
* leaves a mind that can never recover 47.
|
|
80
|
+
*
|
|
81
|
+
* Distinct from `raw`, which is the whole `SensoryInput` envelope and is
|
|
82
|
+
* engine-internal — never persisted. This is, and it reaches state, working
|
|
83
|
+
* memory and the prompt.
|
|
84
|
+
*/
|
|
85
|
+
data?: unknown
|
|
43
86
|
}
|
|
44
87
|
|
|
45
88
|
/** Audition-specific percept — carries language content and thread context. */
|
|
@@ -56,16 +99,43 @@ export interface LanguagePercept extends Percept {
|
|
|
56
99
|
// ── Sensory input types (discriminated union) ─────────────────
|
|
57
100
|
|
|
58
101
|
// Audition
|
|
59
|
-
export interface TextMessage {
|
|
102
|
+
export interface TextMessage extends SensorySignal {
|
|
60
103
|
kind: 'text'
|
|
61
104
|
entityId: string
|
|
62
105
|
threadId: string
|
|
63
106
|
content: string
|
|
64
107
|
/** Display name — used in the facet focus content. */
|
|
65
108
|
speakerName?: string
|
|
109
|
+
/**
|
|
110
|
+
* True when `threadId` is a PRIVATE thread — this someone and the mind, nobody
|
|
111
|
+
* else listening. The single fact that decides whether a room is the right
|
|
112
|
+
* place for a given utterance, and the Discord edge has always computed it
|
|
113
|
+
* (`isDM`) and discarded it before the mind could see it: a follow-up promised
|
|
114
|
+
* in a DM went out to a public channel, because the roster's "where did I last
|
|
115
|
+
* see them" is a different question from "where did I promise this".
|
|
116
|
+
*
|
|
117
|
+
* Undefined means the channel did not say, which is honestly different from
|
|
118
|
+
* false — an unknown room is not known to be public.
|
|
119
|
+
*/
|
|
120
|
+
direct?: boolean
|
|
121
|
+
/**
|
|
122
|
+
* What this room is CALLED, where the channel knows.
|
|
123
|
+
*
|
|
124
|
+
* A room has had a dossier of its own since 0.9.0 and no way to be named, so
|
|
125
|
+
* every place the mind knew was the id it was reached at —
|
|
126
|
+
* `discord:1531261362838441996`. It rendered to the mind as "something", which
|
|
127
|
+
* is what the prompt says for a `thing` with no name, and left a mind deciding
|
|
128
|
+
* *where* to say something choosing between two opaque numbers.
|
|
129
|
+
*
|
|
130
|
+
* A display label, not an address: `#general` is what a person calls the room,
|
|
131
|
+
* `discord:1531…` is how a message gets there, and 0.9.0 established that those
|
|
132
|
+
* are different facts. The bridge composes it, because what a room is called is
|
|
133
|
+
* a platform's business and the mind should not learn Discord's spelling.
|
|
134
|
+
*/
|
|
135
|
+
threadName?: string
|
|
66
136
|
}
|
|
67
137
|
|
|
68
|
-
export interface VoiceChunk {
|
|
138
|
+
export interface VoiceChunk extends SensorySignal {
|
|
69
139
|
kind: 'voice'
|
|
70
140
|
entityId: string
|
|
71
141
|
threadId: string
|
|
@@ -74,14 +144,14 @@ export interface VoiceChunk {
|
|
|
74
144
|
}
|
|
75
145
|
|
|
76
146
|
// Vision (stub)
|
|
77
|
-
export interface ImageFrame {
|
|
147
|
+
export interface ImageFrame extends SensorySignal {
|
|
78
148
|
kind: 'image'
|
|
79
149
|
entityId: string
|
|
80
150
|
data: Buffer
|
|
81
151
|
mimeType: string
|
|
82
152
|
}
|
|
83
153
|
|
|
84
|
-
export interface VideoSegment {
|
|
154
|
+
export interface VideoSegment extends SensorySignal {
|
|
85
155
|
kind: 'video'
|
|
86
156
|
entityId: string
|
|
87
157
|
frames: ImageFrame[]
|
|
@@ -89,41 +159,41 @@ export interface VideoSegment {
|
|
|
89
159
|
}
|
|
90
160
|
|
|
91
161
|
// Somatosensation (stub)
|
|
92
|
-
export interface WebhookEvent {
|
|
162
|
+
export interface WebhookEvent extends SensorySignal {
|
|
93
163
|
kind: 'webhook'
|
|
94
164
|
source: string
|
|
95
165
|
payload: unknown
|
|
96
166
|
headers: Record<string, string>
|
|
97
167
|
}
|
|
98
168
|
|
|
99
|
-
export interface SystemSignal {
|
|
169
|
+
export interface SystemSignal extends SensorySignal {
|
|
100
170
|
kind: 'system'
|
|
101
171
|
signal: string
|
|
102
172
|
data: unknown
|
|
103
173
|
}
|
|
104
174
|
|
|
105
175
|
// Olfaction (stub)
|
|
106
|
-
export interface AmbientMetric {
|
|
176
|
+
export interface AmbientMetric extends SensorySignal {
|
|
107
177
|
kind: 'ambient'
|
|
108
178
|
metricKey: string
|
|
109
179
|
value: number
|
|
110
180
|
trend: 'rising' | 'falling' | 'stable'
|
|
111
181
|
}
|
|
112
182
|
|
|
113
|
-
export interface BackgroundSignal {
|
|
183
|
+
export interface BackgroundSignal extends SensorySignal {
|
|
114
184
|
kind: 'background'
|
|
115
185
|
category: string
|
|
116
186
|
data: unknown
|
|
117
187
|
}
|
|
118
188
|
|
|
119
189
|
// Gustation (stub)
|
|
120
|
-
export interface InternalEvaluation {
|
|
190
|
+
export interface InternalEvaluation extends SensorySignal {
|
|
121
191
|
kind: 'self-eval'
|
|
122
192
|
context: string
|
|
123
193
|
trigger: string
|
|
124
194
|
}
|
|
125
195
|
|
|
126
|
-
export interface SelfAssessmentTrigger {
|
|
196
|
+
export interface SelfAssessmentTrigger extends SensorySignal {
|
|
127
197
|
kind: 'assessment'
|
|
128
198
|
goalId?: string
|
|
129
199
|
checkType: string
|
|
@@ -159,7 +229,7 @@ export type SensoryInput =
|
|
|
159
229
|
export interface SenseEngine extends CognitiveEngine {
|
|
160
230
|
readonly domain: SenseDomain
|
|
161
231
|
attachBus( bus: CognitiveBus ): void
|
|
162
|
-
|
|
232
|
+
sense( input: SensoryInput ): Promise<void>
|
|
163
233
|
}
|
|
164
234
|
|
|
165
235
|
// ── Re-exports ────────────────────────────────────────────────
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/cognition/senses/provenance.ts
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Signal provenance — the boundary vocabulary for "whose doing was this?"
|
|
7
|
+
* (SIGNAL_BOUNDARY P0a).
|
|
8
|
+
*
|
|
9
|
+
* Its own module, not part of `senses/index`, for one concrete reason:
|
|
10
|
+
* `BaseSenseEngine` needs the VALUE `provenanceOf` at the emit chokepoint, and
|
|
11
|
+
* `senses/index` re-exports `BaseSenseEngine` — importing it from there would
|
|
12
|
+
* be a runtime import cycle rather than the erased type-only one that was there
|
|
13
|
+
* before. `senses/index` re-exports everything here, so the public path is
|
|
14
|
+
* unchanged.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Whether a signal arriving at the senses was caused by this mind's own
|
|
19
|
+
* efference (SIGNAL_BOUNDARY P0a).
|
|
20
|
+
*
|
|
21
|
+
* • 'exafferent' — the world did this. Nothing of mine preceded it.
|
|
22
|
+
* • 'reafferent' — I did this, and I am now sensing the consequence.
|
|
23
|
+
* • 'unknown' — the source cannot say.
|
|
24
|
+
*
|
|
25
|
+
* There is no fourth, silent state — see `SensorySignal`.
|
|
26
|
+
*
|
|
27
|
+
* ONE CONCEPT, TWO WAYS OF ARRIVING AT IT. This type is shared with the percept
|
|
28
|
+
* entities `exteroception` and `outbox.controller` write, which used the same
|
|
29
|
+
* two spellings before it existed (EXAFFERENCE P2/P3), and it is deliberately
|
|
30
|
+
* not "asserted only":
|
|
31
|
+
*
|
|
32
|
+
* • At the WORLD door, provenance is INFERRED and that is correct — the mind
|
|
33
|
+
* holds both sides. `Exteroception` matches an observed change against its
|
|
34
|
+
* own live consequence descriptors (`matchConsequenceText` /
|
|
35
|
+
* `matchConsequenceEntity`) and tags the hit `'reafferent'`.
|
|
36
|
+
* • At the SENSE door, provenance must be ASSERTED — there is nothing to
|
|
37
|
+
* match against. A host's Discord message and the mind's own echo of it are
|
|
38
|
+
* identical from the inside; only the host can tell them apart.
|
|
39
|
+
*
|
|
40
|
+
* What the two share is the consequence, and it is load-bearing:
|
|
41
|
+
* `action.selector`'s rupture gate counts only `'exafferent'` percepts, so
|
|
42
|
+
* anything tagged (or defaulted) as mine can never rupture a commitment.
|
|
43
|
+
*/
|
|
44
|
+
export type SignalProvenance = 'exafferent' | 'reafferent' | 'unknown'
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* What EVERY sensory signal carries, whatever kind it is — the provenance stamp.
|
|
48
|
+
*
|
|
49
|
+
* It lives on the input rather than being derived downstream because only the
|
|
50
|
+
* host knows. A Discord bridge knows that the message it just received is the
|
|
51
|
+
* webhook echo of the message the mind sent; nothing inside the mind can tell
|
|
52
|
+
* that from a stranger saying the same words. So the boundary asserts it, and
|
|
53
|
+
* the mind trusts the assertion.
|
|
54
|
+
*
|
|
55
|
+
* WHY `provenance` IS REQUIRED AND HAS NO DEFAULT. It shipped optional first,
|
|
56
|
+
* defaulting to `'exafferent'`, and that was wrong for a reason worth keeping
|
|
57
|
+
* written down. This codebase makes a field optional when absence is the ONLY
|
|
58
|
+
* way to say a third thing: `speakerName?` (no string means "no name learned"),
|
|
59
|
+
* `direct?` (no boolean means "the channel did not say"). Provenance already
|
|
60
|
+
* HAS its third thing — `'unknown'`. So optionality bought a fourth state that
|
|
61
|
+
* was behaviourally identical to `'exafferent'` and epistemically its opposite:
|
|
62
|
+
* a claim that the world did this, made by nobody. Worse, it was lossy in the
|
|
63
|
+
* one direction that costs — a reader could never separate "the host asserts
|
|
64
|
+
* exafference" from "the host said nothing", which want different treatment
|
|
65
|
+
* downstream, and `'unknown'` exists precisely to keep them apart.
|
|
66
|
+
*
|
|
67
|
+
* The related-but-different precedent is `asFinality()`. That one DOES default,
|
|
68
|
+
* because it normalizes an `unknown` read back off entity metadata or a host
|
|
69
|
+
* ack, where the type system cannot help and something must be chosen. Here the
|
|
70
|
+
* type system can help, so it does. Should provenance ever need reading off a
|
|
71
|
+
* tape, that reader gets its own `asFinality`-shaped normalizer, and the
|
|
72
|
+
* direction of ITS fallback is `'exafferent'`: a percept wrongly marked as mine
|
|
73
|
+
* is attenuated and can never rupture a commitment, so a mind that mislabels
|
|
74
|
+
* the world as its own doing goes quiet about real events. Erring the other way
|
|
75
|
+
* costs only a little redundant salience.
|
|
76
|
+
*
|
|
77
|
+
* `sourceIntentId` stays optional by the same rule that made `provenance`
|
|
78
|
+
* required: there is no id meaning "no intent", so absence is the only way to
|
|
79
|
+
* say it, and it is meaningless unless `provenance` is `'reafferent'`.
|
|
80
|
+
*/
|
|
81
|
+
export interface SensorySignal {
|
|
82
|
+
/** Mine, the world's, or honestly unknown. Asserted by the host — never inferred. */
|
|
83
|
+
provenance: SignalProvenance
|
|
84
|
+
/**
|
|
85
|
+
* The `agency.intent` id this is the consequence of. Only meaningful when
|
|
86
|
+
* `provenance` is `'reafferent'`; the correlation handle a later mechanism
|
|
87
|
+
* needs to ask *"is this the echo of that?"* (`ACT_EXPECTATIONS`).
|
|
88
|
+
*/
|
|
89
|
+
sourceIntentId?: string
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* A percept as a sense engine BUILDS it — provenance omitted, because it is not
|
|
94
|
+
* the engine's to write. `publishPercept()` applies the host's assertion, so
|
|
95
|
+
* this type is what makes laundering a compile error rather than a runtime one.
|
|
96
|
+
*/
|
|
97
|
+
export type Transduced<P extends { provenance: SignalProvenance }> =
|
|
98
|
+
Omit<P, 'provenance' | 'sourceIntentId'>
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Normalize an UNTYPED provenance — one arriving over a protocol the type
|
|
102
|
+
* system cannot police: an MCP tool call, an HTTP body, a wire envelope, a
|
|
103
|
+
* value read back off a snapshot.
|
|
104
|
+
*
|
|
105
|
+
* This is the `asFinality()` situation, and the ONLY situation in which
|
|
106
|
+
* provenance has a default. Inside the package the field is required, because
|
|
107
|
+
* there the compiler can make a producer answer. At an untyped boundary it
|
|
108
|
+
* cannot, something must be chosen, and every such read goes through here
|
|
109
|
+
* rather than comparing string literals at the call site.
|
|
110
|
+
*
|
|
111
|
+
* THE DEFAULT IS 'exafferent', AND NEVER 'reafferent'. A percept wrongly marked
|
|
112
|
+
* as mine is attenuated on the exafference path and can never rupture a
|
|
113
|
+
* commitment — so a mind that mislabels the world as its own doing goes quiet
|
|
114
|
+
* about real events, which is the failure that matters. Erring the other way
|
|
115
|
+
* costs only a little redundant salience. 'reafferent' is a claim only a caller
|
|
116
|
+
* that actually knows can make.
|
|
117
|
+
*
|
|
118
|
+
* Note it does NOT fall back to 'unknown', which would be the tidy-looking
|
|
119
|
+
* choice. 'unknown' is an assertion too — "I looked and I cannot tell" — and a
|
|
120
|
+
* caller that simply did not send the field has not looked. Where the ABSENCE
|
|
121
|
+
* is structural rather than a caller's omission (a wire envelope with no such
|
|
122
|
+
* field), say 'unknown' at that site explicitly instead of routing through here.
|
|
123
|
+
*/
|
|
124
|
+
export function asProvenance( raw: unknown ): SignalProvenance {
|
|
125
|
+
return raw === 'reafferent' ? 'reafferent'
|
|
126
|
+
: raw === 'unknown' ? 'unknown'
|
|
127
|
+
: 'exafferent'
|
|
128
|
+
}
|
|
@@ -3,21 +3,129 @@
|
|
|
3
3
|
// ─────────────────────────────────────────────────────────────
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* SomatosensationEngine
|
|
6
|
+
* SomatosensationEngine — the sense that feels things happen TO the mind.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* Webhooks, system signals, external callbacks: not something said, not
|
|
9
|
+
* something seen, but the world touching the mind directly. It was the first
|
|
10
|
+
* shell to be implemented because the wake event needed a door — SIGNAL_BOUNDARY
|
|
11
|
+
* P1 — and a hand-written percept in `stem/index.ts` was the bypass that proved
|
|
12
|
+
* the door was missing.
|
|
11
13
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
+
* WHAT MAKES THIS A SENSE RATHER THAN A HELPER. It does nothing the other senses
|
|
15
|
+
* do not: transduce an input into a `Percept` with a salience and a summary, and
|
|
16
|
+
* hand it to `publishPercept`, which stamps provenance from the host's assertion
|
|
17
|
+
* and lays down the trace. Everything downstream — the rupture gate, working
|
|
18
|
+
* memory, the executive prompt, novelty — receives it because it is a percept,
|
|
19
|
+
* not because anyone wired those five places to a wake event.
|
|
20
|
+
*
|
|
21
|
+
* That is the whole point of a door: the wake event stops being special.
|
|
14
22
|
*/
|
|
15
23
|
|
|
16
|
-
import type { SensoryInput } from '#senses/index'
|
|
17
|
-
import {
|
|
24
|
+
import type { SensoryInput, SystemSignal, WebhookEvent, Percept, Transduced } from '#senses/index'
|
|
25
|
+
import { BaseSenseEngine } from '#senses/base.sense.engine'
|
|
26
|
+
import { PERCEPT_SUMMARY_CAP } from '#cognition/percept.entity'
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* How loud a system signal is by default.
|
|
30
|
+
*
|
|
31
|
+
* A signal arrives because something HAPPENED to the mind — a host woke it, a
|
|
32
|
+
* webhook fired — so it outranks the ambient world-change percepts
|
|
33
|
+
* `exteroception` produces at 0.3, and clears `action.selector`'s rupture gate
|
|
34
|
+
* (0.4). A host that knows better says so with `salience` on the input.
|
|
35
|
+
*/
|
|
36
|
+
export const SYSTEM_SIGNAL_SALIENCE = 0.75
|
|
18
37
|
|
|
19
|
-
export class SomatosensationEngine extends
|
|
38
|
+
export class SomatosensationEngine extends BaseSenseEngine {
|
|
20
39
|
readonly name = 'somatosensation-engine'
|
|
21
40
|
readonly domain = 'somatosensation' as const
|
|
22
41
|
protected readonly acceptedKinds = new Set<SensoryInput['kind']>( [ 'webhook', 'system' ] )
|
|
42
|
+
|
|
43
|
+
protected async _perceive( input: SensoryInput ): Promise<void> {
|
|
44
|
+
const percept: Transduced<Percept> = input.kind === 'system'
|
|
45
|
+
? this._fromSignal( input as SystemSignal )
|
|
46
|
+
: this._fromWebhook( input as WebhookEvent )
|
|
47
|
+
|
|
48
|
+
this.publishPercept( percept, input )
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private _fromSignal( s: SystemSignal ): Transduced<Percept> {
|
|
52
|
+
return {
|
|
53
|
+
domain: this.domain,
|
|
54
|
+
// The signal IS the source: nothing in the world sent it, the substrate did.
|
|
55
|
+
sourceEntityId: `system:${ s.signal }`,
|
|
56
|
+
timestamp: 0,
|
|
57
|
+
salience: salienceOf( s.data, SYSTEM_SIGNAL_SALIENCE ),
|
|
58
|
+
summary: labelFor( s.signal, s.data ),
|
|
59
|
+
...( s.data !== undefined && s.data !== null ? { data: s.data } : {} ),
|
|
60
|
+
raw: s,
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private _fromWebhook( w: WebhookEvent ): Transduced<Percept> {
|
|
65
|
+
return {
|
|
66
|
+
domain: this.domain,
|
|
67
|
+
sourceEntityId: `webhook:${ w.source }`,
|
|
68
|
+
timestamp: 0,
|
|
69
|
+
salience: salienceOf( w.payload, SYSTEM_SIGNAL_SALIENCE ),
|
|
70
|
+
summary: labelFor( w.source, w.payload ),
|
|
71
|
+
...( w.payload !== undefined && w.payload !== null ? { data: w.payload } : {} ),
|
|
72
|
+
raw: w,
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* A LABEL for a signal — what arrived, not what it means.
|
|
79
|
+
*
|
|
80
|
+
* The mind makes the meaning; this only has to make the percept legible enough
|
|
81
|
+
* to be noticed and connected to its data. A HOST IS NEVER ASKED FOR PROSE:
|
|
82
|
+
* demanding a sentence from a robot's control layer puts the mind's own work on
|
|
83
|
+
* the wrong side of the integration boundary, and a lidar driver has no business
|
|
84
|
+
* describing what a scan implies.
|
|
85
|
+
*
|
|
86
|
+
* The signal's own NAME is the hint, and it is free — `discord_server_snapshot`,
|
|
87
|
+
* `WAKE`, `lidar.scan` already say what kind of thing this is. A host that
|
|
88
|
+
* happens to have words may put a `summary` on its data and they are used
|
|
89
|
+
* instead; it is an option, never an obligation.
|
|
90
|
+
*
|
|
91
|
+
* Bounded by `PERCEPT_SUMMARY_CAP` because the ENGINE writes it. The data it
|
|
92
|
+
* labels is beside it, whole and uncapped.
|
|
93
|
+
*/
|
|
94
|
+
function labelFor( signal: string, data: unknown ): string {
|
|
95
|
+
const words = hostWords( data )
|
|
96
|
+
if( words ) return words.length > PERCEPT_SUMMARY_CAP
|
|
97
|
+
? `${ words.slice( 0, PERCEPT_SUMMARY_CAP - 1 ) }\u2026` : words
|
|
98
|
+
|
|
99
|
+
const rendered = compact( data )
|
|
100
|
+
const label = rendered ? `${ signal }: ${ rendered }` : `Something happened: ${ signal }.`
|
|
101
|
+
return label.length > PERCEPT_SUMMARY_CAP
|
|
102
|
+
? `${ label.slice( 0, PERCEPT_SUMMARY_CAP - 1 ) }\u2026` : label
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** A host's own words, if it chose to offer any. Optional, never required. */
|
|
106
|
+
function hostWords( data: unknown ): string | undefined {
|
|
107
|
+
if( typeof data === 'string') return data.length > 0 ? data : undefined
|
|
108
|
+
if( typeof data === 'object' && data !== null && !Array.isArray( data ) ){
|
|
109
|
+
const s = ( data as Record<string, unknown> )['summary']
|
|
110
|
+
if( typeof s === 'string' && s.length > 0 ) return s
|
|
111
|
+
}
|
|
112
|
+
return undefined
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** A glance at the shape, for the label only. The whole thing rides in `data`. */
|
|
116
|
+
function compact( data: unknown ): string | undefined {
|
|
117
|
+
if( data === undefined || data === null ) return undefined
|
|
118
|
+
if( typeof data !== 'object') return String( data )
|
|
119
|
+
try {
|
|
120
|
+
const json = JSON.stringify( data )
|
|
121
|
+
return json === '{}' || json === '[]' ? undefined : json
|
|
122
|
+
}
|
|
123
|
+
catch { return undefined } // circular — the label falls back to the name
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Same door for salience: the host may say, otherwise the default stands. */
|
|
127
|
+
function salienceOf( data: unknown, fallback: number ): number {
|
|
128
|
+
if( typeof data !== 'object' || data === null || Array.isArray( data ) ) return fallback
|
|
129
|
+
const s = ( data as Record<string, unknown> )['salience']
|
|
130
|
+
return typeof s === 'number' && Number.isFinite( s ) ? Math.max( 0, Math.min( 1, s ) ) : fallback
|
|
23
131
|
}
|