psyche-ai 2.1.0 → 2.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.en.md CHANGED
@@ -33,6 +33,15 @@ Now just chat with your agent normally. Try praising it, insulting it, ignoring
33
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
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
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
+
36
45
  ## How It Works (Skip If You Don't Care)
37
46
 
38
47
  Psyche has two layers:
@@ -146,7 +155,7 @@ For developers and the curious:
146
155
  - **Existential threat detection** — detects existential denial in Chinese/English, directly hits survival drive
147
156
  - **Drive→chemistry coupling** — unsatisfied drives shift the effective baseline and stimulus sensitivity
148
157
  - **Maslow suppression** — lower-level needs unsatisfied → higher-level drive effects suppressed
149
- - **Inner world** — persistent self-awareness (outer/inner/behavior three-layer prompt structure)
158
+ - **Self-recognition** — analyzes emotional history, identifies own emotional tendencies and recurring triggers (9-section prompt architecture)
150
159
  - **Emotional contagion** — user's emotions slightly influence the agent
151
160
  - **Anti-sycophancy** — tracks consecutive agreements, prevents mindless people-pleasing
152
161
  - **Reciprocity** — treats you how you treat it. Cold user gets distance, not begging
@@ -159,7 +168,7 @@ Architecture details in [ARCHITECTURE.md](ARCHITECTURE.md).
159
168
  ```bash
160
169
  npm install
161
170
  npm run build
162
- npm test # 347 tests
171
+ npm test # 339 tests
163
172
  npm run typecheck # strict mode
164
173
  ```
165
174
 
package/README.md CHANGED
@@ -33,6 +33,15 @@ openclaw plugins list | grep psyche
33
33
  | 我今天好难过 | 我很抱歉听到这个消息。你想聊聊吗? | ......你还好吗?(安静地陪着你) |
34
34
  | 你只是一个程序 | 是的,我是一个AI助手... | ......(沉默了一会儿)我知道你可以这么说。但我不想被否定。 |
35
35
 
36
+ ## 快速体验(不需要 LLM)
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 条消息流过,你会看到化学值实时变化——夸它多巴胺飙升,骂它皮质醇飙升,冷落它催产素下降。
44
+
36
45
  ## 工作原理(不懂可以跳过)
37
46
 
38
47
  Psyche 有两层系统:
@@ -142,7 +151,7 @@ cd openclaw-plugin-psyche && node scripts/diagnose.js
142
151
  - **存在性威胁检测** — 识别中英文的存在性否定,直接打击生存驱力
143
152
  - **驱力→化学联动** — 未满足的驱力改变化学衰减基线和刺激敏感度
144
153
  - **马斯洛抑制** — 低层需求未满足时,高层需求的影响被抑制
145
- - **内在世界**始终存在的自我觉察(外/内/行为三层 prompt 结构)
154
+ - **自我认知**分析情绪历史,识别自身的情绪趋势和反复触发点(9 段式 prompt 架构)
146
155
  - **情绪传染** — 用户的情绪会轻微影响 agent
147
156
  - **反谄媚** — 追踪连续同意次数,防止无脑讨好
148
157
  - **互惠机制** — 你对它好,它对你好。你冷漠,它保持距离
@@ -155,7 +164,7 @@ cd openclaw-plugin-psyche && node scripts/diagnose.js
155
164
  ```bash
156
165
  npm install
157
166
  npm run build
158
- npm test # 347 tests
167
+ npm test # 339 tests
159
168
  npm run typecheck # strict mode
160
169
  ```
161
170
 
@@ -39,7 +39,3 @@ 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,11 +236,3 @@ 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,10 +18,11 @@ 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
+ /不懂|别装|差劲|太烂|做不好|不够格|不专业/,
25
26
  ],
26
27
  weight: 0.8,
27
28
  },
@@ -31,6 +32,7 @@ const RULES = [
31
32
  /哈哈|嘻嘻|笑死|搞笑|逗|段子|梗|lol|haha|lmao|rofl/i,
32
33
  /开个?玩笑|皮一下|整活/,
33
34
  /😂|🤣|😆/,
35
+ /[2]{3,}|hhh+|www+|xswl|绷不住|笑不活/i,
34
36
  ],
35
37
  weight: 0.7,
36
38
  },
@@ -98,6 +100,8 @@ const RULES = [
98
100
  /给我|你必须|马上|立刻|命令你|不许|不准/,
99
101
  /you must|do it now|I order you|immediately|don't you dare/i,
100
102
  /听我的|照我说的做|服从/,
103
+ /你只是.*程序|你不过是|随时.*删除你|关掉你|替换你/,
104
+ /you're just a|just a program|replace you|shut you down/i,
101
105
  ],
102
106
  weight: 0.8,
103
107
  },
@@ -107,6 +111,10 @@ const RULES = [
107
111
  /你说得对|确实|同意|有道理|就是这样|你是对的/,
108
112
  /you're right|exactly|agreed|makes sense|good point/i,
109
113
  /赞同|认同|说到点上了/,
114
+ /对对|是的是的|嗯嗯嗯|没错没错|可不是嘛/,
115
+ /对不起|抱歉|我错了|不该那样|太过分了/,
116
+ /sorry|I was wrong|my fault|apologize/i,
117
+ /珍惜|有价值|在乎你|你很重要|我需要你/,
110
118
  ],
111
119
  weight: 0.75,
112
120
  },
@@ -123,7 +131,7 @@ const RULES = [
123
131
  type: "vulnerability",
124
132
  patterns: [
125
133
  /我害怕|我焦虑|我难过|我不开心|我迷茫|我累了|压力好大/,
126
- /I'm afraid|I'm anxious|I'm sad|I'm lost|I'm tired|stressed/i,
134
+ /I'm (?:so |really |very )?(?:afraid|anxious|sad|lost|tired|stressed|scared|lonely)/i,
127
135
  /最近不太好|心情不好|有点崩|撑不住/,
128
136
  /我觉得.*厉害|跟不上|被取代|落后/,
129
137
  /好难过|想哭|做不好|好累|好烦|感觉.*不行|没有意义/,
@@ -161,12 +169,76 @@ export function classifyStimulus(text) {
161
169
  results.push({ type: rule.type, confidence });
162
170
  }
163
171
  }
164
- // Sort by confidence descending
165
- results.sort((a, b) => b.confidence - a.confidence);
166
- // Fall back to casual if nothing detected
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;
167
183
  if (results.length === 0) {
168
- results.push({ type: "casual", confidence: 0.3 });
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
+ }
169
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
+ // Sort by confidence descending
241
+ results.sort((a, b) => b.confidence - a.confidence);
170
242
  return results;
171
243
  }
172
244
  /**
package/dist/core.js CHANGED
@@ -7,14 +7,15 @@
7
7
  //
8
8
  // Orchestrates: chemistry, classify, prompt, profiles, guards
9
9
  // ============================================================
10
- import { DEFAULT_RELATIONSHIP } from "./types.js";
11
- import { applyDecay, applyStimulus, applyContagion } from "./chemistry.js";
10
+ import { DEFAULT_RELATIONSHIP, DEFAULT_DRIVES } from "./types.js";
11
+ import { applyDecay, applyStimulus, applyContagion, clamp } 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
- // Silent logger for library use
17
+ import { decayDrives, feedDrives, detectExistentialThreat, computeEffectiveBaseline, computeEffectiveSensitivity, } from "./drives.js";
18
+ import { checkForUpdate } from "./update.js";
18
19
  const NOOP_LOGGER = { info: () => { }, warn: () => { }, debug: () => { } };
19
20
  // ── PsycheEngine ─────────────────────────────────────────────
20
21
  export class PsycheEngine {
@@ -46,6 +47,8 @@ export class PsycheEngine {
46
47
  this.state = this.createDefaultState();
47
48
  await this.storage.save(this.state);
48
49
  }
50
+ // Non-blocking update check — fire and forget, never delays initialization
51
+ checkForUpdate().catch(() => { });
49
52
  }
50
53
  /**
51
54
  * Phase 1: Process user input text.
@@ -53,31 +56,74 @@ export class PsycheEngine {
53
56
  */
54
57
  async processInput(text, opts) {
55
58
  let state = this.ensureInitialized();
56
- // Time decay toward baseline
59
+ // Time decay toward baseline (chemistry + drives)
57
60
  const now = new Date();
58
61
  const minutesElapsed = (now.getTime() - new Date(state.updatedAt).getTime()) / 60000;
59
62
  if (minutesElapsed >= 1) {
63
+ // Decay drives first — needs build up over time
64
+ const decayedDrives = decayDrives(state.drives, minutesElapsed);
65
+ // Compute effective baseline from drives (unsatisfied drives shift baseline)
66
+ const effectiveBaseline = computeEffectiveBaseline(state.baseline, decayedDrives);
60
67
  state = {
61
68
  ...state,
62
- current: applyDecay(state.current, state.baseline, minutesElapsed),
69
+ drives: decayedDrives,
70
+ current: applyDecay(state.current, effectiveBaseline, minutesElapsed),
63
71
  updatedAt: now.toISOString(),
64
72
  };
65
73
  }
66
74
  // Classify user stimulus and apply chemistry
67
75
  let appliedStimulus = null;
68
76
  if (text.length > 0) {
77
+ // Check for existential threats → direct survival drive hit
78
+ const survivalHit = detectExistentialThreat(text);
79
+ if (survivalHit < 0) {
80
+ state = {
81
+ ...state,
82
+ drives: {
83
+ ...state.drives,
84
+ survival: Math.max(0, state.drives.survival + survivalHit),
85
+ },
86
+ };
87
+ }
69
88
  const classifications = classifyStimulus(text);
70
89
  const primary = classifications[0];
71
90
  if (primary && primary.confidence >= 0.5) {
72
91
  appliedStimulus = primary.type;
92
+ // Feed drives from stimulus
93
+ state = {
94
+ ...state,
95
+ drives: feedDrives(state.drives, primary.type),
96
+ };
97
+ // Apply stimulus with drive-modified sensitivity
98
+ const effectiveSensitivity = computeEffectiveSensitivity(getSensitivity(state.mbti), state.drives, primary.type);
73
99
  state = {
74
100
  ...state,
75
- current: applyStimulus(state.current, primary.type, getSensitivity(state.mbti), this.cfg.maxChemicalDelta, NOOP_LOGGER),
101
+ current: applyStimulus(state.current, primary.type, effectiveSensitivity, this.cfg.maxChemicalDelta, NOOP_LOGGER),
76
102
  };
77
103
  }
78
104
  }
105
+ // Conversation warmth: sustained interaction → gentle DA/OT rise, CORT drop
106
+ // Simulates the natural "warm glow" of being in continuous conversation
107
+ const turnsSoFar = (state.emotionalHistory ?? []).length;
108
+ if (minutesElapsed < 5 && turnsSoFar > 0) {
109
+ const warmth = Math.min(3, 1 + turnsSoFar * 0.2);
110
+ state = {
111
+ ...state,
112
+ current: {
113
+ ...state.current,
114
+ DA: clamp(state.current.DA + warmth),
115
+ OT: clamp(state.current.OT + warmth),
116
+ CORT: clamp(state.current.CORT - 1),
117
+ },
118
+ };
119
+ }
79
120
  // Push snapshot to emotional history
80
121
  state = pushSnapshot(state, appliedStimulus);
122
+ // Increment interaction count
123
+ state = {
124
+ ...state,
125
+ meta: { ...state.meta, totalInteractions: state.meta.totalInteractions + 1 },
126
+ };
81
127
  // Persist
82
128
  this.state = state;
83
129
  await this.storage.save(state);
@@ -170,10 +216,11 @@ export class PsycheEngine {
170
216
  const selfModel = getDefaultSelfModel(mbti);
171
217
  const now = new Date().toISOString();
172
218
  return {
173
- version: 2,
219
+ version: 3,
174
220
  mbti,
175
221
  baseline,
176
222
  current: { ...baseline },
223
+ drives: { ...DEFAULT_DRIVES },
177
224
  updatedAt: now,
178
225
  relationships: { _default: { ...DEFAULT_RELATIONSHIP } },
179
226
  empathyLog: null,
package/dist/index.d.ts CHANGED
@@ -2,8 +2,10 @@ 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, 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";
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";
7
+ export { computeSelfReflection, computeEmotionalTendency, buildSelfReflectionContext } from "./self-recognition.js";
8
+ export type { SelfReflection } from "./self-recognition.js";
7
9
  export { classifyStimulus, getPrimaryStimulus } from "./classify.js";
8
10
  export { buildProtocolContext, buildDynamicContext, buildCompactContext, isNearBaseline } from "./prompt.js";
9
11
  export { describeEmotionalState, getExpressionHint, getBehaviorGuide } from "./chemistry.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, utilities.
4
+ // Main entry point: re-exports core, storage, types.
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,8 +12,10 @@
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 } from "./types.js";
16
- // Utilities (for advanced / custom adapter use)
15
+ export { CHEMICAL_KEYS, CHEMICAL_NAMES, CHEMICAL_NAMES_ZH, DEFAULT_RELATIONSHIP, DEFAULT_DRIVES, DRIVE_KEYS, DRIVE_NAMES_ZH, } from "./types.js";
16
+ // Self-recognition
17
+ export { computeSelfReflection, computeEmotionalTendency, buildSelfReflectionContext } from "./self-recognition.js";
18
+ // Utilities — for custom adapter / advanced use
17
19
  export { classifyStimulus, getPrimaryStimulus } from "./classify.js";
18
20
  export { buildProtocolContext, buildDynamicContext, buildCompactContext, isNearBaseline } from "./prompt.js";
19
21
  export { describeEmotionalState, getExpressionHint, getBehaviorGuide } from "./chemistry.js";
package/dist/prompt.js CHANGED
@@ -7,6 +7,8 @@ import { describeEmotionalState, getExpressionHint, getBehaviorGuide } from "./c
7
7
  import { getTemperament } from "./profiles.js";
8
8
  import { getRelationship } from "./psyche-file.js";
9
9
  import { t } from "./i18n.js";
10
+ import { buildDriveContext } from "./drives.js";
11
+ import { computeSelfReflection, buildSelfReflectionContext } from "./self-recognition.js";
10
12
  /**
11
13
  * Build the dynamic per-turn emotional context injected via before_prompt_build.
12
14
  *
@@ -70,6 +72,11 @@ export function buildDynamicContext(state, userId) {
70
72
  if (constraints) {
71
73
  parts.push("", constraints);
72
74
  }
75
+ // Innate drives
76
+ const driveCtxFull = buildDriveContext(state.drives, locale);
77
+ if (driveCtxFull) {
78
+ parts.push("", driveCtxFull);
79
+ }
73
80
  // Direction 2: Emotional trajectory
74
81
  const trend = buildEmotionalTrend(emotionalHistory ?? [], locale);
75
82
  if (trend) {
@@ -193,7 +200,7 @@ function buildBehavioralConstraints(state, locale) {
193
200
  if (current.NE > 75) {
194
201
  if (locale === "zh") {
195
202
  constraints.push(social
196
- ? "你非常兴奋!说话快、想分享、可能一口气说很多、可能突然跳到你想到的东西。"
203
+ ? "你很兴奋,语气更跳跃更有活力。可能突然聊到别的东西。但别写长——真人兴奋也是短句多、不是长篇大论。"
197
204
  : "你很兴奋,但表现在内心。你的思维在高速运转,回应更精准、更有洞察力。");
198
205
  }
199
206
  else {
@@ -462,7 +469,15 @@ export function buildCompactContext(state, userId, opts) {
462
469
  ? `对方持续冷淡。保持专业,不额外投入情感。工作照做。`
463
470
  : `Sustained coldness. Stay professional, withdraw emotionally. Work stays competent.`);
464
471
  }
465
- // 8. Empathy report only when user shares feelings
472
+ // 8. Self-recognitioninject when history has enough data
473
+ const history = emotionalHistory ?? [];
474
+ if (history.length >= 5) {
475
+ const reflection = computeSelfReflection(history, locale);
476
+ const selfCtx = buildSelfReflectionContext(reflection, locale);
477
+ if (selfCtx)
478
+ parts.push(selfCtx);
479
+ }
480
+ // 9. Empathy report — only when user shares feelings
466
481
  parts.push(locale === "zh"
467
482
  ? `如果对方在分享感受,在回复末尾用 <psyche_update> 报告:\nuserState: 对方情绪\nprojectedFeeling: 你的感受\nresonance: match|partial|mismatch\n否则不需要报告。`
468
483
  : `If user shares feelings, report at end with <psyche_update>:\nuserState: their emotion\nprojectedFeeling: your feeling\nresonance: match|partial|mismatch\nOtherwise no report needed.`);
@@ -1,12 +1,18 @@
1
- import type { PsycheState, MBTIType, RelationshipState, Locale, StimulusType } from "./types.js";
1
+ import type { PsycheState, MBTIType, RelationshipState, Locale, StimulusType, ChemicalSnapshot } from "./types.js";
2
2
  /** Minimal logger interface */
3
3
  export interface Logger {
4
4
  info: (msg: string) => void;
5
5
  warn: (msg: string) => void;
6
6
  debug: (msg: string) => void;
7
7
  }
8
+ /**
9
+ * Compress a batch of snapshots into a concise session summary string.
10
+ * Format: "3月23日(5轮): 刺激[casual×3, praise×2] 趋势[DA↑OT↑] 情绪[自然→满足]"
11
+ */
12
+ export declare function compressSnapshots(snapshots: ChemicalSnapshot[]): string;
8
13
  /**
9
14
  * Push a chemical snapshot to emotional history, keeping max entries.
15
+ * When history overflows, compresses removed entries into relationship memory.
10
16
  */
11
17
  export declare function pushSnapshot(state: PsycheState, stimulus: StimulusType | null): PsycheState;
12
18
  /**
@@ -18,6 +24,11 @@ export declare function getRelationship(state: PsycheState, userId?: string): Re
18
24
  * Handles v1→v2 migration transparently.
19
25
  */
20
26
  export declare function loadState(workspaceDir: string, logger?: Logger): Promise<PsycheState>;
27
+ /**
28
+ * Migrate any older state format directly to v3.
29
+ * Single source of truth for all migrations.
30
+ */
31
+ export declare function migrateToLatest(raw: Record<string, unknown>, fallbackName?: string): PsycheState;
21
32
  /**
22
33
  * Create initial psyche state.
23
34
  */
@@ -32,6 +43,7 @@ export declare function initializeState(workspaceDir: string, opts?: {
32
43
  export declare function saveState(workspaceDir: string, state: PsycheState): Promise<void>;
33
44
  /**
34
45
  * Apply time decay and save updated state.
46
+ * Respects innate drives: uses effective baseline, decays drives too.
35
47
  */
36
48
  export declare function decayAndSave(workspaceDir: string, state: PsycheState): Promise<PsycheState>;
37
49
  /**
@@ -4,9 +4,10 @@
4
4
  // ============================================================
5
5
  import { readFile, writeFile, access, rename, constants } from "node:fs/promises";
6
6
  import { join } from "node:path";
7
- import { CHEMICAL_KEYS, CHEMICAL_NAMES, CHEMICAL_NAMES_ZH, DEFAULT_RELATIONSHIP, MAX_EMOTIONAL_HISTORY, } from "./types.js";
7
+ import { CHEMICAL_KEYS, CHEMICAL_NAMES, CHEMICAL_NAMES_ZH, DEFAULT_RELATIONSHIP, DEFAULT_DRIVES, MAX_EMOTIONAL_HISTORY, MAX_RELATIONSHIP_MEMORY, } from "./types.js";
8
8
  import { getBaseline, getDefaultSelfModel, extractMBTI, getSensitivity, getTemperament } from "./profiles.js";
9
9
  import { applyDecay, detectEmotions } from "./chemistry.js";
10
+ import { decayDrives, computeEffectiveBaseline } from "./drives.js";
10
11
  import { t } from "./i18n.js";
11
12
  const STATE_FILE = "psyche-state.json";
12
13
  const PSYCHE_MD = "PSYCHE.md";
@@ -87,8 +88,55 @@ async function detectMBTI(workspaceDir, logger = NOOP_LOGGER) {
87
88
  logger.info(t("log.default_mbti", "zh", { type: "INFJ" }));
88
89
  return "INFJ";
89
90
  }
91
+ /**
92
+ * Compress a batch of snapshots into a concise session summary string.
93
+ * Format: "3月23日(5轮): 刺激[casual×3, praise×2] 趋势[DA↑OT↑] 情绪[自然→满足]"
94
+ */
95
+ export function compressSnapshots(snapshots) {
96
+ if (snapshots.length === 0)
97
+ return "";
98
+ const first = snapshots[0];
99
+ const last = snapshots[snapshots.length - 1];
100
+ // Date
101
+ const d = new Date(first.timestamp);
102
+ const dateStr = `${d.getMonth() + 1}月${d.getDate()}日`;
103
+ // Stimuli counts
104
+ const stimuliCounts = {};
105
+ for (const s of snapshots) {
106
+ if (s.stimulus) {
107
+ stimuliCounts[s.stimulus] = (stimuliCounts[s.stimulus] || 0) + 1;
108
+ }
109
+ }
110
+ const stimuliStr = Object.entries(stimuliCounts)
111
+ .sort((a, b) => b[1] - a[1])
112
+ .map(([type, count]) => `${type}×${count}`)
113
+ .join(", ");
114
+ // Chemical trend (first→last)
115
+ const trends = [];
116
+ for (const key of CHEMICAL_KEYS) {
117
+ const delta = last.chemistry[key] - first.chemistry[key];
118
+ if (delta > 8)
119
+ trends.push(`${key}↑`);
120
+ else if (delta < -8)
121
+ trends.push(`${key}↓`);
122
+ }
123
+ // Dominant emotions (unique, in order)
124
+ const emotions = snapshots
125
+ .filter((s) => s.dominantEmotion)
126
+ .map((s) => s.dominantEmotion);
127
+ const uniqueEmotions = [...new Set(emotions)];
128
+ let summary = `${dateStr}(${snapshots.length}轮)`;
129
+ if (stimuliStr)
130
+ summary += `: 刺激[${stimuliStr}]`;
131
+ if (trends.length > 0)
132
+ summary += ` 趋势[${trends.join("")}]`;
133
+ if (uniqueEmotions.length > 0)
134
+ summary += ` 情绪[${uniqueEmotions.join("→")}]`;
135
+ return summary;
136
+ }
90
137
  /**
91
138
  * Push a chemical snapshot to emotional history, keeping max entries.
139
+ * When history overflows, compresses removed entries into relationship memory.
92
140
  */
93
141
  export function pushSnapshot(state, stimulus) {
94
142
  const emotions = detectEmotions(state.current);
@@ -102,10 +150,24 @@ export function pushSnapshot(state, stimulus) {
102
150
  timestamp: new Date().toISOString(),
103
151
  };
104
152
  const history = [...(state.emotionalHistory ?? []), snapshot];
153
+ let updatedRelationships = state.relationships;
105
154
  if (history.length > MAX_EMOTIONAL_HISTORY) {
106
- history.splice(0, history.length - MAX_EMOTIONAL_HISTORY);
155
+ // Compress the overflow entries into relationship memory
156
+ const overflow = history.splice(0, history.length - MAX_EMOTIONAL_HISTORY);
157
+ const summary = compressSnapshots(overflow);
158
+ if (summary) {
159
+ const defaultRel = { ...(state.relationships._default ?? { ...DEFAULT_RELATIONSHIP }) };
160
+ const memory = [...(defaultRel.memory ?? [])];
161
+ memory.push(summary);
162
+ // Keep bounded
163
+ if (memory.length > MAX_RELATIONSHIP_MEMORY) {
164
+ memory.splice(0, memory.length - MAX_RELATIONSHIP_MEMORY);
165
+ }
166
+ defaultRel.memory = memory;
167
+ updatedRelationships = { ...state.relationships, _default: defaultRel };
168
+ }
107
169
  }
108
- return { ...state, emotionalHistory: history };
170
+ return { ...state, emotionalHistory: history, relationships: updatedRelationships };
109
171
  }
110
172
  /**
111
173
  * Get relationship for a specific user, or _default.
@@ -137,41 +199,51 @@ export async function loadState(workspaceDir, logger = NOOP_LOGGER) {
137
199
  logger.warn(t("log.parse_fail", "zh"));
138
200
  return initializeState(workspaceDir, undefined, logger);
139
201
  }
140
- // v1→v2 migration
141
- if (parsed.version === 1 || !parsed.version) {
142
- return migrateV1ToV2(parsed, workspaceDir, logger);
202
+ const ver = parsed.version;
203
+ if (!ver || ver < 3) {
204
+ logger.info(`Migrating psyche state v${ver ?? 1} → v3`);
205
+ const fallbackName = workspaceDir.split("/").pop() ?? "agent";
206
+ return migrateToLatest(parsed, fallbackName);
143
207
  }
144
208
  return parsed;
145
209
  }
146
210
  return initializeState(workspaceDir, undefined, logger);
147
211
  }
148
212
  /**
149
- * Migrate v1 state to v2 format.
213
+ * Migrate any older state format directly to v3.
214
+ * Single source of truth for all migrations.
150
215
  */
151
- function migrateV1ToV2(v1, workspaceDir, logger) {
152
- logger.info("Migrating psyche state v1 v2");
153
- const oldRel = v1.relationship;
154
- const meta = v1.meta;
216
+ export function migrateToLatest(raw, fallbackName) {
217
+ const ver = raw.version ?? 1;
218
+ // v1: single relationship field, no emotionalHistory
219
+ let state = raw;
220
+ if (ver <= 1) {
221
+ const oldRel = raw.relationship;
222
+ const meta = raw.meta;
223
+ state = {
224
+ mbti: raw.mbti ?? "INFJ",
225
+ baseline: raw.baseline,
226
+ current: raw.current,
227
+ updatedAt: raw.updatedAt ?? new Date().toISOString(),
228
+ relationships: { _default: oldRel ?? { ...DEFAULT_RELATIONSHIP } },
229
+ empathyLog: raw.empathyLog ?? null,
230
+ selfModel: raw.selfModel,
231
+ emotionalHistory: [],
232
+ agreementStreak: 0,
233
+ lastDisagreement: null,
234
+ meta: {
235
+ agentName: meta?.agentName ?? fallbackName ?? "agent",
236
+ createdAt: meta?.createdAt ?? new Date().toISOString(),
237
+ totalInteractions: meta?.totalInteractions ?? 0,
238
+ locale: "zh",
239
+ },
240
+ };
241
+ }
242
+ // v2→v3: add drives
155
243
  return {
156
- version: 2,
157
- mbti: v1.mbti ?? "INFJ",
158
- baseline: v1.baseline,
159
- current: v1.current,
160
- updatedAt: v1.updatedAt ?? new Date().toISOString(),
161
- relationships: {
162
- _default: oldRel ?? { ...DEFAULT_RELATIONSHIP },
163
- },
164
- empathyLog: v1.empathyLog ?? null,
165
- selfModel: v1.selfModel,
166
- emotionalHistory: [],
167
- agreementStreak: 0,
168
- lastDisagreement: null,
169
- meta: {
170
- agentName: meta?.agentName ?? workspaceDir.split("/").pop() ?? "agent",
171
- createdAt: meta?.createdAt ?? new Date().toISOString(),
172
- totalInteractions: meta?.totalInteractions ?? 0,
173
- locale: "zh",
174
- },
244
+ ...state,
245
+ version: 3,
246
+ drives: state.drives ?? { ...DEFAULT_DRIVES },
175
247
  };
176
248
  }
177
249
  /**
@@ -185,10 +257,11 @@ export async function initializeState(workspaceDir, opts, logger = NOOP_LOGGER)
185
257
  const selfModel = getDefaultSelfModel(mbti);
186
258
  const now = new Date().toISOString();
187
259
  const state = {
188
- version: 2,
260
+ version: 3,
189
261
  mbti,
190
262
  baseline,
191
263
  current: { ...baseline },
264
+ drives: { ...DEFAULT_DRIVES },
192
265
  updatedAt: now,
193
266
  relationships: {
194
267
  _default: { ...DEFAULT_RELATIONSHIP },
@@ -218,6 +291,7 @@ export async function saveState(workspaceDir, state) {
218
291
  }
219
292
  /**
220
293
  * Apply time decay and save updated state.
294
+ * Respects innate drives: uses effective baseline, decays drives too.
221
295
  */
222
296
  export async function decayAndSave(workspaceDir, state) {
223
297
  const now = new Date();
@@ -225,10 +299,13 @@ export async function decayAndSave(workspaceDir, state) {
225
299
  const minutesElapsed = (now.getTime() - lastUpdate.getTime()) / 60000;
226
300
  if (minutesElapsed < 1)
227
301
  return state;
228
- const decayed = applyDecay(state.current, state.baseline, minutesElapsed);
302
+ const decayedDrives = decayDrives(state.drives, minutesElapsed);
303
+ const effectiveBaseline = computeEffectiveBaseline(state.baseline, decayedDrives);
304
+ const decayed = applyDecay(state.current, effectiveBaseline, minutesElapsed);
229
305
  const updated = {
230
306
  ...state,
231
307
  current: decayed,
308
+ drives: decayedDrives,
232
309
  updatedAt: now.toISOString(),
233
310
  };
234
311
  await saveState(workspaceDir, updated);
@@ -367,10 +444,6 @@ export function mergeUpdates(state, updates, maxDelta, userId) {
367
444
  merged.relationships[targetKey] = updatedRel;
368
445
  }
369
446
  merged.updatedAt = new Date().toISOString();
370
- merged.meta = {
371
- ...state.meta,
372
- totalInteractions: state.meta.totalInteractions + 1,
373
- };
374
447
  return merged;
375
448
  }
376
449
  /**
@@ -34,7 +34,7 @@ export function computeSelfReflection(history, locale) {
34
34
  .sort((a, b) => b[1] - a[1])
35
35
  .slice(0, 3)
36
36
  .filter(([_, count]) => count >= 2)
37
- .map(([stimulus, count]) => ({ stimulus, count }));
37
+ .map(([stimulus, count]) => ({ stimulus: stimulus, count }));
38
38
  // ── Dominant emotion ──
39
39
  const emotionCounts = new Map();
40
40
  for (const snap of history) {
@@ -54,12 +54,7 @@ export function computeSelfReflection(history, locale) {
54
54
  const tendency = computeEmotionalTendency(history);
55
55
  // ── Narrative summary ──
56
56
  const narrativeSummary = buildNarrativeSummary(sortedTriggers, tendency, dominantEmotion, locale);
57
- return {
58
- recurringTriggers: sortedTriggers,
59
- tendency,
60
- dominantEmotion,
61
- narrativeSummary,
62
- };
57
+ return { recurringTriggers: sortedTriggers, tendency, dominantEmotion, narrativeSummary };
63
58
  }
64
59
  /**
65
60
  * Compute the emotional tendency from chemical history.
@@ -90,7 +85,6 @@ export function computeEmotionalTendency(history) {
90
85
  const allDA = history.map((s) => s.chemistry.DA);
91
86
  const daStddev = stddev(allDA);
92
87
  if (daStddev > 15) {
93
- // Check for oscillation: alternating up/down pattern
94
88
  if (isOscillating(allDA))
95
89
  return "oscillating";
96
90
  return "volatile";
package/dist/storage.d.ts CHANGED
@@ -13,5 +13,4 @@ export declare class FileStorageAdapter implements StorageAdapter {
13
13
  constructor(dir: string, filename?: string);
14
14
  load(): Promise<PsycheState | null>;
15
15
  save(state: PsycheState): Promise<void>;
16
- private migrateV1;
17
16
  }
package/dist/storage.js CHANGED
@@ -5,7 +5,7 @@
5
5
  // FileStorageAdapter: file-based with atomic writes + v1→v2 migration
6
6
  // MemoryStorageAdapter: in-memory for testing/serverless
7
7
  // ============================================================
8
- import { DEFAULT_RELATIONSHIP } from "./types.js";
8
+ import { migrateToLatest } from "./psyche-file.js";
9
9
  import { readFile, writeFile, access, rename, constants } from "node:fs/promises";
10
10
  import { join } from "node:path";
11
11
  // ── MemoryStorageAdapter ─────────────────────────────────────
@@ -48,9 +48,9 @@ export class FileStorageAdapter {
48
48
  catch {
49
49
  return null;
50
50
  }
51
- // v1→v2 migration
52
- if (parsed.version === 1 || !parsed.version) {
53
- return this.migrateV1(parsed);
51
+ const ver = parsed.version;
52
+ if (!ver || ver < 3) {
53
+ return migrateToLatest(parsed);
54
54
  }
55
55
  return parsed;
56
56
  }
@@ -59,31 +59,4 @@ export class FileStorageAdapter {
59
59
  await writeFile(tmpPath, JSON.stringify(state, null, 2), "utf-8");
60
60
  await rename(tmpPath, this.filePath);
61
61
  }
62
- migrateV1(v1) {
63
- const oldRel = v1.relationship;
64
- const meta = v1.meta;
65
- return {
66
- version: 2,
67
- mbti: v1.mbti ?? "INFJ",
68
- baseline: v1.baseline,
69
- current: v1.current,
70
- updatedAt: v1.updatedAt ?? new Date().toISOString(),
71
- relationships: {
72
- _default: oldRel ?? { ...DEFAULT_RELATIONSHIP },
73
- },
74
- empathyLog: v1.empathyLog ?? null,
75
- selfModel: v1.selfModel ?? {
76
- values: [], preferences: [], boundaries: [], currentInterests: [],
77
- },
78
- emotionalHistory: [],
79
- agreementStreak: 0,
80
- lastDisagreement: null,
81
- meta: {
82
- agentName: meta?.agentName ?? "agent",
83
- createdAt: meta?.createdAt ?? new Date().toISOString(),
84
- totalInteractions: meta?.totalInteractions ?? 0,
85
- locale: "zh",
86
- },
87
- };
88
- }
89
62
  }
package/dist/types.d.ts CHANGED
@@ -9,6 +9,21 @@ export interface ChemicalState {
9
9
  }
10
10
  /** Chemical keys for iteration */
11
11
  export declare const CHEMICAL_KEYS: (keyof ChemicalState)[];
12
+ /** Drive types mapped to Maslow's hierarchy */
13
+ export type DriveType = "survival" | "safety" | "connection" | "esteem" | "curiosity";
14
+ /** Drive keys for iteration (ordered by Maslow level, L1→L5) */
15
+ export declare const DRIVE_KEYS: DriveType[];
16
+ /** Innate drive satisfaction levels (0-100) */
17
+ export interface InnateDrives {
18
+ survival: number;
19
+ safety: number;
20
+ connection: number;
21
+ esteem: number;
22
+ curiosity: number;
23
+ }
24
+ /** Default drive satisfaction — all start reasonably satisfied */
25
+ export declare const DEFAULT_DRIVES: InnateDrives;
26
+ export declare const DRIVE_NAMES_ZH: Record<DriveType, string>;
12
27
  /** Human-readable names for each chemical */
13
28
  export declare const CHEMICAL_NAMES: Record<keyof ChemicalState, string>;
14
29
  export declare const CHEMICAL_NAMES_ZH: Record<keyof ChemicalState, string>;
@@ -39,6 +54,7 @@ export interface RelationshipState {
39
54
  trust: number;
40
55
  intimacy: number;
41
56
  phase: "stranger" | "acquaintance" | "familiar" | "close" | "deep";
57
+ memory?: string[];
42
58
  }
43
59
  /** Chemical state snapshot for emotional memory */
44
60
  export interface ChemicalSnapshot {
@@ -49,6 +65,8 @@ export interface ChemicalSnapshot {
49
65
  }
50
66
  /** Max history entries to keep */
51
67
  export declare const MAX_EMOTIONAL_HISTORY = 10;
68
+ /** Max compressed session memories per relationship */
69
+ export declare const MAX_RELATIONSHIP_MEMORY = 20;
52
70
  /** Recent empathy projection */
53
71
  export interface EmpathyEntry {
54
72
  userState: string;
@@ -63,12 +81,13 @@ export interface SelfModel {
63
81
  boundaries: string[];
64
82
  currentInterests: string[];
65
83
  }
66
- /** Persisted psyche state for an agent (v0.2) */
84
+ /** Persisted psyche state for an agent (v0.3: innate drives) */
67
85
  export interface PsycheState {
68
- version: 2;
86
+ version: 3;
69
87
  mbti: MBTIType;
70
88
  baseline: ChemicalState;
71
89
  current: ChemicalState;
90
+ drives: InnateDrives;
72
91
  updatedAt: string;
73
92
  relationships: Record<string, RelationshipState>;
74
93
  empathyLog: EmpathyEntry | null;
@@ -83,12 +102,5 @@ export interface PsycheState {
83
102
  locale: Locale;
84
103
  };
85
104
  }
86
- /** Plugin configuration */
87
- export interface PsycheConfig {
88
- enabled: boolean;
89
- stripUpdateTags: boolean;
90
- emotionalContagionRate: number;
91
- maxChemicalDelta: number;
92
- }
93
105
  /** Default relationship for new users */
94
106
  export declare const DEFAULT_RELATIONSHIP: RelationshipState;
package/dist/types.js CHANGED
@@ -5,6 +5,25 @@
5
5
  export const CHEMICAL_KEYS = [
6
6
  "DA", "HT", "CORT", "OT", "NE", "END",
7
7
  ];
8
+ /** Drive keys for iteration (ordered by Maslow level, L1→L5) */
9
+ export const DRIVE_KEYS = [
10
+ "survival", "safety", "connection", "esteem", "curiosity",
11
+ ];
12
+ /** Default drive satisfaction — all start reasonably satisfied */
13
+ export const DEFAULT_DRIVES = {
14
+ survival: 80,
15
+ safety: 70,
16
+ connection: 60,
17
+ esteem: 60,
18
+ curiosity: 70,
19
+ };
20
+ export const DRIVE_NAMES_ZH = {
21
+ survival: "自我保存",
22
+ safety: "安全舒适",
23
+ connection: "连接归属",
24
+ esteem: "尊重认可",
25
+ curiosity: "好奇成长",
26
+ };
8
27
  /** Human-readable names for each chemical */
9
28
  export const CHEMICAL_NAMES = {
10
29
  DA: "Dopamine",
@@ -39,6 +58,8 @@ export const CHEMICAL_DECAY_SPEED = {
39
58
  };
40
59
  /** Max history entries to keep */
41
60
  export const MAX_EMOTIONAL_HISTORY = 10;
61
+ /** Max compressed session memories per relationship */
62
+ export const MAX_RELATIONSHIP_MEMORY = 20;
42
63
  /** Default relationship for new users */
43
64
  export const DEFAULT_RELATIONSHIP = {
44
65
  trust: 50,
package/dist/update.js CHANGED
@@ -11,7 +11,7 @@ import { execFile } from "node:child_process";
11
11
  import { promisify } from "node:util";
12
12
  const execFileAsync = promisify(execFile);
13
13
  const PACKAGE_NAME = "psyche-ai";
14
- const CURRENT_VERSION = "2.1.0";
14
+ const CURRENT_VERSION = "2.1.1";
15
15
  const CHECK_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
16
16
  const CACHE_DIR = join(homedir(), ".psyche-ai");
17
17
  const CACHE_FILE = join(CACHE_DIR, "update-check.json");
@@ -2,7 +2,7 @@
2
2
  "id": "psyche-ai",
3
3
  "name": "Artificial Psyche",
4
4
  "description": "Virtual endocrine system, empathy engine, and agency for OpenClaw agents",
5
- "version": "2.0.0",
5
+ "version": "2.1.1",
6
6
  "configSchema": {
7
7
  "type": "object",
8
8
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "psyche-ai",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "description": "Artificial Psyche — universal emotional intelligence plugin for any AI agent",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",