@mindot/will 0.5.0 → 0.7.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/dist/channels/discord.d.ts +1 -1
- package/dist/channels/discord.js.map +1 -1
- package/dist/channels/whatsapp.d.ts +1 -1
- package/dist/channels/whatsapp.js.map +1 -1
- package/dist/cli.js +998 -59
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +998 -59
- package/dist/index.js.map +1 -1
- package/dist/mcp/effectors.d.ts +1 -1
- package/dist/mcp/effectors.js.map +1 -1
- package/dist/{will-BDq-TMQr.d.ts → will-DAW0l-lY.d.ts} +90 -10
- package/package.json +1 -1
- package/src/channels/discord.ts +11 -11
- package/src/channels/roster.ts +1 -1
- package/src/channels/types.ts +1 -1
- package/src/channels/whatsapp.ts +25 -25
- package/src/cli.ts +20 -20
- package/src/cognition/acp.ts +28 -0
- package/src/cognition/agency/consequence.ts +174 -0
- package/src/cognition/agency/engines/action.selector.ts +333 -48
- package/src/cognition/agency/engines/affordance.synthesizer.ts +35 -16
- package/src/cognition/agency/engines/deliberation.engine.ts +28 -14
- package/src/cognition/agency/engines/motor.schema.executor.ts +90 -14
- package/src/cognition/agency/engines/reafference.engine.ts +152 -23
- package/src/cognition/agency/execution.primitives.ts +13 -13
- package/src/cognition/agency/proactive.communicator.ts +1 -1
- package/src/cognition/agency/reconcile.learning.ts +10 -1
- package/src/cognition/agency/revocation.ts +79 -0
- package/src/cognition/agency/schemas/repertoire.ts +109 -9
- package/src/cognition/agency/selection.scoring.ts +32 -10
- package/src/cognition/agency/types.ts +9 -0
- package/src/cognition/bus.ts +2 -2
- package/src/cognition/completion.inbox.ts +1 -1
- package/src/cognition/config.mirror.entities.ts +5 -5
- package/src/cognition/event.log.ts +2 -2
- package/src/cognition/event.schemas.ts +67 -67
- package/src/cognition/faculties/aesthetic.evaluator.ts +6 -6
- package/src/cognition/faculties/affective.blender.ts +36 -4
- package/src/cognition/faculties/attachment.evaluator.ts +6 -6
- package/src/cognition/faculties/attention.allocator.ts +63 -8
- package/src/cognition/faculties/autobiographical.narrator.ts +3 -3
- package/src/cognition/faculties/bias.detector.ts +7 -7
- package/src/cognition/faculties/circadian.oscillator.ts +3 -3
- package/src/cognition/faculties/confidence.calibrator.ts +7 -7
- package/src/cognition/faculties/dream.simulator.ts +5 -5
- package/src/cognition/faculties/empathy.simulator.ts +7 -7
- package/src/cognition/faculties/energy.regulator.ts +4 -4
- package/src/cognition/faculties/episodic.consolidator.ts +6 -6
- package/src/cognition/faculties/executive.engine/commands.ts +6 -6
- package/src/cognition/faculties/executive.engine/context.ts +42 -42
- package/src/cognition/faculties/executive.engine/deferred.effects.ts +2 -2
- package/src/cognition/faculties/executive.engine/engine.ts +25 -25
- package/src/cognition/faculties/executive.engine/facet.supervisor.ts +12 -12
- package/src/cognition/faculties/executive.engine/facet.ts +18 -18
- package/src/cognition/faculties/executive.engine/gating.ts +2 -2
- package/src/cognition/faculties/executive.engine/messages.ts +3 -3
- package/src/cognition/faculties/executive.engine/parser.ts +11 -11
- package/src/cognition/faculties/executive.engine/prompt.factory.ts +55 -55
- package/src/cognition/faculties/exteroception.ts +99 -5
- package/src/cognition/faculties/forgetting.curve.ts +3 -3
- package/src/cognition/faculties/frustration.evaluator.ts +6 -6
- package/src/cognition/faculties/goal.manager.ts +15 -15
- package/src/cognition/faculties/inhibition.controller.ts +3 -3
- package/src/cognition/faculties/interoception.ts +3 -3
- package/src/cognition/faculties/introspection.engine.ts +3 -3
- package/src/cognition/faculties/known.entity.tracker.ts +15 -15
- package/src/cognition/faculties/loss.evaluator.ts +2 -2
- package/src/cognition/faculties/moral.evaluator.ts +4 -4
- package/src/cognition/faculties/novelty.detector.ts +4 -4
- package/src/cognition/faculties/persona.consolidator.ts +12 -12
- package/src/cognition/faculties/planning.engine/engine.ts +47 -47
- package/src/cognition/faculties/planning.engine/plan.frontier.ts +5 -5
- package/src/cognition/faculties/planning.engine/plan.store.ts +1 -1
- package/src/cognition/faculties/planning.engine/plan.supervision.ts +20 -20
- package/src/cognition/faculties/reputation.tracker.ts +6 -6
- package/src/cognition/faculties/reward.evaluator.ts +4 -4
- package/src/cognition/faculties/self.model.updater.ts +8 -8
- package/src/cognition/faculties/semantic.engine/integrator.ts +15 -15
- package/src/cognition/faculties/sleep.pressure.regulator.ts +4 -4
- package/src/cognition/faculties/social.perception.ts +3 -3
- package/src/cognition/faculties/spaced.repetition.ts +2 -2
- package/src/cognition/faculties/stress.regulator.ts +27 -4
- package/src/cognition/faculties/task.switcher.ts +11 -6
- package/src/cognition/faculties/theory.of.mind.ts +6 -6
- package/src/cognition/faculties/threat.evaluator.ts +3 -3
- package/src/cognition/faculties/working.memory.ts +8 -8
- package/src/cognition/generative.model.ts +2 -2
- package/src/cognition/memory/vector.adapter.ts +5 -5
- package/src/cognition/memory/vector.content.ts +4 -4
- package/src/cognition/memory/vector.embedder.ts +9 -9
- package/src/cognition/persona.prior.ts +1 -1
- package/src/cognition/schema.registry.ts +2 -2
- package/src/cognition/senses/audition.engine/engine.ts +2 -2
- package/src/cognition/senses/base.sense.engine.ts +1 -1
- package/src/cognition/utilities/token.tracker.ts +5 -5
- package/src/core/abstracts.ts +1 -1
- package/src/core/completion.recorder.ts +2 -2
- package/src/core/serialization.ts +3 -3
- package/src/core/snapshot.manager.ts +1 -1
- package/src/core/state.manager.ts +1 -1
- package/src/core/utils.ts +1 -1
- package/src/host/boot.ts +30 -30
- package/src/host/utterances.ts +1 -1
- package/src/llm/index.ts +9 -9
- package/src/llm/summarizer.ts +4 -4
- package/src/llm/wire.contracts.ts +1 -1
- package/src/mcp/effectors.ts +10 -10
- package/src/mcp/server.ts +11 -11
- package/src/pma/eval.ts +1 -1
- package/src/pma/index.ts +21 -21
- package/src/runners/outreach.runner.ts +3 -3
- package/src/runners/social.runner.ts +1 -1
- package/src/runners/thin-shim.runner.ts +1 -1
- package/src/sdk/will.ts +5 -5
- package/src/serve/server.ts +12 -12
- package/src/stem/assembly.audit.ts +3 -3
- package/src/stem/guards/identity.coherence.ts +10 -10
- package/src/stem/guards/identity.guard.ts +22 -22
- package/src/stem/index.ts +26 -11
- package/src/stem/mind.ts +12 -12
- package/src/stem/policy/arbiter.ts +136 -0
- package/src/stem/policy/rule.table.ts +172 -0
- package/src/stem/policy/verdict.recorder.ts +0 -0
- package/src/stem/tracts/biography.writer.ts +3 -3
- package/src/stem/tracts/effector.controller.ts +383 -4
- package/src/stem/tracts/health.reporter.ts +1 -1
- package/src/stem/tracts/outbox.controller.ts +4 -0
- package/src/stem/tracts/outbox.writer.ts +4 -4
- package/src/stem/tracts/pma.controller.ts +1 -1
- package/src/stem/tracts/replay.controller.ts +10 -10
- package/src/stem/tracts/sensory.controller.ts +2 -2
- package/src/stem/tracts/session.logger.ts +4 -4
- package/src/stem/tracts/transport/socketio.transport.ts +7 -7
- package/src/stem/tracts/transport/stream.transport.ts +2 -2
- package/src/stem/tracts/transport.controller.ts +15 -15
|
@@ -68,10 +68,10 @@ export class OpenAICompatibleEmbedder implements EmbeddingProvider {
|
|
|
68
68
|
this._tokenTracker = config.tokenTracker ?? null
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
async embed( content: unknown, fn: string = 'recall'
|
|
71
|
+
async embed( content: unknown, fn: string = 'recall'): Promise<number[]> {
|
|
72
72
|
let response: Response
|
|
73
73
|
try {
|
|
74
|
-
response = await fetch(
|
|
74
|
+
response = await fetch(`${this._apiUrl}/embeddings`, {
|
|
75
75
|
method: 'POST',
|
|
76
76
|
headers: {
|
|
77
77
|
'Content-Type': 'application/json',
|
|
@@ -87,13 +87,13 @@ export class OpenAICompatibleEmbedder implements EmbeddingProvider {
|
|
|
87
87
|
}
|
|
88
88
|
catch( err ){
|
|
89
89
|
// AbortSignal.timeout rejects with a DOMException named 'TimeoutError'.
|
|
90
|
-
if( err instanceof Error && err.name === 'TimeoutError'
|
|
91
|
-
throw new Error(
|
|
90
|
+
if( err instanceof Error && err.name === 'TimeoutError')
|
|
91
|
+
throw new Error(`Embedding request timed out after ${this._timeoutMs}ms`)
|
|
92
92
|
throw err
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
if( !response.ok )
|
|
96
|
-
throw new Error(
|
|
96
|
+
throw new Error(`Embedding failed: ${response.status} ${response.statusText}`)
|
|
97
97
|
|
|
98
98
|
const data = await response.json() as {
|
|
99
99
|
data?: Array<{ embedding?: number[] }>
|
|
@@ -104,7 +104,7 @@ export class OpenAICompatibleEmbedder implements EmbeddingProvider {
|
|
|
104
104
|
// opaque TypeError on an empty/malformed body (FN16).
|
|
105
105
|
const embedding = data?.data?.[ 0 ]?.embedding
|
|
106
106
|
if( !Array.isArray( embedding ) || embedding.length === 0 )
|
|
107
|
-
throw new Error(
|
|
107
|
+
throw new Error(`Embedding response was empty or malformed for model ${this.modelName}`)
|
|
108
108
|
|
|
109
109
|
// Meter embedding token usage (input-only — embeddings have no completion).
|
|
110
110
|
// Recorded under the 'embedding' category so per-Will dashboards can split
|
|
@@ -128,7 +128,7 @@ export class OpenAICompatibleEmbedder implements EmbeddingProvider {
|
|
|
128
128
|
return embedding
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
async embedBatch( contents: unknown[], fn: string = 'index'
|
|
131
|
+
async embedBatch( contents: unknown[], fn: string = 'index'): Promise<number[][]> {
|
|
132
132
|
// Bounded fan-out: cap concurrent requests at _maxConcurrency instead of
|
|
133
133
|
// firing all of them at once (FN16), while preserving input order.
|
|
134
134
|
const results: number[][] = new Array( contents.length )
|
|
@@ -170,7 +170,7 @@ export class MockEmbedder implements EmbeddingProvider {
|
|
|
170
170
|
this._seed = seed
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
async embed( content: unknown, _fn: string = 'recall'
|
|
173
|
+
async embed( content: unknown, _fn: string = 'recall'): Promise<number[]> {
|
|
174
174
|
const str = typeof content === 'string' ? content : JSON.stringify( content )
|
|
175
175
|
const hash = this._hashString( str )
|
|
176
176
|
const embedding: number[] = []
|
|
@@ -184,7 +184,7 @@ export class MockEmbedder implements EmbeddingProvider {
|
|
|
184
184
|
return embedding
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
async embedBatch( contents: unknown[], fn: string = 'index'
|
|
187
|
+
async embedBatch( contents: unknown[], fn: string = 'index'): Promise<number[][]> {
|
|
188
188
|
return Promise.all( contents.map( c => this.embed( c, fn ) ) )
|
|
189
189
|
}
|
|
190
190
|
|
|
@@ -77,7 +77,7 @@ export function readEffectiveParams(
|
|
|
77
77
|
|
|
78
78
|
const out: Record<string, number> = { ...base }
|
|
79
79
|
for( const [ key, delta ] of Object.entries( prior ) )
|
|
80
|
-
if( typeof out[ key ] === 'number' && typeof delta === 'number'
|
|
80
|
+
if( typeof out[ key ] === 'number' && typeof delta === 'number')
|
|
81
81
|
out[ key ] = out[ key ] + delta
|
|
82
82
|
|
|
83
83
|
return out
|
|
@@ -137,8 +137,8 @@ globalSchemaRegistry.register({
|
|
|
137
137
|
version: 1,
|
|
138
138
|
validate( payload ){
|
|
139
139
|
if( !isRecord( payload ) ) return 'clock.tick payload must be an object'
|
|
140
|
-
if( typeof payload['tick'] !== 'number'
|
|
141
|
-
if( typeof payload['delta'] !== 'number'
|
|
140
|
+
if( typeof payload['tick'] !== 'number') return 'clock.tick requires numeric tick'
|
|
141
|
+
if( typeof payload['delta'] !== 'number') return 'clock.tick requires numeric delta'
|
|
142
142
|
|
|
143
143
|
return null
|
|
144
144
|
}
|
|
@@ -474,7 +474,7 @@ export class AuditionEngine extends BaseSenseEngine {
|
|
|
474
474
|
attachmentScore: this._getAttachmentScore?.( entityId ) ?? 0,
|
|
475
475
|
activeGoalText: this._getActiveGoalText?.() ?? [],
|
|
476
476
|
})
|
|
477
|
-
const salience = this._model.observe(
|
|
477
|
+
const salience = this._model.observe(`audition.${entityId}`, langEnergy ).salience
|
|
478
478
|
|
|
479
479
|
// ── Percept ────────────────────────────────────────────────
|
|
480
480
|
const percept: LanguagePercept = {
|
|
@@ -717,7 +717,7 @@ export class AuditionEngine extends BaseSenseEngine {
|
|
|
717
717
|
goalsToAbandon: output.goalsToAbandon,
|
|
718
718
|
newBeliefs: output.newBeliefs,
|
|
719
719
|
knownEntityUpdates: output.knownEntityUpdates,
|
|
720
|
-
requiresMasterAttention: ( output.actions ?? [] ).some( a => a.type === 'escalate'
|
|
720
|
+
requiresMasterAttention: ( output.actions ?? [] ).some( a => a.type === 'escalate'),
|
|
721
721
|
}
|
|
722
722
|
}
|
|
723
723
|
}
|
|
@@ -112,6 +112,6 @@ export abstract class ShellSenseEngine extends BaseSenseEngine {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
protected async _perceive( _input: SensoryInput ): Promise<void> {
|
|
115
|
-
logger.warn(
|
|
115
|
+
logger.warn(`[${this.name}] ingest() called — engine is a shell, not yet implemented.`)
|
|
116
116
|
}
|
|
117
117
|
}
|
|
@@ -96,9 +96,9 @@ const CACHE_WRITE_MULT = 1.25
|
|
|
96
96
|
*/
|
|
97
97
|
function normalizeModelKey( model: string ): string {
|
|
98
98
|
let m = model.toLowerCase().trim()
|
|
99
|
-
const slash = m.lastIndexOf(
|
|
99
|
+
const slash = m.lastIndexOf('/')
|
|
100
100
|
if( slash >= 0 ) m = m.slice( slash + 1 ) // drop "provider/" prefix
|
|
101
|
-
return m.replace( /[-@]\d{6,8}$/, ''
|
|
101
|
+
return m.replace( /[-@]\d{6,8}$/, '') // drop trailing -YYYYMMDD date stamp
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
// Pre-index the pricing table by normalized model name for O(1), date-insensitive
|
|
@@ -106,7 +106,7 @@ function normalizeModelKey( model: string ): string {
|
|
|
106
106
|
const PRICING_BY_NORM: Record<string, { input: number; output: number }> = ( () => {
|
|
107
107
|
const out: Record<string, { input: number; output: number }> = {}
|
|
108
108
|
for( const [ key, price ] of Object.entries( MODEL_PRICING ) ){
|
|
109
|
-
if( key === '__default__'
|
|
109
|
+
if( key === '__default__') continue
|
|
110
110
|
out[ normalizeModelKey( key ) ] = price
|
|
111
111
|
}
|
|
112
112
|
return out
|
|
@@ -305,10 +305,10 @@ export class TokenTracker implements SimulationEngine {
|
|
|
305
305
|
if( this._ledgerPath ){
|
|
306
306
|
try {
|
|
307
307
|
if( !this._ledgerDirReady ){
|
|
308
|
-
mkdirSync( this._ledgerPath.slice( 0, this._ledgerPath.lastIndexOf(
|
|
308
|
+
mkdirSync( this._ledgerPath.slice( 0, this._ledgerPath.lastIndexOf('/') ), { recursive: true } )
|
|
309
309
|
this._ledgerDirReady = true
|
|
310
310
|
}
|
|
311
|
-
appendFileSync( this._ledgerPath, JSON.stringify( record ) + '\n'
|
|
311
|
+
appendFileSync( this._ledgerPath, JSON.stringify( record ) + '\n')
|
|
312
312
|
}
|
|
313
313
|
catch { /* ledger file is best-effort */ }
|
|
314
314
|
}
|
package/src/core/abstracts.ts
CHANGED
|
@@ -47,7 +47,7 @@ export class BunStorageAdapter implements StorageAdapter {
|
|
|
47
47
|
return Bun.file( path ).text()
|
|
48
48
|
|
|
49
49
|
const { readFile } = await import('node:fs/promises')
|
|
50
|
-
return readFile( path, 'utf8'
|
|
50
|
+
return readFile( path, 'utf8')
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
async readBytes( path: string ): Promise<Uint8Array> {
|
|
@@ -97,11 +97,11 @@ export class RecordedCompletionSource implements LLMCompletionSource {
|
|
|
97
97
|
nextCompletion( tick: number, systemPrompt: string, userMessage: string ): LLMCompletionRecord {
|
|
98
98
|
const queue = this._byTick.get( tick )
|
|
99
99
|
if( !queue || queue.length === 0 )
|
|
100
|
-
throw new Error(
|
|
100
|
+
throw new Error(`replay: no recorded LLM completion for tick ${tick} (recording is incomplete or call timing diverged)`)
|
|
101
101
|
|
|
102
102
|
const idx = queue.findIndex( r => r.systemPrompt === systemPrompt && r.userMessage === userMessage )
|
|
103
103
|
if( idx === -1 )
|
|
104
|
-
throw new Error(
|
|
104
|
+
throw new Error(`replay: LLM prompt diverged at tick ${tick} — no recorded completion matches this call (upstream non-determinism)`)
|
|
105
105
|
|
|
106
106
|
return queue.splice( idx, 1 )[ 0 ]!
|
|
107
107
|
}
|
|
@@ -222,7 +222,7 @@ export class DefaultSerializer implements Serializer {
|
|
|
222
222
|
if( Array.isArray( value ) )
|
|
223
223
|
return value.map( v => this._encodeSpecial( v ) )
|
|
224
224
|
|
|
225
|
-
if( typeof value === 'object'
|
|
225
|
+
if( typeof value === 'object'){
|
|
226
226
|
const out: Record<string, unknown> = {}
|
|
227
227
|
for( const [ k, v ] of Object.entries( value as Record<string, unknown> ) )
|
|
228
228
|
out[k] = this._encodeSpecial( v )
|
|
@@ -233,11 +233,11 @@ export class DefaultSerializer implements Serializer {
|
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
private _decodeSpecial( value: unknown ): unknown {
|
|
236
|
-
if( value === null || typeof value !== 'object'
|
|
236
|
+
if( value === null || typeof value !== 'object') return value
|
|
237
237
|
if( Array.isArray( value ) ) return value.map( v => this._decodeSpecial( v ) )
|
|
238
238
|
|
|
239
239
|
const tag = ( value as Record<string, unknown> ).__ser
|
|
240
|
-
if( typeof tag === 'string'
|
|
240
|
+
if( typeof tag === 'string'){
|
|
241
241
|
const payload = ( value as Record<string, unknown> ).__val
|
|
242
242
|
switch( tag ){
|
|
243
243
|
case 'undefined': return undefined
|
|
@@ -243,7 +243,7 @@ export class SnapshotManager {
|
|
|
243
243
|
return this._serializer.deserialize( JSON.stringify( parsed ) )
|
|
244
244
|
}
|
|
245
245
|
catch( err ){
|
|
246
|
-
logger.warn(
|
|
246
|
+
logger.warn(`[SnapshotManager] Could not load latest snapshot:`, err )
|
|
247
247
|
return undefined
|
|
248
248
|
}
|
|
249
249
|
}
|
|
@@ -14,7 +14,7 @@ import { deepFreeze } from '#core/utils'
|
|
|
14
14
|
*/
|
|
15
15
|
function resolveFreezeState(): boolean {
|
|
16
16
|
const flag = process.env.WILL_FREEZE_STATE
|
|
17
|
-
if( flag !== undefined && flag !== ''
|
|
17
|
+
if( flag !== undefined && flag !== '')
|
|
18
18
|
return flag !== '0' && flag.toLowerCase() !== 'false'
|
|
19
19
|
return process.env.NODE_ENV !== 'production'
|
|
20
20
|
}
|
package/src/core/utils.ts
CHANGED
|
@@ -52,7 +52,7 @@ export function createPRNG( seed: number ): SeededPRNG {
|
|
|
52
52
|
*/
|
|
53
53
|
export function deepFreeze<T>( value: T ): T {
|
|
54
54
|
// Only objects/arrays/functions are freezable; primitives are immutable already.
|
|
55
|
-
if( value === null || typeof value !== 'object'
|
|
55
|
+
if( value === null || typeof value !== 'object') return value
|
|
56
56
|
if( Object.isFrozen( value ) ) return value
|
|
57
57
|
|
|
58
58
|
// Freeze the container first so a cyclic reference back to it hits the
|
package/src/host/boot.ts
CHANGED
|
@@ -37,12 +37,12 @@ import { anthropicWireHeaders, defaultBaseFor, defaultModelFor } from '#llm/inde
|
|
|
37
37
|
*/
|
|
38
38
|
export function routeLogsToStderr(): void {
|
|
39
39
|
const err = ( level: string ) => ( msg: string, ...rest: unknown[] ) =>
|
|
40
|
-
console.error(
|
|
41
|
-
setLogger( { debug: () => {}, info: err(
|
|
40
|
+
console.error(`[will:${ level }] ${ msg }`, ...rest )
|
|
41
|
+
setLogger( { debug: () => {}, info: err('info'), warn: err('warn'), error: err('error') } )
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
function slug( s: string ): string {
|
|
45
|
-
return s.toLowerCase().replace( /[^a-z0-9]+/g, '-'
|
|
45
|
+
return s.toLowerCase().replace( /[^a-z0-9]+/g, '-').replace( /^-+|-+$/g, '') || 'will'
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/** The LLM mode the hosts will boot with: an explicit WILL_LLM, else whichever
|
|
@@ -57,7 +57,7 @@ export function resolveLlmMode(): 'mock' | 'anthropic' | 'glm' {
|
|
|
57
57
|
|
|
58
58
|
/** The key for a live mode — the provider-agnostic override first, then the
|
|
59
59
|
* provider's own env. */
|
|
60
|
-
function resolveLlmKey( mode: 'anthropic' | 'glm'
|
|
60
|
+
function resolveLlmKey( mode: 'anthropic' | 'glm'): string | undefined {
|
|
61
61
|
return process.env.WILL_LLM_API_KEY
|
|
62
62
|
?? ( mode === 'glm' ? process.env.ZAI_API_KEY : process.env.ANTHROPIC_API_KEY )
|
|
63
63
|
}
|
|
@@ -79,13 +79,13 @@ function resolveLlmKey( mode: 'anthropic' | 'glm' ): string | undefined {
|
|
|
79
79
|
*/
|
|
80
80
|
async function preflightLLM( anatomy: string ): Promise<void> {
|
|
81
81
|
const mode = resolveLlmMode()
|
|
82
|
-
if( mode === 'mock' || anatomy === 'reflex'
|
|
82
|
+
if( mode === 'mock' || anatomy === 'reflex') return
|
|
83
83
|
|
|
84
84
|
const key = resolveLlmKey( mode )
|
|
85
85
|
if( !key ){
|
|
86
86
|
const expected = mode === 'glm' ? 'ZAI_API_KEY' : 'ANTHROPIC_API_KEY'
|
|
87
|
-
console.error(
|
|
88
|
-
console.error(
|
|
87
|
+
console.error(`[will] WILL_LLM=${ mode } but no ${ expected } / WILL_LLM_API_KEY is set.`)
|
|
88
|
+
console.error('[will] The Will would boot, perceive, and never speak. Set a key, or run keyless with WILL_LLM=mock.')
|
|
89
89
|
process.exit( 2 )
|
|
90
90
|
}
|
|
91
91
|
|
|
@@ -95,9 +95,9 @@ async function preflightLLM( anatomy: string ): Promise<void> {
|
|
|
95
95
|
// speaks the same wire at Z.ai's compat endpoint, so one ping serves both.
|
|
96
96
|
const base = process.env.WILL_LLM_BASE_URL ?? defaultBaseFor( mode )
|
|
97
97
|
const model = process.env.WILL_LLM_MODEL
|
|
98
|
-
?? ( mode === 'glm' ? defaultModelFor(
|
|
98
|
+
?? ( mode === 'glm' ? defaultModelFor('glm') : 'claude-haiku-4-5-20251001')
|
|
99
99
|
try {
|
|
100
|
-
const res = await fetch(
|
|
100
|
+
const res = await fetch(`${ base }/messages`, {
|
|
101
101
|
method: 'POST',
|
|
102
102
|
headers: anthropicWireHeaders( mode, key ),
|
|
103
103
|
body: JSON.stringify( { model, max_tokens: 1, messages: [ { role: 'user', content: 'ping' } ] } ),
|
|
@@ -105,20 +105,20 @@ async function preflightLLM( anatomy: string ): Promise<void> {
|
|
|
105
105
|
} )
|
|
106
106
|
if( res.ok ) return
|
|
107
107
|
|
|
108
|
-
const detail = ( await res.text().catch( () => ''
|
|
108
|
+
const detail = ( await res.text().catch( () => '') ).slice( 0, 300 )
|
|
109
109
|
const fatal = res.status === 400 || res.status === 401 || res.status === 403
|
|
110
110
|
if( !fatal ){
|
|
111
|
-
console.error(
|
|
111
|
+
console.error(`[will] the executive's LLM answered ${ res.status } on a test call — raising the mind anyway (it retries): ${ detail }`)
|
|
112
112
|
return
|
|
113
113
|
}
|
|
114
|
-
console.error(
|
|
115
|
-
console.error(
|
|
116
|
-
console.error(
|
|
114
|
+
console.error(`[will] the executive's LLM refused a test call (${ res.status }) — this Will would boot, perceive, and never speak:`)
|
|
115
|
+
console.error(` ${ detail }`)
|
|
116
|
+
console.error('[will] fix the key / credit / model above, or run keyless with WILL_LLM=mock.')
|
|
117
117
|
process.exit( 1 )
|
|
118
118
|
}
|
|
119
119
|
catch( e ){
|
|
120
|
-
console.error(
|
|
121
|
-
console.error(
|
|
120
|
+
console.error(`[will] could not reach the executive's LLM: ${ ( e as Error ).message }`)
|
|
121
|
+
console.error('[will] the Will would boot and stay silent. Check the network / WILL_LLM_BASE_URL, or run keyless with WILL_LLM=mock.')
|
|
122
122
|
process.exit( 1 )
|
|
123
123
|
}
|
|
124
124
|
}
|
|
@@ -138,8 +138,8 @@ export interface BootedWill {
|
|
|
138
138
|
/** Raise the mind from env config — wake from the artifact if one exists. */
|
|
139
139
|
export async function bootWillFromEnv(): Promise<BootedWill> {
|
|
140
140
|
const name = process.env.WILL_NAME ?? 'Will'
|
|
141
|
-
const pmaPath = resolve( process.env.WILL_PMA_PATH ?? `.will/${ slug( name ) }.pma.json`
|
|
142
|
-
const tickMs = parseInt( process.env.WILL_TICK_MS ?? '1000'
|
|
141
|
+
const pmaPath = resolve( process.env.WILL_PMA_PATH ?? `.will/${ slug( name ) }.pma.json`)
|
|
142
|
+
const tickMs = parseInt( process.env.WILL_TICK_MS ?? '1000')
|
|
143
143
|
const anatomy = ( process.env.WILL_ANATOMY as CreateWillOptions['anatomy'] ) ?? 'mind'
|
|
144
144
|
|
|
145
145
|
await preflightLLM( anatomy )
|
|
@@ -153,23 +153,23 @@ export async function bootWillFromEnv(): Promise<BootedWill> {
|
|
|
153
153
|
|
|
154
154
|
let will: Will
|
|
155
155
|
if( existsSync( pmaPath ) ){
|
|
156
|
-
const pma = JSON.parse( readFileSync( pmaPath, 'utf8'
|
|
156
|
+
const pma = JSON.parse( readFileSync( pmaPath, 'utf8') ) as PMASnapshot
|
|
157
157
|
will = await Will.wake( pma, opts )
|
|
158
|
-
console.error(
|
|
158
|
+
console.error(`[will] ${ name } woke from ${ pmaPath }`)
|
|
159
159
|
// A woken Will carries its own identity — that is the point of an artifact.
|
|
160
160
|
// But an operator editing WILL_IDENTITY and seeing nothing change deserves
|
|
161
161
|
// to know why, rather than concluding the persona layer is broken.
|
|
162
162
|
if( process.env.WILL_IDENTITY )
|
|
163
|
-
console.error(
|
|
163
|
+
console.error(`[will] note: WILL_IDENTITY is ignored — ${ name } woke as itself. Delete ${ pmaPath } to be born fresh from it.`)
|
|
164
164
|
}
|
|
165
165
|
else {
|
|
166
166
|
will = await Will.create( {
|
|
167
167
|
...opts,
|
|
168
168
|
identity: { prompt: process.env.WILL_IDENTITY ?? `I am ${ name }, a persistent mind.` },
|
|
169
169
|
} )
|
|
170
|
-
console.error(
|
|
170
|
+
console.error(`[will] ${ name } born (no artifact at ${ pmaPath } yet)`)
|
|
171
171
|
}
|
|
172
|
-
will.on(
|
|
172
|
+
will.on('error', e => console.error(`[will] error: ${ e.message }`) )
|
|
173
173
|
|
|
174
174
|
// Onward bridges: MCP servers whose tools become the Will's OWN abilities.
|
|
175
175
|
// Best-effort — a bad entry warns and is skipped; the mind still boots.
|
|
@@ -181,12 +181,12 @@ export async function bootWillFromEnv(): Promise<BootedWill> {
|
|
|
181
181
|
try {
|
|
182
182
|
const { names, close } = await connectMcpEffectors( will, source )
|
|
183
183
|
cleanups.push( close )
|
|
184
|
-
console.error(
|
|
184
|
+
console.error(`[will] ${ name } gained abilities: ${ names.join(', ') }`)
|
|
185
185
|
}
|
|
186
|
-
catch( e ){ console.error(
|
|
186
|
+
catch( e ){ console.error(`[will] MCP bridge failed (skipped): ${ ( e as Error ).message }`) }
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
|
-
catch( e ){ console.error(
|
|
189
|
+
catch( e ){ console.error(`[will] WILL_MCP_SERVERS is not valid JSON — ignoring: ${ ( e as Error ).message }`) }
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
// Hibernate exactly once on the way out — cleanups (LIFO), distill + stop, persist.
|
|
@@ -199,13 +199,13 @@ export async function bootWillFromEnv(): Promise<BootedWill> {
|
|
|
199
199
|
const pma = await will.hibernate()
|
|
200
200
|
mkdirSync( dirname( pmaPath ), { recursive: true } )
|
|
201
201
|
writeFileSync( pmaPath, JSON.stringify( pma ) )
|
|
202
|
-
console.error(
|
|
202
|
+
console.error(`[will] ${ name } hibernated to ${ pmaPath } (${ why })`)
|
|
203
203
|
}
|
|
204
|
-
catch( e ){ console.error(
|
|
204
|
+
catch( e ){ console.error(`[will] hibernate failed: ${ ( e as Error ).message }`) }
|
|
205
205
|
process.exit( 0 )
|
|
206
206
|
}
|
|
207
|
-
process.on(
|
|
208
|
-
process.on(
|
|
207
|
+
process.on('SIGINT', () => void shutdown('SIGINT') )
|
|
208
|
+
process.on('SIGTERM', () => void shutdown('SIGTERM') )
|
|
209
209
|
|
|
210
210
|
return {
|
|
211
211
|
will, name, pmaPath, tickMs, anatomy,
|
package/src/host/utterances.ts
CHANGED
package/src/llm/index.ts
CHANGED
|
@@ -187,7 +187,7 @@ export class LLMDirector {
|
|
|
187
187
|
* {"actions":[{"type":"...","reasoning":"...","expectedOutcome":"..."}],...}
|
|
188
188
|
* Strategy 1 (JSON.parse) handles this directly.
|
|
189
189
|
*/
|
|
190
|
-
private _mockResponse( tick: number, userMessage: string = ''
|
|
190
|
+
private _mockResponse( tick: number, userMessage: string = ''): LLMCallResult {
|
|
191
191
|
// ── Conversation facet turn ────────────────────────────────
|
|
192
192
|
// Detect the AuditionEngine facet focus via the SHARED contract (see
|
|
193
193
|
// llm/wire.contracts.ts) — the render/match pair whose earlier drift
|
|
@@ -404,7 +404,7 @@ export class LLMDirector {
|
|
|
404
404
|
catch( err ){
|
|
405
405
|
clearTimeout( timer )
|
|
406
406
|
if( controller.signal.aborted )
|
|
407
|
-
throw new Error(
|
|
407
|
+
throw new Error(`LLM stream to ${this._provider} timed out after ${this._timeoutMs}ms (no response)`)
|
|
408
408
|
throw err
|
|
409
409
|
}
|
|
410
410
|
|
|
@@ -434,7 +434,7 @@ export class LLMDirector {
|
|
|
434
434
|
for( const line of lines ){
|
|
435
435
|
if( !line.startsWith('data: ') ) continue
|
|
436
436
|
const raw = line.slice(6).trim()
|
|
437
|
-
if( raw === '[DONE]'
|
|
437
|
+
if( raw === '[DONE]') break
|
|
438
438
|
|
|
439
439
|
try {
|
|
440
440
|
const ev = JSON.parse( raw ) as {
|
|
@@ -552,8 +552,8 @@ export class LLMDirector {
|
|
|
552
552
|
}
|
|
553
553
|
catch( err ){
|
|
554
554
|
// AbortSignal.timeout rejects with a DOMException named 'TimeoutError'.
|
|
555
|
-
if( err instanceof Error && err.name === 'TimeoutError'
|
|
556
|
-
throw new Error(
|
|
555
|
+
if( err instanceof Error && err.name === 'TimeoutError')
|
|
556
|
+
throw new Error(`LLM request to ${this._provider} timed out after ${this._timeoutMs}ms`)
|
|
557
557
|
throw err
|
|
558
558
|
}
|
|
559
559
|
}
|
|
@@ -592,7 +592,7 @@ export class LLMDirector {
|
|
|
592
592
|
content: Array<{ type: string; text: string }>
|
|
593
593
|
usage: { input_tokens: number; output_tokens: number; cache_read_input_tokens?: number; cache_creation_input_tokens?: number }
|
|
594
594
|
},
|
|
595
|
-
text = data.content.find( b => b.type === 'text'
|
|
595
|
+
text = data.content.find( b => b.type === 'text')?.text ?? ''
|
|
596
596
|
|
|
597
597
|
return {
|
|
598
598
|
text,
|
|
@@ -673,7 +673,7 @@ export class LLMDirector {
|
|
|
673
673
|
}
|
|
674
674
|
|
|
675
675
|
const text = ( data.candidates?.[ 0 ]?.content?.parts ?? [] )
|
|
676
|
-
.map( p => p.text ?? ''
|
|
676
|
+
.map( p => p.text ?? '')
|
|
677
677
|
.join('')
|
|
678
678
|
|
|
679
679
|
return {
|
|
@@ -715,7 +715,7 @@ export class LLMDirector {
|
|
|
715
715
|
userMessage,
|
|
716
716
|
].join('\n')
|
|
717
717
|
|
|
718
|
-
const filepath = `${debugDir}/prompt-tick-${String( tick ).padStart( 6, '0'
|
|
718
|
+
const filepath = `${debugDir}/prompt-tick-${String( tick ).padStart( 6, '0')}.txt`
|
|
719
719
|
writeFileSync( filepath, content )
|
|
720
720
|
|
|
721
721
|
logger.info(`[executive] Debug prompt written → ${filepath} (~${estimatedTokens} tok estimated)`)
|
|
@@ -755,7 +755,7 @@ export class LLMDirector {
|
|
|
755
755
|
responseText,
|
|
756
756
|
].join('\n')
|
|
757
757
|
|
|
758
|
-
const filepath = `${debugDir}/response-tick-${String( tick ).padStart( 6, '0'
|
|
758
|
+
const filepath = `${debugDir}/response-tick-${String( tick ).padStart( 6, '0')}.txt`
|
|
759
759
|
writeFileSync( filepath, content )
|
|
760
760
|
}
|
|
761
761
|
catch {
|
package/src/llm/summarizer.ts
CHANGED
|
@@ -130,7 +130,7 @@ export class ExecutiveSummarizer {
|
|
|
130
130
|
|
|
131
131
|
private async _run(): Promise<void> {
|
|
132
132
|
if( !this._llmDirector ){
|
|
133
|
-
logger.warn(
|
|
133
|
+
logger.warn('[summarizer] skipping — LLMDirector not attached yet')
|
|
134
134
|
return
|
|
135
135
|
}
|
|
136
136
|
|
|
@@ -139,8 +139,8 @@ export class ExecutiveSummarizer {
|
|
|
139
139
|
|
|
140
140
|
try {
|
|
141
141
|
const userMessage = snapshot
|
|
142
|
-
.map( ( r, i ) => `[Cycle ${i + 1}]\n${r}`
|
|
143
|
-
.join(
|
|
142
|
+
.map( ( r, i ) => `[Cycle ${i + 1}]\n${r}`)
|
|
143
|
+
.join('\n\n---\n\n')
|
|
144
144
|
|
|
145
145
|
// Use _callCount as the tick value — it's only used for token tracking/logging
|
|
146
146
|
const result = await this._llmDirector.call(
|
|
@@ -161,7 +161,7 @@ export class ExecutiveSummarizer {
|
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
catch( err ){
|
|
164
|
-
logger.warn(
|
|
164
|
+
logger.warn('[summarizer] failed:', err instanceof Error ? err.message : err )
|
|
165
165
|
}
|
|
166
166
|
finally {
|
|
167
167
|
this._summarizing = false
|
|
@@ -36,7 +36,7 @@ export const REPLY_TEXT_CLOSE = `[/${REPLY_TEXT_TAG}]`
|
|
|
36
36
|
|
|
37
37
|
/** Wrap a reply body in the block markers (the mock's emission shape). */
|
|
38
38
|
export function wrapReplyText( body: string ): string {
|
|
39
|
-
return [ REPLY_TEXT_OPEN, body, REPLY_TEXT_CLOSE ].join(
|
|
39
|
+
return [ REPLY_TEXT_OPEN, body, REPLY_TEXT_CLOSE ].join('\n')
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
// ── Conversation-facet focus — render ↔ match pair ────────────
|
package/src/mcp/effectors.ts
CHANGED
|
@@ -67,10 +67,10 @@ export function describeMcpTool( tool: McpToolInfo ): string {
|
|
|
67
67
|
const props = tool.inputSchema?.properties ?? {}
|
|
68
68
|
const required = new Set( tool.inputSchema?.required ?? [] )
|
|
69
69
|
const argHints = Object.entries( props ).map( ( [ key, p ] ) =>
|
|
70
|
-
`${ key }${ required.has( key ) ? '' : '?' }${ p.description ? `: ${ p.description }` : '' }`
|
|
70
|
+
`${ key }${ required.has( key ) ? '' : '?' }${ p.description ? `: ${ p.description }` : '' }`)
|
|
71
71
|
|
|
72
|
-
const base = ( tool.description ?? `The ${ tool.name } tool.`
|
|
73
|
-
const hint = argHints.length > 0 ? ` (args — ${ argHints.join(
|
|
72
|
+
const base = ( tool.description ?? `The ${ tool.name } tool.`).trim().replace( /\s+/g, ' ')
|
|
73
|
+
const hint = argHints.length > 0 ? ` (args — ${ argHints.join('; ') })` : ''
|
|
74
74
|
const full = `${ base }${ hint }`
|
|
75
75
|
return full.length > MEANING_CAP ? `${ full.slice( 0, MEANING_CAP - 1 ) }…` : full
|
|
76
76
|
}
|
|
@@ -90,21 +90,21 @@ export function buildMcpHandler( client: Client, tool: McpToolInfo ): EffectorHa
|
|
|
90
90
|
if( !props || k in props ) filtered[ k ] = v
|
|
91
91
|
|
|
92
92
|
const missing = ( tool.inputSchema?.required ?? [] ).filter(
|
|
93
|
-
k => filtered[ k ] === undefined || filtered[ k ] === ''
|
|
93
|
+
k => filtered[ k ] === undefined || filtered[ k ] === '')
|
|
94
94
|
if( missing.length > 0 )
|
|
95
95
|
return {
|
|
96
96
|
success: false,
|
|
97
|
-
description: `${ tool.name } needs ${ missing.join(
|
|
97
|
+
description: `${ tool.name } needs ${ missing.join(', ') } — enact it deliberately, supplying them in the action's args.`,
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
try {
|
|
101
101
|
const res = await client.callTool( { name: tool.name, arguments: filtered } ) as
|
|
102
102
|
{ content?: Array<{ type: string; text?: string }>; isError?: boolean }
|
|
103
103
|
const text = ( res.content ?? [] )
|
|
104
|
-
.filter( c => c.type === 'text' && typeof c.text === 'string'
|
|
104
|
+
.filter( c => c.type === 'text' && typeof c.text === 'string')
|
|
105
105
|
.map( c => c.text as string )
|
|
106
|
-
.join(
|
|
107
|
-
.trim() || ( res.isError ? 'The tool reported an error.' : 'Done (no output).'
|
|
106
|
+
.join('\n')
|
|
107
|
+
.trim() || ( res.isError ? 'The tool reported an error.' : 'Done (no output).')
|
|
108
108
|
const bounded = text.length > RESULT_DESCRIPTION_CAP ? `${ text.slice( 0, RESULT_DESCRIPTION_CAP - 1 ) }…` : text
|
|
109
109
|
return { success: !res.isError, description: bounded }
|
|
110
110
|
}
|
|
@@ -115,10 +115,10 @@ export function buildMcpHandler( client: Client, tool: McpToolInfo ): EffectorHa
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
async function connect( source: McpToolsSource ): Promise<{ client: Client; owned: boolean }> {
|
|
118
|
-
if(
|
|
118
|
+
if('client' in source ) return { client: source.client, owned: false }
|
|
119
119
|
|
|
120
120
|
const client = new Client( { name: 'mindot-will', version: '0' } )
|
|
121
|
-
if(
|
|
121
|
+
if('url' in source )
|
|
122
122
|
await client.connect( new StreamableHTTPClientTransport( new URL( source.url ) ) )
|
|
123
123
|
else
|
|
124
124
|
await client.connect( new StdioClientTransport( {
|
package/src/mcp/server.ts
CHANGED
|
@@ -35,7 +35,7 @@ export interface WillMcpOptions {
|
|
|
35
35
|
function serverVersion(): string {
|
|
36
36
|
// Same relative depth from src/mcp/ and dist/mcp/ — resolves in both.
|
|
37
37
|
try {
|
|
38
|
-
return JSON.parse( readFileSync( new URL(
|
|
38
|
+
return JSON.parse( readFileSync( new URL('../../package.json', import.meta.url ), 'utf8') ).version ?? '0.0.0'
|
|
39
39
|
}
|
|
40
40
|
catch { return '0.0.0' }
|
|
41
41
|
}
|
|
@@ -54,7 +54,7 @@ export function buildWillMcpServer( will: Will, opts: WillMcpOptions = {} ): Mcp
|
|
|
54
54
|
|
|
55
55
|
// ── Tools ─────────────────────────────────────────────────
|
|
56
56
|
|
|
57
|
-
server.registerTool(
|
|
57
|
+
server.registerTool('perceive', {
|
|
58
58
|
title: 'Perceive',
|
|
59
59
|
description:
|
|
60
60
|
`Deliver a stimulus (something said or observed) into ${ will.name }'s sensory field. ` +
|
|
@@ -62,9 +62,9 @@ export function buildWillMcpServer( will: Will, opts: WillMcpOptions = {} ): Mcp
|
|
|
62
62
|
`NOT when it has responded. Its response — if it chooses to give one — arrives as its next ` +
|
|
63
63
|
`utterance (use the next_utterance tool). Staying silent is a valid choice, not an error.`,
|
|
64
64
|
inputSchema: {
|
|
65
|
-
text: z.string().describe(
|
|
65
|
+
text: z.string().describe('What is said or observed.'),
|
|
66
66
|
from: z.string().optional().describe( "Who it's from (entity id, default 'user'). Use a stable id per person." ),
|
|
67
|
-
speaker: z.string().optional().describe(
|
|
67
|
+
speaker: z.string().optional().describe('Display name of the speaker.'),
|
|
68
68
|
},
|
|
69
69
|
}, async ( { text, from, speaker } ) => {
|
|
70
70
|
await will.perceive( { text, ...( from ? { from } : {} ), ...( speaker ? { speaker } : {} ) } )
|
|
@@ -77,15 +77,15 @@ export function buildWillMcpServer( will: Will, opts: WillMcpOptions = {} ): Mcp
|
|
|
77
77
|
}
|
|
78
78
|
} )
|
|
79
79
|
|
|
80
|
-
server.registerTool(
|
|
80
|
+
server.registerTool('next_utterance', {
|
|
81
81
|
title: 'Next utterance',
|
|
82
82
|
description:
|
|
83
83
|
`Await ${ will.name }'s next spontaneous utterance. Returns what it says next, or reports ` +
|
|
84
84
|
`that it chose silence within the wait window — silence is a real outcome, not a failure. ` +
|
|
85
85
|
`Call after perceive to hear the response, or on its own to listen for unprompted speech.`,
|
|
86
86
|
inputSchema: {
|
|
87
|
-
within_ms: z.number().optional().describe(
|
|
88
|
-
from: z.string().optional().describe(
|
|
87
|
+
within_ms: z.number().optional().describe('How long to wait before accepting silence (default 15000, max 120000).'),
|
|
88
|
+
from: z.string().optional().describe('Only accept an utterance addressed to this entity id.'),
|
|
89
89
|
},
|
|
90
90
|
}, async ( { within_ms, from } ) => {
|
|
91
91
|
const within = Math.min( Math.max( within_ms ?? 15_000, 100 ), 120_000 )
|
|
@@ -101,7 +101,7 @@ export function buildWillMcpServer( will: Will, opts: WillMcpOptions = {} ): Mcp
|
|
|
101
101
|
}
|
|
102
102
|
} )
|
|
103
103
|
|
|
104
|
-
server.registerTool(
|
|
104
|
+
server.registerTool('state', {
|
|
105
105
|
title: 'Inner state',
|
|
106
106
|
description:
|
|
107
107
|
`Read a snapshot of ${ will.name }'s current inner life: tick, body/affect metrics ` +
|
|
@@ -112,7 +112,7 @@ export function buildWillMcpServer( will: Will, opts: WillMcpOptions = {} ): Mcp
|
|
|
112
112
|
{ content: [ { type: 'text', text: JSON.stringify( will.state(), null, 2 ) } ] }
|
|
113
113
|
) )
|
|
114
114
|
|
|
115
|
-
server.registerTool(
|
|
115
|
+
server.registerTool('save', {
|
|
116
116
|
title: 'Save (checkpoint)',
|
|
117
117
|
description:
|
|
118
118
|
`Checkpoint ${ will.name } into a portable PMA artifact on disk — non-destructive, it keeps ` +
|
|
@@ -130,12 +130,12 @@ export function buildWillMcpServer( will: Will, opts: WillMcpOptions = {} ): Mcp
|
|
|
130
130
|
|
|
131
131
|
// ── Resources (read-only projections) ─────────────────────
|
|
132
132
|
|
|
133
|
-
server.registerResource(
|
|
133
|
+
server.registerResource('state', 'will://state',
|
|
134
134
|
{ title: `${ will.name } — inner state`, description: 'Live snapshot of the mind: metrics, goals, beliefs, narrative.', mimeType: 'application/json' },
|
|
135
135
|
async uri => ( { contents: [ { uri: uri.href, mimeType: 'application/json', text: JSON.stringify( will.state(), null, 2 ) } ] } ),
|
|
136
136
|
)
|
|
137
137
|
|
|
138
|
-
server.registerResource(
|
|
138
|
+
server.registerResource('narrative', 'will://narrative',
|
|
139
139
|
{ title: `${ will.name } — self-narrative`, description: 'The story the mind currently tells about itself.', mimeType: 'text/plain' },
|
|
140
140
|
async uri => ( { contents: [ { uri: uri.href, mimeType: 'text/plain', text: will.state().narrative || '(no narrative formed yet)' } ] } ),
|
|
141
141
|
)
|