@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.
Files changed (136) hide show
  1. package/dist/channels/discord.d.ts +1 -1
  2. package/dist/channels/discord.js.map +1 -1
  3. package/dist/channels/whatsapp.d.ts +1 -1
  4. package/dist/channels/whatsapp.js.map +1 -1
  5. package/dist/cli.js +998 -59
  6. package/dist/cli.js.map +1 -1
  7. package/dist/index.d.ts +2 -2
  8. package/dist/index.js +998 -59
  9. package/dist/index.js.map +1 -1
  10. package/dist/mcp/effectors.d.ts +1 -1
  11. package/dist/mcp/effectors.js.map +1 -1
  12. package/dist/{will-BDq-TMQr.d.ts → will-DAW0l-lY.d.ts} +90 -10
  13. package/package.json +1 -1
  14. package/src/channels/discord.ts +11 -11
  15. package/src/channels/roster.ts +1 -1
  16. package/src/channels/types.ts +1 -1
  17. package/src/channels/whatsapp.ts +25 -25
  18. package/src/cli.ts +20 -20
  19. package/src/cognition/acp.ts +28 -0
  20. package/src/cognition/agency/consequence.ts +174 -0
  21. package/src/cognition/agency/engines/action.selector.ts +333 -48
  22. package/src/cognition/agency/engines/affordance.synthesizer.ts +35 -16
  23. package/src/cognition/agency/engines/deliberation.engine.ts +28 -14
  24. package/src/cognition/agency/engines/motor.schema.executor.ts +90 -14
  25. package/src/cognition/agency/engines/reafference.engine.ts +152 -23
  26. package/src/cognition/agency/execution.primitives.ts +13 -13
  27. package/src/cognition/agency/proactive.communicator.ts +1 -1
  28. package/src/cognition/agency/reconcile.learning.ts +10 -1
  29. package/src/cognition/agency/revocation.ts +79 -0
  30. package/src/cognition/agency/schemas/repertoire.ts +109 -9
  31. package/src/cognition/agency/selection.scoring.ts +32 -10
  32. package/src/cognition/agency/types.ts +9 -0
  33. package/src/cognition/bus.ts +2 -2
  34. package/src/cognition/completion.inbox.ts +1 -1
  35. package/src/cognition/config.mirror.entities.ts +5 -5
  36. package/src/cognition/event.log.ts +2 -2
  37. package/src/cognition/event.schemas.ts +67 -67
  38. package/src/cognition/faculties/aesthetic.evaluator.ts +6 -6
  39. package/src/cognition/faculties/affective.blender.ts +36 -4
  40. package/src/cognition/faculties/attachment.evaluator.ts +6 -6
  41. package/src/cognition/faculties/attention.allocator.ts +63 -8
  42. package/src/cognition/faculties/autobiographical.narrator.ts +3 -3
  43. package/src/cognition/faculties/bias.detector.ts +7 -7
  44. package/src/cognition/faculties/circadian.oscillator.ts +3 -3
  45. package/src/cognition/faculties/confidence.calibrator.ts +7 -7
  46. package/src/cognition/faculties/dream.simulator.ts +5 -5
  47. package/src/cognition/faculties/empathy.simulator.ts +7 -7
  48. package/src/cognition/faculties/energy.regulator.ts +4 -4
  49. package/src/cognition/faculties/episodic.consolidator.ts +6 -6
  50. package/src/cognition/faculties/executive.engine/commands.ts +6 -6
  51. package/src/cognition/faculties/executive.engine/context.ts +42 -42
  52. package/src/cognition/faculties/executive.engine/deferred.effects.ts +2 -2
  53. package/src/cognition/faculties/executive.engine/engine.ts +25 -25
  54. package/src/cognition/faculties/executive.engine/facet.supervisor.ts +12 -12
  55. package/src/cognition/faculties/executive.engine/facet.ts +18 -18
  56. package/src/cognition/faculties/executive.engine/gating.ts +2 -2
  57. package/src/cognition/faculties/executive.engine/messages.ts +3 -3
  58. package/src/cognition/faculties/executive.engine/parser.ts +11 -11
  59. package/src/cognition/faculties/executive.engine/prompt.factory.ts +55 -55
  60. package/src/cognition/faculties/exteroception.ts +99 -5
  61. package/src/cognition/faculties/forgetting.curve.ts +3 -3
  62. package/src/cognition/faculties/frustration.evaluator.ts +6 -6
  63. package/src/cognition/faculties/goal.manager.ts +15 -15
  64. package/src/cognition/faculties/inhibition.controller.ts +3 -3
  65. package/src/cognition/faculties/interoception.ts +3 -3
  66. package/src/cognition/faculties/introspection.engine.ts +3 -3
  67. package/src/cognition/faculties/known.entity.tracker.ts +15 -15
  68. package/src/cognition/faculties/loss.evaluator.ts +2 -2
  69. package/src/cognition/faculties/moral.evaluator.ts +4 -4
  70. package/src/cognition/faculties/novelty.detector.ts +4 -4
  71. package/src/cognition/faculties/persona.consolidator.ts +12 -12
  72. package/src/cognition/faculties/planning.engine/engine.ts +47 -47
  73. package/src/cognition/faculties/planning.engine/plan.frontier.ts +5 -5
  74. package/src/cognition/faculties/planning.engine/plan.store.ts +1 -1
  75. package/src/cognition/faculties/planning.engine/plan.supervision.ts +20 -20
  76. package/src/cognition/faculties/reputation.tracker.ts +6 -6
  77. package/src/cognition/faculties/reward.evaluator.ts +4 -4
  78. package/src/cognition/faculties/self.model.updater.ts +8 -8
  79. package/src/cognition/faculties/semantic.engine/integrator.ts +15 -15
  80. package/src/cognition/faculties/sleep.pressure.regulator.ts +4 -4
  81. package/src/cognition/faculties/social.perception.ts +3 -3
  82. package/src/cognition/faculties/spaced.repetition.ts +2 -2
  83. package/src/cognition/faculties/stress.regulator.ts +27 -4
  84. package/src/cognition/faculties/task.switcher.ts +11 -6
  85. package/src/cognition/faculties/theory.of.mind.ts +6 -6
  86. package/src/cognition/faculties/threat.evaluator.ts +3 -3
  87. package/src/cognition/faculties/working.memory.ts +8 -8
  88. package/src/cognition/generative.model.ts +2 -2
  89. package/src/cognition/memory/vector.adapter.ts +5 -5
  90. package/src/cognition/memory/vector.content.ts +4 -4
  91. package/src/cognition/memory/vector.embedder.ts +9 -9
  92. package/src/cognition/persona.prior.ts +1 -1
  93. package/src/cognition/schema.registry.ts +2 -2
  94. package/src/cognition/senses/audition.engine/engine.ts +2 -2
  95. package/src/cognition/senses/base.sense.engine.ts +1 -1
  96. package/src/cognition/utilities/token.tracker.ts +5 -5
  97. package/src/core/abstracts.ts +1 -1
  98. package/src/core/completion.recorder.ts +2 -2
  99. package/src/core/serialization.ts +3 -3
  100. package/src/core/snapshot.manager.ts +1 -1
  101. package/src/core/state.manager.ts +1 -1
  102. package/src/core/utils.ts +1 -1
  103. package/src/host/boot.ts +30 -30
  104. package/src/host/utterances.ts +1 -1
  105. package/src/llm/index.ts +9 -9
  106. package/src/llm/summarizer.ts +4 -4
  107. package/src/llm/wire.contracts.ts +1 -1
  108. package/src/mcp/effectors.ts +10 -10
  109. package/src/mcp/server.ts +11 -11
  110. package/src/pma/eval.ts +1 -1
  111. package/src/pma/index.ts +21 -21
  112. package/src/runners/outreach.runner.ts +3 -3
  113. package/src/runners/social.runner.ts +1 -1
  114. package/src/runners/thin-shim.runner.ts +1 -1
  115. package/src/sdk/will.ts +5 -5
  116. package/src/serve/server.ts +12 -12
  117. package/src/stem/assembly.audit.ts +3 -3
  118. package/src/stem/guards/identity.coherence.ts +10 -10
  119. package/src/stem/guards/identity.guard.ts +22 -22
  120. package/src/stem/index.ts +26 -11
  121. package/src/stem/mind.ts +12 -12
  122. package/src/stem/policy/arbiter.ts +136 -0
  123. package/src/stem/policy/rule.table.ts +172 -0
  124. package/src/stem/policy/verdict.recorder.ts +0 -0
  125. package/src/stem/tracts/biography.writer.ts +3 -3
  126. package/src/stem/tracts/effector.controller.ts +383 -4
  127. package/src/stem/tracts/health.reporter.ts +1 -1
  128. package/src/stem/tracts/outbox.controller.ts +4 -0
  129. package/src/stem/tracts/outbox.writer.ts +4 -4
  130. package/src/stem/tracts/pma.controller.ts +1 -1
  131. package/src/stem/tracts/replay.controller.ts +10 -10
  132. package/src/stem/tracts/sensory.controller.ts +2 -2
  133. package/src/stem/tracts/session.logger.ts +4 -4
  134. package/src/stem/tracts/transport/socketio.transport.ts +7 -7
  135. package/src/stem/tracts/transport/stream.transport.ts +2 -2
  136. package/src/stem/tracts/transport.controller.ts +15 -15
@@ -1,5 +1,5 @@
1
1
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
- import { E as EffectorHandler, W as Will } from '../will-BDq-TMQr.js';
2
+ import { E as EffectorHandler, W as Will } from '../will-DAW0l-lY.js';
3
3
 
4
4
  /** Where the tools live: spawn a local server, reach a remote one, or bring a connected client. */
5
5
  type McpToolsSource = {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/mcp/effectors.ts"],"names":[],"mappings":"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","file":"effectors.js","sourcesContent":["// ─────────────────────────────────────────────────────────────\n// src/mcp/effectors.ts — a Will EMPLOYING MCP tools (Seam 1)\n// ─────────────────────────────────────────────────────────────\n//\n// The other direction from server.ts: connect a Will to an external MCP server\n// and register that server's tools as the Will's own ABILITIES. Each tool\n// becomes a learnable affordance — its description (plus a compact hint of the\n// arguments it takes) is the ability's meaning, surfaced to the executive and\n// the deliberator; the WILL decides when to enact one (nothing here dispatches);\n// the tool's result feeds back through reafference, so the Will gets *skilled*\n// at the tools it uses.\n//\n// Arguments come from conscious intent: the executive supplies them via an\n// action's `args`, which ride the ideomotor leg into the invocation (see\n// executive commands.ts). A tool with required arguments enacted habitually\n// (without args) fails informatively — reafference then teaches the Will that\n// this ability wants deliberate articulation.\n//\n// const { names, close } = await connectMcpEffectors( will, {\n// command: 'npx', args: [ '-y', '@modelcontextprotocol/server-filesystem', '/tmp' ],\n// } )\n//\n// Import from '@mindot/will/mcp' — kept off the main entry so non-MCP\n// consumers never load the MCP SDK.\n// ─────────────────────────────────────────────────────────────\n\nimport { Client } from '@modelcontextprotocol/sdk/client/index.js'\nimport { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/sdk/client/stdio.js'\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'\nimport type { Will, EffectorHandler, EffectorResult } from '#sdk/will'\n\n/** Where the tools live: spawn a local server, reach a remote one, or bring a connected client. */\nexport type McpToolsSource =\n | { command: string; args?: string[]; env?: Record<string, string> }\n | { url: string }\n | { client: Client }\n\nexport interface McpEffectorsOptions {\n /** Intrinsic effort prior 0..1 seeded on every bridged ability (default 0.2). */\n cost?: number\n /** Prefix for the ability names (e.g. 'fs_') — avoids collisions across servers. */\n prefix?: string\n}\n\n/** Minimal structural view of an MCP tool (the SDK's zod-inferred type, loosened). */\nexport interface McpToolInfo {\n name: string\n description?: string\n inputSchema?: {\n type?: string\n properties?: Record<string, { type?: string; description?: string }>\n required?: string[]\n }\n}\n\n/** Keep tool outcomes bounded — the description feeds reafference + episodic memory. */\nconst RESULT_DESCRIPTION_CAP = 700\n/** Keep ability meanings bounded — they render into the executive prompt. */\nconst MEANING_CAP = 300\n\n/**\n * The ability's *meaning*: the tool's description plus a compact hint of the\n * arguments it takes — so the executive knows what to supply in an action's\n * `args` when it enacts this ability.\n */\nexport function describeMcpTool( tool: McpToolInfo ): string {\n const props = tool.inputSchema?.properties ?? {}\n const required = new Set( tool.inputSchema?.required ?? [] )\n const argHints = Object.entries( props ).map( ( [ key, p ] ) =>\n `${ key }${ required.has( key ) ? '' : '?' }${ p.description ? `: ${ p.description }` : '' }` )\n\n const base = ( tool.description ?? `The ${ tool.name } tool.` ).trim().replace( /\\s+/g, ' ' )\n const hint = argHints.length > 0 ? ` (args — ${ argHints.join( '; ' ) })` : ''\n const full = `${ base }${ hint }`\n return full.length > MEANING_CAP ? `${ full.slice( 0, MEANING_CAP - 1 ) }…` : full\n}\n\n/**\n * The effector handler for one bridged tool: checks required args (an ability\n * enacted without its needed articulation fails informatively — reafference\n * learns from it), calls the tool, and maps the result onto EffectorResult.\n */\nexport function buildMcpHandler( client: Client, tool: McpToolInfo ): EffectorHandler {\n return async ( args ): Promise<EffectorResult> => {\n const props = tool.inputSchema?.properties\n // Only pass keys the tool declares — invocation params can carry situation\n // extras (targetEntityName, learned priors) the tool never asked for.\n const filtered: Record<string, unknown> = {}\n for( const [ k, v ] of Object.entries( args ?? {} ) )\n if( !props || k in props ) filtered[ k ] = v\n\n const missing = ( tool.inputSchema?.required ?? [] ).filter(\n k => filtered[ k ] === undefined || filtered[ k ] === '' )\n if( missing.length > 0 )\n return {\n success: false,\n description: `${ tool.name } needs ${ missing.join( ', ' ) } — enact it deliberately, supplying them in the action's args.`,\n }\n\n try {\n const res = await client.callTool( { name: tool.name, arguments: filtered } ) as\n { content?: Array<{ type: string; text?: string }>; isError?: boolean }\n const text = ( res.content ?? [] )\n .filter( c => c.type === 'text' && typeof c.text === 'string' )\n .map( c => c.text as string )\n .join( '\\n' )\n .trim() || ( res.isError ? 'The tool reported an error.' : 'Done (no output).' )\n const bounded = text.length > RESULT_DESCRIPTION_CAP ? `${ text.slice( 0, RESULT_DESCRIPTION_CAP - 1 ) }…` : text\n return { success: !res.isError, description: bounded }\n }\n catch( err ){\n return { success: false, description: `${ tool.name } failed: ${ err instanceof Error ? err.message : String( err ) }` }\n }\n }\n}\n\nasync function connect( source: McpToolsSource ): Promise<{ client: Client; owned: boolean }> {\n if( 'client' in source ) return { client: source.client, owned: false }\n\n const client = new Client( { name: 'mindot-will', version: '0' } )\n if( 'url' in source )\n await client.connect( new StreamableHTTPClientTransport( new URL( source.url ) ) )\n else\n await client.connect( new StdioClientTransport( {\n command: source.command,\n ...( source.args ? { args: source.args } : {} ),\n // Merge over the SDK's safe default env so PATH etc. survive a custom env.\n env: { ...getDefaultEnvironment(), ...( source.env ?? {} ) },\n } ) )\n return { client, owned: true }\n}\n\n/**\n * Register an MCP server's tools as the Will's abilities. Returns the ability\n * names registered and a `close()` for the connection (call it when the Will\n * stops; a client passed in via `source.client` is left open).\n */\nexport async function connectMcpEffectors(\n will: Will,\n source: McpToolsSource,\n opts: McpEffectorsOptions = {},\n): Promise<{ names: string[]; close: () => Promise<void> }> {\n const { client, owned } = await connect( source )\n const { tools } = await client.listTools() as unknown as { tools: McpToolInfo[] }\n\n const names: string[] = []\n for( const tool of tools ){\n const name = `${ opts.prefix ?? '' }${ tool.name }`\n will.effector( name, {\n description: describeMcpTool( tool ),\n cost: opts.cost ?? 0.2,\n tags: [ 'mcp' ],\n handler: buildMcpHandler( client, tool ),\n } )\n names.push( name )\n }\n\n return { names, close: async () => { if( owned ) await client.close() } }\n}\n"]}
1
+ {"version":3,"sources":["../../src/mcp/effectors.ts"],"names":[],"mappings":"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","file":"effectors.js","sourcesContent":["// ─────────────────────────────────────────────────────────────\n// src/mcp/effectors.ts — a Will EMPLOYING MCP tools (Seam 1)\n// ─────────────────────────────────────────────────────────────\n//\n// The other direction from server.ts: connect a Will to an external MCP server\n// and register that server's tools as the Will's own ABILITIES. Each tool\n// becomes a learnable affordance — its description (plus a compact hint of the\n// arguments it takes) is the ability's meaning, surfaced to the executive and\n// the deliberator; the WILL decides when to enact one (nothing here dispatches);\n// the tool's result feeds back through reafference, so the Will gets *skilled*\n// at the tools it uses.\n//\n// Arguments come from conscious intent: the executive supplies them via an\n// action's `args`, which ride the ideomotor leg into the invocation (see\n// executive commands.ts). A tool with required arguments enacted habitually\n// (without args) fails informatively — reafference then teaches the Will that\n// this ability wants deliberate articulation.\n//\n// const { names, close } = await connectMcpEffectors( will, {\n// command: 'npx', args: [ '-y', '@modelcontextprotocol/server-filesystem', '/tmp' ],\n// } )\n//\n// Import from '@mindot/will/mcp' — kept off the main entry so non-MCP\n// consumers never load the MCP SDK.\n// ─────────────────────────────────────────────────────────────\n\nimport { Client } from '@modelcontextprotocol/sdk/client/index.js'\nimport { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/sdk/client/stdio.js'\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'\nimport type { Will, EffectorHandler, EffectorResult } from '#sdk/will'\n\n/** Where the tools live: spawn a local server, reach a remote one, or bring a connected client. */\nexport type McpToolsSource =\n | { command: string; args?: string[]; env?: Record<string, string> }\n | { url: string }\n | { client: Client }\n\nexport interface McpEffectorsOptions {\n /** Intrinsic effort prior 0..1 seeded on every bridged ability (default 0.2). */\n cost?: number\n /** Prefix for the ability names (e.g. 'fs_') — avoids collisions across servers. */\n prefix?: string\n}\n\n/** Minimal structural view of an MCP tool (the SDK's zod-inferred type, loosened). */\nexport interface McpToolInfo {\n name: string\n description?: string\n inputSchema?: {\n type?: string\n properties?: Record<string, { type?: string; description?: string }>\n required?: string[]\n }\n}\n\n/** Keep tool outcomes bounded — the description feeds reafference + episodic memory. */\nconst RESULT_DESCRIPTION_CAP = 700\n/** Keep ability meanings bounded — they render into the executive prompt. */\nconst MEANING_CAP = 300\n\n/**\n * The ability's *meaning*: the tool's description plus a compact hint of the\n * arguments it takes — so the executive knows what to supply in an action's\n * `args` when it enacts this ability.\n */\nexport function describeMcpTool( tool: McpToolInfo ): string {\n const props = tool.inputSchema?.properties ?? {}\n const required = new Set( tool.inputSchema?.required ?? [] )\n const argHints = Object.entries( props ).map( ( [ key, p ] ) =>\n `${ key }${ required.has( key ) ? '' : '?' }${ p.description ? `: ${ p.description }` : '' }`)\n\n const base = ( tool.description ?? `The ${ tool.name } tool.`).trim().replace( /\\s+/g, ' ')\n const hint = argHints.length > 0 ? ` (args — ${ argHints.join('; ') })` : ''\n const full = `${ base }${ hint }`\n return full.length > MEANING_CAP ? `${ full.slice( 0, MEANING_CAP - 1 ) }…` : full\n}\n\n/**\n * The effector handler for one bridged tool: checks required args (an ability\n * enacted without its needed articulation fails informatively — reafference\n * learns from it), calls the tool, and maps the result onto EffectorResult.\n */\nexport function buildMcpHandler( client: Client, tool: McpToolInfo ): EffectorHandler {\n return async ( args ): Promise<EffectorResult> => {\n const props = tool.inputSchema?.properties\n // Only pass keys the tool declares — invocation params can carry situation\n // extras (targetEntityName, learned priors) the tool never asked for.\n const filtered: Record<string, unknown> = {}\n for( const [ k, v ] of Object.entries( args ?? {} ) )\n if( !props || k in props ) filtered[ k ] = v\n\n const missing = ( tool.inputSchema?.required ?? [] ).filter(\n k => filtered[ k ] === undefined || filtered[ k ] === '')\n if( missing.length > 0 )\n return {\n success: false,\n description: `${ tool.name } needs ${ missing.join(', ') } — enact it deliberately, supplying them in the action's args.`,\n }\n\n try {\n const res = await client.callTool( { name: tool.name, arguments: filtered } ) as\n { content?: Array<{ type: string; text?: string }>; isError?: boolean }\n const text = ( res.content ?? [] )\n .filter( c => c.type === 'text' && typeof c.text === 'string')\n .map( c => c.text as string )\n .join('\\n')\n .trim() || ( res.isError ? 'The tool reported an error.' : 'Done (no output).')\n const bounded = text.length > RESULT_DESCRIPTION_CAP ? `${ text.slice( 0, RESULT_DESCRIPTION_CAP - 1 ) }…` : text\n return { success: !res.isError, description: bounded }\n }\n catch( err ){\n return { success: false, description: `${ tool.name } failed: ${ err instanceof Error ? err.message : String( err ) }` }\n }\n }\n}\n\nasync function connect( source: McpToolsSource ): Promise<{ client: Client; owned: boolean }> {\n if('client' in source ) return { client: source.client, owned: false }\n\n const client = new Client( { name: 'mindot-will', version: '0' } )\n if('url' in source )\n await client.connect( new StreamableHTTPClientTransport( new URL( source.url ) ) )\n else\n await client.connect( new StdioClientTransport( {\n command: source.command,\n ...( source.args ? { args: source.args } : {} ),\n // Merge over the SDK's safe default env so PATH etc. survive a custom env.\n env: { ...getDefaultEnvironment(), ...( source.env ?? {} ) },\n } ) )\n return { client, owned: true }\n}\n\n/**\n * Register an MCP server's tools as the Will's abilities. Returns the ability\n * names registered and a `close()` for the connection (call it when the Will\n * stops; a client passed in via `source.client` is left open).\n */\nexport async function connectMcpEffectors(\n will: Will,\n source: McpToolsSource,\n opts: McpEffectorsOptions = {},\n): Promise<{ names: string[]; close: () => Promise<void> }> {\n const { client, owned } = await connect( source )\n const { tools } = await client.listTools() as unknown as { tools: McpToolInfo[] }\n\n const names: string[] = []\n for( const tool of tools ){\n const name = `${ opts.prefix ?? '' }${ tool.name }`\n will.effector( name, {\n description: describeMcpTool( tool ),\n cost: opts.cost ?? 0.2,\n tags: [ 'mcp' ],\n handler: buildMcpHandler( client, tool ),\n } )\n names.push( name )\n }\n\n return { names, close: async () => { if( owned ) await client.close() } }\n}\n"]}
@@ -3347,6 +3347,8 @@ declare class AttentionAllocator implements SimulationEngine, CognitiveEngine {
3347
3347
  private _effort;
3348
3348
  /** A one-shot focus/rest request from the executive, applied next react(). */
3349
3349
  private _effortRequest;
3350
+ /** ACP-P2: a self-caused precision attenuation is armed; react() restores after one observe. */
3351
+ private _acpOneShot;
3350
3352
  private _bus;
3351
3353
  private readonly _model;
3352
3354
  constructor(config?: AttentionAllocatorConfig);
@@ -3435,6 +3437,8 @@ declare class StressRegulator implements SimulationEngine, CognitiveEngine {
3435
3437
  subscribes(): string[];
3436
3438
  publishes(): CognitiveEventSchema[];
3437
3439
  onCognitiveEvent(e: CognitiveEvent): StateCommands | void;
3440
+ /** ACP-P2: self-caused stress attenuation armed; react() restores after one observe. */
3441
+ private _acpOneShot;
3438
3442
  snapshot(): Record<string, unknown>;
3439
3443
  react(delta: Duration, _tick: Tick, state: ReadonlySimulationState, context: SimulationContext): Promise<EngineResult>;
3440
3444
  private _readConfigFromState;
@@ -3549,6 +3553,28 @@ declare class Exteroception implements SimulationEngine, CognitiveEngine {
3549
3553
  * Generate a meaningful summary for an entity.
3550
3554
  * Instead of "New percept: percept-54-0", produce something useful.
3551
3555
  */
3556
+ /**
3557
+ * The text the corollary-discharge matcher inspects for this entity — its
3558
+ * content (a message body) over its description over its summary. Where our
3559
+ * own delivered words would surface if the world echoes them back.
3560
+ */
3561
+ /**
3562
+ * The affect→percept valence seam (registry #5). A percept carries how the
3563
+ * mind *feels* about what it is a percept OF, resolved most-specific-first:
3564
+ *
3565
+ * 1. the KnownEntityTracker's dossier for this entity (`ke-<id>.valence`) —
3566
+ * a real per-entity felt valence, the honest signal;
3567
+ * 2. otherwise the ambient `affect.valence` — the felt tone at perception
3568
+ * time. Weaker evidence, so it is tagged `'ambient'` and consumers
3569
+ * weight it down (mood is context, not appraisal of this thing).
3570
+ *
3571
+ * Absent both, no valence is stamped and every consumer keeps its
3572
+ * pre-seam behaviour.
3573
+ */
3574
+ /** Spread-friendly form of `_valenceFor` for percept construction. */
3575
+ private _valenceOf;
3576
+ private _valenceFor;
3577
+ private _matchText;
3552
3578
  private _summarizeEntity;
3553
3579
  /**
3554
3580
  * Compute salience of a percept based on entity characteristics.
@@ -4263,6 +4289,8 @@ declare class AffectiveBlender implements SimulationEngine, CognitiveEngine {
4263
4289
  private _previousPAD;
4264
4290
  private _emotionHistory;
4265
4291
  private _moodBaseline;
4292
+ /** ACP-P2: self-caused arousal attenuation armed; react() restores after one observe. */
4293
+ private _acpOneShot;
4266
4294
  private _energyLevel;
4267
4295
  private _comfort;
4268
4296
  private _sleepPressure;
@@ -5451,25 +5479,25 @@ declare class AccessGrants {
5451
5479
  declare class VisionEngine extends ShellSenseEngine {
5452
5480
  readonly name = "vision-engine";
5453
5481
  readonly domain: "vision";
5454
- protected readonly acceptedKinds: Set<"text" | "system" | "voice" | "image" | "video" | "webhook" | "ambient" | "background" | "self-eval" | "assessment">;
5482
+ protected readonly acceptedKinds: Set<"text" | "system" | "ambient" | "voice" | "image" | "video" | "webhook" | "background" | "self-eval" | "assessment">;
5455
5483
  }
5456
5484
 
5457
5485
  declare class SomatosensationEngine extends ShellSenseEngine {
5458
5486
  readonly name = "somatosensation-engine";
5459
5487
  readonly domain: "somatosensation";
5460
- protected readonly acceptedKinds: Set<"text" | "system" | "voice" | "image" | "video" | "webhook" | "ambient" | "background" | "self-eval" | "assessment">;
5488
+ protected readonly acceptedKinds: Set<"text" | "system" | "ambient" | "voice" | "image" | "video" | "webhook" | "background" | "self-eval" | "assessment">;
5461
5489
  }
5462
5490
 
5463
5491
  declare class OlfactionEngine extends ShellSenseEngine {
5464
5492
  readonly name = "olfaction-engine";
5465
5493
  readonly domain: "olfaction";
5466
- protected readonly acceptedKinds: Set<"text" | "system" | "voice" | "image" | "video" | "webhook" | "ambient" | "background" | "self-eval" | "assessment">;
5494
+ protected readonly acceptedKinds: Set<"text" | "system" | "ambient" | "voice" | "image" | "video" | "webhook" | "background" | "self-eval" | "assessment">;
5467
5495
  }
5468
5496
 
5469
5497
  declare class GustationEngine extends ShellSenseEngine {
5470
5498
  readonly name = "gustation-engine";
5471
5499
  readonly domain: "gustation";
5472
- protected readonly acceptedKinds: Set<"text" | "system" | "voice" | "image" | "video" | "webhook" | "ambient" | "background" | "self-eval" | "assessment">;
5500
+ protected readonly acceptedKinds: Set<"text" | "system" | "ambient" | "voice" | "image" | "video" | "webhook" | "background" | "self-eval" | "assessment">;
5473
5501
  }
5474
5502
 
5475
5503
  /**
@@ -5639,7 +5667,7 @@ declare class AuditionEngine extends BaseSenseEngine {
5639
5667
  readonly name = "audition-engine";
5640
5668
  readonly domain: "audition";
5641
5669
  /** Audition consumes language input; the base filters every other kind out. */
5642
- protected readonly acceptedKinds: Set<"text" | "system" | "voice" | "image" | "video" | "webhook" | "ambient" | "background" | "self-eval" | "assessment">;
5670
+ protected readonly acceptedKinds: Set<"text" | "system" | "ambient" | "voice" | "image" | "video" | "webhook" | "background" | "self-eval" | "assessment">;
5643
5671
  /** Inbound is gated on the 'listen' effector — the base enforces it before _perceive(). */
5644
5672
  protected readonly gateEffector = "listen";
5645
5673
  private _executiveEngine;
@@ -5998,6 +6026,9 @@ declare class SchemaRepertoire {
5998
6026
  private _skills;
5999
6027
  /** Tracks which templates were learned at runtime (vs innate) so decay can forget them. */
6000
6028
  private _learned;
6029
+ /** Availability layer (P2): schema → { value 0..1, lastRefusedTick }. Empty until
6030
+ * a refusal lands — a never-refused Will writes nothing here (byte-identical). */
6031
+ private _availability;
6001
6032
  constructor(seed?: MotorSchema[]);
6002
6033
  schemas(): MotorSchema[];
6003
6034
  getSchema(id: string): MotorSchema | undefined;
@@ -6012,6 +6043,23 @@ declare class SchemaRepertoire {
6012
6043
  registerExternal(schema: MotorSchema): void;
6013
6044
  skills(): ReadonlyMap<string, LearnedSkill>;
6014
6045
  getSkill(id: string): LearnedSkill | undefined;
6046
+ availability(): ReadonlyMap<string, {
6047
+ value: number;
6048
+ lastRefusedTick: number;
6049
+ }>;
6050
+ /**
6051
+ * How available a schema is right now, 0..1. Absent from the ledger ⇒ 1
6052
+ * (fully available — the common case). This is the ONLY value the
6053
+ * AffordanceSynthesizer reads; it never touches competence.
6054
+ */
6055
+ availabilityOf(schema: string): number;
6056
+ /**
6057
+ * Fold a policy refusal into the availability layer (NOT competence). A
6058
+ * `class` refusal cuts availability hard; an `instance` refusal dents it
6059
+ * lightly. Multiplicative so repeated refusals compound toward — but never
6060
+ * reach — zero, keeping re-probe alive.
6061
+ */
6062
+ recordRefusal(schema: string, finality: 'class' | 'instance', tick: number): number;
6015
6063
  /**
6016
6064
  * Fold one outcome into the schema's learned skill. Returns the updated skill
6017
6065
  * and whether it just crossed the proceduralization threshold this update.
@@ -6021,11 +6069,16 @@ declare class SchemaRepertoire {
6021
6069
  proceduralized: boolean;
6022
6070
  };
6023
6071
  /**
6024
- * Forgetting curve over the competence layer. Skills unused for IDLE_TICKS
6025
- * lose habit; learned composites that fall below DROP_HABIT are dropped
6026
- * entirely (template + skill). Returns the schema ids that were forgotten.
6072
+ * Forgetting curve over the competence layer, plus availability recovery.
6073
+ * Skills unused for IDLE_TICKS lose habit; learned composites below DROP_HABIT
6074
+ * are dropped entirely (template + skill). Availability entries climb back
6075
+ * toward 1 and are dropped once fully recovered. Returns the ids that were
6076
+ * removed from each layer so their mirrored state entities can be deleted.
6027
6077
  */
6028
- decay(tick: number): string[];
6078
+ decay(tick: number): {
6079
+ skills: string[];
6080
+ availability: string[];
6081
+ };
6029
6082
  /** Learned composite templates + all skills above a confidence floor. */
6030
6083
  export(minHabit?: number): {
6031
6084
  composites: MotorSchema[];
@@ -6045,6 +6098,12 @@ declare class SchemaRepertoire {
6045
6098
  * Mirrors GoalManager._syncFromStateGoals.
6046
6099
  */
6047
6100
  restoreComposites(entities: ReadonlySimulationState['entities']): void;
6101
+ /** Availability ledger encoded as `agency.availability` state entities (P2).
6102
+ * Empty until a refusal lands, so the quiet path writes nothing. */
6103
+ availabilityEntities(): EntityInput[];
6104
+ /** Rehydrate the availability ledger from state after a restore. Idempotent;
6105
+ * keeps whichever value is more restrictive so a concurrent refusal isn't lost. */
6106
+ restoreAvailability(entities: ReadonlySimulationState['entities']): void;
6048
6107
  }
6049
6108
 
6050
6109
  type SkillAccessor = () => ReadonlyMap<string, LearnedSkill>;
@@ -6085,11 +6144,25 @@ declare class ActionSelector implements CognitiveEngine {
6085
6144
  private _bus;
6086
6145
  private _lastEntropy;
6087
6146
  private _lastDeliberate;
6147
+ private _lastRevoked;
6148
+ private _senseBuffer;
6088
6149
  attachBus(bus: CognitiveBus): void;
6089
6150
  publishes(): CognitiveEventSchema[];
6090
6151
  subscribes(): string[];
6091
- onCognitiveEvent(): void;
6152
+ /**
6153
+ * Mostly pull-model — but two afferent classes never become entities and
6154
+ * would leave rupture blind: sense-channel percepts
6155
+ * (ACTION_CONDITIONED_PREDICTION §2b) and the model-error state-change
6156
+ * events (registry #6). Buffer both (cross-tick: bus flush at T, consumed
6157
+ * by react at T+1 — FN9-snapshotted); the echo guard is applied at read
6158
+ * time for texts, and at the SOURCE for model errors (ACP-P2 precision).
6159
+ */
6160
+ onCognitiveEvent(e: CognitiveEvent): void;
6092
6161
  snapshot(): Record<string, unknown>;
6162
+ /** FN9: `_lastRevoked` has behavioral effect (the Channel-B `revokedBy` stamp),
6163
+ * so a restored mind must carry it — a rupture-driven letting-go survives a
6164
+ * snapshot boundary instead of silently losing its narrative thread. */
6165
+ restore(s: Record<string, unknown>): void;
6093
6166
  react(_delta: Duration, tick: Tick, state: ReadonlySimulationState, _context: SimulationContext): Promise<EngineResult>;
6094
6167
  }
6095
6168
 
@@ -7653,6 +7726,13 @@ declare class WillStem {
7653
7726
  description: string;
7654
7727
  metrics?: Record<string, number>;
7655
7728
  }): void;
7729
+ /**
7730
+ * Resolve a policy escalation the Will raised (POLICY_REAFFERENCE P4).
7731
+ * `approved` dispatches the held invocation to the world; otherwise it is
7732
+ * refused. Applied at the next tick boundary. `invocationId` is the awaiting
7733
+ * `agency.intent` id the escalation ask referenced.
7734
+ */
7735
+ resolveEscalation(id: string, invocationId: string, approved: boolean): void;
7656
7736
  /**
7657
7737
  * Confirm a message was received by the target entity. Writes a
7658
7738
  * message.delivery percept ("ear hears the word you spoke") and updates the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindot/will",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "author": "Fabrice <fabrice8@github.com>",
5
5
  "repository": {
6
6
  "type": "git",
@@ -84,8 +84,8 @@ export interface DiscordBridgeOptions {
84
84
  * itself is not stopped; it simply loses this surface.
85
85
  */
86
86
  export async function connectDiscord( will: Will, opts: DiscordBridgeOptions ): Promise<ChannelBridge> {
87
- const log = opts.log ?? ( ( m: string ) => console.error( `[will:discord] ${ m }` ) )
88
- const roster = new ChannelRoster( opts.rosterPath ?? `.will/${ will.id }.discord.json` )
87
+ const log = opts.log ?? ( ( m: string ) => console.error(`[will:discord] ${ m }`) )
88
+ const roster = new ChannelRoster( opts.rosterPath ?? `.will/${ will.id }.discord.json`)
89
89
  const allowed = opts.channels?.length ? new Set( opts.channels ) : null
90
90
 
91
91
  const client = opts.client ?? await createDiscordClient()
@@ -94,7 +94,7 @@ export async function connectDiscord( will: Will, opts: DiscordBridgeOptions ):
94
94
  let lastActiveChannelId: string | null = opts.homeChannelId ?? null
95
95
 
96
96
  // ── inbound: platform message → stimulus ──────────────────────────────────
97
- client.on( 'messageCreate', message => { void onMessage( message ) } )
97
+ client.on('messageCreate', message => { void onMessage( message ) } )
98
98
 
99
99
  async function onMessage( message: DiscordLikeMessage ): Promise<void> {
100
100
  const self = client.user
@@ -135,7 +135,7 @@ export async function connectDiscord( will: Will, opts: DiscordBridgeOptions ):
135
135
  // ── outbound: projected utterance → the addressee ─────────────────────────
136
136
  // The facade has no off(); the bridge gates its handler on `closed` instead.
137
137
  let closed = false
138
- will.on( 'message', ( m: WillMessage ) => { if( !closed ) void deliver( m ) } )
138
+ will.on('message', ( m: WillMessage ) => { if( !closed ) void deliver( m ) } )
139
139
 
140
140
  async function deliver( m: WillMessage ): Promise<void> {
141
141
  const peer = m.to ? roster.resolve( m.to ) : undefined
@@ -161,7 +161,7 @@ export async function connectDiscord( will: Will, opts: DiscordBridgeOptions ):
161
161
  }
162
162
  catch { /* fall through */ }
163
163
  }
164
- log( `no route for utterance to '${ m.to }' — dropped (${ m.content.length } chars)` )
164
+ log(`no route for utterance to '${ m.to }' — dropped (${ m.content.length } chars)`)
165
165
  }
166
166
 
167
167
  // ── lifecycle ──────────────────────────────────────────────────────────────
@@ -175,14 +175,14 @@ export async function connectDiscord( will: Will, opts: DiscordBridgeOptions ):
175
175
  const ready = new Promise<void>( resolve => {
176
176
  let poll: ReturnType<typeof setInterval> | null = null
177
177
  const done = (): void => { if( poll ) clearInterval( poll ); resolve() }
178
- client.once( 'clientReady', done )
178
+ client.once('clientReady', done )
179
179
  poll = setInterval( () => { if( client.isReady?.() ) done() }, 100 )
180
180
  poll.unref?.()
181
181
  } )
182
- await client.login( opts.token ?? '' )
182
+ await client.login( opts.token ?? '')
183
183
  await ready
184
184
  }
185
- log( `${ will.name } is present on Discord as user ${ client.user?.id }` )
185
+ log(`${ will.name } is present on Discord as user ${ client.user?.id }`)
186
186
  },
187
187
  async close(): Promise<void> {
188
188
  if( closed ) return
@@ -196,10 +196,10 @@ export async function connectDiscord( will: Will, opts: DiscordBridgeOptions ):
196
196
 
197
197
  /** Build a real discord.js client (lazy import keeps it out of non-Discord hosts). */
198
198
  async function createDiscordClient(): Promise<DiscordLikeClient> {
199
- let mod: typeof import( 'discord.js' )
200
- try { mod = await import( 'discord.js' ) }
199
+ let mod: typeof import('discord.js')
200
+ try { mod = await import('discord.js') }
201
201
  catch {
202
- throw new Error( 'discord.js is not installed (it is an optionalDependency) — run `bun add discord.js` / `npm i discord.js` and retry.' )
202
+ throw new Error('discord.js is not installed (it is an optionalDependency) — run `bun add discord.js` / `npm i discord.js` and retry.')
203
203
  }
204
204
  const { Client, GatewayIntentBits, Partials } = mod
205
205
  return new Client( {
@@ -42,7 +42,7 @@ export class ChannelRoster {
42
42
  constructor( private readonly path: string ) {
43
43
  if( existsSync( path ) ){
44
44
  try {
45
- const raw = JSON.parse( readFileSync( path, 'utf8' ) ) as RosterEntry[]
45
+ const raw = JSON.parse( readFileSync( path, 'utf8') ) as RosterEntry[]
46
46
  for( const e of Array.isArray( raw ) ? raw : [] ) this.entries.set( e.entityId, e )
47
47
  }
48
48
  catch { /* a corrupt roster is not worth failing a boot over — start fresh */ }
@@ -36,7 +36,7 @@ export function chunkText( text: string, max: number ): string[] {
36
36
  while( rest.length > max ){
37
37
  // Prefer a paragraph break, then a line break, then a space — else hard-cut.
38
38
  const window = rest.slice( 0, max )
39
- const cut = Math.max( window.lastIndexOf( '\n\n' ), window.lastIndexOf( '\n' ), window.lastIndexOf( ' ' ) )
39
+ const cut = Math.max( window.lastIndexOf('\n\n'), window.lastIndexOf('\n'), window.lastIndexOf(' ') )
40
40
  const at = cut > max * 0.5 ? cut : max
41
41
  chunks.push( rest.slice( 0, at ).trimEnd() )
42
42
  rest = rest.slice( at ).trimStart()
@@ -82,10 +82,10 @@ export interface WhatsAppBridgeOptions {
82
82
 
83
83
  // ── Helpers ──────────────────────────────────────────────────────────────────
84
84
 
85
- const isGroupJid = ( jid: string ): boolean => jid.endsWith( '@g.us' )
85
+ const isGroupJid = ( jid: string ): boolean => jid.endsWith('@g.us')
86
86
 
87
87
  /** '4915123:7@s.whatsapp.net' → '4915123' (device + server stripped). */
88
- const bareId = ( jid: string ): string => jid.split( '@' )[0]!.split( ':' )[0]!
88
+ const bareId = ( jid: string ): string => jid.split('@')[0]!.split(':')[0]!
89
89
 
90
90
  /** A DM jid is derivable from the bare number — WhatsApp's gift to proactivity. */
91
91
  const dmJidFor = ( userId: string ): string => `${ userId }@s.whatsapp.net`
@@ -108,8 +108,8 @@ function textOf( m: WaLikeMessage ): string {
108
108
  * `ChannelBridge.close()` — the Will keeps ticking; it only loses this surface.
109
109
  */
110
110
  export async function connectWhatsApp( will: Will, opts: WhatsAppBridgeOptions = {} ): Promise<ChannelBridge> {
111
- const log = opts.log ?? ( ( m: string ) => console.error( `[will:whatsapp] ${ m }` ) )
112
- const roster = new ChannelRoster( opts.rosterPath ?? `.will/${ will.id }.whatsapp.json` )
111
+ const log = opts.log ?? ( ( m: string ) => console.error(`[will:whatsapp] ${ m }`) )
112
+ const roster = new ChannelRoster( opts.rosterPath ?? `.will/${ will.id }.whatsapp.json`)
113
113
  const allowed = opts.chats?.length ? new Set( opts.chats ) : null
114
114
 
115
115
  let closed = false
@@ -123,15 +123,15 @@ export async function connectWhatsApp( will: Will, opts: WhatsAppBridgeOptions =
123
123
  let lastActiveChatId: string | null = opts.homeChatId ?? null
124
124
 
125
125
  // ── inbound: platform message → stimulus ──────────────────────────────────
126
- socket.ev.on( 'messages.upsert', ( { messages, type } ) => {
127
- if( type && type !== 'notify' ) return // history sync/corrections are not new percepts
126
+ socket.ev.on('messages.upsert', ( { messages, type } ) => {
127
+ if( type && type !== 'notify') return // history sync/corrections are not new percepts
128
128
  for( const m of messages ) void onMessage( m )
129
129
  } )
130
130
 
131
131
  async function onMessage( m: WaLikeMessage ): Promise<void> {
132
132
  const jid = m.key.remoteJid
133
133
  if( !jid || m.key.fromMe || !m.message || m.messageStubType ) return
134
- if( jid.endsWith( '@broadcast' ) || jid.endsWith( '@newsletter' ) ) return // stories/broadcasts aren't a room the Will is in
134
+ if( jid.endsWith('@broadcast') || jid.endsWith('@newsletter') ) return // stories/broadcasts aren't a room the Will is in
135
135
  if( allowed && !allowed.has( jid ) ) return
136
136
 
137
137
  const isGroup = isGroupJid( jid )
@@ -157,7 +157,7 @@ export async function connectWhatsApp( will: Will, opts: WhatsAppBridgeOptions =
157
157
 
158
158
  // Same rule as Discord: a presence cue only when addressed — the mind may
159
159
  // still choose silence, and 'composing' expires on its own.
160
- if( addressed ) await socket.sendPresenceUpdate?.( 'composing', jid ).catch( () => {} )
160
+ if( addressed ) await socket.sendPresenceUpdate?.('composing', jid ).catch( () => {} )
161
161
 
162
162
  const text = textOf( m )
163
163
  if( !text.trim() ) return
@@ -171,7 +171,7 @@ export async function connectWhatsApp( will: Will, opts: WhatsAppBridgeOptions =
171
171
  }
172
172
 
173
173
  // ── outbound: projected utterance → the addressee ─────────────────────────
174
- will.on( 'message', ( m: WillMessage ) => { if( !closed ) void deliver( m ) } )
174
+ will.on('message', ( m: WillMessage ) => { if( !closed ) void deliver( m ) } )
175
175
 
176
176
  async function deliver( m: WillMessage ): Promise<void> {
177
177
  const peer = m.to ? roster.resolve( m.to ) : undefined
@@ -180,7 +180,7 @@ export async function connectWhatsApp( will: Will, opts: WhatsAppBridgeOptions =
180
180
  // Last shared group → known DM → DM derived from the entity id itself →
181
181
  // home chat → last active chat. Unlike Discord, an unmet-but-addressed
182
182
  // entity is still reachable: `whatsapp:<number>` implies its DM jid.
183
- const derivedDm = m.to?.startsWith( 'whatsapp:' ) ? dmJidFor( m.to.slice( 'whatsapp:'.length ) ) : undefined
183
+ const derivedDm = m.to?.startsWith('whatsapp:') ? dmJidFor( m.to.slice('whatsapp:'.length ) ) : undefined
184
184
  const targets = [ peer?.lastChannelId, peer?.dmChannelId, derivedDm, opts.homeChatId ?? undefined, lastActiveChatId ?? undefined ]
185
185
  for( const jid of targets ){
186
186
  if( !jid ) continue
@@ -190,7 +190,7 @@ export async function connectWhatsApp( will: Will, opts: WhatsAppBridgeOptions =
190
190
  }
191
191
  catch { /* try the next route */ }
192
192
  }
193
- log( `no route for utterance to '${ m.to }' — dropped (${ m.content.length } chars)` )
193
+ log(`no route for utterance to '${ m.to }' — dropped (${ m.content.length } chars)`)
194
194
  }
195
195
 
196
196
  // ── lifecycle ──────────────────────────────────────────────────────────────
@@ -199,7 +199,7 @@ export async function connectWhatsApp( will: Will, opts: WhatsAppBridgeOptions =
199
199
  async start(): Promise<void> {
200
200
  // createWhatsAppSocket resolves already-open; an injected socket is the
201
201
  // caller's to have readied.
202
- log( `${ will.name } is present on WhatsApp${ socket.user ? ` as ${ bareId( socket.user.id ) }` : '' }` )
202
+ log(`${ will.name } is present on WhatsApp${ socket.user ? ` as ${ bareId( socket.user.id ) }` : '' }`)
203
203
  },
204
204
  async close(): Promise<void> {
205
205
  if( closed ) return
@@ -226,10 +226,10 @@ interface SocketFactoryOpts {
226
226
  * Resolves once the first connection is open (after QR pairing on first run).
227
227
  */
228
228
  async function createWhatsAppSocket( o: SocketFactoryOpts ): Promise<WaLikeSocket> {
229
- let baileys: typeof import( 'baileys' )
230
- try { baileys = await import( 'baileys' ) }
229
+ let baileys: typeof import('baileys')
230
+ try { baileys = await import('baileys') }
231
231
  catch {
232
- throw new Error( 'baileys is not installed (it is an optionalDependency) — run `bun add baileys` / `npm i baileys` and retry.' )
232
+ throw new Error('baileys is not installed (it is an optionalDependency) — run `bun add baileys` / `npm i baileys` and retry.')
233
233
  }
234
234
  const { default: makeWASocket, useMultiFileAuthState, DisconnectReason } = baileys
235
235
 
@@ -252,7 +252,7 @@ async function createWhatsAppSocket( o: SocketFactoryOpts ): Promise<WaLikeSocke
252
252
  get user(){ return inner?.user ? { id: inner.user.id, name: inner.user.name ?? undefined } : null },
253
253
  ev: { on: ( _e, fn ) => { subscribers.push( fn ) } },
254
254
  sendMessage: ( jid, content ) => {
255
- if( !inner ) return Promise.reject( new Error( 'whatsapp socket not connected' ) )
255
+ if( !inner ) return Promise.reject( new Error('whatsapp socket not connected') )
256
256
  return inner.sendMessage( jid, content )
257
257
  },
258
258
  sendPresenceUpdate: ( state, jid ) => inner?.sendPresenceUpdate( state, jid ) ?? Promise.resolve(),
@@ -268,10 +268,10 @@ async function createWhatsAppSocket( o: SocketFactoryOpts ): Promise<WaLikeSocke
268
268
  const sock = makeWASocket( { auth: state, logger: logger as never } )
269
269
  inner = sock
270
270
 
271
- sock.ev.on( 'creds.update', saveCreds )
272
- sock.ev.on( 'messages.upsert', u => { for( const fn of subscribers ) fn( u as never ) } )
271
+ sock.ev.on('creds.update', saveCreds )
272
+ sock.ev.on('messages.upsert', u => { for( const fn of subscribers ) fn( u as never ) } )
273
273
 
274
- sock.ev.on( 'connection.update', update => {
274
+ sock.ev.on('connection.update', update => {
275
275
  const { connection, lastDisconnect, qr } = update as {
276
276
  connection?: string; lastDisconnect?: { error?: unknown }; qr?: string
277
277
  }
@@ -282,16 +282,16 @@ async function createWhatsAppSocket( o: SocketFactoryOpts ): Promise<WaLikeSocke
282
282
  opened = true
283
283
  resolveOpen()
284
284
  }
285
- if( connection === 'close' ){
285
+ if( connection === 'close'){
286
286
  const code = ( lastDisconnect?.error as { output?: { statusCode?: number } } | undefined )?.output?.statusCode
287
287
  if( code === DisconnectReason.loggedOut ){
288
- const err = new Error( 'WhatsApp unlinked this device (logged out) — delete the auth dir and pair again.' )
288
+ const err = new Error('WhatsApp unlinked this device (logged out) — delete the auth dir and pair again.')
289
289
  o.log( err.message )
290
290
  if( !opened ) rejectOpen( err )
291
291
  return
292
292
  }
293
293
  if( o.stillOpen() ){
294
- o.log( `connection closed (status ${ code ?? '?' }) — reconnecting…` )
294
+ o.log(`connection closed (status ${ code ?? '?' }) — reconnecting…`)
295
295
  setTimeout( connect, 3_000 )
296
296
  }
297
297
  }
@@ -306,13 +306,13 @@ async function createWhatsAppSocket( o: SocketFactoryOpts ): Promise<WaLikeSocke
306
306
 
307
307
  /** QR to the terminal; falls back to the raw pairing string if the tiny renderer is absent. */
308
308
  async function printQr( qr: string, log: ( m: string ) => void ): Promise<void> {
309
- log( 'pair this device: WhatsApp → Settings → Linked devices → Link a device' )
309
+ log('pair this device: WhatsApp → Settings → Linked devices → Link a device')
310
310
  try {
311
311
  type QrModule = { generate( s: string, o: { small: boolean } ): void }
312
- const qrt = ( await import( 'qrcode-terminal' as string ) ) as QrModule & { default?: QrModule }
312
+ const qrt = ( await import('qrcode-terminal' as string ) ) as QrModule & { default?: QrModule }
313
313
  ;( qrt.default ?? qrt ).generate( qr, { small: true } )
314
314
  }
315
315
  catch {
316
- log( `qrcode-terminal not installed — raw pairing code:\n${ qr }` )
316
+ log(`qrcode-terminal not installed — raw pairing code:\n${ qr }`)
317
317
  }
318
318
  }
package/src/cli.ts CHANGED
@@ -50,74 +50,74 @@ its PMA artifact.`
50
50
  async function main(): Promise<void> {
51
51
  const sub = process.argv[2]
52
52
 
53
- if( sub !== 'mcp' && sub !== 'serve' && sub !== 'discord' && sub !== 'whatsapp' ){
53
+ if( sub !== 'mcp' && sub !== 'serve' && sub !== 'discord' && sub !== 'whatsapp'){
54
54
  console.error( sub ? `unknown subcommand: ${ sub }\n\n${ USAGE }` : USAGE )
55
55
  process.exit( sub ? 2 : 0 )
56
56
  }
57
57
 
58
58
  // Fail on missing platform credentials BEFORE raising a mind.
59
59
  if( sub === 'discord' && !process.env.DISCORD_BOT_TOKEN ){
60
- console.error( '[will] DISCORD_BOT_TOKEN is required for `will discord` — create a bot at https://discord.com/developers/applications (enable the Message Content intent) and set the token.' )
60
+ console.error('[will] DISCORD_BOT_TOKEN is required for `will discord` — create a bot at https://discord.com/developers/applications (enable the Message Content intent) and set the token.')
61
61
  process.exit( 2 )
62
62
  }
63
63
 
64
64
  const { will, name, pmaPath, tickMs, anatomy, onCleanup, shutdown } = await bootWillFromEnv()
65
65
 
66
- if( sub === 'mcp' ){
66
+ if( sub === 'mcp'){
67
67
  // The MCP client owns our stdin — its disconnect is the shutdown signal.
68
- process.stdin.on( 'end', () => void shutdown( 'client disconnected' ) )
68
+ process.stdin.on('end', () => void shutdown('client disconnected') )
69
69
  const server = buildWillMcpServer( will, { pmaPath } )
70
70
  await server.connect( new StdioServerTransport() )
71
- console.error( `[will] ${ name } is listening on MCP stdio (tick ${ tickMs }ms, anatomy ${ anatomy })` )
71
+ console.error(`[will] ${ name } is listening on MCP stdio (tick ${ tickMs }ms, anatomy ${ anatomy })`)
72
72
  return
73
73
  }
74
74
 
75
- if( sub === 'discord' ){
76
- const csv = ( v?: string ) => v?.split( ',' ).map( s => s.trim() ).filter( Boolean )
75
+ if( sub === 'discord'){
76
+ const csv = ( v?: string ) => v?.split(',').map( s => s.trim() ).filter( Boolean )
77
77
  const bridge = await connectDiscord( will, {
78
78
  token: process.env.DISCORD_BOT_TOKEN!,
79
79
  channels: csv( process.env.WILL_DISCORD_CHANNELS ),
80
- mentionOnly: /^(1|true|yes)$/i.test( process.env.WILL_DISCORD_MENTION_ONLY ?? '' ),
80
+ mentionOnly: /^(1|true|yes)$/i.test( process.env.WILL_DISCORD_MENTION_ONLY ?? ''),
81
81
  homeChannelId: process.env.WILL_DISCORD_HOME_CHANNEL,
82
- rosterPath: pmaPath.replace( /(\.pma)?\.json$/, '' ) + '.discord.json',
82
+ rosterPath: pmaPath.replace( /(\.pma)?\.json$/, '') + '.discord.json',
83
83
  } )
84
84
  onCleanup( () => bridge.close() )
85
85
  await bridge.start()
86
- console.error( `[will] ${ name } is present on Discord (tick ${ tickMs }ms, anatomy ${ anatomy }) — it speaks when it decides to.` )
86
+ console.error(`[will] ${ name } is present on Discord (tick ${ tickMs }ms, anatomy ${ anatomy }) — it speaks when it decides to.`)
87
87
  return
88
88
  }
89
89
 
90
- if( sub === 'whatsapp' ){
91
- const csv = ( v?: string ) => v?.split( ',' ).map( s => s.trim() ).filter( Boolean )
92
- const stem = pmaPath.replace( /(\.pma)?\.json$/, '' )
90
+ if( sub === 'whatsapp'){
91
+ const csv = ( v?: string ) => v?.split(',').map( s => s.trim() ).filter( Boolean )
92
+ const stem = pmaPath.replace( /(\.pma)?\.json$/, '')
93
93
  // connectWhatsApp blocks here on first run until the printed QR is scanned.
94
94
  const bridge = await connectWhatsApp( will, {
95
95
  chats: csv( process.env.WILL_WHATSAPP_CHATS ),
96
- mentionOnly: /^(1|true|yes)$/i.test( process.env.WILL_WHATSAPP_MENTION_ONLY ?? '' ),
96
+ mentionOnly: /^(1|true|yes)$/i.test( process.env.WILL_WHATSAPP_MENTION_ONLY ?? ''),
97
97
  homeChatId: process.env.WILL_WHATSAPP_HOME_CHAT,
98
98
  authPath: stem + '.wa-auth',
99
99
  rosterPath: stem + '.whatsapp.json',
100
100
  } )
101
101
  onCleanup( () => bridge.close() )
102
102
  await bridge.start()
103
- console.error( `[will] ${ name } is present on WhatsApp (tick ${ tickMs }ms, anatomy ${ anatomy }) — it speaks when it decides to.` )
103
+ console.error(`[will] ${ name } is present on WhatsApp (tick ${ tickMs }ms, anatomy ${ anatomy }) — it speaks when it decides to.`)
104
104
  return
105
105
  }
106
106
 
107
107
  // serve — the HTTP sidecar.
108
- const port = parseInt( process.env.WILL_PORT ?? '7777' )
108
+ const port = parseInt( process.env.WILL_PORT ?? '7777')
109
109
  const host = process.env.WILL_HOST ?? '127.0.0.1'
110
110
  const server = buildWillHttpServer( will, { pmaPath } )
111
111
  onCleanup( () => new Promise<void>( r => server.close( () => r() ) ) )
112
112
  await new Promise<void>( ( resolve, reject ) => {
113
- server.once( 'error', reject )
113
+ server.once('error', reject )
114
114
  server.listen( port, host, () => resolve() )
115
115
  } )
116
- console.error( `[will] ${ name } is listening on http://${ host }:${ port } (tick ${ tickMs }ms, anatomy ${ anatomy })` )
117
- console.error( `[will] try: curl -X POST http://${ host }:${ port }/perceive -H 'content-type: application/json' -d '{"text":"Hello"}'` )
116
+ console.error(`[will] ${ name } is listening on http://${ host }:${ port } (tick ${ tickMs }ms, anatomy ${ anatomy })`)
117
+ console.error(`[will] try: curl -X POST http://${ host }:${ port }/perceive -H 'content-type: application/json' -d '{"text":"Hello"}'`)
118
118
  }
119
119
 
120
120
  main().catch( e => {
121
- console.error( `[will] fatal: ${ e instanceof Error ? e.stack ?? e.message : String( e ) }` )
121
+ console.error(`[will] fatal: ${ e instanceof Error ? e.stack ?? e.message : String( e ) }`)
122
122
  process.exit( 1 )
123
123
  } )