@mindot/will 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -22
- package/dist/channels/discord.d.ts +67 -6
- package/dist/channels/discord.js +112 -6
- package/dist/channels/discord.js.map +1 -1
- package/dist/channels/whatsapp.d.ts +1 -1
- package/dist/channels/whatsapp.js +4 -1
- package/dist/channels/whatsapp.js.map +1 -1
- package/dist/cli.js +13733 -11057
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3729 -1199
- package/dist/index.js.map +1 -1
- package/dist/mcp/effectors.d.ts +1 -1
- package/dist/{will-DAW0l-lY.d.ts → will-DbDj_TEH.d.ts} +1214 -93
- package/package.json +1 -1
- package/src/channels/discord.ts +189 -11
- package/src/channels/types.ts +90 -0
- package/src/channels/whatsapp.ts +13 -4
- package/src/cli.ts +9 -4
- package/src/cognition/agency/consequence.ts +122 -1
- package/src/cognition/agency/conversation.aim.ts +260 -0
- package/src/cognition/agency/engines/action.selector.ts +85 -3
- package/src/cognition/agency/engines/affordance.synthesizer.ts +90 -1
- package/src/cognition/agency/engines/motor.schema.executor.ts +152 -10
- package/src/cognition/agency/engines/reafference.engine.ts +129 -2
- package/src/cognition/agency/proactive.communicator.ts +19 -3
- package/src/cognition/agency/reconcile.learning.ts +16 -2
- package/src/cognition/agency/restart.ts +66 -0
- package/src/cognition/agency/schemas/repertoire.ts +12 -5
- package/src/cognition/agency/selection.scoring.ts +33 -0
- package/src/cognition/agency/types.ts +35 -0
- package/src/cognition/cache/composition.ts +232 -0
- package/src/cognition/cache/deliberation.cache.ts +219 -0
- package/src/cognition/cache/fingerprint.ts +120 -0
- package/src/cognition/cache/types.ts +105 -0
- package/src/cognition/config.mirror.entities.ts +109 -1
- package/src/cognition/event.schemas.ts +22 -0
- package/src/cognition/faculties/autobiographical.narrator.ts +5 -10
- package/src/cognition/faculties/episodic.consolidator.ts +59 -3
- package/src/cognition/faculties/executive.engine/commands.ts +189 -14
- package/src/cognition/faculties/executive.engine/context.ts +67 -13
- package/src/cognition/faculties/executive.engine/deliberate.reasoning.ts +1 -1
- package/src/cognition/faculties/executive.engine/engine.ts +686 -187
- package/src/cognition/faculties/executive.engine/escalation.buffer.ts +162 -44
- package/src/cognition/faculties/executive.engine/facet.supervisor.ts +310 -65
- package/src/cognition/faculties/executive.engine/facet.ts +89 -26
- package/src/cognition/faculties/executive.engine/gating.ts +14 -14
- package/src/cognition/faculties/executive.engine/parser.ts +21 -1
- package/src/cognition/faculties/executive.engine/prompt.factory.ts +169 -20
- package/src/cognition/faculties/executive.engine/types.ts +69 -0
- package/src/cognition/faculties/goal.manager.ts +94 -14
- package/src/cognition/faculties/known.entity.tracker.ts +267 -28
- package/src/cognition/faculties/moral.evaluator.ts +8 -3
- package/src/cognition/faculties/persona.consolidator.ts +141 -0
- package/src/cognition/faculties/reputation.tracker.ts +66 -2
- package/src/cognition/faculties/self.model.updater.ts +19 -12
- package/src/cognition/faculties/social.perception.ts +47 -3
- package/src/cognition/faculties/threat.evaluator.ts +7 -0
- package/src/cognition/faculties/working.memory.ts +10 -20
- package/src/cognition/identity.entity.ts +205 -0
- package/src/cognition/index.ts +11 -0
- package/src/cognition/memory/vector.adapter.ts +12 -3
- package/src/cognition/memory/vector.embedder.ts +54 -7
- package/src/cognition/persona.prior.ts +6 -0
- package/src/cognition/senses/audition.engine/engine.ts +404 -46
- package/src/cognition/senses/base.sense.engine.ts +1 -1
- package/src/cognition/senses/index.ts +12 -0
- package/src/cognition/social.identity.ts +273 -0
- package/src/cognition/utilities/token.tracker.ts +246 -98
- package/src/core/orchestrator.ts +38 -0
- package/src/host/boot.ts +78 -22
- package/src/index.ts +35 -0
- package/src/llm/index.ts +415 -97
- package/src/llm/routing.ts +204 -0
- package/src/llm/summarizer.ts +5 -1
- package/src/llm/wire.contracts.ts +19 -0
- package/src/pma/index.ts +67 -53
- package/src/runners/thin-shim.runner.ts +18 -6
- package/src/sdk/will.ts +121 -22
- package/src/stem/assembly.audit.ts +1 -0
- package/src/stem/guards/identity.coherence.ts +17 -6
- package/src/stem/index.ts +82 -5
- package/src/stem/mind.ts +327 -79
- package/src/stem/policy/arbiter.ts +49 -14
- package/src/stem/policy/rule.table.ts +2 -2
- package/src/stem/tracts/effector.controller.ts +56 -9
- package/src/stem/tracts/outbox.writer.ts +40 -2
- package/src/cognition/faculties/executive.engine/messages.ts +0 -102
|
@@ -63,6 +63,18 @@ export interface TextMessage {
|
|
|
63
63
|
content: string
|
|
64
64
|
/** Display name — used in the facet focus content. */
|
|
65
65
|
speakerName?: string
|
|
66
|
+
/**
|
|
67
|
+
* True when `threadId` is a PRIVATE thread — this someone and the mind, nobody
|
|
68
|
+
* else listening. The single fact that decides whether a room is the right
|
|
69
|
+
* place for a given utterance, and the Discord edge has always computed it
|
|
70
|
+
* (`isDM`) and discarded it before the mind could see it: a follow-up promised
|
|
71
|
+
* in a DM went out to a public channel, because the roster's "where did I last
|
|
72
|
+
* see them" is a different question from "where did I promise this".
|
|
73
|
+
*
|
|
74
|
+
* Undefined means the channel did not say, which is honestly different from
|
|
75
|
+
* false — an unknown room is not known to be public.
|
|
76
|
+
*/
|
|
77
|
+
direct?: boolean
|
|
66
78
|
}
|
|
67
79
|
|
|
68
80
|
export interface VoiceChunk {
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/cognition/social.identity.ts — what something IS, vs where to find it
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// A `keid` used to be minted by the transport: `discord:${author.id}`,
|
|
6
|
+
// `whatsapp:${userId}`. Identity WAS the address, and whichever channel spoke
|
|
7
|
+
// first won the right to name the person. Everything downstream inherited that:
|
|
8
|
+
// twenty-two files key off a keid, so the same human met on two channels was two
|
|
9
|
+
// people to the reputation tracker, the theory-of-mind model, the attachment
|
|
10
|
+
// bond and the PMA — with no way to notice, and no way to say so.
|
|
11
|
+
//
|
|
12
|
+
// It also made "how should I reach them?" unaskable. There was exactly one id
|
|
13
|
+
// and it WAS a route, so the question collapsed into a roster guess about where
|
|
14
|
+
// the person was last seen. Live, that sent a follow-up promised in a DM into a
|
|
15
|
+
// public channel, because the roster's last-seen answer is not the same question
|
|
16
|
+
// as "where did I promise this".
|
|
17
|
+
//
|
|
18
|
+
// So identity and route are separated here:
|
|
19
|
+
//
|
|
20
|
+
// ke:<opaque> — a referent. Never a route. The anchor everything hangs on.
|
|
21
|
+
// handle — a way that referent has been reachable, with the
|
|
22
|
+
// circumstances under which it worked.
|
|
23
|
+
//
|
|
24
|
+
// Deliberately NOT social-only. `keid` has always stood for *known entity* id and
|
|
25
|
+
// the dossier has carried `kind: 'sentient' | 'thing'` since it shipped; the
|
|
26
|
+
// first cut of this minted `person:` ids, which was a narrower word than the
|
|
27
|
+
// system already used. A document, a repo, a dashboard, a room each have a what
|
|
28
|
+
// and a where, and the where can change while the what stays put.
|
|
29
|
+
//
|
|
30
|
+
// This is closer to how a person actually holds it. You know someone; you know
|
|
31
|
+
// places; and separately you know where you usually find whom. Three things that
|
|
32
|
+
// compose, not one contact record — which is why a handle carries evidence
|
|
33
|
+
// (`lastAnsweredTick`) rather than a priority number somebody configured.
|
|
34
|
+
//
|
|
35
|
+
// Everything here is pure and deterministic. `mintReferentId` hashes the first
|
|
36
|
+
// transport id ever seen for that referent, so a recorded run and its replay mint
|
|
37
|
+
// the same id (R2) — no clock, no counter, no RNG.
|
|
38
|
+
// ─────────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
import type { Tick } from '#core/types'
|
|
41
|
+
import { fnv1a } from '#agency/consequence'
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Marks an id as a referent rather than an address.
|
|
45
|
+
*
|
|
46
|
+
* `ke` for known-entity, which is what `keid` has always stood for — the
|
|
47
|
+
* vocabulary was never social. The first cut of this minted `person:` ids and
|
|
48
|
+
* that was a narrower word than the system uses: the dossier has carried
|
|
49
|
+
* `kind: 'sentient' | 'thing'` since it shipped, and the split here is not a
|
|
50
|
+
* social idea. It is referent vs. access path, which is how anything is held —
|
|
51
|
+
* you remember the book, and separately that it is on the shelf, on the Kindle,
|
|
52
|
+
* or at the library. A document, a repo, a dashboard, a room all have several
|
|
53
|
+
* routes that come and go while the thing itself stays put.
|
|
54
|
+
*/
|
|
55
|
+
export const REFERENT_PREFIX = 'ke:'
|
|
56
|
+
|
|
57
|
+
export const ALIAS_TYPE = 'known-entity-alias'
|
|
58
|
+
export const DOSSIER_TYPE = 'known-entity'
|
|
59
|
+
|
|
60
|
+
/** True for an anchor, false for a transport address (`discord:…`, `whatsapp:…`). */
|
|
61
|
+
export function isReferentId( id: string ): boolean {
|
|
62
|
+
return id.startsWith( REFERENT_PREFIX )
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Mint the anchor for a referent first met at `seedKeid`.
|
|
67
|
+
*
|
|
68
|
+
* Kind is deliberately NOT in the id. It lives on the dossier, because kind is
|
|
69
|
+
* LEARNED and correctable — a handle you took for a bot turns out to be a person.
|
|
70
|
+
* Baked into the id, correcting it would mean re-identifying, and every faculty
|
|
71
|
+
* keyed off that id would lose its history of them at the moment it finally
|
|
72
|
+
* understood what they were.
|
|
73
|
+
*
|
|
74
|
+
* Deterministic by construction: the same first-seen transport id always yields
|
|
75
|
+
* the same referent id, so a replay of a recorded run mints identically and the
|
|
76
|
+
* state hashes match (R2). A counter would drift the moment two runs met people
|
|
77
|
+
* in a different order; a clock or RNG would never match at all.
|
|
78
|
+
*
|
|
79
|
+
* Opaque on purpose. The moment an id is readable as `ke:discord:123`
|
|
80
|
+
* something downstream starts parsing it back into a route, and the separation
|
|
81
|
+
* this whole module exists for quietly stops holding.
|
|
82
|
+
*/
|
|
83
|
+
export function mintReferentId( seedKeid: string ): string {
|
|
84
|
+
return `${ REFERENT_PREFIX }${ fnv1a( seedKeid ).toString( 36 ) }`
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A way this referent has been reachable, and what happened there.
|
|
89
|
+
*
|
|
90
|
+
* `kind` is the one fact that decides whether a room is the right place for a
|
|
91
|
+
* given utterance, and it was being computed at the Discord edge (`isDM`) and
|
|
92
|
+
* discarded before the mind could see it.
|
|
93
|
+
*
|
|
94
|
+
* `lastAnsweredTick` is evidence, not configuration — it arrives free from the
|
|
95
|
+
* `social.responsiveness` signal. It is what lets the mind prefer the DM because
|
|
96
|
+
* that is where this person actually answers, rather than because a constant in
|
|
97
|
+
* the code says DMs rank higher.
|
|
98
|
+
*/
|
|
99
|
+
export interface Handle {
|
|
100
|
+
/** The transport address — what a channel bridge can actually deliver to. */
|
|
101
|
+
keid: string
|
|
102
|
+
/**
|
|
103
|
+
* 'dm' — a private thread. 'room' — somewhere others are listening. Left open
|
|
104
|
+
* for a non-social referent, where the meaningful distinction is a different one.
|
|
105
|
+
*/
|
|
106
|
+
kind: 'dm' | 'room' | 'unknown'
|
|
107
|
+
/** The place this handle lives in, once places are dossiers of their own. */
|
|
108
|
+
place?: string
|
|
109
|
+
/** When the mind last SAID something here. */
|
|
110
|
+
lastUsedTick?: Tick
|
|
111
|
+
/** When someone last answered it here — the only evidence that this route works. */
|
|
112
|
+
lastAnsweredTick?: Tick
|
|
113
|
+
/** Free-form, so a host can mark what its own vocabulary cares about. */
|
|
114
|
+
tags?: string[]
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
interface EntityLike {
|
|
118
|
+
type: string
|
|
119
|
+
/** A frozen state entity may hand metadata over as either shape. */
|
|
120
|
+
metadata?: ReadonlyMap<string, unknown> | Record<string, unknown>
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Normalize whichever shape metadata arrived in. */
|
|
124
|
+
function meta( e: EntityLike ): Record<string, unknown> {
|
|
125
|
+
const m = e.metadata
|
|
126
|
+
if( !m ) return {}
|
|
127
|
+
return m instanceof Map ? Object.fromEntries( m ) : m as Record<string, unknown>
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function str( v: unknown ): string | undefined {
|
|
131
|
+
return typeof v === 'string' && v.length > 0 ? v : undefined
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* alias keid → canonical referent id.
|
|
136
|
+
*
|
|
137
|
+
* Every transport address a referent has been met at is an alias of its anchor,
|
|
138
|
+
* which is what lets the twenty-two keid consumers keep working untouched: they
|
|
139
|
+
* still see one opaque string per referent, it is simply no longer a route.
|
|
140
|
+
*/
|
|
141
|
+
export function readAliases( entities: ReadonlyMap<string, EntityLike> ): Map<string, string> {
|
|
142
|
+
const out = new Map<string, string>()
|
|
143
|
+
for( const [ , e ] of entities ){
|
|
144
|
+
if( e.type !== ALIAS_TYPE ) continue
|
|
145
|
+
const m = meta( e )
|
|
146
|
+
const a = str( m['aliasKeid'] )
|
|
147
|
+
const c = str( m['canonicalKeid'] )
|
|
148
|
+
if( a && c ) out.set( a, c )
|
|
149
|
+
}
|
|
150
|
+
return out
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Follow an alias chain to the anchor. Cycle-safe; returns the input when unaliased. */
|
|
154
|
+
export function canonicalOf( aliases: ReadonlyMap<string, string>, keid: string ): string {
|
|
155
|
+
const seen = new Set<string>()
|
|
156
|
+
let id = keid
|
|
157
|
+
while( true ){
|
|
158
|
+
const next = aliases.get( id )
|
|
159
|
+
if( !next || next === id || seen.has( next ) ) return id
|
|
160
|
+
seen.add( id )
|
|
161
|
+
id = next
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Resolve anything the mind might name — an anchor, a transport address, or a
|
|
167
|
+
* learned name — to the anchor.
|
|
168
|
+
*
|
|
169
|
+
* ONE resolver, because there were two and they disagreed:
|
|
170
|
+
* `extractKnownEntities` folded aliases (so the prompt showed one person) while
|
|
171
|
+
* `resolveKnownEntity` did not (so willing a reach-out to that same person could
|
|
172
|
+
* resolve to a keid that had been merged away, and the intention evaporated).
|
|
173
|
+
* Same question, two answers, in the same tick.
|
|
174
|
+
*
|
|
175
|
+
* Name matching stays last and exact. It is the weakest evidence here — two
|
|
176
|
+
* people genuinely can share a name — and the KnownEntityTracker's recognition
|
|
177
|
+
* pass already guards fusing them.
|
|
178
|
+
*/
|
|
179
|
+
export function resolveKeid(
|
|
180
|
+
entities: ReadonlyMap<string, EntityLike>,
|
|
181
|
+
ref: string,
|
|
182
|
+
): string | undefined {
|
|
183
|
+
const needle = ref.trim().toLowerCase()
|
|
184
|
+
if( !needle ) return undefined
|
|
185
|
+
|
|
186
|
+
const aliases = readAliases( entities )
|
|
187
|
+
|
|
188
|
+
// An anchor or an address named directly.
|
|
189
|
+
const direct = canonicalOf( aliases, ref.trim() )
|
|
190
|
+
for( const [ , e ] of entities )
|
|
191
|
+
if( e.type === DOSSIER_TYPE && str( meta( e )['keid'] ) === direct ) return direct
|
|
192
|
+
|
|
193
|
+
// Otherwise a keid or a name, case-insensitively, in stable entity order.
|
|
194
|
+
for( const [ , e ] of entities ){
|
|
195
|
+
if( e.type !== DOSSIER_TYPE ) continue
|
|
196
|
+
const m = meta( e )
|
|
197
|
+
const keid = str( m['keid'] )
|
|
198
|
+
if( !keid ) continue
|
|
199
|
+
if( keid.toLowerCase() === needle ) return canonicalOf( aliases, keid )
|
|
200
|
+
if( str( m['name'] )?.toLowerCase() === needle ) return canonicalOf( aliases, keid )
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// A known alias whose dossier was absorbed — still a real reference to someone.
|
|
204
|
+
for( const [ alias, canon ] of aliases )
|
|
205
|
+
if( alias.toLowerCase() === needle ) return canon
|
|
206
|
+
|
|
207
|
+
return undefined
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** The name the mind has learned for this referent, or undefined — never a placeholder. */
|
|
211
|
+
export function nameOf( entities: ReadonlyMap<string, EntityLike>, referentId: string ): string | undefined {
|
|
212
|
+
for( const [ , e ] of entities ){
|
|
213
|
+
if( e.type !== DOSSIER_TYPE || str( meta( e )['keid'] ) !== referentId ) continue
|
|
214
|
+
return str( meta( e )['name'] )?.trim() || undefined
|
|
215
|
+
}
|
|
216
|
+
return undefined
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Every route the mind holds for this referent, most recently answered first. */
|
|
220
|
+
export function handlesOf( entities: ReadonlyMap<string, EntityLike>, referentId: string ): Handle[] {
|
|
221
|
+
for( const [ , e ] of entities ){
|
|
222
|
+
if( e.type !== DOSSIER_TYPE || str( meta( e )['keid'] ) !== referentId ) continue
|
|
223
|
+
const raw = meta( e )['handles']
|
|
224
|
+
if( !Array.isArray( raw ) ) return []
|
|
225
|
+
return ( raw as Handle[] )
|
|
226
|
+
.filter( h => h && typeof h.keid === 'string' )
|
|
227
|
+
.sort( ( a, b ) =>
|
|
228
|
+
( b.lastAnsweredTick ?? -1 ) - ( a.lastAnsweredTick ?? -1 )
|
|
229
|
+
|| ( b.lastUsedTick ?? -1 ) - ( a.lastUsedTick ?? -1 )
|
|
230
|
+
|| ( a.keid < b.keid ? -1 : a.keid > b.keid ? 1 : 0 ) )
|
|
231
|
+
}
|
|
232
|
+
return []
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* The route to use when the mind has expressed no preference.
|
|
237
|
+
*
|
|
238
|
+
* A DEFAULT, not a decision. Which room to speak in is the mind's call, made
|
|
239
|
+
* from the circumstances it can now see; this only answers "nothing was chosen
|
|
240
|
+
* and the words must still go somewhere" — the case where the alternative is
|
|
241
|
+
* dropping the message.
|
|
242
|
+
*
|
|
243
|
+
* Ordering is evidence-first: somewhere this person has actually answered beats
|
|
244
|
+
* somewhere they have not, and a DM beats a room only as a tiebreak. A live Will
|
|
245
|
+
* lost a promised follow-up into a public channel precisely because its fallback
|
|
246
|
+
* ranked "where I last saw them" above "where they talk to me".
|
|
247
|
+
*/
|
|
248
|
+
export function defaultHandle( handles: readonly Handle[] ): Handle | undefined {
|
|
249
|
+
if( handles.length === 0 ) return undefined
|
|
250
|
+
const answered = handles.filter( h => h.lastAnsweredTick !== undefined )
|
|
251
|
+
const pool = answered.length > 0 ? answered : handles
|
|
252
|
+
return pool.find( h => h.kind === 'dm') ?? pool[0]
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Fold a newly-seen address into a person's handle list.
|
|
257
|
+
*
|
|
258
|
+
* Merges rather than appends: meeting someone again in a room already known is
|
|
259
|
+
* not a new way to reach them, it is news about an existing one. Pure — returns
|
|
260
|
+
* a fresh array, so a caller writing it back through `setEntity` cannot
|
|
261
|
+
* accidentally share a mutable reference with frozen state.
|
|
262
|
+
*/
|
|
263
|
+
export function withHandle( handles: readonly Handle[], next: Handle ): Handle[] {
|
|
264
|
+
const out = handles.filter( h => h.keid !== next.keid )
|
|
265
|
+
const old = handles.find( h => h.keid === next.keid )
|
|
266
|
+
out.push( old ? { ...old, ...next,
|
|
267
|
+
// Never let a fresh sighting erase evidence the old record already held.
|
|
268
|
+
lastUsedTick: next.lastUsedTick ?? old.lastUsedTick,
|
|
269
|
+
lastAnsweredTick: next.lastAnsweredTick ?? old.lastAnsweredTick,
|
|
270
|
+
tags: [ ...new Set([ ...( old.tags ?? [] ), ...( next.tags ?? [] ) ]) ],
|
|
271
|
+
} : next )
|
|
272
|
+
return out.sort( ( a, b ) => ( a.keid < b.keid ? -1 : a.keid > b.keid ? 1 : 0 ) )
|
|
273
|
+
}
|