@mindot/will 0.1.0 → 0.2.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 (36) hide show
  1. package/README.md +38 -3
  2. package/dist/index.d.ts +52 -7760
  3. package/dist/index.js +374 -86
  4. package/dist/index.js.map +1 -1
  5. package/dist/mcp/cli.d.ts +1 -0
  6. package/dist/mcp/cli.js +26048 -0
  7. package/dist/mcp/cli.js.map +1 -0
  8. package/dist/mcp/effectors.d.ts +55 -0
  9. package/dist/mcp/effectors.js +76 -0
  10. package/dist/mcp/effectors.js.map +1 -0
  11. package/dist/will-B5eKs3Wv.d.ts +7903 -0
  12. package/package.json +38 -30
  13. package/src/cognition/agency/engines/action.selector.ts +3 -0
  14. package/src/cognition/agency/engines/affordance.synthesizer.ts +26 -12
  15. package/src/cognition/agency/engines/deliberation.engine.ts +7 -2
  16. package/src/cognition/agency/engines/motor.schema.executor.ts +2 -0
  17. package/src/cognition/agency/schemas/external.ts +27 -10
  18. package/src/cognition/agency/schemas/repertoire.ts +12 -0
  19. package/src/cognition/agency/types.ts +51 -2
  20. package/src/cognition/faculties/executive.engine/commands.ts +47 -11
  21. package/src/cognition/faculties/executive.engine/context.ts +28 -0
  22. package/src/cognition/faculties/executive.engine/facet.supervisor.ts +15 -1
  23. package/src/cognition/faculties/executive.engine/facet.ts +32 -6
  24. package/src/cognition/faculties/executive.engine/prompt.factory.ts +11 -1
  25. package/src/cognition/faculties/executive.engine/types.ts +24 -2
  26. package/src/cognition/index.ts +1 -1
  27. package/src/core/abstracts.ts +39 -12
  28. package/src/index.ts +6 -0
  29. package/src/mcp/cli.ts +129 -0
  30. package/src/mcp/effectors.ts +159 -0
  31. package/src/mcp/server.ts +167 -0
  32. package/src/sdk/will.ts +269 -44
  33. package/src/stem/index.ts +16 -0
  34. package/src/stem/mind.ts +11 -4
  35. package/src/stem/tracts/effector.controller.ts +1 -0
  36. package/src/types.ts +2 -0
package/dist/index.js CHANGED
@@ -998,27 +998,54 @@ var DefaultStateManager = class {
998
998
 
999
999
  // src/core/abstracts.ts
1000
1000
  var BunStorageAdapter = class {
1001
+ get _isBun() {
1002
+ return typeof Bun !== "undefined";
1003
+ }
1001
1004
  async write(path, content) {
1002
- await Bun.write(path, content);
1005
+ if (this._isBun) {
1006
+ await Bun.write(path, content);
1007
+ return;
1008
+ }
1009
+ const { mkdir, writeFile } = await import('fs/promises');
1010
+ const { dirname } = await import('path');
1011
+ await mkdir(dirname(path), { recursive: true });
1012
+ await writeFile(path, content);
1003
1013
  }
1004
1014
  async read(path) {
1005
- const file = Bun.file(path);
1006
- if (!await file.exists())
1015
+ if (!await this.exists(path))
1007
1016
  throw new Error(`File not found: ${path}`);
1008
- return file.text();
1017
+ if (this._isBun)
1018
+ return Bun.file(path).text();
1019
+ const { readFile } = await import('fs/promises');
1020
+ return readFile(path, "utf8");
1009
1021
  }
1010
1022
  async readBytes(path) {
1011
- const file = Bun.file(path);
1012
- if (!await file.exists())
1023
+ if (!await this.exists(path))
1013
1024
  throw new Error(`File not found: ${path}`);
1014
- return new Uint8Array(await file.arrayBuffer());
1025
+ if (this._isBun)
1026
+ return new Uint8Array(await Bun.file(path).arrayBuffer());
1027
+ const { readFile } = await import('fs/promises');
1028
+ return new Uint8Array(await readFile(path));
1015
1029
  }
1016
1030
  async exists(path) {
1017
- return Bun.file(path).exists();
1031
+ if (this._isBun)
1032
+ return Bun.file(path).exists();
1033
+ const { access } = await import('fs/promises');
1034
+ try {
1035
+ await access(path);
1036
+ return true;
1037
+ } catch {
1038
+ return false;
1039
+ }
1018
1040
  }
1019
1041
  async delete(path) {
1020
- const file = Bun.file(path);
1021
- await file.exists() && await file.delete();
1042
+ if (this._isBun) {
1043
+ const file = Bun.file(path);
1044
+ await file.exists() && await file.delete();
1045
+ return;
1046
+ }
1047
+ const { rm } = await import('fs/promises');
1048
+ await rm(path, { force: true });
1022
1049
  }
1023
1050
  async ensureDir(path) {
1024
1051
  const { mkdirSync: mkdirSync6 } = await import('fs');
@@ -7856,16 +7883,43 @@ function buildIdeomotorIntents(output, state, footprint) {
7856
7883
  const set = [];
7857
7884
  const seen = /* @__PURE__ */ new Set();
7858
7885
  const priority = clamp01(output.confidence ?? 0.8);
7886
+ const externalBySchema = /* @__PURE__ */ new Map();
7887
+ for (const e of state.entities.values()) {
7888
+ if (e.type !== "affordance") continue;
7889
+ const m = e.metadata;
7890
+ if (m?.["source"] !== "external") continue;
7891
+ const schema = typeof m["schema"] === "string" ? m["schema"] : void 0;
7892
+ if (schema) externalBySchema.set(schema.toLowerCase(), schema);
7893
+ }
7859
7894
  for (const action of output.actions) {
7860
- if (!COMMUNICATE_ACTION_TYPES.has(action.type.toLowerCase())) continue;
7861
- if (!action.target) continue;
7862
- const keid = resolveKnownEntity(action.target, state);
7863
- if (!keid || seen.has(keid)) continue;
7864
- seen.add(keid);
7895
+ const t = action.type.toLowerCase();
7896
+ if (COMMUNICATE_ACTION_TYPES.has(t)) {
7897
+ if (!action.target) continue;
7898
+ const keid2 = resolveKnownEntity(action.target, state);
7899
+ if (!keid2 || seen.has(keid2)) continue;
7900
+ seen.add(keid2);
7901
+ set.push({
7902
+ id: `ideomotor-reach-out-${keid2}`,
7903
+ type: "ideomotor.intent",
7904
+ metadata: { schema: "reach-out", targetEntityId: keid2, priority, origin: "executive", tick: footprint.tickObserved }
7905
+ });
7906
+ continue;
7907
+ }
7908
+ const schema = externalBySchema.get(t);
7909
+ if (!schema || seen.has(`ability:${schema}`)) continue;
7910
+ seen.add(`ability:${schema}`);
7911
+ const keid = action.target ? resolveKnownEntity(action.target, state) : void 0;
7865
7912
  set.push({
7866
- id: `ideomotor-reach-out-${keid}`,
7913
+ id: `ideomotor-${schema}${keid ? `-${keid}` : ""}`,
7867
7914
  type: "ideomotor.intent",
7868
- metadata: { schema: "reach-out", targetEntityId: keid, priority, origin: "executive", tick: footprint.tickObserved }
7915
+ metadata: {
7916
+ schema,
7917
+ ...keid ? { targetEntityId: keid } : {},
7918
+ ...action.args && typeof action.args === "object" ? { parameters: action.args } : {},
7919
+ priority,
7920
+ origin: "executive",
7921
+ tick: footprint.tickObserved
7922
+ }
7869
7923
  });
7870
7924
  }
7871
7925
  const currentIds = new Set(set.map((s) => s.id));
@@ -9918,6 +9972,7 @@ async function buildExecutiveContext(state, deps, recallQuery) {
9918
9972
  plans,
9919
9973
  relevantPlanIds,
9920
9974
  percepts,
9975
+ abilities: extractAbilities(state),
9921
9976
  workingMemory,
9922
9977
  memories,
9923
9978
  beliefs,
@@ -9929,6 +9984,23 @@ async function buildExecutiveContext(state, deps, recallQuery) {
9929
9984
  currentFocus: extractCurrentFocus(state, goals)
9930
9985
  };
9931
9986
  }
9987
+ var MAX_SURFACED_ABILITIES = 8;
9988
+ function extractAbilities(state) {
9989
+ const out = [];
9990
+ for (const e of state.entities.values()) {
9991
+ if (e.type !== "affordance") continue;
9992
+ const m = e.metadata;
9993
+ if (m?.["source"] !== "external" || m?.["available"] === false) continue;
9994
+ const name = typeof m?.["schema"] === "string" ? m["schema"] : void 0;
9995
+ if (!name) continue;
9996
+ const description = typeof m["description"] === "string" ? m["description"] : void 0;
9997
+ const params = m["parameters"];
9998
+ const target = m["targetEntityId"] ? typeof params?.["targetEntityName"] === "string" ? params["targetEntityName"] : String(m["targetEntityId"]) : void 0;
9999
+ out.push({ name, ...description ? { description } : {}, ...target ? { target } : {} });
10000
+ if (out.length >= MAX_SURFACED_ABILITIES) break;
10001
+ }
10002
+ return out.length > 0 ? out : void 0;
10003
+ }
9932
10004
  function extractCurrentFocus(state, goals) {
9933
10005
  const m = state.entities.get("task-switch-focus")?.metadata;
9934
10006
  if (!m?.goalId) return void 0;
@@ -10185,7 +10257,7 @@ ${roleDescription}
10185
10257
  ${consciousnessArchitecture}
10186
10258
 
10187
10259
  ## Output Guidelines
10188
- - **actions**: Choose from effectors you know about. If uncertain, describe what you want to achieve in natural language and your body will try to match it.
10260
+ - **actions**: Choose from effectors you know about. If uncertain, describe what you want to achieve in natural language and your body will try to match it. When enacting one of your available abilities that needs specifics (a query, a message, a value), supply them in the action's "args" object \u2014 e.g. {"type": "search_docs", "args": {"query": "tick loop design"}, ...}. Your body enacts the ability with exactly those args.
10189
10261
  - **plans**: Include for goals without existing plans or where plans need revision. You may keep multiple plans per goal \u2014 set **planId** to act on a specific existing plan (validate/execute/revise/cancel); omit it to draft a new one. Your current plans are listed under "## Active Plans".
10190
10262
  - **newBeliefs**: Extract patterns from experiences visible in your current state. Only record a belief if you can point to a specific observation that supports it \u2014 do not infer experiences you have no record of. Set 'evidence' honestly: 'single_observation' (first time noticing), 'recurring_pattern' (seen multiple times), 'strong_pattern' (deeply established).
10191
10263
  - **introspection**: Include when significant events occurred or you notice patterns. When you spot a cognitive bias in your own reasoning, name it in 'identifiedBiases' using its common term where one fits (e.g. overgeneralization, confirmation bias, recency bias) \u2014 this lets your self-assessment line up with the patterns your faculties detect on their own.
@@ -10408,6 +10480,11 @@ ${context.goals.map((g) => {
10408
10480
  const recentOutcomesBlock = has("recentActions") ? this._buildRecentOutcomesSection(context.recentActions, state.tick).trim() : "";
10409
10481
  const perceptsBlock = has("percepts") ? `## Percepts (What You Notice)
10410
10482
  ${context.percepts.slice(0, 10).map((p) => `- [${p.category}] ${p.summary} (salience: ${p.salience.toFixed(2)})`).join("\n") || "Nothing notable"}` : "";
10483
+ const abilitiesBlock = context.abilities && context.abilities.length > 0 ? `## Abilities Available Now
10484
+ Things you can do in this situation \u2014 name one as an action's "type" (with "args" for any specifics it needs) and your body enacts it:
10485
+ ${context.abilities.map(
10486
+ (a) => `- **${a.name}**${a.target ? ` (toward ${a.target})` : ""}${a.description ? ` \u2014 ${a.description}` : ""}`
10487
+ ).join("\n")}` : "";
10411
10488
  const ruminationsBlock = has("ruminations") ? `## Active Ruminations (retrieved memories & thoughts)
10412
10489
  ${context.workingMemory.map((w) => `- [${w.type}] ${w.summary} (activation: ${w.activation.toFixed(2)})`).join("\n") || "Nothing actively held in mind"}` : "";
10413
10490
  const memoriesBlock = has("memories") ? this._buildMemoriesSection(context.memories, state.tick) : "";
@@ -10437,6 +10514,7 @@ You've been focused on ${context.currentFocus.goalDescription ? `"${context.curr
10437
10514
  actionDiversity.trim(),
10438
10515
  recentOutcomesBlock,
10439
10516
  perceptsBlock,
10517
+ abilitiesBlock,
10440
10518
  ruminationsBlock,
10441
10519
  recentIntrospection.trim(),
10442
10520
  memoriesBlock,
@@ -11071,6 +11149,17 @@ var ExecutiveFacet = class {
11071
11149
  markActive(tick) {
11072
11150
  this._lastActiveTick = tick;
11073
11151
  }
11152
+ /** _reason() calls currently in flight — a real LLM call spans many ticks. */
11153
+ _inflight = 0;
11154
+ /**
11155
+ * True while the facet has work the reaper must not discard: queued reports
11156
+ * awaiting the pump, or an in-flight _reason() whose decision hasn't landed.
11157
+ * The idle TTL only measures *quiet* facets — reaping a busy one destroys the
11158
+ * listeners its pending decision needs, silently dropping a conversation reply.
11159
+ */
11160
+ get busy() {
11161
+ return this._inflight > 0 || this._pendingReports.length > 0;
11162
+ }
11074
11163
  /**
11075
11164
  * Per-facet chunk handler — fires for every LLM token during _reason().
11076
11165
  * Set by the creating engine (e.g. AuditionEngine) for entity-scoped streaming.
@@ -11124,9 +11213,24 @@ var ExecutiveFacet = class {
11124
11213
  this._pendingReports.push(report);
11125
11214
  return;
11126
11215
  }
11216
+ this._launchReason(report);
11217
+ }
11218
+ /**
11219
+ * Launch _reason() with in-flight accounting. `busy` must hold for the whole
11220
+ * span (a real LLM call is 10–30s ≈ many ticks) so the supervisor's idle
11221
+ * reaper never destroys a facet whose decision is still coming — that would
11222
+ * clear its listeners and silently drop the reply. Completion re-stamps
11223
+ * activity so the idle TTL measures quiet time *after* the decision, not
11224
+ * after the report that started it.
11225
+ */
11226
+ _launchReason(report) {
11227
+ this._inflight++;
11127
11228
  this._reason(report).catch(
11128
11229
  (err) => logger.error(`[executive.facet] ${this.facetId} reasoning error:`, err)
11129
- );
11230
+ ).finally(() => {
11231
+ this._inflight--;
11232
+ this.markActive(this._currentStateRef?.tick ?? this._lastActiveTick);
11233
+ });
11130
11234
  }
11131
11235
  /**
11132
11236
  * Per-tick pump — called by the FacetSupervisor from the ExecutiveEngine's
@@ -11142,9 +11246,7 @@ var ExecutiveFacet = class {
11142
11246
  const batch = this._pendingReports;
11143
11247
  this._pendingReports = [];
11144
11248
  for (const report of batch)
11145
- this._reason(report).catch(
11146
- (err) => logger.error(`[executive.facet] ${this.facetId} reasoning error:`, err)
11147
- );
11249
+ this._launchReason(report);
11148
11250
  }
11149
11251
  subscribe(listener) {
11150
11252
  this._listeners.add(listener);
@@ -12321,9 +12423,11 @@ var FacetSupervisor = class {
12321
12423
  this._reapIdle(state.tick);
12322
12424
  }
12323
12425
  _reapIdle(tick) {
12324
- for (const [id, facet] of [...this._facets])
12426
+ for (const [id, facet] of [...this._facets]) {
12427
+ if (facet.busy) continue;
12325
12428
  if (tick - facet.lastActiveTick > this._idleTtlTicks)
12326
12429
  this._reap(id, "idle");
12430
+ }
12327
12431
  }
12328
12432
  /** Destroy + deregister a facet and notify its owner. Shared by the reaper + LRU eviction. */
12329
12433
  _reap(facetId, reason) {
@@ -12350,6 +12454,12 @@ var FacetSupervisor = class {
12350
12454
  _leastRecentlyActive() {
12351
12455
  let id = null;
12352
12456
  let min = Infinity;
12457
+ for (const [fid, facet] of this._facets)
12458
+ if (!facet.busy && facet.lastActiveTick < min) {
12459
+ min = facet.lastActiveTick;
12460
+ id = fid;
12461
+ }
12462
+ if (id) return id;
12353
12463
  for (const [fid, facet] of this._facets)
12354
12464
  if (facet.lastActiveTick < min) {
12355
12465
  min = facet.lastActiveTick;
@@ -18402,24 +18512,30 @@ var AffordanceSynthesizer = class {
18402
18512
  })
18403
18513
  });
18404
18514
  }
18405
- const entitySchema = schemas.find((s) => s.binds === "entity");
18406
- if (entitySchema)
18515
+ const personSchemas = schemas.filter((s) => s.binds === "entity");
18516
+ const objectSchemas = schemas.filter((s) => s.binds === "object");
18517
+ if (personSchemas.length > 0 || objectSchemas.length > 0)
18407
18518
  for (const [id, e] of state.entities) {
18408
18519
  if (e.type !== "known-entity") continue;
18409
18520
  const m = e.metadata;
18410
- if (str(m?.["kind"]) !== "sentient") continue;
18521
+ const kind = str(m?.["kind"]);
18522
+ const applicable = kind === "sentient" ? personSchemas : kind === "thing" ? objectSchemas : null;
18523
+ if (!applicable || applicable.length === 0) continue;
18411
18524
  const keid = str(m?.["keid"]) ?? id;
18412
18525
  const fam = num(m?.["familiarity"], 0);
18413
18526
  const val = num(m?.["valence"], 0);
18414
18527
  const res = num(m?.["resolutionConfidence"], 0);
18415
- candidates.push({
18416
- salience: fam * 0.6 + Math.max(0, val) * 0.3 + res * 0.1 + (goalTargets.get(keid) ?? 0),
18417
- affordance: this._build(entitySchema, tick, state, valence, energyLow, skills, {
18418
- evokedBy: id,
18419
- targetEntityId: keid,
18420
- parameters: { targetEntityName: str(m?.["name"]) ?? keid }
18421
- })
18422
- });
18528
+ const salience = fam * 0.6 + Math.max(0, val) * 0.3 + res * 0.1 + (goalTargets.get(keid) ?? 0);
18529
+ const name = str(m?.["name"]) ?? keid;
18530
+ for (const schema of applicable)
18531
+ candidates.push({
18532
+ salience,
18533
+ affordance: this._build(schema, tick, state, valence, energyLow, skills, {
18534
+ evokedBy: id,
18535
+ targetEntityId: keid,
18536
+ parameters: { targetEntityName: name }
18537
+ })
18538
+ });
18423
18539
  }
18424
18540
  for (const [id, e] of state.entities) {
18425
18541
  if (e.type !== "ideomotor.intent") continue;
@@ -18511,6 +18627,7 @@ var AffordanceSynthesizer = class {
18511
18627
  habitStrength,
18512
18628
  available: this._available(schema.preconditions, (k) => metric(state, k, 0)),
18513
18629
  tags: schema.tags ?? [],
18630
+ ...schema.description ? { description: schema.description } : {},
18514
18631
  planBias: ctx.planBias,
18515
18632
  planId: ctx.planId,
18516
18633
  stepId: ctx.stepId,
@@ -18533,6 +18650,7 @@ var AffordanceSynthesizer = class {
18533
18650
  habitStrength: a.habitStrength,
18534
18651
  available: a.available,
18535
18652
  tags: a.tags,
18653
+ description: a.description,
18536
18654
  planBias: a.planBias,
18537
18655
  planId: a.planId,
18538
18656
  stepId: a.stepId,
@@ -18739,6 +18857,9 @@ var ActionSelector = class {
18739
18857
  targetEntityId: s.affordance.targetEntityId,
18740
18858
  parameters: s.affordance.parameters,
18741
18859
  activation: s.activation,
18860
+ // Carry the ability's meaning so the Deliberator weighs what each
18861
+ // option is FOR, not bare labels.
18862
+ ...s.affordance.description ? { description: s.affordance.description } : {},
18742
18863
  // Channel B: flag a candidate that is an active plan's frontier step, so
18743
18864
  // the deliberation facet can own "this is my plan's next step" in-character.
18744
18865
  ...s.affordance.source === "plan" ? { fromPlan: true } : {}
@@ -18998,8 +19119,9 @@ var DeliberationEngine = class {
18998
19119
  lines.push("Your automatic action-selection was uncertain. Candidate actions:");
18999
19120
  candidates.forEach((c, i) => {
19000
19121
  const to = c.targetEntityId ? ` toward ${c.targetEntityId}` : "";
19001
- const plan = c.fromPlan ? " \u2014 your current plan's next step" : "";
19002
- lines.push(`${i + 1}. ${c.schema}${to}${plan}`);
19122
+ const what = c.description ? ` \u2014 ${c.description}` : "";
19123
+ const plan = c.fromPlan ? " (your current plan's next step)" : "";
19124
+ lines.push(`${i + 1}. ${c.schema}${to}${what}${plan}`);
19003
19125
  });
19004
19126
  return lines.join("\n");
19005
19127
  }
@@ -19476,7 +19598,9 @@ var MotorSchemaExecutor = class {
19476
19598
  intentId: intent.id,
19477
19599
  targetEntityId: intent.targetEntityId,
19478
19600
  parameters: intent.parameters,
19479
- tick
19601
+ tick,
19602
+ // The ability's declared meaning, carried to the host handler.
19603
+ description: this._resolve(intent.schema)?.description
19480
19604
  }
19481
19605
  });
19482
19606
  } catch (err) {
@@ -19570,6 +19694,15 @@ var SchemaRepertoire = class {
19570
19694
  if (!this._skills.has(schema.id))
19571
19695
  this._skills.set(schema.id, freshSkill(schema.id, 0.4, 0));
19572
19696
  }
19697
+ /**
19698
+ * Register a host effector's primitive schema at runtime (post-create
19699
+ * `.effector()`). Unlike a composite it is NOT marked learned — it is a
19700
+ * capacity the host granted, which the synthesizer surfaces immediately and
19701
+ * reafference then builds skill on. Idempotent; re-registering updates it.
19702
+ */
19703
+ registerExternal(schema) {
19704
+ this._templates.set(schema.id, schema);
19705
+ }
19573
19706
  // ── skills ────────────────────────────────────────────────────
19574
19707
  skills() {
19575
19708
  return this._skills;
@@ -19703,6 +19836,7 @@ function schemaEntity(s) {
19703
19836
  preconditions: s.preconditions,
19704
19837
  composedOf: s.composedOf,
19705
19838
  baseValence: s.baseValence,
19839
+ description: s.description,
19706
19840
  tags: s.tags
19707
19841
  }
19708
19842
  };
@@ -19721,6 +19855,7 @@ function readSchema(m) {
19721
19855
  preconditions: meta["preconditions"],
19722
19856
  composedOf: Array.isArray(meta["composedOf"]) ? meta["composedOf"] : void 0,
19723
19857
  baseValence: typeof meta["baseValence"] === "number" ? meta["baseValence"] : void 0,
19858
+ description: typeof meta["description"] === "string" ? meta["description"] : void 0,
19724
19859
  tags: Array.isArray(meta["tags"]) ? meta["tags"] : void 0
19725
19860
  };
19726
19861
  }
@@ -21821,7 +21956,7 @@ var ProactiveCommunicator = class {
21821
21956
  }
21822
21957
  };
21823
21958
  }
21824
- async _handleOutboundMessage(effectorName, request, commands) {
21959
+ async _handleOutboundMessage(effectorName2, request, commands) {
21825
21960
  const targetEntityId = request.targetEntityId ?? request.parameters?.targetEntityId;
21826
21961
  const targetEntityName = request.parameters?.targetEntityName ?? targetEntityId ?? "them";
21827
21962
  const rawMessages = request.parameters?.messages;
@@ -21829,7 +21964,7 @@ var ProactiveCommunicator = class {
21829
21964
  if (!targetEntityId) {
21830
21965
  return {
21831
21966
  success: false,
21832
- description: `You want to ${effectorName} but there is no one specific to reach out to.`,
21967
+ description: `You want to ${effectorName2} but there is no one specific to reach out to.`,
21833
21968
  commands,
21834
21969
  feedback: {
21835
21970
  outcomeQuality: 0,
@@ -21841,7 +21976,7 @@ var ProactiveCommunicator = class {
21841
21976
  if (bubbles.length === 0) {
21842
21977
  return {
21843
21978
  success: false,
21844
- description: `You wanted to ${effectorName} ${targetEntityName} but didn't write anything.`,
21979
+ description: `You wanted to ${effectorName2} ${targetEntityName} but didn't write anything.`,
21845
21980
  commands,
21846
21981
  feedback: { outcomeQuality: 0, surprise: 0.1, lessons: ["Provide a messages array with the actual words."] }
21847
21982
  };
@@ -21852,12 +21987,12 @@ var ProactiveCommunicator = class {
21852
21987
  const isAck = request.parameters?.isAck ?? false;
21853
21988
  const outboxMessageIds = [];
21854
21989
  bubbles.forEach((bubble, i) => {
21855
- logger.info(`[communication] pushing to outbox: ${effectorName} \u2192 ${targetEntityId} "${bubble.slice(0, 80)}"`);
21990
+ logger.info(`[communication] pushing to outbox: ${effectorName2} \u2192 ${targetEntityId} "${bubble.slice(0, 80)}"`);
21856
21991
  outboxMessageIds.push(this._writer.enqueue({
21857
21992
  targetEntityId,
21858
21993
  targetEntityName,
21859
21994
  content: bubble,
21860
- effectorName,
21995
+ effectorName: effectorName2,
21861
21996
  replyToMessageId
21862
21997
  }, `-${i}`));
21863
21998
  });
@@ -21872,7 +22007,7 @@ var ProactiveCommunicator = class {
21872
22007
  targetEntityName,
21873
22008
  messageCount: bubbles.length,
21874
22009
  preview: bubbles[0]?.slice(0, 100) ?? "",
21875
- effectorName,
22010
+ effectorName: effectorName2,
21876
22011
  tick: deliveryTick,
21877
22012
  delivered: false,
21878
22013
  isAck,
@@ -21950,28 +22085,40 @@ var InstructionIntake = class {
21950
22085
 
21951
22086
  // src/cognition/agency/schemas/external.ts
21952
22087
  var DEFAULT_EXTERNAL_COST = 0.15;
22088
+ var clamp = (n, lo, hi) => n < lo ? lo : n > hi ? hi : n;
21953
22089
  function externalSchemas(effectors) {
21954
22090
  const seen = /* @__PURE__ */ new Set();
21955
22091
  const out = [];
21956
- for (const name of effectors ?? []) {
22092
+ for (const decl of effectors ?? []) {
22093
+ const name = typeof decl === "string" ? decl : decl?.name;
21957
22094
  if (typeof name !== "string" || name.length === 0) continue;
21958
22095
  if (EXPLICIT_EFFECTORS.has(name)) continue;
21959
22096
  if (INNATE_SCHEMA_BY_ID.has(name)) continue;
21960
22097
  if (seen.has(name)) continue;
21961
22098
  seen.add(name);
22099
+ const meta = typeof decl === "string" ? null : decl;
22100
+ const binds = meta?.binds === "entity" ? "entity" : meta?.binds === "object" ? "object" : "none";
22101
+ const tags = [.../* @__PURE__ */ new Set([...meta?.tags ?? [], "external", "host"])];
21962
22102
  out.push({
21963
22103
  id: name,
21964
22104
  kind: "primitive",
21965
22105
  source: "external",
21966
- cost: DEFAULT_EXTERNAL_COST,
21967
- binds: "none",
21968
- baseValence: 0,
21969
- tags: ["external", "host"]
22106
+ cost: typeof meta?.cost === "number" ? clamp(meta.cost, 0, 1) : DEFAULT_EXTERNAL_COST,
22107
+ binds,
22108
+ baseValence: typeof meta?.valence === "number" ? clamp(meta.valence, -1, 1) : 0,
22109
+ ...meta?.preconditions ? { preconditions: meta.preconditions } : {},
22110
+ ...meta?.description ? { description: meta.description } : {},
22111
+ tags
21970
22112
  });
21971
22113
  }
21972
22114
  return out;
21973
22115
  }
21974
22116
 
22117
+ // src/cognition/agency/types.ts
22118
+ function effectorName(d) {
22119
+ return typeof d === "string" ? d : d.name;
22120
+ }
22121
+
21975
22122
  // src/cognition/config.mirror.entities.ts
21976
22123
  function buildEngineConfigEntities(config, executiveInterval) {
21977
22124
  const selfBelonging = parseFloat(process.env.WILL_SELF_BELONGING ?? "0.35");
@@ -22529,7 +22676,7 @@ function assembleMind(willId, config) {
22529
22676
  const profile = config.profile ? resolveProfile(config.profile) : void 0;
22530
22677
  const idGuard = validateWillIdentity({
22531
22678
  identity: config.identity,
22532
- effectors: Array.isArray(config.allowedGenericEffectors) ? config.allowedGenericEffectors : profile?.effectors ?? null,
22679
+ effectors: (Array.isArray(config.allowedGenericEffectors) ? config.allowedGenericEffectors : profile?.effectors ?? null)?.map(effectorName) ?? null,
22533
22680
  profileContext: profile?.context
22534
22681
  });
22535
22682
  if (!idGuard.ok)
@@ -22609,7 +22756,8 @@ function _constructCognition({ simulation, willId, config, randomSeed, executive
22609
22756
  dreamSimulator.attachConsolidator(episodicConsolidator);
22610
22757
  const goalManager = new GoalManager();
22611
22758
  const resolvedEffectors = Array.isArray(config.allowedGenericEffectors) ? config.allowedGenericEffectors : profile?.effectors ?? null;
22612
- const accessGrants = new AccessGrants(resolvedEffectors);
22759
+ const resolvedEffectorNames = resolvedEffectors?.map(effectorName) ?? null;
22760
+ const accessGrants = new AccessGrants(resolvedEffectorNames);
22613
22761
  const executiveEngine = new ExecutiveEngine({ executiveInterval, cooldownTicks: 5 });
22614
22762
  executiveEngine.willId = willId;
22615
22763
  executiveEngine.modelId = resolveModelId(
@@ -24718,6 +24866,7 @@ var effectorController = class {
24718
24866
  parameters: payload.parameters ?? {},
24719
24867
  targetEntityId: payload.targetEntityId,
24720
24868
  reasoning: payload.reasoning ?? "",
24869
+ ...typeof payload.description === "string" ? { description: payload.description } : {},
24721
24870
  tick: payload.tick ?? 0,
24722
24871
  timestamp: Date.now()
24723
24872
  });
@@ -25561,6 +25710,19 @@ var WillStem = class {
25561
25710
  setAllowedEffectors(id, effectors) {
25562
25711
  this._effector.setAllowed(this._get(id), effectors);
25563
25712
  }
25713
+ /**
25714
+ * Register a host effector on a *running* Will (post-create `.effector()`).
25715
+ * Builds its external schema and adds it to the live repertoire so the Will
25716
+ * can actually perceive + enact it — a grant alone only gates; without the
25717
+ * schema the ability could never be afforded. Comms names are no-ops here
25718
+ * (governed by AccessGrants). This is a runtime mutation, like a grant change;
25719
+ * the deterministic/replayable path is declaring effectors at create time.
25720
+ */
25721
+ registerEffector(id, declaration) {
25722
+ const repertoire = this._get(id).cognition.schemaRepertoire;
25723
+ for (const schema of externalSchemas([declaration]))
25724
+ repertoire.registerExternal(schema);
25725
+ }
25564
25726
  /**
25565
25727
  * Called by the host/WorldInterface after executing a host-owned effector.
25566
25728
  * `invocationId` is the correlation handle the host echoed (the awaiting
@@ -26015,6 +26177,7 @@ var SocketIoTransport = class {
26015
26177
  };
26016
26178
 
26017
26179
  // src/sdk/will.ts
26180
+ var AFFECT_EPSILON = 0.02;
26018
26181
  var Will = class _Will {
26019
26182
  /** The underlying WillStem — drop here for the full contract. */
26020
26183
  stem;
@@ -26022,9 +26185,15 @@ var Will = class _Will {
26022
26185
  id;
26023
26186
  name;
26024
26187
  _effectors = /* @__PURE__ */ new Map();
26188
+ /** Rich declarations for create-time effectors → seed the affordance repertoire. */
26189
+ _effectorDecls = /* @__PURE__ */ new Map();
26025
26190
  _messageHandlers = /* @__PURE__ */ new Set();
26026
26191
  _stateHandlers = /* @__PURE__ */ new Set();
26192
+ _effectorHandlers = /* @__PURE__ */ new Set();
26193
+ _emotionHandlers = /* @__PURE__ */ new Set();
26027
26194
  _errorHandlers = /* @__PURE__ */ new Set();
26195
+ _utteranceWaiters = /* @__PURE__ */ new Set();
26196
+ _lastAffect = null;
26028
26197
  _unsub = null;
26029
26198
  constructor(stem, id, name) {
26030
26199
  this.stem = stem;
@@ -26036,8 +26205,8 @@ var Will = class _Will {
26036
26205
  const id = opts.id ?? `${slug(opts.name)}-${Math.random().toString(36).slice(2, 8)}`;
26037
26206
  const stem = new WillStem();
26038
26207
  const will = new _Will(stem, id, opts.name);
26039
- for (const [name, handler] of Object.entries(opts.effectors ?? {}))
26040
- will._effectors.set(name, handler);
26208
+ for (const [name, entry] of Object.entries(opts.effectors ?? {}))
26209
+ will._register(name, entry);
26041
26210
  await stem.createWill(will._buildConfig(id, opts));
26042
26211
  will._attach();
26043
26212
  return will;
@@ -26051,8 +26220,8 @@ var Will = class _Will {
26051
26220
  const id = opts.id ?? `${slug(opts.name)}-${Math.random().toString(36).slice(2, 8)}`;
26052
26221
  const stem = new WillStem();
26053
26222
  const will = new _Will(stem, id, opts.name);
26054
- for (const [name, handler] of Object.entries(opts.effectors ?? {}))
26055
- will._effectors.set(name, handler);
26223
+ for (const [name, entry] of Object.entries(opts.effectors ?? {}))
26224
+ will._register(name, entry);
26056
26225
  await stem.createWill(
26057
26226
  will._buildConfig(id, { ...opts, identity: { prompt: "", ...opts.identity } }),
26058
26227
  true
@@ -26062,36 +26231,92 @@ var Will = class _Will {
26062
26231
  will._attach();
26063
26232
  return will;
26064
26233
  }
26065
- // ── Talking ────────────────────────────────────────────────
26234
+ // ── Perceiving ─────────────────────────────────────────────
26066
26235
  /**
26067
- * Speak to the Will as the default user. The reply is asynchronous it
26068
- * arrives on the `message` event once the Will has reasoned about it.
26236
+ * Deliver a stimulus into the Will's sensory field. This is the one true
26237
+ * intake `say`/`tell` are sugar over it. It returns once the stimulus is
26238
+ * *delivered*, NOT once the Will has responded: a response (if any) is a
26239
+ * projection that arrives later on the `message` event, or via
26240
+ * `nextUtterance()`. The Will may also stay silent — that is not an error.
26069
26241
  */
26242
+ async perceive(stimulus) {
26243
+ const from = stimulus.from ?? "user";
26244
+ await this.stem.ingestText(this.id, {
26245
+ kind: "text",
26246
+ entityId: from,
26247
+ threadId: stimulus.thread ?? from,
26248
+ content: stimulus.text,
26249
+ speakerName: stimulus.speaker ?? (from === "user" ? "You" : from)
26250
+ });
26251
+ }
26252
+ /** Perceive from the default user. Sugar over `perceive`. */
26070
26253
  async say(text) {
26071
- return this.tell("user", "You", text);
26254
+ return this.perceive({ text, from: "user", speaker: "You" });
26072
26255
  }
26073
- /** Speak as a specific interlocutor (multi-party conversations). */
26256
+ /** Perceive from a specific interlocutor (multi-party). Sugar over `perceive`. */
26074
26257
  async tell(entityId, speakerName, text) {
26075
- await this.stem.ingestText(this.id, {
26076
- kind: "text",
26077
- entityId,
26078
- threadId: entityId,
26079
- content: text,
26080
- speakerName
26258
+ return this.perceive({ text, from: entityId, speaker: speakerName });
26259
+ }
26260
+ /**
26261
+ * Await the Will's *next spontaneous utterance* — a thin, honest adapter over
26262
+ * the `message` projection stream for request/response callers. Resolves with
26263
+ * the message, or `null` if the Will stays silent within `within` ms (default
26264
+ * 5000). `null` is a real outcome — the Will chose not to speak — not a
26265
+ * failure. Pass `to` to only accept an utterance addressed to that entity.
26266
+ *
26267
+ * await will.perceive( { from: 'ada', text: 'Hi!' } )
26268
+ * const reply = await will.nextUtterance( { to: 'ada', within: 3000 } )
26269
+ * // reply is a WillMessage, or null if Ada got the silent treatment.
26270
+ */
26271
+ nextUtterance(opts = {}) {
26272
+ return new Promise((resolve) => {
26273
+ const timer = setTimeout(() => {
26274
+ this._utteranceWaiters.delete(waiter);
26275
+ resolve(null);
26276
+ }, opts.within ?? 5e3);
26277
+ timer.unref?.();
26278
+ const waiter = { to: opts.to, resolve, timer };
26279
+ this._utteranceWaiters.add(waiter);
26081
26280
  });
26082
26281
  }
26083
26282
  // ── Abilities ──────────────────────────────────────────────
26084
26283
  /**
26085
- * Register an ability the Will can choose to enact. When the Will decides to
26086
- * use `name`, your handler runs with the arguments it chose; the return value
26087
- * is fed back as the outcome (closing the reafference loop that lets the Will
26088
- * learn the ability). Registering makes the effector available immediately.
26089
- */
26090
- effector(name, handler) {
26091
- this._effectors.set(name, handler);
26284
+ * Register an ability the Will can choose to enact, at runtime. `entry` is a
26285
+ * bare handler or a full spec (`{ handler, description?, cost?, valence?,
26286
+ * preconditions?, binds?, tags? }`). When the Will decides to use `name`, your
26287
+ * handler runs with the arguments it chose; the return value feeds back as the
26288
+ * outcome (the reafference loop that lets the Will learn the ability).
26289
+ *
26290
+ * The ability's schema is added to the live repertoire so it can actually be
26291
+ * *afforded* immediately (a grant alone only gates), then granted. Note: this
26292
+ * is a runtime mutation — the deterministic/replayable path is declaring
26293
+ * effectors in `create()`'s `effectors` map.
26294
+ */
26295
+ effector(name, entry) {
26296
+ this._register(name, entry);
26297
+ this.stem.registerEffector(this.id, this._effectorDecls.get(name));
26092
26298
  this.stem.setAllowedEffectors(this.id, [...COMMUNICATION, ...this._effectors.keys()]);
26093
26299
  return this;
26094
26300
  }
26301
+ /** Split an effectors-map entry into a handler + an EffectorDeclaration. */
26302
+ _register(name, entry) {
26303
+ if (typeof entry === "function") {
26304
+ this._effectors.set(name, entry);
26305
+ this._effectorDecls.set(name, name);
26306
+ return;
26307
+ }
26308
+ this._effectors.set(name, entry.handler);
26309
+ const hasMeta = entry.description !== void 0 || entry.cost !== void 0 || entry.valence !== void 0 || entry.preconditions !== void 0 || entry.binds !== void 0 || entry.tags !== void 0;
26310
+ this._effectorDecls.set(name, hasMeta ? {
26311
+ name,
26312
+ ...entry.description !== void 0 ? { description: entry.description } : {},
26313
+ ...entry.cost !== void 0 ? { cost: entry.cost } : {},
26314
+ ...entry.valence !== void 0 ? { valence: entry.valence } : {},
26315
+ ...entry.preconditions !== void 0 ? { preconditions: entry.preconditions } : {},
26316
+ ...entry.binds !== void 0 ? { binds: entry.binds } : {},
26317
+ ...entry.tags !== void 0 ? { tags: entry.tags } : {}
26318
+ } : name);
26319
+ }
26095
26320
  // ── Introspection ──────────────────────────────────────────
26096
26321
  /** A compact snapshot of the mind's current inner state. */
26097
26322
  state() {
@@ -26114,9 +26339,22 @@ var Will = class _Will {
26114
26339
  };
26115
26340
  }
26116
26341
  on(event, handler) {
26117
- if (event === "message") this._messageHandlers.add(handler);
26118
- else if (event === "state") this._stateHandlers.add(handler);
26119
- else this._errorHandlers.add(handler);
26342
+ switch (event) {
26343
+ case "message":
26344
+ this._messageHandlers.add(handler);
26345
+ break;
26346
+ case "state":
26347
+ this._stateHandlers.add(handler);
26348
+ break;
26349
+ case "effector":
26350
+ this._effectorHandlers.add(handler);
26351
+ break;
26352
+ case "emotion":
26353
+ this._emotionHandlers.add(handler);
26354
+ break;
26355
+ default:
26356
+ this._errorHandlers.add(handler);
26357
+ }
26120
26358
  return this;
26121
26359
  }
26122
26360
  // ── Lifecycle ──────────────────────────────────────────────
@@ -26127,9 +26365,18 @@ var Will = class _Will {
26127
26365
  this.stem.resumeWill(this.id);
26128
26366
  }
26129
26367
  /**
26130
- * Distil the mind into a portable PMA artifact and archive it. The returned
26131
- * snapshot restores the same self via `Will.wake()` across a restart, a
26132
- * fork, or a machine boundary.
26368
+ * Checkpoint the living mind into a portable PMA artifact NON-destructive.
26369
+ * The Will keeps ticking; the snapshot is a point-in-time copy you can archive
26370
+ * or wake elsewhere. Use this for periodic saves; use `hibernate()` to sleep.
26371
+ */
26372
+ async save() {
26373
+ return this.stem.distillPMA(this.id);
26374
+ }
26375
+ /**
26376
+ * Distil the mind into a portable PMA artifact and archive it — DESTRUCTIVE:
26377
+ * the tick loop stops (the Will sleeps). The returned snapshot restores the
26378
+ * same self via `Will.wake()` — across a restart, a fork, or a machine
26379
+ * boundary. For a copy that leaves the Will running, use `save()`.
26133
26380
  */
26134
26381
  async hibernate() {
26135
26382
  const pma = this.stem.distillPMA(this.id);
@@ -26138,6 +26385,11 @@ var Will = class _Will {
26138
26385
  }
26139
26386
  /** Tear the Will down (its tick loop stops; state is discarded unless persisted). */
26140
26387
  async stop() {
26388
+ for (const w of this._utteranceWaiters) {
26389
+ clearTimeout(w.timer);
26390
+ w.resolve(null);
26391
+ }
26392
+ this._utteranceWaiters.clear();
26141
26393
  this._unsub?.();
26142
26394
  this._unsub = null;
26143
26395
  await this.stem.archiveWill(this.id);
@@ -26160,7 +26412,7 @@ var Will = class _Will {
26160
26412
  persistentMemory: opts.persist ?? false,
26161
26413
  snapshotInterval: 100,
26162
26414
  tickIntervalMs: opts.tickMs ?? 1e3,
26163
- allowedGenericEffectors: [...COMMUNICATION, ...this._effectors.keys()],
26415
+ allowedGenericEffectors: [...COMMUNICATION, ...this._effectorDecls.values()],
26164
26416
  initialGoals: opts.initialGoals ?? [],
26165
26417
  ...opts.seed !== void 0 ? { randomSeed: opts.seed, clock: { fixedDeltaMs: 1e3, startTime: 0 } } : {}
26166
26418
  };
@@ -26175,13 +26427,20 @@ var Will = class _Will {
26175
26427
  } catch {
26176
26428
  }
26177
26429
  }
26178
- for (const inv of invocations)
26430
+ for (const inv of invocations) {
26431
+ this._emitEffectorAct({ name: inv.effectorName, args: inv.parameters, reasoning: inv.reasoning, to: inv.targetEntityId });
26179
26432
  void this._runEffector(inv);
26180
- if (this._stateHandlers.size > 0) {
26181
- const s = this.state();
26182
- for (const h of this._stateHandlers) try {
26183
- h(s);
26184
- } catch {
26433
+ }
26434
+ if (this._stateHandlers.size > 0 || this._emotionHandlers.size > 0) {
26435
+ try {
26436
+ const s = this.state();
26437
+ for (const h of this._stateHandlers) try {
26438
+ h(s);
26439
+ } catch {
26440
+ }
26441
+ if (this._emotionHandlers.size > 0) this._maybeEmitAffect(s.metrics.valence, s.metrics.arousal);
26442
+ } catch (e) {
26443
+ this._emitError(e);
26185
26444
  }
26186
26445
  }
26187
26446
  }) ?? null;
@@ -26196,7 +26455,11 @@ var Will = class _Will {
26196
26455
  return;
26197
26456
  }
26198
26457
  try {
26199
- const raw = await handler(inv.parameters, { reasoning: inv.reasoning, targetEntityId: inv.targetEntityId });
26458
+ const raw = await handler(inv.parameters, {
26459
+ reasoning: inv.reasoning,
26460
+ targetEntityId: inv.targetEntityId,
26461
+ ...inv.description ? { description: inv.description } : {}
26462
+ });
26200
26463
  const result = typeof raw === "string" ? { success: true, description: raw } : raw;
26201
26464
  this.stem.confirmEffectorExecution(this.id, inv.decisionRecordId, result);
26202
26465
  } catch (err) {
@@ -26213,6 +26476,31 @@ var Will = class _Will {
26213
26476
  } catch (e) {
26214
26477
  this._emitError(e);
26215
26478
  }
26479
+ if (this._utteranceWaiters.size > 0) {
26480
+ for (const w of [...this._utteranceWaiters]) {
26481
+ if (w.to !== void 0 && w.to !== m.to) continue;
26482
+ clearTimeout(w.timer);
26483
+ this._utteranceWaiters.delete(w);
26484
+ w.resolve(m);
26485
+ }
26486
+ }
26487
+ }
26488
+ _emitEffectorAct(a) {
26489
+ for (const h of this._effectorHandlers) try {
26490
+ h(a);
26491
+ } catch (e) {
26492
+ this._emitError(e);
26493
+ }
26494
+ }
26495
+ _maybeEmitAffect(valence, arousal) {
26496
+ const last = this._lastAffect;
26497
+ if (last && Math.abs(last.valence - valence) < AFFECT_EPSILON && Math.abs(last.arousal - arousal) < AFFECT_EPSILON) return;
26498
+ this._lastAffect = { valence, arousal };
26499
+ for (const h of this._emotionHandlers) try {
26500
+ h({ valence, arousal });
26501
+ } catch (e) {
26502
+ this._emitError(e);
26503
+ }
26216
26504
  }
26217
26505
  _emitError(e) {
26218
26506
  for (const h of this._errorHandlers) try {