psyche-ai 2.3.0 → 3.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/README.md CHANGED
@@ -161,6 +161,8 @@ cd openclaw-plugin-psyche && node scripts/diagnose.js
161
161
  - **流式支持** — Vercel AI SDK `streamText` 中间件,自动缓冲和剥离标签
162
162
  - **渠道修饰** — Discord/Slack/飞书/终端等不同渠道自动调整表达风格
163
163
  - **自定义人格** — 超越 MBTI 预设,完全自定义 baseline/敏感度/气质
164
+ - **情绪学习** — 从交互结果中学习,调整情绪反应参数(躯体标记假说)
165
+ - **上下文分类** — 关系/驱力/历史感知的刺激分类,超越简单正则
164
166
  - **Compact Mode** — 算法做化学计算,LLM 只看行为指令(~15-180 tokens vs ~550)
165
167
 
166
168
  架构详情见 [ARCHITECTURE.md](ARCHITECTURE.md)。
@@ -170,7 +172,7 @@ cd openclaw-plugin-psyche && node scripts/diagnose.js
170
172
  ```bash
171
173
  npm install
172
174
  npm run build
173
- npm test # 469 tests
175
+ npm test # 525 tests
174
176
  npm run typecheck # strict mode
175
177
  ```
176
178
 
@@ -0,0 +1,39 @@
1
+ import type { StimulusType, DriveType, RelationshipState, PsycheState } from "./types.js";
2
+ export interface ContextFeatures {
3
+ relationshipPhase: RelationshipState["phase"];
4
+ recentStimuli: StimulusType[];
5
+ driveSatisfaction: Record<DriveType, "high" | "mid" | "low">;
6
+ timeSinceLastMessage: number;
7
+ totalInteractions: number;
8
+ agreementStreak: number;
9
+ }
10
+ export interface ContextualClassification {
11
+ type: StimulusType;
12
+ baseConfidence: number;
13
+ contextConfidence: number;
14
+ contextModifiers: string[];
15
+ }
16
+ /**
17
+ * Extract contextual features from the current psyche state.
18
+ * Used to feed into classifyStimulusWithContext.
19
+ */
20
+ export declare function extractContextFeatures(state: PsycheState, userId?: string): ContextFeatures;
21
+ /**
22
+ * Classify a stimulus with context modifiers applied.
23
+ *
24
+ * Wraps classifyStimulus(text) and adjusts confidence based on:
25
+ * - Relationship depth
26
+ * - Recent stimulus patterns
27
+ * - Drive hunger
28
+ * - Agreement streak
29
+ * - Time gap
30
+ *
31
+ * Returns results sorted by contextConfidence descending.
32
+ */
33
+ export declare function classifyStimulusWithContext(text: string, context: ContextFeatures): ContextualClassification[];
34
+ /**
35
+ * Map a stimulus type to a warmth score for outcome evaluation.
36
+ * Positive stimuli return positive values; negative stimuli return negative.
37
+ * null returns 0.
38
+ */
39
+ export declare function stimulusWarmth(stimulus: StimulusType | null): number;
@@ -0,0 +1,204 @@
1
+ // ============================================================
2
+ // Context-Aware Stimulus Classification
3
+ //
4
+ // Wraps classify.ts with contextual signals (relationship depth,
5
+ // recent stimulus patterns, drive hunger, agreement streaks,
6
+ // time gaps) to improve classification accuracy.
7
+ // ============================================================
8
+ import { DRIVE_KEYS } from "./types.js";
9
+ import { classifyStimulus } from "./classify.js";
10
+ // ── Drive Satisfaction Thresholds ────────────────────────────
11
+ function driveSatisfactionLevel(value) {
12
+ if (value >= 70)
13
+ return "high";
14
+ if (value >= 40)
15
+ return "mid";
16
+ return "low";
17
+ }
18
+ // ── Extract Context Features ─────────────────────────────────
19
+ /**
20
+ * Extract contextual features from the current psyche state.
21
+ * Used to feed into classifyStimulusWithContext.
22
+ */
23
+ export function extractContextFeatures(state, userId) {
24
+ // Relationship phase
25
+ const relKey = userId ?? "_default";
26
+ const relationship = state.relationships[relKey] ?? state.relationships["_default"];
27
+ const relationshipPhase = relationship?.phase ?? "stranger";
28
+ // Recent stimuli from emotional history (last 3)
29
+ const recentStimuli = state.emotionalHistory
30
+ .slice(-3)
31
+ .map((snap) => snap.stimulus)
32
+ .filter((s) => s !== null);
33
+ // Drive satisfaction levels
34
+ const driveSatisfaction = {};
35
+ for (const key of DRIVE_KEYS) {
36
+ driveSatisfaction[key] = driveSatisfactionLevel(state.drives[key]);
37
+ }
38
+ // Time since last message (minutes)
39
+ const now = Date.now();
40
+ const updatedAt = new Date(state.updatedAt).getTime();
41
+ const timeSinceLastMessage = Math.max(0, (now - updatedAt) / 60_000);
42
+ // Total interactions
43
+ const totalInteractions = state.meta.totalInteractions;
44
+ // Agreement streak
45
+ const agreementStreak = state.agreementStreak;
46
+ return {
47
+ relationshipPhase,
48
+ recentStimuli,
49
+ driveSatisfaction,
50
+ timeSinceLastMessage,
51
+ totalInteractions,
52
+ agreementStreak,
53
+ };
54
+ }
55
+ // ── Context-Adjusted Classification ──────────────────────────
56
+ /**
57
+ * Classify a stimulus with context modifiers applied.
58
+ *
59
+ * Wraps classifyStimulus(text) and adjusts confidence based on:
60
+ * - Relationship depth
61
+ * - Recent stimulus patterns
62
+ * - Drive hunger
63
+ * - Agreement streak
64
+ * - Time gap
65
+ *
66
+ * Returns results sorted by contextConfidence descending.
67
+ */
68
+ export function classifyStimulusWithContext(text, context) {
69
+ const baseResults = classifyStimulus(text);
70
+ const results = baseResults.map((r) => ({
71
+ type: r.type,
72
+ baseConfidence: r.confidence,
73
+ contextConfidence: r.confidence,
74
+ contextModifiers: [],
75
+ }));
76
+ // Ensure all stimulus types that need boosting have an entry
77
+ const ensureType = (type) => {
78
+ let entry = results.find((r) => r.type === type);
79
+ if (!entry) {
80
+ entry = {
81
+ type,
82
+ baseConfidence: 0,
83
+ contextConfidence: 0,
84
+ contextModifiers: [],
85
+ };
86
+ results.push(entry);
87
+ }
88
+ return entry;
89
+ };
90
+ for (const r of results) {
91
+ // ── Relationship depth modifiers ──
92
+ if (context.relationshipPhase === "stranger" && r.type === "intimacy") {
93
+ r.contextConfidence *= 0.7;
94
+ r.contextModifiers.push("stranger penalty on intimacy");
95
+ }
96
+ if ((context.relationshipPhase === "close" || context.relationshipPhase === "deep") &&
97
+ r.type === "casual") {
98
+ r.contextConfidence += 0.1;
99
+ r.contextModifiers.push("close relationship boost on casual");
100
+ }
101
+ if (context.relationshipPhase === "stranger" && r.type === "vulnerability") {
102
+ r.contextConfidence *= 0.6;
103
+ r.contextModifiers.push("stranger penalty on vulnerability");
104
+ }
105
+ // ── Recent stimulus pattern modifiers ──
106
+ // Same stimulus 3x in a row → confidence * 0.8 (repetition fatigue)
107
+ if (context.recentStimuli.length >= 3 &&
108
+ context.recentStimuli.every((s) => s === r.type)) {
109
+ r.contextConfidence *= 0.8;
110
+ r.contextModifiers.push("repetition fatigue penalty");
111
+ }
112
+ // ── Agreement streak modifiers ──
113
+ if (context.agreementStreak >= 5 && r.type === "validation") {
114
+ r.contextConfidence *= 0.8;
115
+ r.contextModifiers.push("sycophantic loop dampening on validation");
116
+ }
117
+ // ── Time gap modifiers ──
118
+ if (context.timeSinceLastMessage > 1440) {
119
+ if (r.type === "casual") {
120
+ r.contextConfidence += 0.1;
121
+ r.contextModifiers.push("long absence boost on casual");
122
+ }
123
+ if (r.type === "intimacy") {
124
+ r.contextConfidence *= 0.9;
125
+ r.contextModifiers.push("long absence penalty on intimacy");
126
+ }
127
+ }
128
+ }
129
+ // ── De-escalation pattern (conflict → casual) ──
130
+ if (context.recentStimuli.length > 0 &&
131
+ context.recentStimuli[context.recentStimuli.length - 1] === "conflict") {
132
+ const casual = ensureType("casual");
133
+ casual.contextConfidence += 0.15;
134
+ casual.contextModifiers.push("de-escalation boost after conflict");
135
+ }
136
+ // ── Fake praise follow-up (praise → sarcasm) ──
137
+ if (context.recentStimuli.length > 0 &&
138
+ context.recentStimuli[context.recentStimuli.length - 1] === "praise") {
139
+ const sarcasm = results.find((r) => r.type === "sarcasm");
140
+ if (sarcasm) {
141
+ sarcasm.contextConfidence += 0.1;
142
+ sarcasm.contextModifiers.push("possible fake praise follow-up boost on sarcasm");
143
+ }
144
+ }
145
+ // ── Drive-hunger modifiers ──
146
+ if (context.driveSatisfaction.connection === "low") {
147
+ // Positive stimuli get a warmth boost
148
+ const positiveTypes = [
149
+ "praise", "validation", "intimacy", "humor", "casual", "vulnerability",
150
+ ];
151
+ for (const r of results) {
152
+ if (positiveTypes.includes(r.type)) {
153
+ r.contextConfidence += 0.05;
154
+ r.contextModifiers.push("connection hunger boost");
155
+ }
156
+ }
157
+ }
158
+ if (context.driveSatisfaction.esteem === "low") {
159
+ for (const r of results) {
160
+ if (r.type === "validation" || r.type === "praise") {
161
+ r.contextConfidence += 0.05;
162
+ r.contextModifiers.push("esteem hunger boost");
163
+ }
164
+ }
165
+ }
166
+ if (context.driveSatisfaction.survival === "low") {
167
+ for (const r of results) {
168
+ if (r.type === "authority" || r.type === "conflict") {
169
+ r.contextConfidence += 0.1;
170
+ r.contextModifiers.push("survival threat sensitivity boost");
171
+ }
172
+ }
173
+ }
174
+ // ── Sort by contextConfidence descending ──
175
+ results.sort((a, b) => b.contextConfidence - a.contextConfidence);
176
+ return results;
177
+ }
178
+ // ── Warmth Scoring ───────────────────────────────────────────
179
+ const WARMTH_MAP = {
180
+ praise: 0.8,
181
+ validation: 0.7,
182
+ intimacy: 0.9,
183
+ humor: 0.5,
184
+ surprise: 0.3,
185
+ casual: 0.1,
186
+ intellectual: 0.2,
187
+ vulnerability: 0.3,
188
+ sarcasm: -0.5,
189
+ criticism: -0.7,
190
+ conflict: -0.9,
191
+ authority: -0.4,
192
+ neglect: -0.8,
193
+ boredom: -0.3,
194
+ };
195
+ /**
196
+ * Map a stimulus type to a warmth score for outcome evaluation.
197
+ * Positive stimuli return positive values; negative stimuli return negative.
198
+ * null returns 0.
199
+ */
200
+ export function stimulusWarmth(stimulus) {
201
+ if (stimulus === null)
202
+ return 0;
203
+ return WARMTH_MAP[stimulus] ?? 0;
204
+ }
package/dist/core.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { PsycheState, StimulusType, Locale, MBTIType } from "./types.js";
1
+ import type { PsycheState, StimulusType, Locale, MBTIType, OutcomeScore } from "./types.js";
2
2
  import type { StorageAdapter } from "./storage.js";
3
3
  export interface PsycheEngineConfig {
4
4
  mbti?: MBTIType;
@@ -24,11 +24,19 @@ export interface ProcessOutputResult {
24
24
  /** Whether chemistry was meaningfully updated (contagion or psyche_update) */
25
25
  stateChanged: boolean;
26
26
  }
27
+ export interface ProcessOutcomeResult {
28
+ /** Outcome evaluation score (-1 to 1) */
29
+ outcomeScore: OutcomeScore;
30
+ /** Whether learning state was updated */
31
+ learningUpdated: boolean;
32
+ }
27
33
  export declare class PsycheEngine {
28
34
  private state;
29
35
  private readonly storage;
30
36
  private readonly cfg;
31
37
  private readonly protocolCache;
38
+ /** Pending prediction from last processInput for auto-learning */
39
+ private pendingPrediction;
32
40
  constructor(config: PsycheEngineConfig | undefined, storage: StorageAdapter);
33
41
  /**
34
42
  * Load or create initial state. Must be called before processInput/processOutput.
@@ -48,6 +56,19 @@ export declare class PsycheEngine {
48
56
  processOutput(text: string, opts?: {
49
57
  userId?: string;
50
58
  }): Promise<ProcessOutputResult>;
59
+ /**
60
+ * Phase 3 (optional): Explicitly evaluate the outcome of the last interaction.
61
+ *
62
+ * This is automatically called at the start of processInput, so most users
63
+ * don't need to call it manually. Use this for explicit outcome evaluation
64
+ * (e.g., when a session ends without a follow-up message).
65
+ *
66
+ * @param nextUserStimulus - The stimulus detected in the user's next message,
67
+ * or null if the session ended.
68
+ */
69
+ processOutcome(nextUserStimulus: StimulusType | null, opts?: {
70
+ userId?: string;
71
+ }): Promise<ProcessOutcomeResult | null>;
51
72
  /**
52
73
  * Get the current psyche state (read-only snapshot).
53
74
  */
package/dist/core.js CHANGED
@@ -1,13 +1,17 @@
1
1
  // ============================================================
2
2
  // PsycheEngine — Framework-agnostic emotional intelligence core
3
3
  //
4
- // Two-phase API:
5
- // processInput(text) → systemContext + dynamicContext + stimulus
6
- // processOutput(text) → cleanedText + stateChanged
4
+ // Three-phase API:
5
+ // processInput(text) → systemContext + dynamicContext + stimulus
6
+ // processOutput(text) → cleanedText + stateChanged
7
+ // processOutcome(text) → outcomeScore (optional: evaluate last interaction)
7
8
  //
8
- // Orchestrates: chemistry, classify, prompt, profiles, guards
9
+ // Auto-learning: processInput auto-evaluates the previous turn's
10
+ // outcome using the new user message as the outcome signal.
11
+ //
12
+ // Orchestrates: chemistry, classify, prompt, profiles, guards, learning
9
13
  // ============================================================
10
- import { DEFAULT_RELATIONSHIP, DEFAULT_DRIVES } from "./types.js";
14
+ import { DEFAULT_RELATIONSHIP, DEFAULT_DRIVES, DEFAULT_LEARNING_STATE } from "./types.js";
11
15
  import { applyDecay, applyStimulus, applyContagion, clamp } from "./chemistry.js";
12
16
  import { classifyStimulus } from "./classify.js";
13
17
  import { buildDynamicContext, buildProtocolContext, buildCompactContext } from "./prompt.js";
@@ -16,6 +20,7 @@ import { isStimulusType } from "./guards.js";
16
20
  import { parsePsycheUpdate, mergeUpdates, updateAgreementStreak, pushSnapshot, } from "./psyche-file.js";
17
21
  import { decayDrives, feedDrives, detectExistentialThreat, computeEffectiveBaseline, computeEffectiveSensitivity, } from "./drives.js";
18
22
  import { checkForUpdate } from "./update.js";
23
+ import { evaluateOutcome, computeContextHash, updateLearnedVector, predictChemistry, recordPrediction, } from "./learning.js";
19
24
  const NOOP_LOGGER = { info: () => { }, warn: () => { }, debug: () => { } };
20
25
  // ── PsycheEngine ─────────────────────────────────────────────
21
26
  export class PsycheEngine {
@@ -23,6 +28,8 @@ export class PsycheEngine {
23
28
  storage;
24
29
  cfg;
25
30
  protocolCache = new Map();
31
+ /** Pending prediction from last processInput for auto-learning */
32
+ pendingPrediction = null;
26
33
  constructor(config = {}, storage) {
27
34
  this.storage = storage;
28
35
  this.cfg = {
@@ -41,6 +48,11 @@ export class PsycheEngine {
41
48
  async initialize() {
42
49
  const loaded = await this.storage.load();
43
50
  if (loaded) {
51
+ // Migrate v3 → v4: add learning state if missing
52
+ if (!loaded.learning) {
53
+ loaded.learning = { ...DEFAULT_LEARNING_STATE };
54
+ loaded.version = 4;
55
+ }
44
56
  this.state = loaded;
45
57
  }
46
58
  else {
@@ -56,6 +68,29 @@ export class PsycheEngine {
56
68
  */
57
69
  async processInput(text, opts) {
58
70
  let state = this.ensureInitialized();
71
+ // ── Auto-learning: evaluate previous turn's outcome ──────
72
+ if (this.pendingPrediction && text.length > 0) {
73
+ const nextClassifications = classifyStimulus(text);
74
+ const nextStimulus = (nextClassifications[0]?.confidence ?? 0) >= 0.5
75
+ ? nextClassifications[0].type
76
+ : null;
77
+ const outcome = evaluateOutcome(this.pendingPrediction.preInteractionState, state, nextStimulus, this.pendingPrediction.appliedStimulus);
78
+ // Record prediction accuracy
79
+ state = {
80
+ ...state,
81
+ learning: recordPrediction(state.learning, this.pendingPrediction.predictedChemistry, state.current, this.pendingPrediction.appliedStimulus),
82
+ };
83
+ // Update learned vectors based on outcome
84
+ if (this.pendingPrediction.appliedStimulus) {
85
+ state = {
86
+ ...state,
87
+ learning: updateLearnedVector(state.learning, this.pendingPrediction.appliedStimulus, this.pendingPrediction.contextHash, outcome.adaptiveScore, state.current, state.baseline),
88
+ };
89
+ }
90
+ this.pendingPrediction = null;
91
+ }
92
+ // ── Snapshot pre-interaction state for next turn's outcome evaluation
93
+ const preInteractionState = { ...state };
59
94
  // Time decay toward baseline (chemistry + drives)
60
95
  const now = new Date();
61
96
  const minutesElapsed = (now.getTime() - new Date(state.updatedAt).getTime()) / 60000;
@@ -124,6 +159,21 @@ export class PsycheEngine {
124
159
  ...state,
125
160
  meta: { ...state.meta, totalInteractions: state.meta.totalInteractions + 1 },
126
161
  };
162
+ // ── Generate prediction for next turn's auto-learning ────
163
+ if (appliedStimulus) {
164
+ const ctxHash = computeContextHash(state, opts?.userId);
165
+ const effectiveSensitivity = computeEffectiveSensitivity(getSensitivity(state.mbti), state.drives, appliedStimulus);
166
+ const predicted = predictChemistry(preInteractionState.current, appliedStimulus, state.learning, ctxHash, effectiveSensitivity, this.cfg.maxChemicalDelta);
167
+ this.pendingPrediction = {
168
+ predictedChemistry: predicted,
169
+ preInteractionState,
170
+ appliedStimulus,
171
+ contextHash: ctxHash,
172
+ };
173
+ }
174
+ else {
175
+ this.pendingPrediction = null;
176
+ }
127
177
  // Persist
128
178
  this.state = state;
129
179
  await this.storage.save(state);
@@ -185,6 +235,41 @@ export class PsycheEngine {
185
235
  }
186
236
  return { cleanedText, stateChanged };
187
237
  }
238
+ /**
239
+ * Phase 3 (optional): Explicitly evaluate the outcome of the last interaction.
240
+ *
241
+ * This is automatically called at the start of processInput, so most users
242
+ * don't need to call it manually. Use this for explicit outcome evaluation
243
+ * (e.g., when a session ends without a follow-up message).
244
+ *
245
+ * @param nextUserStimulus - The stimulus detected in the user's next message,
246
+ * or null if the session ended.
247
+ */
248
+ async processOutcome(nextUserStimulus, opts) {
249
+ if (!this.pendingPrediction)
250
+ return null;
251
+ let state = this.ensureInitialized();
252
+ const pending = this.pendingPrediction;
253
+ this.pendingPrediction = null;
254
+ const outcome = evaluateOutcome(pending.preInteractionState, state, nextUserStimulus, pending.appliedStimulus);
255
+ // Record prediction
256
+ state = {
257
+ ...state,
258
+ learning: recordPrediction(state.learning, pending.predictedChemistry, state.current, pending.appliedStimulus),
259
+ };
260
+ // Update learned vectors
261
+ let learningUpdated = false;
262
+ if (pending.appliedStimulus) {
263
+ state = {
264
+ ...state,
265
+ learning: updateLearnedVector(state.learning, pending.appliedStimulus, pending.contextHash, outcome.adaptiveScore, state.current, state.baseline),
266
+ };
267
+ learningUpdated = true;
268
+ }
269
+ this.state = state;
270
+ await this.storage.save(state);
271
+ return { outcomeScore: outcome, learningUpdated };
272
+ }
188
273
  /**
189
274
  * Get the current psyche state (read-only snapshot).
190
275
  */
@@ -216,7 +301,7 @@ export class PsycheEngine {
216
301
  const selfModel = getDefaultSelfModel(mbti);
217
302
  const now = new Date().toISOString();
218
303
  return {
219
- version: 3,
304
+ version: 4,
220
305
  mbti,
221
306
  baseline,
222
307
  current: { ...baseline },
@@ -228,6 +313,7 @@ export class PsycheEngine {
228
313
  emotionalHistory: [],
229
314
  agreementStreak: 0,
230
315
  lastDisagreement: null,
316
+ learning: { ...DEFAULT_LEARNING_STATE },
231
317
  meta: {
232
318
  agentName: name,
233
319
  createdAt: now,
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  export { PsycheEngine } from "./core.js";
2
- export type { PsycheEngineConfig, ProcessInputResult, ProcessOutputResult } from "./core.js";
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, } from "./types.js";
6
- export { CHEMICAL_KEYS, CHEMICAL_NAMES, CHEMICAL_NAMES_ZH, DEFAULT_RELATIONSHIP, DEFAULT_DRIVES, 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, } from "./types.js";
6
+ export { CHEMICAL_KEYS, CHEMICAL_NAMES, CHEMICAL_NAMES_ZH, DEFAULT_RELATIONSHIP, DEFAULT_DRIVES, DEFAULT_LEARNING_STATE, DRIVE_KEYS, DRIVE_NAMES_ZH, } 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";
@@ -12,6 +12,9 @@ export { getChannelProfile, buildChannelModifier, createCustomChannel } from "./
12
12
  export type { ChannelType, ChannelProfile } from "./channels.js";
13
13
  export { createCustomProfile, validateProfileConfig, PRESET_PROFILES } from "./custom-profile.js";
14
14
  export type { CustomProfileConfig, ResolvedProfile } from "./custom-profile.js";
15
+ export { evaluateOutcome, getLearnedVector, updateLearnedVector, computeContextHash, predictChemistry, computePredictionError, recordPrediction, getAveragePredictionError, } from "./learning.js";
16
+ export { classifyStimulusWithContext, extractContextFeatures, stimulusWarmth } from "./context-classifier.js";
17
+ export type { ContextFeatures, ContextualClassification } from "./context-classifier.js";
15
18
  export { classifyStimulus, getPrimaryStimulus } from "./classify.js";
16
19
  export { buildProtocolContext, buildDynamicContext, buildCompactContext, isNearBaseline } from "./prompt.js";
17
20
  export { describeEmotionalState, getExpressionHint, getBehaviorGuide } from "./chemistry.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, DRIVE_KEYS, DRIVE_NAMES_ZH, } from "./types.js";
15
+ export { CHEMICAL_KEYS, CHEMICAL_NAMES, CHEMICAL_NAMES_ZH, DEFAULT_RELATIONSHIP, DEFAULT_DRIVES, DEFAULT_LEARNING_STATE, DRIVE_KEYS, DRIVE_NAMES_ZH, } from "./types.js";
16
16
  // Self-recognition
17
17
  export { computeSelfReflection, computeEmotionalTendency, buildSelfReflectionContext } from "./self-recognition.js";
18
18
  // Multi-agent interaction
@@ -21,6 +21,10 @@ export { PsycheInteraction } from "./interaction.js";
21
21
  export { getChannelProfile, buildChannelModifier, createCustomChannel } from "./channels.js";
22
22
  // Custom profiles — beyond MBTI presets
23
23
  export { createCustomProfile, validateProfileConfig, PRESET_PROFILES } from "./custom-profile.js";
24
+ // Emotional learning (P3)
25
+ export { evaluateOutcome, getLearnedVector, updateLearnedVector, computeContextHash, predictChemistry, computePredictionError, recordPrediction, getAveragePredictionError, } from "./learning.js";
26
+ // Context-aware classification (P3)
27
+ export { classifyStimulusWithContext, extractContextFeatures, stimulusWarmth } from "./context-classifier.js";
24
28
  // Utilities — for custom adapter / advanced use
25
29
  export { classifyStimulus, getPrimaryStimulus } from "./classify.js";
26
30
  export { buildProtocolContext, buildDynamicContext, buildCompactContext, isNearBaseline } from "./prompt.js";
@@ -0,0 +1,56 @@
1
+ import type { PsycheState, StimulusType, ChemicalState, StimulusVector, LearningState, OutcomeScore } from "./types.js";
2
+ /**
3
+ * Evaluate the adaptive outcome of an interaction turn.
4
+ *
5
+ * Computes a score from -1 to 1 using multiple signals:
6
+ * drive changes, relationship changes, user warmth, conversation continuation.
7
+ */
8
+ export declare function evaluateOutcome(prevState: PsycheState, currentState: PsycheState, nextUserStimulus: StimulusType | null, appliedStimulus: StimulusType | null): OutcomeScore;
9
+ /**
10
+ * Get the effective stimulus vector for a given stimulus + context,
11
+ * combining the base vector with any learned adjustment.
12
+ */
13
+ export declare function getLearnedVector(learning: LearningState, stimulus: StimulusType, contextHash: string): StimulusVector;
14
+ /**
15
+ * Update a learned vector adjustment based on an outcome.
16
+ *
17
+ * Learning rule:
18
+ * - Positive outcome → reinforce (adjust toward actual delta)
19
+ * - Negative outcome → suppress (adjust away from actual delta)
20
+ * - Learning rate: 0.05 * |outcomeScore|
21
+ * - Each adjustment clamped to +/- 50% of base vector value
22
+ */
23
+ export declare function updateLearnedVector(learning: LearningState, stimulus: StimulusType, contextHash: string, outcomeScore: number, actualChemistry: ChemicalState, baselineChemistry: ChemicalState): LearningState;
24
+ /**
25
+ * Compute a context hash from the current psyche state.
26
+ *
27
+ * Format: "{phase}:{last3stimuli}:{driveLevels}"
28
+ * Drive levels are encoded as h(igh)/m(id)/l(ow) for each of the 5 drives.
29
+ *
30
+ * Example: "familiar:praise,casual,intellectual:hml_hh"
31
+ */
32
+ export declare function computeContextHash(state: PsycheState, _userId?: string): string;
33
+ /**
34
+ * Predict the resulting chemistry after applying a stimulus,
35
+ * using learned vectors instead of raw base vectors.
36
+ *
37
+ * Same math as applyStimulus in chemistry.ts but with learned adjustments.
38
+ */
39
+ export declare function predictChemistry(current: ChemicalState, stimulus: StimulusType, learning: LearningState, contextHash: string, sensitivity: number, maxDelta: number): ChemicalState;
40
+ /**
41
+ * Compute the prediction error between predicted and actual chemistry.
42
+ *
43
+ * Euclidean distance across all 6 chemicals, normalized to 0-1 range.
44
+ * Normalization factor: sqrt(6 * 100^2) = sqrt(60000) ~= 244.95
45
+ */
46
+ export declare function computePredictionError(predicted: ChemicalState, actual: ChemicalState): number;
47
+ /**
48
+ * Record a prediction and its actual outcome.
49
+ * Pushes to predictionHistory and trims to MAX_PREDICTION_HISTORY.
50
+ */
51
+ export declare function recordPrediction(learning: LearningState, predicted: ChemicalState, actual: ChemicalState, stimulus: StimulusType | null): LearningState;
52
+ /**
53
+ * Get the average prediction error over recent history.
54
+ * Returns 1.0 if no history exists (maximum uncertainty).
55
+ */
56
+ export declare function getAveragePredictionError(learning: LearningState): number;
@@ -0,0 +1,272 @@
1
+ // ============================================================
2
+ // Emotional Learning Engine — Damasio's Somatic Marker Hypothesis
3
+ //
4
+ // The system learns from interaction outcomes which emotional
5
+ // reactions are adaptive. Over time, stimulus→chemistry mappings
6
+ // shift based on what actually worked in context.
7
+ //
8
+ // Components:
9
+ // 1. OutcomeEvaluator — scores interaction outcomes
10
+ // 2. StimulusVectorStore — learned vector adjustments
11
+ // 3. PredictionEngine — predict & track prediction error
12
+ // ============================================================
13
+ import { CHEMICAL_KEYS, DRIVE_KEYS, MAX_LEARNED_VECTORS, MAX_PREDICTION_HISTORY, } from "./types.js";
14
+ import { STIMULUS_VECTORS, clamp } from "./chemistry.js";
15
+ // ── 1. OutcomeEvaluator ─────────────────────────────────────
16
+ /** Warmth mapping for nextUserStimulus */
17
+ const WARMTH_MAP = {
18
+ praise: 0.8,
19
+ validation: 0.7,
20
+ intimacy: 0.9,
21
+ humor: 0.5,
22
+ casual: 0,
23
+ intellectual: 0.2,
24
+ surprise: 0.3,
25
+ vulnerability: 0.4,
26
+ criticism: -0.6,
27
+ conflict: -0.8,
28
+ neglect: -0.9,
29
+ sarcasm: -0.5,
30
+ authority: -0.4,
31
+ boredom: -0.3,
32
+ };
33
+ /**
34
+ * Evaluate the adaptive outcome of an interaction turn.
35
+ *
36
+ * Computes a score from -1 to 1 using multiple signals:
37
+ * drive changes, relationship changes, user warmth, conversation continuation.
38
+ */
39
+ export function evaluateOutcome(prevState, currentState, nextUserStimulus, appliedStimulus) {
40
+ // Drive delta: sum of all drive changes, normalized
41
+ let driveSum = 0;
42
+ for (const key of DRIVE_KEYS) {
43
+ driveSum += currentState.drives[key] - prevState.drives[key];
44
+ }
45
+ const driveDelta = Math.max(-1, Math.min(1, driveSum / 50));
46
+ // Relationship delta: change in trust + intimacy of _default relationship
47
+ const prevRel = prevState.relationships._default ?? { trust: 50, intimacy: 30 };
48
+ const curRel = currentState.relationships._default ?? { trust: 50, intimacy: 30 };
49
+ const relChange = (curRel.trust - prevRel.trust) + (curRel.intimacy - prevRel.intimacy);
50
+ const relationshipDelta = Math.max(-1, Math.min(1, relChange / 20));
51
+ // User warmth: what the user said next
52
+ const userWarmthDelta = nextUserStimulus !== null ? (WARMTH_MAP[nextUserStimulus] ?? 0) : 0;
53
+ // Conversation continued
54
+ const conversationContinued = nextUserStimulus !== null;
55
+ // Weighted average
56
+ const continuedBonus = conversationContinued ? 0.15 : -0.15;
57
+ const raw = driveDelta * 0.25
58
+ + relationshipDelta * 0.25
59
+ + userWarmthDelta * 0.35
60
+ + continuedBonus;
61
+ const adaptiveScore = Math.max(-1, Math.min(1, raw));
62
+ const signals = {
63
+ driveDelta,
64
+ relationshipDelta,
65
+ userWarmthDelta,
66
+ conversationContinued,
67
+ };
68
+ return {
69
+ turnIndex: currentState.meta.totalInteractions,
70
+ stimulus: appliedStimulus,
71
+ adaptiveScore,
72
+ signals,
73
+ timestamp: new Date().toISOString(),
74
+ };
75
+ }
76
+ // ── 2. StimulusVectorStore ──────────────────────────────────
77
+ /**
78
+ * Get the effective stimulus vector for a given stimulus + context,
79
+ * combining the base vector with any learned adjustment.
80
+ */
81
+ export function getLearnedVector(learning, stimulus, contextHash) {
82
+ const base = STIMULUS_VECTORS[stimulus];
83
+ if (!base) {
84
+ // Unknown stimulus — return zeros
85
+ return { DA: 0, HT: 0, CORT: 0, OT: 0, NE: 0, END: 0 };
86
+ }
87
+ // Look for a learned adjustment matching this stimulus + context
88
+ const entry = learning.learnedVectors.find((v) => v.stimulus === stimulus && v.contextHash === contextHash);
89
+ if (!entry)
90
+ return { ...base };
91
+ // Apply adjustment
92
+ const result = { ...base };
93
+ for (const key of CHEMICAL_KEYS) {
94
+ const adj = entry.adjustment[key] ?? 0;
95
+ result[key] = base[key] + adj;
96
+ }
97
+ return result;
98
+ }
99
+ /**
100
+ * Update a learned vector adjustment based on an outcome.
101
+ *
102
+ * Learning rule:
103
+ * - Positive outcome → reinforce (adjust toward actual delta)
104
+ * - Negative outcome → suppress (adjust away from actual delta)
105
+ * - Learning rate: 0.05 * |outcomeScore|
106
+ * - Each adjustment clamped to +/- 50% of base vector value
107
+ */
108
+ export function updateLearnedVector(learning, stimulus, contextHash, outcomeScore, actualChemistry, baselineChemistry) {
109
+ const base = STIMULUS_VECTORS[stimulus];
110
+ if (!base)
111
+ return learning;
112
+ // Chemistry delta: what actually happened
113
+ const chemDelta = {};
114
+ for (const key of CHEMICAL_KEYS) {
115
+ chemDelta[key] = actualChemistry[key] - baselineChemistry[key];
116
+ }
117
+ // Learning rate: conservative, proportional to outcome strength
118
+ const learningRate = 0.05 * Math.abs(outcomeScore);
119
+ const direction = outcomeScore >= 0 ? 1 : -1;
120
+ // Find or create entry
121
+ const existingIdx = learning.learnedVectors.findIndex((v) => v.stimulus === stimulus && v.contextHash === contextHash);
122
+ let entry;
123
+ if (existingIdx >= 0) {
124
+ entry = { ...learning.learnedVectors[existingIdx] };
125
+ entry.adjustment = { ...entry.adjustment };
126
+ }
127
+ else {
128
+ entry = {
129
+ stimulus,
130
+ contextHash,
131
+ adjustment: {},
132
+ confidence: 0,
133
+ sampleCount: 0,
134
+ lastUpdated: new Date().toISOString(),
135
+ };
136
+ }
137
+ // Update adjustment for each chemical
138
+ for (const key of CHEMICAL_KEYS) {
139
+ const currentAdj = entry.adjustment[key] ?? 0;
140
+ const delta = chemDelta[key] * direction * learningRate;
141
+ let newAdj = currentAdj + delta;
142
+ // Clamp to +/- 50% of base vector absolute value
143
+ const baseAbs = Math.abs(base[key]);
144
+ const maxAdj = Math.max(baseAbs * 0.5, 1); // at least 1 to allow learning on zero-base values
145
+ newAdj = Math.max(-maxAdj, Math.min(maxAdj, newAdj));
146
+ entry.adjustment[key] = newAdj;
147
+ }
148
+ // Update metadata
149
+ entry.sampleCount += 1;
150
+ entry.confidence = 0.9 * entry.confidence + 0.1 * Math.abs(outcomeScore);
151
+ entry.lastUpdated = new Date().toISOString();
152
+ // Build new vectors array
153
+ let newVectors;
154
+ if (existingIdx >= 0) {
155
+ newVectors = [...learning.learnedVectors];
156
+ newVectors[existingIdx] = entry;
157
+ }
158
+ else {
159
+ newVectors = [...learning.learnedVectors, entry];
160
+ }
161
+ // Trim to MAX_LEARNED_VECTORS: keep highest sampleCount entries
162
+ if (newVectors.length > MAX_LEARNED_VECTORS) {
163
+ newVectors.sort((a, b) => b.sampleCount - a.sampleCount);
164
+ newVectors = newVectors.slice(0, MAX_LEARNED_VECTORS);
165
+ }
166
+ return {
167
+ ...learning,
168
+ learnedVectors: newVectors,
169
+ };
170
+ }
171
+ /**
172
+ * Compute a context hash from the current psyche state.
173
+ *
174
+ * Format: "{phase}:{last3stimuli}:{driveLevels}"
175
+ * Drive levels are encoded as h(igh)/m(id)/l(ow) for each of the 5 drives.
176
+ *
177
+ * Example: "familiar:praise,casual,intellectual:hml_hh"
178
+ */
179
+ export function computeContextHash(state, _userId) {
180
+ // Relationship phase
181
+ const rel = state.relationships._default ?? { phase: "stranger" };
182
+ const phase = rel.phase;
183
+ // Last 3 stimuli from emotional history
184
+ const history = state.emotionalHistory ?? [];
185
+ const recentStimuli = history
186
+ .slice(-3)
187
+ .map((s) => s.stimulus ?? "none")
188
+ .join(",");
189
+ // Drive satisfaction levels: h(igh >=67), m(id 34-66), l(ow <34)
190
+ const driveLevels = [];
191
+ for (const key of DRIVE_KEYS) {
192
+ const val = state.drives[key];
193
+ if (val >= 67)
194
+ driveLevels.push("h");
195
+ else if (val >= 34)
196
+ driveLevels.push("m");
197
+ else
198
+ driveLevels.push("l");
199
+ }
200
+ // Format: separate safety from the rest for readability
201
+ // survival_safety_connection_esteem_curiosity
202
+ const driveStr = driveLevels.join("");
203
+ return `${phase}:${recentStimuli || "none"}:${driveStr}`;
204
+ }
205
+ // ── 3. PredictionEngine ─────────────────────────────────────
206
+ /**
207
+ * Predict the resulting chemistry after applying a stimulus,
208
+ * using learned vectors instead of raw base vectors.
209
+ *
210
+ * Same math as applyStimulus in chemistry.ts but with learned adjustments.
211
+ */
212
+ export function predictChemistry(current, stimulus, learning, contextHash, sensitivity, maxDelta) {
213
+ const vector = getLearnedVector(learning, stimulus, contextHash);
214
+ const result = { ...current };
215
+ for (const key of CHEMICAL_KEYS) {
216
+ const raw = vector[key] * sensitivity;
217
+ const clamped = Math.max(-maxDelta, Math.min(maxDelta, raw));
218
+ result[key] = clamp(current[key] + clamped);
219
+ }
220
+ return result;
221
+ }
222
+ /**
223
+ * Compute the prediction error between predicted and actual chemistry.
224
+ *
225
+ * Euclidean distance across all 6 chemicals, normalized to 0-1 range.
226
+ * Normalization factor: sqrt(6 * 100^2) = sqrt(60000) ~= 244.95
227
+ */
228
+ export function computePredictionError(predicted, actual) {
229
+ let sumSq = 0;
230
+ for (const key of CHEMICAL_KEYS) {
231
+ const diff = predicted[key] - actual[key];
232
+ sumSq += diff * diff;
233
+ }
234
+ const maxDistance = Math.sqrt(6 * 100 * 100); // ~244.95
235
+ return Math.sqrt(sumSq) / maxDistance;
236
+ }
237
+ /**
238
+ * Record a prediction and its actual outcome.
239
+ * Pushes to predictionHistory and trims to MAX_PREDICTION_HISTORY.
240
+ */
241
+ export function recordPrediction(learning, predicted, actual, stimulus) {
242
+ const error = computePredictionError(predicted, actual);
243
+ const record = {
244
+ predictedChemistry: { ...predicted },
245
+ actualChemistry: { ...actual },
246
+ stimulus,
247
+ predictionError: error,
248
+ timestamp: new Date().toISOString(),
249
+ };
250
+ let newHistory = [...learning.predictionHistory, record];
251
+ if (newHistory.length > MAX_PREDICTION_HISTORY) {
252
+ newHistory = newHistory.slice(newHistory.length - MAX_PREDICTION_HISTORY);
253
+ }
254
+ return {
255
+ ...learning,
256
+ predictionHistory: newHistory,
257
+ };
258
+ }
259
+ // ── 4. Utility ──────────────────────────────────────────────
260
+ /**
261
+ * Get the average prediction error over recent history.
262
+ * Returns 1.0 if no history exists (maximum uncertainty).
263
+ */
264
+ export function getAveragePredictionError(learning) {
265
+ if (learning.predictionHistory.length === 0)
266
+ return 1.0;
267
+ let sum = 0;
268
+ for (const record of learning.predictionHistory) {
269
+ sum += record.predictionError;
270
+ }
271
+ return sum / learning.predictionHistory.length;
272
+ }
@@ -4,7 +4,7 @@
4
4
  // ============================================================
5
5
  import { readFile, writeFile, access, rename, constants } from "node:fs/promises";
6
6
  import { join } from "node:path";
7
- import { CHEMICAL_KEYS, CHEMICAL_NAMES, CHEMICAL_NAMES_ZH, DEFAULT_RELATIONSHIP, DEFAULT_DRIVES, MAX_EMOTIONAL_HISTORY, MAX_RELATIONSHIP_MEMORY, } from "./types.js";
7
+ import { CHEMICAL_KEYS, CHEMICAL_NAMES, CHEMICAL_NAMES_ZH, DEFAULT_RELATIONSHIP, DEFAULT_DRIVES, DEFAULT_LEARNING_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
10
  import { decayDrives, computeEffectiveBaseline } from "./drives.js";
@@ -240,10 +240,12 @@ export function migrateToLatest(raw, fallbackName) {
240
240
  };
241
241
  }
242
242
  // v2→v3: add drives
243
+ // v3→v4: add learning
243
244
  return {
244
245
  ...state,
245
- version: 3,
246
+ version: 4,
246
247
  drives: state.drives ?? { ...DEFAULT_DRIVES },
248
+ learning: state.learning ?? { ...DEFAULT_LEARNING_STATE },
247
249
  };
248
250
  }
249
251
  /**
@@ -257,7 +259,7 @@ export async function initializeState(workspaceDir, opts, logger = NOOP_LOGGER)
257
259
  const selfModel = getDefaultSelfModel(mbti);
258
260
  const now = new Date().toISOString();
259
261
  const state = {
260
- version: 3,
262
+ version: 4,
261
263
  mbti,
262
264
  baseline,
263
265
  current: { ...baseline },
@@ -271,6 +273,7 @@ export async function initializeState(workspaceDir, opts, logger = NOOP_LOGGER)
271
273
  emotionalHistory: [],
272
274
  agreementStreak: 0,
273
275
  lastDisagreement: null,
276
+ learning: { ...DEFAULT_LEARNING_STATE },
274
277
  meta: {
275
278
  agentName,
276
279
  createdAt: now,
package/dist/types.d.ts CHANGED
@@ -81,9 +81,56 @@ export interface SelfModel {
81
81
  boundaries: string[];
82
82
  currentInterests: string[];
83
83
  }
84
- /** Persisted psyche state for an agent (v0.3: innate drives) */
84
+ /** Learned adjustment to a stimulus vector for a specific context */
85
+ export interface LearnedVectorAdjustment {
86
+ stimulus: StimulusType;
87
+ contextHash: string;
88
+ adjustment: Partial<StimulusVector>;
89
+ confidence: number;
90
+ sampleCount: number;
91
+ lastUpdated: string;
92
+ }
93
+ /** A single prediction record for prediction error tracking */
94
+ export interface PredictionRecord {
95
+ predictedChemistry: ChemicalState;
96
+ actualChemistry: ChemicalState;
97
+ stimulus: StimulusType | null;
98
+ predictionError: number;
99
+ timestamp: string;
100
+ }
101
+ /** Outcome evaluation signals for a turn */
102
+ export interface OutcomeSignals {
103
+ driveDelta: number;
104
+ relationshipDelta: number;
105
+ userWarmthDelta: number;
106
+ conversationContinued: boolean;
107
+ }
108
+ /** Outcome evaluation for a single interaction turn */
109
+ export interface OutcomeScore {
110
+ turnIndex: number;
111
+ stimulus: StimulusType | null;
112
+ adaptiveScore: number;
113
+ signals: OutcomeSignals;
114
+ timestamp: string;
115
+ }
116
+ /** Persisted learning state */
117
+ export interface LearningState {
118
+ learnedVectors: LearnedVectorAdjustment[];
119
+ predictionHistory: PredictionRecord[];
120
+ outcomeHistory: OutcomeScore[];
121
+ totalOutcomesProcessed: number;
122
+ }
123
+ /** Default empty learning state */
124
+ export declare const DEFAULT_LEARNING_STATE: LearningState;
125
+ /** Max learned vector entries */
126
+ export declare const MAX_LEARNED_VECTORS = 200;
127
+ /** Max prediction history entries */
128
+ export declare const MAX_PREDICTION_HISTORY = 50;
129
+ /** Max outcome history entries */
130
+ export declare const MAX_OUTCOME_HISTORY = 50;
131
+ /** Persisted psyche state for an agent (v4: emotional learning) */
85
132
  export interface PsycheState {
86
- version: 3;
133
+ version: 3 | 4;
87
134
  mbti: MBTIType;
88
135
  baseline: ChemicalState;
89
136
  current: ChemicalState;
@@ -95,6 +142,7 @@ export interface PsycheState {
95
142
  emotionalHistory: ChemicalSnapshot[];
96
143
  agreementStreak: number;
97
144
  lastDisagreement: string | null;
145
+ learning: LearningState;
98
146
  meta: {
99
147
  agentName: string;
100
148
  createdAt: string;
package/dist/types.js CHANGED
@@ -60,6 +60,19 @@ export const CHEMICAL_DECAY_SPEED = {
60
60
  export const MAX_EMOTIONAL_HISTORY = 10;
61
61
  /** Max compressed session memories per relationship */
62
62
  export const MAX_RELATIONSHIP_MEMORY = 20;
63
+ /** Default empty learning state */
64
+ export const DEFAULT_LEARNING_STATE = {
65
+ learnedVectors: [],
66
+ predictionHistory: [],
67
+ outcomeHistory: [],
68
+ totalOutcomesProcessed: 0,
69
+ };
70
+ /** Max learned vector entries */
71
+ export const MAX_LEARNED_VECTORS = 200;
72
+ /** Max prediction history entries */
73
+ export const MAX_PREDICTION_HISTORY = 50;
74
+ /** Max outcome history entries */
75
+ export const MAX_OUTCOME_HISTORY = 50;
63
76
  /** Default relationship for new users */
64
77
  export const DEFAULT_RELATIONSHIP = {
65
78
  trust: 50,
package/dist/update.js CHANGED
@@ -11,7 +11,7 @@ import { execFile } from "node:child_process";
11
11
  import { promisify } from "node:util";
12
12
  const execFileAsync = promisify(execFile);
13
13
  const PACKAGE_NAME = "psyche-ai";
14
- const CURRENT_VERSION = "2.3.0";
14
+ const CURRENT_VERSION = "3.0.0";
15
15
  const CHECK_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
16
16
  const CACHE_DIR = join(homedir(), ".psyche-ai");
17
17
  const CACHE_FILE = join(CACHE_DIR, "update-check.json");
@@ -2,7 +2,7 @@
2
2
  "id": "psyche-ai",
3
3
  "name": "Artificial Psyche",
4
4
  "description": "Virtual endocrine system, empathy engine, and agency for OpenClaw agents",
5
- "version": "2.3.0",
5
+ "version": "3.0.0",
6
6
  "configSchema": {
7
7
  "type": "object",
8
8
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "psyche-ai",
3
- "version": "2.3.0",
3
+ "version": "3.0.0",
4
4
  "description": "Artificial Psyche — universal emotional intelligence plugin for any AI agent",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",