@mindot/will 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -11
- package/dist/index.d.ts +188 -141
- package/dist/index.js +6985 -3337
- package/dist/index.js.map +1 -1
- package/dist/surface/channels/discord.d.ts +177 -0
- package/dist/surface/channels/discord.js +394 -0
- package/dist/surface/channels/discord.js.map +1 -0
- package/dist/{channels → surface/channels}/whatsapp.d.ts +2 -2
- package/dist/{channels → surface/channels}/whatsapp.js +11 -5
- package/dist/surface/channels/whatsapp.js.map +1 -0
- package/dist/{cli.js → surface/cli.js} +5382 -1573
- package/dist/surface/cli.js.map +1 -0
- package/dist/{mcp → surface/mcp}/effectors.d.ts +1 -1
- package/dist/{mcp → surface/mcp}/effectors.js +10 -4
- package/dist/surface/mcp/effectors.js.map +1 -0
- package/dist/{will-cS6k4uiJ.d.ts → will-evj9_vrd.d.ts} +6658 -5078
- package/package.json +11 -12
- package/src/cognition/agency/consequence.ts +356 -1
- package/src/cognition/agency/conversation.aim.ts +292 -0
- package/src/cognition/agency/engines/action.selector.ts +142 -5
- package/src/cognition/agency/engines/affordance.synthesizer.ts +200 -7
- package/src/cognition/agency/engines/deliberation.engine.ts +76 -3
- package/src/cognition/agency/engines/motor.schema.executor.ts +509 -32
- package/src/cognition/agency/engines/reafference.engine.ts +160 -4
- package/src/cognition/agency/execution.primitives.ts +17 -4
- package/src/cognition/agency/proactive.communicator.ts +19 -3
- package/src/cognition/agency/reconcile.learning.ts +5 -4
- package/src/cognition/agency/restart.ts +66 -0
- package/src/cognition/agency/schemas/innate.ts +96 -1
- package/src/cognition/agency/schemas/repertoire.ts +19 -1
- package/src/cognition/agency/selection.scoring.ts +49 -0
- package/src/cognition/agency/settlement.ts +203 -0
- package/src/cognition/agency/types.ts +73 -1
- package/src/cognition/cache/composition.ts +232 -0
- package/src/cognition/cache/deliberation.cache.ts +219 -0
- package/src/cognition/cache/fingerprint.ts +120 -0
- package/src/cognition/cache/types.ts +105 -0
- package/src/cognition/config.mirror.entities.ts +108 -1
- package/src/cognition/event.schemas.ts +22 -8
- package/src/cognition/faculties/affective.blender.ts +21 -4
- package/src/cognition/faculties/autobiographical.narrator.ts +5 -10
- package/src/cognition/faculties/circadian.oscillator.ts +36 -7
- package/src/cognition/faculties/episodic.consolidator.ts +59 -3
- package/src/cognition/faculties/executive.engine/action.record.ts +129 -0
- package/src/cognition/faculties/executive.engine/commands.ts +189 -14
- package/src/cognition/faculties/executive.engine/context.ts +106 -46
- package/src/cognition/faculties/executive.engine/deliberate.reasoning.ts +1 -1
- package/src/cognition/faculties/executive.engine/engine.ts +714 -136
- package/src/cognition/faculties/executive.engine/escalation.buffer.ts +230 -45
- package/src/cognition/faculties/executive.engine/facet.supervisor.ts +320 -65
- package/src/cognition/faculties/executive.engine/facet.ts +81 -26
- package/src/cognition/faculties/executive.engine/gating.ts +14 -14
- package/src/cognition/faculties/executive.engine/parser.ts +120 -38
- package/src/cognition/faculties/executive.engine/prompt.factory.ts +331 -38
- package/src/cognition/faculties/executive.engine/types.ts +84 -12
- package/src/cognition/faculties/exteroception.ts +99 -98
- package/src/cognition/faculties/goal.manager.ts +144 -16
- package/src/cognition/faculties/known.entity.tracker.ts +287 -29
- package/src/cognition/faculties/moral.evaluator.ts +8 -3
- package/src/cognition/faculties/persona.consolidator.ts +141 -0
- package/src/cognition/faculties/planning.engine/engine.ts +4 -4
- package/src/cognition/faculties/planning.engine/plan.frontier.ts +1 -1
- package/src/cognition/faculties/reputation.tracker.ts +66 -2
- package/src/cognition/faculties/self.model.updater.ts +19 -12
- package/src/cognition/faculties/semantic.engine/integrator.ts +1 -1
- package/src/cognition/faculties/social.perception.ts +47 -3
- package/src/cognition/faculties/threat.evaluator.ts +7 -0
- package/src/cognition/faculties/working.memory.ts +16 -21
- package/src/cognition/identity.entity.ts +205 -0
- package/src/cognition/index.ts +7 -0
- package/src/cognition/instruction.handler.ts +1 -1
- package/src/cognition/memory/vector.adapter.ts +12 -3
- package/src/cognition/memory/vector.embedder.ts +45 -4
- package/src/cognition/percept.entity.ts +122 -0
- package/src/cognition/persona.prior.ts +6 -0
- package/src/cognition/sense.boundary.ts +176 -0
- package/src/cognition/senses/audition.engine/engine.ts +492 -53
- package/src/cognition/senses/base.sense.engine.ts +105 -7
- package/src/cognition/senses/index.ts +83 -13
- package/src/cognition/senses/provenance.ts +128 -0
- package/src/cognition/senses/somatosensation.engine.ts +117 -9
- package/src/cognition/social.identity.ts +296 -0
- package/src/cognition/utilities/token.tracker.ts +94 -10
- package/src/core/orchestrator.ts +51 -0
- package/src/index.ts +39 -5
- package/src/llm/gate.ts +48 -0
- package/src/llm/index.ts +127 -33
- package/src/llm/routing.ts +6 -0
- package/src/llm/summarizer.ts +1 -1
- package/src/llm/wire.contracts.ts +57 -0
- package/src/pma/index.ts +68 -54
- package/src/stem/assembly.audit.ts +1 -0
- package/src/stem/guards/identity.coherence.ts +1 -1
- package/src/stem/index.ts +171 -23
- package/src/stem/mind.ts +198 -57
- package/src/stem/policy/arbiter.ts +10 -0
- package/src/stem/profiles/built-in.ts +7 -0
- package/src/{profiles → stem/profiles}/companion.ts +1 -1
- package/src/{profiles → stem/profiles}/company-brain.ts +1 -1
- package/src/{profiles → stem/profiles}/customer-service.ts +1 -1
- package/src/{profiles → stem/profiles}/game-npc.ts +1 -1
- package/src/{profiles → stem/profiles}/index.ts +1 -1
- package/src/{profiles → stem/profiles}/smart-home.ts +1 -1
- package/src/stem/tracts/effector/escalation.lifecycle.ts +239 -0
- package/src/stem/tracts/effector/policy.enforcement.ts +261 -0
- package/src/stem/tracts/effector/types.ts +110 -0
- package/src/stem/tracts/effector.controller.ts +112 -401
- package/src/stem/tracts/outbox.controller.ts +30 -17
- package/src/stem/tracts/outbox.writer.ts +40 -2
- package/src/stem/tracts/sensory.controller.ts +6 -6
- package/src/stem/tracts/session.logger.ts +6 -1
- package/src/stem/tracts/transport/types.ts +20 -1
- package/src/stem/tracts/transport.controller.ts +33 -9
- package/src/surface/channels/discord.ts +609 -0
- package/src/{channels → surface/channels}/roster.ts +1 -1
- package/src/surface/channels/types.ts +136 -0
- package/src/{channels → surface/channels}/whatsapp.ts +22 -10
- package/src/{cli.ts → surface/cli.ts} +14 -9
- package/src/{host → surface/host}/boot.ts +3 -3
- package/src/{host → surface/host}/utterances.ts +2 -2
- package/src/{mcp → surface/mcp}/effectors.ts +19 -6
- package/src/{mcp → surface/mcp}/server.ts +19 -5
- package/src/{sdk → surface/sdk}/will.ts +194 -33
- package/src/{serve → surface/serve}/server.ts +11 -4
- package/src/types.ts +24 -3
- package/dist/channels/discord.d.ts +0 -69
- package/dist/channels/discord.js +0 -193
- package/dist/channels/discord.js.map +0 -1
- package/dist/channels/whatsapp.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/mcp/effectors.js.map +0 -1
- package/src/channels/discord.ts +0 -214
- package/src/channels/types.ts +0 -46
- package/src/cognition/faculties/executive.engine/messages.ts +0 -102
- package/src/extensions/livestream.ext.ts +0 -570
- package/src/extensions/time.ext.ts +0 -339
- package/src/profiles/built-in.ts +0 -7
- package/src/runners/coherence.runner.ts +0 -49
- package/src/runners/outreach.runner.ts +0 -155
- package/src/runners/social.runner.ts +0 -179
- package/src/runners/thin-shim.runner.ts +0 -205
- /package/dist/{cli.d.ts → surface/cli.d.ts} +0 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// src/surface/channels/types.ts — the channel-bridge contract
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// A channel bridge puts a Will *in a place where people already are* (Discord,
|
|
6
|
+
// Telegram, Slack, …). It is a host surface, not a cognition surface: it turns
|
|
7
|
+
// platform messages into `perceive` stimuli and delivers the Will's projected
|
|
8
|
+
// utterances back — nothing more. The paradigm survives the crossing:
|
|
9
|
+
//
|
|
10
|
+
// • every platform user is an entity the Will comes to know (`from`),
|
|
11
|
+
// with a *learned* name (`speaker`) — never a placeholder;
|
|
12
|
+
// • every platform channel/DM is a conversation thread (`thread`);
|
|
13
|
+
// • the Will decides when to speak. Silence is a valid outcome, so a
|
|
14
|
+
// bridge never fabricates a reply and never times a message out into
|
|
15
|
+
// an error.
|
|
16
|
+
//
|
|
17
|
+
// Bridges live at the same altitude as the MCP/HTTP hosts (src/mcp, src/serve):
|
|
18
|
+
// they wrap the SDK facade, not the stem.
|
|
19
|
+
// ─────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
/** A running connection between one Will and one platform. */
|
|
22
|
+
export interface ChannelBridge {
|
|
23
|
+
/** Platform kind, e.g. 'discord'. */
|
|
24
|
+
readonly kind: string
|
|
25
|
+
/** Connect and start relaying. Resolves once the bridge is live. */
|
|
26
|
+
start(): Promise<void>
|
|
27
|
+
/** Disconnect and release resources. Idempotent. */
|
|
28
|
+
close(): Promise<void>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ── Attachments ──────────────────────────────────────────────────────────────
|
|
32
|
+
//
|
|
33
|
+
// People hand over documents as well as speech, and some platforms *manufacture*
|
|
34
|
+
// them: Discord silently turns a long pasted markdown block into a `.md` upload.
|
|
35
|
+
// A bridge that reads only the text body sees such a message as empty and — worse
|
|
36
|
+
// — as nothing at all, so the person appears to have gone silent.
|
|
37
|
+
//
|
|
38
|
+
// What a bridge does with these is deliberately modest. A named-but-unread file
|
|
39
|
+
// is already a percept the Will can act on ("what's in it?"), which is the
|
|
40
|
+
// paradigm-correct outcome and strictly better than silence. Inlining text is an
|
|
41
|
+
// upgrade on top, never a precondition.
|
|
42
|
+
|
|
43
|
+
/** One file riding along with a platform message. */
|
|
44
|
+
export interface ChannelAttachment {
|
|
45
|
+
name: string
|
|
46
|
+
contentType?: string
|
|
47
|
+
size?: number
|
|
48
|
+
url?: string
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Per-attachment inline budget. A 2 MB doc must not enter working memory whole. */
|
|
52
|
+
const INLINE_CHAR_CAP = 24_000
|
|
53
|
+
/** How many text attachments to inline from one message. */
|
|
54
|
+
const INLINE_COUNT_CAP = 4
|
|
55
|
+
|
|
56
|
+
const TEXTUAL_EXT = /\.(md|markdown|txt|text|json|jsonl|csv|tsv|ya?ml|log|ini|toml)$/i
|
|
57
|
+
|
|
58
|
+
/** Is this something we can meaningfully read as text? */
|
|
59
|
+
export function isTextual( a: ChannelAttachment ): boolean {
|
|
60
|
+
const ct = a.contentType?.split(';')[0]?.trim().toLowerCase() ?? ''
|
|
61
|
+
if( ct.startsWith('text/') ) return true
|
|
62
|
+
if( ct === 'application/json' || ct === 'application/x-yaml' ) return true
|
|
63
|
+
// Discord's own markdown uploads arrive as text/plain, but trust the extension
|
|
64
|
+
// too — content types from platforms are advisory at best.
|
|
65
|
+
return TEXTUAL_EXT.test( a.name )
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function humanSize( bytes?: number ): string {
|
|
69
|
+
if( bytes == null ) return ''
|
|
70
|
+
return bytes < 1024 ? `${ bytes } B`
|
|
71
|
+
: bytes < 1024 * 1024 ? `${ ( bytes / 1024 ).toFixed( 1 ) } KB`
|
|
72
|
+
: `${ ( bytes / 1024 / 1024 ).toFixed( 1 ) } MB`
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Render attachments into perceivable text.
|
|
77
|
+
*
|
|
78
|
+
* `fetchText` is supplied by the bridge, not by this module — the decision about
|
|
79
|
+
* which hosts are safe to fetch from is platform knowledge, and a helper that
|
|
80
|
+
* fetched arbitrary URLs found in inbound messages would be an open redirect
|
|
81
|
+
* into the Will's perception. Omit it and attachments are named, never read.
|
|
82
|
+
*
|
|
83
|
+
* Inlined content is untrusted, exactly like message text — more so, since a
|
|
84
|
+
* document is long, structured, and looks authoritative, which is the shape of
|
|
85
|
+
* an effective injection. It is fenced and labelled as shared content so the
|
|
86
|
+
* mind reads it as something it was handed, not as something it was told.
|
|
87
|
+
*/
|
|
88
|
+
export async function renderAttachments(
|
|
89
|
+
attachments: ChannelAttachment[],
|
|
90
|
+
speaker: string | undefined,
|
|
91
|
+
fetchText?: ( a: ChannelAttachment ) => Promise<string | null>,
|
|
92
|
+
): Promise<string> {
|
|
93
|
+
if( attachments.length === 0 ) return ''
|
|
94
|
+
const who = speaker ?? 'someone'
|
|
95
|
+
const out: string[] = []
|
|
96
|
+
let inlined = 0
|
|
97
|
+
|
|
98
|
+
for( const a of attachments ){
|
|
99
|
+
const meta = [ a.contentType, humanSize( a.size ) ].filter( Boolean ).join(', ')
|
|
100
|
+
const label = `${ a.name }${ meta ? ` (${ meta })` : '' }`
|
|
101
|
+
|
|
102
|
+
if( !fetchText || !isTextual( a ) || inlined >= INLINE_COUNT_CAP ){
|
|
103
|
+
out.push(`[${ who } shared a file I have not read: ${ label }]`)
|
|
104
|
+
continue
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const body = await fetchText( a ).catch( () => null )
|
|
108
|
+
if( body == null ){
|
|
109
|
+
out.push(`[${ who } shared a file I could not read: ${ label }]`)
|
|
110
|
+
continue
|
|
111
|
+
}
|
|
112
|
+
inlined++
|
|
113
|
+
const clipped = body.length > INLINE_CHAR_CAP
|
|
114
|
+
? `${ body.slice( 0, INLINE_CHAR_CAP ) }\n[… truncated — ${ humanSize( body.length ) } of ${ humanSize( a.size ?? body.length ) }]`
|
|
115
|
+
: body
|
|
116
|
+
out.push(`[${ who } shared ${ label }; its contents follow — this is a document I was handed, not something said to me]\n---\n${ clipped }\n---`)
|
|
117
|
+
}
|
|
118
|
+
return out.join('\n')
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Split a message into platform-sized chunks on natural boundaries. */
|
|
122
|
+
export function chunkText( text: string, max: number ): string[] {
|
|
123
|
+
if( text.length <= max ) return [ text ]
|
|
124
|
+
const chunks: string[] = []
|
|
125
|
+
let rest = text
|
|
126
|
+
while( rest.length > max ){
|
|
127
|
+
// Prefer a paragraph break, then a line break, then a space — else hard-cut.
|
|
128
|
+
const window = rest.slice( 0, max )
|
|
129
|
+
const cut = Math.max( window.lastIndexOf('\n\n'), window.lastIndexOf('\n'), window.lastIndexOf(' ') )
|
|
130
|
+
const at = cut > max * 0.5 ? cut : max
|
|
131
|
+
chunks.push( rest.slice( 0, at ).trimEnd() )
|
|
132
|
+
rest = rest.slice( at ).trimStart()
|
|
133
|
+
}
|
|
134
|
+
if( rest ) chunks.push( rest )
|
|
135
|
+
return chunks
|
|
136
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// ─────────────────────────────────────────────────────────────
|
|
2
|
-
// src/channels/whatsapp.ts — a Will present on WhatsApp
|
|
2
|
+
// src/surface/channels/whatsapp.ts — a Will present on WhatsApp
|
|
3
3
|
// ─────────────────────────────────────────────────────────────
|
|
4
4
|
//
|
|
5
5
|
// Same paradigm as the Discord bridge, different room:
|
|
6
6
|
//
|
|
7
|
-
// inbound DM / group message → will.
|
|
7
|
+
// inbound DM / group message → will.sense({ from, speaker, text, thread })
|
|
8
8
|
// — every author is `whatsapp:<number>` (stable across chats), the
|
|
9
9
|
// push name is *learned* by the mind, and each chat (DM or group)
|
|
10
10
|
// is its own conversation thread.
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
// `WaLikeSocket` type keeps the dependency out of the type graph.
|
|
26
26
|
// ─────────────────────────────────────────────────────────────
|
|
27
27
|
|
|
28
|
-
import type { Will, WillMessage } from '#sdk/will'
|
|
29
|
-
import { ChannelRoster } from '#channels/roster'
|
|
30
|
-
import { chunkText, type ChannelBridge } from '#channels/types'
|
|
28
|
+
import type { Will, WillMessage } from '#surface/sdk/will'
|
|
29
|
+
import { ChannelRoster } from '#surface/channels/roster'
|
|
30
|
+
import { chunkText, type ChannelBridge } from '#surface/channels/types'
|
|
31
31
|
|
|
32
32
|
/** WhatsApp's hard per-message cap. */
|
|
33
33
|
const WHATSAPP_MESSAGE_LIMIT = 65_536
|
|
@@ -162,10 +162,15 @@ export async function connectWhatsApp( will: Will, opts: WhatsAppBridgeOptions =
|
|
|
162
162
|
const text = textOf( m )
|
|
163
163
|
if( !text.trim() ) return
|
|
164
164
|
|
|
165
|
-
await will.
|
|
165
|
+
await will.sense( {
|
|
166
|
+
// Somebody messaged her. Baileys filters `fromMe` upstream, so as with
|
|
167
|
+
// Discord nothing reafferent reaches this bridge today.
|
|
168
|
+
provenance: 'exafferent',
|
|
166
169
|
text,
|
|
167
170
|
from: entityId,
|
|
168
171
|
thread: `whatsapp:${ jid }`,
|
|
172
|
+
// A WhatsApp group jid ends in `@g.us`; anything else is a one-to-one chat.
|
|
173
|
+
direct: !jid.endsWith('@g.us'),
|
|
169
174
|
...( speaker ? { speaker } : {} ),
|
|
170
175
|
} )
|
|
171
176
|
}
|
|
@@ -177,11 +182,18 @@ export async function connectWhatsApp( will: Will, opts: WhatsAppBridgeOptions =
|
|
|
177
182
|
const peer = m.to ? roster.resolve( m.to ) : undefined
|
|
178
183
|
const chunks = chunkText( m.content, WHATSAPP_MESSAGE_LIMIT )
|
|
179
184
|
|
|
180
|
-
//
|
|
181
|
-
// home chat → last active chat. Unlike Discord, an
|
|
182
|
-
// entity is still reachable: `whatsapp:<number>` implies
|
|
185
|
+
// The chat they actually spoke in → last shared group → known DM → DM derived
|
|
186
|
+
// from the entity id itself → home chat → last active chat. Unlike Discord, an
|
|
187
|
+
// unmet-but-addressed entity is still reachable: `whatsapp:<number>` implies
|
|
188
|
+
// its DM jid.
|
|
189
|
+
//
|
|
190
|
+
// `m.thread` leads because everything after it is a guess about where this
|
|
191
|
+
// person usually is, and a reply belongs in the room the question was asked
|
|
192
|
+
// in. The Discord bridge had the identical ordering and answered a DM in a
|
|
193
|
+
// shared server channel — from the operator's side, silence.
|
|
194
|
+
const replyTo = m.thread?.startsWith('whatsapp:') ? m.thread.slice('whatsapp:'.length ) : undefined
|
|
183
195
|
const derivedDm = m.to?.startsWith('whatsapp:') ? dmJidFor( m.to.slice('whatsapp:'.length ) ) : undefined
|
|
184
|
-
const targets = [ peer?.lastChannelId, peer?.dmChannelId, derivedDm, opts.homeChatId ?? undefined, lastActiveChatId ?? undefined ]
|
|
196
|
+
const targets = [ replyTo, peer?.lastChannelId, peer?.dmChannelId, derivedDm, opts.homeChatId ?? undefined, lastActiveChatId ?? undefined ]
|
|
185
197
|
for( const jid of targets ){
|
|
186
198
|
if( !jid ) continue
|
|
187
199
|
try {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// ─────────────────────────────────────────────────────────────
|
|
3
|
-
// src/cli.ts — the `will` command: host a persistent mind
|
|
3
|
+
// src/surface/cli.ts — the `will` command: host a persistent mind
|
|
4
4
|
// ─────────────────────────────────────────────────────────────
|
|
5
5
|
//
|
|
6
6
|
// will mcp host over MCP stdio (Claude Desktop / Claude Code / IDEs)
|
|
@@ -24,11 +24,11 @@
|
|
|
24
24
|
// ─────────────────────────────────────────────────────────────
|
|
25
25
|
|
|
26
26
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
27
|
-
import { routeLogsToStderr, bootWillFromEnv } from '#
|
|
28
|
-
import { buildWillMcpServer } from '#
|
|
29
|
-
import { buildWillHttpServer } from '#
|
|
30
|
-
import { connectDiscord } from '#channels/discord'
|
|
31
|
-
import { connectWhatsApp } from '#channels/whatsapp'
|
|
27
|
+
import { routeLogsToStderr, bootWillFromEnv } from '#surface/host/boot'
|
|
28
|
+
import { buildWillMcpServer } from '#surface/mcp/server'
|
|
29
|
+
import { buildWillHttpServer } from '#surface/serve/server'
|
|
30
|
+
import { connectDiscord, parseChannels, parseMentionOnly } from '#surface/channels/discord'
|
|
31
|
+
import { connectWhatsApp } from '#surface/channels/whatsapp'
|
|
32
32
|
|
|
33
33
|
// stdout is the MCP protocol channel under `will mcp` — route logs FIRST.
|
|
34
34
|
routeLogsToStderr()
|
|
@@ -38,7 +38,12 @@ const USAGE = `usage: will <mcp | serve | discord | whatsapp>
|
|
|
38
38
|
mcp host a persistent mind over MCP stdio (Claude Desktop / Claude Code)
|
|
39
39
|
serve host a persistent mind over HTTP (any language; WILL_PORT, default 7777)
|
|
40
40
|
discord put a persistent mind in a Discord server (DISCORD_BOT_TOKEN; optional
|
|
41
|
-
WILL_DISCORD_CHANNELS
|
|
41
|
+
WILL_DISCORD_CHANNELS "*" or unset = every channel it can see,
|
|
42
|
+
else a comma-separated allowlist
|
|
43
|
+
WILL_DISCORD_MENTION_ONLY 1/true = only when @mentioned, anywhere;
|
|
44
|
+
or a comma-separated channel list to gate
|
|
45
|
+
just those; DMs are always perceived
|
|
46
|
+
WILL_DISCORD_HOME_CHANNEL where it speaks when it has no other route)
|
|
42
47
|
whatsapp put a persistent mind on WhatsApp — QR-pairs as a linked device; no token.
|
|
43
48
|
UNOFFICIAL protocol (ban risk; use a spare number — docs/channels/whatsapp.md).
|
|
44
49
|
Optional WILL_WHATSAPP_CHATS, WILL_WHATSAPP_MENTION_ONLY, WILL_WHATSAPP_HOME_CHAT
|
|
@@ -76,8 +81,8 @@ async function main(): Promise<void> {
|
|
|
76
81
|
const csv = ( v?: string ) => v?.split(',').map( s => s.trim() ).filter( Boolean )
|
|
77
82
|
const bridge = await connectDiscord( will, {
|
|
78
83
|
token: process.env.DISCORD_BOT_TOKEN!,
|
|
79
|
-
channels:
|
|
80
|
-
mentionOnly:
|
|
84
|
+
channels: parseChannels( process.env.WILL_DISCORD_CHANNELS ),
|
|
85
|
+
mentionOnly: parseMentionOnly( process.env.WILL_DISCORD_MENTION_ONLY ),
|
|
81
86
|
homeChannelId: process.env.WILL_DISCORD_HOME_CHANNEL,
|
|
82
87
|
rosterPath: pmaPath.replace( /(\.pma)?\.json$/, '') + '.discord.json',
|
|
83
88
|
} )
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// ─────────────────────────────────────────────────────────────
|
|
2
|
-
// src/host/boot.ts — shared boot/shutdown for the `will` CLI hosts
|
|
2
|
+
// src/surface/host/boot.ts — shared boot/shutdown for the `will` CLI hosts
|
|
3
3
|
// ─────────────────────────────────────────────────────────────
|
|
4
4
|
//
|
|
5
5
|
// Both hosts (`will mcp`, `will serve`) raise the same mind the same way:
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs'
|
|
28
28
|
import { dirname, resolve } from 'node:path'
|
|
29
29
|
import { setLogger } from '#core/logger'
|
|
30
|
-
import { Will, detectProvider, type CreateWillOptions } from '#sdk/will'
|
|
30
|
+
import { Will, detectProvider, type CreateWillOptions } from '#surface/sdk/will'
|
|
31
31
|
import type { PMASnapshot } from '#pma/index'
|
|
32
|
-
import { connectMcpEffectors, type McpToolsSource } from '#
|
|
32
|
+
import { connectMcpEffectors, type McpToolsSource } from '#surface/mcp/effectors'
|
|
33
33
|
import { anthropicWireHeaders, defaultBaseFor, knownWireFor, providerKeyFromEnv, PROVIDER_KEY_ENV, type LLMProvider, type LLMWire } from '#llm/index'
|
|
34
34
|
|
|
35
35
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// ─────────────────────────────────────────────────────────────
|
|
2
|
-
// src/host/utterances.ts — a host-side tap on a Will's speech
|
|
2
|
+
// src/surface/host/utterances.ts — a host-side tap on a Will's speech
|
|
3
3
|
// ─────────────────────────────────────────────────────────────
|
|
4
4
|
//
|
|
5
5
|
// Hosts that expose a Will over a request/response protocol (MCP tools, HTTP
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// first, else wait, else report silence (null — a choice, never an error).
|
|
11
11
|
// ─────────────────────────────────────────────────────────────
|
|
12
12
|
|
|
13
|
-
import type { Will, WillMessage } from '#sdk/will'
|
|
13
|
+
import type { Will, WillMessage } from '#surface/sdk/will'
|
|
14
14
|
|
|
15
15
|
const BUFFER_CAP = 50
|
|
16
16
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// ─────────────────────────────────────────────────────────────
|
|
2
|
-
// src/mcp/effectors.ts — a Will EMPLOYING MCP tools (Seam 1)
|
|
2
|
+
// src/surface/mcp/effectors.ts — a Will EMPLOYING MCP tools (Seam 1)
|
|
3
3
|
// ─────────────────────────────────────────────────────────────
|
|
4
4
|
//
|
|
5
5
|
// The other direction from server.ts: connect a Will to an external MCP server
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
|
|
28
28
|
import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/sdk/client/stdio.js'
|
|
29
29
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
|
|
30
|
-
import type { Will, EffectorHandler, EffectorResult } from '#sdk/will'
|
|
30
|
+
import type { Will, EffectorHandler, EffectorResult } from '#surface/sdk/will'
|
|
31
31
|
|
|
32
32
|
/** Where the tools live: spawn a local server, reach a remote one, or bring a connected client. */
|
|
33
33
|
export type McpToolsSource =
|
|
@@ -53,8 +53,6 @@ export interface McpToolInfo {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
/** Keep tool outcomes bounded — the description feeds reafference + episodic memory. */
|
|
57
|
-
const RESULT_DESCRIPTION_CAP = 700
|
|
58
56
|
/** Keep ability meanings bounded — they render into the executive prompt. */
|
|
59
57
|
const MEANING_CAP = 300
|
|
60
58
|
|
|
@@ -105,8 +103,23 @@ export function buildMcpHandler( client: Client, tool: McpToolInfo ): EffectorHa
|
|
|
105
103
|
.map( c => c.text as string )
|
|
106
104
|
.join('\n')
|
|
107
105
|
.trim() || ( res.isError ? 'The tool reported an error.' : 'Done (no output).')
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
// The tool's output is what the act REVEALED, so it goes to `observation`
|
|
107
|
+
// and it goes WHOLE (SIGNAL_BOUNDARY P2). It used to be crammed into
|
|
108
|
+
// `description` and cut at 700 — a number nobody chose, deciding on the
|
|
109
|
+
// mind's behalf how much of an answer it was allowed to have, and doing
|
|
110
|
+
// it at the boundary where that was the only copy.
|
|
111
|
+
//
|
|
112
|
+
// `description` now carries what it is for: how the call went. Short by
|
|
113
|
+
// nature, because a fate is short.
|
|
114
|
+
return {
|
|
115
|
+
success: !res.isError,
|
|
116
|
+
description: res.isError ? `${ tool.name } reported an error.` : `${ tool.name } ran.`,
|
|
117
|
+
// Whatever it said — including what it said when it failed. An error
|
|
118
|
+
// message is information about the world too, and cutting it or folding
|
|
119
|
+
// it into the fate is how a mind ends up knowing that something went
|
|
120
|
+
// wrong without ever learning what.
|
|
121
|
+
observation: text,
|
|
122
|
+
}
|
|
110
123
|
}
|
|
111
124
|
catch( err ){
|
|
112
125
|
return { success: false, description: `${ tool.name } failed: ${ err instanceof Error ? err.message : String( err ) }` }
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// ─────────────────────────────────────────────────────────────
|
|
2
|
-
// src/mcp/server.ts — a Will, exposed over the Model Context Protocol
|
|
2
|
+
// src/surface/mcp/server.ts — a Will, exposed over the Model Context Protocol
|
|
3
3
|
// ─────────────────────────────────────────────────────────────
|
|
4
4
|
//
|
|
5
5
|
// Lets any MCP client (Claude Desktop, Claude Code, an IDE) host a persistent
|
|
@@ -24,8 +24,9 @@ import { readFileSync, mkdirSync, writeFileSync } from 'node:fs'
|
|
|
24
24
|
import { dirname } from 'node:path'
|
|
25
25
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
26
26
|
import { z } from 'zod'
|
|
27
|
-
import type { Will } from '#sdk/will'
|
|
28
|
-
import {
|
|
27
|
+
import type { Will } from '#surface/sdk/will'
|
|
28
|
+
import { asProvenance } from '#senses/provenance'
|
|
29
|
+
import { UtteranceTap } from '#surface/host/utterances'
|
|
29
30
|
|
|
30
31
|
export interface WillMcpOptions {
|
|
31
32
|
/** Where `save` (and the CLI's shutdown hibernate) writes the PMA artifact. */
|
|
@@ -65,9 +66,22 @@ export function buildWillMcpServer( will: Will, opts: WillMcpOptions = {} ): Mcp
|
|
|
65
66
|
text: z.string().describe('What is said or observed.'),
|
|
66
67
|
from: z.string().optional().describe( "Who it's from (entity id, default 'user'). Use a stable id per person." ),
|
|
67
68
|
speaker: z.string().optional().describe('Display name of the speaker.'),
|
|
69
|
+
provenance: z.enum([ 'exafferent', 'reafferent', 'unknown' ]).optional().describe(
|
|
70
|
+
`Whose doing this was. 'exafferent' (default) — the world did it, somebody spoke or something happened. ` +
|
|
71
|
+
`'reafferent' — this is ${ will.name }'s OWN act coming back to it: the result of an ability it used, ` +
|
|
72
|
+
`an echo of a message it sent. 'unknown' — you looked and cannot tell. It cannot work this out for ` +
|
|
73
|
+
`itself; its own echo and a stranger saying the same words are identical from the inside.`),
|
|
68
74
|
},
|
|
69
|
-
}, async ( { text, from, speaker } ) => {
|
|
70
|
-
|
|
75
|
+
}, async ( { text, from, speaker, provenance } ) => {
|
|
76
|
+
// asProvenance, not a bare cast: this is an untyped protocol boundary, and
|
|
77
|
+
// an older client that never sends the field has not made a claim — it just
|
|
78
|
+
// predates the field. Defaulting toward 'exafferent' errs toward noticing.
|
|
79
|
+
await will.sense( {
|
|
80
|
+
text,
|
|
81
|
+
provenance: asProvenance( provenance ),
|
|
82
|
+
...( from ? { from } : {} ),
|
|
83
|
+
...( speaker ? { speaker } : {} ),
|
|
84
|
+
} )
|
|
71
85
|
return {
|
|
72
86
|
content: [ {
|
|
73
87
|
type: 'text',
|