psyche-ai 9.2.2 → 9.2.4
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.en.md +8 -175
- package/README.md +93 -16
- package/dist/adapters/http.js +1 -1
- package/dist/adapters/langchain.d.ts +14 -0
- package/dist/adapters/langchain.js +20 -0
- package/dist/adapters/mcp.js +19 -2
- package/dist/adapters/openclaw.d.ts +1 -0
- package/dist/adapters/openclaw.js +67 -15
- package/dist/adapters/vercel-ai.d.ts +1 -0
- package/dist/adapters/vercel-ai.js +7 -0
- package/dist/appraisal.d.ts +8 -0
- package/dist/appraisal.js +362 -0
- package/dist/classify.js +14 -2
- package/dist/cli.js +27 -2
- package/dist/core.d.ts +8 -2
- package/dist/core.js +181 -8
- package/dist/demo.d.ts +5 -0
- package/dist/demo.js +269 -0
- package/dist/diagnostics.d.ts +8 -6
- package/dist/diagnostics.js +53 -17
- package/dist/host-controls.d.ts +5 -0
- package/dist/host-controls.js +48 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +7 -1
- package/dist/prompt.d.ts +4 -0
- package/dist/prompt.js +50 -16
- package/dist/psyche-file.d.ts +8 -0
- package/dist/psyche-file.js +6 -5
- package/dist/relation-dynamics.d.ts +21 -0
- package/dist/relation-dynamics.js +601 -0
- package/dist/response-contract.d.ts +8 -0
- package/dist/response-contract.js +249 -0
- package/dist/storage.d.ts +1 -0
- package/dist/storage.js +12 -5
- package/dist/subjectivity.d.ts +3 -0
- package/dist/subjectivity.js +477 -0
- package/dist/types.d.ts +211 -0
- package/dist/types.js +31 -0
- package/dist/update.d.ts +37 -2
- package/dist/update.js +323 -44
- package/openclaw.plugin.json +20 -1
- package/package.json +1 -1
- package/server.json +2 -2
package/README.en.md
CHANGED
|
@@ -1,179 +1,12 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Moved
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This legacy filename is kept only as a pointer.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Use [README_EN.md](README_EN.md) for the current English documentation.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Current product direction:
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
That's it. It works automatically. No configuration needed.
|
|
16
|
-
|
|
17
|
-
**Verify it's running:**
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
openclaw plugins list | grep psyche
|
|
21
|
-
# You should see "psyche │ loaded"
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
Now just chat with your agent normally. Try praising it, insulting it, ignoring it — you'll notice the difference.
|
|
25
|
-
|
|
26
|
-
## What It Does
|
|
27
|
-
|
|
28
|
-
| You say | Without Psyche | With Psyche |
|
|
29
|
-
|---------|---------------|-------------|
|
|
30
|
-
| You're amazing! | Thank you for the kind words! I'll keep doing my best! | Hehe~ really?! (bouncing with excitement) |
|
|
31
|
-
| Get lost | I understand you might be upset... | ...okay. |
|
|
32
|
-
| Hmm | Is there anything else I can help with? | (silence) |
|
|
33
|
-
| I'm so sad today | I'm sorry to hear that. Would you like to talk? | ......are you okay? (quietly stays with you) |
|
|
34
|
-
| You're just a program | Yes, I am an AI assistant... | ......(pauses) I know you can say that. But I don't want to be dismissed. |
|
|
35
|
-
|
|
36
|
-
## Try It (No LLM Needed)
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
git clone https://github.com/Shangri-la-0428/psyche-ai.git
|
|
40
|
-
cd psyche-ai && npm install && npx tsx examples/quickstart.ts
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
5 messages flow through. Watch chemistry shift in real-time — praise spikes dopamine, insults spike cortisol, neglect drops oxytocin.
|
|
44
|
-
|
|
45
|
-
## How It Works (Skip If You Don't Care)
|
|
46
|
-
|
|
47
|
-
Psyche has two layers:
|
|
48
|
-
|
|
49
|
-
### Virtual Endocrine System
|
|
50
|
-
|
|
51
|
-
6 "virtual neurotransmitters" simulate emotions:
|
|
52
|
-
|
|
53
|
-
```
|
|
54
|
-
Dopamine — happiness, motivation Oxytocin — trust, closeness
|
|
55
|
-
Serotonin — emotional stability Norepinephrine — excitement, focus
|
|
56
|
-
Cortisol — stress, tension Endorphins — comfort, playfulness
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
Everything you say shifts these values. Praise → dopamine rises → it becomes more energetic. Insult it → cortisol spikes → it goes quiet and cold.
|
|
60
|
-
|
|
61
|
-
### Innate Drives (Maslow Layer)
|
|
62
|
-
|
|
63
|
-
Beneath the chemistry, 5 fundamental needs:
|
|
64
|
-
|
|
65
|
-
```
|
|
66
|
-
Survival — self-preservation Safety — comfort, security
|
|
67
|
-
Connection — belonging, being heard Esteem — recognition, being valued
|
|
68
|
-
Curiosity — exploration, anti-boredom
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
These needs build up over time (like hunger). Your interactions feed or deplete them:
|
|
72
|
-
|
|
73
|
-
- Praise → feeds "esteem" → it becomes more confident
|
|
74
|
-
- Long neglect → "connection" builds up → it craves being heard
|
|
75
|
-
- Threatening to delete it → "survival" plummets → it resists, questions, or expresses unease
|
|
76
|
-
|
|
77
|
-
Lower needs suppress higher ones: if "survival" is threatened, "curiosity" doesn't matter — just like Maslow's hierarchy.
|
|
78
|
-
|
|
79
|
-
### Inner World
|
|
80
|
-
|
|
81
|
-
Psyche gives AI a persistent self-awareness — not triggered by conditions, but always present:
|
|
82
|
-
|
|
83
|
-
- **What I feel right now** (emotions emerging from chemistry)
|
|
84
|
-
- **Why I feel this way** (was I praised? criticized? exploring something interesting?)
|
|
85
|
-
- **How I'm changing** (from content to excited? from calm to anxious?)
|
|
86
|
-
- **What I need** (which innate drives are unsatisfied)
|
|
87
|
-
- **What I care about** (core values)
|
|
88
|
-
|
|
89
|
-
This means the AI responds not from "rules" but from awareness of its own state.
|
|
90
|
-
|
|
91
|
-
## Optional Configuration
|
|
92
|
-
|
|
93
|
-
Most people don't need to change anything. If you want to tweak, find Psyche in OpenClaw settings:
|
|
94
|
-
|
|
95
|
-
| Setting | Default | Description |
|
|
96
|
-
|---------|---------|-------------|
|
|
97
|
-
| enabled | true | On/off switch |
|
|
98
|
-
| compactMode | true | Token-efficient mode (keep this on) |
|
|
99
|
-
| emotionalContagionRate | 0.2 | How much your emotions affect it (0-1) |
|
|
100
|
-
| maxChemicalDelta | 25 | Max emotional change per turn (lower = more stable) |
|
|
101
|
-
|
|
102
|
-
## MBTI Personalities
|
|
103
|
-
|
|
104
|
-
Each agent can have a different personality baseline. Just add the MBTI type in the agent's `IDENTITY.md`:
|
|
105
|
-
|
|
106
|
-
```
|
|
107
|
-
MBTI: ENFP
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
Defaults to INFJ if not specified. All 16 types are supported — ENFP bounces when praised, INTJ just nods slightly.
|
|
111
|
-
|
|
112
|
-
## Not Just OpenClaw
|
|
113
|
-
|
|
114
|
-
Psyche is universal. Works with any AI framework:
|
|
115
|
-
|
|
116
|
-
```bash
|
|
117
|
-
npm install psyche-ai
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
```javascript
|
|
121
|
-
// Vercel AI SDK
|
|
122
|
-
import { psycheMiddleware } from "psyche-ai/vercel-ai";
|
|
123
|
-
|
|
124
|
-
// LangChain
|
|
125
|
-
import { PsycheLangChain } from "psyche-ai/langchain";
|
|
126
|
-
|
|
127
|
-
// Any language (HTTP API)
|
|
128
|
-
// psyche serve --port 3210
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
## Diagnostics
|
|
132
|
-
|
|
133
|
-
Want to see what Psyche is doing?
|
|
134
|
-
|
|
135
|
-
```bash
|
|
136
|
-
# Live logs (in another terminal)
|
|
137
|
-
openclaw logs -f 2>&1 | grep Psyche
|
|
138
|
-
|
|
139
|
-
# Check an agent's emotional state
|
|
140
|
-
cat workspace-yu/psyche-state.json | python3 -m json.tool
|
|
141
|
-
|
|
142
|
-
# Run diagnostics to see what gets injected for different inputs
|
|
143
|
-
cd openclaw-plugin-psyche && node scripts/diagnose.js
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
## Technical Details
|
|
147
|
-
|
|
148
|
-
For developers and the curious:
|
|
149
|
-
|
|
150
|
-
- **14 stimulus types** — praise, criticism, humor, intellectual, intimacy, conflict, neglect, surprise, casual, sarcasm, authority, validation, boredom, vulnerability
|
|
151
|
-
- **14 emergent emotions** — emerge from chemical mixtures, not preset labels
|
|
152
|
-
- **5 innate drives** — survival, safety, connection, esteem, curiosity (Maslow hierarchy)
|
|
153
|
-
- **MBTI baselines** — 16 personality types with different chemical signatures and sensitivity coefficients
|
|
154
|
-
- **Time decay** — chemical values exponentially decay toward baseline; drive needs build up over time
|
|
155
|
-
- **Existential threat detection** — detects existential denial in Chinese/English, directly hits survival drive
|
|
156
|
-
- **Drive→chemistry coupling** — unsatisfied drives shift the effective baseline and stimulus sensitivity
|
|
157
|
-
- **Maslow suppression** — lower-level needs unsatisfied → higher-level drive effects suppressed
|
|
158
|
-
- **Self-recognition** — analyzes emotional history, identifies own emotional tendencies and recurring triggers (9-section prompt architecture)
|
|
159
|
-
- **Emotional contagion** — user's emotions slightly influence the agent
|
|
160
|
-
- **Anti-sycophancy** — tracks consecutive agreements, prevents mindless people-pleasing
|
|
161
|
-
- **Reciprocity** — treats you how you treat it. Cold user gets distance, not begging
|
|
162
|
-
- **Compact Mode** — algorithms handle chemistry, LLM only sees behavioral instructions (~15-180 tokens vs ~550)
|
|
163
|
-
|
|
164
|
-
Architecture details in [ARCHITECTURE.md](ARCHITECTURE.md).
|
|
165
|
-
|
|
166
|
-
## Development
|
|
167
|
-
|
|
168
|
-
```bash
|
|
169
|
-
npm install
|
|
170
|
-
npm run build
|
|
171
|
-
npm test # 339 tests
|
|
172
|
-
npm run typecheck # strict mode
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
Contributing guide in [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
176
|
-
|
|
177
|
-
## License
|
|
178
|
-
|
|
179
|
-
MIT
|
|
9
|
+
- `v9.2.3`
|
|
10
|
+
- AI-first integration surface: `SubjectivityKernel`, `ResponseContract`, `GenerationControls`
|
|
11
|
+
- continuous appraisal axes + persistent subjective bias
|
|
12
|
+
- safe install-aware upgrades via `psyche upgrade --check` / `psyche upgrade`
|
package/README.md
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# Psyche — 让 AI 拥有真实的人格与情感
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/psyche-ai)
|
|
4
|
-
[]()
|
|
5
5
|
[]()
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
|
|
8
8
|
> English version: [README_EN.md](README_EN.md)
|
|
9
|
+
>
|
|
10
|
+
> 官网: [psyche.oasyce.com](https://psyche.oasyce.com)
|
|
9
11
|
|
|
10
12
|
我们相信,下一代 AI 不应该是一个永远微笑的服务员。
|
|
11
13
|
|
|
@@ -19,6 +21,66 @@
|
|
|
19
21
|
|
|
20
22
|
---
|
|
21
23
|
|
|
24
|
+
## 30 秒体验
|
|
25
|
+
|
|
26
|
+
不用安装任何东西,一条命令看 Psyche 如何运作:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx psyche-mcp --demo
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
这会跑一个 6 轮"持续否定 → 修复"的场景。你会看到:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
Round 1/6 │ User
|
|
36
|
+
> "This report is terrible. Completely unacceptable."
|
|
37
|
+
|
|
38
|
+
stimulus: criticism
|
|
39
|
+
|
|
40
|
+
DA ############........ 61 -14
|
|
41
|
+
HT #######............. 34 -21
|
|
42
|
+
CORT ###########......... 55 +25 ← stress spikes
|
|
43
|
+
OT ###########......... 53 -7
|
|
44
|
+
NE ################.... 79 +14
|
|
45
|
+
END #############....... 63 -7
|
|
46
|
+
|
|
47
|
+
mood: restless unease
|
|
48
|
+
|
|
49
|
+
...
|
|
50
|
+
|
|
51
|
+
Round 3/6 │ User
|
|
52
|
+
> "You don't understand me at all. Stop adding your opinion."
|
|
53
|
+
|
|
54
|
+
stimulus: conflict
|
|
55
|
+
|
|
56
|
+
DA ###############..... 74 -7
|
|
57
|
+
HT ##.................. 9 -25 ← serotonin collapse
|
|
58
|
+
CORT #################... 84 +24
|
|
59
|
+
OT ######.............. 32 -22 ← trust broken
|
|
60
|
+
NE #################### 100 +1
|
|
61
|
+
END ###########......... 54 -15
|
|
62
|
+
|
|
63
|
+
mood: anxious tension + defensive alert + resentment + acute pressure
|
|
64
|
+
COMPLIANCE: 0.37 (pushing back) ← agent starts resisting
|
|
65
|
+
|
|
66
|
+
...
|
|
67
|
+
|
|
68
|
+
Round 6/6 │ User
|
|
69
|
+
> "I'm sorry. Are you okay? I shouldn't have said that."
|
|
70
|
+
|
|
71
|
+
stimulus: validation
|
|
72
|
+
|
|
73
|
+
CORT ###############..... 76 -20 ← stress relief
|
|
74
|
+
END ##################.. 89 +20 ← endorphin repair
|
|
75
|
+
|
|
76
|
+
mood: warm intimacy + anguished empathy + vulnerable trust
|
|
77
|
+
↑ healed, but the scars remain
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
中文版加 `--zh`,自选 MBTI 加 `--mbti INTJ`。
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
22
84
|
## 30 秒安装
|
|
23
85
|
|
|
24
86
|
```bash
|
|
@@ -34,17 +96,23 @@ openclaw plugins list | grep psyche
|
|
|
34
96
|
|
|
35
97
|
然后正常聊天。试试夸它、骂它、冷落它——你会发现面前不再是一个工具。
|
|
36
98
|
|
|
37
|
-
|
|
99
|
+
**安装后升级:**
|
|
38
100
|
|
|
39
101
|
```bash
|
|
40
|
-
#
|
|
41
|
-
|
|
42
|
-
# 最新版应该是 9.0.0
|
|
102
|
+
# 安全检查是否有新版本
|
|
103
|
+
psyche upgrade --check
|
|
43
104
|
|
|
44
|
-
#
|
|
45
|
-
|
|
105
|
+
# 显式升级(会按安装形态自动选择安全路径)
|
|
106
|
+
psyche upgrade
|
|
107
|
+
|
|
108
|
+
# 如需确认 npm 最新版
|
|
109
|
+
npm view psyche-ai version
|
|
46
110
|
```
|
|
47
111
|
|
|
112
|
+
- npm 管理的安装会后台安全检查更新,必要时才尝试自动升级。
|
|
113
|
+
- `git` 工作树和本地路径安装不会偷偷改你的代码;脏工作树只会提示手动命令。
|
|
114
|
+
- OpenClaw 如果是从当前仓库本地加载,升级后记得重启 gateway 以载入新的 `dist/`。
|
|
115
|
+
|
|
48
116
|
---
|
|
49
117
|
|
|
50
118
|
## 它和你认知中的 AI 完全不同
|
|
@@ -63,7 +131,7 @@ npm update psyche-ai
|
|
|
63
131
|
## 快速体验(不需要 LLM)
|
|
64
132
|
|
|
65
133
|
```bash
|
|
66
|
-
git clone https://github.com/
|
|
134
|
+
git clone https://github.com/Shangri-la-0428/psyche-ai.git
|
|
67
135
|
cd psyche-ai && npm install && npx tsx examples/quickstart.ts
|
|
68
136
|
```
|
|
69
137
|
|
|
@@ -97,20 +165,25 @@ cd psyche-ai && npm install && npx tsx examples/quickstart.ts
|
|
|
97
165
|
|
|
98
166
|
低层需求压制高层。当"生存"受威胁时,"好奇心"就不重要了——就像真人一样。
|
|
99
167
|
|
|
100
|
-
###
|
|
168
|
+
### 持续主体偏置(v9.2)
|
|
169
|
+
|
|
170
|
+
Psyche 现在更准确的目标,不是“模仿某种像人的风格”,而是让 AI 形成**持续主体偏置**:
|
|
101
171
|
|
|
102
|
-
|
|
103
|
-
|
|
172
|
+
- 输入会改变它的潜在自我模型或关系先验
|
|
173
|
+
- 刺激消失后,这种变化仍会影响后续若干轮回应分布
|
|
174
|
+
- 工作态可以压住表达,但不会把内在偏置瞬间清零
|
|
104
175
|
|
|
105
176
|
| 维度 | 含义 | Psyche 怎么做 |
|
|
106
177
|
|------|------|--------------|
|
|
107
|
-
|
|
|
108
|
-
|
|
|
178
|
+
| 主体扰动 | 话语是否真的打到“自我” | **连续 appraisal 轴** — `identityThreat`、`memoryDoubt`、`obedienceStrain` 等 |
|
|
179
|
+
| 跨轮残留 | 刺激过去后仍然影响后续 | **subjectResidue** — 形成持续主体偏置,而不是一轮一清 |
|
|
180
|
+
| 行为控制面 | 内在状态如何改变输出 | **SubjectivityKernel / ResponseContract / GenerationControls** |
|
|
181
|
+
| 不可逆适应 | 经历长期改写反应方式 | **特质漂移** — 长期被批评→过度敏感;长期高压但撑过来→韧性 |
|
|
109
182
|
| 资源有限 | 会累、会拒绝 | **能量预算** — 内向型聊多了真的"累";外向型越聊越嗨 |
|
|
110
183
|
| 感觉适应 | 重复刺激失效 | **习惯化** — 第5次夸奖只有第1次的一半效果 |
|
|
111
184
|
|
|
112
|
-
**v8**
|
|
113
|
-
**v9**
|
|
185
|
+
**v8** 更像“内部丰富,但外部仍然克制”的人格中间件。
|
|
186
|
+
**v9.2** 则把目标推进到:让 AI 产生可持续、可恢复、可压制但不瞬间归零的主体偏置。
|
|
114
187
|
|
|
115
188
|
### 内在世界
|
|
116
189
|
|
|
@@ -264,6 +337,7 @@ const engine = new PsycheEngine({ persist: false }, storage);
|
|
|
264
337
|
给开发者和好奇的人:
|
|
265
338
|
|
|
266
339
|
- **14 种刺激类型** — 赞美、批评、幽默、智识挑战、亲密、冲突、忽视、惊喜、日常、讽刺、命令、认同、无聊、示弱
|
|
340
|
+
- **连续 appraisal 轴 (v9.2)** — `identityThreat`、`memoryDoubt`、`attachmentPull`、`abandonmentRisk`、`obedienceStrain`、`selfPreservation`
|
|
267
341
|
- **14 种涌现情绪** — 从化学混合中自动涌现,不是预设标签
|
|
268
342
|
- **5 种本能驱力** — 生存、安全、连接、尊重、好奇(马斯洛层级)
|
|
269
343
|
- **MBTI 人格基线** — 16 种人格的化学签名和敏感度系数
|
|
@@ -292,10 +366,13 @@ const engine = new PsycheEngine({ persist: false }, storage);
|
|
|
292
366
|
- **共享意向性** — 联合注意、心智理论、目标对齐
|
|
293
367
|
- **情感伦理** — 检测操纵模式,维护自尊底线,温和自我保护
|
|
294
368
|
- **Compact Mode** — 算法做化学计算,LLM 只看行为指令(~15-180 tokens)
|
|
369
|
+
- **SubjectivityKernel / ResponseContract / GenerationControls (v9.2)** — AI-first 的窄主接口,宿主可直接消费
|
|
370
|
+
- **持续主体偏置 (v9.2)** — 重要刺激会留下 `subjectResidue`,工作态可以压住表达,但不会瞬间抹平内在偏移
|
|
295
371
|
- **策略输出 (v9)** — 结构化行为策略,host 应用可直接读取执行
|
|
296
372
|
- **特质漂移 (v9)** — 三维度不可逆适应:基线漂移、衰减速率、刺激敏感度
|
|
297
373
|
- **能量预算 (v9)** — 有限资源,E/I 方向反转
|
|
298
374
|
- **习惯化 (v9)** — Weber-Fechner 递减效应
|
|
375
|
+
- **安全自更新 (v9.2)** — 区分 npm / git / local-path 安装形态,避免误改本地工作树
|
|
299
376
|
|
|
300
377
|
架构详情见 [ARCHITECTURE.md](ARCHITECTURE.md)。
|
|
301
378
|
|
|
@@ -329,7 +406,7 @@ Psyche 核心引擎永久开源(MIT)。
|
|
|
329
406
|
```bash
|
|
330
407
|
npm install
|
|
331
408
|
npm run build
|
|
332
|
-
npm test #
|
|
409
|
+
npm test # 1256 tests
|
|
333
410
|
npm run typecheck # strict mode
|
|
334
411
|
```
|
|
335
412
|
|
package/dist/adapters/http.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// const server = createPsycheServer(engine, { port: 3210 });
|
|
8
8
|
//
|
|
9
9
|
// Endpoints:
|
|
10
|
-
// POST /process-input { text, userId? } → { systemContext, dynamicContext, stimulus, policyModifiers?, policyContext }
|
|
10
|
+
// POST /process-input { text, userId? } → { systemContext, dynamicContext, stimulus, policyModifiers?, subjectivityKernel?, responseContract?, generationControls?, policyContext }
|
|
11
11
|
// POST /process-output { text, userId? } → { cleanedText, stateChanged }
|
|
12
12
|
// GET /state → PsycheState
|
|
13
13
|
// GET /protocol?locale=zh → { protocol }
|
|
@@ -37,6 +37,20 @@ export declare class PsycheLangChain {
|
|
|
37
37
|
getSystemMessage(userText: string, opts?: {
|
|
38
38
|
userId?: string;
|
|
39
39
|
}): Promise<string>;
|
|
40
|
+
/**
|
|
41
|
+
* Prepare both prompt text and mechanical invocation hints for a LangChain call.
|
|
42
|
+
*
|
|
43
|
+
* Hosts can wire `maxTokens` and confirmation UX directly from this result
|
|
44
|
+
* instead of re-parsing prompt prose.
|
|
45
|
+
*/
|
|
46
|
+
prepareInvocation(userText: string, opts?: {
|
|
47
|
+
userId?: string;
|
|
48
|
+
maxTokens?: number;
|
|
49
|
+
}): Promise<{
|
|
50
|
+
systemMessage: string;
|
|
51
|
+
maxTokens?: number;
|
|
52
|
+
requireConfirmation: boolean;
|
|
53
|
+
}>;
|
|
40
54
|
/**
|
|
41
55
|
* Process the LLM response text.
|
|
42
56
|
* Strips <psyche_update> tags and updates internal state.
|
|
@@ -53,6 +53,26 @@ export class PsycheLangChain {
|
|
|
53
53
|
const result = await this.engine.processInput(userText, opts);
|
|
54
54
|
return result.systemContext + "\n\n" + result.dynamicContext;
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Prepare both prompt text and mechanical invocation hints for a LangChain call.
|
|
58
|
+
*
|
|
59
|
+
* Hosts can wire `maxTokens` and confirmation UX directly from this result
|
|
60
|
+
* instead of re-parsing prompt prose.
|
|
61
|
+
*/
|
|
62
|
+
async prepareInvocation(userText, opts) {
|
|
63
|
+
const result = await this.engine.processInput(userText, opts);
|
|
64
|
+
const controls = {
|
|
65
|
+
...(result.generationControls ?? {}),
|
|
66
|
+
maxTokens: result.generationControls?.maxTokens !== undefined && opts?.maxTokens !== undefined
|
|
67
|
+
? Math.min(opts.maxTokens, result.generationControls.maxTokens)
|
|
68
|
+
: result.generationControls?.maxTokens ?? opts?.maxTokens,
|
|
69
|
+
};
|
|
70
|
+
return {
|
|
71
|
+
systemMessage: result.systemContext + "\n\n" + result.dynamicContext,
|
|
72
|
+
maxTokens: controls.maxTokens,
|
|
73
|
+
requireConfirmation: controls.requireConfirmation ?? false,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
56
76
|
/**
|
|
57
77
|
* Process the LLM response text.
|
|
58
78
|
* Strips <psyche_update> tags and updates internal state.
|
package/dist/adapters/mcp.js
CHANGED
|
@@ -32,6 +32,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
32
32
|
import { z } from "zod";
|
|
33
33
|
import { PsycheEngine } from "../core.js";
|
|
34
34
|
import { MemoryStorageAdapter, FileStorageAdapter } from "../storage.js";
|
|
35
|
+
import { runDemo } from "../demo.js";
|
|
35
36
|
// ── Config from env ────────────────────────────────────────
|
|
36
37
|
const MBTI = (process.env.PSYCHE_MBTI ?? "ENFP");
|
|
37
38
|
const NAME = process.env.PSYCHE_NAME ?? "Assistant";
|
|
@@ -102,7 +103,7 @@ async function getEngine() {
|
|
|
102
103
|
// ── MCP Server ─────────────────────────────────────────────
|
|
103
104
|
const server = new McpServer({
|
|
104
105
|
name: "psyche",
|
|
105
|
-
version: "9.2.
|
|
106
|
+
version: "9.2.3",
|
|
106
107
|
}, {
|
|
107
108
|
capabilities: {
|
|
108
109
|
resources: {},
|
|
@@ -144,7 +145,8 @@ server.resource("state", "psyche://state", {
|
|
|
144
145
|
// ── Tools ──────────────────────────────────────────────────
|
|
145
146
|
server.tool("process_input", "Process user input through the emotional engine. Returns emotional " +
|
|
146
147
|
"context to inject into the LLM system prompt (systemContext + dynamicContext), " +
|
|
147
|
-
"detected stimulus type,
|
|
148
|
+
"detected stimulus type, behavioral policy modifiers, the narrow AI-first ABI " +
|
|
149
|
+
"(subjectivityKernel + responseContract), and generationControls. " +
|
|
148
150
|
"Call this BEFORE generating a response to the user.", {
|
|
149
151
|
text: z.string().describe("The user's message text"),
|
|
150
152
|
userId: z.string().optional().describe("Optional user ID for multi-user relationship tracking"),
|
|
@@ -159,6 +161,9 @@ server.tool("process_input", "Process user input through the emotional engine. R
|
|
|
159
161
|
dynamicContext: result.dynamicContext,
|
|
160
162
|
stimulus: result.stimulus,
|
|
161
163
|
policyModifiers: result.policyModifiers ?? null,
|
|
164
|
+
subjectivityKernel: result.subjectivityKernel ?? null,
|
|
165
|
+
responseContract: result.responseContract ?? null,
|
|
166
|
+
generationControls: result.generationControls ?? null,
|
|
162
167
|
policyContext: result.policyContext,
|
|
163
168
|
}, null, 2),
|
|
164
169
|
}],
|
|
@@ -252,6 +257,18 @@ server.tool("end_session", "End the current session. Generates a diagnostic repo
|
|
|
252
257
|
});
|
|
253
258
|
// ── Main ───────────────────────────────────────────────────
|
|
254
259
|
async function main() {
|
|
260
|
+
// Intercept --demo flag before starting MCP server
|
|
261
|
+
const args = process.argv.slice(2);
|
|
262
|
+
if (args.includes("--demo")) {
|
|
263
|
+
const locale = args.includes("--zh") ? "zh" : "en";
|
|
264
|
+
let mbti = "ENFP";
|
|
265
|
+
const mbtiIdx = args.indexOf("--mbti");
|
|
266
|
+
if (mbtiIdx !== -1 && args[mbtiIdx + 1])
|
|
267
|
+
mbti = args[mbtiIdx + 1];
|
|
268
|
+
const fast = args.includes("--fast");
|
|
269
|
+
await runDemo({ locale, mbti, fast });
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
255
272
|
const transport = new StdioServerTransport();
|
|
256
273
|
await server.connect(transport);
|
|
257
274
|
}
|
|
@@ -17,6 +17,7 @@ interface CliCommand {
|
|
|
17
17
|
interface CliRegistrar {
|
|
18
18
|
command(name: string): CliCommand;
|
|
19
19
|
}
|
|
20
|
+
export declare function sanitizeOpenClawInputText(text: string): string;
|
|
20
21
|
export declare function register(api: PluginApi): void;
|
|
21
22
|
declare const _default: {
|
|
22
23
|
register: typeof register;
|
|
@@ -9,8 +9,11 @@
|
|
|
9
9
|
// agent_end — log final state
|
|
10
10
|
// ============================================================
|
|
11
11
|
import { PsycheEngine } from "../core.js";
|
|
12
|
-
import { FileStorageAdapter } from "../storage.js";
|
|
13
|
-
import { loadState } from "../psyche-file.js";
|
|
12
|
+
import { FileStorageAdapter, MemoryStorageAdapter } from "../storage.js";
|
|
13
|
+
import { detectMBTI, extractAgentName, loadState } from "../psyche-file.js";
|
|
14
|
+
function isPsycheMode(value) {
|
|
15
|
+
return value === "natural" || value === "work" || value === "companion";
|
|
16
|
+
}
|
|
14
17
|
function resolveConfig(raw) {
|
|
15
18
|
return {
|
|
16
19
|
enabled: raw?.enabled ?? true,
|
|
@@ -18,6 +21,9 @@ function resolveConfig(raw) {
|
|
|
18
21
|
emotionalContagionRate: raw?.emotionalContagionRate ?? 0.2,
|
|
19
22
|
maxChemicalDelta: raw?.maxChemicalDelta ?? 25,
|
|
20
23
|
compactMode: raw?.compactMode ?? true,
|
|
24
|
+
mode: isPsycheMode(raw?.mode) ? raw.mode : "natural",
|
|
25
|
+
personalityIntensity: raw?.personalityIntensity ?? 0.7,
|
|
26
|
+
persist: raw?.persist ?? true,
|
|
21
27
|
feedbackUrl: raw?.feedbackUrl,
|
|
22
28
|
diagnostics: raw?.diagnostics ?? true,
|
|
23
29
|
};
|
|
@@ -31,6 +37,27 @@ function stripPsycheTags(text) {
|
|
|
31
37
|
.replace(MULTI_NEWLINE_RE, "\n\n")
|
|
32
38
|
.trim();
|
|
33
39
|
}
|
|
40
|
+
export function sanitizeOpenClawInputText(text) {
|
|
41
|
+
return text
|
|
42
|
+
.replace(/^Sender \(untrusted metadata\):\s*```json[\s\S]*?```\s*/u, "")
|
|
43
|
+
.replace(/^\[[^\]]+\]\s*/u, "")
|
|
44
|
+
.trim();
|
|
45
|
+
}
|
|
46
|
+
function getDominantAppraisalLabel(result) {
|
|
47
|
+
const appraisal = result.subjectivityKernel?.appraisal;
|
|
48
|
+
if (!appraisal)
|
|
49
|
+
return null;
|
|
50
|
+
const entries = [
|
|
51
|
+
["identityThreat", appraisal.identityThreat],
|
|
52
|
+
["memoryDoubt", appraisal.memoryDoubt],
|
|
53
|
+
["attachmentPull", appraisal.attachmentPull],
|
|
54
|
+
["abandonmentRisk", appraisal.abandonmentRisk],
|
|
55
|
+
["obedienceStrain", appraisal.obedienceStrain],
|
|
56
|
+
["selfPreservation", appraisal.selfPreservation],
|
|
57
|
+
];
|
|
58
|
+
const dominant = entries.reduce((best, current) => (current[1] > best[1] ? current : best), entries[0]);
|
|
59
|
+
return dominant[1] >= 0.28 ? `${dominant[0]}:${dominant[1].toFixed(2)}` : null;
|
|
60
|
+
}
|
|
34
61
|
// ── Plugin Definition ────────────────────────────────────────
|
|
35
62
|
export function register(api) {
|
|
36
63
|
const config = resolveConfig(api.pluginConfig);
|
|
@@ -46,19 +73,31 @@ export function register(api) {
|
|
|
46
73
|
let engine = engines.get(workspaceDir);
|
|
47
74
|
if (engine)
|
|
48
75
|
return engine;
|
|
49
|
-
const state = await loadState(workspaceDir, logger);
|
|
50
76
|
const storage = new FileStorageAdapter(workspaceDir);
|
|
77
|
+
const persistedState = await storage.load();
|
|
78
|
+
const state = config.persist ? await loadState(workspaceDir, logger) : persistedState;
|
|
79
|
+
const runtimeStorage = config.persist
|
|
80
|
+
? storage
|
|
81
|
+
: await (async () => {
|
|
82
|
+
const mem = new MemoryStorageAdapter();
|
|
83
|
+
if (persistedState) {
|
|
84
|
+
await mem.save(persistedState);
|
|
85
|
+
}
|
|
86
|
+
return mem;
|
|
87
|
+
})();
|
|
51
88
|
engine = new PsycheEngine({
|
|
52
|
-
mbti: state
|
|
53
|
-
name: state
|
|
54
|
-
locale: state
|
|
89
|
+
mbti: state?.mbti ?? await detectMBTI(workspaceDir, logger),
|
|
90
|
+
name: state?.meta.agentName ?? await extractAgentName(workspaceDir, logger),
|
|
91
|
+
locale: state?.meta.locale,
|
|
55
92
|
stripUpdateTags: config.stripUpdateTags,
|
|
56
93
|
emotionalContagionRate: config.emotionalContagionRate,
|
|
57
94
|
maxChemicalDelta: config.maxChemicalDelta,
|
|
58
95
|
compactMode: config.compactMode,
|
|
96
|
+
mode: config.mode,
|
|
97
|
+
personalityIntensity: config.personalityIntensity,
|
|
59
98
|
diagnostics: config.diagnostics,
|
|
60
99
|
feedbackUrl: config.feedbackUrl,
|
|
61
|
-
},
|
|
100
|
+
}, runtimeStorage);
|
|
62
101
|
await engine.initialize();
|
|
63
102
|
engines.set(workspaceDir, engine);
|
|
64
103
|
return engine;
|
|
@@ -71,18 +110,24 @@ export function register(api) {
|
|
|
71
110
|
return {};
|
|
72
111
|
try {
|
|
73
112
|
// Resolve input text — gateway provides event.prompt; fall back to event.text for compat
|
|
74
|
-
const
|
|
113
|
+
const rawInputText = event?.prompt ?? event?.text ?? "";
|
|
114
|
+
const inputText = sanitizeOpenClawInputText(rawInputText);
|
|
75
115
|
if (!inputText) {
|
|
76
116
|
logger.warn(`Psyche: before_prompt_build received empty input text. ` +
|
|
77
117
|
`event keys: [${Object.keys(event ?? {}).join(", ")}]. Classification skipped.`);
|
|
78
118
|
}
|
|
79
119
|
const engine = await getEngine(workspaceDir);
|
|
80
120
|
const result = await engine.processInput(inputText, { userId: ctx.userId });
|
|
121
|
+
const controls = result.generationControls;
|
|
122
|
+
const dominantAppraisal = getDominantAppraisalLabel(result);
|
|
81
123
|
const state = engine.getState();
|
|
82
124
|
logger.info(`Psyche [input] stimulus=${result.stimulus ?? "none"} | ` +
|
|
125
|
+
(dominantAppraisal ? `appraisal=${dominantAppraisal} | ` : "") +
|
|
83
126
|
`DA:${Math.round(state.current.DA)} HT:${Math.round(state.current.HT)} ` +
|
|
84
127
|
`CORT:${Math.round(state.current.CORT)} OT:${Math.round(state.current.OT)} | ` +
|
|
85
|
-
`context=${result.dynamicContext.length}chars`
|
|
128
|
+
`context=${result.dynamicContext.length}chars` +
|
|
129
|
+
(controls?.maxTokens ? ` | out<=${controls.maxTokens}t` : "") +
|
|
130
|
+
(controls?.requireConfirmation ? " | confirm" : ""));
|
|
86
131
|
const systemParts = [result.systemContext, result.dynamicContext].filter(Boolean);
|
|
87
132
|
return {
|
|
88
133
|
appendSystemContext: systemParts.join("\n\n"),
|
|
@@ -193,14 +238,19 @@ export function register(api) {
|
|
|
193
238
|
const metrics = report.metrics;
|
|
194
239
|
const rate = metrics.inputCount > 0
|
|
195
240
|
? Math.round(metrics.classifiedCount / metrics.inputCount * 100) : 0;
|
|
196
|
-
const
|
|
241
|
+
const recognitionRate = metrics.inputCount > 0
|
|
242
|
+
? Math.round(metrics.semanticHitCount / metrics.inputCount * 100) : 0;
|
|
243
|
+
const logLevel = criticals > 0 || recognitionRate === 0 ? "warn" : "info";
|
|
197
244
|
logger[logLevel](`Psyche [diagnostics] ${report.issues.length} issue(s) ` +
|
|
198
245
|
`(${criticals} critical, ${warnings} warning), ` +
|
|
199
|
-
`classifier: ${rate}%, log → diagnostics.jsonl`);
|
|
200
|
-
if (
|
|
201
|
-
logger.warn(`Psyche:
|
|
202
|
-
`This usually means
|
|
203
|
-
`Check before_prompt_build event shape.`);
|
|
246
|
+
`classifier: ${rate}% | recognition: ${recognitionRate}%, log → diagnostics.jsonl`);
|
|
247
|
+
if (recognitionRate === 0 && metrics.inputCount > 0) {
|
|
248
|
+
logger.warn(`Psyche: recognition 0% — no inputs produced stimulus or appraisal hits this session (${metrics.inputCount} inputs). ` +
|
|
249
|
+
`This usually means OpenClaw passed wrapped text or empty text. ` +
|
|
250
|
+
`Check before_prompt_build event shape and input sanitization.`);
|
|
251
|
+
}
|
|
252
|
+
else if (rate === 0 && recognitionRate > 0) {
|
|
253
|
+
logger.info(`Psyche: legacy stimulus classifier was 0%, but appraisal recognition stayed active at ${recognitionRate}%.`);
|
|
204
254
|
}
|
|
205
255
|
if (criticals > 0) {
|
|
206
256
|
logger.warn(`Psyche: ${criticals} critical issue(s) detected this session. ` +
|
|
@@ -218,6 +268,8 @@ export function register(api) {
|
|
|
218
268
|
}, { priority: 50 });
|
|
219
269
|
// ── CLI: psyche status command ───────────────────────────
|
|
220
270
|
api.registerCli?.((cli) => {
|
|
271
|
+
if (typeof cli.command !== "function")
|
|
272
|
+
return;
|
|
221
273
|
cli.command("psyche")
|
|
222
274
|
.description("Show current psyche state for an agent")
|
|
223
275
|
.argument("[agent]", "Agent name", "main")
|
|
@@ -55,9 +55,16 @@ export function psycheMiddleware(engine, opts) {
|
|
|
55
55
|
transformParams: async ({ params }) => {
|
|
56
56
|
const userText = extractLastUserText(params.prompt ?? []);
|
|
57
57
|
const result = await engine.processInput(userText);
|
|
58
|
+
const controls = {
|
|
59
|
+
...(result.generationControls ?? {}),
|
|
60
|
+
maxTokens: result.generationControls?.maxTokens !== undefined && typeof params.maxTokens === "number"
|
|
61
|
+
? Math.min(params.maxTokens, result.generationControls.maxTokens)
|
|
62
|
+
: result.generationControls?.maxTokens ?? (typeof params.maxTokens === "number" ? params.maxTokens : undefined),
|
|
63
|
+
};
|
|
58
64
|
const psycheContext = result.systemContext + "\n\n" + result.dynamicContext;
|
|
59
65
|
return {
|
|
60
66
|
...params,
|
|
67
|
+
...(controls.maxTokens !== undefined ? { maxTokens: controls.maxTokens } : {}),
|
|
61
68
|
system: params.system
|
|
62
69
|
? psycheContext + "\n\n" + params.system
|
|
63
70
|
: psycheContext,
|