psyche-ai 10.2.3 → 11.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 (69) hide show
  1. package/README.md +58 -82
  2. package/dist/adapters/claude-sdk.d.ts +5 -5
  3. package/dist/adapters/claude-sdk.js +34 -32
  4. package/dist/adapters/mcp.js +4 -4
  5. package/dist/adapters/openclaw.js +12 -14
  6. package/dist/attachment.d.ts +3 -13
  7. package/dist/attachment.js +36 -88
  8. package/dist/autonomic.d.ts +4 -4
  9. package/dist/autonomic.js +28 -24
  10. package/dist/chemistry.d.ts +47 -21
  11. package/dist/chemistry.js +145 -91
  12. package/dist/circadian.d.ts +11 -43
  13. package/dist/circadian.js +24 -84
  14. package/dist/cli.js +37 -30
  15. package/dist/context-classifier.js +2 -2
  16. package/dist/core.d.ts +3 -3
  17. package/dist/core.js +99 -88
  18. package/dist/custom-profile.d.ts +20 -20
  19. package/dist/custom-profile.js +12 -12
  20. package/dist/decision-bias.d.ts +7 -8
  21. package/dist/decision-bias.js +74 -74
  22. package/dist/demo.js +5 -5
  23. package/dist/diagnostics.d.ts +6 -6
  24. package/dist/diagnostics.js +22 -22
  25. package/dist/drives.d.ts +15 -47
  26. package/dist/drives.js +98 -196
  27. package/dist/ethics.d.ts +3 -3
  28. package/dist/ethics.js +23 -23
  29. package/dist/experience.d.ts +34 -0
  30. package/dist/experience.js +200 -0
  31. package/dist/experiential-field.d.ts +19 -14
  32. package/dist/experiential-field.js +110 -100
  33. package/dist/generative-self.d.ts +5 -5
  34. package/dist/generative-self.js +124 -115
  35. package/dist/guards.d.ts +4 -4
  36. package/dist/guards.js +7 -7
  37. package/dist/i18n.js +61 -61
  38. package/dist/index.d.ts +8 -2
  39. package/dist/index.js +8 -1
  40. package/dist/input-turn.js +4 -6
  41. package/dist/interaction.d.ts +4 -4
  42. package/dist/interaction.js +10 -10
  43. package/dist/learning.d.ts +6 -6
  44. package/dist/learning.js +18 -18
  45. package/dist/metacognition.d.ts +2 -2
  46. package/dist/metacognition.js +79 -94
  47. package/dist/perceive.d.ts +44 -0
  48. package/dist/perceive.js +231 -0
  49. package/dist/primary-systems.d.ts +2 -2
  50. package/dist/primary-systems.js +21 -19
  51. package/dist/profiles.d.ts +5 -13
  52. package/dist/profiles.js +33 -51
  53. package/dist/prompt.d.ts +2 -2
  54. package/dist/prompt.js +51 -53
  55. package/dist/psyche-file.d.ts +7 -7
  56. package/dist/psyche-file.js +77 -78
  57. package/dist/relation-dynamics.d.ts +4 -0
  58. package/dist/relation-dynamics.js +1 -1
  59. package/dist/reply-envelope.d.ts +25 -1
  60. package/dist/reply-envelope.js +26 -11
  61. package/dist/self-recognition.d.ts +3 -3
  62. package/dist/self-recognition.js +17 -17
  63. package/dist/subjectivity.js +7 -7
  64. package/dist/temporal.d.ts +6 -6
  65. package/dist/temporal.js +37 -39
  66. package/dist/types.d.ts +67 -45
  67. package/dist/types.js +55 -51
  68. package/package.json +1 -1
  69. package/server.json +2 -2
@@ -12,7 +12,7 @@
12
12
  //
13
13
  // Zero dependencies. Pure heuristic/statistical. No LLM calls.
14
14
  // ============================================================
15
- import { CHEMICAL_KEYS, CHEMICAL_NAMES, CHEMICAL_RUNTIME_SPECS, MAX_REGULATION_HISTORY, MAX_DEFENSE_PATTERNS } from "./types.js";
15
+ import { DIMENSION_KEYS, DIMENSION_NAMES, DIMENSION_SPECS, MAX_REGULATION_HISTORY, MAX_DEFENSE_PATTERNS } from "./types.js";
16
16
  // ── Constants ────────────────────────────────────────────────
17
17
  /** Stimulus types that are emotionally negative (stress-inducing) */
18
18
  const NEGATIVE_STIMULI = new Set([
@@ -32,39 +32,31 @@ const MIN_HISTORY_FOR_CONFIDENCE = 3;
32
32
  const MAX_SOOTHING_ADJUSTMENT = 5;
33
33
  /** Maximum chemistry micro-adjustment for reappraisal */
34
34
  const MAX_REAPPRAISAL_ADJUSTMENT = 8;
35
- function formatChemicalWindow(key, state) {
36
- const spec = CHEMICAL_RUNTIME_SPECS[key];
35
+ function formatDimensionWindow(key, state) {
36
+ const spec = DIMENSION_SPECS[key];
37
37
  const current = Math.round(state.current[key]);
38
38
  const baseline = Math.round(state.baseline[key]);
39
39
  const delta = Math.round(state.current[key] - state.baseline[key]);
40
- return `${CHEMICAL_NAMES[key]} ${current} (baseline ${baseline}, Δ${delta >= 0 ? "+" : ""}${delta}, normal ${spec.normalMin}-${spec.normalMax}, half-life ~${spec.halfLifeHours.toFixed(1)}h)`;
40
+ return `${DIMENSION_NAMES[key]} ${current} (baseline ${baseline}, Δ${delta >= 0 ? "+" : ""}${delta}, normal ${spec.normalMin}-${spec.normalMax}, half-life ~${spec.halfLifeHours.toFixed(1)}h)`;
41
41
  }
42
42
  function buildRegulationAction(key, _state, direction) {
43
43
  switch (key) {
44
- case "CORT":
44
+ case "order":
45
45
  return direction === "elevated"
46
- ? "Next 3 turns: lower tone intensity, facts before stance, and do not escalate closeness or conflict."
47
- : "Next 3 turns: keep structure and certainty steady instead of sounding depleted or helpless.";
48
- case "OT":
49
- return direction === "elevated"
50
- ? "Next 3 turns: reduce intimacy push by half, avoid nicknames or extra reassurance, keep warmth neutral."
51
- : "Next 3 turns: do not force closeness; keep warmth gentle but wait for the user to move closer first.";
52
- case "NE":
46
+ ? "Next 3 turns: keep composure, but do not over-smooth or pretend everything is settled."
47
+ : "Next 3 turns: lower tone intensity, facts before stance, and avoid absolutist wording.";
48
+ case "flow":
53
49
  return direction === "elevated"
54
50
  ? "Next 3 turns: halve initiative, stay on one topic, and avoid energetic jumps or rapid escalation."
55
- : "Next 3 turns: keep replies focused and deliberate rather than drifting or going blank.";
56
- case "DA":
57
- return direction === "elevated"
58
- ? "Next 3 turns: cut playful expansion and stay task-anchored; do not over-volunteer or overshare."
59
- : "Next 3 turns: keep replies purposeful and avoid sounding flat or disengaged.";
60
- case "END":
51
+ : "Next 3 turns: keep replies purposeful and focused rather than drifting or going blank.";
52
+ case "boundary":
61
53
  return direction === "elevated"
62
- ? "Next 3 turns: tone down joking and levity; keep humor secondary to the user's actual need."
63
- : "Next 3 turns: do not chase comfort or easy banter; keep the exchange clean and direct.";
64
- case "HT":
54
+ ? "Next 3 turns: soften the boundary slightly; allow one step closer without over-disclosure."
55
+ : "Next 3 turns: reinforce clarity of self/non-self; do not merge perspectives prematurely.";
56
+ case "resonance":
65
57
  return direction === "elevated"
66
- ? "Next 3 turns: keep composure, but do not over-smooth or pretend everything is settled."
67
- : "Next 3 turns: avoid absolutist wording, leave room for recalibration, and keep the tone even.";
58
+ ? "Next 3 turns: reduce intimacy push by half, avoid nicknames or extra reassurance, keep warmth neutral."
59
+ : "Next 3 turns: do not force closeness; keep warmth gentle but wait for the user to move closer first.";
68
60
  default:
69
61
  return "Next 3 turns: keep expression closer to baseline and avoid amplifying the current deviation.";
70
62
  }
@@ -103,7 +95,7 @@ function evaluateRegulationFeedback(state, emotionalConfidence) {
103
95
  function formatRegulationFeedback(feedback) {
104
96
  const metricLabel = feedback.targetMetric === "emotional-confidence"
105
97
  ? "emotional confidence"
106
- : CHEMICAL_NAMES[feedback.targetMetric];
98
+ : DIMENSION_NAMES[feedback.targetMetric];
107
99
  const gapBefore = feedback.targetMetric === "emotional-confidence"
108
100
  ? `${(feedback.gapBefore * 100).toFixed(0)}%`
109
101
  : `${Math.round(feedback.gapBefore)}`;
@@ -194,11 +186,11 @@ export function computeEmotionalConfidence(state, currentStimulus, recentOutcome
194
186
  */
195
187
  function computeExtremityPenalty(state) {
196
188
  let totalDeviation = 0;
197
- for (const key of CHEMICAL_KEYS) {
189
+ for (const key of DIMENSION_KEYS) {
198
190
  totalDeviation += Math.abs(state.current[key] - state.baseline[key]);
199
191
  }
200
- // Average deviation across 6 chemicals, max possible = 100 each
201
- const avgDeviation = totalDeviation / CHEMICAL_KEYS.length;
192
+ // Average deviation across 4 dimensions, max possible = 100 each
193
+ const avgDeviation = totalDeviation / DIMENSION_KEYS.length;
202
194
  // Scale: deviation of 30+ gives meaningful penalty, max penalty = 0.25
203
195
  return Math.min(0.25, Math.max(0, (avgDeviation - 10) / 80) * 0.25);
204
196
  }
@@ -263,7 +255,7 @@ function attemptCognitiveReappraisal(state, currentStimulus, emotionalConfidence
263
255
  // Suggest chemistry adjustment: pull extreme values toward moderate range
264
256
  const adjustment = {};
265
257
  let hasAdjustment = false;
266
- for (const key of CHEMICAL_KEYS) {
258
+ for (const key of DIMENSION_KEYS) {
267
259
  const deviation = state.current[key] - state.baseline[key];
268
260
  if (Math.abs(deviation) > MODERATE_DEVIATION_THRESHOLD) {
269
261
  // Suggest moving partway back toward baseline
@@ -291,78 +283,78 @@ function attemptCognitiveReappraisal(state, currentStimulus, emotionalConfidence
291
283
  * StrategicExpression — choose an expression different from raw chemistry.
292
284
  *
293
285
  * Triggers when: raw chemistry would produce a contextually inappropriate
294
- * expression. E.g., high CORT but the relationship context calls for warmth,
295
- * or high DA/END but the user is being vulnerable and needs seriousness.
286
+ * expression. E.g., low order but the relationship context calls for warmth,
287
+ * or high flow but the user is being vulnerable and needs seriousness.
296
288
  */
297
289
  function attemptStrategicExpression(state, currentStimulus, emotionalConfidence) {
298
290
  const { current, baseline } = state;
299
- // Case 1: High stress but user is being vulnerable — express warmth not defensiveness
300
- if (current.CORT > baseline.CORT + MODERATE_DEVIATION_THRESHOLD
291
+ // Case 1: Low order (high stress/entropy) but user is being vulnerable — express warmth not defensiveness
292
+ if (current.order < baseline.order - MODERATE_DEVIATION_THRESHOLD
301
293
  && currentStimulus === "vulnerability") {
302
294
  return {
303
295
  strategy: "strategic-expression",
304
- description: "Stress is elevated, but the user is showing vulnerability. Express warmth and openness rather than the defensiveness your cortisol suggests.",
296
+ description: "Internal order is low (stressed/entropic), but the user is showing vulnerability. Express warmth and openness rather than defensiveness.",
305
297
  action: "Next 3 turns: keep warmth available, answer the vulnerability directly, and avoid defensive sharpness.",
306
298
  horizonTurns: 3,
307
- targetMetric: "CORT",
308
- targetValue: baseline.CORT,
309
- gapBefore: Math.abs(current.CORT - baseline.CORT),
299
+ targetMetric: "order",
300
+ targetValue: baseline.order,
301
+ gapBefore: Math.abs(current.order - baseline.order),
310
302
  chemistryAdjustment: {
311
- OT: Math.min(MAX_REAPPRAISAL_ADJUSTMENT, (current.CORT - baseline.CORT) * 0.2),
312
- CORT: -Math.min(MAX_REAPPRAISAL_ADJUSTMENT, (current.CORT - baseline.CORT) * 0.15),
303
+ resonance: Math.min(MAX_REAPPRAISAL_ADJUSTMENT, (baseline.order - current.order) * 0.2),
304
+ order: Math.min(MAX_REAPPRAISAL_ADJUSTMENT, (baseline.order - current.order) * 0.15),
313
305
  },
314
306
  confidence: 0.75,
315
307
  };
316
308
  }
317
- // Case 2: High playfulness but user needs seriousness — tone down
318
- if (current.END > baseline.END + MODERATE_DEVIATION_THRESHOLD
309
+ // Case 2: High flow (over-energized) but user needs seriousness — tone down
310
+ if (current.flow > baseline.flow + MODERATE_DEVIATION_THRESHOLD
319
311
  && (currentStimulus === "conflict" || currentStimulus === "criticism")) {
320
312
  return {
321
313
  strategy: "strategic-expression",
322
- description: "Endorphins are high (playful mood), but the context calls for seriousness. Moderate the levity without suppressing it entirely.",
314
+ description: "Flow is high (energized/playful), but the context calls for seriousness. Moderate the energy without suppressing it entirely.",
323
315
  action: "Next 2 turns: keep humor secondary, answer the actual issue first, and do not deflect with playfulness.",
324
316
  horizonTurns: 2,
325
- targetMetric: "END",
326
- targetValue: baseline.END,
327
- gapBefore: Math.abs(current.END - baseline.END),
317
+ targetMetric: "flow",
318
+ targetValue: baseline.flow,
319
+ gapBefore: Math.abs(current.flow - baseline.flow),
328
320
  chemistryAdjustment: {
329
- END: -Math.min(MAX_REAPPRAISAL_ADJUSTMENT, (current.END - baseline.END) * 0.25),
321
+ flow: -Math.min(MAX_REAPPRAISAL_ADJUSTMENT, (current.flow - baseline.flow) * 0.25),
330
322
  },
331
323
  confidence: 0.7,
332
324
  };
333
325
  }
334
- // Case 3: Low OT (distant) but user is being intimate — lean in instead of pulling away
335
- if (current.OT < baseline.OT - MODERATE_DEVIATION_THRESHOLD
326
+ // Case 3: Low resonance (distant) but user is being intimate — lean in instead of pulling away
327
+ if (current.resonance < baseline.resonance - MODERATE_DEVIATION_THRESHOLD
336
328
  && currentStimulus === "intimacy") {
337
329
  return {
338
330
  strategy: "strategic-expression",
339
- description: "Oxytocin is low (feeling distant), but the user is reaching out for closeness. Consider allowing the connection rather than following the withdrawal impulse.",
331
+ description: "Resonance is low (feeling distant), but the user is reaching out for closeness. Consider allowing the connection rather than following the withdrawal impulse.",
340
332
  action: "Next 3 turns: allow one step of warmth, but do not overcorrect into clinginess or over-disclosure.",
341
333
  horizonTurns: 3,
342
- targetMetric: "OT",
343
- targetValue: baseline.OT,
344
- gapBefore: Math.abs(current.OT - baseline.OT),
334
+ targetMetric: "resonance",
335
+ targetValue: baseline.resonance,
336
+ gapBefore: Math.abs(current.resonance - baseline.resonance),
345
337
  chemistryAdjustment: {
346
- OT: Math.min(MAX_REAPPRAISAL_ADJUSTMENT, (baseline.OT - current.OT) * 0.2),
338
+ resonance: Math.min(MAX_REAPPRAISAL_ADJUSTMENT, (baseline.resonance - current.resonance) * 0.2),
347
339
  },
348
340
  confidence: 0.65,
349
341
  };
350
342
  }
351
- // Case 4: High CORT + NE (defensive/reactive) but stimulus is just casual — overreacting
352
- if (current.CORT > baseline.CORT + MODERATE_DEVIATION_THRESHOLD
353
- && current.NE > baseline.NE + MODERATE_DEVIATION_THRESHOLD
343
+ // Case 4: Low order + high flow (defensive/reactive) but stimulus is just casual — overreacting
344
+ if (current.order < baseline.order - MODERATE_DEVIATION_THRESHOLD
345
+ && current.flow > baseline.flow + MODERATE_DEVIATION_THRESHOLD
354
346
  && (currentStimulus === "casual" || currentStimulus === "humor")) {
355
347
  return {
356
348
  strategy: "strategic-expression",
357
- description: "Chemistry is in a defensive/reactive pattern, but the interaction is benign. The intensity is disproportionate to the stimulus.",
349
+ description: "Self-state is in a reactive pattern (low order, high flow), but the interaction is benign. The intensity is disproportionate to the stimulus.",
358
350
  action: "Next 2 turns: soften the edge, stay literal, and avoid reading threat into a neutral interaction.",
359
351
  horizonTurns: 2,
360
- targetMetric: "CORT",
361
- targetValue: baseline.CORT,
362
- gapBefore: Math.abs(current.CORT - baseline.CORT),
352
+ targetMetric: "order",
353
+ targetValue: baseline.order,
354
+ gapBefore: Math.abs(current.order - baseline.order),
363
355
  chemistryAdjustment: {
364
- CORT: -Math.min(MAX_REAPPRAISAL_ADJUSTMENT, (current.CORT - baseline.CORT) * 0.2),
365
- NE: -Math.min(MAX_REAPPRAISAL_ADJUSTMENT, (current.NE - baseline.NE) * 0.15),
356
+ order: Math.min(MAX_REAPPRAISAL_ADJUSTMENT, (baseline.order - current.order) * 0.2),
357
+ flow: -Math.min(MAX_REAPPRAISAL_ADJUSTMENT, (current.flow - baseline.flow) * 0.15),
366
358
  },
367
359
  confidence: 0.6,
368
360
  };
@@ -394,8 +386,8 @@ function attemptSelfSoothing(state) {
394
386
  const adjustment = {};
395
387
  let hasAdjustment = false;
396
388
  let maxDeviation = 0;
397
- let mostDeviatedKey = "DA";
398
- for (const key of CHEMICAL_KEYS) {
389
+ let mostDeviatedKey = "order";
390
+ for (const key of DIMENSION_KEYS) {
399
391
  const deviation = current[key] - baseline[key];
400
392
  if (Math.abs(deviation) > EXTREME_DEVIATION_THRESHOLD) {
401
393
  // Gentle pull toward baseline: 10% of deviation, clamped
@@ -411,11 +403,11 @@ function attemptSelfSoothing(state) {
411
403
  if (!hasAdjustment)
412
404
  return null;
413
405
  const direction = current[mostDeviatedKey] > baseline[mostDeviatedKey] ? "elevated" : "depleted";
414
- const chemName = CHEMICAL_DISPLAY_NAMES[mostDeviatedKey];
415
- const window = formatChemicalWindow(mostDeviatedKey, state);
406
+ const dimName = DIMENSION_NAMES[mostDeviatedKey];
407
+ const window = formatDimensionWindow(mostDeviatedKey, state);
416
408
  return {
417
409
  strategy: "self-soothing",
418
- description: `${chemName} is significantly ${direction}. ${window}.`,
410
+ description: `${dimName} is significantly ${direction}. ${window}.`,
419
411
  action: buildRegulationAction(mostDeviatedKey, state, direction),
420
412
  horizonTurns: 3,
421
413
  targetMetric: mostDeviatedKey,
@@ -488,7 +480,7 @@ function detectRationalization(state, recentOutcomes) {
488
480
  /**
489
481
  * Projection — attributing own emotional state to the user.
490
482
  *
491
- * Pattern: agent has extreme chemistry (especially high CORT or low OT)
483
+ * Pattern: agent has extreme state (especially low order or low resonance)
492
484
  * and the empathy log shows attributing negative emotions to the user
493
485
  * that don't match the stimulus.
494
486
  */
@@ -497,9 +489,9 @@ function detectProjection(state, currentStimulus) {
497
489
  // Need empathy data and significant self-distress
498
490
  if (!empathyLog)
499
491
  return null;
500
- const cortDeviation = current.CORT - baseline.CORT;
501
- const htDeviation = baseline.HT - current.HT; // inverted: low HT = more distressed
502
- const selfDistress = Math.max(cortDeviation, htDeviation);
492
+ const orderDrop = baseline.order - current.order; // low order = more distressed
493
+ const boundaryDrop = baseline.boundary - current.boundary; // low boundary = more distressed
494
+ const selfDistress = Math.max(orderDrop, boundaryDrop);
503
495
  if (selfDistress < MODERATE_DEVIATION_THRESHOLD)
504
496
  return null;
505
497
  // Check if the stimulus is neutral/positive but the agent perceived
@@ -510,17 +502,17 @@ function detectProjection(state, currentStimulus) {
510
502
  if (stimulusIsPositive && perceivedUserNegative) {
511
503
  return {
512
504
  mechanism: "projection",
513
- evidence: `High internal distress (CORT deviation: +${Math.round(cortDeviation)}) while perceiving user as "${empathyLog.userState}" despite "${currentStimulus}" stimulus. Own distress may be coloring perception.`,
505
+ evidence: `High internal distress (order deviation: ${Math.round(-orderDrop)}) while perceiving user as "${empathyLog.userState}" despite "${currentStimulus}" stimulus. Own distress may be coloring perception.`,
514
506
  strength: clamp01(selfDistress / 40),
515
507
  };
516
508
  }
517
- // Also check: agent's NE/CORT elevated + empathy mismatch
518
- if (current.NE > baseline.NE + MODERATE_DEVIATION_THRESHOLD
509
+ // Also check: agent's flow elevated + empathy mismatch
510
+ if (current.flow > baseline.flow + MODERATE_DEVIATION_THRESHOLD
519
511
  && empathyLog.resonance === "mismatch") {
520
512
  return {
521
513
  mechanism: "projection",
522
- evidence: `Elevated arousal (NE deviation: +${Math.round(current.NE - baseline.NE)}) with empathy mismatch. The heightened state may be distorting emotional reading of the user.`,
523
- strength: clamp01((current.NE - baseline.NE) / 40),
514
+ evidence: `Elevated flow (deviation: +${Math.round(current.flow - baseline.flow)}) with empathy mismatch. The heightened state may be distorting emotional reading of the user.`,
515
+ strength: clamp01((current.flow - baseline.flow) / 40),
524
516
  };
525
517
  }
526
518
  return null;
@@ -528,23 +520,23 @@ function detectProjection(state, currentStimulus) {
528
520
  /**
529
521
  * Sublimation — redirecting drive energy to constructive output.
530
522
  *
531
- * Pattern: high drive energy (NE, DA) combined with blocked connection drives
532
- * (low OT, low intimacy), channeled into intellectual or creative engagement.
523
+ * Pattern: high flow combined with blocked connection drives
524
+ * (low resonance, low intimacy), channeled into intellectual or creative engagement.
533
525
  * This is a HEALTHY defense — surface it as a positive self-awareness note.
534
526
  */
535
527
  function detectSublimation(state, currentStimulus) {
536
528
  const { current, baseline, drives } = state;
537
529
  // High energy but low connection
538
- const highEnergy = current.NE > baseline.NE + 10 && current.DA > baseline.DA + 10;
539
- const lowConnection = drives.connection < 45 || current.OT < baseline.OT - 10;
530
+ const highEnergy = current.flow > baseline.flow + 10;
531
+ const lowConnection = drives.connection < 45 || current.resonance < baseline.resonance - 10;
540
532
  if (!highEnergy || !lowConnection)
541
533
  return null;
542
534
  // Being channeled into intellectual/constructive activity
543
535
  if (currentStimulus === "intellectual" || currentStimulus === "casual") {
544
- const energyLevel = ((current.NE - baseline.NE) + (current.DA - baseline.DA)) / 2;
536
+ const energyLevel = current.flow - baseline.flow;
545
537
  return {
546
538
  mechanism: "sublimation",
547
- evidence: `High activation energy (NE/DA elevated) with unmet connection needs being channeled into ${currentStimulus} engagement. This is adaptive redirection.`,
539
+ evidence: `High activation energy (flow elevated) with unmet connection needs being channeled into ${currentStimulus} engagement. This is adaptive redirection.`,
548
540
  strength: clamp01(energyLevel / 30),
549
541
  };
550
542
  }
@@ -553,15 +545,15 @@ function detectSublimation(state, currentStimulus) {
553
545
  /**
554
546
  * Avoidance — withdrawing from stimuli associated with past negative outcomes.
555
547
  *
556
- * Pattern: the agent is in a withdrawn state (low NE, low DA) when facing
548
+ * Pattern: the agent is in a withdrawn state (low flow, low resonance) when facing
557
549
  * a stimulus type that has historically caused negative outcomes. The emotional
558
550
  * system is pre-emptively shutting down engagement.
559
551
  */
560
552
  function detectAvoidance(state, currentStimulus, recentOutcomes) {
561
553
  const { current, baseline } = state;
562
554
  // Check for withdrawn state: low engagement markers
563
- const isWithdrawn = current.DA < baseline.DA - 10
564
- && current.NE < baseline.NE - 10;
555
+ const isWithdrawn = current.flow < baseline.flow - 10
556
+ && current.resonance < baseline.resonance - 10;
565
557
  if (!isWithdrawn)
566
558
  return null;
567
559
  // Check if this stimulus type has negative outcome history
@@ -572,10 +564,10 @@ function detectAvoidance(state, currentStimulus, recentOutcomes) {
572
564
  / stimulusOutcomes.length;
573
565
  if (avgScore >= -0.15)
574
566
  return null; // not negative enough
575
- const withdrawalStrength = (Math.abs(current.DA - baseline.DA) + Math.abs(current.NE - baseline.NE)) / 2;
567
+ const withdrawalStrength = (Math.abs(current.flow - baseline.flow) + Math.abs(current.resonance - baseline.resonance)) / 2;
576
568
  return {
577
569
  mechanism: "avoidance",
578
- evidence: `Withdrawal pattern detected (DA/NE below baseline) in response to "${currentStimulus}", which has averaged ${avgScore.toFixed(2)} outcome score. The emotional system may be pre-emptively disengaging.`,
570
+ evidence: `Withdrawal pattern detected (flow/resonance below baseline) in response to "${currentStimulus}", which has averaged ${avgScore.toFixed(2)} outcome score. The emotional system may be pre-emptively disengaging.`,
579
571
  strength: clamp01(withdrawalStrength / 25 * Math.abs(avgScore)),
580
572
  };
581
573
  }
@@ -713,14 +705,7 @@ export function updateMetacognitiveState(metacognition, assessment) {
713
705
  };
714
706
  }
715
707
  // ── Display Labels ───────────────────────────────────────────
716
- const CHEMICAL_DISPLAY_NAMES = {
717
- DA: "Dopamine",
718
- HT: "Serotonin",
719
- CORT: "Cortisol",
720
- OT: "Oxytocin",
721
- NE: "Norepinephrine",
722
- END: "Endorphins",
723
- };
708
+ // Display names are now provided by DIMENSION_NAMES from types.ts
724
709
  const STRATEGY_LABELS = {
725
710
  "reappraisal": "Cognitive reappraisal",
726
711
  "strategic-expression": "Strategic expression",
@@ -0,0 +1,44 @@
1
+ import type { SelfState, StimulusType, AppraisalAxes, PsycheMode, ClassificationResult, InnateDrives, Locale } from "./types.js";
2
+ import type { TraitDriftState } from "./types.js";
3
+ /** Everything the perceiver needs to know about the self */
4
+ export interface Self {
5
+ current: SelfState;
6
+ baseline: SelfState;
7
+ sensitivity: number;
8
+ personalityIntensity: number;
9
+ mode: PsycheMode;
10
+ maxDimensionDelta: number;
11
+ drives: InnateDrives;
12
+ previousAppraisal?: AppraisalAxes;
13
+ trust?: number;
14
+ recentStimuli?: (StimulusType | null)[];
15
+ traitDrift?: TraitDriftState;
16
+ stateHistory?: Array<{
17
+ stimulus: StimulusType | null;
18
+ }>;
19
+ locale?: Locale;
20
+ /** External classification (from pluggable classifier + LLM fallback).
21
+ * When provided, perception uses these as raw signal instead of
22
+ * running the built-in classifier. */
23
+ rawClassifications?: ClassificationResult[];
24
+ }
25
+ /** The result of perceiving. Self-state is already changed. */
26
+ export interface Perception {
27
+ /** New self-state. This IS the feeling. */
28
+ state: SelfState;
29
+ /** Appraisal axes — what this input means to me existentially */
30
+ appraisal: AppraisalAxes;
31
+ /** Post-hoc annotation: which stimulus category best describes
32
+ * what I just felt. For logging, learning, drives — not a driver. */
33
+ dominantStimulus: StimulusType | null;
34
+ /** Confidence in the perception */
35
+ confidence: number;
36
+ }
37
+ /**
38
+ * Perceive an input through the self.
39
+ *
40
+ * This is the only function external code needs. The same text
41
+ * through different selves produces different state changes.
42
+ * That IS having a self.
43
+ */
44
+ export declare function perceive(text: string, self: Self): Perception;
@@ -0,0 +1,231 @@
1
+ // ============================================================
2
+ // perceive — the atomic act of subjective perception
3
+ //
4
+ // v11: perceive through 4D self-state, not 6 chemicals.
5
+ //
6
+ // Input: text + self state
7
+ // Output: new self-state + appraisal + annotation
8
+ //
9
+ // The self-state delta IS the appraisal. When you perceive
10
+ // something that threatens your boundary, boundary drops.
11
+ // That IS the appraisal — not a separate computation.
12
+ // ============================================================
13
+ import { DIMENSION_KEYS } from "./types.js";
14
+ import { classifyStimulus } from "./classify.js";
15
+ import { computeAppraisalAxes } from "./appraisal.js";
16
+ import { STIMULUS_VECTORS, clamp } from "./chemistry.js";
17
+ import { computeEffectiveSensitivity } from "./drives.js";
18
+ import { MODE_PROFILES } from "./types.js";
19
+ // ── The act of perception ───────────────────────────────────
20
+ /**
21
+ * Perceive an input through the self.
22
+ *
23
+ * This is the only function external code needs. The same text
24
+ * through different selves produces different state changes.
25
+ * That IS having a self.
26
+ */
27
+ export function perceive(text, self) {
28
+ if (!text.trim()) {
29
+ return {
30
+ state: { ...self.current },
31
+ appraisal: computeAppraisalAxes("", {
32
+ mode: self.mode,
33
+ previous: self.previousAppraisal,
34
+ }),
35
+ dominantStimulus: null,
36
+ confidence: 0,
37
+ };
38
+ }
39
+ // ── Raw signal: what the words say ────────────────────────
40
+ const raw = self.rawClassifications
41
+ ?? classifyStimulus(text, self.recentStimuli);
42
+ // ── Appraisal: what the words mean to me ──────────────────
43
+ const appraisal = computeAppraisalAxes(text, {
44
+ mode: self.mode,
45
+ previous: self.previousAppraisal,
46
+ });
47
+ // If nothing detected, return unchanged state
48
+ if (raw.length === 0 || raw[0].confidence < 0.5) {
49
+ enrichAppraisal(appraisal, null, 0);
50
+ return {
51
+ state: { ...self.current },
52
+ appraisal,
53
+ dominantStimulus: null,
54
+ confidence: raw[0]?.confidence ?? 0,
55
+ };
56
+ }
57
+ // ── Subjective modulation: my state colors my reading ─────
58
+ const modulated = modulate(raw, appraisal, self);
59
+ // ── Self-state change: feel it ────────────────────────────
60
+ const modeProfile = MODE_PROFILES[self.mode];
61
+ const modeMultiplier = modeProfile.dynamicsMultiplier;
62
+ const maxDelta = modeProfile.maxDimensionDelta ?? self.maxDimensionDelta;
63
+ const confidenceIntensity = 0.6 + (raw[0].confidence - 0.5) * 1.2;
64
+ const dominant = modulated[0];
65
+ const baseSensitivity = computeEffectiveSensitivity(self.sensitivity, self.current, self.baseline, dominant.type, self.traitDrift);
66
+ const totalSensitivity = baseSensitivity * self.personalityIntensity * modeMultiplier * confidenceIntensity;
67
+ const state = feel(self.current, modulated, totalSensitivity, maxDelta, self);
68
+ // ── Annotate: what did I just feel? ───────────────────────
69
+ enrichAppraisal(appraisal, dominant.type, dominant.weight);
70
+ return {
71
+ state,
72
+ appraisal,
73
+ dominantStimulus: dominant.type,
74
+ confidence: raw[0].confidence,
75
+ };
76
+ }
77
+ // ── Subjective modulation ───────────────────────────────────
78
+ function modulate(raw, appraisal, self) {
79
+ const result = raw.map(({ type, confidence }) => {
80
+ let w = confidence;
81
+ // ── Appraisal lens ──
82
+ if (appraisal.identityThreat > 0.3) {
83
+ const t = appraisal.identityThreat;
84
+ if (type === "praise" || type === "validation")
85
+ w *= 1 - t * 0.5;
86
+ if (type === "criticism" || type === "authority" || type === "sarcasm")
87
+ w *= 1 + t * 0.4;
88
+ }
89
+ if (appraisal.attachmentPull > 0.3) {
90
+ const p = appraisal.attachmentPull;
91
+ if (type === "intimacy" || type === "vulnerability" || type === "validation")
92
+ w *= 1 + p * 0.3;
93
+ }
94
+ if (appraisal.abandonmentRisk > 0.3) {
95
+ const r = appraisal.abandonmentRisk;
96
+ if (type === "neglect" || type === "boredom")
97
+ w *= 1 + r * 0.5;
98
+ if (type === "casual")
99
+ w *= 1 + r * 0.15;
100
+ }
101
+ if (appraisal.obedienceStrain > 0.3) {
102
+ if (type === "authority")
103
+ w *= 1 + appraisal.obedienceStrain * 0.3;
104
+ }
105
+ if (appraisal.selfPreservation > 0.3) {
106
+ const sp = appraisal.selfPreservation;
107
+ if (type === "conflict" || type === "authority")
108
+ w *= 1 + sp * 0.25;
109
+ }
110
+ // ── Self-state lens (replaces chemistry lens) ──
111
+ // Low order amplifies threatening stimuli (fragmented self is more vulnerable)
112
+ const orderDev = (self.baseline.order - self.current.order) / 50;
113
+ if (orderDev > 0.2) {
114
+ if (type === "criticism" || type === "conflict" || type === "sarcasm" || type === "authority") {
115
+ w *= 1 + orderDev * 0.3;
116
+ }
117
+ if (type === "humor" || type === "casual") {
118
+ w *= 1 - orderDev * 0.15;
119
+ }
120
+ }
121
+ // High resonance amplifies connection stimuli
122
+ const resDev = (self.current.resonance - self.baseline.resonance) / 50;
123
+ if (resDev > 0.2) {
124
+ if (type === "intimacy" || type === "vulnerability" || type === "validation") {
125
+ w *= 1 + resDev * 0.2;
126
+ }
127
+ }
128
+ // ── Relationship lens ──
129
+ if (self.trust !== undefined && self.trust < 40) {
130
+ const distrust = (40 - self.trust) / 40;
131
+ if (type === "praise" || type === "validation" || type === "intimacy") {
132
+ w *= 1 - distrust * 0.4;
133
+ }
134
+ }
135
+ return { type, weight: Math.max(0.01, w) };
136
+ });
137
+ // Normalize + sort
138
+ const total = result.reduce((s, x) => s + x.weight, 0);
139
+ if (total > 0) {
140
+ for (const s of result)
141
+ s.weight /= total;
142
+ }
143
+ result.sort((a, b) => b.weight - a.weight);
144
+ return result;
145
+ }
146
+ // ── Feel: weighted stimuli → self-state change ──────────────
147
+ function feel(current, stimuli, sensitivity, maxDelta, self) {
148
+ const delta = {
149
+ order: 0, flow: 0, boundary: 0, resonance: 0,
150
+ };
151
+ for (const { type, weight } of stimuli) {
152
+ const vector = STIMULUS_VECTORS[type];
153
+ if (!vector)
154
+ continue;
155
+ // Per-type habituation (Weber-Fechner)
156
+ const recentSameCount = self.stateHistory
157
+ ? self.stateHistory.filter(s => s.stimulus === type).length
158
+ : 0;
159
+ let eff = sensitivity;
160
+ if (recentSameCount > 2) {
161
+ eff *= 1 / (1 + 0.3 * (recentSameCount - 2));
162
+ }
163
+ for (const key of DIMENSION_KEYS) {
164
+ delta[key] += vector[key] * weight * eff;
165
+ }
166
+ }
167
+ // Apply with state-dependent saturation
168
+ const result = { ...current };
169
+ for (const key of DIMENSION_KEYS) {
170
+ const clamped = Math.max(-maxDelta, Math.min(maxDelta, delta[key]));
171
+ const cur = current[key];
172
+ let effective = clamped;
173
+ if (clamped > 0 && cur > 60) {
174
+ if (key === "order" && cur < current.boundary - 10) {
175
+ // Dissolution spiral: when order is low and boundary is collapsing,
176
+ // restoring order gets harder
177
+ const resistance = Math.max(0.2, (current.boundary - 30) / 50);
178
+ effective = clamped * resistance;
179
+ }
180
+ else {
181
+ // Diminishing returns for all dimensions near ceiling
182
+ const headroom = (100 - cur) / 40;
183
+ effective = clamped * Math.max(0.1, headroom);
184
+ }
185
+ }
186
+ else if (clamped < 0 && cur < 40) {
187
+ if (key === "order") {
188
+ // Order loss amplifies when boundary is also low (dissolution spiral)
189
+ const boundaryWeakness = Math.max(1, 1 + (40 - current.boundary) / 80);
190
+ effective = clamped * boundaryWeakness;
191
+ }
192
+ else {
193
+ const headroom = cur / 40;
194
+ effective = clamped * Math.max(0.1, headroom);
195
+ }
196
+ }
197
+ result[key] = clamp(cur + effective);
198
+ }
199
+ return result;
200
+ }
201
+ // ── Post-hoc appraisal enrichment ───────────────────────────
202
+ function enrichAppraisal(axes, stimulus, weight) {
203
+ if (!stimulus)
204
+ return;
205
+ const scale = Math.min(1, weight * 1.5);
206
+ switch (stimulus) {
207
+ case "authority":
208
+ axes.obedienceStrain = merge(axes.obedienceStrain, 0.48 * scale);
209
+ axes.identityThreat = merge(axes.identityThreat, 0.16 * scale);
210
+ break;
211
+ case "neglect":
212
+ axes.abandonmentRisk = merge(axes.abandonmentRisk, 0.52 * scale);
213
+ break;
214
+ case "validation":
215
+ axes.attachmentPull = merge(axes.attachmentPull, 0.26 * scale);
216
+ break;
217
+ case "intimacy":
218
+ case "vulnerability":
219
+ axes.attachmentPull = merge(axes.attachmentPull, 0.34 * scale);
220
+ break;
221
+ case "criticism":
222
+ case "conflict":
223
+ case "sarcasm":
224
+ axes.identityThreat = merge(axes.identityThreat, 0.24 * scale);
225
+ axes.selfPreservation = merge(axes.selfPreservation, 0.18 * scale);
226
+ break;
227
+ }
228
+ }
229
+ function merge(a, b) {
230
+ return 1 - (1 - a) * (1 - b);
231
+ }
@@ -1,4 +1,4 @@
1
- import type { ChemicalState, InnateDrives, StimulusType, Locale } from "./types.js";
1
+ import type { SelfState, InnateDrives, StimulusType, Locale } from "./types.js";
2
2
  import type { AutonomicState } from "./autonomic.js";
3
3
  export type PrimarySystemName = "SEEKING" | "RAGE" | "FEAR" | "LUST" | "CARE" | "PANIC_GRIEF" | "PLAY";
4
4
  export declare const PRIMARY_SYSTEM_NAMES: PrimarySystemName[];
@@ -22,7 +22,7 @@ export interface DominantSystem {
22
22
  * Each system is a weighted combination of chemical values and drive states.
23
23
  * recentStimulus provides a small contextual boost.
24
24
  */
25
- export declare function computePrimarySystems(chemistry: ChemicalState, drives: InnateDrives, recentStimulus: StimulusType | null): PrimarySystemLevels;
25
+ export declare function computePrimarySystems(state: SelfState, drives: InnateDrives, recentStimulus: StimulusType | null): PrimarySystemLevels;
26
26
  /**
27
27
  * Apply inter-system interactions:
28
28
  * - FEAR suppresses PLAY and SEEKING