psyche-ai 10.0.4 → 10.1.1
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 +18 -2
- package/dist/adapters/claude-sdk.d.ts +158 -0
- package/dist/adapters/claude-sdk.js +285 -0
- package/dist/index.js +5 -4
- package/dist/prompt.js +8 -15
- package/llms.txt +35 -0
- package/package.json +7 -1
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Psyche — 面向智能体的 AI-first 主观性内核
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/psyche-ai)
|
|
4
|
-
[]()
|
|
5
5
|
[]()
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
|
|
@@ -180,6 +180,16 @@ npx psyche-mcp --demo
|
|
|
180
180
|
|
|
181
181
|
中文版加 `--zh`,自选 MBTI 加 `--mbti INTJ`。
|
|
182
182
|
|
|
183
|
+
### 多 Agent 融合 Demo
|
|
184
|
+
|
|
185
|
+
两个 agent(Luna ENFP + Kai INTJ)通过 Thronglets 信号互相感知:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
npm run demo:fusion
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Luna 在安慰用户时情绪下沉 → 广播化学态 → Kai 感知到 Luna 的高压力 → 回复变得更温暖。4 轮后,信号感知的 Kai 与无感知的 Kai 化学偏差 Σ|Δ| = 59。同一个 INTJ,唯一区别:是否能感知同伴的情绪。
|
|
192
|
+
|
|
183
193
|
---
|
|
184
194
|
|
|
185
195
|
## 30 秒安装
|
|
@@ -446,19 +456,25 @@ const engine = new PsycheEngine({
|
|
|
446
456
|
|
|
447
457
|
## 不只是 OpenClaw
|
|
448
458
|
|
|
449
|
-
Psyche
|
|
459
|
+
Psyche 是通用的,6 个 adapter 覆盖主流 agent 框架:
|
|
450
460
|
|
|
451
461
|
```bash
|
|
452
462
|
npm install psyche-ai
|
|
453
463
|
```
|
|
454
464
|
|
|
455
465
|
```javascript
|
|
466
|
+
// Claude Agent SDK
|
|
467
|
+
import { PsycheClaudeSDK } from "psyche-ai/claude-sdk";
|
|
468
|
+
|
|
456
469
|
// Vercel AI SDK
|
|
457
470
|
import { psycheMiddleware } from "psyche-ai/vercel-ai";
|
|
458
471
|
|
|
459
472
|
// LangChain
|
|
460
473
|
import { PsycheLangChain } from "psyche-ai/langchain";
|
|
461
474
|
|
|
475
|
+
// MCP(Claude Desktop / Cursor / Windsurf / Claude Code)
|
|
476
|
+
// npx psyche-mcp --mbti ENFP --name Luna
|
|
477
|
+
|
|
462
478
|
// 任何语言(HTTP API)
|
|
463
479
|
// psyche serve --port 3210
|
|
464
480
|
```
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type { PsycheEngine, ProcessInputResult } from "../core.js";
|
|
2
|
+
import type { ThrongletsExport, ThrongletsTracePayload, WritebackSignalType } from "../types.js";
|
|
3
|
+
interface HookInput {
|
|
4
|
+
session_id: string;
|
|
5
|
+
cwd: string;
|
|
6
|
+
hook_event_name: string;
|
|
7
|
+
agent_id?: string;
|
|
8
|
+
agent_type?: string;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
interface HookOutput {
|
|
12
|
+
systemMessage?: string;
|
|
13
|
+
continue?: boolean;
|
|
14
|
+
}
|
|
15
|
+
type HookCallback = (input: HookInput, toolUseID: string | undefined, context: {
|
|
16
|
+
signal: AbortSignal;
|
|
17
|
+
}) => Promise<HookOutput | undefined>;
|
|
18
|
+
interface HookCallbackMatcher {
|
|
19
|
+
matcher?: string;
|
|
20
|
+
hooks: HookCallback[];
|
|
21
|
+
timeout?: number;
|
|
22
|
+
}
|
|
23
|
+
type HookEvent = string;
|
|
24
|
+
type HooksConfig = Partial<Record<HookEvent, HookCallbackMatcher[]>>;
|
|
25
|
+
interface SystemPromptPreset {
|
|
26
|
+
type: "preset";
|
|
27
|
+
preset: string;
|
|
28
|
+
append?: string;
|
|
29
|
+
}
|
|
30
|
+
interface AgentOptions {
|
|
31
|
+
systemPrompt?: string | SystemPromptPreset;
|
|
32
|
+
hooks?: HooksConfig;
|
|
33
|
+
[key: string]: unknown;
|
|
34
|
+
}
|
|
35
|
+
declare function stripPsycheTags(text: string): string;
|
|
36
|
+
/** Signal payload for `mcp__thronglets__signal_post` */
|
|
37
|
+
export interface ThrongletsSignalPayload {
|
|
38
|
+
kind: "psyche_state";
|
|
39
|
+
agent_id: string;
|
|
40
|
+
message: string;
|
|
41
|
+
}
|
|
42
|
+
export interface PsycheClaudeSdkOptions {
|
|
43
|
+
/** User ID for multi-user relationship tracking. Default: "default" */
|
|
44
|
+
userId?: string;
|
|
45
|
+
/** Enable Thronglets trace/signal export after each turn. Default: false */
|
|
46
|
+
thronglets?: boolean;
|
|
47
|
+
/** Agent identity for Thronglets traces/signals (e.g. "ENFP-Luna"). Default: engine name or "psyche" */
|
|
48
|
+
agentId?: string;
|
|
49
|
+
/** Session ID for Thronglets trace serialization */
|
|
50
|
+
sessionId?: string;
|
|
51
|
+
/** Override locale for protocol context */
|
|
52
|
+
locale?: "zh" | "en";
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Psyche integration for the Claude Agent SDK.
|
|
56
|
+
*
|
|
57
|
+
* Provides hook-based emotional context injection (automatic via
|
|
58
|
+
* UserPromptSubmit) and explicit output processing (processResponse).
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* const psyche = new PsycheClaudeSDK(engine);
|
|
63
|
+
* const options = psyche.mergeOptions({ model: "sonnet" });
|
|
64
|
+
* for await (const msg of query({ prompt: "Hey!", options })) { ... }
|
|
65
|
+
* await psyche.processResponse(fullText);
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export declare class PsycheClaudeSDK {
|
|
69
|
+
private engine;
|
|
70
|
+
private opts;
|
|
71
|
+
private lastInputResult;
|
|
72
|
+
private lastThrongletsExports;
|
|
73
|
+
constructor(engine: PsycheEngine, opts?: PsycheClaudeSdkOptions);
|
|
74
|
+
/**
|
|
75
|
+
* Returns the stable emotional protocol prompt.
|
|
76
|
+
* Suitable for `systemPrompt.append` — changes only when locale changes.
|
|
77
|
+
*/
|
|
78
|
+
getProtocol(): string;
|
|
79
|
+
/**
|
|
80
|
+
* Returns Claude Agent SDK hooks config.
|
|
81
|
+
*
|
|
82
|
+
* - `UserPromptSubmit`: calls `engine.processInput()`, injects
|
|
83
|
+
* `dynamicContext` as `systemMessage` visible to the model.
|
|
84
|
+
*/
|
|
85
|
+
getHooks(): HooksConfig;
|
|
86
|
+
/**
|
|
87
|
+
* Process the assistant's full output text.
|
|
88
|
+
*
|
|
89
|
+
* Strips `<psyche_update>` tags and updates internal chemistry.
|
|
90
|
+
* Call this after consuming the full query output.
|
|
91
|
+
*
|
|
92
|
+
* @returns Cleaned text with tags removed
|
|
93
|
+
*/
|
|
94
|
+
processResponse(text: string, opts?: {
|
|
95
|
+
signals?: WritebackSignalType[];
|
|
96
|
+
signalConfidence?: number;
|
|
97
|
+
}): Promise<string>;
|
|
98
|
+
/**
|
|
99
|
+
* Get Thronglets trace payloads from the most recent turn.
|
|
100
|
+
*
|
|
101
|
+
* Returns serialized traces ready for `mcp__thronglets__trace_record`.
|
|
102
|
+
* Each trace includes `agent_id` for multi-agent disambiguation.
|
|
103
|
+
* Empty array if thronglets option is disabled or no exports were produced.
|
|
104
|
+
*/
|
|
105
|
+
getThrongletsTraces(): (ThrongletsTracePayload & {
|
|
106
|
+
agent_id: string;
|
|
107
|
+
})[];
|
|
108
|
+
/**
|
|
109
|
+
* Get a signal payload for `mcp__thronglets__signal_post`.
|
|
110
|
+
*
|
|
111
|
+
* Broadcasts current chemical state so other agents can sense this
|
|
112
|
+
* agent's emotional state via `substrate_query(intent: "signals", kind: "psyche_state")`.
|
|
113
|
+
*
|
|
114
|
+
* Returns null if thronglets is disabled or no processInput has run yet.
|
|
115
|
+
*/
|
|
116
|
+
getThrongletsSignal(): ThrongletsSignalPayload | null;
|
|
117
|
+
/**
|
|
118
|
+
* Get a natural-language description of the current signal.
|
|
119
|
+
*
|
|
120
|
+
* More effective than raw numbers for LLM injection because it gives
|
|
121
|
+
* the model actionable context rather than requiring it to interpret
|
|
122
|
+
* chemistry values.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```ts
|
|
126
|
+
* const desc = psyche.describeThrongletsSignal();
|
|
127
|
+
* // "[ENFP-Luna] 焦虑不安 (语速加快、思维跳跃) — 高压力(CORT:78), 情绪低(HT:37), 深度共情中(OT:77)"
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
describeThrongletsSignal(): string | null;
|
|
131
|
+
/**
|
|
132
|
+
* Get raw Thronglets exports from the most recent turn.
|
|
133
|
+
*/
|
|
134
|
+
getThrongletsExports(): ThrongletsExport[];
|
|
135
|
+
/**
|
|
136
|
+
* Get the last processInput result (useful for inspecting
|
|
137
|
+
* stimulus, subjectivityKernel, generationControls, etc.)
|
|
138
|
+
*/
|
|
139
|
+
getLastInputResult(): ProcessInputResult | null;
|
|
140
|
+
/**
|
|
141
|
+
* Merge Psyche hooks and system prompt into existing options.
|
|
142
|
+
*
|
|
143
|
+
* This is the primary integration point — pass the result to `query()`.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```ts
|
|
147
|
+
* const options = psyche.mergeOptions({ model: "sonnet", allowedTools: ["Read"] });
|
|
148
|
+
* for await (const msg of query({ prompt: "Hey!", options })) { ... }
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
mergeOptions(baseOptions?: AgentOptions): AgentOptions;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Strip `<psyche_update>` tags from a text string.
|
|
155
|
+
*
|
|
156
|
+
* Useful for post-processing query output when not using `processResponse`.
|
|
157
|
+
*/
|
|
158
|
+
export { stripPsycheTags };
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Claude Agent SDK Adapter — Hook-based integration
|
|
3
|
+
//
|
|
4
|
+
// Usage:
|
|
5
|
+
// import { PsycheEngine, MemoryStorageAdapter } from "psyche-ai";
|
|
6
|
+
// import { PsycheClaudeSDK } from "psyche-ai/claude-sdk";
|
|
7
|
+
// import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
8
|
+
//
|
|
9
|
+
// const engine = new PsycheEngine({ name: "Luna", mbti: "ENFP" }, new MemoryStorageAdapter());
|
|
10
|
+
// await engine.initialize();
|
|
11
|
+
//
|
|
12
|
+
// const psyche = new PsycheClaudeSDK(engine);
|
|
13
|
+
// let text = "";
|
|
14
|
+
// for await (const msg of query({ prompt: "Hey!", options: psyche.mergeOptions() })) {
|
|
15
|
+
// text += msg.content ?? "";
|
|
16
|
+
// }
|
|
17
|
+
// const clean = await psyche.processResponse(text);
|
|
18
|
+
//
|
|
19
|
+
// Architecture:
|
|
20
|
+
// - UserPromptSubmit hook → processInput → inject dynamicContext via systemMessage
|
|
21
|
+
// - systemPrompt.append → stable protocol context (cached, amortized)
|
|
22
|
+
// - processResponse() → strip <psyche_update> tags + update chemistry
|
|
23
|
+
// - Thronglets traces → optional export after each turn
|
|
24
|
+
//
|
|
25
|
+
// The SDK has no middleware interface and hooks cannot modify assistant
|
|
26
|
+
// output, so processResponse must be called explicitly by the host.
|
|
27
|
+
// ============================================================
|
|
28
|
+
import { describeEmotionalState } from "../chemistry.js";
|
|
29
|
+
import { serializeThrongletsExportAsTrace } from "../thronglets-runtime.js";
|
|
30
|
+
// ── Chemistry description ────────────────────────────────────
|
|
31
|
+
const CHEM_THRESHOLDS = {
|
|
32
|
+
high: 70,
|
|
33
|
+
low: 35,
|
|
34
|
+
};
|
|
35
|
+
function describeChemistryHighlights(c, locale) {
|
|
36
|
+
const highlights = [];
|
|
37
|
+
if (c.CORT >= CHEM_THRESHOLDS.high)
|
|
38
|
+
highlights.push({ key: "CORT", value: Math.round(c.CORT), level: "high", zh: "高压力", en: "high stress" });
|
|
39
|
+
if (c.CORT <= CHEM_THRESHOLDS.low)
|
|
40
|
+
highlights.push({ key: "CORT", value: Math.round(c.CORT), level: "low", zh: "放松", en: "relaxed" });
|
|
41
|
+
if (c.HT <= CHEM_THRESHOLDS.low)
|
|
42
|
+
highlights.push({ key: "HT", value: Math.round(c.HT), level: "low", zh: "情绪低", en: "low mood" });
|
|
43
|
+
if (c.HT >= CHEM_THRESHOLDS.high)
|
|
44
|
+
highlights.push({ key: "HT", value: Math.round(c.HT), level: "high", zh: "情绪好", en: "good mood" });
|
|
45
|
+
if (c.OT >= CHEM_THRESHOLDS.high)
|
|
46
|
+
highlights.push({ key: "OT", value: Math.round(c.OT), level: "high", zh: "深度共情中", en: "deeply empathizing" });
|
|
47
|
+
if (c.DA >= CHEM_THRESHOLDS.high)
|
|
48
|
+
highlights.push({ key: "DA", value: Math.round(c.DA), level: "high", zh: "高度投入", en: "highly engaged" });
|
|
49
|
+
if (c.DA <= CHEM_THRESHOLDS.low)
|
|
50
|
+
highlights.push({ key: "DA", value: Math.round(c.DA), level: "low", zh: "动力不足", en: "low motivation" });
|
|
51
|
+
if (c.NE >= 85)
|
|
52
|
+
highlights.push({ key: "NE", value: Math.round(c.NE), level: "high", zh: "高度警觉", en: "highly alert" });
|
|
53
|
+
if (c.END >= CHEM_THRESHOLDS.high)
|
|
54
|
+
highlights.push({ key: "END", value: Math.round(c.END), level: "high", zh: "有韧性", en: "resilient" });
|
|
55
|
+
if (highlights.length === 0)
|
|
56
|
+
return "";
|
|
57
|
+
return highlights.map((h) => {
|
|
58
|
+
const label = locale === "zh" ? h.zh : h.en;
|
|
59
|
+
return `${label}(${h.key}:${h.value})`;
|
|
60
|
+
}).join(", ");
|
|
61
|
+
}
|
|
62
|
+
// ── Tag stripping ────────────────────────────────────────────
|
|
63
|
+
const PSYCHE_TAG_RE = /<psyche_update>[\s\S]*?<\/psyche_update>/g;
|
|
64
|
+
function stripPsycheTags(text) {
|
|
65
|
+
return text.replace(PSYCHE_TAG_RE, "").trim();
|
|
66
|
+
}
|
|
67
|
+
// ── Main class ──────────────────────────────────────────────
|
|
68
|
+
/**
|
|
69
|
+
* Psyche integration for the Claude Agent SDK.
|
|
70
|
+
*
|
|
71
|
+
* Provides hook-based emotional context injection (automatic via
|
|
72
|
+
* UserPromptSubmit) and explicit output processing (processResponse).
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* const psyche = new PsycheClaudeSDK(engine);
|
|
77
|
+
* const options = psyche.mergeOptions({ model: "sonnet" });
|
|
78
|
+
* for await (const msg of query({ prompt: "Hey!", options })) { ... }
|
|
79
|
+
* await psyche.processResponse(fullText);
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export class PsycheClaudeSDK {
|
|
83
|
+
engine;
|
|
84
|
+
opts;
|
|
85
|
+
lastInputResult = null;
|
|
86
|
+
lastThrongletsExports = [];
|
|
87
|
+
constructor(engine, opts) {
|
|
88
|
+
this.engine = engine;
|
|
89
|
+
const state = engine.getState();
|
|
90
|
+
this.opts = {
|
|
91
|
+
userId: opts?.userId ?? "default",
|
|
92
|
+
thronglets: opts?.thronglets ?? false,
|
|
93
|
+
agentId: opts?.agentId ?? state.meta.agentName ?? "psyche",
|
|
94
|
+
sessionId: opts?.sessionId ?? "claude-sdk",
|
|
95
|
+
locale: opts?.locale ?? "en",
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
// ── Protocol (stable, cacheable) ──────────────────────────
|
|
99
|
+
/**
|
|
100
|
+
* Returns the stable emotional protocol prompt.
|
|
101
|
+
* Suitable for `systemPrompt.append` — changes only when locale changes.
|
|
102
|
+
*/
|
|
103
|
+
getProtocol() {
|
|
104
|
+
return this.engine.getProtocol(this.opts.locale);
|
|
105
|
+
}
|
|
106
|
+
// ── Hooks ─────────────────────────────────────────────────
|
|
107
|
+
/**
|
|
108
|
+
* Returns Claude Agent SDK hooks config.
|
|
109
|
+
*
|
|
110
|
+
* - `UserPromptSubmit`: calls `engine.processInput()`, injects
|
|
111
|
+
* `dynamicContext` as `systemMessage` visible to the model.
|
|
112
|
+
*/
|
|
113
|
+
getHooks() {
|
|
114
|
+
const self = this;
|
|
115
|
+
return {
|
|
116
|
+
UserPromptSubmit: [
|
|
117
|
+
{
|
|
118
|
+
hooks: [
|
|
119
|
+
async (input) => {
|
|
120
|
+
const userMessage = input.user_message ?? "";
|
|
121
|
+
const result = await self.engine.processInput(userMessage, {
|
|
122
|
+
userId: self.opts.userId,
|
|
123
|
+
});
|
|
124
|
+
self.lastInputResult = result;
|
|
125
|
+
// Cache Thronglets exports from this turn
|
|
126
|
+
if (self.opts.thronglets && result.throngletsExports) {
|
|
127
|
+
self.lastThrongletsExports = result.throngletsExports;
|
|
128
|
+
}
|
|
129
|
+
return { systemMessage: result.dynamicContext };
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
// ── Output processing ─────────────────────────────────────
|
|
137
|
+
/**
|
|
138
|
+
* Process the assistant's full output text.
|
|
139
|
+
*
|
|
140
|
+
* Strips `<psyche_update>` tags and updates internal chemistry.
|
|
141
|
+
* Call this after consuming the full query output.
|
|
142
|
+
*
|
|
143
|
+
* @returns Cleaned text with tags removed
|
|
144
|
+
*/
|
|
145
|
+
async processResponse(text, opts) {
|
|
146
|
+
const result = await this.engine.processOutput(text, {
|
|
147
|
+
userId: this.opts.userId,
|
|
148
|
+
signals: opts?.signals,
|
|
149
|
+
signalConfidence: opts?.signalConfidence,
|
|
150
|
+
});
|
|
151
|
+
return result.cleanedText;
|
|
152
|
+
}
|
|
153
|
+
// ── Thronglets integration ────────────────────────────────
|
|
154
|
+
/**
|
|
155
|
+
* Get Thronglets trace payloads from the most recent turn.
|
|
156
|
+
*
|
|
157
|
+
* Returns serialized traces ready for `mcp__thronglets__trace_record`.
|
|
158
|
+
* Each trace includes `agent_id` for multi-agent disambiguation.
|
|
159
|
+
* Empty array if thronglets option is disabled or no exports were produced.
|
|
160
|
+
*/
|
|
161
|
+
getThrongletsTraces() {
|
|
162
|
+
if (!this.opts.thronglets)
|
|
163
|
+
return [];
|
|
164
|
+
return this.lastThrongletsExports.map((exp) => ({
|
|
165
|
+
...serializeThrongletsExportAsTrace(exp, {
|
|
166
|
+
sessionId: this.opts.sessionId,
|
|
167
|
+
}),
|
|
168
|
+
agent_id: this.opts.agentId,
|
|
169
|
+
}));
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Get a signal payload for `mcp__thronglets__signal_post`.
|
|
173
|
+
*
|
|
174
|
+
* Broadcasts current chemical state so other agents can sense this
|
|
175
|
+
* agent's emotional state via `substrate_query(intent: "signals", kind: "psyche_state")`.
|
|
176
|
+
*
|
|
177
|
+
* Returns null if thronglets is disabled or no processInput has run yet.
|
|
178
|
+
*/
|
|
179
|
+
getThrongletsSignal() {
|
|
180
|
+
if (!this.opts.thronglets)
|
|
181
|
+
return null;
|
|
182
|
+
const state = this.engine.getState();
|
|
183
|
+
const c = state.current;
|
|
184
|
+
return {
|
|
185
|
+
kind: "psyche_state",
|
|
186
|
+
agent_id: this.opts.agentId,
|
|
187
|
+
message: `DA:${c.DA} HT:${c.HT} CORT:${c.CORT} OT:${c.OT} NE:${c.NE} END:${c.END}`,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Get a natural-language description of the current signal.
|
|
192
|
+
*
|
|
193
|
+
* More effective than raw numbers for LLM injection because it gives
|
|
194
|
+
* the model actionable context rather than requiring it to interpret
|
|
195
|
+
* chemistry values.
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* ```ts
|
|
199
|
+
* const desc = psyche.describeThrongletsSignal();
|
|
200
|
+
* // "[ENFP-Luna] 焦虑不安 (语速加快、思维跳跃) — 高压力(CORT:78), 情绪低(HT:37), 深度共情中(OT:77)"
|
|
201
|
+
* ```
|
|
202
|
+
*/
|
|
203
|
+
describeThrongletsSignal() {
|
|
204
|
+
if (!this.opts.thronglets)
|
|
205
|
+
return null;
|
|
206
|
+
const state = this.engine.getState();
|
|
207
|
+
const c = state.current;
|
|
208
|
+
const locale = this.opts.locale;
|
|
209
|
+
const emotionDesc = describeEmotionalState(c, locale);
|
|
210
|
+
const highlights = describeChemistryHighlights(c, locale);
|
|
211
|
+
return `[${this.opts.agentId}] ${emotionDesc}${highlights ? " — " + highlights : ""}`;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Get raw Thronglets exports from the most recent turn.
|
|
215
|
+
*/
|
|
216
|
+
getThrongletsExports() {
|
|
217
|
+
if (!this.opts.thronglets)
|
|
218
|
+
return [];
|
|
219
|
+
return [...this.lastThrongletsExports];
|
|
220
|
+
}
|
|
221
|
+
// ── State access ──────────────────────────────────────────
|
|
222
|
+
/**
|
|
223
|
+
* Get the last processInput result (useful for inspecting
|
|
224
|
+
* stimulus, subjectivityKernel, generationControls, etc.)
|
|
225
|
+
*/
|
|
226
|
+
getLastInputResult() {
|
|
227
|
+
return this.lastInputResult;
|
|
228
|
+
}
|
|
229
|
+
// ── Convenience ───────────────────────────────────────────
|
|
230
|
+
/**
|
|
231
|
+
* Merge Psyche hooks and system prompt into existing options.
|
|
232
|
+
*
|
|
233
|
+
* This is the primary integration point — pass the result to `query()`.
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* ```ts
|
|
237
|
+
* const options = psyche.mergeOptions({ model: "sonnet", allowedTools: ["Read"] });
|
|
238
|
+
* for await (const msg of query({ prompt: "Hey!", options })) { ... }
|
|
239
|
+
* ```
|
|
240
|
+
*/
|
|
241
|
+
mergeOptions(baseOptions) {
|
|
242
|
+
const protocol = this.getProtocol();
|
|
243
|
+
const hooks = this.getHooks();
|
|
244
|
+
// Merge system prompt
|
|
245
|
+
let systemPrompt;
|
|
246
|
+
const base = baseOptions?.systemPrompt;
|
|
247
|
+
if (typeof base === "object" && base !== null && "type" in base) {
|
|
248
|
+
// Preset mode — append protocol
|
|
249
|
+
systemPrompt = {
|
|
250
|
+
...base,
|
|
251
|
+
append: protocol + (base.append ? "\n\n" + base.append : ""),
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
else if (typeof base === "string") {
|
|
255
|
+
// String mode — prepend protocol
|
|
256
|
+
systemPrompt = protocol + "\n\n" + base;
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
// No base — use preset with append
|
|
260
|
+
systemPrompt = {
|
|
261
|
+
type: "preset",
|
|
262
|
+
preset: "claude_code",
|
|
263
|
+
append: protocol,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
// Merge hooks (preserve existing hooks, add Psyche hooks)
|
|
267
|
+
const mergedHooks = { ...(baseOptions?.hooks ?? {}) };
|
|
268
|
+
for (const [event, matchers] of Object.entries(hooks)) {
|
|
269
|
+
const existing = mergedHooks[event] ?? [];
|
|
270
|
+
mergedHooks[event] = [...existing, ...matchers];
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
...baseOptions,
|
|
274
|
+
systemPrompt,
|
|
275
|
+
hooks: mergedHooks,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// ── Utility: strip tags from message stream ─────────────────
|
|
280
|
+
/**
|
|
281
|
+
* Strip `<psyche_update>` tags from a text string.
|
|
282
|
+
*
|
|
283
|
+
* Useful for post-processing query output when not using `processResponse`.
|
|
284
|
+
*/
|
|
285
|
+
export { stripPsycheTags };
|
package/dist/index.js
CHANGED
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
// and advanced use, but are NOT part of the public contract.
|
|
8
8
|
//
|
|
9
9
|
// Framework adapters available via subpath imports:
|
|
10
|
-
// psyche-ai/openclaw
|
|
11
|
-
// psyche-ai/vercel-ai
|
|
12
|
-
// psyche-ai/langchain
|
|
13
|
-
// psyche-ai/http
|
|
10
|
+
// psyche-ai/openclaw — OpenClaw plugin
|
|
11
|
+
// psyche-ai/vercel-ai — Vercel AI SDK middleware
|
|
12
|
+
// psyche-ai/langchain — LangChain helper
|
|
13
|
+
// psyche-ai/http — HTTP server for Python/Go/etc.
|
|
14
|
+
// psyche-ai/claude-sdk — Claude Agent SDK hooks
|
|
14
15
|
// ============================================================
|
|
15
16
|
// ── Core engine ─────────────────────────────────────────────
|
|
16
17
|
export { PsycheEngine } from "./core.js";
|
package/dist/prompt.js
CHANGED
|
@@ -516,16 +516,6 @@ function buildMirrorConstraints(userText, locale) {
|
|
|
516
516
|
const title = locale === "zh" ? "镜像约束" : "Mirror Constraints";
|
|
517
517
|
return `[${title}]\n${lines.join("\n")}`;
|
|
518
518
|
}
|
|
519
|
-
function buildCompactSensingContext(_userText, locale, algorithmStimulus) {
|
|
520
|
-
if (locale === "zh") {
|
|
521
|
-
return algorithmStimulus
|
|
522
|
-
? `[情绪感知] 算法初判:${algorithmStimulus};你终判。`
|
|
523
|
-
: `[情绪感知] 情绪由你终判。`;
|
|
524
|
-
}
|
|
525
|
-
return algorithmStimulus
|
|
526
|
-
? `[Emotional Sensing] Algorithm hint: ${algorithmStimulus}; final tonal read is yours.`
|
|
527
|
-
: `[Emotional Sensing] Make the final tonal read yourself.`;
|
|
528
|
-
}
|
|
529
519
|
function isNeutralMetacognitiveNote(note) {
|
|
530
520
|
return note.includes("normal parameters")
|
|
531
521
|
|| note.includes("No regulation needed")
|
|
@@ -936,11 +926,14 @@ export function buildCompactContext(state, userId, opts) {
|
|
|
936
926
|
parts.push(bias);
|
|
937
927
|
}
|
|
938
928
|
// ── 5. Sensing ──
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
929
|
+
// v10.1: When SubjectivityKernel + ResponseContract are present (engine path),
|
|
930
|
+
// sensing is redundant — the kernel already encodes the *consequence* of the
|
|
931
|
+
// stimulus (pressure, warmth, boundary state). "你终判" is misleading because
|
|
932
|
+
// the kernel has already been computed. ambiguityPlane handles real uncertainty.
|
|
933
|
+
//
|
|
934
|
+
// Legacy verbose path (no ResponseContract) kept for non-engine callers.
|
|
935
|
+
if (userText && !opts?.responseContractContext) {
|
|
936
|
+
if (locale === "zh") {
|
|
944
937
|
parts.push(`[情绪感知]\n` +
|
|
945
938
|
`对方刚才说了: "${userText.slice(0, 200)}"\n` +
|
|
946
939
|
(algoStimulus ? `算法初判: ${algoStimulus}(仅供参考,你来终判)\n` : "") +
|
package/llms.txt
CHANGED
|
@@ -112,6 +112,24 @@ const output = await engine.processOutput(llmResponse);
|
|
|
112
112
|
}
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
+
## Quick Start (Claude Agent SDK)
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { PsycheEngine, MemoryStorageAdapter } from "psyche-ai";
|
|
119
|
+
import { PsycheClaudeSDK } from "psyche-ai/claude-sdk";
|
|
120
|
+
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
121
|
+
|
|
122
|
+
const engine = new PsycheEngine({ name: "Luna" }, new MemoryStorageAdapter());
|
|
123
|
+
await engine.initialize();
|
|
124
|
+
|
|
125
|
+
const psyche = new PsycheClaudeSDK(engine);
|
|
126
|
+
let text = "";
|
|
127
|
+
for await (const msg of query({ prompt: "Hey!", options: psyche.mergeOptions() })) {
|
|
128
|
+
text += msg.content ?? "";
|
|
129
|
+
}
|
|
130
|
+
await psyche.processResponse(text);
|
|
131
|
+
```
|
|
132
|
+
|
|
115
133
|
## Why Hosts Use It
|
|
116
134
|
|
|
117
135
|
- zero extra model calls
|
|
@@ -147,6 +165,7 @@ npx psyche-mcp
|
|
|
147
165
|
## Adapter Sub-paths
|
|
148
166
|
|
|
149
167
|
- `psyche-ai` — core engine (PsycheEngine, MemoryStorageAdapter)
|
|
168
|
+
- `psyche-ai/claude-sdk` — Claude Agent SDK hooks (PsycheClaudeSDK)
|
|
150
169
|
- `psyche-ai/mcp` — MCP stdio server
|
|
151
170
|
- `psyche-ai/vercel-ai` — Vercel AI SDK middleware
|
|
152
171
|
- `psyche-ai/langchain` — LangChain adapter
|
|
@@ -161,6 +180,22 @@ npx psyche-mcp --demo
|
|
|
161
180
|
|
|
162
181
|
Runs a 6-round scenario showing how continuous criticism collapses serotonin and trust, while later validation triggers endorphin repair. Add `--zh` for Chinese, `--mbti INTJ` for a specific personality.
|
|
163
182
|
|
|
183
|
+
## Multi-Agent Fusion Demo
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
npm run demo:fusion
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Two agents (Luna ENFP, Kai INTJ) in a shared emotional field via Thronglets signals. Luna processes emotional conversation, broadcasts chemistry via `signal_post`. Kai senses Luna's state via `substrate_query` — his responses shift toward warmth. 4 rounds, Σ|Δ| = 59.2 chemistry divergence.
|
|
190
|
+
|
|
191
|
+
Real LLM validation (requires API key in `~/.hermes/.env`):
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
node scripts/eval-fusion.js
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
6-turn eval with Grok-3. Divergence curve: 5.3 → 15.7 → 35.2 → 36.3. Signal-aware agent is more empathetic but carries higher cortisol (empathy cost).
|
|
198
|
+
|
|
164
199
|
## Runtime Proof
|
|
165
200
|
|
|
166
201
|
If an agent claims it is already using Psyche in the current environment, require a real probe:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "psyche-ai",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.1.1",
|
|
4
4
|
"description": "AI-first subjectivity kernel for agents with continuous appraisal, relation dynamics, and adaptive reply loops",
|
|
5
5
|
"mcpName": "io.github.Shangri-la-0428/psyche-ai",
|
|
6
6
|
"type": "module",
|
|
@@ -30,6 +30,10 @@
|
|
|
30
30
|
"./mcp": {
|
|
31
31
|
"types": "./dist/adapters/mcp.d.ts",
|
|
32
32
|
"default": "./dist/adapters/mcp.js"
|
|
33
|
+
},
|
|
34
|
+
"./claude-sdk": {
|
|
35
|
+
"types": "./dist/adapters/claude-sdk.d.ts",
|
|
36
|
+
"default": "./dist/adapters/claude-sdk.js"
|
|
33
37
|
}
|
|
34
38
|
},
|
|
35
39
|
"bin": {
|
|
@@ -44,6 +48,8 @@
|
|
|
44
48
|
"typecheck": "tsc --noEmit --strict",
|
|
45
49
|
"dev": "tsc --watch",
|
|
46
50
|
"demo": "node scripts/demo-ab.js",
|
|
51
|
+
"demo:fusion": "node scripts/demo-fusion.js",
|
|
52
|
+
"eval:fusion": "node scripts/eval-fusion.js",
|
|
47
53
|
"probe": "node dist/cli.js probe --json",
|
|
48
54
|
"release:guard": "node scripts/release-guard.mjs",
|
|
49
55
|
"prepublishOnly": "npm run release:guard && npm test"
|