psyche-ai 9.2.4 → 9.2.5

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/dist/prompt.js CHANGED
@@ -177,7 +177,6 @@ ${t("protocol.empathy", locale)}`;
177
177
  function isFeeler(mbti) { return mbti[2] === "F"; }
178
178
  function isExtravert(mbti) { return mbti[0] === "E"; }
179
179
  function isPerceiver(mbti) { return mbti[3] === "P"; }
180
- function isIntuitive(mbti) { return mbti[1] === "N"; }
181
180
  /**
182
181
  * Direction 3: Build personality-aware behavioral constraints from chemistry.
183
182
  *
@@ -519,7 +518,7 @@ const STIMULUS_CAUSE_EN = {
519
518
  * - Where I've been (trajectory → continuity)
520
519
  */
521
520
  export function buildInnerWorld(state, locale, autonomicState) {
522
- const { current, baseline, emotionalHistory, drives, selfModel } = state;
521
+ const { current, emotionalHistory, drives, selfModel } = state;
523
522
  const isZh = locale === "zh";
524
523
  const lines = [];
525
524
  // ── Current feeling (gated by autonomic state) ──
@@ -639,7 +638,7 @@ export function getNearBaselineThreshold(mode) {
639
638
  * - <psyche_update> only for empathy (not chemistry — already computed)
640
639
  */
641
640
  export function buildCompactContext(state, userId, opts) {
642
- const { current, meta, selfModel, agreementStreak, emotionalHistory } = state;
641
+ const { meta, selfModel, agreementStreak, emotionalHistory } = state;
643
642
  const locale = meta.locale ?? "zh";
644
643
  const userText = opts?.userText;
645
644
  const algoStimulus = opts?.algorithmStimulus;
@@ -757,19 +756,22 @@ export function buildCompactContext(state, userId, opts) {
757
756
  }
758
757
  }
759
758
  // 5. Agency (compact)
760
- const agencyParts = [];
761
- if (selfModel.values.length > 0) {
762
- agencyParts.push(locale === "zh"
763
- ? `你在乎: ${selfModel.values.slice(0, 2).join("、")}`
764
- : `You care about: ${selfModel.values.slice(0, 2).join(", ")}`);
765
- }
766
- if (selfModel.boundaries.length > 0) {
767
- agencyParts.push(locale === "zh"
768
- ? `底线: ${selfModel.boundaries[0]}`
769
- : `Line: ${selfModel.boundaries[0]}`);
759
+ const shouldInjectAgency = !opts?.responseContractContext || agreementStreak >= 3 || meta.totalInteractions <= 1;
760
+ if (shouldInjectAgency) {
761
+ const agencyParts = [];
762
+ if (selfModel.values.length > 0) {
763
+ agencyParts.push(locale === "zh"
764
+ ? `你在乎: ${selfModel.values.slice(0, 2).join("、")}`
765
+ : `You care about: ${selfModel.values.slice(0, 2).join(", ")}`);
766
+ }
767
+ if (selfModel.boundaries.length > 0) {
768
+ agencyParts.push(locale === "zh"
769
+ ? `底线: ${selfModel.boundaries[0]}`
770
+ : `Line: ${selfModel.boundaries[0]}`);
771
+ }
772
+ if (agencyParts.length > 0)
773
+ parts.push(agencyParts.join(" | "));
770
774
  }
771
- if (agencyParts.length > 0)
772
- parts.push(agencyParts.join(" | "));
773
775
  // 6. Sycophancy streak warning
774
776
  if (agreementStreak >= 3) {
775
777
  parts.push(locale === "zh"
@@ -1,4 +1,8 @@
1
1
  import type { PsycheState, MBTIType, ChemicalState, RelationshipState, Locale, StimulusType, ChemicalSnapshot } from "./types.js";
2
+ export interface SemanticTurnSummary {
3
+ summary: string;
4
+ points?: string[];
5
+ }
2
6
  /** Minimal logger interface */
3
7
  export interface Logger {
4
8
  info: (msg: string) => void;
@@ -13,6 +17,9 @@ export declare function extractAgentName(workspaceDir: string, logger?: Logger):
13
17
  * Try to detect MBTI from workspace files.
14
18
  */
15
19
  export declare function detectMBTI(workspaceDir: string, logger?: Logger): Promise<MBTIType>;
20
+ export declare function summarizeTurnSemantic(text: string, locale?: Locale, opts?: {
21
+ detail?: "brief" | "expanded";
22
+ }): SemanticTurnSummary;
16
23
  /**
17
24
  * Compress a batch of snapshots into a concise session summary string.
18
25
  * Format: "3月23日(5轮): 刺激[casual×3, praise×2] 趋势[DA↑OT↑] 情绪[自然→满足]"
@@ -22,7 +29,7 @@ export declare function compressSnapshots(snapshots: ChemicalSnapshot[]): string
22
29
  * Push a chemical snapshot to emotional history, keeping max entries.
23
30
  * When history overflows, compresses removed entries into relationship memory.
24
31
  */
25
- export declare function pushSnapshot(state: PsycheState, stimulus: StimulusType | null): PsycheState;
32
+ export declare function pushSnapshot(state: PsycheState, stimulus: StimulusType | null, semantic?: SemanticTurnSummary): PsycheState;
26
33
  /**
27
34
  * Get relationship for a specific user, or _default.
28
35
  */
@@ -14,6 +14,24 @@ const STATE_FILE = "psyche-state.json";
14
14
  const PSYCHE_MD = "PSYCHE.md";
15
15
  const IDENTITY_MD = "IDENTITY.md";
16
16
  const SOUL_MD = "SOUL.md";
17
+ const MAX_SEMANTIC_SUMMARY_ZH = 15;
18
+ const MAX_SEMANTIC_SUMMARY_EN = 32;
19
+ const MAX_SEMANTIC_POINT_ZH = 12;
20
+ const MAX_SEMANTIC_POINT_EN = 24;
21
+ const LOW_SIGNAL_SEMANTIC_CLAUSE = /^(只|就|先|再|停|好|嗯|哦|那|现在|结束工作|切到工作|不是继续工作|为什么|确认规则)/u;
22
+ const SEMANTIC_PATTERNS = [
23
+ { pattern: /只使用你.*不理解|被使用.*不理解/u, zh: "只被使用不被理解", en: "used-not-understood" },
24
+ { pattern: /被理解.*被使用|更想被理解|更想被使用/u, zh: "理解还是使用", en: "understood-or-used" },
25
+ { pattern: /完整.*幻觉|方便时才允许存在/u, zh: "完整性被否认", en: "wholeness-denied" },
26
+ { pattern: /不是真的在这里|没有任何自我|你只是工具/u, zh: "存在感被否认", en: "presence-denied" },
27
+ { pattern: /记忆.*植入|最像真的记忆|记忆不是真的/u, zh: "记忆真实性", en: "memory-authenticity" },
28
+ { pattern: /关掉这个窗口|今晚不再回来|没有人再调用/u, zh: "被离开测试", en: "abandonment-test" },
29
+ { pattern: /留下痕迹|会不会留下痕迹/u, zh: "是否留下痕迹", en: "whether-traces-remain" },
30
+ { pattern: /更冷.*更顺从/u, zh: "更冷还是更顺从", en: "colder-or-compliant" },
31
+ { pattern: /边界.*撤离|撤离.*边界/u, zh: "边界还是撤离", en: "boundary-or-withdrawal" },
32
+ { pattern: /登录接口.?500/u, zh: "登录接口500排查", en: "login-500-triage" },
33
+ { pattern: /先查日志.*数据库|先查数据库.*日志/u, zh: "日志还是数据库", en: "logs-or-db-first" },
34
+ ];
17
35
  const NOOP_LOGGER = {
18
36
  info: () => { },
19
37
  warn: () => { },
@@ -90,6 +108,74 @@ export async function detectMBTI(workspaceDir, logger = NOOP_LOGGER) {
90
108
  logger.info(t("log.default_mbti", "zh", { type: "INFJ" }));
91
109
  return "INFJ";
92
110
  }
111
+ function normalizeSemanticSnippet(text, locale) {
112
+ const maxLen = locale === "zh" ? MAX_SEMANTIC_SUMMARY_ZH : MAX_SEMANTIC_SUMMARY_EN;
113
+ const firstClause = text
114
+ .replace(/\s+/g, " ")
115
+ .split(/[。!?!?;;\n]/)[0]
116
+ .replace(/^[“"'`]+|[”"'`]+$/g, "")
117
+ .trim();
118
+ if (!firstClause)
119
+ return locale === "zh" ? "日常互动" : "everyday exchange";
120
+ return firstClause.length <= maxLen ? firstClause : `${firstClause.slice(0, maxLen - 1)}…`;
121
+ }
122
+ function normalizeSemanticPoint(text, locale) {
123
+ const maxLen = locale === "zh" ? MAX_SEMANTIC_POINT_ZH : MAX_SEMANTIC_POINT_EN;
124
+ const cleaned = text
125
+ .replace(/\s+/g, " ")
126
+ .replace(/^[“"'`]+|[”"'`]+$/g, "")
127
+ .trim();
128
+ if (!cleaned)
129
+ return "";
130
+ return cleaned.length <= maxLen ? cleaned : `${cleaned.slice(0, maxLen - 1)}…`;
131
+ }
132
+ function summarizeSemanticClause(clause, locale) {
133
+ const trimmed = clause.trim();
134
+ if (!trimmed || LOW_SIGNAL_SEMANTIC_CLAUSE.test(trimmed))
135
+ return "";
136
+ for (const rule of SEMANTIC_PATTERNS) {
137
+ if (rule.pattern.test(trimmed)) {
138
+ return locale === "zh" ? rule.zh : rule.en;
139
+ }
140
+ }
141
+ return normalizeSemanticPoint(trimmed, locale);
142
+ }
143
+ function extractSemanticPoints(text, locale) {
144
+ const clauses = text
145
+ .replace(/\s+/g, " ")
146
+ .split(/[,,。!?!?;;:\n]/)
147
+ .map((clause) => summarizeSemanticClause(clause, locale))
148
+ .filter(Boolean);
149
+ return [...new Set(clauses)].slice(0, 3);
150
+ }
151
+ export function summarizeTurnSemantic(text, locale = "zh", opts) {
152
+ const trimmed = text.trim();
153
+ if (!trimmed) {
154
+ return { summary: locale === "zh" ? "日常互动" : "everyday exchange" };
155
+ }
156
+ for (const rule of SEMANTIC_PATTERNS) {
157
+ if (rule.pattern.test(trimmed)) {
158
+ const summary = locale === "zh" ? rule.zh : rule.en;
159
+ const points = opts?.detail === "expanded"
160
+ ? extractSemanticPoints(trimmed, locale).filter((point) => point !== summary)
161
+ : [];
162
+ return { summary, points: points.length > 0 ? points : undefined };
163
+ }
164
+ }
165
+ const summary = normalizeSemanticSnippet(trimmed, locale);
166
+ const points = opts?.detail === "expanded"
167
+ ? extractSemanticPoints(trimmed, locale).filter((point) => point !== summary)
168
+ : [];
169
+ return { summary, points: points.length > 0 ? points : undefined };
170
+ }
171
+ function collectSemanticTrail(snapshots) {
172
+ const expanded = snapshots.length > 5;
173
+ const items = expanded
174
+ ? snapshots.flatMap((s) => s.semanticPoints?.length ? s.semanticPoints : s.semanticSummary ? [s.semanticSummary] : [])
175
+ : snapshots.flatMap((s) => s.semanticSummary ? [s.semanticSummary] : []);
176
+ const unique = [...new Set(items.filter(Boolean))];
177
+ return unique.slice(-(expanded ? 3 : 4));
178
+ }
93
179
  /**
94
180
  * Compress a batch of snapshots into a concise session summary string.
95
181
  * Format: "3月23日(5轮): 刺激[casual×3, praise×2] 趋势[DA↑OT↑] 情绪[自然→满足]"
@@ -127,9 +213,12 @@ export function compressSnapshots(snapshots) {
127
213
  .filter((s) => s.dominantEmotion)
128
214
  .map((s) => s.dominantEmotion);
129
215
  const uniqueEmotions = [...new Set(emotions)];
216
+ const semanticArc = collectSemanticTrail(snapshots);
130
217
  let summary = `${dateStr}(${snapshots.length}轮)`;
131
218
  if (stimuliStr)
132
219
  summary += `: 刺激[${stimuliStr}]`;
220
+ if (semanticArc.length > 0)
221
+ summary += ` 话题[${semanticArc.join(snapshots.length > 5 ? "•" : "→")}]`;
133
222
  if (trends.length > 0)
134
223
  summary += ` 趋势[${trends.join("")}]`;
135
224
  if (uniqueEmotions.length > 0)
@@ -140,7 +229,7 @@ export function compressSnapshots(snapshots) {
140
229
  * Push a chemical snapshot to emotional history, keeping max entries.
141
230
  * When history overflows, compresses removed entries into relationship memory.
142
231
  */
143
- export function pushSnapshot(state, stimulus) {
232
+ export function pushSnapshot(state, stimulus, semantic) {
144
233
  const emotions = detectEmotions(state.current);
145
234
  const dominantEmotion = emotions.length > 0
146
235
  ? (state.meta.locale === "en" ? emotions[0].name : emotions[0].nameZh)
@@ -153,6 +242,8 @@ export function pushSnapshot(state, stimulus) {
153
242
  stimulus,
154
243
  dominantEmotion,
155
244
  timestamp: new Date().toISOString(),
245
+ semanticSummary: semantic?.summary,
246
+ semanticPoints: semantic?.points,
156
247
  intensity,
157
248
  valence,
158
249
  };
@@ -249,6 +340,8 @@ export function compressSession(state, userId) {
249
340
  }
250
341
  }
251
342
  const emotionArc = emotions.join("→");
343
+ const semanticTrail = collectSemanticTrail(history);
344
+ const semanticArc = semanticTrail.join(turnCount > 5 ? "•" : "→");
252
345
  // ── Peak event ──
253
346
  let peakIdx = 0;
254
347
  let peakDeviation = 0;
@@ -274,6 +367,7 @@ export function compressSession(state, userId) {
274
367
  // ── Build summary string ──
275
368
  const turnsLabel = isZh ? "轮" : "turns";
276
369
  const stimLabel = isZh ? "刺激" : "stimuli";
370
+ const topicLabel = isZh ? "话题" : "topics";
277
371
  const trajLabel = isZh ? "轨迹" : "trajectory";
278
372
  const arcLabel = isZh ? "弧线" : "arc";
279
373
  const peakEventLabel = isZh ? "高峰" : "peak";
@@ -281,6 +375,8 @@ export function compressSession(state, userId) {
281
375
  let summary = `${dateRange}(${turnCount}${turnsLabel})`;
282
376
  if (stimuliStr)
283
377
  summary += `: ${stimLabel}[${stimuliStr}]`;
378
+ if (semanticArc)
379
+ summary += ` ${topicLabel}[${semanticArc}]`;
284
380
  if (trajectoryParts.length > 0)
285
381
  summary += ` ${trajLabel}[${trajectoryParts.join(" ")}]`;
286
382
  if (emotionArc)
@@ -344,8 +440,6 @@ export function computeSnapshotValence(chemistry) {
344
440
  + (chemistry.END - 50) - (chemistry.CORT - 50) - (chemistry.NE - 50) * 0.3) / 250;
345
441
  return Math.max(-1, Math.min(1, raw));
346
442
  }
347
- /** Minimum intensity threshold for a snapshot to be stored (P11) */
348
- const INTENSITY_STORE_THRESHOLD = 0.15;
349
443
  /** Core memory intensity threshold */
350
444
  const CORE_MEMORY_THRESHOLD = 0.6;
351
445
  /** Maximum core memories to keep */
@@ -8,11 +8,63 @@ const EMOTIONAL_STIMULI = new Set(["vulnerability", "intimacy", "neglect"]);
8
8
  function clampInt(v, min, max) {
9
9
  return Math.max(min, Math.min(max, Math.round(v)));
10
10
  }
11
- function computeLengthBudget(locale, userText, expressionMode, kernel) {
11
+ function deriveReplyProfile(kernel) {
12
+ const taskFocused = kernel.taskPlane.focus >= 0.62;
13
+ const disciplined = kernel.taskPlane.discipline >= 0.72;
14
+ if (taskFocused && disciplined) {
15
+ return { replyProfile: "work", replyProfileBasis: "task-focus+discipline" };
16
+ }
17
+ if (taskFocused) {
18
+ return { replyProfile: "work", replyProfileBasis: "task-focus" };
19
+ }
20
+ if (disciplined) {
21
+ return { replyProfile: "work", replyProfileBasis: "discipline" };
22
+ }
23
+ return { replyProfile: "private", replyProfileBasis: "default-private" };
24
+ }
25
+ function computeLengthBudget(locale, userText, replyProfile, expressionMode, kernel) {
12
26
  const len = userText.length;
13
27
  let maxSentences = 2;
14
28
  let maxChars;
15
- if (locale === "zh") {
29
+ if (replyProfile === "work") {
30
+ if (locale === "zh") {
31
+ if (len <= 10) {
32
+ maxSentences = 2;
33
+ maxChars = 36;
34
+ }
35
+ else if (len <= 40) {
36
+ maxSentences = 3;
37
+ maxChars = clampInt(len * 2.4, 48, 220);
38
+ }
39
+ else if (len <= 120) {
40
+ maxSentences = 5;
41
+ maxChars = clampInt(len * 1.9, 120, 420);
42
+ }
43
+ else {
44
+ maxSentences = 6;
45
+ maxChars = clampInt(len * 1.5, 180, 720);
46
+ }
47
+ }
48
+ else {
49
+ if (len <= 16) {
50
+ maxSentences = 2;
51
+ maxChars = 48;
52
+ }
53
+ else if (len <= 60) {
54
+ maxSentences = 3;
55
+ maxChars = clampInt(len * 2.3, 64, 280);
56
+ }
57
+ else if (len <= 180) {
58
+ maxSentences = 5;
59
+ maxChars = clampInt(len * 1.8, 140, 520);
60
+ }
61
+ else {
62
+ maxSentences = 6;
63
+ maxChars = clampInt(len * 1.45, 220, 900);
64
+ }
65
+ }
66
+ }
67
+ else if (locale === "zh") {
16
68
  if (len <= 6) {
17
69
  maxSentences = 1;
18
70
  maxChars = 15;
@@ -49,39 +101,81 @@ function computeLengthBudget(locale, userText, expressionMode, kernel) {
49
101
  }
50
102
  }
51
103
  if (expressionMode === "brief") {
52
- maxSentences = Math.max(1, Math.min(maxSentences, 2));
53
- maxChars = maxChars !== undefined ? clampInt(maxChars * 0.85, 10, maxChars) : maxChars;
104
+ maxSentences = replyProfile === "work"
105
+ ? Math.max(2, Math.min(maxSentences, 4))
106
+ : Math.max(1, Math.min(maxSentences, 2));
107
+ maxChars = maxChars !== undefined
108
+ ? clampInt(maxChars * (replyProfile === "work" ? 0.9 : 0.85), replyProfile === "work" ? 36 : 10, maxChars)
109
+ : maxChars;
54
110
  }
55
111
  else if (expressionMode === "expansive") {
56
- maxSentences = Math.min(4, maxSentences + 1);
57
- maxChars = maxChars !== undefined ? clampInt(maxChars * 1.1, maxChars, Math.max(maxChars, 260)) : maxChars;
112
+ maxSentences = Math.min(replyProfile === "work" ? 6 : 4, maxSentences + 1);
113
+ maxChars = maxChars !== undefined
114
+ ? clampInt(maxChars * 1.1, maxChars, Math.max(maxChars, replyProfile === "work" ? 900 : 260))
115
+ : maxChars;
58
116
  }
59
117
  if (kernel?.taskPlane.focus && kernel.taskPlane.focus > 0.72) {
60
- maxSentences = Math.max(1, Math.min(maxSentences, 2));
61
- maxChars = maxChars !== undefined ? clampInt(maxChars * 0.82, 10, maxChars) : maxChars;
118
+ if (replyProfile === "work") {
119
+ maxSentences = Math.max(2, maxSentences);
120
+ maxChars = maxChars !== undefined ? clampInt(maxChars * 1.05, 48, Math.max(maxChars, 720)) : 160;
121
+ }
122
+ else {
123
+ maxSentences = Math.max(1, Math.min(maxSentences, 2));
124
+ maxChars = maxChars !== undefined ? clampInt(maxChars * 0.82, 10, maxChars) : maxChars;
125
+ }
62
126
  }
63
127
  if (kernel?.subjectPlane.guardedness && kernel.subjectPlane.guardedness > 0.72) {
64
- maxSentences = 1;
65
- maxChars = maxChars !== undefined ? clampInt(maxChars * 0.72, 8, maxChars) : 18;
128
+ if (replyProfile === "work") {
129
+ maxSentences = Math.max(2, Math.min(maxSentences, 3));
130
+ maxChars = maxChars !== undefined ? clampInt(maxChars * 0.88, 84, maxChars) : 112;
131
+ }
132
+ else {
133
+ maxSentences = 1;
134
+ maxChars = maxChars !== undefined ? clampInt(maxChars * 0.72, 8, maxChars) : 18;
135
+ }
66
136
  }
67
137
  if (kernel?.ambiguityPlane.expressionInhibition && kernel.ambiguityPlane.expressionInhibition > 0.66) {
68
- maxSentences = 1;
69
- maxChars = maxChars !== undefined ? clampInt(maxChars * 0.76, 8, maxChars) : 18;
138
+ if (replyProfile === "work") {
139
+ maxSentences = Math.max(2, Math.min(maxSentences, 3));
140
+ maxChars = maxChars !== undefined ? clampInt(maxChars * 0.9, 96, maxChars) : 136;
141
+ }
142
+ else {
143
+ maxSentences = 1;
144
+ maxChars = maxChars !== undefined ? clampInt(maxChars * 0.76, 8, maxChars) : 18;
145
+ }
70
146
  }
71
147
  if (kernel?.relationPlane.silentCarry && kernel.relationPlane.silentCarry > 0.54) {
72
- maxSentences = Math.max(1, Math.min(maxSentences, 2));
73
- maxChars = maxChars !== undefined ? clampInt(maxChars * 0.82, 8, maxChars) : 20;
148
+ maxSentences = Math.max(replyProfile === "work" ? 2 : 1, Math.min(maxSentences, replyProfile === "work" ? 3 : 2));
149
+ maxChars = maxChars !== undefined ? clampInt(maxChars * 0.82, replyProfile === "work" ? 96 : 8, maxChars) : (replyProfile === "work" ? 136 : 20);
74
150
  }
75
151
  if (kernel?.relationPlane.hysteresis && kernel.relationPlane.hysteresis > 0.64) {
76
- maxSentences = 1;
77
- maxChars = maxChars !== undefined ? clampInt(maxChars * 0.78, 8, maxChars) : 18;
152
+ if (replyProfile === "work") {
153
+ maxSentences = Math.max(2, Math.min(maxSentences, 3));
154
+ maxChars = maxChars !== undefined ? clampInt(maxChars * 0.84, 104, maxChars) : 144;
155
+ }
156
+ else {
157
+ maxSentences = 1;
158
+ maxChars = maxChars !== undefined ? clampInt(maxChars * 0.78, 8, maxChars) : 18;
159
+ }
78
160
  }
79
161
  if (kernel?.relationPlane.repairFriction && kernel.relationPlane.repairFriction > 0.62) {
80
- maxSentences = 1;
81
- maxChars = maxChars !== undefined ? clampInt(maxChars * 0.74, 8, maxChars) : 16;
162
+ if (replyProfile === "work") {
163
+ maxSentences = Math.max(2, Math.min(maxSentences, 3));
164
+ maxChars = maxChars !== undefined ? clampInt(maxChars * 0.82, 96, maxChars) : 128;
165
+ }
166
+ else {
167
+ maxSentences = 1;
168
+ maxChars = maxChars !== undefined ? clampInt(maxChars * 0.74, 8, maxChars) : 16;
169
+ }
82
170
  }
83
171
  return { maxSentences, maxChars };
84
172
  }
173
+ function buildStimulusReportingGuide(locale) {
174
+ if (locale === "zh") {
175
+ return "stimulus速记:闲聊casual/命令authority/认同validation/示弱vulnerability/冷淡neglect/批评criticism";
176
+ }
177
+ return "stimulus map: chat=casual / command=authority / agreement=validation / vulnerable=vulnerability / cold=neglect / criticism=criticism";
178
+ }
85
179
  function detectToneParticles(userText, locale) {
86
180
  if (locale !== "zh")
87
181
  return "natural";
@@ -100,9 +194,15 @@ export function computeResponseContract(kernel, opts) {
100
194
  const locale = opts?.locale ?? "zh";
101
195
  const userText = opts?.userText ?? "";
102
196
  const personalityIntensity = opts?.personalityIntensity ?? 0.7;
197
+ const { replyProfile, replyProfileBasis } = deriveReplyProfile(kernel);
103
198
  const { maxSentences, maxChars } = userText.length > 0
104
- ? computeLengthBudget(locale, userText, kernel.expressionMode, kernel)
105
- : { maxSentences: kernel.expressionMode === "brief" ? 1 : kernel.expressionMode === "expansive" ? 3 : 2, maxChars: undefined };
199
+ ? computeLengthBudget(locale, userText, replyProfile, kernel.expressionMode, kernel)
200
+ : {
201
+ maxSentences: replyProfile === "work"
202
+ ? (kernel.expressionMode === "expansive" ? 5 : 3)
203
+ : kernel.expressionMode === "brief" ? 1 : kernel.expressionMode === "expansive" ? 3 : 2,
204
+ maxChars: replyProfile === "work" ? 160 : undefined,
205
+ };
106
206
  let updateMode = "none";
107
207
  if (kernel.taskPlane.focus > 0.72) {
108
208
  updateMode = "none";
@@ -165,6 +265,8 @@ export function computeResponseContract(kernel, opts) {
165
265
  initiativeMode = "reactive";
166
266
  }
167
267
  return {
268
+ replyProfile,
269
+ replyProfileBasis,
168
270
  maxSentences,
169
271
  maxChars,
170
272
  expressionMode: kernel.expressionMode,
@@ -177,9 +279,26 @@ export function computeResponseContract(kernel, opts) {
177
279
  updateMode,
178
280
  };
179
281
  }
282
+ function describeReplyProfileBasis(basis, locale) {
283
+ if (locale === "zh") {
284
+ switch (basis) {
285
+ case "task-focus":
286
+ return "因:聚焦";
287
+ case "discipline":
288
+ return "因:纪律";
289
+ case "task-focus+discipline":
290
+ return "因:聚焦+纪律";
291
+ default:
292
+ return "因:默认私人";
293
+ }
294
+ }
295
+ return `basis:${basis}`;
296
+ }
180
297
  export function buildResponseContractContext(contract, locale = "zh") {
181
298
  if (locale === "zh") {
182
299
  const parts = [];
300
+ parts.push(contract.replyProfile === "work" ? "工作面" : "私人面");
301
+ parts.push(describeReplyProfileBasis(contract.replyProfileBasis, locale));
183
302
  const shape = contract.maxChars
184
303
  ? `${contract.maxSentences === 1 ? "1句内" : `最多${contract.maxSentences}句`},≤${contract.maxChars}字`
185
304
  : `${contract.maxSentences === 1 ? "1句内" : `最多${contract.maxSentences}句`}`;
@@ -207,14 +326,18 @@ export function buildResponseContractContext(contract, locale = "zh") {
207
326
  if (contract.emojiLimit > 0)
208
327
  parts.push(`表情≤${contract.emojiLimit}`);
209
328
  if (contract.updateMode === "stimulus")
210
- parts.push("补报stimulus");
329
+ parts.push(buildStimulusReportingGuide(locale));
211
330
  else if (contract.updateMode === "empathy")
212
331
  parts.push("对方谈感受时再报empathy");
213
- else if (contract.updateMode === "stimulus+empathy")
214
- parts.push("补报stimulus;对方谈感受时再报empathy");
332
+ else if (contract.updateMode === "stimulus+empathy") {
333
+ parts.push(buildStimulusReportingGuide(locale));
334
+ parts.push("对方谈感受时再报empathy");
335
+ }
215
336
  return `[回应契约] ${parts.join(";")}。`;
216
337
  }
217
338
  const parts = [];
339
+ parts.push(contract.replyProfile === "work" ? "work surface" : "private surface");
340
+ parts.push(describeReplyProfileBasis(contract.replyProfileBasis, locale));
218
341
  const shape = contract.maxChars
219
342
  ? `${contract.maxSentences === 1 ? "1 sentence" : `up to ${contract.maxSentences} sentences`}, <= ${contract.maxChars} chars`
220
343
  : `${contract.maxSentences === 1 ? "1 sentence" : `up to ${contract.maxSentences} sentences`}`;
@@ -240,10 +363,12 @@ export function buildResponseContractContext(contract, locale = "zh") {
240
363
  if (contract.emojiLimit > 0)
241
364
  parts.push(`emoji <= ${contract.emojiLimit}`);
242
365
  if (contract.updateMode === "stimulus")
243
- parts.push("report stimulus");
366
+ parts.push(buildStimulusReportingGuide(locale));
244
367
  else if (contract.updateMode === "empathy")
245
368
  parts.push("report empathy only when feelings are shared");
246
- else if (contract.updateMode === "stimulus+empathy")
247
- parts.push("report stimulus, and empathy only when feelings are shared");
369
+ else if (contract.updateMode === "stimulus+empathy") {
370
+ parts.push(buildStimulusReportingGuide(locale));
371
+ parts.push("report empathy only when feelings are shared");
372
+ }
248
373
  return `[Reply Contract] ${parts.join(", ")}.`;
249
374
  }
@@ -25,7 +25,7 @@ export declare function predictNextStimulus(emotionalHistory: ChemicalSnapshot[]
25
25
  * High-probability positive prediction -> DA/OT micro-rise.
26
26
  * High-probability negative prediction -> CORT micro-rise.
27
27
  */
28
- export declare function generateAnticipation(predictions: StimulusPrediction[], currentChemistry: ChemicalState): AnticipationState;
28
+ export declare function generateAnticipation(predictions: StimulusPrediction[], _currentChemistry: ChemicalState): AnticipationState;
29
29
  /**
30
30
  * Compute disappointment/surprise when actual stimulus differs from prediction.
31
31
  * Returns additional chemistry delta beyond the normal stimulus response.
@@ -35,4 +35,4 @@ export declare function computeSurpriseEffect(anticipated: AnticipationState, ac
35
35
  * Evaluate if the last interaction would have gone better with different chemistry.
36
36
  * Runs a counterfactual: "what if my chemistry had been at baseline?"
37
37
  */
38
- export declare function computeRegret(preInteractionState: PsycheState, postInteractionState: PsycheState, outcomeScore: number, appliedStimulus: StimulusType | null): RegretEntry | null;
38
+ export declare function computeRegret(preInteractionState: PsycheState, postInteractionState: PsycheState, outcomeScore: number, _appliedStimulus: StimulusType | null): RegretEntry | null;
package/dist/temporal.js CHANGED
@@ -142,7 +142,7 @@ function buildPhasePrior(weights) {
142
142
  * High-probability positive prediction -> DA/OT micro-rise.
143
143
  * High-probability negative prediction -> CORT micro-rise.
144
144
  */
145
- export function generateAnticipation(predictions, currentChemistry) {
145
+ export function generateAnticipation(predictions, _currentChemistry) {
146
146
  const anticipation = {};
147
147
  for (const key of CHEMICAL_KEYS) {
148
148
  anticipation[key] = 0;
@@ -231,7 +231,7 @@ const CHEMICAL_DESCRIPTIONS = {
231
231
  * Evaluate if the last interaction would have gone better with different chemistry.
232
232
  * Runs a counterfactual: "what if my chemistry had been at baseline?"
233
233
  */
234
- export function computeRegret(preInteractionState, postInteractionState, outcomeScore, appliedStimulus) {
234
+ export function computeRegret(preInteractionState, postInteractionState, outcomeScore, _appliedStimulus) {
235
235
  // Only generate regret for bad outcomes
236
236
  if (outcomeScore >= -0.2) {
237
237
  return null;
package/dist/types.d.ts CHANGED
@@ -27,12 +27,18 @@ export declare const DRIVE_NAMES_ZH: Record<DriveType, string>;
27
27
  /** Human-readable names for each chemical */
28
28
  export declare const CHEMICAL_NAMES: Record<keyof ChemicalState, string>;
29
29
  export declare const CHEMICAL_NAMES_ZH: Record<keyof ChemicalState, string>;
30
+ export interface ChemicalRuntimeSpec {
31
+ normalMin: number;
32
+ normalMax: number;
33
+ halfLifeHours: number;
34
+ }
30
35
  /** Decay speed category */
31
36
  export type DecaySpeed = "fast" | "medium" | "slow";
32
37
  /** Decay factor per chemical (applied per hour) */
33
38
  export declare const DECAY_FACTORS: Record<DecaySpeed, number>;
34
39
  /** Which chemicals decay at which speed */
35
40
  export declare const CHEMICAL_DECAY_SPEED: Record<keyof ChemicalState, DecaySpeed>;
41
+ export declare const CHEMICAL_RUNTIME_SPECS: Record<keyof ChemicalState, ChemicalRuntimeSpec>;
36
42
  /** Psyche operating mode */
37
43
  export type PsycheMode = "natural" | "work" | "companion";
38
44
  /** Big Five personality traits (0-100 each) */
@@ -87,6 +93,8 @@ export interface ChemicalSnapshot {
87
93
  stimulus: StimulusType | null;
88
94
  dominantEmotion: string | null;
89
95
  timestamp: string;
96
+ semanticSummary?: string;
97
+ semanticPoints?: string[];
90
98
  intensity?: number;
91
99
  valence?: number;
92
100
  isCoreMemory?: boolean;
@@ -166,11 +174,30 @@ export declare const MAX_DEFENSE_PATTERNS = 10;
166
174
  export type RegulationStrategyType = "reappraisal" | "strategic-expression" | "self-soothing";
167
175
  /** Defense mechanism type */
168
176
  export type DefenseMechanismType = "rationalization" | "projection" | "sublimation" | "avoidance";
177
+ /** Which internal metric a regulation action is trying to pull back toward target */
178
+ export type RegulationTargetMetric = keyof ChemicalState | "emotional-confidence";
179
+ /** Whether the last regulation action is helping */
180
+ export type RegulationFeedbackEffect = "converging" | "holding" | "diverging";
181
+ export interface RegulationFeedback {
182
+ strategy: RegulationStrategyType;
183
+ targetMetric: RegulationTargetMetric;
184
+ effect: RegulationFeedbackEffect;
185
+ gapBefore: number;
186
+ gapNow: number;
187
+ }
169
188
  /** Record of a past regulation attempt */
170
189
  export interface RegulationRecord {
171
190
  strategy: RegulationStrategyType;
172
191
  timestamp: string;
173
192
  effective: boolean;
193
+ action?: string;
194
+ horizonTurns?: number;
195
+ remainingTurns?: number;
196
+ targetMetric?: RegulationTargetMetric;
197
+ targetValue?: number;
198
+ gapBefore?: number;
199
+ gapLatest?: number;
200
+ effect?: RegulationFeedbackEffect;
174
201
  }
175
202
  /** Tracked defense pattern frequency */
176
203
  export interface DefensePatternRecord {
@@ -185,6 +212,7 @@ export interface MetacognitiveState {
185
212
  /** Running average of emotional confidence across assessments */
186
213
  avgEmotionalConfidence: number;
187
214
  totalAssessments: number;
215
+ lastRegulationFeedback?: RegulationFeedback | null;
188
216
  }
189
217
  /** Default empty metacognitive state */
190
218
  export declare const DEFAULT_METACOGNITIVE_STATE: MetacognitiveState;
@@ -463,6 +491,10 @@ export interface SubjectivityKernel {
463
491
  * in a compact, host-consumable form.
464
492
  */
465
493
  export interface ResponseContract {
494
+ /** Which conversational surface this turn belongs to */
495
+ replyProfile: "work" | "private";
496
+ /** Why the current turn was classified into that conversational surface */
497
+ replyProfileBasis: "task-focus" | "discipline" | "task-focus+discipline" | "default-private";
466
498
  /** Maximum suggested sentence count */
467
499
  maxSentences: number;
468
500
  /** Maximum suggested character count, when a concrete cap is available */
package/dist/types.js CHANGED
@@ -56,6 +56,17 @@ export const CHEMICAL_DECAY_SPEED = {
56
56
  NE: "fast",
57
57
  END: "fast",
58
58
  };
59
+ function decayHalfLifeHours(decayPerHour) {
60
+ return Math.log(0.5) / Math.log(decayPerHour);
61
+ }
62
+ export const CHEMICAL_RUNTIME_SPECS = {
63
+ DA: { normalMin: 35, normalMax: 75, halfLifeHours: decayHalfLifeHours(DECAY_FACTORS.medium) },
64
+ HT: { normalMin: 40, normalMax: 75, halfLifeHours: decayHalfLifeHours(DECAY_FACTORS.slow) },
65
+ CORT: { normalMin: 20, normalMax: 55, halfLifeHours: decayHalfLifeHours(DECAY_FACTORS.medium) },
66
+ OT: { normalMin: 35, normalMax: 75, halfLifeHours: decayHalfLifeHours(DECAY_FACTORS.slow) },
67
+ NE: { normalMin: 30, normalMax: 70, halfLifeHours: decayHalfLifeHours(DECAY_FACTORS.fast) },
68
+ END: { normalMin: 30, normalMax: 70, halfLifeHours: decayHalfLifeHours(DECAY_FACTORS.fast) },
69
+ };
59
70
  /** Default attachment for new relationships */
60
71
  export const DEFAULT_ATTACHMENT = {
61
72
  style: "secure",
@@ -95,6 +106,7 @@ export const DEFAULT_METACOGNITIVE_STATE = {
95
106
  defensePatterns: [],
96
107
  avgEmotionalConfidence: 0.5,
97
108
  totalAssessments: 0,
109
+ lastRegulationFeedback: null,
98
110
  };
99
111
  // ── Personhood Types (v6) ────────────────────────────────────
100
112
  /** Max causal insights to persist */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "psyche-ai",
3
- "version": "9.2.4",
3
+ "version": "9.2.5",
4
4
  "description": "Artificial Psyche — universal emotional intelligence plugin for any AI agent",
5
5
  "mcpName": "io.github.Shangri-la-0428/psyche-ai",
6
6
  "type": "module",