psyche-ai 3.0.0 → 3.1.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 +3 -1
- package/dist/attachment.d.ts +30 -0
- package/dist/attachment.js +241 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +5 -1
- package/dist/temporal.d.ts +38 -0
- package/dist/temporal.js +276 -0
- package/dist/types.d.ts +17 -0
- package/dist/types.js +12 -0
- package/dist/update.js +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -163,6 +163,8 @@ cd openclaw-plugin-psyche && node scripts/diagnose.js
|
|
|
163
163
|
- **自定义人格** — 超越 MBTI 预设,完全自定义 baseline/敏感度/气质
|
|
164
164
|
- **情绪学习** — 从交互结果中学习,调整情绪反应参数(躯体标记假说)
|
|
165
165
|
- **上下文分类** — 关系/驱力/历史感知的刺激分类,超越简单正则
|
|
166
|
+
- **时间意识** — 预期、惊喜/失望、遗憾(马尔可夫预测+反事实分析)
|
|
167
|
+
- **依恋动力学** — 4种依恋风格(安全/焦虑/回避/混乱),分离焦虑,重逢效应
|
|
166
168
|
- **Compact Mode** — 算法做化学计算,LLM 只看行为指令(~15-180 tokens vs ~550)
|
|
167
169
|
|
|
168
170
|
架构详情见 [ARCHITECTURE.md](ARCHITECTURE.md)。
|
|
@@ -172,7 +174,7 @@ cd openclaw-plugin-psyche && node scripts/diagnose.js
|
|
|
172
174
|
```bash
|
|
173
175
|
npm install
|
|
174
176
|
npm run build
|
|
175
|
-
npm test #
|
|
177
|
+
npm test # 568 tests
|
|
176
178
|
npm run typecheck # strict mode
|
|
177
179
|
```
|
|
178
180
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ChemicalState, StimulusType } from "./types.js";
|
|
2
|
+
export type AttachmentStyle = "secure" | "anxious" | "avoidant" | "disorganized";
|
|
3
|
+
export interface AttachmentState {
|
|
4
|
+
style: AttachmentStyle;
|
|
5
|
+
strength: number;
|
|
6
|
+
securityScore: number;
|
|
7
|
+
anxietyScore: number;
|
|
8
|
+
avoidanceScore: number;
|
|
9
|
+
lastInteractionAt: string;
|
|
10
|
+
interactionCount: number;
|
|
11
|
+
}
|
|
12
|
+
export interface SeparationEffect {
|
|
13
|
+
chemistryDelta: Partial<ChemicalState>;
|
|
14
|
+
description: string;
|
|
15
|
+
intensity: number;
|
|
16
|
+
}
|
|
17
|
+
export declare const DEFAULT_ATTACHMENT: AttachmentState;
|
|
18
|
+
/**
|
|
19
|
+
* Update attachment based on interaction outcome.
|
|
20
|
+
*/
|
|
21
|
+
export declare function updateAttachment(attachment: AttachmentState, stimulus: StimulusType | null, outcomeScore: number): AttachmentState;
|
|
22
|
+
/**
|
|
23
|
+
* Compute chemistry effects of absence based on attachment.
|
|
24
|
+
* Called when time since last interaction is significant.
|
|
25
|
+
*/
|
|
26
|
+
export declare function computeSeparationEffect(attachment: AttachmentState, minutesSinceLastInteraction: number): SeparationEffect | null;
|
|
27
|
+
/**
|
|
28
|
+
* Compute chemistry effects when reuniting after absence.
|
|
29
|
+
*/
|
|
30
|
+
export declare function computeReunionEffect(attachment: AttachmentState, minutesSinceLastInteraction: number): Partial<ChemicalState> | null;
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Attachment Dynamics — Bowlby-inspired attachment formation
|
|
3
|
+
//
|
|
4
|
+
// Models relationship attachment through interaction patterns:
|
|
5
|
+
// 1. AttachmentModel — style classification + strength tracking
|
|
6
|
+
// 2. SeparationAnxiety — absence effects on chemistry
|
|
7
|
+
// 3. ReunionEffect — return effects on chemistry
|
|
8
|
+
//
|
|
9
|
+
// Attachment style emerges from interaction history, not from
|
|
10
|
+
// configuration. Consistent positive interaction → secure.
|
|
11
|
+
// Inconsistency → anxious. Rejection/neglect → avoidant.
|
|
12
|
+
// ============================================================
|
|
13
|
+
// ── Defaults ─────────────────────────────────────────────────
|
|
14
|
+
export const DEFAULT_ATTACHMENT = {
|
|
15
|
+
style: "secure",
|
|
16
|
+
strength: 0,
|
|
17
|
+
securityScore: 50,
|
|
18
|
+
anxietyScore: 50,
|
|
19
|
+
avoidanceScore: 50,
|
|
20
|
+
lastInteractionAt: new Date().toISOString(),
|
|
21
|
+
interactionCount: 0,
|
|
22
|
+
};
|
|
23
|
+
// ── Stimulus Classification ──────────────────────────────────
|
|
24
|
+
const POSITIVE_STIMULI = new Set([
|
|
25
|
+
"praise", "validation", "intimacy", "humor",
|
|
26
|
+
]);
|
|
27
|
+
const NEGATIVE_STIMULI = new Set([
|
|
28
|
+
"criticism", "conflict", "neglect", "sarcasm", "authority",
|
|
29
|
+
]);
|
|
30
|
+
const REJECTION_STIMULI = new Set([
|
|
31
|
+
"neglect", "authority", "boredom",
|
|
32
|
+
]);
|
|
33
|
+
// EMA smoothing factor: weight given to new observation
|
|
34
|
+
const EMA_ALPHA = 0.15;
|
|
35
|
+
// ── 1. AttachmentModel ──────────────────────────────────────
|
|
36
|
+
/**
|
|
37
|
+
* Determine attachment style from scores.
|
|
38
|
+
*/
|
|
39
|
+
function determineStyle(securityScore, anxietyScore, avoidanceScore) {
|
|
40
|
+
// Disorganized: both anxiety and avoidance elevated
|
|
41
|
+
if (anxietyScore > 50 && avoidanceScore > 50) {
|
|
42
|
+
return "disorganized";
|
|
43
|
+
}
|
|
44
|
+
// Anxious: high anxiety
|
|
45
|
+
if (anxietyScore > 60) {
|
|
46
|
+
return "anxious";
|
|
47
|
+
}
|
|
48
|
+
// Avoidant: high avoidance
|
|
49
|
+
if (avoidanceScore > 60) {
|
|
50
|
+
return "avoidant";
|
|
51
|
+
}
|
|
52
|
+
// Secure: high security, low anxiety, low avoidance
|
|
53
|
+
if (securityScore > 60 && anxietyScore < 40 && avoidanceScore < 40) {
|
|
54
|
+
return "secure";
|
|
55
|
+
}
|
|
56
|
+
// Default to current trajectory — mild insecurity stays secure
|
|
57
|
+
if (securityScore >= 50)
|
|
58
|
+
return "secure";
|
|
59
|
+
if (anxietyScore > avoidanceScore)
|
|
60
|
+
return "anxious";
|
|
61
|
+
return "avoidant";
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Update attachment based on interaction outcome.
|
|
65
|
+
*/
|
|
66
|
+
export function updateAttachment(attachment, stimulus, outcomeScore) {
|
|
67
|
+
const result = { ...attachment };
|
|
68
|
+
// Strength increases slowly with interaction count
|
|
69
|
+
result.interactionCount = attachment.interactionCount + 1;
|
|
70
|
+
result.strength = Math.min(100, attachment.strength + 1);
|
|
71
|
+
result.lastInteractionAt = new Date().toISOString();
|
|
72
|
+
if (stimulus === null) {
|
|
73
|
+
// No stimulus — just update count/strength, reclassify
|
|
74
|
+
result.style = determineStyle(result.securityScore, result.anxietyScore, result.avoidanceScore);
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
// SecurityScore: EMA with positive/negative stimuli
|
|
78
|
+
const isPositive = POSITIVE_STIMULI.has(stimulus);
|
|
79
|
+
const isNegative = NEGATIVE_STIMULI.has(stimulus);
|
|
80
|
+
if (isPositive) {
|
|
81
|
+
const target = Math.min(100, result.securityScore + 5);
|
|
82
|
+
result.securityScore = result.securityScore * (1 - EMA_ALPHA) + target * EMA_ALPHA;
|
|
83
|
+
}
|
|
84
|
+
else if (isNegative) {
|
|
85
|
+
const target = Math.max(0, result.securityScore - 5);
|
|
86
|
+
result.securityScore = result.securityScore * (1 - EMA_ALPHA) + target * EMA_ALPHA;
|
|
87
|
+
}
|
|
88
|
+
// AnxietyScore: increases with inconsistency (rapid alternation between positive and negative)
|
|
89
|
+
// We detect inconsistency by checking if outcomeScore diverges from the security trend
|
|
90
|
+
const expectedDirection = result.securityScore > 50 ? 1 : -1;
|
|
91
|
+
const actualDirection = outcomeScore >= 0 ? 1 : -1;
|
|
92
|
+
const isInconsistent = expectedDirection !== actualDirection;
|
|
93
|
+
if (isInconsistent) {
|
|
94
|
+
// Inconsistency → anxiety rises
|
|
95
|
+
const anxietyTarget = Math.min(100, result.anxietyScore + 8);
|
|
96
|
+
result.anxietyScore = result.anxietyScore * (1 - EMA_ALPHA) + anxietyTarget * EMA_ALPHA;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
// Consistency → anxiety decreases
|
|
100
|
+
const anxietyTarget = Math.max(0, result.anxietyScore - 3);
|
|
101
|
+
result.anxietyScore = result.anxietyScore * (1 - EMA_ALPHA) + anxietyTarget * EMA_ALPHA;
|
|
102
|
+
}
|
|
103
|
+
// AvoidanceScore: increases with rejection/neglect stimuli
|
|
104
|
+
if (REJECTION_STIMULI.has(stimulus)) {
|
|
105
|
+
const avoidTarget = Math.min(100, result.avoidanceScore + 6);
|
|
106
|
+
result.avoidanceScore = result.avoidanceScore * (1 - EMA_ALPHA) + avoidTarget * EMA_ALPHA;
|
|
107
|
+
}
|
|
108
|
+
else if (isPositive) {
|
|
109
|
+
// Positive interactions reduce avoidance
|
|
110
|
+
const avoidTarget = Math.max(0, result.avoidanceScore - 3);
|
|
111
|
+
result.avoidanceScore = result.avoidanceScore * (1 - EMA_ALPHA) + avoidTarget * EMA_ALPHA;
|
|
112
|
+
}
|
|
113
|
+
// Clamp all scores
|
|
114
|
+
result.securityScore = Math.max(0, Math.min(100, result.securityScore));
|
|
115
|
+
result.anxietyScore = Math.max(0, Math.min(100, result.anxietyScore));
|
|
116
|
+
result.avoidanceScore = Math.max(0, Math.min(100, result.avoidanceScore));
|
|
117
|
+
// Determine style
|
|
118
|
+
result.style = determineStyle(result.securityScore, result.anxietyScore, result.avoidanceScore);
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
// ── 2. SeparationAnxiety ────────────────────────────────────
|
|
122
|
+
/**
|
|
123
|
+
* Compute chemistry effects of absence based on attachment.
|
|
124
|
+
* Called when time since last interaction is significant.
|
|
125
|
+
*/
|
|
126
|
+
export function computeSeparationEffect(attachment, minutesSinceLastInteraction) {
|
|
127
|
+
// No effect for short absence or weak attachment
|
|
128
|
+
if (minutesSinceLastInteraction < 60 || attachment.strength < 20) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const hours = minutesSinceLastInteraction / 60;
|
|
132
|
+
// Intensity scales with attachment strength and time (logarithmic growth, capped at 1)
|
|
133
|
+
const baseIntensity = (attachment.strength / 100) * Math.min(1, Math.log2(hours + 1) / 5);
|
|
134
|
+
switch (attachment.style) {
|
|
135
|
+
case "secure": {
|
|
136
|
+
// Mild longing after 24h
|
|
137
|
+
if (hours < 24)
|
|
138
|
+
return null;
|
|
139
|
+
const intensity = baseIntensity * 0.5;
|
|
140
|
+
return {
|
|
141
|
+
chemistryDelta: {
|
|
142
|
+
OT: -5 * intensity,
|
|
143
|
+
DA: -3 * intensity,
|
|
144
|
+
},
|
|
145
|
+
description: "gentle longing from sustained absence",
|
|
146
|
+
intensity: Math.min(1, intensity),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
case "anxious": {
|
|
150
|
+
// Distress after 4h, grows with time
|
|
151
|
+
if (hours < 4)
|
|
152
|
+
return null;
|
|
153
|
+
const intensity = baseIntensity * 1.5;
|
|
154
|
+
// OT oscillation: represented as net negative with anxiety
|
|
155
|
+
return {
|
|
156
|
+
chemistryDelta: {
|
|
157
|
+
CORT: 10 * intensity,
|
|
158
|
+
OT: -5 * intensity,
|
|
159
|
+
NE: 8 * intensity,
|
|
160
|
+
DA: -3 * intensity,
|
|
161
|
+
},
|
|
162
|
+
description: "anxious distress from absence — fear of abandonment",
|
|
163
|
+
intensity: Math.min(1, intensity),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
case "avoidant": {
|
|
167
|
+
// Relief initially, discomfort after 48h
|
|
168
|
+
if (hours < 48)
|
|
169
|
+
return null;
|
|
170
|
+
const intensity = baseIntensity * 0.4;
|
|
171
|
+
return {
|
|
172
|
+
chemistryDelta: {
|
|
173
|
+
OT: -3 * intensity,
|
|
174
|
+
},
|
|
175
|
+
description: "subtle discomfort surfacing through avoidant defense",
|
|
176
|
+
intensity: Math.min(1, intensity),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
case "disorganized": {
|
|
180
|
+
// Conflicting signals
|
|
181
|
+
if (hours < 4)
|
|
182
|
+
return null;
|
|
183
|
+
const intensity = baseIntensity * 1.0;
|
|
184
|
+
return {
|
|
185
|
+
chemistryDelta: {
|
|
186
|
+
CORT: 5 * intensity,
|
|
187
|
+
OT: 5 * intensity,
|
|
188
|
+
NE: 3 * intensity,
|
|
189
|
+
},
|
|
190
|
+
description: "conflicting signals — wanting closeness and fearing it",
|
|
191
|
+
intensity: Math.min(1, intensity),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// ── 3. ReunionEffect ────────────────────────────────────────
|
|
197
|
+
/**
|
|
198
|
+
* Compute chemistry effects when reuniting after absence.
|
|
199
|
+
*/
|
|
200
|
+
export function computeReunionEffect(attachment, minutesSinceLastInteraction) {
|
|
201
|
+
// No effect for short absence or weak attachment
|
|
202
|
+
if (minutesSinceLastInteraction < 60 || attachment.strength < 20) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
const hours = minutesSinceLastInteraction / 60;
|
|
206
|
+
// Scale with time (logarithmic) and attachment strength
|
|
207
|
+
const scale = (attachment.strength / 100) * Math.min(1, Math.log2(hours + 1) / 5);
|
|
208
|
+
switch (attachment.style) {
|
|
209
|
+
case "secure": {
|
|
210
|
+
// Warm reunion
|
|
211
|
+
return {
|
|
212
|
+
OT: 8 * scale,
|
|
213
|
+
DA: 5 * scale,
|
|
214
|
+
END: 3 * scale,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
case "anxious": {
|
|
218
|
+
// Intense but short-lived relief (CORT still elevated)
|
|
219
|
+
return {
|
|
220
|
+
OT: 15 * scale,
|
|
221
|
+
DA: 10 * scale,
|
|
222
|
+
CORT: 5 * scale,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
case "avoidant": {
|
|
226
|
+
// Cautious re-engagement
|
|
227
|
+
return {
|
|
228
|
+
OT: 3 * scale,
|
|
229
|
+
NE: 5 * scale,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
case "disorganized": {
|
|
233
|
+
// Mixed signals
|
|
234
|
+
return {
|
|
235
|
+
OT: 5 * scale,
|
|
236
|
+
CORT: 5 * scale,
|
|
237
|
+
NE: 5 * scale,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
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, } 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";
|
|
5
|
+
export type { PsycheState, MBTIType, Locale, StimulusType, ChemicalState, ChemicalSnapshot, SelfModel, RelationshipState, EmpathyEntry, EmotionPattern, DriveType, InnateDrives, LearningState, LearnedVectorAdjustment, PredictionRecord, OutcomeScore, OutcomeSignals, AttachmentStyle, AttachmentData, } from "./types.js";
|
|
6
|
+
export { CHEMICAL_KEYS, CHEMICAL_NAMES, CHEMICAL_NAMES_ZH, DEFAULT_RELATIONSHIP, DEFAULT_DRIVES, DEFAULT_LEARNING_STATE, DEFAULT_ATTACHMENT, 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";
|
|
@@ -15,6 +15,10 @@ export type { CustomProfileConfig, ResolvedProfile } from "./custom-profile.js";
|
|
|
15
15
|
export { evaluateOutcome, getLearnedVector, updateLearnedVector, computeContextHash, predictChemistry, computePredictionError, recordPrediction, getAveragePredictionError, } from "./learning.js";
|
|
16
16
|
export { classifyStimulusWithContext, extractContextFeatures, stimulusWarmth } from "./context-classifier.js";
|
|
17
17
|
export type { ContextFeatures, ContextualClassification } from "./context-classifier.js";
|
|
18
|
+
export { predictNextStimulus, generateAnticipation, computeSurpriseEffect, computeRegret, } from "./temporal.js";
|
|
19
|
+
export type { StimulusPrediction, AnticipationState, RegretEntry } from "./temporal.js";
|
|
20
|
+
export { updateAttachment, computeSeparationEffect, computeReunionEffect, } from "./attachment.js";
|
|
21
|
+
export type { SeparationEffect } from "./attachment.js";
|
|
18
22
|
export { classifyStimulus, getPrimaryStimulus } from "./classify.js";
|
|
19
23
|
export { buildProtocolContext, buildDynamicContext, buildCompactContext, isNearBaseline } from "./prompt.js";
|
|
20
24
|
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, DEFAULT_LEARNING_STATE, 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_ATTACHMENT, 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
|
|
@@ -25,6 +25,10 @@ export { createCustomProfile, validateProfileConfig, PRESET_PROFILES } from "./c
|
|
|
25
25
|
export { evaluateOutcome, getLearnedVector, updateLearnedVector, computeContextHash, predictChemistry, computePredictionError, recordPrediction, getAveragePredictionError, } from "./learning.js";
|
|
26
26
|
// Context-aware classification (P3)
|
|
27
27
|
export { classifyStimulusWithContext, extractContextFeatures, stimulusWarmth } from "./context-classifier.js";
|
|
28
|
+
// Temporal consciousness (P4)
|
|
29
|
+
export { predictNextStimulus, generateAnticipation, computeSurpriseEffect, computeRegret, } from "./temporal.js";
|
|
30
|
+
// Attachment dynamics (P4)
|
|
31
|
+
export { updateAttachment, computeSeparationEffect, computeReunionEffect, } from "./attachment.js";
|
|
28
32
|
// Utilities — for custom adapter / advanced use
|
|
29
33
|
export { classifyStimulus, getPrimaryStimulus } from "./classify.js";
|
|
30
34
|
export { buildProtocolContext, buildDynamicContext, buildCompactContext, isNearBaseline } from "./prompt.js";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ChemicalState, ChemicalSnapshot, StimulusType, PsycheState, RelationshipState } from "./types.js";
|
|
2
|
+
export interface StimulusPrediction {
|
|
3
|
+
stimulus: StimulusType;
|
|
4
|
+
probability: number;
|
|
5
|
+
}
|
|
6
|
+
export interface AnticipationState {
|
|
7
|
+
predictions: StimulusPrediction[];
|
|
8
|
+
anticipatoryChemistry: Partial<ChemicalState>;
|
|
9
|
+
timestamp: string;
|
|
10
|
+
}
|
|
11
|
+
export interface RegretEntry {
|
|
12
|
+
turnIndex: number;
|
|
13
|
+
counterfactualDelta: Partial<ChemicalState>;
|
|
14
|
+
regretIntensity: number;
|
|
15
|
+
description: string;
|
|
16
|
+
timestamp: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Predict likely next stimulus based on interaction history.
|
|
20
|
+
* Uses simple Markov property: given recent stimulus sequence, what comes next?
|
|
21
|
+
*/
|
|
22
|
+
export declare function predictNextStimulus(emotionalHistory: ChemicalSnapshot[], relationshipPhase: RelationshipState["phase"]): StimulusPrediction[];
|
|
23
|
+
/**
|
|
24
|
+
* Generate anticipatory chemistry changes based on predictions.
|
|
25
|
+
* High-probability positive prediction -> DA/OT micro-rise.
|
|
26
|
+
* High-probability negative prediction -> CORT micro-rise.
|
|
27
|
+
*/
|
|
28
|
+
export declare function generateAnticipation(predictions: StimulusPrediction[], currentChemistry: ChemicalState): AnticipationState;
|
|
29
|
+
/**
|
|
30
|
+
* Compute disappointment/surprise when actual stimulus differs from prediction.
|
|
31
|
+
* Returns additional chemistry delta beyond the normal stimulus response.
|
|
32
|
+
*/
|
|
33
|
+
export declare function computeSurpriseEffect(anticipated: AnticipationState, actualStimulus: StimulusType | null): Partial<ChemicalState>;
|
|
34
|
+
/**
|
|
35
|
+
* Evaluate if the last interaction would have gone better with different chemistry.
|
|
36
|
+
* Runs a counterfactual: "what if my chemistry had been at baseline?"
|
|
37
|
+
*/
|
|
38
|
+
export declare function computeRegret(preInteractionState: PsycheState, postInteractionState: PsycheState, outcomeScore: number, appliedStimulus: StimulusType | null): RegretEntry | null;
|
package/dist/temporal.js
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Temporal Consciousness — Anticipation, Surprise, and Regret
|
|
3
|
+
//
|
|
4
|
+
// Implements predictive emotional processing:
|
|
5
|
+
// 1. PredictiveModel — Markov-based stimulus prediction
|
|
6
|
+
// 2. AnticipationEngine — pre-stimulus chemistry shifts
|
|
7
|
+
// 3. RegretComputer — counterfactual outcome analysis
|
|
8
|
+
//
|
|
9
|
+
// These enable the agent to "look forward" and "look back",
|
|
10
|
+
// creating a temporal dimension to emotional experience.
|
|
11
|
+
// ============================================================
|
|
12
|
+
import { CHEMICAL_KEYS } from "./types.js";
|
|
13
|
+
import { STIMULUS_VECTORS } from "./chemistry.js";
|
|
14
|
+
// ── All StimulusType values ──────────────────────────────────
|
|
15
|
+
const ALL_STIMULI = [
|
|
16
|
+
"praise", "criticism", "humor", "intellectual", "intimacy",
|
|
17
|
+
"conflict", "neglect", "surprise", "casual",
|
|
18
|
+
"sarcasm", "authority", "validation", "boredom", "vulnerability",
|
|
19
|
+
];
|
|
20
|
+
// ── Phase Priors ─────────────────────────────────────────────
|
|
21
|
+
// Default probability weights per relationship phase.
|
|
22
|
+
const PHASE_PRIORS = {
|
|
23
|
+
stranger: {
|
|
24
|
+
casual: 3, intellectual: 2, humor: 1.5, boredom: 1.5,
|
|
25
|
+
criticism: 0.5, intimacy: 0.3, vulnerability: 0.3,
|
|
26
|
+
praise: 1, validation: 0.8, surprise: 1, conflict: 0.5,
|
|
27
|
+
neglect: 1, sarcasm: 0.8, authority: 0.8,
|
|
28
|
+
},
|
|
29
|
+
acquaintance: {
|
|
30
|
+
casual: 2.5, intellectual: 2, humor: 2, praise: 1.5,
|
|
31
|
+
validation: 1.2, surprise: 1, criticism: 0.8, conflict: 0.5,
|
|
32
|
+
intimacy: 0.5, vulnerability: 0.5, neglect: 0.8,
|
|
33
|
+
sarcasm: 0.8, authority: 0.8, boredom: 1,
|
|
34
|
+
},
|
|
35
|
+
familiar: {
|
|
36
|
+
casual: 2, humor: 2.5, praise: 2, validation: 2,
|
|
37
|
+
intellectual: 2, intimacy: 1.5, vulnerability: 1,
|
|
38
|
+
surprise: 1.2, criticism: 1, conflict: 0.8, neglect: 0.6,
|
|
39
|
+
sarcasm: 1, authority: 0.6, boredom: 0.8,
|
|
40
|
+
},
|
|
41
|
+
close: {
|
|
42
|
+
intimacy: 3, humor: 2.5, validation: 2.5, praise: 2,
|
|
43
|
+
vulnerability: 2, casual: 2, intellectual: 1.5,
|
|
44
|
+
surprise: 1.5, criticism: 1, conflict: 0.8, neglect: 0.5,
|
|
45
|
+
sarcasm: 0.8, authority: 0.5, boredom: 0.5,
|
|
46
|
+
},
|
|
47
|
+
deep: {
|
|
48
|
+
intimacy: 3.5, vulnerability: 3, validation: 2.5, humor: 2.5,
|
|
49
|
+
praise: 2, casual: 1.5, intellectual: 2, surprise: 1.5,
|
|
50
|
+
criticism: 1, conflict: 0.8, neglect: 0.3, sarcasm: 0.5,
|
|
51
|
+
authority: 0.3, boredom: 0.3,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
// ── 1. PredictiveModel ──────────────────────────────────────
|
|
55
|
+
/**
|
|
56
|
+
* Predict likely next stimulus based on interaction history.
|
|
57
|
+
* Uses simple Markov property: given recent stimulus sequence, what comes next?
|
|
58
|
+
*/
|
|
59
|
+
export function predictNextStimulus(emotionalHistory, relationshipPhase) {
|
|
60
|
+
const phasePrior = PHASE_PRIORS[relationshipPhase] ?? PHASE_PRIORS.acquaintance;
|
|
61
|
+
// Insufficient history: return flat prior weighted by phase
|
|
62
|
+
if (emotionalHistory.length < 3) {
|
|
63
|
+
return buildPhasePrior(phasePrior);
|
|
64
|
+
}
|
|
65
|
+
// Extract the last 2 stimuli for bigram transition
|
|
66
|
+
const recent = emotionalHistory.slice(-2);
|
|
67
|
+
const lastTwo = recent.map((s) => s.stimulus).filter((s) => s !== null);
|
|
68
|
+
if (lastTwo.length < 2) {
|
|
69
|
+
return buildPhasePrior(phasePrior);
|
|
70
|
+
}
|
|
71
|
+
// Build transition counts from history (all consecutive pairs)
|
|
72
|
+
const transitionCounts = new Map();
|
|
73
|
+
for (let i = 1; i < emotionalHistory.length; i++) {
|
|
74
|
+
const prev = emotionalHistory[i - 1].stimulus;
|
|
75
|
+
const cur = emotionalHistory[i].stimulus;
|
|
76
|
+
if (prev === null || cur === null)
|
|
77
|
+
continue;
|
|
78
|
+
const key = prev;
|
|
79
|
+
if (!transitionCounts.has(key)) {
|
|
80
|
+
transitionCounts.set(key, new Map());
|
|
81
|
+
}
|
|
82
|
+
const counts = transitionCounts.get(key);
|
|
83
|
+
counts.set(cur, (counts.get(cur) ?? 0) + 1);
|
|
84
|
+
}
|
|
85
|
+
// Get transition probabilities from the last stimulus
|
|
86
|
+
const lastStimulus = lastTwo[lastTwo.length - 1];
|
|
87
|
+
const transitions = transitionCounts.get(lastStimulus);
|
|
88
|
+
// If no transitions observed from this stimulus, fall back to phase prior
|
|
89
|
+
if (!transitions || transitions.size === 0) {
|
|
90
|
+
return buildPhasePrior(phasePrior);
|
|
91
|
+
}
|
|
92
|
+
// Merge Markov transitions with phase prior (50/50 blend)
|
|
93
|
+
let totalTransitions = 0;
|
|
94
|
+
for (const count of transitions.values()) {
|
|
95
|
+
totalTransitions += count;
|
|
96
|
+
}
|
|
97
|
+
const predictions = [];
|
|
98
|
+
let totalWeight = 0;
|
|
99
|
+
for (const stim of ALL_STIMULI) {
|
|
100
|
+
const markovProb = totalTransitions > 0
|
|
101
|
+
? (transitions.get(stim) ?? 0) / totalTransitions
|
|
102
|
+
: 0;
|
|
103
|
+
const priorWeight = phasePrior[stim] ?? 0.5;
|
|
104
|
+
// Blend: 50% Markov, 50% phase prior (normalized)
|
|
105
|
+
const combined = markovProb * 0.5 + (priorWeight / 20) * 0.5;
|
|
106
|
+
totalWeight += combined;
|
|
107
|
+
predictions.push({ stimulus: stim, probability: combined });
|
|
108
|
+
}
|
|
109
|
+
// Normalize
|
|
110
|
+
if (totalWeight > 0) {
|
|
111
|
+
for (const p of predictions) {
|
|
112
|
+
p.probability = p.probability / totalWeight;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Sort by probability descending
|
|
116
|
+
predictions.sort((a, b) => b.probability - a.probability);
|
|
117
|
+
return predictions;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Build a flat phase-weighted prior distribution.
|
|
121
|
+
*/
|
|
122
|
+
function buildPhasePrior(weights) {
|
|
123
|
+
let totalWeight = 0;
|
|
124
|
+
const predictions = [];
|
|
125
|
+
for (const stim of ALL_STIMULI) {
|
|
126
|
+
const w = weights[stim] ?? 0.5;
|
|
127
|
+
totalWeight += w;
|
|
128
|
+
predictions.push({ stimulus: stim, probability: w });
|
|
129
|
+
}
|
|
130
|
+
// Normalize
|
|
131
|
+
if (totalWeight > 0) {
|
|
132
|
+
for (const p of predictions) {
|
|
133
|
+
p.probability = p.probability / totalWeight;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
predictions.sort((a, b) => b.probability - a.probability);
|
|
137
|
+
return predictions;
|
|
138
|
+
}
|
|
139
|
+
// ── 2. AnticipationEngine ───────────────────────────────────
|
|
140
|
+
/**
|
|
141
|
+
* Generate anticipatory chemistry changes based on predictions.
|
|
142
|
+
* High-probability positive prediction -> DA/OT micro-rise.
|
|
143
|
+
* High-probability negative prediction -> CORT micro-rise.
|
|
144
|
+
*/
|
|
145
|
+
export function generateAnticipation(predictions, currentChemistry) {
|
|
146
|
+
const anticipation = {};
|
|
147
|
+
for (const key of CHEMICAL_KEYS) {
|
|
148
|
+
anticipation[key] = 0;
|
|
149
|
+
}
|
|
150
|
+
// For each prediction with probability > 0.2, compute micro shift
|
|
151
|
+
for (const pred of predictions) {
|
|
152
|
+
if (pred.probability <= 0.2)
|
|
153
|
+
continue;
|
|
154
|
+
const vector = STIMULUS_VECTORS[pred.stimulus];
|
|
155
|
+
if (!vector)
|
|
156
|
+
continue;
|
|
157
|
+
const scale = 0.15 * pred.probability;
|
|
158
|
+
for (const key of CHEMICAL_KEYS) {
|
|
159
|
+
anticipation[key] += vector[key] * scale;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Clamp total anticipation shift to +/-5 per chemical
|
|
163
|
+
const clamped = {};
|
|
164
|
+
for (const key of CHEMICAL_KEYS) {
|
|
165
|
+
const val = Math.max(-5, Math.min(5, anticipation[key]));
|
|
166
|
+
if (Math.abs(val) > 0.01) {
|
|
167
|
+
clamped[key] = Math.round(val * 100) / 100;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
predictions,
|
|
172
|
+
anticipatoryChemistry: clamped,
|
|
173
|
+
timestamp: new Date().toISOString(),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Compute disappointment/surprise when actual stimulus differs from prediction.
|
|
178
|
+
* Returns additional chemistry delta beyond the normal stimulus response.
|
|
179
|
+
*/
|
|
180
|
+
export function computeSurpriseEffect(anticipated, actualStimulus) {
|
|
181
|
+
if (!actualStimulus || anticipated.predictions.length === 0) {
|
|
182
|
+
return {};
|
|
183
|
+
}
|
|
184
|
+
const topPrediction = anticipated.predictions[0];
|
|
185
|
+
const topConfidence = topPrediction.probability;
|
|
186
|
+
// Find the predicted probability for the actual stimulus
|
|
187
|
+
const actualPrediction = anticipated.predictions.find((p) => p.stimulus === actualStimulus);
|
|
188
|
+
const actualProbability = actualPrediction?.probability ?? 0;
|
|
189
|
+
// If actual matches top prediction, no surprise
|
|
190
|
+
if (actualStimulus === topPrediction.stimulus) {
|
|
191
|
+
return {};
|
|
192
|
+
}
|
|
193
|
+
// Determine if the actual stimulus is positive or negative
|
|
194
|
+
const actualVector = STIMULUS_VECTORS[actualStimulus];
|
|
195
|
+
if (!actualVector)
|
|
196
|
+
return {};
|
|
197
|
+
const actualValence = actualVector.DA + actualVector.HT + actualVector.OT - actualVector.CORT;
|
|
198
|
+
const topVector = STIMULUS_VECTORS[topPrediction.stimulus];
|
|
199
|
+
const topValence = topVector
|
|
200
|
+
? topVector.DA + topVector.HT + topVector.OT - topVector.CORT
|
|
201
|
+
: 0;
|
|
202
|
+
// Surprise magnitude scales with: (1) how confident the prediction was, (2) how unexpected the actual is
|
|
203
|
+
const surpriseMagnitude = topConfidence * (1 - actualProbability);
|
|
204
|
+
if (actualValence > 0 && topValence <= actualValence) {
|
|
205
|
+
// Pleasant surprise: actual is more positive than expected
|
|
206
|
+
return {
|
|
207
|
+
DA: Math.round(5 * surpriseMagnitude * 100) / 100,
|
|
208
|
+
END: Math.round(3 * surpriseMagnitude * 100) / 100,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
else if (actualValence < topValence) {
|
|
212
|
+
// Disappointment: actual is worse than expected (the "crash" from anticipated warmth)
|
|
213
|
+
return {
|
|
214
|
+
DA: Math.round(-5 * surpriseMagnitude * 100) / 100,
|
|
215
|
+
CORT: Math.round(5 * surpriseMagnitude * 100) / 100,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
return {};
|
|
219
|
+
}
|
|
220
|
+
// ── 3. RegretComputer ───────────────────────────────────────
|
|
221
|
+
/** Chemical descriptions for regret messages */
|
|
222
|
+
const CHEMICAL_DESCRIPTIONS = {
|
|
223
|
+
DA: { high: "high dopamine made response too eager", low: "low dopamine made response flat" },
|
|
224
|
+
HT: { high: "high serotonin made response complacent", low: "low serotonin made response unstable" },
|
|
225
|
+
CORT: { high: "high CORT made response too defensive", low: "low CORT made response careless" },
|
|
226
|
+
OT: { high: "high oxytocin made response too trusting", low: "low oxytocin made response too cold" },
|
|
227
|
+
NE: { high: "high norepinephrine made response too reactive", low: "low norepinephrine made response sluggish" },
|
|
228
|
+
END: { high: "high endorphins made response too flippant", low: "low endorphins made response too serious" },
|
|
229
|
+
};
|
|
230
|
+
/**
|
|
231
|
+
* Evaluate if the last interaction would have gone better with different chemistry.
|
|
232
|
+
* Runs a counterfactual: "what if my chemistry had been at baseline?"
|
|
233
|
+
*/
|
|
234
|
+
export function computeRegret(preInteractionState, postInteractionState, outcomeScore, appliedStimulus) {
|
|
235
|
+
// Only generate regret for bad outcomes
|
|
236
|
+
if (outcomeScore >= -0.2) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
const baseline = preInteractionState.baseline;
|
|
240
|
+
const preChemistry = preInteractionState.current;
|
|
241
|
+
// Check if chemistry was significantly deviated from baseline
|
|
242
|
+
let maxDeviation = 0;
|
|
243
|
+
let mostDeviatedKey = "DA";
|
|
244
|
+
for (const key of CHEMICAL_KEYS) {
|
|
245
|
+
const deviation = Math.abs(preChemistry[key] - baseline[key]);
|
|
246
|
+
if (deviation > maxDeviation) {
|
|
247
|
+
maxDeviation = deviation;
|
|
248
|
+
mostDeviatedKey = key;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// No regret if chemistry was near baseline (deviation < 15)
|
|
252
|
+
if (maxDeviation < 15) {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
// Compute regret intensity: |outcomeScore| * (maxDeviation / 100)
|
|
256
|
+
const regretIntensity = Math.min(1, Math.abs(outcomeScore) * (maxDeviation / 100));
|
|
257
|
+
// Build counterfactual delta: difference between baseline and actual pre-interaction chemistry
|
|
258
|
+
const counterfactualDelta = {};
|
|
259
|
+
for (const key of CHEMICAL_KEYS) {
|
|
260
|
+
const diff = baseline[key] - preChemistry[key];
|
|
261
|
+
if (Math.abs(diff) > 5) {
|
|
262
|
+
counterfactualDelta[key] = Math.round(diff * 100) / 100;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// Build description identifying the most deviated chemical
|
|
266
|
+
const deviationDirection = preChemistry[mostDeviatedKey] > baseline[mostDeviatedKey]
|
|
267
|
+
? "high" : "low";
|
|
268
|
+
const description = CHEMICAL_DESCRIPTIONS[mostDeviatedKey][deviationDirection];
|
|
269
|
+
return {
|
|
270
|
+
turnIndex: postInteractionState.meta.totalInteractions,
|
|
271
|
+
counterfactualDelta,
|
|
272
|
+
regretIntensity,
|
|
273
|
+
description,
|
|
274
|
+
timestamp: new Date().toISOString(),
|
|
275
|
+
};
|
|
276
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -49,12 +49,27 @@ export interface EmotionPattern {
|
|
|
49
49
|
expressionHint: string;
|
|
50
50
|
behaviorGuide: string;
|
|
51
51
|
}
|
|
52
|
+
/** Attachment style for relationship dynamics */
|
|
53
|
+
export type AttachmentStyle = "secure" | "anxious" | "avoidant" | "disorganized";
|
|
54
|
+
/** Attachment state tracked per-relationship */
|
|
55
|
+
export interface AttachmentData {
|
|
56
|
+
style: AttachmentStyle;
|
|
57
|
+
strength: number;
|
|
58
|
+
securityScore: number;
|
|
59
|
+
anxietyScore: number;
|
|
60
|
+
avoidanceScore: number;
|
|
61
|
+
lastInteractionAt: string;
|
|
62
|
+
interactionCount: number;
|
|
63
|
+
}
|
|
64
|
+
/** Default attachment for new relationships */
|
|
65
|
+
export declare const DEFAULT_ATTACHMENT: AttachmentData;
|
|
52
66
|
/** Relationship tracking */
|
|
53
67
|
export interface RelationshipState {
|
|
54
68
|
trust: number;
|
|
55
69
|
intimacy: number;
|
|
56
70
|
phase: "stranger" | "acquaintance" | "familiar" | "close" | "deep";
|
|
57
71
|
memory?: string[];
|
|
72
|
+
attachment?: AttachmentData;
|
|
58
73
|
}
|
|
59
74
|
/** Chemical state snapshot for emotional memory */
|
|
60
75
|
export interface ChemicalSnapshot {
|
|
@@ -128,6 +143,8 @@ export declare const MAX_LEARNED_VECTORS = 200;
|
|
|
128
143
|
export declare const MAX_PREDICTION_HISTORY = 50;
|
|
129
144
|
/** Max outcome history entries */
|
|
130
145
|
export declare const MAX_OUTCOME_HISTORY = 50;
|
|
146
|
+
/** Max regret history entries */
|
|
147
|
+
export declare const MAX_REGRET_HISTORY = 20;
|
|
131
148
|
/** Persisted psyche state for an agent (v4: emotional learning) */
|
|
132
149
|
export interface PsycheState {
|
|
133
150
|
version: 3 | 4;
|
package/dist/types.js
CHANGED
|
@@ -56,6 +56,16 @@ export const CHEMICAL_DECAY_SPEED = {
|
|
|
56
56
|
NE: "fast",
|
|
57
57
|
END: "fast",
|
|
58
58
|
};
|
|
59
|
+
/** Default attachment for new relationships */
|
|
60
|
+
export const DEFAULT_ATTACHMENT = {
|
|
61
|
+
style: "secure",
|
|
62
|
+
strength: 0,
|
|
63
|
+
securityScore: 50,
|
|
64
|
+
anxietyScore: 50,
|
|
65
|
+
avoidanceScore: 50,
|
|
66
|
+
lastInteractionAt: new Date().toISOString(),
|
|
67
|
+
interactionCount: 0,
|
|
68
|
+
};
|
|
59
69
|
/** Max history entries to keep */
|
|
60
70
|
export const MAX_EMOTIONAL_HISTORY = 10;
|
|
61
71
|
/** Max compressed session memories per relationship */
|
|
@@ -73,6 +83,8 @@ export const MAX_LEARNED_VECTORS = 200;
|
|
|
73
83
|
export const MAX_PREDICTION_HISTORY = 50;
|
|
74
84
|
/** Max outcome history entries */
|
|
75
85
|
export const MAX_OUTCOME_HISTORY = 50;
|
|
86
|
+
/** Max regret history entries */
|
|
87
|
+
export const MAX_REGRET_HISTORY = 20;
|
|
76
88
|
/** Default relationship for new users */
|
|
77
89
|
export const DEFAULT_RELATIONSHIP = {
|
|
78
90
|
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 = "3.
|
|
14
|
+
const CURRENT_VERSION = "3.1.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");
|
package/openclaw.plugin.json
CHANGED