@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.
Files changed (88) hide show
  1. package/README.md +87 -22
  2. package/dist/channels/discord.d.ts +67 -6
  3. package/dist/channels/discord.js +112 -6
  4. package/dist/channels/discord.js.map +1 -1
  5. package/dist/channels/whatsapp.d.ts +1 -1
  6. package/dist/channels/whatsapp.js +4 -1
  7. package/dist/channels/whatsapp.js.map +1 -1
  8. package/dist/cli.js +13733 -11057
  9. package/dist/cli.js.map +1 -1
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.js +3729 -1199
  12. package/dist/index.js.map +1 -1
  13. package/dist/mcp/effectors.d.ts +1 -1
  14. package/dist/{will-DAW0l-lY.d.ts → will-DbDj_TEH.d.ts} +1214 -93
  15. package/package.json +1 -1
  16. package/src/channels/discord.ts +189 -11
  17. package/src/channels/types.ts +90 -0
  18. package/src/channels/whatsapp.ts +13 -4
  19. package/src/cli.ts +9 -4
  20. package/src/cognition/agency/consequence.ts +122 -1
  21. package/src/cognition/agency/conversation.aim.ts +260 -0
  22. package/src/cognition/agency/engines/action.selector.ts +85 -3
  23. package/src/cognition/agency/engines/affordance.synthesizer.ts +90 -1
  24. package/src/cognition/agency/engines/motor.schema.executor.ts +152 -10
  25. package/src/cognition/agency/engines/reafference.engine.ts +129 -2
  26. package/src/cognition/agency/proactive.communicator.ts +19 -3
  27. package/src/cognition/agency/reconcile.learning.ts +16 -2
  28. package/src/cognition/agency/restart.ts +66 -0
  29. package/src/cognition/agency/schemas/repertoire.ts +12 -5
  30. package/src/cognition/agency/selection.scoring.ts +33 -0
  31. package/src/cognition/agency/types.ts +35 -0
  32. package/src/cognition/cache/composition.ts +232 -0
  33. package/src/cognition/cache/deliberation.cache.ts +219 -0
  34. package/src/cognition/cache/fingerprint.ts +120 -0
  35. package/src/cognition/cache/types.ts +105 -0
  36. package/src/cognition/config.mirror.entities.ts +109 -1
  37. package/src/cognition/event.schemas.ts +22 -0
  38. package/src/cognition/faculties/autobiographical.narrator.ts +5 -10
  39. package/src/cognition/faculties/episodic.consolidator.ts +59 -3
  40. package/src/cognition/faculties/executive.engine/commands.ts +189 -14
  41. package/src/cognition/faculties/executive.engine/context.ts +67 -13
  42. package/src/cognition/faculties/executive.engine/deliberate.reasoning.ts +1 -1
  43. package/src/cognition/faculties/executive.engine/engine.ts +686 -187
  44. package/src/cognition/faculties/executive.engine/escalation.buffer.ts +162 -44
  45. package/src/cognition/faculties/executive.engine/facet.supervisor.ts +310 -65
  46. package/src/cognition/faculties/executive.engine/facet.ts +89 -26
  47. package/src/cognition/faculties/executive.engine/gating.ts +14 -14
  48. package/src/cognition/faculties/executive.engine/parser.ts +21 -1
  49. package/src/cognition/faculties/executive.engine/prompt.factory.ts +169 -20
  50. package/src/cognition/faculties/executive.engine/types.ts +69 -0
  51. package/src/cognition/faculties/goal.manager.ts +94 -14
  52. package/src/cognition/faculties/known.entity.tracker.ts +267 -28
  53. package/src/cognition/faculties/moral.evaluator.ts +8 -3
  54. package/src/cognition/faculties/persona.consolidator.ts +141 -0
  55. package/src/cognition/faculties/reputation.tracker.ts +66 -2
  56. package/src/cognition/faculties/self.model.updater.ts +19 -12
  57. package/src/cognition/faculties/social.perception.ts +47 -3
  58. package/src/cognition/faculties/threat.evaluator.ts +7 -0
  59. package/src/cognition/faculties/working.memory.ts +10 -20
  60. package/src/cognition/identity.entity.ts +205 -0
  61. package/src/cognition/index.ts +11 -0
  62. package/src/cognition/memory/vector.adapter.ts +12 -3
  63. package/src/cognition/memory/vector.embedder.ts +54 -7
  64. package/src/cognition/persona.prior.ts +6 -0
  65. package/src/cognition/senses/audition.engine/engine.ts +404 -46
  66. package/src/cognition/senses/base.sense.engine.ts +1 -1
  67. package/src/cognition/senses/index.ts +12 -0
  68. package/src/cognition/social.identity.ts +273 -0
  69. package/src/cognition/utilities/token.tracker.ts +246 -98
  70. package/src/core/orchestrator.ts +38 -0
  71. package/src/host/boot.ts +78 -22
  72. package/src/index.ts +35 -0
  73. package/src/llm/index.ts +415 -97
  74. package/src/llm/routing.ts +204 -0
  75. package/src/llm/summarizer.ts +5 -1
  76. package/src/llm/wire.contracts.ts +19 -0
  77. package/src/pma/index.ts +67 -53
  78. package/src/runners/thin-shim.runner.ts +18 -6
  79. package/src/sdk/will.ts +121 -22
  80. package/src/stem/assembly.audit.ts +1 -0
  81. package/src/stem/guards/identity.coherence.ts +17 -6
  82. package/src/stem/index.ts +82 -5
  83. package/src/stem/mind.ts +327 -79
  84. package/src/stem/policy/arbiter.ts +49 -14
  85. package/src/stem/policy/rule.table.ts +2 -2
  86. package/src/stem/tracts/effector.controller.ts +56 -9
  87. package/src/stem/tracts/outbox.writer.ts +40 -2
  88. 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
+ }