psyche-ai 7.1.0 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/autonomic.d.ts +20 -2
- package/dist/autonomic.js +73 -20
- package/dist/chemistry.d.ts +2 -2
- package/dist/chemistry.js +19 -4
- package/dist/circadian.d.ts +19 -1
- package/dist/circadian.js +66 -0
- package/dist/core.d.ts +3 -1
- package/dist/core.js +105 -46
- package/dist/decision-bias.d.ts +13 -1
- package/dist/decision-bias.js +189 -0
- package/dist/drives.d.ts +11 -3
- package/dist/drives.js +181 -2
- package/dist/experiential-field.d.ts +25 -2
- package/dist/experiential-field.js +118 -125
- package/dist/index.d.ts +9 -8
- package/dist/index.js +11 -8
- package/dist/prompt.d.ts +2 -0
- package/dist/prompt.js +7 -0
- package/dist/psyche-file.d.ts +21 -1
- package/dist/psyche-file.js +113 -3
- package/dist/types.d.ts +81 -3
- package/dist/types.js +21 -2
- package/package.json +1 -1
|
@@ -26,6 +26,45 @@ const CHEM_HIGH = 65;
|
|
|
26
26
|
const CHEM_LOW = 35;
|
|
27
27
|
/** If total activation is below this, the state is "flat/numb" */
|
|
28
28
|
const FLATNESS_THRESHOLD = 0.15;
|
|
29
|
+
// ── Affect Core (Russell Circumplex) ─────────────────────────
|
|
30
|
+
/**
|
|
31
|
+
* Map chemistry to valence + arousal — the two fundamental affective dimensions
|
|
32
|
+
* (Russell's Circumplex Model of Affect, 1980).
|
|
33
|
+
*
|
|
34
|
+
* Valence: pleasure ↔ displeasure (-1 to 1)
|
|
35
|
+
* Arousal: activation level (0 to 1)
|
|
36
|
+
*/
|
|
37
|
+
export function computeAffectCore(chemistry) {
|
|
38
|
+
const { DA, HT, OT, END, CORT, NE } = chemistry;
|
|
39
|
+
// Valence: positive chemicals push up, stress pushes down
|
|
40
|
+
// CORT weighted 1.5× (cortisol is the dominant negative signal)
|
|
41
|
+
// NE weighted 0.5× (arousing but not inherently negative)
|
|
42
|
+
const rawValence = (DA - 50) + (HT - 50) + (OT - 50) + (END - 50) - (CORT - 50) * 1.5 - (NE - 50) * 0.5;
|
|
43
|
+
const valence = Math.max(-1, Math.min(1, rawValence / 250));
|
|
44
|
+
// Arousal: NE dominant, CORT and DA contribute
|
|
45
|
+
const rawArousal = NE + CORT * 0.5 + DA * 0.3;
|
|
46
|
+
const arousal = Math.max(0, Math.min(1, rawArousal / 180));
|
|
47
|
+
return { valence, arousal };
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Each ExperientialQuality occupies a region in the Russell Circumplex.
|
|
51
|
+
* Matching = Euclidean distance from the concept center, within its radius.
|
|
52
|
+
* Context biases allow situational factors to pull toward specific concepts.
|
|
53
|
+
*/
|
|
54
|
+
const QUALITY_CONCEPTS = [
|
|
55
|
+
{ quality: "flow", valenceCenter: 0.35, arousalCenter: 0.6, radius: 0.25, contextBiases: {} },
|
|
56
|
+
{ quality: "contentment", valenceCenter: 0.4, arousalCenter: 0.2, radius: 0.25, contextBiases: { close: 0.1, deep: 0.15 } },
|
|
57
|
+
{ quality: "yearning", valenceCenter: -0.2, arousalCenter: 0.4, radius: 0.3, contextBiases: { stranger: 0.1 } },
|
|
58
|
+
{ quality: "vigilance", valenceCenter: -0.15, arousalCenter: 0.7, radius: 0.35, contextBiases: { sympathetic: 0.2 } },
|
|
59
|
+
{ quality: "creative-surge", valenceCenter: 0.65, arousalCenter: 0.75, radius: 0.25, contextBiases: {} },
|
|
60
|
+
{ quality: "wounded-retreat", valenceCenter: -0.5, arousalCenter: 0.3, radius: 0.25, contextBiases: { criticism: 0.2 } },
|
|
61
|
+
{ quality: "warm-connection", valenceCenter: 0.45, arousalCenter: 0.35, radius: 0.25, contextBiases: { deep: 0.2, close: 0.15 } },
|
|
62
|
+
{ quality: "restless-boredom", valenceCenter: -0.1, arousalCenter: 0.15, radius: 0.2, contextBiases: { boredom: 0.3 } },
|
|
63
|
+
{ quality: "existential-unease", valenceCenter: -0.6, arousalCenter: 0.5, radius: 0.25, contextBiases: {} },
|
|
64
|
+
{ quality: "playful-mischief", valenceCenter: 0.55, arousalCenter: 0.55, radius: 0.2, contextBiases: { humor: 0.2 } },
|
|
65
|
+
{ quality: "conflicted", valenceCenter: 0.0, arousalCenter: 0.5, radius: 0.2, contextBiases: {} },
|
|
66
|
+
{ quality: "numb", valenceCenter: 0.0, arousalCenter: 0.05, radius: 0.15, contextBiases: { "dorsal-vagal": 0.3 } },
|
|
67
|
+
];
|
|
29
68
|
// ── Main Export ──────────────────────────────────────────────
|
|
30
69
|
/**
|
|
31
70
|
* Compute the unified experiential field from the full psyche state.
|
|
@@ -34,12 +73,12 @@ const FLATNESS_THRESHOLD = 0.15;
|
|
|
34
73
|
* relationship context, and optional metacognitive/bias data, then
|
|
35
74
|
* synthesizes them into a single coherent experience description.
|
|
36
75
|
*/
|
|
37
|
-
export function computeExperientialField(state, metacognition, decisionBias) {
|
|
76
|
+
export function computeExperientialField(state, metacognition, decisionBias, context) {
|
|
38
77
|
const locale = state.meta.locale ?? "zh";
|
|
39
78
|
const rel = state.relationships._default ?? state.relationships[Object.keys(state.relationships)[0]];
|
|
40
79
|
const coherence = computeCoherence(state.current, state.baseline, state.drives, rel);
|
|
41
80
|
const intensity = computeIntensity(state.current, state.baseline);
|
|
42
|
-
const quality =
|
|
81
|
+
const quality = constructQuality(state, coherence, intensity, rel, metacognition, context);
|
|
43
82
|
const phenomenalDescription = generatePhenomenalDescription(quality, state, coherence, intensity, locale);
|
|
44
83
|
const narrative = generateNarrative(quality, state, coherence, intensity, rel, locale, metacognition);
|
|
45
84
|
return {
|
|
@@ -138,15 +177,18 @@ function computeIntensity(current, baseline) {
|
|
|
138
177
|
// Map so that deviation of ~120 (avg 20 per chemical) = 0.5 intensity
|
|
139
178
|
return clamp01(totalDeviation / 240);
|
|
140
179
|
}
|
|
141
|
-
// ── Quality
|
|
180
|
+
// ── Quality Construction (Barrett's Constructed Emotion) ─────
|
|
142
181
|
/**
|
|
143
|
-
*
|
|
182
|
+
* Construct the dominant experiential quality using Barrett's theory:
|
|
183
|
+
* 1. Chemistry → valence + arousal (Russell Circumplex)
|
|
184
|
+
* 2. Concept matching: find closest quality in affective space
|
|
185
|
+
* 3. Context biases: autonomic state, stimulus, relationship, memories
|
|
144
186
|
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
187
|
+
* Special override states (numb, conflicted, existential-unease) are
|
|
188
|
+
* preserved as hard gates — these represent extreme conditions where
|
|
189
|
+
* the normal construction process doesn't apply.
|
|
147
190
|
*/
|
|
148
|
-
function
|
|
149
|
-
const c = state.current;
|
|
191
|
+
function constructQuality(state, coherence, intensity, relationship, metacognition, context) {
|
|
150
192
|
const d = state.drives;
|
|
151
193
|
// ── Special states first (override everything) ──
|
|
152
194
|
// Numb: nothing is happening. All near baseline, low intensity.
|
|
@@ -161,130 +203,81 @@ function selectQuality(state, coherence, intensity, relationship, metacognition,
|
|
|
161
203
|
if (d.survival < 30) {
|
|
162
204
|
return "existential-unease";
|
|
163
205
|
}
|
|
164
|
-
// ──
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
"contentment": 0,
|
|
169
|
-
"yearning": 0,
|
|
170
|
-
"vigilance": 0,
|
|
171
|
-
"creative-surge": 0,
|
|
172
|
-
"wounded-retreat": 0,
|
|
173
|
-
"warm-connection": 0,
|
|
174
|
-
"restless-boredom": 0,
|
|
175
|
-
"existential-unease": 0,
|
|
176
|
-
"playful-mischief": 0,
|
|
177
|
-
"conflicted": 0,
|
|
178
|
-
"numb": 0,
|
|
179
|
-
};
|
|
180
|
-
// Flow: NE + DA high, CORT low, curiosity satisfied, high coherence
|
|
181
|
-
if (c.NE > CHEM_HIGH && c.DA > CHEM_HIGH - 5 && c.CORT < CHEM_LOW + 5) {
|
|
182
|
-
scores["flow"] += 0.6;
|
|
183
|
-
if (d.curiosity > 60)
|
|
184
|
-
scores["flow"] += 0.2;
|
|
185
|
-
if (coherence > 0.7)
|
|
186
|
-
scores["flow"] += 0.2;
|
|
187
|
-
}
|
|
188
|
-
// Contentment: HT + OT stable/high, CORT low, drives mostly satisfied
|
|
189
|
-
if (c.HT > 55 && c.CORT < 45) {
|
|
190
|
-
scores["contentment"] += 0.3;
|
|
191
|
-
if (c.OT > 50)
|
|
192
|
-
scores["contentment"] += 0.15;
|
|
193
|
-
if (meanDriveValue(d) > 60)
|
|
194
|
-
scores["contentment"] += 0.3;
|
|
195
|
-
if (coherence > 0.6)
|
|
196
|
-
scores["contentment"] += 0.15;
|
|
197
|
-
}
|
|
198
|
-
// Yearning: connection/esteem drives hungry, OT may be low or high (wanting)
|
|
199
|
-
{
|
|
200
|
-
const connectionHunger = d.connection < DRIVE_HUNGRY_THRESHOLD ? (DRIVE_HUNGRY_THRESHOLD - d.connection) / DRIVE_HUNGRY_THRESHOLD : 0;
|
|
201
|
-
const esteemHunger = d.esteem < DRIVE_HUNGRY_THRESHOLD ? (DRIVE_HUNGRY_THRESHOLD - d.esteem) / DRIVE_HUNGRY_THRESHOLD : 0;
|
|
202
|
-
const hungerSignal = Math.max(connectionHunger, esteemHunger);
|
|
203
|
-
if (hungerSignal > 0.3) {
|
|
204
|
-
scores["yearning"] += hungerSignal * 0.6;
|
|
205
|
-
// OT elevated (wanting connection) makes yearning stronger
|
|
206
|
-
if (c.OT > 50)
|
|
207
|
-
scores["yearning"] += 0.15;
|
|
208
|
-
// OT depleted (missing connection) also valid
|
|
209
|
-
if (c.OT < CHEM_LOW)
|
|
210
|
-
scores["yearning"] += 0.1;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
// Vigilance: CORT high, safety/survival drives hungry
|
|
214
|
-
if (c.CORT > CHEM_HIGH - 5) {
|
|
215
|
-
scores["vigilance"] += 0.4;
|
|
216
|
-
if (d.safety < DRIVE_HUNGRY_THRESHOLD)
|
|
217
|
-
scores["vigilance"] += 0.25;
|
|
218
|
-
if (c.NE > 55)
|
|
219
|
-
scores["vigilance"] += 0.15;
|
|
220
|
-
if (d.survival < 50)
|
|
221
|
-
scores["vigilance"] += 0.2;
|
|
222
|
-
}
|
|
223
|
-
// Creative surge: DA + NE elevated, low stress, curiosity drive satisfied or hungry+seeking
|
|
224
|
-
if (c.DA > CHEM_HIGH - 5 && c.NE > 55 && c.CORT < 45) {
|
|
225
|
-
scores["creative-surge"] += 0.5;
|
|
226
|
-
if (c.END > 50)
|
|
227
|
-
scores["creative-surge"] += 0.15;
|
|
228
|
-
if (d.curiosity > 50 || d.curiosity < DRIVE_HUNGRY_THRESHOLD)
|
|
229
|
-
scores["creative-surge"] += 0.15;
|
|
230
|
-
if (decisionBias && decisionBias.creativityBias > 0.65)
|
|
231
|
-
scores["creative-surge"] += 0.15;
|
|
232
|
-
}
|
|
233
|
-
// Wounded retreat: CORT high, OT low, pulling back. Relationship may be strained.
|
|
234
|
-
if (c.CORT > 55 && c.OT < 45 && c.HT < 45) {
|
|
235
|
-
scores["wounded-retreat"] += 0.5;
|
|
236
|
-
if (c.DA < CHEM_LOW)
|
|
237
|
-
scores["wounded-retreat"] += 0.15;
|
|
238
|
-
if (relationship && relationship.trust < 40)
|
|
239
|
-
scores["wounded-retreat"] += 0.2;
|
|
240
|
-
if (c.NE < 50)
|
|
241
|
-
scores["wounded-retreat"] += 0.1;
|
|
242
|
-
}
|
|
243
|
-
// Warm connection: OT high, trust high, CORT low
|
|
244
|
-
if (c.OT > CHEM_HIGH && c.CORT < 45) {
|
|
245
|
-
scores["warm-connection"] += 0.5;
|
|
246
|
-
if (relationship && relationship.trust > 60)
|
|
247
|
-
scores["warm-connection"] += 0.2;
|
|
248
|
-
if (c.END > 50)
|
|
249
|
-
scores["warm-connection"] += 0.15;
|
|
250
|
-
if (relationship && relationship.intimacy > 50)
|
|
251
|
-
scores["warm-connection"] += 0.15;
|
|
252
|
-
}
|
|
253
|
-
// Restless boredom: low stimulation across the board, drives mildly hungry
|
|
254
|
-
if (c.DA < 45 && c.NE < 45) {
|
|
255
|
-
scores["restless-boredom"] += 0.3;
|
|
256
|
-
if (c.CORT < 45)
|
|
257
|
-
scores["restless-boredom"] += 0.15;
|
|
258
|
-
if (d.curiosity < 50)
|
|
259
|
-
scores["restless-boredom"] += 0.25;
|
|
260
|
-
if (intensity < 0.3)
|
|
261
|
-
scores["restless-boredom"] += 0.15;
|
|
262
|
-
}
|
|
263
|
-
// Playful mischief: END high, social energy, safe
|
|
264
|
-
if (c.END > CHEM_HIGH && c.CORT < CHEM_LOW + 5) {
|
|
265
|
-
scores["playful-mischief"] += 0.5;
|
|
266
|
-
if (c.DA > 55)
|
|
267
|
-
scores["playful-mischief"] += 0.15;
|
|
268
|
-
if (d.safety > 60)
|
|
269
|
-
scores["playful-mischief"] += 0.15;
|
|
270
|
-
if (c.OT > 50)
|
|
271
|
-
scores["playful-mischief"] += 0.1;
|
|
272
|
-
}
|
|
273
|
-
// ── Pick highest scoring quality ──
|
|
206
|
+
// ── Barrett Step 1: Chemistry → Affect Core ──
|
|
207
|
+
const { valence, arousal } = computeAffectCore(state.current);
|
|
208
|
+
// ── Barrett Step 2+3: Concept matching with context ──
|
|
209
|
+
const contextKeys = buildContextKeys(context, relationship);
|
|
274
210
|
let bestQuality = "contentment";
|
|
275
|
-
let bestScore = -
|
|
276
|
-
for (const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
211
|
+
let bestScore = -Infinity;
|
|
212
|
+
for (const concept of QUALITY_CONCEPTS) {
|
|
213
|
+
// Skip special states — already handled above
|
|
214
|
+
if (concept.quality === "numb" || concept.quality === "conflicted"
|
|
215
|
+
|| concept.quality === "existential-unease") {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
// Euclidean distance in affective space
|
|
219
|
+
const dv = valence - concept.valenceCenter;
|
|
220
|
+
const da = arousal - concept.arousalCenter;
|
|
221
|
+
const distance = Math.sqrt(dv * dv + da * da);
|
|
222
|
+
// Score: inverse distance, normalized by radius (Gaussian-like decay)
|
|
223
|
+
let score = Math.max(0, 1 - distance / concept.radius);
|
|
224
|
+
// Context biases: each matching context key adds its bias
|
|
225
|
+
for (const key of contextKeys) {
|
|
226
|
+
if (concept.contextBiases[key] !== undefined) {
|
|
227
|
+
score += concept.contextBiases[key];
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// Core memory bias (P11): if related memories match this quality's region, boost
|
|
231
|
+
if (context?.coreMemories && context.coreMemories.length > 0) {
|
|
232
|
+
const memoryResonance = computeMemoryResonance(context.coreMemories, concept.valenceCenter, concept.arousalCenter);
|
|
233
|
+
score += memoryResonance * 0.1;
|
|
234
|
+
}
|
|
235
|
+
// Prediction error: high error weakens the current concept (forces re-evaluation)
|
|
236
|
+
if (context?.predictionError !== undefined && context.predictionError > 0.3) {
|
|
237
|
+
score -= context.predictionError * 0.15;
|
|
238
|
+
}
|
|
239
|
+
if (score > bestScore) {
|
|
240
|
+
bestScore = score;
|
|
241
|
+
bestQuality = concept.quality;
|
|
280
242
|
}
|
|
281
243
|
}
|
|
282
|
-
// If no
|
|
283
|
-
if (bestScore < 0.
|
|
244
|
+
// If no concept matched meaningfully, fall back
|
|
245
|
+
if (bestScore < 0.1) {
|
|
284
246
|
return intensity < 0.2 ? "numb" : "contentment";
|
|
285
247
|
}
|
|
286
248
|
return bestQuality;
|
|
287
249
|
}
|
|
250
|
+
/**
|
|
251
|
+
* Build context keys for Barrett concept matching from available context signals.
|
|
252
|
+
*/
|
|
253
|
+
function buildContextKeys(context, relationship) {
|
|
254
|
+
const keys = [];
|
|
255
|
+
if (context?.autonomicState) {
|
|
256
|
+
keys.push(context.autonomicState); // "ventral-vagal", "sympathetic", "dorsal-vagal"
|
|
257
|
+
}
|
|
258
|
+
if (context?.stimulus) {
|
|
259
|
+
keys.push(context.stimulus); // "praise", "criticism", "humor", etc.
|
|
260
|
+
}
|
|
261
|
+
if (relationship?.phase) {
|
|
262
|
+
keys.push(relationship.phase); // "stranger", "acquaintance", "familiar", "close", "deep"
|
|
263
|
+
}
|
|
264
|
+
return keys;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Compute how much core memories resonate with a given affective region.
|
|
268
|
+
* Returns 0-1 where 1 = strong resonance (memories cluster near the concept center).
|
|
269
|
+
*/
|
|
270
|
+
function computeMemoryResonance(memories, targetValence, targetArousal) {
|
|
271
|
+
if (memories.length === 0)
|
|
272
|
+
return 0;
|
|
273
|
+
let totalResonance = 0;
|
|
274
|
+
for (const mem of memories) {
|
|
275
|
+
const { valence: mv, arousal: ma } = computeAffectCore(mem.chemistry);
|
|
276
|
+
const dist = Math.sqrt((mv - targetValence) ** 2 + (ma - targetArousal) ** 2);
|
|
277
|
+
totalResonance += Math.max(0, 1 - dist / 0.5); // resonance fades at distance 0.5
|
|
278
|
+
}
|
|
279
|
+
return Math.min(1, totalResonance / memories.length);
|
|
280
|
+
}
|
|
288
281
|
// ── Phenomenal Description ───────────────────────────────────
|
|
289
282
|
/**
|
|
290
283
|
* Generate the raw "what it feels like" phrase.
|
package/dist/index.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ export { PsycheEngine } from "./core.js";
|
|
|
2
2
|
export type { PsycheEngineConfig, ProcessInputResult, ProcessOutputResult, ProcessOutcomeResult } from "./core.js";
|
|
3
3
|
export { FileStorageAdapter, MemoryStorageAdapter } from "./storage.js";
|
|
4
4
|
export type { StorageAdapter } from "./storage.js";
|
|
5
|
-
export type { PsycheState, MBTIType, Locale, StimulusType, ChemicalState, ChemicalSnapshot, SelfModel, RelationshipState, EmpathyEntry, EmotionPattern, DriveType, InnateDrives, LearningState, LearnedVectorAdjustment, PredictionRecord, OutcomeScore, OutcomeSignals, AttachmentStyle, AttachmentData, MetacognitiveState, RegulationRecord, DefensePatternRecord, RegulationStrategyType, DefenseMechanismType, PersonhoodState, PersistedCausalInsight, GrowthDirection, PersonalityTraits, PsycheMode, } from "./types.js";
|
|
6
|
-
export { CHEMICAL_KEYS, CHEMICAL_NAMES, CHEMICAL_NAMES_ZH, DEFAULT_RELATIONSHIP, DEFAULT_DRIVES, DEFAULT_LEARNING_STATE, DEFAULT_METACOGNITIVE_STATE, DEFAULT_PERSONHOOD_STATE, DEFAULT_ATTACHMENT, DRIVE_KEYS, DRIVE_NAMES_ZH, } from "./types.js";
|
|
5
|
+
export type { PsycheState, MBTIType, Locale, StimulusType, ChemicalState, ChemicalSnapshot, SelfModel, RelationshipState, EmpathyEntry, EmotionPattern, DriveType, InnateDrives, LearningState, LearnedVectorAdjustment, PredictionRecord, OutcomeScore, OutcomeSignals, AttachmentStyle, AttachmentData, MetacognitiveState, RegulationRecord, DefensePatternRecord, RegulationStrategyType, DefenseMechanismType, PersonhoodState, PersistedCausalInsight, GrowthDirection, PersonalityTraits, PsycheMode, PolicyModifiers, TraitDriftState, EnergyBudgets, } from "./types.js";
|
|
6
|
+
export { CHEMICAL_KEYS, CHEMICAL_NAMES, CHEMICAL_NAMES_ZH, DEFAULT_RELATIONSHIP, DEFAULT_DRIVES, DEFAULT_LEARNING_STATE, DEFAULT_METACOGNITIVE_STATE, DEFAULT_PERSONHOOD_STATE, DEFAULT_ATTACHMENT, DRIVE_KEYS, DRIVE_NAMES_ZH, DEFAULT_TRAIT_DRIFT, DEFAULT_ENERGY_BUDGETS, } from "./types.js";
|
|
7
7
|
export { computeSelfReflection, computeEmotionalTendency, buildSelfReflectionContext } from "./self-recognition.js";
|
|
8
8
|
export type { SelfReflection } from "./self-recognition.js";
|
|
9
9
|
export { PsycheInteraction } from "./interaction.js";
|
|
@@ -21,25 +21,26 @@ export { updateAttachment, computeSeparationEffect, computeReunionEffect, } from
|
|
|
21
21
|
export type { SeparationEffect } from "./attachment.js";
|
|
22
22
|
export { assessMetacognition, computeEmotionalConfidence, generateRegulationSuggestions, detectDefenseMechanisms, } from "./metacognition.js";
|
|
23
23
|
export type { MetacognitiveAssessment, RegulationSuggestion, DetectedDefense } from "./metacognition.js";
|
|
24
|
-
export { computeDecisionBias, computeAttentionWeights, computeExploreExploit, buildDecisionContext, } from "./decision-bias.js";
|
|
24
|
+
export { computeDecisionBias, computeAttentionWeights, computeExploreExploit, buildDecisionContext, computePolicyModifiers, buildPolicyContext, } from "./decision-bias.js";
|
|
25
25
|
export type { DecisionBiasVector, AttentionWeights } from "./decision-bias.js";
|
|
26
|
-
export { computeExperientialField, computeCoherence, detectUnnamedEmotion } from "./experiential-field.js";
|
|
27
|
-
export type { ExperientialField, ExperientialQuality } from "./experiential-field.js";
|
|
26
|
+
export { computeExperientialField, computeCoherence, detectUnnamedEmotion, computeAffectCore } from "./experiential-field.js";
|
|
27
|
+
export type { ExperientialField, ExperientialQuality, ConstructionContext } from "./experiential-field.js";
|
|
28
28
|
export { computeGenerativeSelf, predictSelfReaction, detectInternalConflicts, buildIdentityNarrative } from "./generative-self.js";
|
|
29
29
|
export type { GenerativeSelfModel, CausalInsight, SelfPrediction, GrowthArc, InternalConflict } from "./generative-self.js";
|
|
30
30
|
export { updateSharedIntentionality, estimateOtherMood, buildSharedIntentionalityContext } from "./shared-intentionality.js";
|
|
31
31
|
export type { SharedIntentionalityState, TheoryOfMindModel, JointAttentionTopic, GoalAlignment } from "./shared-intentionality.js";
|
|
32
32
|
export { assessEthics, detectIntermittentReinforcement, detectDependencyRisk, buildEthicalContext, } from "./ethics.js";
|
|
33
33
|
export type { EthicalAssessment, EthicalConcern, SelfProtectionAction } from "./ethics.js";
|
|
34
|
-
export { computeAutonomicResult, computeAutonomicState, gateEmotions, getTransitionTime, describeAutonomicState } from "./autonomic.js";
|
|
34
|
+
export { computeAutonomicResult, computeAutonomicState, computeProcessingDepth, gateEmotions, getTransitionTime, describeAutonomicState } from "./autonomic.js";
|
|
35
35
|
export type { AutonomicState, AutonomicResult, AutonomicTransition } from "./autonomic.js";
|
|
36
|
-
export { computeCircadianModulation, computeHomeostaticPressure, getCircadianPhase } from "./circadian.js";
|
|
36
|
+
export { computeCircadianModulation, computeHomeostaticPressure, getCircadianPhase, computeEnergyDepletion, computeEnergyRecovery } from "./circadian.js";
|
|
37
37
|
export type { CircadianPhase } from "./circadian.js";
|
|
38
38
|
export { computePrimarySystems, computeSystemInteractions, gatePrimarySystemsByAutonomic, getDominantSystems, describeBehavioralTendencies, PRIMARY_SYSTEM_NAMES, } from "./primary-systems.js";
|
|
39
39
|
export type { PrimarySystemName, PrimarySystemLevels, BehavioralTendency, DominantSystem, } from "./primary-systems.js";
|
|
40
|
+
export { updateTraitDrift } from "./drives.js";
|
|
40
41
|
export { classifyStimulus, getPrimaryStimulus, scoreSentiment, scoreEmoji } from "./classify.js";
|
|
41
42
|
export { buildProtocolContext, buildDynamicContext, buildCompactContext, isNearBaseline, getNearBaselineThreshold } from "./prompt.js";
|
|
42
43
|
export { describeEmotionalState, getExpressionHint, getBehaviorGuide, detectEmotions } from "./chemistry.js";
|
|
43
44
|
export { getBaseline, getTemperament, getSensitivity, getDefaultSelfModel, traitsToBaseline, mbtiToTraits } from "./profiles.js";
|
|
44
|
-
export { migrateToLatest, compressSession, parsePsycheUpdate } from "./psyche-file.js";
|
|
45
|
+
export { migrateToLatest, compressSession, parsePsycheUpdate, computeSnapshotIntensity, computeSnapshotValence, consolidateHistory, retrieveRelatedMemories, } from "./psyche-file.js";
|
|
45
46
|
export type { PsycheUpdateResult } from "./psyche-file.js";
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
export { PsycheEngine } from "./core.js";
|
|
13
13
|
// Storage
|
|
14
14
|
export { FileStorageAdapter, MemoryStorageAdapter } from "./storage.js";
|
|
15
|
-
export { CHEMICAL_KEYS, CHEMICAL_NAMES, CHEMICAL_NAMES_ZH, DEFAULT_RELATIONSHIP, DEFAULT_DRIVES, DEFAULT_LEARNING_STATE, DEFAULT_METACOGNITIVE_STATE, DEFAULT_PERSONHOOD_STATE, DEFAULT_ATTACHMENT, DRIVE_KEYS, DRIVE_NAMES_ZH, } from "./types.js";
|
|
15
|
+
export { CHEMICAL_KEYS, CHEMICAL_NAMES, CHEMICAL_NAMES_ZH, DEFAULT_RELATIONSHIP, DEFAULT_DRIVES, DEFAULT_LEARNING_STATE, DEFAULT_METACOGNITIVE_STATE, DEFAULT_PERSONHOOD_STATE, DEFAULT_ATTACHMENT, DRIVE_KEYS, DRIVE_NAMES_ZH, DEFAULT_TRAIT_DRIFT, DEFAULT_ENERGY_BUDGETS, } from "./types.js";
|
|
16
16
|
// Self-recognition
|
|
17
17
|
export { computeSelfReflection, computeEmotionalTendency, buildSelfReflectionContext } from "./self-recognition.js";
|
|
18
18
|
// Multi-agent interaction
|
|
@@ -31,10 +31,10 @@ export { predictNextStimulus, generateAnticipation, computeSurpriseEffect, compu
|
|
|
31
31
|
export { updateAttachment, computeSeparationEffect, computeReunionEffect, } from "./attachment.js";
|
|
32
32
|
// Metacognition (P5)
|
|
33
33
|
export { assessMetacognition, computeEmotionalConfidence, generateRegulationSuggestions, detectDefenseMechanisms, } from "./metacognition.js";
|
|
34
|
-
// Decision bias (P5)
|
|
35
|
-
export { computeDecisionBias, computeAttentionWeights, computeExploreExploit, buildDecisionContext, } from "./decision-bias.js";
|
|
36
|
-
// Experiential field (P6)
|
|
37
|
-
export { computeExperientialField, computeCoherence, detectUnnamedEmotion } from "./experiential-field.js";
|
|
34
|
+
// Decision bias (P5) + PolicyModifiers (v9)
|
|
35
|
+
export { computeDecisionBias, computeAttentionWeights, computeExploreExploit, buildDecisionContext, computePolicyModifiers, buildPolicyContext, } from "./decision-bias.js";
|
|
36
|
+
// Experiential field (P6 + P8 Barrett construction)
|
|
37
|
+
export { computeExperientialField, computeCoherence, detectUnnamedEmotion, computeAffectCore } from "./experiential-field.js";
|
|
38
38
|
// Generative self (P6)
|
|
39
39
|
export { computeGenerativeSelf, predictSelfReaction, detectInternalConflicts, buildIdentityNarrative } from "./generative-self.js";
|
|
40
40
|
// Shared intentionality (P6)
|
|
@@ -42,14 +42,17 @@ export { updateSharedIntentionality, estimateOtherMood, buildSharedIntentionalit
|
|
|
42
42
|
// Emotional ethics (P6)
|
|
43
43
|
export { assessEthics, detectIntermittentReinforcement, detectDependencyRisk, buildEthicalContext, } from "./ethics.js";
|
|
44
44
|
// Autonomic nervous system (P7)
|
|
45
|
-
export { computeAutonomicResult, computeAutonomicState, gateEmotions, getTransitionTime, describeAutonomicState } from "./autonomic.js";
|
|
45
|
+
export { computeAutonomicResult, computeAutonomicState, computeProcessingDepth, gateEmotions, getTransitionTime, describeAutonomicState } from "./autonomic.js";
|
|
46
46
|
// Circadian rhythms (P12)
|
|
47
|
-
export { computeCircadianModulation, computeHomeostaticPressure, getCircadianPhase } from "./circadian.js";
|
|
47
|
+
export { computeCircadianModulation, computeHomeostaticPressure, getCircadianPhase, computeEnergyDepletion, computeEnergyRecovery } from "./circadian.js";
|
|
48
48
|
// Primary emotional systems — Panksepp (P9)
|
|
49
49
|
export { computePrimarySystems, computeSystemInteractions, gatePrimarySystemsByAutonomic, getDominantSystems, describeBehavioralTendencies, PRIMARY_SYSTEM_NAMES, } from "./primary-systems.js";
|
|
50
50
|
// Utilities — for custom adapter / advanced use
|
|
51
|
+
// Trait drift (v9)
|
|
52
|
+
export { updateTraitDrift } from "./drives.js";
|
|
53
|
+
// Utilities — for custom adapter / advanced use
|
|
51
54
|
export { classifyStimulus, getPrimaryStimulus, scoreSentiment, scoreEmoji } from "./classify.js";
|
|
52
55
|
export { buildProtocolContext, buildDynamicContext, buildCompactContext, isNearBaseline, getNearBaselineThreshold } from "./prompt.js";
|
|
53
56
|
export { describeEmotionalState, getExpressionHint, getBehaviorGuide, detectEmotions } from "./chemistry.js";
|
|
54
57
|
export { getBaseline, getTemperament, getSensitivity, getDefaultSelfModel, traitsToBaseline, mbtiToTraits } from "./profiles.js";
|
|
55
|
-
export { migrateToLatest, compressSession, parsePsycheUpdate } from "./psyche-file.js";
|
|
58
|
+
export { migrateToLatest, compressSession, parsePsycheUpdate, computeSnapshotIntensity, computeSnapshotValence, consolidateHistory, retrieveRelatedMemories, } from "./psyche-file.js";
|
package/dist/prompt.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export declare function buildDynamicContext(state: PsycheState, userId?: string,
|
|
|
13
13
|
experientialNarrative?: string;
|
|
14
14
|
autonomicDescription?: string;
|
|
15
15
|
primarySystemsDescription?: string;
|
|
16
|
+
policyContext?: string;
|
|
16
17
|
}): string;
|
|
17
18
|
/**
|
|
18
19
|
* Build the static protocol injected as cacheable system context.
|
|
@@ -64,4 +65,5 @@ export declare function buildCompactContext(state: PsycheState, userId?: string,
|
|
|
64
65
|
experientialNarrative?: string;
|
|
65
66
|
autonomicDescription?: string;
|
|
66
67
|
primarySystemsDescription?: string;
|
|
68
|
+
policyContext?: string;
|
|
67
69
|
}): string;
|
package/dist/prompt.js
CHANGED
|
@@ -121,6 +121,9 @@ export function buildDynamicContext(state, userId, opts) {
|
|
|
121
121
|
const psTitle = locale === "zh" ? "行为倾向" : "Behavioral Tendencies";
|
|
122
122
|
parts.push("", `[${psTitle}] ${opts.primarySystemsDescription}`);
|
|
123
123
|
}
|
|
124
|
+
if (opts?.policyContext) {
|
|
125
|
+
parts.push("", opts.policyContext);
|
|
126
|
+
}
|
|
124
127
|
parts.push("", agencyReminder, sycophancyWarning, "", t("dynamic.update_reminder", locale));
|
|
125
128
|
return parts.filter((l) => l !== undefined).join("\n");
|
|
126
129
|
}
|
|
@@ -788,6 +791,10 @@ export function buildCompactContext(state, userId, opts) {
|
|
|
788
791
|
? `[行为倾向] ${opts.primarySystemsDescription}`
|
|
789
792
|
: `[Tendencies] ${opts.primarySystemsDescription}`);
|
|
790
793
|
}
|
|
794
|
+
// 9h. Policy context (v9)
|
|
795
|
+
if (opts?.policyContext) {
|
|
796
|
+
parts.push(opts.policyContext);
|
|
797
|
+
}
|
|
791
798
|
// 10. Channel modifier — expression style per platform
|
|
792
799
|
if (opts?.channelType) {
|
|
793
800
|
const channelProfile = getChannelProfile(opts.channelType);
|
package/dist/psyche-file.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PsycheState, MBTIType, RelationshipState, Locale, StimulusType, ChemicalSnapshot } from "./types.js";
|
|
1
|
+
import type { PsycheState, MBTIType, ChemicalState, RelationshipState, Locale, StimulusType, ChemicalSnapshot } from "./types.js";
|
|
2
2
|
/** Minimal logger interface */
|
|
3
3
|
export interface Logger {
|
|
4
4
|
info: (msg: string) => void;
|
|
@@ -26,6 +26,26 @@ export declare function getRelationship(state: PsycheState, userId?: string): Re
|
|
|
26
26
|
* Pure computation, no LLM calls.
|
|
27
27
|
*/
|
|
28
28
|
export declare function compressSession(state: PsycheState, userId?: string): PsycheState;
|
|
29
|
+
/**
|
|
30
|
+
* Compute snapshot intensity: how far current chemistry deviates from baseline.
|
|
31
|
+
* Returns 0-1 (0 = at baseline, 1 = maximum possible deviation).
|
|
32
|
+
*/
|
|
33
|
+
export declare function computeSnapshotIntensity(current: ChemicalState, baseline: ChemicalState): number;
|
|
34
|
+
/**
|
|
35
|
+
* Compute emotional valence from chemistry.
|
|
36
|
+
* Returns -1 (negative) to 1 (positive).
|
|
37
|
+
*/
|
|
38
|
+
export declare function computeSnapshotValence(chemistry: ChemicalState): number;
|
|
39
|
+
/**
|
|
40
|
+
* Consolidate emotional history: mark core memories, fade weak ones.
|
|
41
|
+
* Called at session end or when history overflows.
|
|
42
|
+
*/
|
|
43
|
+
export declare function consolidateHistory(snapshots: ChemicalSnapshot[], maxEntries?: number): ChemicalSnapshot[];
|
|
44
|
+
/**
|
|
45
|
+
* Retrieve memories related to current chemistry and stimulus.
|
|
46
|
+
* Uses chemical similarity + stimulus matching + core memory bonus.
|
|
47
|
+
*/
|
|
48
|
+
export declare function retrieveRelatedMemories(history: ChemicalSnapshot[], currentChemistry: ChemicalState, stimulus: StimulusType | null, limit?: number): ChemicalSnapshot[];
|
|
29
49
|
/**
|
|
30
50
|
* Load psyche state from workspace. Auto-initializes if missing.
|
|
31
51
|
* Handles v1→v2 migration transparently.
|
package/dist/psyche-file.js
CHANGED
|
@@ -7,7 +7,7 @@ import { join } from "node:path";
|
|
|
7
7
|
import { CHEMICAL_KEYS, CHEMICAL_NAMES, CHEMICAL_NAMES_ZH, DEFAULT_RELATIONSHIP, DEFAULT_DRIVES, DEFAULT_LEARNING_STATE, DEFAULT_METACOGNITIVE_STATE, DEFAULT_PERSONHOOD_STATE, MAX_EMOTIONAL_HISTORY, MAX_RELATIONSHIP_MEMORY, } from "./types.js";
|
|
8
8
|
import { getBaseline, getDefaultSelfModel, extractMBTI, getSensitivity, getTemperament } from "./profiles.js";
|
|
9
9
|
import { applyDecay, detectEmotions } from "./chemistry.js";
|
|
10
|
-
import { decayDrives, computeEffectiveBaseline } from "./drives.js";
|
|
10
|
+
import { decayDrives, computeEffectiveBaseline, updateTraitDrift } from "./drives.js";
|
|
11
11
|
import { t } from "./i18n.js";
|
|
12
12
|
import { computeSelfReflection } from "./self-recognition.js";
|
|
13
13
|
const STATE_FILE = "psyche-state.json";
|
|
@@ -144,11 +144,16 @@ export function pushSnapshot(state, stimulus) {
|
|
|
144
144
|
const dominantEmotion = emotions.length > 0
|
|
145
145
|
? (state.meta.locale === "en" ? emotions[0].name : emotions[0].nameZh)
|
|
146
146
|
: null;
|
|
147
|
+
// P11: Compute intensity and valence for memory consolidation
|
|
148
|
+
const intensity = computeSnapshotIntensity(state.current, state.baseline);
|
|
149
|
+
const valence = computeSnapshotValence(state.current);
|
|
147
150
|
const snapshot = {
|
|
148
151
|
chemistry: { ...state.current },
|
|
149
152
|
stimulus,
|
|
150
153
|
dominantEmotion,
|
|
151
154
|
timestamp: new Date().toISOString(),
|
|
155
|
+
intensity,
|
|
156
|
+
valence,
|
|
152
157
|
};
|
|
153
158
|
const history = [...(state.emotionalHistory ?? []), snapshot];
|
|
154
159
|
let updatedRelationships = state.relationships;
|
|
@@ -291,13 +296,118 @@ export function compressSession(state, userId) {
|
|
|
291
296
|
}
|
|
292
297
|
const updatedRel = { ...existing, memory };
|
|
293
298
|
const updatedRelationships = { ...state.relationships, [relKey]: updatedRel };
|
|
294
|
-
// ──
|
|
299
|
+
// ── P11: Consolidate and keep only core memories ──
|
|
300
|
+
const consolidated = consolidateHistory(history, MAX_EMOTIONAL_HISTORY);
|
|
301
|
+
const coreMemories = consolidated.filter((s) => s.isCoreMemory);
|
|
302
|
+
// ── v9: Update trait drift from session patterns ──
|
|
303
|
+
const currentDrift = state.traitDrift ?? {
|
|
304
|
+
accumulators: { praiseExposure: 0, pressureExposure: 0, neglectExposure: 0, connectionExposure: 0, conflictExposure: 0 },
|
|
305
|
+
sessionCount: 0,
|
|
306
|
+
baselineDelta: {},
|
|
307
|
+
decayRateModifiers: {},
|
|
308
|
+
sensitivityModifiers: {},
|
|
309
|
+
};
|
|
310
|
+
const updatedDrift = updateTraitDrift(currentDrift, history, state.learning);
|
|
311
|
+
// ── Clear non-core history, preserve core memories ──
|
|
295
312
|
return {
|
|
296
313
|
...state,
|
|
297
|
-
emotionalHistory:
|
|
314
|
+
emotionalHistory: coreMemories,
|
|
298
315
|
relationships: updatedRelationships,
|
|
316
|
+
traitDrift: updatedDrift,
|
|
299
317
|
};
|
|
300
318
|
}
|
|
319
|
+
// ── P11: Emotional Memory Consolidation (McGaugh/Squire) ────
|
|
320
|
+
/**
|
|
321
|
+
* Compute snapshot intensity: how far current chemistry deviates from baseline.
|
|
322
|
+
* Returns 0-1 (0 = at baseline, 1 = maximum possible deviation).
|
|
323
|
+
*/
|
|
324
|
+
export function computeSnapshotIntensity(current, baseline) {
|
|
325
|
+
let totalDeviation = 0;
|
|
326
|
+
for (const key of CHEMICAL_KEYS) {
|
|
327
|
+
totalDeviation += Math.abs(current[key] - baseline[key]);
|
|
328
|
+
}
|
|
329
|
+
return Math.min(1, totalDeviation / 600);
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Compute emotional valence from chemistry.
|
|
333
|
+
* Returns -1 (negative) to 1 (positive).
|
|
334
|
+
*/
|
|
335
|
+
export function computeSnapshotValence(chemistry) {
|
|
336
|
+
const raw = ((chemistry.DA - 50) + (chemistry.HT - 50) + (chemistry.OT - 50)
|
|
337
|
+
+ (chemistry.END - 50) - (chemistry.CORT - 50) - (chemistry.NE - 50) * 0.3) / 250;
|
|
338
|
+
return Math.max(-1, Math.min(1, raw));
|
|
339
|
+
}
|
|
340
|
+
/** Minimum intensity threshold for a snapshot to be stored (P11) */
|
|
341
|
+
const INTENSITY_STORE_THRESHOLD = 0.15;
|
|
342
|
+
/** Core memory intensity threshold */
|
|
343
|
+
const CORE_MEMORY_THRESHOLD = 0.6;
|
|
344
|
+
/** Maximum core memories to keep */
|
|
345
|
+
const MAX_CORE_MEMORIES = 5;
|
|
346
|
+
/**
|
|
347
|
+
* Consolidate emotional history: mark core memories, fade weak ones.
|
|
348
|
+
* Called at session end or when history overflows.
|
|
349
|
+
*/
|
|
350
|
+
export function consolidateHistory(snapshots, maxEntries = MAX_EMOTIONAL_HISTORY) {
|
|
351
|
+
if (snapshots.length === 0)
|
|
352
|
+
return [];
|
|
353
|
+
// Mark core memories
|
|
354
|
+
const marked = snapshots.map((s) => ({
|
|
355
|
+
...s,
|
|
356
|
+
isCoreMemory: s.isCoreMemory || (s.intensity !== undefined && s.intensity >= CORE_MEMORY_THRESHOLD),
|
|
357
|
+
}));
|
|
358
|
+
// Separate core and non-core
|
|
359
|
+
const core = marked.filter((s) => s.isCoreMemory);
|
|
360
|
+
const nonCore = marked.filter((s) => !s.isCoreMemory);
|
|
361
|
+
// Enforce core memory limit (always, not just when over maxEntries)
|
|
362
|
+
let keptCore = core;
|
|
363
|
+
if (core.length > MAX_CORE_MEMORIES) {
|
|
364
|
+
const sorted = [...core].sort((a, b) => (b.intensity ?? 0) - (a.intensity ?? 0));
|
|
365
|
+
keptCore = sorted.slice(0, MAX_CORE_MEMORIES);
|
|
366
|
+
// Demoted cores become non-core
|
|
367
|
+
const demoted = sorted.slice(MAX_CORE_MEMORIES).map((s) => ({ ...s, isCoreMemory: false }));
|
|
368
|
+
nonCore.push(...demoted);
|
|
369
|
+
}
|
|
370
|
+
// If within limits after core trimming, keep all
|
|
371
|
+
if (keptCore.length + nonCore.length <= maxEntries) {
|
|
372
|
+
return [...keptCore, ...nonCore]
|
|
373
|
+
.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
374
|
+
}
|
|
375
|
+
// Fill remaining slots with non-core (highest intensity first)
|
|
376
|
+
const remainingSlots = maxEntries - keptCore.length;
|
|
377
|
+
const keptNonCore = [...nonCore]
|
|
378
|
+
.sort((a, b) => (b.intensity ?? 0) - (a.intensity ?? 0))
|
|
379
|
+
.slice(0, Math.max(0, remainingSlots));
|
|
380
|
+
// Merge and sort chronologically
|
|
381
|
+
return [...keptCore, ...keptNonCore]
|
|
382
|
+
.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Retrieve memories related to current chemistry and stimulus.
|
|
386
|
+
* Uses chemical similarity + stimulus matching + core memory bonus.
|
|
387
|
+
*/
|
|
388
|
+
export function retrieveRelatedMemories(history, currentChemistry, stimulus, limit = 3) {
|
|
389
|
+
if (history.length === 0)
|
|
390
|
+
return [];
|
|
391
|
+
const scored = history.map((snap) => {
|
|
392
|
+
// Chemical similarity (Euclidean distance normalized)
|
|
393
|
+
let sumSqDiff = 0;
|
|
394
|
+
for (const key of CHEMICAL_KEYS) {
|
|
395
|
+
const diff = snap.chemistry[key] - currentChemistry[key];
|
|
396
|
+
sumSqDiff += diff * diff;
|
|
397
|
+
}
|
|
398
|
+
const maxDist = Math.sqrt(6) * 100; // theoretical max distance
|
|
399
|
+
const similarity = 1 - Math.sqrt(sumSqDiff) / maxDist;
|
|
400
|
+
// Stimulus match bonus
|
|
401
|
+
const stimulusBonus = (stimulus && snap.stimulus === stimulus) ? 0.2 : 0;
|
|
402
|
+
// Core memory bonus
|
|
403
|
+
const coreBonus = snap.isCoreMemory ? 0.1 : 0;
|
|
404
|
+
return { snap, score: similarity + stimulusBonus + coreBonus };
|
|
405
|
+
});
|
|
406
|
+
return scored
|
|
407
|
+
.sort((a, b) => b.score - a.score)
|
|
408
|
+
.slice(0, limit)
|
|
409
|
+
.map((s) => s.snap);
|
|
410
|
+
}
|
|
301
411
|
/**
|
|
302
412
|
* Load psyche state from workspace. Auto-initializes if missing.
|
|
303
413
|
* Handles v1→v2 migration transparently.
|