@mindot/will 0.4.0 → 0.6.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 +32 -12
- package/dist/channels/discord.d.ts +2 -11
- package/dist/channels/discord.js.map +1 -1
- package/dist/channels/whatsapp.d.ts +72 -0
- package/dist/channels/whatsapp.js +252 -0
- package/dist/channels/whatsapp.js.map +1 -0
- package/dist/cli.js +837 -91
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +620 -79
- 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/types-E9-HV-SW.d.ts +11 -0
- package/dist/{will-D-slky1N.d.ts → will-Bikuk4s2.d.ts} +56 -12
- package/package.json +12 -3
- 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 +318 -0
- package/src/cli.ts +46 -23
- package/src/cognition/acp.ts +28 -0
- package/src/cognition/agency/consequence.ts +174 -0
- package/src/cognition/agency/engines/action.selector.ts +301 -48
- package/src/cognition/agency/engines/affordance.synthesizer.ts +26 -16
- package/src/cognition/agency/engines/deliberation.engine.ts +28 -14
- package/src/cognition/agency/engines/motor.schema.executor.ts +86 -14
- package/src/cognition/agency/engines/reafference.engine.ts +122 -18
- 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 +1 -1
- package/src/cognition/agency/revocation.ts +79 -0
- package/src/cognition/agency/schemas/repertoire.ts +2 -2
- package/src/cognition/agency/selection.scoring.ts +25 -9
- 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 +27 -26
- 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 +11 -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 +59 -37
- package/src/host/utterances.ts +1 -1
- package/src/llm/index.ts +84 -34
- 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 +20 -11
- package/src/serve/server.ts +12 -12
- package/src/stem/assembly.audit.ts +3 -3
- package/src/stem/guards/identity.coherence.ts +13 -12
- package/src/stem/guards/identity.guard.ts +22 -22
- package/src/stem/index.ts +11 -11
- package/src/stem/mind.ts +12 -12
- package/src/stem/tracts/biography.writer.ts +3 -3
- package/src/stem/tracts/effector.controller.ts +4 -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
|
}
|
|
@@ -53,6 +53,12 @@ const MODEL_PRICING: Record<string, { input: number; output: number }> = {
|
|
|
53
53
|
'anthropic/claude-haiku-4': { input: 1.00, output: 5.00 },
|
|
54
54
|
'anthropic/claude-opus-4': { input: 5.00, output: 25.00 },
|
|
55
55
|
|
|
56
|
+
// Z.ai (GLM-5 family). `glm-5.2[1m]` is the same model asking for its 1M
|
|
57
|
+
// context window — same rate, so it gets its own row rather than relying on
|
|
58
|
+
// the normalizer (a future long-context tier would price differently).
|
|
59
|
+
'glm/glm-5.2': { input: 1.40, output: 4.40 },
|
|
60
|
+
'glm/glm-5.2[1m]': { input: 1.40, output: 4.40 },
|
|
61
|
+
|
|
56
62
|
// Google
|
|
57
63
|
'google/gemini-2.0-flash': { input: 0.10, output: 0.40 },
|
|
58
64
|
'google/gemini-2.0-pro': { input: 1.25, output: 5.00 },
|
|
@@ -90,9 +96,9 @@ const CACHE_WRITE_MULT = 1.25
|
|
|
90
96
|
*/
|
|
91
97
|
function normalizeModelKey( model: string ): string {
|
|
92
98
|
let m = model.toLowerCase().trim()
|
|
93
|
-
const slash = m.lastIndexOf(
|
|
99
|
+
const slash = m.lastIndexOf('/')
|
|
94
100
|
if( slash >= 0 ) m = m.slice( slash + 1 ) // drop "provider/" prefix
|
|
95
|
-
return m.replace( /[-@]\d{6,8}$/, ''
|
|
101
|
+
return m.replace( /[-@]\d{6,8}$/, '') // drop trailing -YYYYMMDD date stamp
|
|
96
102
|
}
|
|
97
103
|
|
|
98
104
|
// Pre-index the pricing table by normalized model name for O(1), date-insensitive
|
|
@@ -100,7 +106,7 @@ function normalizeModelKey( model: string ): string {
|
|
|
100
106
|
const PRICING_BY_NORM: Record<string, { input: number; output: number }> = ( () => {
|
|
101
107
|
const out: Record<string, { input: number; output: number }> = {}
|
|
102
108
|
for( const [ key, price ] of Object.entries( MODEL_PRICING ) ){
|
|
103
|
-
if( key === '__default__'
|
|
109
|
+
if( key === '__default__') continue
|
|
104
110
|
out[ normalizeModelKey( key ) ] = price
|
|
105
111
|
}
|
|
106
112
|
return out
|
|
@@ -299,10 +305,10 @@ export class TokenTracker implements SimulationEngine {
|
|
|
299
305
|
if( this._ledgerPath ){
|
|
300
306
|
try {
|
|
301
307
|
if( !this._ledgerDirReady ){
|
|
302
|
-
mkdirSync( this._ledgerPath.slice( 0, this._ledgerPath.lastIndexOf(
|
|
308
|
+
mkdirSync( this._ledgerPath.slice( 0, this._ledgerPath.lastIndexOf('/') ), { recursive: true } )
|
|
303
309
|
this._ledgerDirReady = true
|
|
304
310
|
}
|
|
305
|
-
appendFileSync( this._ledgerPath, JSON.stringify( record ) + '\n'
|
|
311
|
+
appendFileSync( this._ledgerPath, JSON.stringify( record ) + '\n')
|
|
306
312
|
}
|
|
307
313
|
catch { /* ledger file is best-effort */ }
|
|
308
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
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
// WILL_NAME display name (default "Will")
|
|
13
13
|
// WILL_IDENTITY persona prompt (default a minimal self)
|
|
14
14
|
// WILL_TIER basic | standard | full (default standard)
|
|
15
|
-
// WILL_LLM mock | anthropic
|
|
16
|
-
// ANTHROPIC_API_KEY is set,
|
|
15
|
+
// WILL_LLM mock | anthropic | glm (default: auto — anthropic when
|
|
16
|
+
// ANTHROPIC_API_KEY is set, glm when
|
|
17
|
+
// ZAI_API_KEY is, else mock)
|
|
17
18
|
// WILL_TICK_MS ms per tick (default 1000)
|
|
18
19
|
// WILL_SEED deterministic seed (testing) (default unseeded/wall-time)
|
|
19
20
|
// WILL_PMA_PATH PMA artifact path (default ./.will/<name>.pma.json)
|
|
@@ -27,6 +28,7 @@ import { setLogger } from '#core/logger'
|
|
|
27
28
|
import { Will, type CreateWillOptions } from '#sdk/will'
|
|
28
29
|
import type { PMASnapshot } from '#pma/index'
|
|
29
30
|
import { connectMcpEffectors, type McpToolsSource } from '#root/mcp/effectors'
|
|
31
|
+
import { anthropicWireHeaders, defaultBaseFor, defaultModelFor } from '#llm/index'
|
|
30
32
|
|
|
31
33
|
/**
|
|
32
34
|
* Route every engine log line to stderr. For `will mcp`, stdout is the MCP
|
|
@@ -35,12 +37,29 @@ import { connectMcpEffectors, type McpToolsSource } from '#root/mcp/effectors'
|
|
|
35
37
|
*/
|
|
36
38
|
export function routeLogsToStderr(): void {
|
|
37
39
|
const err = ( level: string ) => ( msg: string, ...rest: unknown[] ) =>
|
|
38
|
-
console.error(
|
|
39
|
-
setLogger( { debug: () => {}, info: err(
|
|
40
|
+
console.error(`[will:${ level }] ${ msg }`, ...rest )
|
|
41
|
+
setLogger( { debug: () => {}, info: err('info'), warn: err('warn'), error: err('error') } )
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
function slug( s: string ): string {
|
|
43
|
-
return s.toLowerCase().replace( /[^a-z0-9]+/g, '-'
|
|
45
|
+
return s.toLowerCase().replace( /[^a-z0-9]+/g, '-').replace( /^-+|-+$/g, '') || 'will'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The LLM mode the hosts will boot with: an explicit WILL_LLM, else whichever
|
|
49
|
+
* provider's key is present, else the zero-key mock. */
|
|
50
|
+
export function resolveLlmMode(): 'mock' | 'anthropic' | 'glm' {
|
|
51
|
+
const explicit = process.env.WILL_LLM as 'mock' | 'anthropic' | 'glm' | undefined
|
|
52
|
+
if( explicit ) return explicit
|
|
53
|
+
if( process.env.ANTHROPIC_API_KEY ) return 'anthropic'
|
|
54
|
+
if( process.env.ZAI_API_KEY ) return 'glm'
|
|
55
|
+
return 'mock'
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The key for a live mode — the provider-agnostic override first, then the
|
|
59
|
+
* provider's own env. */
|
|
60
|
+
function resolveLlmKey( mode: 'anthropic' | 'glm'): string | undefined {
|
|
61
|
+
return process.env.WILL_LLM_API_KEY
|
|
62
|
+
?? ( mode === 'glm' ? process.env.ZAI_API_KEY : process.env.ANTHROPIC_API_KEY )
|
|
44
63
|
}
|
|
45
64
|
|
|
46
65
|
/**
|
|
@@ -59,44 +78,47 @@ function slug( s: string ): string {
|
|
|
59
78
|
* Skipped for the mock executive and the no-LLM `reflex` anatomy.
|
|
60
79
|
*/
|
|
61
80
|
async function preflightLLM( anatomy: string ): Promise<void> {
|
|
62
|
-
const mode =
|
|
63
|
-
if( mode === 'mock' || anatomy === 'reflex'
|
|
81
|
+
const mode = resolveLlmMode()
|
|
82
|
+
if( mode === 'mock' || anatomy === 'reflex') return
|
|
64
83
|
|
|
65
|
-
const key =
|
|
84
|
+
const key = resolveLlmKey( mode )
|
|
66
85
|
if( !key ){
|
|
67
|
-
|
|
68
|
-
console.error(
|
|
86
|
+
const expected = mode === 'glm' ? 'ZAI_API_KEY' : 'ANTHROPIC_API_KEY'
|
|
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.')
|
|
69
89
|
process.exit( 2 )
|
|
70
90
|
}
|
|
71
91
|
|
|
72
92
|
// The ping validates key + balance + reachability, which is the failure class
|
|
73
93
|
// that strands an operator. It uses the pinned model when there is one, so a
|
|
74
|
-
// bad model id is caught too; otherwise the cheapest model stands in.
|
|
75
|
-
|
|
76
|
-
const
|
|
94
|
+
// bad model id is caught too; otherwise the cheapest model stands in. GLM
|
|
95
|
+
// speaks the same wire at Z.ai's compat endpoint, so one ping serves both.
|
|
96
|
+
const base = process.env.WILL_LLM_BASE_URL ?? defaultBaseFor( mode )
|
|
97
|
+
const model = process.env.WILL_LLM_MODEL
|
|
98
|
+
?? ( mode === 'glm' ? defaultModelFor('glm') : 'claude-haiku-4-5-20251001')
|
|
77
99
|
try {
|
|
78
|
-
const res = await fetch(
|
|
100
|
+
const res = await fetch(`${ base }/messages`, {
|
|
79
101
|
method: 'POST',
|
|
80
|
-
headers:
|
|
102
|
+
headers: anthropicWireHeaders( mode, key ),
|
|
81
103
|
body: JSON.stringify( { model, max_tokens: 1, messages: [ { role: 'user', content: 'ping' } ] } ),
|
|
82
104
|
signal: AbortSignal.timeout( 20_000 ),
|
|
83
105
|
} )
|
|
84
106
|
if( res.ok ) return
|
|
85
107
|
|
|
86
|
-
const detail = ( await res.text().catch( () => ''
|
|
108
|
+
const detail = ( await res.text().catch( () => '') ).slice( 0, 300 )
|
|
87
109
|
const fatal = res.status === 400 || res.status === 401 || res.status === 403
|
|
88
110
|
if( !fatal ){
|
|
89
|
-
console.error(
|
|
111
|
+
console.error(`[will] the executive's LLM answered ${ res.status } on a test call — raising the mind anyway (it retries): ${ detail }`)
|
|
90
112
|
return
|
|
91
113
|
}
|
|
92
|
-
console.error(
|
|
93
|
-
console.error(
|
|
94
|
-
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.')
|
|
95
117
|
process.exit( 1 )
|
|
96
118
|
}
|
|
97
119
|
catch( e ){
|
|
98
|
-
console.error(
|
|
99
|
-
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.')
|
|
100
122
|
process.exit( 1 )
|
|
101
123
|
}
|
|
102
124
|
}
|
|
@@ -116,8 +138,8 @@ export interface BootedWill {
|
|
|
116
138
|
/** Raise the mind from env config — wake from the artifact if one exists. */
|
|
117
139
|
export async function bootWillFromEnv(): Promise<BootedWill> {
|
|
118
140
|
const name = process.env.WILL_NAME ?? 'Will'
|
|
119
|
-
const pmaPath = resolve( process.env.WILL_PMA_PATH ?? `.will/${ slug( name ) }.pma.json`
|
|
120
|
-
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')
|
|
121
143
|
const anatomy = ( process.env.WILL_ANATOMY as CreateWillOptions['anatomy'] ) ?? 'mind'
|
|
122
144
|
|
|
123
145
|
await preflightLLM( anatomy )
|
|
@@ -125,29 +147,29 @@ export async function bootWillFromEnv(): Promise<BootedWill> {
|
|
|
125
147
|
const opts: Omit<CreateWillOptions, 'identity'> = {
|
|
126
148
|
name, anatomy, tickMs,
|
|
127
149
|
...( process.env.WILL_LLM_MODEL ? { model: process.env.WILL_LLM_MODEL } : {} ),
|
|
128
|
-
...( process.env.WILL_LLM
|
|
150
|
+
...( process.env.WILL_LLM ? { llm: process.env.WILL_LLM as CreateWillOptions['llm'] } : {} ),
|
|
129
151
|
...( process.env.WILL_SEED ? { seed: parseInt( process.env.WILL_SEED ) } : {} ),
|
|
130
152
|
}
|
|
131
153
|
|
|
132
154
|
let will: Will
|
|
133
155
|
if( existsSync( pmaPath ) ){
|
|
134
|
-
const pma = JSON.parse( readFileSync( pmaPath, 'utf8'
|
|
156
|
+
const pma = JSON.parse( readFileSync( pmaPath, 'utf8') ) as PMASnapshot
|
|
135
157
|
will = await Will.wake( pma, opts )
|
|
136
|
-
console.error(
|
|
158
|
+
console.error(`[will] ${ name } woke from ${ pmaPath }`)
|
|
137
159
|
// A woken Will carries its own identity — that is the point of an artifact.
|
|
138
160
|
// But an operator editing WILL_IDENTITY and seeing nothing change deserves
|
|
139
161
|
// to know why, rather than concluding the persona layer is broken.
|
|
140
162
|
if( process.env.WILL_IDENTITY )
|
|
141
|
-
console.error(
|
|
163
|
+
console.error(`[will] note: WILL_IDENTITY is ignored — ${ name } woke as itself. Delete ${ pmaPath } to be born fresh from it.`)
|
|
142
164
|
}
|
|
143
165
|
else {
|
|
144
166
|
will = await Will.create( {
|
|
145
167
|
...opts,
|
|
146
168
|
identity: { prompt: process.env.WILL_IDENTITY ?? `I am ${ name }, a persistent mind.` },
|
|
147
169
|
} )
|
|
148
|
-
console.error(
|
|
170
|
+
console.error(`[will] ${ name } born (no artifact at ${ pmaPath } yet)`)
|
|
149
171
|
}
|
|
150
|
-
will.on(
|
|
172
|
+
will.on('error', e => console.error(`[will] error: ${ e.message }`) )
|
|
151
173
|
|
|
152
174
|
// Onward bridges: MCP servers whose tools become the Will's OWN abilities.
|
|
153
175
|
// Best-effort — a bad entry warns and is skipped; the mind still boots.
|
|
@@ -159,12 +181,12 @@ export async function bootWillFromEnv(): Promise<BootedWill> {
|
|
|
159
181
|
try {
|
|
160
182
|
const { names, close } = await connectMcpEffectors( will, source )
|
|
161
183
|
cleanups.push( close )
|
|
162
|
-
console.error(
|
|
184
|
+
console.error(`[will] ${ name } gained abilities: ${ names.join(', ') }`)
|
|
163
185
|
}
|
|
164
|
-
catch( e ){ console.error(
|
|
186
|
+
catch( e ){ console.error(`[will] MCP bridge failed (skipped): ${ ( e as Error ).message }`) }
|
|
165
187
|
}
|
|
166
188
|
}
|
|
167
|
-
catch( e ){ console.error(
|
|
189
|
+
catch( e ){ console.error(`[will] WILL_MCP_SERVERS is not valid JSON — ignoring: ${ ( e as Error ).message }`) }
|
|
168
190
|
}
|
|
169
191
|
|
|
170
192
|
// Hibernate exactly once on the way out — cleanups (LIFO), distill + stop, persist.
|
|
@@ -177,13 +199,13 @@ export async function bootWillFromEnv(): Promise<BootedWill> {
|
|
|
177
199
|
const pma = await will.hibernate()
|
|
178
200
|
mkdirSync( dirname( pmaPath ), { recursive: true } )
|
|
179
201
|
writeFileSync( pmaPath, JSON.stringify( pma ) )
|
|
180
|
-
console.error(
|
|
202
|
+
console.error(`[will] ${ name } hibernated to ${ pmaPath } (${ why })`)
|
|
181
203
|
}
|
|
182
|
-
catch( e ){ console.error(
|
|
204
|
+
catch( e ){ console.error(`[will] hibernate failed: ${ ( e as Error ).message }`) }
|
|
183
205
|
process.exit( 0 )
|
|
184
206
|
}
|
|
185
|
-
process.on(
|
|
186
|
-
process.on(
|
|
207
|
+
process.on('SIGINT', () => void shutdown('SIGINT') )
|
|
208
|
+
process.on('SIGTERM', () => void shutdown('SIGTERM') )
|
|
187
209
|
|
|
188
210
|
return {
|
|
189
211
|
will, name, pmaPath, tickMs, anatomy,
|
package/src/host/utterances.ts
CHANGED