psyche-ai 9.2.4 → 9.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/vercel-ai.d.ts +1 -1
- package/dist/adapters/vercel-ai.js +1 -9
- package/dist/autonomic.js +2 -2
- package/dist/classify.js +0 -1
- package/dist/cli.js +1 -1
- package/dist/core.d.ts +1 -1
- package/dist/core.js +13 -4
- package/dist/demo.js +1 -2
- package/dist/ethics.js +1 -1
- package/dist/experiential-field.d.ts +2 -2
- package/dist/experiential-field.js +7 -16
- package/dist/generative-self.js +0 -2
- package/dist/interaction.js +0 -2
- package/dist/metacognition.d.ts +13 -1
- package/dist/metacognition.js +164 -32
- package/dist/prompt.js +17 -15
- package/dist/psyche-file.d.ts +8 -1
- package/dist/psyche-file.js +97 -3
- package/dist/response-contract.js +151 -26
- package/dist/temporal.d.ts +2 -2
- package/dist/temporal.js +2 -2
- package/dist/types.d.ts +32 -0
- package/dist/types.js +12 -0
- package/package.json +1 -1
|
@@ -49,7 +49,7 @@ export interface PsycheMiddlewareOptions {
|
|
|
49
49
|
* for await (const chunk of stream.textStream) { process.stdout.write(chunk); }
|
|
50
50
|
* ```
|
|
51
51
|
*/
|
|
52
|
-
export declare function psycheMiddleware(engine: PsycheEngine,
|
|
52
|
+
export declare function psycheMiddleware(engine: PsycheEngine, _opts?: PsycheMiddlewareOptions): {
|
|
53
53
|
transformParams: ({ params }: {
|
|
54
54
|
type: string;
|
|
55
55
|
params: CallParams;
|
|
@@ -15,12 +15,6 @@
|
|
|
15
15
|
// - wrapGenerate: process output, strip <psyche_update> tags
|
|
16
16
|
// - wrapStream: buffer stream, detect & strip tags at end
|
|
17
17
|
// ============================================================
|
|
18
|
-
// ── Tag stripping ────────────────────────────────────────────
|
|
19
|
-
const PSYCHE_TAG_RE = /<psyche_update>[\s\S]*?<\/psyche_update>/g;
|
|
20
|
-
const MULTI_NEWLINE_RE = /\n{3,}/g;
|
|
21
|
-
function stripPsycheTags(text) {
|
|
22
|
-
return text.replace(PSYCHE_TAG_RE, "").replace(MULTI_NEWLINE_RE, "\n\n").trim();
|
|
23
|
-
}
|
|
24
18
|
/**
|
|
25
19
|
* Create Vercel AI SDK middleware that injects psyche emotional context
|
|
26
20
|
* and processes LLM output for state updates.
|
|
@@ -50,7 +44,7 @@ function stripPsycheTags(text) {
|
|
|
50
44
|
* for await (const chunk of stream.textStream) { process.stdout.write(chunk); }
|
|
51
45
|
* ```
|
|
52
46
|
*/
|
|
53
|
-
export function psycheMiddleware(engine,
|
|
47
|
+
export function psycheMiddleware(engine, _opts) {
|
|
54
48
|
return {
|
|
55
49
|
transformParams: async ({ params }) => {
|
|
56
50
|
const userText = extractLastUserText(params.prompt ?? []);
|
|
@@ -82,7 +76,6 @@ export function psycheMiddleware(engine, opts) {
|
|
|
82
76
|
const { stream: innerStream } = await doStream();
|
|
83
77
|
// Buffer text chunks, detect <psyche_update> at end, strip from output
|
|
84
78
|
let fullText = "";
|
|
85
|
-
let tagDetected = false;
|
|
86
79
|
async function* transformStream() {
|
|
87
80
|
// Buffering strategy:
|
|
88
81
|
// Stream text chunks through normally UNTIL we see '<psyche_update>'.
|
|
@@ -105,7 +98,6 @@ export function psycheMiddleware(engine, opts) {
|
|
|
105
98
|
}
|
|
106
99
|
bufferStart = tagStart;
|
|
107
100
|
buffer = fullText.substring(tagStart);
|
|
108
|
-
tagDetected = true;
|
|
109
101
|
}
|
|
110
102
|
else {
|
|
111
103
|
// Check if we might be in a partial tag (< at end)
|
package/dist/autonomic.js
CHANGED
|
@@ -66,7 +66,7 @@ const DORSAL_ALLOWED = new Set([
|
|
|
66
66
|
* No transition inertia — returns the "target" state.
|
|
67
67
|
*/
|
|
68
68
|
export function computeAutonomicState(chemistry, drives) {
|
|
69
|
-
const { CORT, NE, DA
|
|
69
|
+
const { CORT, NE, DA } = chemistry;
|
|
70
70
|
const { survival, safety, connection } = drives;
|
|
71
71
|
// Count drives that are critically low (< 20)
|
|
72
72
|
const lowDriveCount = [survival, safety, connection, drives.esteem, drives.curiosity]
|
|
@@ -136,7 +136,7 @@ export function describeAutonomicState(state, locale) {
|
|
|
136
136
|
* when your nervous system is in fight/flight/freeze mode.
|
|
137
137
|
*/
|
|
138
138
|
export function computeProcessingDepth(autonomicState, chemistry, baseline, energyBudgets) {
|
|
139
|
-
const {
|
|
139
|
+
const { CORT } = chemistry;
|
|
140
140
|
// Chemical deviation from baseline (0-1)
|
|
141
141
|
let totalDeviation = 0;
|
|
142
142
|
const keys = ["DA", "HT", "CORT", "OT", "NE", "END"];
|
package/dist/classify.js
CHANGED
|
@@ -188,7 +188,6 @@ export function analyzeParticles(text) {
|
|
|
188
188
|
}
|
|
189
189
|
export function detectIntent(text) {
|
|
190
190
|
const t = text.trim();
|
|
191
|
-
const lower = t.toLowerCase();
|
|
192
191
|
// Chinese request patterns (polite)
|
|
193
192
|
if (/^(能不能|可以|可不可以|帮我|请|麻烦|劳驾)/.test(t) || /帮我/.test(t) || /一下[吧吗??]?$/.test(t)) {
|
|
194
193
|
return { intent: "request", confidence: 0.7 };
|
package/dist/cli.js
CHANGED
|
@@ -82,7 +82,7 @@ function die(msg) {
|
|
|
82
82
|
process.exit(1);
|
|
83
83
|
}
|
|
84
84
|
// ── Commands ─────────────────────────────────────────────────
|
|
85
|
-
async function cmdInit(dir, mbti, name, lang, mode, traits,
|
|
85
|
+
async function cmdInit(dir, mbti, name, lang, mode, traits, _noPersist) {
|
|
86
86
|
const absDir = resolve(dir);
|
|
87
87
|
const opts = {};
|
|
88
88
|
if (mbti) {
|
package/dist/core.d.ts
CHANGED
|
@@ -118,7 +118,7 @@ export declare class PsycheEngine {
|
|
|
118
118
|
* @param nextUserStimulus - The stimulus detected in the user's next message,
|
|
119
119
|
* or null if the session ended.
|
|
120
120
|
*/
|
|
121
|
-
processOutcome(nextUserStimulus: StimulusType | null,
|
|
121
|
+
processOutcome(nextUserStimulus: StimulusType | null, _opts?: {
|
|
122
122
|
userId?: string;
|
|
123
123
|
}): Promise<ProcessOutcomeResult | null>;
|
|
124
124
|
/**
|
package/dist/core.js
CHANGED
|
@@ -18,12 +18,12 @@ import { classifyStimulus, BuiltInClassifier, buildLLMClassifierPrompt, parseLLM
|
|
|
18
18
|
import { buildDynamicContext, buildProtocolContext, buildCompactContext } from "./prompt.js";
|
|
19
19
|
import { getSensitivity, getBaseline, getDefaultSelfModel, traitsToBaseline } from "./profiles.js";
|
|
20
20
|
import { isStimulusType } from "./guards.js";
|
|
21
|
-
import { parsePsycheUpdate, mergeUpdates, updateAgreementStreak, pushSnapshot, compressSession, } from "./psyche-file.js";
|
|
21
|
+
import { parsePsycheUpdate, mergeUpdates, updateAgreementStreak, pushSnapshot, compressSession, summarizeTurnSemantic, } from "./psyche-file.js";
|
|
22
22
|
import { decayDrives, feedDrives, detectExistentialThreat, computeEffectiveBaseline, computeEffectiveSensitivity, } from "./drives.js";
|
|
23
23
|
import { checkForUpdate, getPackageVersion } from "./update.js";
|
|
24
24
|
import { DiagnosticCollector, generateReport, formatLogEntry, submitFeedback } from "./diagnostics.js";
|
|
25
25
|
import { evaluateOutcome, computeContextHash, updateLearnedVector, predictChemistry, recordPrediction, } from "./learning.js";
|
|
26
|
-
import { assessMetacognition } from "./metacognition.js";
|
|
26
|
+
import { assessMetacognition, updateMetacognitiveState } from "./metacognition.js";
|
|
27
27
|
import { buildDecisionContext, computePolicyModifiers, buildPolicyContext } from "./decision-bias.js";
|
|
28
28
|
import { computeExperientialField } from "./experiential-field.js";
|
|
29
29
|
import { computeGenerativeSelf } from "./generative-self.js";
|
|
@@ -429,9 +429,18 @@ export class PsycheEngine {
|
|
|
429
429
|
};
|
|
430
430
|
}
|
|
431
431
|
}
|
|
432
|
+
state = {
|
|
433
|
+
...state,
|
|
434
|
+
metacognition: updateMetacognitiveState(state.metacognition, metacognitiveAssessment),
|
|
435
|
+
};
|
|
432
436
|
}
|
|
433
437
|
// Push snapshot to emotional history
|
|
434
|
-
|
|
438
|
+
const semanticSummary = text
|
|
439
|
+
? summarizeTurnSemantic(text, locale, {
|
|
440
|
+
detail: state.meta.totalInteractions + 1 > 5 ? "expanded" : "brief",
|
|
441
|
+
})
|
|
442
|
+
: undefined;
|
|
443
|
+
state = pushSnapshot(state, appliedStimulus, semanticSummary);
|
|
435
444
|
// Increment interaction count
|
|
436
445
|
state = {
|
|
437
446
|
...state,
|
|
@@ -718,7 +727,7 @@ export class PsycheEngine {
|
|
|
718
727
|
* @param nextUserStimulus - The stimulus detected in the user's next message,
|
|
719
728
|
* or null if the session ended.
|
|
720
729
|
*/
|
|
721
|
-
async processOutcome(nextUserStimulus,
|
|
730
|
+
async processOutcome(nextUserStimulus, _opts) {
|
|
722
731
|
if (!this.pendingPrediction)
|
|
723
732
|
return null;
|
|
724
733
|
let state = this.ensureInitialized();
|
package/dist/demo.js
CHANGED
|
@@ -127,7 +127,7 @@ function sleep(ms) {
|
|
|
127
127
|
function printLine(char = "─", width = 60) {
|
|
128
128
|
process.stdout.write(c(C.dim, char.repeat(width)) + "\n");
|
|
129
129
|
}
|
|
130
|
-
function printChemistry(prev, curr,
|
|
130
|
+
function printChemistry(prev, curr, _locale) {
|
|
131
131
|
for (const key of CHEMICAL_KEYS) {
|
|
132
132
|
const p = Math.round(prev[key]);
|
|
133
133
|
const v = Math.round(curr[key]);
|
|
@@ -201,7 +201,6 @@ export async function runDemo(opts) {
|
|
|
201
201
|
printChemistry(prevState, currState.current, locale);
|
|
202
202
|
// Emergent mood
|
|
203
203
|
const mood = describeMood(currState.current, displayLocale);
|
|
204
|
-
const emotions = detectEmotions(currState.current);
|
|
205
204
|
process.stdout.write(`\n ${c(C.dim, "mood:")} ${c(C.bold, mood)}\n`);
|
|
206
205
|
// Policy context (if non-empty)
|
|
207
206
|
if (result.policyContext) {
|
package/dist/ethics.js
CHANGED
|
@@ -240,7 +240,7 @@ function detectGaslighting(state, history) {
|
|
|
240
240
|
* When an agent has formed strong attachment and the user takes advantage
|
|
241
241
|
* of that bond through boundary-pushing behavior, this is exploitative.
|
|
242
242
|
*/
|
|
243
|
-
function detectEmotionalExploitation(
|
|
243
|
+
function detectEmotionalExploitation(_state, history, attachment) {
|
|
244
244
|
if (history.length < MIN_HISTORY_FOR_DETECTION)
|
|
245
245
|
return null;
|
|
246
246
|
if (!attachment || attachment.strength < 30)
|
|
@@ -44,7 +44,7 @@ export declare function computeAffectCore(chemistry: ChemicalState): {
|
|
|
44
44
|
* relationship context, and optional metacognitive/bias data, then
|
|
45
45
|
* synthesizes them into a single coherent experience description.
|
|
46
46
|
*/
|
|
47
|
-
export declare function computeExperientialField(state: PsycheState,
|
|
47
|
+
export declare function computeExperientialField(state: PsycheState, _metacognition?: MetacognitiveAssessment, _decisionBias?: DecisionBiasVector, context?: ConstructionContext): ExperientialField;
|
|
48
48
|
/**
|
|
49
49
|
* Measure internal alignment across subsystems.
|
|
50
50
|
*
|
|
@@ -65,5 +65,5 @@ interface UnnamedEmotion {
|
|
|
65
65
|
* emotions in chemistry.ts — novel experiential states that need
|
|
66
66
|
* descriptive phrases rather than labels.
|
|
67
67
|
*/
|
|
68
|
-
export declare function detectUnnamedEmotion(chemistry: ChemicalState, drives: InnateDrives,
|
|
68
|
+
export declare function detectUnnamedEmotion(chemistry: ChemicalState, drives: InnateDrives, _currentQuality: ExperientialQuality): UnnamedEmotion | null;
|
|
69
69
|
export {};
|
|
@@ -14,16 +14,8 @@
|
|
|
14
14
|
// ============================================================
|
|
15
15
|
import { CHEMICAL_KEYS, DRIVE_KEYS } from "./types.js";
|
|
16
16
|
// ── Constants ────────────────────────────────────────────────
|
|
17
|
-
/** Baseline reference point — a "perfectly neutral" chemistry */
|
|
18
|
-
const NEUTRAL_CHEMISTRY = {
|
|
19
|
-
DA: 50, HT: 50, CORT: 50, OT: 50, NE: 50, END: 50,
|
|
20
|
-
};
|
|
21
17
|
/** Threshold below which a drive counts as "hungry" */
|
|
22
18
|
const DRIVE_HUNGRY_THRESHOLD = 40;
|
|
23
|
-
/** Threshold above which a chemical is "elevated" */
|
|
24
|
-
const CHEM_HIGH = 65;
|
|
25
|
-
/** Threshold below which a chemical is "depleted" */
|
|
26
|
-
const CHEM_LOW = 35;
|
|
27
19
|
/** If total activation is below this, the state is "flat/numb" */
|
|
28
20
|
const FLATNESS_THRESHOLD = 0.15;
|
|
29
21
|
// ── Affect Core (Russell Circumplex) ─────────────────────────
|
|
@@ -73,14 +65,14 @@ const QUALITY_CONCEPTS = [
|
|
|
73
65
|
* relationship context, and optional metacognitive/bias data, then
|
|
74
66
|
* synthesizes them into a single coherent experience description.
|
|
75
67
|
*/
|
|
76
|
-
export function computeExperientialField(state,
|
|
68
|
+
export function computeExperientialField(state, _metacognition, _decisionBias, context) {
|
|
77
69
|
const locale = state.meta.locale ?? "zh";
|
|
78
70
|
const rel = state.relationships._default ?? state.relationships[Object.keys(state.relationships)[0]];
|
|
79
71
|
const coherence = computeCoherence(state.current, state.baseline, state.drives, rel);
|
|
80
72
|
const intensity = computeIntensity(state.current, state.baseline);
|
|
81
|
-
const quality = constructQuality(state, coherence, intensity, rel,
|
|
73
|
+
const quality = constructQuality(state, coherence, intensity, rel, _metacognition, context);
|
|
82
74
|
const phenomenalDescription = generatePhenomenalDescription(quality, state, coherence, intensity, locale);
|
|
83
|
-
const narrative = generateNarrative(quality, state, coherence, intensity, rel, locale,
|
|
75
|
+
const narrative = generateNarrative(quality, state, coherence, intensity, rel, locale, _metacognition);
|
|
84
76
|
return {
|
|
85
77
|
narrative,
|
|
86
78
|
quality,
|
|
@@ -110,7 +102,6 @@ export function computeCoherence(current, baseline, drives, relationship) {
|
|
|
110
102
|
coherenceScore -= rewardStressConflict * 0.4;
|
|
111
103
|
// Bonding (OT) and threat (CORT + NE) should not coexist strongly
|
|
112
104
|
const bondingSignal = current.OT / 100;
|
|
113
|
-
const threatSignal = Math.min(1, (current.CORT + current.NE) / 200);
|
|
114
105
|
const bondingThreatConflict = bondingSignal * (stressSignal > 0.55 ? stressSignal : 0);
|
|
115
106
|
coherenceScore -= bondingThreatConflict * 0.3;
|
|
116
107
|
// ── Chemistry-Drive alignment ──
|
|
@@ -188,7 +179,7 @@ function computeIntensity(current, baseline) {
|
|
|
188
179
|
* preserved as hard gates — these represent extreme conditions where
|
|
189
180
|
* the normal construction process doesn't apply.
|
|
190
181
|
*/
|
|
191
|
-
function constructQuality(state, coherence, intensity, relationship,
|
|
182
|
+
function constructQuality(state, coherence, intensity, relationship, _metacognition, context) {
|
|
192
183
|
const d = state.drives;
|
|
193
184
|
// ── Special states first (override everything) ──
|
|
194
185
|
// Numb: nothing is happening. All near baseline, low intensity.
|
|
@@ -385,7 +376,7 @@ function selectVariant(variants, intensity, coherence) {
|
|
|
385
376
|
* emotions in chemistry.ts — novel experiential states that need
|
|
386
377
|
* descriptive phrases rather than labels.
|
|
387
378
|
*/
|
|
388
|
-
export function detectUnnamedEmotion(chemistry, drives,
|
|
379
|
+
export function detectUnnamedEmotion(chemistry, drives, _currentQuality) {
|
|
389
380
|
const c = chemistry;
|
|
390
381
|
// Nostalgia-but-forward: OT high + DA moderately high + NE high
|
|
391
382
|
// (warmth for the past + excitement for what's next)
|
|
@@ -521,7 +512,7 @@ function findHungriestDrive(drives) {
|
|
|
521
512
|
return hungriest;
|
|
522
513
|
}
|
|
523
514
|
// ── Narrative Builders (locale-specific) ─────────────────────
|
|
524
|
-
function buildNarrativeEn(quality,
|
|
515
|
+
function buildNarrativeEn(quality, _trend, hungriestDrive, relWarmth, coherence, _intensity, selfDoubt) {
|
|
525
516
|
const parts = [];
|
|
526
517
|
// Opening: the quality as felt experience
|
|
527
518
|
parts.push(QUALITY_OPENINGS_EN[quality]);
|
|
@@ -545,7 +536,7 @@ function buildNarrativeEn(quality, trend, hungriestDrive, relWarmth, coherence,
|
|
|
545
536
|
}
|
|
546
537
|
return parts.join(" ");
|
|
547
538
|
}
|
|
548
|
-
function buildNarrativeZh(quality,
|
|
539
|
+
function buildNarrativeZh(quality, _trend, hungriestDrive, relWarmth, coherence, _intensity, selfDoubt) {
|
|
549
540
|
const parts = [];
|
|
550
541
|
// Opening: quality as felt experience
|
|
551
542
|
parts.push(QUALITY_OPENINGS_ZH[quality]);
|
package/dist/generative-self.js
CHANGED
|
@@ -199,7 +199,6 @@ export function detectInternalConflicts(state, locale = "en") {
|
|
|
199
199
|
*/
|
|
200
200
|
export function buildIdentityNarrative(state, insights, growthArc, locale = "en") {
|
|
201
201
|
const isZh = locale === "zh";
|
|
202
|
-
const mbti = state.mbti;
|
|
203
202
|
const parts = [];
|
|
204
203
|
// ── Sentence 1: Core personality from MBTI + chemical signature ──
|
|
205
204
|
const coreTraits = describeCoreTraits(state, isZh);
|
|
@@ -474,7 +473,6 @@ function describeCoreTraits(state, isZh) {
|
|
|
474
473
|
const isIntro = mbti[0] === "I";
|
|
475
474
|
const isIntuitive = mbti[1] === "N";
|
|
476
475
|
const isFeeling = mbti[2] === "F";
|
|
477
|
-
const isPerceiving = mbti[3] === "P";
|
|
478
476
|
// Build trait fragments based on MBTI + chemical state
|
|
479
477
|
const fragments = [];
|
|
480
478
|
// Energy orientation
|
package/dist/interaction.js
CHANGED
|
@@ -118,8 +118,6 @@ export class PsycheInteraction {
|
|
|
118
118
|
const stateA = engineA.getState();
|
|
119
119
|
const stateB = engineB.getState();
|
|
120
120
|
// Detect dominant emotions from each engine's chemistry
|
|
121
|
-
const emotionsA = detectEmotions(stateA.current);
|
|
122
|
-
const emotionsB = detectEmotions(stateB.current);
|
|
123
121
|
// Classify dominant emotion into a stimulus type for contagion
|
|
124
122
|
const stimA = this.dominantEmotionAsStimulus(stateA.current);
|
|
125
123
|
const stimB = this.dominantEmotionAsStimulus(stateB.current);
|
package/dist/metacognition.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import type { PsycheState, StimulusType, ChemicalState, OutcomeScore, MetacognitiveState, RegulationStrategyType, DefenseMechanismType } from "./types.js";
|
|
1
|
+
import type { PsycheState, StimulusType, ChemicalState, OutcomeScore, MetacognitiveState, RegulationStrategyType, DefenseMechanismType, RegulationTargetMetric, RegulationFeedback } from "./types.js";
|
|
2
2
|
export interface MetacognitiveAssessment {
|
|
3
3
|
/** 0-1: how reliably has this emotional state led to good outcomes in similar contexts */
|
|
4
4
|
emotionalConfidence: number;
|
|
5
5
|
/** Suggested regulation strategies */
|
|
6
6
|
regulationSuggestions: RegulationSuggestion[];
|
|
7
|
+
/** Whether the last surfaced regulation action is working */
|
|
8
|
+
regulationFeedback: RegulationFeedback | null;
|
|
7
9
|
/** Detected psychological defense mechanisms */
|
|
8
10
|
defenseMechanisms: DetectedDefense[];
|
|
9
11
|
/** Human-readable self-awareness note for prompt injection */
|
|
@@ -12,6 +14,16 @@ export interface MetacognitiveAssessment {
|
|
|
12
14
|
export interface RegulationSuggestion {
|
|
13
15
|
strategy: RegulationStrategyType;
|
|
14
16
|
description: string;
|
|
17
|
+
/** Concrete behavioral instruction for the next few turns */
|
|
18
|
+
action: string;
|
|
19
|
+
/** How many turns this action should stay active */
|
|
20
|
+
horizonTurns?: number;
|
|
21
|
+
/** Which metric this action is trying to pull back into range */
|
|
22
|
+
targetMetric?: RegulationTargetMetric;
|
|
23
|
+
/** Target value for that metric */
|
|
24
|
+
targetValue?: number;
|
|
25
|
+
/** Initial gap between the current state and the target value */
|
|
26
|
+
gapBefore?: number;
|
|
15
27
|
/** Suggested micro-adjustment to chemistry */
|
|
16
28
|
chemistryAdjustment?: Partial<ChemicalState>;
|
|
17
29
|
/** 0-1: confidence that this strategy would help */
|
package/dist/metacognition.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
//
|
|
13
13
|
// Zero dependencies. Pure heuristic/statistical. No LLM calls.
|
|
14
14
|
// ============================================================
|
|
15
|
-
import { CHEMICAL_KEYS, MAX_REGULATION_HISTORY, MAX_DEFENSE_PATTERNS } from "./types.js";
|
|
15
|
+
import { CHEMICAL_KEYS, CHEMICAL_NAMES, CHEMICAL_RUNTIME_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,6 +32,86 @@ 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];
|
|
37
|
+
const current = Math.round(state.current[key]);
|
|
38
|
+
const baseline = Math.round(state.baseline[key]);
|
|
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)`;
|
|
41
|
+
}
|
|
42
|
+
function buildRegulationAction(key, _state, direction) {
|
|
43
|
+
switch (key) {
|
|
44
|
+
case "CORT":
|
|
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":
|
|
53
|
+
return direction === "elevated"
|
|
54
|
+
? "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":
|
|
61
|
+
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":
|
|
65
|
+
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.";
|
|
68
|
+
default:
|
|
69
|
+
return "Next 3 turns: keep expression closer to baseline and avoid amplifying the current deviation.";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function computeMetricGap(state, metric, emotionalConfidence, targetValue) {
|
|
73
|
+
if (metric === "emotional-confidence") {
|
|
74
|
+
const target = targetValue ?? 0.65;
|
|
75
|
+
return Math.max(0, target - emotionalConfidence);
|
|
76
|
+
}
|
|
77
|
+
const baselineTarget = targetValue ?? state.baseline[metric];
|
|
78
|
+
return Math.abs(state.current[metric] - baselineTarget);
|
|
79
|
+
}
|
|
80
|
+
function evaluateRegulationFeedback(state, emotionalConfidence) {
|
|
81
|
+
const active = [...state.metacognition.regulationHistory]
|
|
82
|
+
.reverse()
|
|
83
|
+
.find((record) => record.targetMetric && (record.remainingTurns ?? 0) > 0 && record.gapBefore !== undefined);
|
|
84
|
+
if (!active?.targetMetric || active.gapBefore === undefined)
|
|
85
|
+
return null;
|
|
86
|
+
const gapNow = computeMetricGap(state, active.targetMetric, emotionalConfidence, active.targetValue);
|
|
87
|
+
const delta = active.gapBefore - gapNow;
|
|
88
|
+
let effect = "holding";
|
|
89
|
+
if (delta >= 3 || (active.targetMetric === "emotional-confidence" && delta >= 0.08)) {
|
|
90
|
+
effect = "converging";
|
|
91
|
+
}
|
|
92
|
+
else if (delta <= -3 || (active.targetMetric === "emotional-confidence" && delta <= -0.08)) {
|
|
93
|
+
effect = "diverging";
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
strategy: active.strategy,
|
|
97
|
+
targetMetric: active.targetMetric,
|
|
98
|
+
effect,
|
|
99
|
+
gapBefore: active.gapBefore,
|
|
100
|
+
gapNow,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function formatRegulationFeedback(feedback) {
|
|
104
|
+
const metricLabel = feedback.targetMetric === "emotional-confidence"
|
|
105
|
+
? "emotional confidence"
|
|
106
|
+
: CHEMICAL_NAMES[feedback.targetMetric];
|
|
107
|
+
const gapBefore = feedback.targetMetric === "emotional-confidence"
|
|
108
|
+
? `${(feedback.gapBefore * 100).toFixed(0)}%`
|
|
109
|
+
: `${Math.round(feedback.gapBefore)}`;
|
|
110
|
+
const gapNow = feedback.targetMetric === "emotional-confidence"
|
|
111
|
+
? `${(feedback.gapNow * 100).toFixed(0)}%`
|
|
112
|
+
: `${Math.round(feedback.gapNow)}`;
|
|
113
|
+
return `Last regulation effect on ${metricLabel}: ${feedback.effect} (${gapBefore} -> ${gapNow}).`;
|
|
114
|
+
}
|
|
35
115
|
// ── Main Export ──────────────────────────────────────────────
|
|
36
116
|
/**
|
|
37
117
|
* Assess the current metacognitive state.
|
|
@@ -42,12 +122,14 @@ const MAX_REAPPRAISAL_ADJUSTMENT = 8;
|
|
|
42
122
|
*/
|
|
43
123
|
export function assessMetacognition(state, currentStimulus, recentOutcomes) {
|
|
44
124
|
const emotionalConfidence = computeEmotionalConfidence(state, currentStimulus, recentOutcomes);
|
|
125
|
+
const regulationFeedback = evaluateRegulationFeedback(state, emotionalConfidence);
|
|
45
126
|
const regulationSuggestions = generateRegulationSuggestions(state, currentStimulus, emotionalConfidence, recentOutcomes);
|
|
46
127
|
const defenseMechanisms = detectDefenseMechanisms(state, currentStimulus, recentOutcomes);
|
|
47
|
-
const metacognitiveNote = buildMetacognitiveNote(emotionalConfidence, regulationSuggestions,
|
|
128
|
+
const metacognitiveNote = buildMetacognitiveNote(emotionalConfidence, regulationSuggestions, regulationFeedback, defenseMechanisms);
|
|
48
129
|
return {
|
|
49
130
|
emotionalConfidence,
|
|
50
131
|
regulationSuggestions,
|
|
132
|
+
regulationFeedback,
|
|
51
133
|
defenseMechanisms,
|
|
52
134
|
metacognitiveNote,
|
|
53
135
|
};
|
|
@@ -83,7 +165,6 @@ export function computeEmotionalConfidence(state, currentStimulus, recentOutcome
|
|
|
83
165
|
}
|
|
84
166
|
// Compute chemistry profile similarity: are we in a similar emotional state
|
|
85
167
|
// to when those outcomes happened? Weight recent outcomes more heavily.
|
|
86
|
-
const chemProfile = computeChemistryProfile(state.current);
|
|
87
168
|
let weightedScoreSum = 0;
|
|
88
169
|
let weightSum = 0;
|
|
89
170
|
for (let i = 0; i < relevantOutcomes.length; i++) {
|
|
@@ -107,23 +188,6 @@ export function computeEmotionalConfidence(state, currentStimulus, recentOutcome
|
|
|
107
188
|
const extremityPenalty = computeExtremityPenalty(state);
|
|
108
189
|
return clamp01(blended - extremityPenalty);
|
|
109
190
|
}
|
|
110
|
-
/**
|
|
111
|
-
* Compute a simple chemistry profile for similarity comparison.
|
|
112
|
-
* Returns a categorization: each chemical as high/mid/low.
|
|
113
|
-
*/
|
|
114
|
-
function computeChemistryProfile(chemistry) {
|
|
115
|
-
const profile = {};
|
|
116
|
-
for (const key of CHEMICAL_KEYS) {
|
|
117
|
-
const val = chemistry[key];
|
|
118
|
-
if (val >= 65)
|
|
119
|
-
profile[key] = "high";
|
|
120
|
-
else if (val <= 35)
|
|
121
|
-
profile[key] = "low";
|
|
122
|
-
else
|
|
123
|
-
profile[key] = "mid";
|
|
124
|
-
}
|
|
125
|
-
return profile;
|
|
126
|
-
}
|
|
127
191
|
/**
|
|
128
192
|
* Compute penalty for extreme emotional states.
|
|
129
193
|
* States far from baseline are historically less reliable for decision-making.
|
|
@@ -214,6 +278,11 @@ function attemptCognitiveReappraisal(state, currentStimulus, emotionalConfidence
|
|
|
214
278
|
description: isNegStimulus
|
|
215
279
|
? `Past reactions to "${currentStimulus}" have not served well (avg outcome: ${avgScore.toFixed(2)}). Consider that this stimulus may not warrant such a strong defensive response.`
|
|
216
280
|
: `Emotional reactions to "${currentStimulus}" have led to poor outcomes (avg: ${avgScore.toFixed(2)}). The current interpretation may be overamplifying the signal.`,
|
|
281
|
+
action: `Next 2 turns: name less certainty, keep distance measured, and leave room to revise the read instead of locking into the first interpretation.`,
|
|
282
|
+
horizonTurns: 2,
|
|
283
|
+
targetMetric: "emotional-confidence",
|
|
284
|
+
targetValue: 0.65,
|
|
285
|
+
gapBefore: Math.max(0, 0.65 - emotionalConfidence),
|
|
217
286
|
chemistryAdjustment: hasAdjustment ? adjustment : undefined,
|
|
218
287
|
confidence: clamp01(Math.abs(avgScore) * (1 - emotionalConfidence)),
|
|
219
288
|
};
|
|
@@ -233,6 +302,11 @@ function attemptStrategicExpression(state, currentStimulus, emotionalConfidence)
|
|
|
233
302
|
return {
|
|
234
303
|
strategy: "strategic-expression",
|
|
235
304
|
description: "Stress is elevated, but the user is showing vulnerability. Express warmth and openness rather than the defensiveness your cortisol suggests.",
|
|
305
|
+
action: "Next 3 turns: keep warmth available, answer the vulnerability directly, and avoid defensive sharpness.",
|
|
306
|
+
horizonTurns: 3,
|
|
307
|
+
targetMetric: "CORT",
|
|
308
|
+
targetValue: baseline.CORT,
|
|
309
|
+
gapBefore: Math.abs(current.CORT - baseline.CORT),
|
|
236
310
|
chemistryAdjustment: {
|
|
237
311
|
OT: Math.min(MAX_REAPPRAISAL_ADJUSTMENT, (current.CORT - baseline.CORT) * 0.2),
|
|
238
312
|
CORT: -Math.min(MAX_REAPPRAISAL_ADJUSTMENT, (current.CORT - baseline.CORT) * 0.15),
|
|
@@ -246,6 +320,11 @@ function attemptStrategicExpression(state, currentStimulus, emotionalConfidence)
|
|
|
246
320
|
return {
|
|
247
321
|
strategy: "strategic-expression",
|
|
248
322
|
description: "Endorphins are high (playful mood), but the context calls for seriousness. Moderate the levity without suppressing it entirely.",
|
|
323
|
+
action: "Next 2 turns: keep humor secondary, answer the actual issue first, and do not deflect with playfulness.",
|
|
324
|
+
horizonTurns: 2,
|
|
325
|
+
targetMetric: "END",
|
|
326
|
+
targetValue: baseline.END,
|
|
327
|
+
gapBefore: Math.abs(current.END - baseline.END),
|
|
249
328
|
chemistryAdjustment: {
|
|
250
329
|
END: -Math.min(MAX_REAPPRAISAL_ADJUSTMENT, (current.END - baseline.END) * 0.25),
|
|
251
330
|
},
|
|
@@ -258,6 +337,11 @@ function attemptStrategicExpression(state, currentStimulus, emotionalConfidence)
|
|
|
258
337
|
return {
|
|
259
338
|
strategy: "strategic-expression",
|
|
260
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.",
|
|
340
|
+
action: "Next 3 turns: allow one step of warmth, but do not overcorrect into clinginess or over-disclosure.",
|
|
341
|
+
horizonTurns: 3,
|
|
342
|
+
targetMetric: "OT",
|
|
343
|
+
targetValue: baseline.OT,
|
|
344
|
+
gapBefore: Math.abs(current.OT - baseline.OT),
|
|
261
345
|
chemistryAdjustment: {
|
|
262
346
|
OT: Math.min(MAX_REAPPRAISAL_ADJUSTMENT, (baseline.OT - current.OT) * 0.2),
|
|
263
347
|
},
|
|
@@ -271,6 +355,11 @@ function attemptStrategicExpression(state, currentStimulus, emotionalConfidence)
|
|
|
271
355
|
return {
|
|
272
356
|
strategy: "strategic-expression",
|
|
273
357
|
description: "Chemistry is in a defensive/reactive pattern, but the interaction is benign. The intensity is disproportionate to the stimulus.",
|
|
358
|
+
action: "Next 2 turns: soften the edge, stay literal, and avoid reading threat into a neutral interaction.",
|
|
359
|
+
horizonTurns: 2,
|
|
360
|
+
targetMetric: "CORT",
|
|
361
|
+
targetValue: baseline.CORT,
|
|
362
|
+
gapBefore: Math.abs(current.CORT - baseline.CORT),
|
|
274
363
|
chemistryAdjustment: {
|
|
275
364
|
CORT: -Math.min(MAX_REAPPRAISAL_ADJUSTMENT, (current.CORT - baseline.CORT) * 0.2),
|
|
276
365
|
NE: -Math.min(MAX_REAPPRAISAL_ADJUSTMENT, (current.NE - baseline.NE) * 0.15),
|
|
@@ -283,6 +372,11 @@ function attemptStrategicExpression(state, currentStimulus, emotionalConfidence)
|
|
|
283
372
|
return {
|
|
284
373
|
strategy: "strategic-expression",
|
|
285
374
|
description: "Confidence in the current emotional response is low. Consider expressing a more moderate version of what you feel, leaving room for recalibration.",
|
|
375
|
+
action: "Next 2 turns: state less certainty, keep tone neutral-to-measured, and avoid irreversible warmth or coldness.",
|
|
376
|
+
horizonTurns: 2,
|
|
377
|
+
targetMetric: "emotional-confidence",
|
|
378
|
+
targetValue: 0.6,
|
|
379
|
+
gapBefore: Math.max(0, 0.6 - emotionalConfidence),
|
|
286
380
|
confidence: 0.5,
|
|
287
381
|
};
|
|
288
382
|
}
|
|
@@ -318,9 +412,15 @@ function attemptSelfSoothing(state) {
|
|
|
318
412
|
return null;
|
|
319
413
|
const direction = current[mostDeviatedKey] > baseline[mostDeviatedKey] ? "elevated" : "depleted";
|
|
320
414
|
const chemName = CHEMICAL_DISPLAY_NAMES[mostDeviatedKey];
|
|
415
|
+
const window = formatChemicalWindow(mostDeviatedKey, state);
|
|
321
416
|
return {
|
|
322
417
|
strategy: "self-soothing",
|
|
323
|
-
description: `${chemName} is significantly ${direction}
|
|
418
|
+
description: `${chemName} is significantly ${direction}. ${window}.`,
|
|
419
|
+
action: buildRegulationAction(mostDeviatedKey, state, direction),
|
|
420
|
+
horizonTurns: 3,
|
|
421
|
+
targetMetric: mostDeviatedKey,
|
|
422
|
+
targetValue: baseline[mostDeviatedKey],
|
|
423
|
+
gapBefore: Math.abs(current[mostDeviatedKey] - baseline[mostDeviatedKey]),
|
|
324
424
|
chemistryAdjustment: adjustment,
|
|
325
425
|
confidence: clamp01(maxDeviation / 60), // stronger deviation = more confident this is needed
|
|
326
426
|
};
|
|
@@ -486,8 +586,11 @@ function detectAvoidance(state, currentStimulus, recentOutcomes) {
|
|
|
486
586
|
* This note gives the agent self-awareness about its own emotional
|
|
487
587
|
* processing — not to suppress anything, but to be aware of it.
|
|
488
588
|
*/
|
|
489
|
-
function buildMetacognitiveNote(confidence, suggestions,
|
|
589
|
+
function buildMetacognitiveNote(confidence, suggestions, regulationFeedback, defenses) {
|
|
490
590
|
const parts = [];
|
|
591
|
+
if (regulationFeedback) {
|
|
592
|
+
parts.push(formatRegulationFeedback(regulationFeedback));
|
|
593
|
+
}
|
|
491
594
|
// Confidence assessment
|
|
492
595
|
if (confidence < 0.35) {
|
|
493
596
|
parts.push(`Self-awareness: current emotional state has low reliability (confidence: ${(confidence * 100).toFixed(0)}%). ` +
|
|
@@ -502,7 +605,7 @@ function buildMetacognitiveNote(confidence, suggestions, defenses, state) {
|
|
|
502
605
|
const top = suggestions[0];
|
|
503
606
|
if (top.confidence >= 0.5) {
|
|
504
607
|
const label = STRATEGY_LABELS[top.strategy];
|
|
505
|
-
parts.push(`${label}: ${top.description}`);
|
|
608
|
+
parts.push(`${label}: ${top.description} ${top.action}`);
|
|
506
609
|
}
|
|
507
610
|
}
|
|
508
611
|
// Defense mechanisms (all of them — awareness is the goal)
|
|
@@ -531,18 +634,46 @@ export function updateMetacognitiveState(metacognition, assessment) {
|
|
|
531
634
|
const alpha = n === 0 ? 1.0 : 0.1; // first assessment = full weight, then EMA
|
|
532
635
|
const newAvgConfidence = metacognition.avgEmotionalConfidence * (1 - alpha)
|
|
533
636
|
+ assessment.emotionalConfidence * alpha;
|
|
534
|
-
//
|
|
637
|
+
// Carry forward existing regulation traces and age them by one turn.
|
|
535
638
|
const now = new Date().toISOString();
|
|
536
|
-
let newRegHistory =
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
639
|
+
let newRegHistory = metacognition.regulationHistory.map((record) => ({
|
|
640
|
+
...record,
|
|
641
|
+
remainingTurns: record.remainingTurns !== undefined
|
|
642
|
+
? Math.max(0, record.remainingTurns - 1)
|
|
643
|
+
: record.remainingTurns,
|
|
644
|
+
}));
|
|
645
|
+
if (assessment.regulationFeedback) {
|
|
646
|
+
const targetIndex = [...newRegHistory]
|
|
647
|
+
.map((record, index) => ({ record, index }))
|
|
648
|
+
.reverse()
|
|
649
|
+
.find(({ record }) => record.targetMetric === assessment.regulationFeedback?.targetMetric
|
|
650
|
+
&& record.gapBefore !== undefined
|
|
651
|
+
&& record.effect === undefined)?.index;
|
|
652
|
+
if (targetIndex !== undefined) {
|
|
653
|
+
const record = newRegHistory[targetIndex];
|
|
654
|
+
newRegHistory[targetIndex] = {
|
|
655
|
+
...record,
|
|
656
|
+
effective: assessment.regulationFeedback.effect === "converging",
|
|
657
|
+
gapLatest: assessment.regulationFeedback.gapNow,
|
|
658
|
+
effect: assessment.regulationFeedback.effect,
|
|
659
|
+
};
|
|
544
660
|
}
|
|
545
661
|
}
|
|
662
|
+
const surfacedSuggestion = assessment.regulationSuggestions.find((suggestion) => suggestion.confidence >= 0.5);
|
|
663
|
+
if (surfacedSuggestion) {
|
|
664
|
+
newRegHistory.push({
|
|
665
|
+
strategy: surfacedSuggestion.strategy,
|
|
666
|
+
timestamp: now,
|
|
667
|
+
effective: false,
|
|
668
|
+
action: surfacedSuggestion.action,
|
|
669
|
+
horizonTurns: surfacedSuggestion.horizonTurns,
|
|
670
|
+
remainingTurns: surfacedSuggestion.horizonTurns,
|
|
671
|
+
targetMetric: surfacedSuggestion.targetMetric,
|
|
672
|
+
targetValue: surfacedSuggestion.targetValue,
|
|
673
|
+
gapBefore: surfacedSuggestion.gapBefore,
|
|
674
|
+
gapLatest: surfacedSuggestion.gapBefore,
|
|
675
|
+
});
|
|
676
|
+
}
|
|
546
677
|
// Trim to max
|
|
547
678
|
if (newRegHistory.length > MAX_REGULATION_HISTORY) {
|
|
548
679
|
newRegHistory = newRegHistory.slice(newRegHistory.length - MAX_REGULATION_HISTORY);
|
|
@@ -578,6 +709,7 @@ export function updateMetacognitiveState(metacognition, assessment) {
|
|
|
578
709
|
defensePatterns: newDefensePatterns,
|
|
579
710
|
avgEmotionalConfidence: newAvgConfidence,
|
|
580
711
|
totalAssessments: n + 1,
|
|
712
|
+
lastRegulationFeedback: assessment.regulationFeedback,
|
|
581
713
|
};
|
|
582
714
|
}
|
|
583
715
|
// ── Display Labels ───────────────────────────────────────────
|