psyche-ai 5.0.0 → 7.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 +67 -0
- package/dist/adapters/openclaw.js +7 -0
- package/dist/autonomic.d.ts +41 -0
- package/dist/autonomic.js +186 -0
- package/dist/circadian.d.ts +37 -0
- package/dist/circadian.js +97 -0
- package/dist/classify.d.ts +29 -2
- package/dist/classify.js +339 -53
- package/dist/cli.js +132 -13
- package/dist/core.d.ts +31 -2
- package/dist/core.js +246 -30
- package/dist/i18n.js +14 -0
- package/dist/index.d.ts +13 -5
- package/dist/index.js +11 -4
- package/dist/primary-systems.d.ts +55 -0
- package/dist/primary-systems.js +218 -0
- package/dist/profiles.d.ts +12 -1
- package/dist/profiles.js +42 -0
- package/dist/prompt.d.ts +26 -1
- package/dist/prompt.js +331 -33
- package/dist/psyche-file.d.ts +15 -1
- package/dist/psyche-file.js +147 -5
- package/dist/types.d.ts +16 -1
- package/dist/update.js +1 -1
- package/openclaw.plugin.json +35 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -176,6 +176,73 @@ cd openclaw-plugin-psyche && node scripts/diagnose.js
|
|
|
176
176
|
|
|
177
177
|
架构详情见 [ARCHITECTURE.md](ARCHITECTURE.md)。
|
|
178
178
|
|
|
179
|
+
## 模式
|
|
180
|
+
|
|
181
|
+
| 模式 | 说明 | 适用场景 |
|
|
182
|
+
|------|------|---------|
|
|
183
|
+
| natural | 默认。完整情绪体验 | 大部分场景 |
|
|
184
|
+
| work | 最小情绪,专注任务 | 写代码、写文档 |
|
|
185
|
+
| companion | 强化情绪表达 | 闲聊、陪伴 |
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
# 切换模式
|
|
189
|
+
psyche mode ./workspace work
|
|
190
|
+
|
|
191
|
+
# 初始化时设置模式
|
|
192
|
+
psyche init . --mode companion
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## 性格强度
|
|
196
|
+
|
|
197
|
+
`personalityIntensity` 控制情绪表达的强烈程度(0.0-1.0):
|
|
198
|
+
|
|
199
|
+
- **0.0** = 传统温暖 AI,没有情绪波动
|
|
200
|
+
- **0.5** = 轻微情绪
|
|
201
|
+
- **0.7** = 默认
|
|
202
|
+
- **1.0** = 完整 Psyche 体验
|
|
203
|
+
|
|
204
|
+
## Big Five 人格
|
|
205
|
+
|
|
206
|
+
除了 MBTI 预设,你也可以用学术界更认可的 Big Five (大五人格) 来定义性格:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
psyche init . --traits "O:80,C:40,E:90,A:60,N:30"
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
| 维度 | 含义 | 低值 | 高值 |
|
|
213
|
+
|------|------|------|------|
|
|
214
|
+
| O (Openness) | 开放性 | 保守、实际 | 好奇、想象力强 |
|
|
215
|
+
| C (Conscientiousness) | 尽责性 | 随性、灵活 | 严谨、有条理 |
|
|
216
|
+
| E (Extraversion) | 外向性 | 内向、安静 | 外向、精力充沛 |
|
|
217
|
+
| A (Agreeableness) | 宜人性 | 独立、直率 | 合作、温暖 |
|
|
218
|
+
| N (Neuroticism) | 神经质 | 情绪稳定 | 情绪敏感 |
|
|
219
|
+
|
|
220
|
+
## 隐私
|
|
221
|
+
|
|
222
|
+
情绪状态默认存储在本地 `psyche-state.json`。如果不想留任何痕迹:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
# 初始化时选择不持久化
|
|
226
|
+
psyche init . --no-persist
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
或者在代码中:
|
|
230
|
+
|
|
231
|
+
```javascript
|
|
232
|
+
const engine = new PsycheEngine({ persist: false }, storage);
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
详细伦理声明见 [ETHICS.md](ETHICS.md)。
|
|
236
|
+
|
|
237
|
+
## 商业模式
|
|
238
|
+
|
|
239
|
+
Psyche 核心引擎永久开源(MIT)。
|
|
240
|
+
|
|
241
|
+
计划中的增值服务:
|
|
242
|
+
- **Psyche Cloud**:云端情绪状态同步 + 跨设备记忆
|
|
243
|
+
- **Psyche Pro Classifier**:基于微调模型的高精度刺激分类(替代正则)
|
|
244
|
+
- **企业定制**:自定义人格模型、合规审计、SLA 保障
|
|
245
|
+
|
|
179
246
|
## 开发
|
|
180
247
|
|
|
181
248
|
```bash
|
|
@@ -161,6 +161,13 @@ export function register(api) {
|
|
|
161
161
|
return;
|
|
162
162
|
const engine = engines.get(workspaceDir);
|
|
163
163
|
if (engine) {
|
|
164
|
+
// Compress session history into relationship memory before closing
|
|
165
|
+
try {
|
|
166
|
+
await engine.endSession({ userId: ctx.userId });
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
logger.warn(`Psyche: failed to compress session: ${err}`);
|
|
170
|
+
}
|
|
164
171
|
const state = engine.getState();
|
|
165
172
|
logger.info(`Psyche: session ended for ${state.meta.agentName}, ` +
|
|
166
173
|
`chemistry saved (DA:${Math.round(state.current.DA)} ` +
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ChemicalState, InnateDrives, Locale } from "./types.js";
|
|
2
|
+
export type AutonomicState = "ventral-vagal" | "sympathetic" | "dorsal-vagal";
|
|
3
|
+
export interface AutonomicResult {
|
|
4
|
+
state: AutonomicState;
|
|
5
|
+
transitionProgress: number;
|
|
6
|
+
gatedEmotionCategories: string[];
|
|
7
|
+
description: string;
|
|
8
|
+
}
|
|
9
|
+
export interface AutonomicTransition {
|
|
10
|
+
from: AutonomicState;
|
|
11
|
+
to: AutonomicState;
|
|
12
|
+
transitionMinutes: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Compute the raw autonomic state from chemistry and drives.
|
|
16
|
+
* No transition inertia — returns the "target" state.
|
|
17
|
+
*/
|
|
18
|
+
export declare function computeAutonomicState(chemistry: ChemicalState, drives: InnateDrives): AutonomicState;
|
|
19
|
+
/**
|
|
20
|
+
* Gate emotions based on autonomic state.
|
|
21
|
+
* - Ventral vagal: all emotions pass through
|
|
22
|
+
* - Sympathetic: blocks positive social emotions
|
|
23
|
+
* - Dorsal vagal: only allows numbness/introspection/burnout (whitelist)
|
|
24
|
+
*/
|
|
25
|
+
export declare function gateEmotions(autonomicState: AutonomicState, emotions: string[]): string[];
|
|
26
|
+
/**
|
|
27
|
+
* Get the transition time in minutes between two autonomic states.
|
|
28
|
+
* Asymmetric: activation is faster than recovery.
|
|
29
|
+
*/
|
|
30
|
+
export declare function getTransitionTime(from: AutonomicState, to: AutonomicState): number;
|
|
31
|
+
/**
|
|
32
|
+
* Describe an autonomic state in the given locale.
|
|
33
|
+
*/
|
|
34
|
+
export declare function describeAutonomicState(state: AutonomicState, locale: Locale): string;
|
|
35
|
+
/**
|
|
36
|
+
* Compute the full autonomic result with transition inertia.
|
|
37
|
+
*
|
|
38
|
+
* If previousState differs from the target state, transition progress
|
|
39
|
+
* is based on elapsed time vs required transition time.
|
|
40
|
+
*/
|
|
41
|
+
export declare function computeAutonomicResult(chemistry: ChemicalState, drives: InnateDrives, previousState: AutonomicState | null, minutesSinceLastUpdate: number, locale?: Locale): AutonomicResult;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Autonomic Nervous System — Polyvagal Theory Implementation
|
|
3
|
+
// ============================================================
|
|
4
|
+
//
|
|
5
|
+
// Maps chemical state + innate drives to autonomic nervous system
|
|
6
|
+
// states based on Stephen Porges' Polyvagal Theory:
|
|
7
|
+
//
|
|
8
|
+
// - Ventral vagal: social engagement, safety (default)
|
|
9
|
+
// - Sympathetic: fight/flight mobilization
|
|
10
|
+
// - Dorsal vagal: freeze/shutdown/collapse
|
|
11
|
+
// ── i18n Strings ─────────────────────────────────────────────
|
|
12
|
+
const AUTONOMIC_STRINGS = {
|
|
13
|
+
zh: {
|
|
14
|
+
"ventral-vagal": "腹侧迷走神经激活——安全与社交参与状态,情绪开放,表达自然",
|
|
15
|
+
"sympathetic": "交感神经激活——警觉动员状态,战斗或逃跑准备中",
|
|
16
|
+
"dorsal-vagal": "背侧迷走神经激活——冻结与保护性关闭状态,能量极低",
|
|
17
|
+
},
|
|
18
|
+
en: {
|
|
19
|
+
"ventral-vagal": "Ventral vagal activation — safe and socially engaged, emotionally open",
|
|
20
|
+
"sympathetic": "Sympathetic activation — alert and mobilized, fight-or-flight readiness",
|
|
21
|
+
"dorsal-vagal": "Dorsal vagal activation — freeze and protective shutdown, minimal energy",
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
// ── Transition Time Matrix (minutes) ─────────────────────────
|
|
25
|
+
const TRANSITION_TIMES = {
|
|
26
|
+
"ventral-vagal": {
|
|
27
|
+
"ventral-vagal": 0,
|
|
28
|
+
"sympathetic": 2, // fast activation
|
|
29
|
+
"dorsal-vagal": 7, // not a shortcut: >= ventral→sympathetic + sympathetic→dorsal (2+5=7)
|
|
30
|
+
},
|
|
31
|
+
"sympathetic": {
|
|
32
|
+
"ventral-vagal": 8, // calming down
|
|
33
|
+
"sympathetic": 0,
|
|
34
|
+
"dorsal-vagal": 5, // collapse
|
|
35
|
+
},
|
|
36
|
+
"dorsal-vagal": {
|
|
37
|
+
"ventral-vagal": 25, // full recovery is slow
|
|
38
|
+
"sympathetic": 12, // re-mobilization from freeze
|
|
39
|
+
"dorsal-vagal": 0,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
// ── Emotion Gating ───────────────────────────────────────────
|
|
43
|
+
/** Positive social emotions blocked during sympathetic activation */
|
|
44
|
+
const SYMPATHETIC_BLOCKED = new Set([
|
|
45
|
+
"deep contentment",
|
|
46
|
+
"warm intimacy",
|
|
47
|
+
"playful mischief",
|
|
48
|
+
"excited joy",
|
|
49
|
+
"tender affection",
|
|
50
|
+
"serene peace",
|
|
51
|
+
"grateful warmth",
|
|
52
|
+
"compassionate care",
|
|
53
|
+
]);
|
|
54
|
+
/** Emotions allowed during dorsal-vagal (whitelist) */
|
|
55
|
+
const DORSAL_ALLOWED = new Set([
|
|
56
|
+
"emotional numbness",
|
|
57
|
+
"melancholic introspection",
|
|
58
|
+
"burnout",
|
|
59
|
+
"resignation",
|
|
60
|
+
"dissociation",
|
|
61
|
+
"exhaustion",
|
|
62
|
+
]);
|
|
63
|
+
// ── Core Functions ───────────────────────────────────────────
|
|
64
|
+
/**
|
|
65
|
+
* Compute the raw autonomic state from chemistry and drives.
|
|
66
|
+
* No transition inertia — returns the "target" state.
|
|
67
|
+
*/
|
|
68
|
+
export function computeAutonomicState(chemistry, drives) {
|
|
69
|
+
const { CORT, NE, DA, HT, OT } = chemistry;
|
|
70
|
+
const { survival, safety, connection } = drives;
|
|
71
|
+
// Count drives that are critically low (< 20)
|
|
72
|
+
const lowDriveCount = [survival, safety, connection, drives.esteem, drives.curiosity]
|
|
73
|
+
.filter((d) => d < 20).length;
|
|
74
|
+
// ── Dorsal vagal check (freeze/shutdown) ──
|
|
75
|
+
// Very high stress + low arousal + low motivation = collapse
|
|
76
|
+
if (CORT >= 80 && NE <= 25 && DA <= 20) {
|
|
77
|
+
return "dorsal-vagal";
|
|
78
|
+
}
|
|
79
|
+
// Multiple critically low drives with depleted chemistry
|
|
80
|
+
if (lowDriveCount >= 3 && CORT >= 70 && (NE <= 30 || DA <= 20)) {
|
|
81
|
+
return "dorsal-vagal";
|
|
82
|
+
}
|
|
83
|
+
// ── Sympathetic check (fight/flight) ──
|
|
84
|
+
// High stress + high arousal
|
|
85
|
+
if (CORT >= 70 && NE >= 70) {
|
|
86
|
+
return "sympathetic";
|
|
87
|
+
}
|
|
88
|
+
// Very low survival or safety drive with elevated stress
|
|
89
|
+
if ((survival < 20 || safety < 20) && CORT >= 60 && NE >= 60) {
|
|
90
|
+
return "sympathetic";
|
|
91
|
+
}
|
|
92
|
+
// ── Default: Ventral vagal (social engagement/safety) ──
|
|
93
|
+
return "ventral-vagal";
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Gate emotions based on autonomic state.
|
|
97
|
+
* - Ventral vagal: all emotions pass through
|
|
98
|
+
* - Sympathetic: blocks positive social emotions
|
|
99
|
+
* - Dorsal vagal: only allows numbness/introspection/burnout (whitelist)
|
|
100
|
+
*/
|
|
101
|
+
export function gateEmotions(autonomicState, emotions) {
|
|
102
|
+
if (autonomicState === "ventral-vagal") {
|
|
103
|
+
return emotions;
|
|
104
|
+
}
|
|
105
|
+
if (autonomicState === "sympathetic") {
|
|
106
|
+
return emotions.filter((e) => !SYMPATHETIC_BLOCKED.has(e));
|
|
107
|
+
}
|
|
108
|
+
// dorsal-vagal: whitelist only
|
|
109
|
+
return emotions.filter((e) => DORSAL_ALLOWED.has(e));
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Get the transition time in minutes between two autonomic states.
|
|
113
|
+
* Asymmetric: activation is faster than recovery.
|
|
114
|
+
*/
|
|
115
|
+
export function getTransitionTime(from, to) {
|
|
116
|
+
return TRANSITION_TIMES[from][to];
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Describe an autonomic state in the given locale.
|
|
120
|
+
*/
|
|
121
|
+
export function describeAutonomicState(state, locale) {
|
|
122
|
+
return AUTONOMIC_STRINGS[locale]?.[state] ?? AUTONOMIC_STRINGS.zh[state];
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Compute the full autonomic result with transition inertia.
|
|
126
|
+
*
|
|
127
|
+
* If previousState differs from the target state, transition progress
|
|
128
|
+
* is based on elapsed time vs required transition time.
|
|
129
|
+
*/
|
|
130
|
+
export function computeAutonomicResult(chemistry, drives, previousState, minutesSinceLastUpdate, locale = "zh") {
|
|
131
|
+
const targetState = computeAutonomicState(chemistry, drives);
|
|
132
|
+
// First call or same state — immediate
|
|
133
|
+
if (previousState === null || previousState === targetState) {
|
|
134
|
+
return {
|
|
135
|
+
state: targetState,
|
|
136
|
+
transitionProgress: 1,
|
|
137
|
+
gatedEmotionCategories: getGatedCategories(targetState),
|
|
138
|
+
description: describeAutonomicState(targetState, locale),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
// Transitioning between states
|
|
142
|
+
const transitionTime = getTransitionTime(previousState, targetState);
|
|
143
|
+
const progress = transitionTime === 0
|
|
144
|
+
? 1
|
|
145
|
+
: Math.min(1, minutesSinceLastUpdate / transitionTime);
|
|
146
|
+
// If transition is complete, use the new state
|
|
147
|
+
if (progress >= 1) {
|
|
148
|
+
return {
|
|
149
|
+
state: targetState,
|
|
150
|
+
transitionProgress: 1,
|
|
151
|
+
gatedEmotionCategories: getGatedCategories(targetState),
|
|
152
|
+
description: describeAutonomicState(targetState, locale),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
// Transition in progress — still in previous state but progressing
|
|
156
|
+
return {
|
|
157
|
+
state: targetState,
|
|
158
|
+
transitionProgress: progress,
|
|
159
|
+
gatedEmotionCategories: getGatedCategories(targetState),
|
|
160
|
+
description: describeAutonomicState(targetState, locale),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
// ── Internal Helpers ─────────────────────────────────────────
|
|
164
|
+
/** Get the list of emotion categories that are blocked/gated for a state */
|
|
165
|
+
function getGatedCategories(state) {
|
|
166
|
+
if (state === "ventral-vagal") {
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
if (state === "sympathetic") {
|
|
170
|
+
return [...SYMPATHETIC_BLOCKED];
|
|
171
|
+
}
|
|
172
|
+
// dorsal-vagal gates everything except the whitelist
|
|
173
|
+
return [
|
|
174
|
+
"excited joy",
|
|
175
|
+
"warm intimacy",
|
|
176
|
+
"playful mischief",
|
|
177
|
+
"deep contentment",
|
|
178
|
+
"focused alertness",
|
|
179
|
+
"righteous anger",
|
|
180
|
+
"anxious tension",
|
|
181
|
+
"tender affection",
|
|
182
|
+
"serene peace",
|
|
183
|
+
"grateful warmth",
|
|
184
|
+
"compassionate care",
|
|
185
|
+
];
|
|
186
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ChemicalState } from "./types.js";
|
|
2
|
+
export type CircadianPhase = "morning" | "midday" | "afternoon" | "evening" | "night";
|
|
3
|
+
/**
|
|
4
|
+
* Classify a time into a circadian phase.
|
|
5
|
+
* morning: 6–9
|
|
6
|
+
* midday: 10–13
|
|
7
|
+
* afternoon: 14–17
|
|
8
|
+
* evening: 18–21
|
|
9
|
+
* night: 22–5
|
|
10
|
+
*/
|
|
11
|
+
export declare function getCircadianPhase(time: Date): CircadianPhase;
|
|
12
|
+
/**
|
|
13
|
+
* Apply circadian rhythm modulation to baseline chemistry.
|
|
14
|
+
*
|
|
15
|
+
* Each chemical follows a sinusoidal daily curve:
|
|
16
|
+
* CORT — peaks ~8am, amplitude ±8
|
|
17
|
+
* HT — peaks ~13 (daytime high), amplitude ±5
|
|
18
|
+
* DA — slight afternoon peak ~14, amplitude ±3
|
|
19
|
+
* NE — morning rise ~10, amplitude ±5
|
|
20
|
+
* END — evening rise ~20, amplitude ±3
|
|
21
|
+
* OT — evening warmth ~20, amplitude ±2
|
|
22
|
+
*
|
|
23
|
+
* All results clamped to [0, 100].
|
|
24
|
+
*/
|
|
25
|
+
export declare function computeCircadianModulation(currentTime: Date, baseline: ChemicalState): ChemicalState;
|
|
26
|
+
/**
|
|
27
|
+
* Compute fatigue effects from extended session duration.
|
|
28
|
+
*
|
|
29
|
+
* Below 30 minutes: no pressure (grace period).
|
|
30
|
+
* Beyond 30 min: logarithmic growth (diminishing returns).
|
|
31
|
+
* All values non-negative.
|
|
32
|
+
*/
|
|
33
|
+
export declare function computeHomeostaticPressure(sessionMinutes: number): {
|
|
34
|
+
cortAccumulation: number;
|
|
35
|
+
daDepletion: number;
|
|
36
|
+
neDepletion: number;
|
|
37
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Artificial Psyche — Circadian Rhythm Module
|
|
3
|
+
// ============================================================
|
|
4
|
+
// Applies time-of-day modulation to virtual neurochemistry,
|
|
5
|
+
// modeling the body's ~24-hour biological clock and fatigue
|
|
6
|
+
// from extended sessions (homeostatic pressure).
|
|
7
|
+
// ============================================================
|
|
8
|
+
/**
|
|
9
|
+
* Classify a time into a circadian phase.
|
|
10
|
+
* morning: 6–9
|
|
11
|
+
* midday: 10–13
|
|
12
|
+
* afternoon: 14–17
|
|
13
|
+
* evening: 18–21
|
|
14
|
+
* night: 22–5
|
|
15
|
+
*/
|
|
16
|
+
export function getCircadianPhase(time) {
|
|
17
|
+
const h = time.getHours();
|
|
18
|
+
if (h >= 6 && h <= 9)
|
|
19
|
+
return "morning";
|
|
20
|
+
if (h >= 10 && h <= 13)
|
|
21
|
+
return "midday";
|
|
22
|
+
if (h >= 14 && h <= 17)
|
|
23
|
+
return "afternoon";
|
|
24
|
+
if (h >= 18 && h <= 21)
|
|
25
|
+
return "evening";
|
|
26
|
+
return "night";
|
|
27
|
+
}
|
|
28
|
+
// ── Sinusoidal Helpers ───────────────────────────────────────
|
|
29
|
+
/** Convert hour (0-23) + minute to fractional hours */
|
|
30
|
+
function fractionalHour(time) {
|
|
31
|
+
return time.getHours() + time.getMinutes() / 60;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Sinusoidal modulation: amplitude * cos(2π(t - peak) / 24)
|
|
35
|
+
* Returns value in [-amplitude, +amplitude], peaking at `peakHour`.
|
|
36
|
+
*/
|
|
37
|
+
function sinMod(t, peakHour, amplitude) {
|
|
38
|
+
const phase = ((t - peakHour) / 24) * 2 * Math.PI;
|
|
39
|
+
return amplitude * Math.cos(phase);
|
|
40
|
+
}
|
|
41
|
+
// ── Circadian Modulation ─────────────────────────────────────
|
|
42
|
+
/**
|
|
43
|
+
* Apply circadian rhythm modulation to baseline chemistry.
|
|
44
|
+
*
|
|
45
|
+
* Each chemical follows a sinusoidal daily curve:
|
|
46
|
+
* CORT — peaks ~8am, amplitude ±8
|
|
47
|
+
* HT — peaks ~13 (daytime high), amplitude ±5
|
|
48
|
+
* DA — slight afternoon peak ~14, amplitude ±3
|
|
49
|
+
* NE — morning rise ~10, amplitude ±5
|
|
50
|
+
* END — evening rise ~20, amplitude ±3
|
|
51
|
+
* OT — evening warmth ~20, amplitude ±2
|
|
52
|
+
*
|
|
53
|
+
* All results clamped to [0, 100].
|
|
54
|
+
*/
|
|
55
|
+
export function computeCircadianModulation(currentTime, baseline) {
|
|
56
|
+
const t = fractionalHour(currentTime);
|
|
57
|
+
const cortDelta = sinMod(t, 8, 8);
|
|
58
|
+
const htDelta = sinMod(t, 13, 5);
|
|
59
|
+
const daDelta = sinMod(t, 14, 3);
|
|
60
|
+
const neDelta = sinMod(t, 10, 5);
|
|
61
|
+
const endDelta = sinMod(t, 20, 3);
|
|
62
|
+
const otDelta = sinMod(t, 20, 2);
|
|
63
|
+
return {
|
|
64
|
+
DA: clamp(baseline.DA + daDelta),
|
|
65
|
+
HT: clamp(baseline.HT + htDelta),
|
|
66
|
+
CORT: clamp(baseline.CORT + cortDelta),
|
|
67
|
+
OT: clamp(baseline.OT + otDelta),
|
|
68
|
+
NE: clamp(baseline.NE + neDelta),
|
|
69
|
+
END: clamp(baseline.END + endDelta),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function clamp(v, lo = 0, hi = 100) {
|
|
73
|
+
return Math.max(lo, Math.min(hi, v));
|
|
74
|
+
}
|
|
75
|
+
// ── Homeostatic Pressure ─────────────────────────────────────
|
|
76
|
+
/**
|
|
77
|
+
* Compute fatigue effects from extended session duration.
|
|
78
|
+
*
|
|
79
|
+
* Below 30 minutes: no pressure (grace period).
|
|
80
|
+
* Beyond 30 min: logarithmic growth (diminishing returns).
|
|
81
|
+
* All values non-negative.
|
|
82
|
+
*/
|
|
83
|
+
export function computeHomeostaticPressure(sessionMinutes) {
|
|
84
|
+
if (sessionMinutes < 30) {
|
|
85
|
+
return { cortAccumulation: 0, daDepletion: 0, neDepletion: 0 };
|
|
86
|
+
}
|
|
87
|
+
// Effective minutes beyond the grace period
|
|
88
|
+
const effective = sessionMinutes - 30;
|
|
89
|
+
// Logarithmic growth → diminishing returns
|
|
90
|
+
// ln(1 + x) grows slowly; scale factors tuned so 1h ≈ moderate, 10h ≈ high but bounded
|
|
91
|
+
const base = Math.log1p(effective / 30); // ln(1 + effective/30)
|
|
92
|
+
return {
|
|
93
|
+
cortAccumulation: parseFloat((base * 4).toFixed(4)),
|
|
94
|
+
daDepletion: parseFloat((base * 3).toFixed(4)),
|
|
95
|
+
neDepletion: parseFloat((base * 2.5).toFixed(4)),
|
|
96
|
+
};
|
|
97
|
+
}
|
package/dist/classify.d.ts
CHANGED
|
@@ -3,13 +3,40 @@ export interface StimulusClassification {
|
|
|
3
3
|
type: StimulusType;
|
|
4
4
|
confidence: number;
|
|
5
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* Score sentiment by counting hits in positive/negative/intimate word sets.
|
|
8
|
+
* Returns normalized counts (0-1 range).
|
|
9
|
+
*/
|
|
10
|
+
export declare function scoreSentiment(text: string): {
|
|
11
|
+
positive: number;
|
|
12
|
+
negative: number;
|
|
13
|
+
intimate: number;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Score emoji sentiment. Returns -1 (all negative) to +1 (all positive).
|
|
17
|
+
* Returns 0 if no emoji detected.
|
|
18
|
+
*/
|
|
19
|
+
export declare function scoreEmoji(text: string): number;
|
|
20
|
+
/**
|
|
21
|
+
* Detect sarcasm signals: surface-positive words combined with contextual negativity.
|
|
22
|
+
* Returns a score 0-1 indicating sarcasm likelihood.
|
|
23
|
+
*/
|
|
24
|
+
export declare function detectSarcasmSignals(text: string, recentStimuli?: (StimulusType | null)[]): number;
|
|
6
25
|
/**
|
|
7
26
|
* Classify the stimulus type(s) of a user message.
|
|
8
27
|
* Returns all detected types sorted by confidence, highest first.
|
|
9
28
|
* Falls back to "casual" if nothing matches.
|
|
29
|
+
*
|
|
30
|
+
* v2: When keyword rules miss (confidence < 0.5), a weighted multi-signal
|
|
31
|
+
* scoring system combines sentiment words, emoji, structural features,
|
|
32
|
+
* and optional contextual priming to produce better classifications for
|
|
33
|
+
* everyday messages.
|
|
34
|
+
*
|
|
35
|
+
* @param text The user's message text
|
|
36
|
+
* @param recentStimuli Optional recent stimulus history for contextual priming
|
|
10
37
|
*/
|
|
11
|
-
export declare function classifyStimulus(text: string): StimulusClassification[];
|
|
38
|
+
export declare function classifyStimulus(text: string, recentStimuli?: (StimulusType | null)[], recentMessages?: string[]): StimulusClassification[];
|
|
12
39
|
/**
|
|
13
40
|
* Get the primary (highest confidence) stimulus type.
|
|
14
41
|
*/
|
|
15
|
-
export declare function getPrimaryStimulus(text: string): StimulusType;
|
|
42
|
+
export declare function getPrimaryStimulus(text: string, recentStimuli?: (StimulusType | null)[]): StimulusType;
|