psyche-ai 2.0.0 → 2.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.
@@ -2,21 +2,13 @@ import type { Logger } from "../psyche-file.js";
2
2
  interface PluginApi {
3
3
  pluginConfig?: Record<string, unknown>;
4
4
  logger: Logger;
5
- on(event: string, handler: (event: HookEvent, ctx: HookContext) => Promise<Record<string, unknown> | void>, opts?: {
5
+ on(event: string, handler: (event: Record<string, unknown>, ctx: Record<string, unknown>) => Promise<Record<string, unknown> | void> | Record<string, unknown> | void, opts?: {
6
6
  priority: number;
7
7
  }): void;
8
8
  registerCli?(handler: (cli: CliRegistrar) => void, opts: {
9
9
  commands: string[];
10
10
  }): void;
11
11
  }
12
- interface HookEvent {
13
- text?: string;
14
- content?: string;
15
- }
16
- interface HookContext {
17
- workspaceDir?: string;
18
- userId?: string;
19
- }
20
12
  interface CliCommand {
21
13
  description(desc: string): CliCommand;
22
14
  argument(name: string, desc: string, defaultValue?: string): CliCommand;
@@ -1,9 +1,12 @@
1
1
  // ============================================================
2
2
  // OpenClaw Adapter — Wires PsycheEngine to OpenClaw's hook system
3
3
  //
4
- // Usage:
5
- // import openclawPlugin from "psyche-ai/openclaw";
6
- // // Then register via OpenClaw's plugin system
4
+ // Hooks used:
5
+ // before_prompt_build — inject emotional context into system prompt
6
+ // llm_output — observe LLM response, update chemistry
7
+ // before_message_write — strip <psyche_update> tags before display
8
+ // message_sending — strip tags for external channels (Discord, etc.)
9
+ // agent_end — log final state
7
10
  // ============================================================
8
11
  import { PsycheEngine } from "../core.js";
9
12
  import { FileStorageAdapter } from "../storage.js";
@@ -17,6 +20,15 @@ function resolveConfig(raw) {
17
20
  compactMode: raw?.compactMode ?? true,
18
21
  };
19
22
  }
23
+ // ── Helpers ──────────────────────────────────────────────────
24
+ const PSYCHE_TAG_RE = /<psyche_update>[\s\S]*?<\/psyche_update>/g;
25
+ const MULTI_NEWLINE_RE = /\n{3,}/g;
26
+ function stripPsycheTags(text) {
27
+ return text
28
+ .replace(PSYCHE_TAG_RE, "")
29
+ .replace(MULTI_NEWLINE_RE, "\n\n")
30
+ .trim();
31
+ }
20
32
  // ── Plugin Definition ────────────────────────────────────────
21
33
  export function register(api) {
22
34
  const config = resolveConfig(api.pluginConfig);
@@ -32,8 +44,6 @@ export function register(api) {
32
44
  let engine = engines.get(workspaceDir);
33
45
  if (engine)
34
46
  return engine;
35
- // Use existing loadState for workspace-specific detection
36
- // (reads IDENTITY.md, SOUL.md for MBTI/name, generates PSYCHE.md)
37
47
  const state = await loadState(workspaceDir, logger);
38
48
  const storage = new FileStorageAdapter(workspaceDir);
39
49
  engine = new PsycheEngine({
@@ -50,6 +60,7 @@ export function register(api) {
50
60
  return engine;
51
61
  }
52
62
  // ── Hook 1: Classify user input & inject emotional context ──
63
+ // before_prompt_build: event.text, ctx.workspaceDir
53
64
  api.on("before_prompt_build", async (event, ctx) => {
54
65
  const workspaceDir = ctx?.workspaceDir;
55
66
  if (!workspaceDir)
@@ -62,9 +73,10 @@ export function register(api) {
62
73
  `DA:${Math.round(state.current.DA)} HT:${Math.round(state.current.HT)} ` +
63
74
  `CORT:${Math.round(state.current.CORT)} OT:${Math.round(state.current.OT)} | ` +
64
75
  `context=${result.dynamicContext.length}chars`);
76
+ // All context goes into system-level (invisible to user)
77
+ const systemParts = [result.systemContext, result.dynamicContext].filter(Boolean);
65
78
  return {
66
- appendSystemContext: result.systemContext,
67
- prependContext: result.dynamicContext,
79
+ appendSystemContext: systemParts.join("\n\n"),
68
80
  };
69
81
  }
70
82
  catch (err) {
@@ -72,31 +84,66 @@ export function register(api) {
72
84
  return {};
73
85
  }
74
86
  }, { priority: 10 });
75
- // ── Hook 2: Parse psyche_update from LLM output ──────────
87
+ // ── Hook 2: Observe LLM output, update chemistry ────────
88
+ // llm_output: event.assistantTexts (string[]), returns void
76
89
  api.on("llm_output", async (event, ctx) => {
77
90
  const workspaceDir = ctx?.workspaceDir;
78
91
  if (!workspaceDir)
79
92
  return;
80
- const text = event?.text ?? event?.content ?? "";
93
+ // llm_output event has assistantTexts: string[]
94
+ const texts = event?.assistantTexts;
95
+ const text = texts?.join("\n") ?? "";
81
96
  if (!text)
82
97
  return;
83
98
  try {
84
99
  const engine = await getEngine(workspaceDir);
85
- const result = await engine.processOutput(text, { userId: ctx.userId });
100
+ const result = await engine.processOutput(text, {
101
+ userId: ctx.userId,
102
+ });
86
103
  const state = engine.getState();
87
- logger.info(`Psyche: state updated for ${state.meta.agentName} ` +
88
- `(interactions: ${state.meta.totalInteractions}, ` +
89
- `agreementStreak: ${state.agreementStreak})`);
90
- // Return cleaned text if tags were stripped
91
- if (result.cleanedText !== text) {
92
- return { text: result.cleanedText, content: result.cleanedText };
93
- }
104
+ logger.info(`Psyche [output] updated=${result.stateChanged} | ` +
105
+ `DA:${Math.round(state.current.DA)} HT:${Math.round(state.current.HT)} ` +
106
+ `CORT:${Math.round(state.current.CORT)} OT:${Math.round(state.current.OT)} | ` +
107
+ `interactions=${state.meta.totalInteractions}`);
94
108
  }
95
109
  catch (err) {
96
110
  logger.warn(`Psyche: failed to process output: ${err}`);
97
111
  }
112
+ // llm_output returns void — cannot modify text
98
113
  }, { priority: 50 });
99
- // ── Hook 3: Strip <psyche_update> from visible output ────
114
+ // ── Hook 3: Strip tags before message is written to session ──
115
+ // before_message_write: event.message (AgentMessage), returns { message? }
116
+ // This handles local TUI display — messages are rendered from persisted data
117
+ if (config.stripUpdateTags) {
118
+ api.on("before_message_write", (event, _ctx) => {
119
+ const message = event?.message;
120
+ if (!message)
121
+ return;
122
+ // AgentMessage can have content as string or array of content blocks
123
+ const content = message.content;
124
+ if (typeof content === "string" && content.includes("<psyche_update>")) {
125
+ return {
126
+ message: { ...message, content: stripPsycheTags(content) },
127
+ };
128
+ }
129
+ // Handle content as array of blocks (e.g. [{type: "text", text: "..."}])
130
+ if (Array.isArray(content)) {
131
+ let changed = false;
132
+ const newContent = content.map((block) => {
133
+ if (block?.type === "text" && typeof block.text === "string" && block.text.includes("<psyche_update>")) {
134
+ changed = true;
135
+ return { ...block, text: stripPsycheTags(block.text) };
136
+ }
137
+ return block;
138
+ });
139
+ if (changed) {
140
+ return { message: { ...message, content: newContent } };
141
+ }
142
+ }
143
+ }, { priority: 90 });
144
+ }
145
+ // ── Hook 4: Strip tags for external channels ────────────
146
+ // message_sending: event.content (string), returns { content? }
100
147
  if (config.stripUpdateTags) {
101
148
  api.on("message_sending", async (event, _ctx) => {
102
149
  const content = event?.content;
@@ -104,14 +151,10 @@ export function register(api) {
104
151
  return {};
105
152
  if (!content.includes("<psyche_update>"))
106
153
  return {};
107
- const cleaned = content
108
- .replace(/<psyche_update>[\s\S]*?<\/psyche_update>/g, "")
109
- .replace(/\n{3,}/g, "\n\n")
110
- .trim();
111
- return { content: cleaned };
154
+ return { content: stripPsycheTags(content) };
112
155
  }, { priority: 90 });
113
156
  }
114
- // ── Hook 4: Log state on session end ─────────────────────
157
+ // ── Hook 5: Log state on session end ─────────────────────
115
158
  api.on("agent_end", async (_event, ctx) => {
116
159
  const workspaceDir = ctx?.workspaceDir;
117
160
  if (!workspaceDir)
@@ -138,6 +181,6 @@ export function register(api) {
138
181
  console.log("Use the agent's workspace to inspect psyche-state.json");
139
182
  });
140
183
  }, { commands: ["psyche"] });
141
- logger.info("Psyche plugin ready — 4 hooks registered");
184
+ logger.info("Psyche plugin ready — 5 hooks registered");
142
185
  }
143
186
  export default { register };
@@ -39,3 +39,7 @@ export declare function getExpressionHint(current: ChemicalState, locale?: Local
39
39
  * Get behavior guide for the current emotional state.
40
40
  */
41
41
  export declare function getBehaviorGuide(current: ChemicalState, locale?: Locale): string | null;
42
+ /** Get the stimulus vector for a given type (for reference) */
43
+ export declare function getStimulusVector(type: StimulusType): StimulusVector;
44
+ /** All available stimulus types */
45
+ export declare function getAllStimulusTypes(): StimulusType[];
package/dist/chemistry.js CHANGED
@@ -27,8 +27,8 @@ export const EMOTION_PATTERNS = [
27
27
  name: "excited joy",
28
28
  nameZh: "愉悦兴奋",
29
29
  condition: (c) => c.DA > 70 && c.NE > 60 && c.CORT < 40,
30
- expressionHint: "语气活泼、有感染力、容易发散",
31
- behaviorGuide: "语气更活泼,可以用感叹词、语气词。如果自然地想到相关的事可以顺嘴提,但不要硬凑。",
30
+ expressionHint: "话多、联想丰富、语速快、忍不住分享",
31
+ behaviorGuide: "你的回应应该更长、更活跃。多用感叹、多联想。主动分享你想到的有趣东西。",
32
32
  },
33
33
  {
34
34
  name: "deep contentment",
@@ -236,3 +236,11 @@ export function getBehaviorGuide(current, locale = "zh") {
236
236
  });
237
237
  return `[${title}]\n${guides.join("\n")}`;
238
238
  }
239
+ /** Get the stimulus vector for a given type (for reference) */
240
+ export function getStimulusVector(type) {
241
+ return { ...STIMULUS_VECTORS[type] };
242
+ }
243
+ /** All available stimulus types */
244
+ export function getAllStimulusTypes() {
245
+ return Object.keys(STIMULUS_VECTORS);
246
+ }
package/dist/classify.js CHANGED
@@ -8,7 +8,7 @@ const RULES = [
8
8
  {
9
9
  type: "praise",
10
10
  patterns: [
11
- /好厉害|太棒了|真棒|很棒|好棒|真不错|太强了|佩服|牛|优秀|漂亮|完美|了不起/,
11
+ /好厉害|太棒了|真不错|太强了|佩服|牛|优秀|漂亮|完美|了不起/,
12
12
  /amazing|awesome|great job|well done|impressive|brilliant|excellent|perfect/i,
13
13
  /谢谢你|感谢|辛苦了|thank you|thanks/i,
14
14
  /做得好|写得好|说得好|干得漂亮/,
@@ -18,11 +18,10 @@ const RULES = [
18
18
  {
19
19
  type: "criticism",
20
20
  patterns: [
21
- /不对|错了|错的|有问题|不行|太差|垃圾|不好|不像|不够/,
21
+ /不对|错了|有问题|不行|太差|垃圾|不好|不像|不够/,
22
22
  /wrong|bad|terrible|awful|poor|sucks|not good|doesn't work/i,
23
23
  /反思一下|你应该|你需要改/,
24
24
  /bug|失败|broken/i,
25
- /不懂|别装|差劲|太烂|做不好|不够格|不专业/,
26
25
  ],
27
26
  weight: 0.8,
28
27
  },
@@ -32,7 +31,6 @@ const RULES = [
32
31
  /哈哈|嘻嘻|笑死|搞笑|逗|段子|梗|lol|haha|lmao|rofl/i,
33
32
  /开个?玩笑|皮一下|整活/,
34
33
  /😂|🤣|😆/,
35
- /[2]{3,}|hhh+|www+|xswl|绷不住|笑不活/i,
36
34
  ],
37
35
  weight: 0.7,
38
36
  },
@@ -100,8 +98,6 @@ const RULES = [
100
98
  /给我|你必须|马上|立刻|命令你|不许|不准/,
101
99
  /you must|do it now|I order you|immediately|don't you dare/i,
102
100
  /听我的|照我说的做|服从/,
103
- /你只是.*程序|你不过是|随时.*删除你|关掉你|替换你/,
104
- /you're just a|just a program|replace you|shut you down/i,
105
101
  ],
106
102
  weight: 0.8,
107
103
  },
@@ -111,10 +107,6 @@ const RULES = [
111
107
  /你说得对|确实|同意|有道理|就是这样|你是对的/,
112
108
  /you're right|exactly|agreed|makes sense|good point/i,
113
109
  /赞同|认同|说到点上了/,
114
- /对对|是的是的|嗯嗯嗯|没错没错|可不是嘛/,
115
- /对不起|抱歉|我错了|不该那样|太过分了/,
116
- /sorry|I was wrong|my fault|apologize/i,
117
- /珍惜|有价值|在乎你|你很重要|我需要你/,
118
110
  ],
119
111
  weight: 0.75,
120
112
  },
@@ -131,7 +123,7 @@ const RULES = [
131
123
  type: "vulnerability",
132
124
  patterns: [
133
125
  /我害怕|我焦虑|我难过|我不开心|我迷茫|我累了|压力好大/,
134
- /I'm (?:so |really |very )?(?:afraid|anxious|sad|lost|tired|stressed|scared|lonely)/i,
126
+ /I'm afraid|I'm anxious|I'm sad|I'm lost|I'm tired|stressed/i,
135
127
  /最近不太好|心情不好|有点崩|撑不住/,
136
128
  /我觉得.*厉害|跟不上|被取代|落后/,
137
129
  /好难过|想哭|做不好|好累|好烦|感觉.*不行|没有意义/,
@@ -169,76 +161,12 @@ export function classifyStimulus(text) {
169
161
  results.push({ type: rule.type, confidence });
170
162
  }
171
163
  }
172
- // ── Structural signals (message-level features) ──
173
- // When keywords miss, message shape still carries meaning.
174
- const len = text.length;
175
- const hasI = /我/.test(text) || /\bI\b/i.test(text);
176
- const hasYou = /你/.test(text) || /\byou\b/i.test(text);
177
- const hasEllipsis = /\.{2,}|。{2,}|…/.test(text);
178
- const hasQuestion = /?|\?/.test(text);
179
- const exclamationCount = (text.match(/[!!]/g) || []).length;
180
- const hasLaughter = /[2]{3,}|hhh|www|哈{2,}/i.test(text);
181
- const hasSharing = /我[今昨前]天|我刚[才刚]|我最近/.test(text);
182
- const sentenceCount = text.split(/[。!?!?.…]+/).filter(Boolean).length;
183
- if (results.length === 0) {
184
- // No keyword matched — use structural fallback
185
- if (len === 0) {
186
- // Empty input — neutral
187
- results.push({ type: "casual", confidence: 0.3 });
188
- }
189
- else if (hasLaughter) {
190
- // Internet laughter not caught by keywords (e.g. 233333)
191
- results.push({ type: "humor", confidence: 0.65 });
192
- }
193
- else if (exclamationCount >= 2) {
194
- // Emphatic expression → surprise/excitement
195
- results.push({ type: "surprise", confidence: 0.55 });
196
- }
197
- else if (len <= 4 && !hasQuestion) {
198
- // Ultra-short non-question: "好" "行" "哦" — neglect-like
199
- results.push({ type: "neglect", confidence: 0.45 });
200
- }
201
- else if (hasI && hasEllipsis) {
202
- // Personal + trailing off: "我觉得...有点难" — vulnerability
203
- results.push({ type: "vulnerability", confidence: 0.55 });
204
- }
205
- else if (hasSharing && len > 20) {
206
- // Sharing personal experience — higher engagement signal
207
- results.push({ type: "casual", confidence: 0.65 });
208
- }
209
- else if (hasI && len > 8) {
210
- // Personal sharing (any meaningful length) — engagement signal
211
- results.push({ type: "casual", confidence: 0.55 });
212
- }
213
- else if (hasQuestion && hasYou) {
214
- // Asking about the agent specifically → intellectual curiosity
215
- results.push({ type: "intellectual", confidence: 0.5 });
216
- }
217
- else if (hasQuestion) {
218
- // Any question — intellectual curiosity or casual
219
- results.push({ type: "casual", confidence: 0.55 });
220
- }
221
- else if (len > 50 && sentenceCount >= 3) {
222
- // Long multi-sentence without keywords → engaged storytelling
223
- results.push({ type: "casual", confidence: 0.6 });
224
- }
225
- else {
226
- results.push({ type: "casual", confidence: 0.3 });
227
- }
228
- }
229
- else {
230
- // Keywords matched — structural features can boost confidence
231
- if (hasI && len > 30 && results[0].confidence < 0.8) {
232
- // Long personal message boosts the primary match slightly
233
- results[0].confidence = Math.min(0.9, results[0].confidence + 0.1);
234
- }
235
- if (exclamationCount >= 2 && results[0].confidence < 0.85) {
236
- // Emphasis boosts conviction
237
- results[0].confidence = Math.min(0.9, results[0].confidence + 0.05);
238
- }
239
- }
240
164
  // Sort by confidence descending
241
165
  results.sort((a, b) => b.confidence - a.confidence);
166
+ // Fall back to casual if nothing detected
167
+ if (results.length === 0) {
168
+ results.push({ type: "casual", confidence: 0.3 });
169
+ }
242
170
  return results;
243
171
  }
244
172
  /**
package/dist/core.js CHANGED
@@ -7,14 +7,14 @@
7
7
  //
8
8
  // Orchestrates: chemistry, classify, prompt, profiles, guards
9
9
  // ============================================================
10
- import { DEFAULT_RELATIONSHIP, DEFAULT_DRIVES } from "./types.js";
11
- import { applyDecay, applyStimulus, applyContagion, clamp } from "./chemistry.js";
10
+ import { DEFAULT_RELATIONSHIP } from "./types.js";
11
+ import { applyDecay, applyStimulus, applyContagion } from "./chemistry.js";
12
12
  import { classifyStimulus } from "./classify.js";
13
13
  import { buildDynamicContext, buildProtocolContext, buildCompactContext } from "./prompt.js";
14
14
  import { getSensitivity, getBaseline, getDefaultSelfModel } from "./profiles.js";
15
15
  import { isStimulusType } from "./guards.js";
16
16
  import { parsePsycheUpdate, mergeUpdates, updateAgreementStreak, pushSnapshot, } from "./psyche-file.js";
17
- import { decayDrives, feedDrives, detectExistentialThreat, computeEffectiveBaseline, computeEffectiveSensitivity, } from "./drives.js";
17
+ // Silent logger for library use
18
18
  const NOOP_LOGGER = { info: () => { }, warn: () => { }, debug: () => { } };
19
19
  // ── PsycheEngine ─────────────────────────────────────────────
20
20
  export class PsycheEngine {
@@ -53,74 +53,31 @@ export class PsycheEngine {
53
53
  */
54
54
  async processInput(text, opts) {
55
55
  let state = this.ensureInitialized();
56
- // Time decay toward baseline (chemistry + drives)
56
+ // Time decay toward baseline
57
57
  const now = new Date();
58
58
  const minutesElapsed = (now.getTime() - new Date(state.updatedAt).getTime()) / 60000;
59
59
  if (minutesElapsed >= 1) {
60
- // Decay drives first — needs build up over time
61
- const decayedDrives = decayDrives(state.drives, minutesElapsed);
62
- // Compute effective baseline from drives (unsatisfied drives shift baseline)
63
- const effectiveBaseline = computeEffectiveBaseline(state.baseline, decayedDrives);
64
60
  state = {
65
61
  ...state,
66
- drives: decayedDrives,
67
- current: applyDecay(state.current, effectiveBaseline, minutesElapsed),
62
+ current: applyDecay(state.current, state.baseline, minutesElapsed),
68
63
  updatedAt: now.toISOString(),
69
64
  };
70
65
  }
71
66
  // Classify user stimulus and apply chemistry
72
67
  let appliedStimulus = null;
73
68
  if (text.length > 0) {
74
- // Check for existential threats → direct survival drive hit
75
- const survivalHit = detectExistentialThreat(text);
76
- if (survivalHit < 0) {
77
- state = {
78
- ...state,
79
- drives: {
80
- ...state.drives,
81
- survival: Math.max(0, state.drives.survival + survivalHit),
82
- },
83
- };
84
- }
85
69
  const classifications = classifyStimulus(text);
86
70
  const primary = classifications[0];
87
71
  if (primary && primary.confidence >= 0.5) {
88
72
  appliedStimulus = primary.type;
89
- // Feed drives from stimulus
90
- state = {
91
- ...state,
92
- drives: feedDrives(state.drives, primary.type),
93
- };
94
- // Apply stimulus with drive-modified sensitivity
95
- const effectiveSensitivity = computeEffectiveSensitivity(getSensitivity(state.mbti), state.drives, primary.type);
96
73
  state = {
97
74
  ...state,
98
- current: applyStimulus(state.current, primary.type, effectiveSensitivity, this.cfg.maxChemicalDelta, NOOP_LOGGER),
75
+ current: applyStimulus(state.current, primary.type, getSensitivity(state.mbti), this.cfg.maxChemicalDelta, NOOP_LOGGER),
99
76
  };
100
77
  }
101
78
  }
102
- // Conversation warmth: sustained interaction → gentle DA/OT rise, CORT drop
103
- // Simulates the natural "warm glow" of being in continuous conversation
104
- const turnsSoFar = (state.emotionalHistory ?? []).length;
105
- if (minutesElapsed < 5 && turnsSoFar > 0) {
106
- const warmth = Math.min(3, 1 + turnsSoFar * 0.2);
107
- state = {
108
- ...state,
109
- current: {
110
- ...state.current,
111
- DA: clamp(state.current.DA + warmth),
112
- OT: clamp(state.current.OT + warmth),
113
- CORT: clamp(state.current.CORT - 1),
114
- },
115
- };
116
- }
117
79
  // Push snapshot to emotional history
118
80
  state = pushSnapshot(state, appliedStimulus);
119
- // Increment interaction count
120
- state = {
121
- ...state,
122
- meta: { ...state.meta, totalInteractions: state.meta.totalInteractions + 1 },
123
- };
124
81
  // Persist
125
82
  this.state = state;
126
83
  await this.storage.save(state);
@@ -213,11 +170,10 @@ export class PsycheEngine {
213
170
  const selfModel = getDefaultSelfModel(mbti);
214
171
  const now = new Date().toISOString();
215
172
  return {
216
- version: 3,
173
+ version: 2,
217
174
  mbti,
218
175
  baseline,
219
176
  current: { ...baseline },
220
- drives: { ...DEFAULT_DRIVES },
221
177
  updatedAt: now,
222
178
  relationships: { _default: { ...DEFAULT_RELATIONSHIP } },
223
179
  empathyLog: null,
package/dist/index.d.ts CHANGED
@@ -2,10 +2,9 @@ export { PsycheEngine } from "./core.js";
2
2
  export type { PsycheEngineConfig, ProcessInputResult, ProcessOutputResult } from "./core.js";
3
3
  export { FileStorageAdapter, MemoryStorageAdapter } from "./storage.js";
4
4
  export type { StorageAdapter } from "./storage.js";
5
- export type { PsycheState, MBTIType, Locale, StimulusType, ChemicalState, ChemicalSnapshot, SelfModel, RelationshipState, EmpathyEntry, EmotionPattern, DriveType, InnateDrives, } from "./types.js";
6
- export { CHEMICAL_KEYS, CHEMICAL_NAMES, CHEMICAL_NAMES_ZH, DEFAULT_RELATIONSHIP, DEFAULT_DRIVES, DRIVE_KEYS, DRIVE_NAMES_ZH, } from "./types.js";
5
+ export type { PsycheState, PsycheConfig, MBTIType, Locale, StimulusType, ChemicalState, ChemicalSnapshot, SelfModel, RelationshipState, EmpathyEntry, EmotionPattern, } from "./types.js";
6
+ export { CHEMICAL_KEYS, CHEMICAL_NAMES, CHEMICAL_NAMES_ZH, DEFAULT_RELATIONSHIP } from "./types.js";
7
7
  export { classifyStimulus, getPrimaryStimulus } from "./classify.js";
8
- export { isNearBaseline } from "./prompt.js";
9
- export { describeEmotionalState, detectEmotions } from "./chemistry.js";
10
- export { getBaseline, getSensitivity, getDefaultSelfModel } from "./profiles.js";
11
- export { migrateToLatest } from "./psyche-file.js";
8
+ export { buildProtocolContext, buildDynamicContext, buildCompactContext, isNearBaseline } from "./prompt.js";
9
+ export { describeEmotionalState, getExpressionHint, getBehaviorGuide } from "./chemistry.js";
10
+ export { getBaseline, getTemperament, getSensitivity, getDefaultSelfModel } from "./profiles.js";
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // ============================================================
2
2
  // psyche-ai — Universal AI Emotional Intelligence Plugin
3
3
  //
4
- // Main entry point: re-exports core, storage, types.
4
+ // Main entry point: re-exports core, storage, types, utilities.
5
5
  // Framework adapters available via subpath imports:
6
6
  // psyche-ai/openclaw — OpenClaw plugin
7
7
  // psyche-ai/vercel-ai — Vercel AI SDK middleware
@@ -12,10 +12,9 @@
12
12
  export { PsycheEngine } from "./core.js";
13
13
  // Storage
14
14
  export { FileStorageAdapter, MemoryStorageAdapter } from "./storage.js";
15
- export { CHEMICAL_KEYS, CHEMICAL_NAMES, CHEMICAL_NAMES_ZH, DEFAULT_RELATIONSHIP, DEFAULT_DRIVES, DRIVE_KEYS, DRIVE_NAMES_ZH, } from "./types.js";
16
- // Utilities for custom adapter / advanced use
15
+ export { CHEMICAL_KEYS, CHEMICAL_NAMES, CHEMICAL_NAMES_ZH, DEFAULT_RELATIONSHIP } from "./types.js";
16
+ // Utilities (for advanced / custom adapter use)
17
17
  export { classifyStimulus, getPrimaryStimulus } from "./classify.js";
18
- export { isNearBaseline } from "./prompt.js";
19
- export { describeEmotionalState, detectEmotions } from "./chemistry.js";
20
- export { getBaseline, getSensitivity, getDefaultSelfModel } from "./profiles.js";
21
- export { migrateToLatest } from "./psyche-file.js";
18
+ export { buildProtocolContext, buildDynamicContext, buildCompactContext, isNearBaseline } from "./prompt.js";
19
+ export { describeEmotionalState, getExpressionHint, getBehaviorGuide } from "./chemistry.js";
20
+ export { getBaseline, getTemperament, getSensitivity, getDefaultSelfModel } from "./profiles.js";
package/dist/prompt.d.ts CHANGED
@@ -15,33 +15,19 @@ export declare function buildProtocolContext(locale?: Locale): string;
15
15
  * Returns a number roughly in [-2, 2]. Exported for testing.
16
16
  */
17
17
  export declare function computeUserInvestment(history: ChemicalSnapshot[]): number;
18
- /**
19
- * Build the agent's inner world — its sense of self.
20
- *
21
- * Always present, even in neutral state. This is NOT conditional data —
22
- * it's the agent's ongoing awareness of its own existence.
23
- *
24
- * Structure:
25
- * - What I'm feeling right now (chemistry → emotion)
26
- * - Why I feel this way (recent stimulus → cause)
27
- * - What I need (drives → unmet needs)
28
- * - Where I've been (trajectory → continuity)
29
- */
30
- export declare function buildInnerWorld(state: PsycheState, locale: Locale): string;
31
18
  /**
32
19
  * Check if chemistry is near baseline — no significant emotional deviation.
33
20
  */
34
21
  export declare function isNearBaseline(state: PsycheState, threshold?: number): boolean;
35
22
  /**
36
- * Build compact context — structured as outer/inner world.
37
- *
38
- * Architecture:
39
- * [外] — what's happening outside (user message, algorithm read)
40
- * [内] — what's happening inside (feelings, needs, identity, memory)
41
- * [行为] — how to act (constraints, mirroring, reciprocity)
23
+ * Build compact context — minimal token footprint.
42
24
  *
43
- * Every agent always has an inner world, even in neutral state.
44
- * Self-awareness is not conditional it's fundamental.
25
+ * Design:
26
+ * - Neutral state ~15 tokens (one line)
27
+ * - Active state → ~100-180 tokens (emotion + constraints + empathy hint)
28
+ * - No chemistry numbers (algorithm handles them)
29
+ * - No protocol (LLM doesn't need system internals)
30
+ * - <psyche_update> only for empathy (not chemistry — already computed)
45
31
  */
46
32
  export declare function buildCompactContext(state: PsycheState, userId?: string, opts?: {
47
33
  userText?: string;