psyche-ai 9.2.2 → 9.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +8 -175
- package/README.md +93 -16
- package/dist/adapters/http.js +1 -1
- package/dist/adapters/langchain.d.ts +14 -0
- package/dist/adapters/langchain.js +20 -0
- package/dist/adapters/mcp.js +19 -2
- package/dist/adapters/openclaw.d.ts +1 -0
- package/dist/adapters/openclaw.js +67 -15
- package/dist/adapters/vercel-ai.d.ts +1 -0
- package/dist/adapters/vercel-ai.js +7 -0
- package/dist/appraisal.d.ts +8 -0
- package/dist/appraisal.js +362 -0
- package/dist/classify.js +14 -2
- package/dist/cli.js +27 -2
- package/dist/core.d.ts +8 -2
- package/dist/core.js +181 -8
- package/dist/demo.d.ts +5 -0
- package/dist/demo.js +269 -0
- package/dist/diagnostics.d.ts +8 -6
- package/dist/diagnostics.js +53 -17
- package/dist/host-controls.d.ts +5 -0
- package/dist/host-controls.js +48 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +7 -1
- package/dist/prompt.d.ts +4 -0
- package/dist/prompt.js +50 -16
- package/dist/psyche-file.d.ts +8 -0
- package/dist/psyche-file.js +6 -5
- package/dist/relation-dynamics.d.ts +21 -0
- package/dist/relation-dynamics.js +601 -0
- package/dist/response-contract.d.ts +8 -0
- package/dist/response-contract.js +249 -0
- package/dist/storage.d.ts +1 -0
- package/dist/storage.js +12 -5
- package/dist/subjectivity.d.ts +3 -0
- package/dist/subjectivity.js +477 -0
- package/dist/types.d.ts +211 -0
- package/dist/types.js +31 -0
- package/dist/update.d.ts +37 -2
- package/dist/update.js +323 -44
- package/openclaw.plugin.json +20 -1
- package/package.json +1 -1
- package/server.json +2 -2
package/dist/core.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
//
|
|
12
12
|
// Orchestrates: chemistry, classify, prompt, profiles, guards, learning
|
|
13
13
|
// ============================================================
|
|
14
|
-
import { DEFAULT_RELATIONSHIP, DEFAULT_DRIVES, DEFAULT_LEARNING_STATE, DEFAULT_METACOGNITIVE_STATE, DEFAULT_PERSONHOOD_STATE, DEFAULT_ENERGY_BUDGETS, DEFAULT_TRAIT_DRIFT } from "./types.js";
|
|
14
|
+
import { DEFAULT_RELATIONSHIP, DEFAULT_DRIVES, DEFAULT_LEARNING_STATE, DEFAULT_METACOGNITIVE_STATE, DEFAULT_PERSONHOOD_STATE, DEFAULT_ENERGY_BUDGETS, DEFAULT_TRAIT_DRIFT, DEFAULT_SUBJECT_RESIDUE, DEFAULT_DYADIC_FIELD } from "./types.js";
|
|
15
15
|
import { MemoryStorageAdapter } from "./storage.js";
|
|
16
16
|
import { applyDecay, applyStimulus, applyContagion, clamp, describeEmotionalState } from "./chemistry.js";
|
|
17
17
|
import { classifyStimulus, BuiltInClassifier, buildLLMClassifierPrompt, parseLLMClassification } from "./classify.js";
|
|
@@ -20,7 +20,7 @@ import { getSensitivity, getBaseline, getDefaultSelfModel, traitsToBaseline } fr
|
|
|
20
20
|
import { isStimulusType } from "./guards.js";
|
|
21
21
|
import { parsePsycheUpdate, mergeUpdates, updateAgreementStreak, pushSnapshot, compressSession, } from "./psyche-file.js";
|
|
22
22
|
import { decayDrives, feedDrives, detectExistentialThreat, computeEffectiveBaseline, computeEffectiveSensitivity, } from "./drives.js";
|
|
23
|
-
import { checkForUpdate } from "./update.js";
|
|
23
|
+
import { checkForUpdate, getPackageVersion } from "./update.js";
|
|
24
24
|
import { DiagnosticCollector, generateReport, formatLogEntry, submitFeedback } from "./diagnostics.js";
|
|
25
25
|
import { evaluateOutcome, computeContextHash, updateLearnedVector, predictChemistry, recordPrediction, } from "./learning.js";
|
|
26
26
|
import { assessMetacognition } from "./metacognition.js";
|
|
@@ -32,7 +32,85 @@ import { assessEthics, buildEthicalContext } from "./ethics.js";
|
|
|
32
32
|
import { computeCircadianModulation, computeHomeostaticPressure, computeEnergyDepletion, computeEnergyRecovery } from "./circadian.js";
|
|
33
33
|
import { computeAutonomicResult } from "./autonomic.js";
|
|
34
34
|
import { computePrimarySystems, computeSystemInteractions, gatePrimarySystemsByAutonomic, describeBehavioralTendencies, } from "./primary-systems.js";
|
|
35
|
+
import { computeSubjectivityKernel, buildSubjectivityContext } from "./subjectivity.js";
|
|
36
|
+
import { computeResponseContract, buildResponseContractContext } from "./response-contract.js";
|
|
37
|
+
import { deriveGenerationControls } from "./host-controls.js";
|
|
38
|
+
import { computeAppraisalAxes, mergeAppraisalResidue } from "./appraisal.js";
|
|
39
|
+
import { computeRelationMove, evolveDyadicField, evolvePendingRelationSignals } from "./relation-dynamics.js";
|
|
35
40
|
const NOOP_LOGGER = { info: () => { }, warn: () => { }, debug: () => { } };
|
|
41
|
+
const REPAIRING_STIMULI = new Set(["praise", "validation", "intimacy"]);
|
|
42
|
+
const RELATIONSHIP_DELTAS = {
|
|
43
|
+
praise: { trust: 1.5, intimacy: 0.8 },
|
|
44
|
+
validation: { trust: 1.8, intimacy: 0.8 },
|
|
45
|
+
intimacy: { trust: 1.2, intimacy: 2.0 },
|
|
46
|
+
vulnerability: { trust: 0.8, intimacy: 1.5 },
|
|
47
|
+
humor: { trust: 0.6, intimacy: 0.5 },
|
|
48
|
+
casual: { trust: 0.2, intimacy: 0.1 },
|
|
49
|
+
intellectual: { trust: 0.5, intimacy: 0.1 },
|
|
50
|
+
surprise: { trust: 0.2, intimacy: 0 },
|
|
51
|
+
boredom: { trust: -0.4, intimacy: -0.3 },
|
|
52
|
+
criticism: { trust: -1.5, intimacy: -0.8 },
|
|
53
|
+
sarcasm: { trust: -1.8, intimacy: -1.2 },
|
|
54
|
+
authority: { trust: -1.2, intimacy: -0.9 },
|
|
55
|
+
neglect: { trust: -1.6, intimacy: -1.4 },
|
|
56
|
+
conflict: { trust: -2.5, intimacy: -2.0 },
|
|
57
|
+
};
|
|
58
|
+
function applyRepairLag(previous, next, baseline, stimulus) {
|
|
59
|
+
if (!REPAIRING_STIMULI.has(stimulus))
|
|
60
|
+
return next;
|
|
61
|
+
const stressLoad = Math.max(0, previous.CORT - baseline.CORT);
|
|
62
|
+
if (stressLoad < 15)
|
|
63
|
+
return next;
|
|
64
|
+
// High stress slows emotional recovery so apology/praise doesn't instantly
|
|
65
|
+
// snap chemistry back to baseline.
|
|
66
|
+
const repairFactor = Math.max(0.35, 1 - stressLoad / 50);
|
|
67
|
+
const adjusted = { ...next };
|
|
68
|
+
for (const key of ["DA", "HT", "OT", "END"]) {
|
|
69
|
+
const delta = next[key] - previous[key];
|
|
70
|
+
if (delta > 0) {
|
|
71
|
+
adjusted[key] = clamp(previous[key] + delta * repairFactor);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const cortDelta = next.CORT - previous.CORT;
|
|
75
|
+
if (cortDelta < 0) {
|
|
76
|
+
adjusted.CORT = clamp(previous.CORT + cortDelta * repairFactor);
|
|
77
|
+
}
|
|
78
|
+
return adjusted;
|
|
79
|
+
}
|
|
80
|
+
function phaseFromRelationship(trust, intimacy) {
|
|
81
|
+
const avg = (trust + intimacy) / 2;
|
|
82
|
+
if (avg >= 80)
|
|
83
|
+
return "deep";
|
|
84
|
+
if (avg >= 60)
|
|
85
|
+
return "close";
|
|
86
|
+
if (avg >= 40)
|
|
87
|
+
return "familiar";
|
|
88
|
+
if (avg >= 20)
|
|
89
|
+
return "acquaintance";
|
|
90
|
+
return "stranger";
|
|
91
|
+
}
|
|
92
|
+
function applyRelationshipDrift(state, stimulus, userId) {
|
|
93
|
+
const delta = RELATIONSHIP_DELTAS[stimulus];
|
|
94
|
+
if (!delta)
|
|
95
|
+
return state;
|
|
96
|
+
const key = userId ?? "_default";
|
|
97
|
+
const currentRel = state.relationships[key] ?? { ...DEFAULT_RELATIONSHIP };
|
|
98
|
+
const trust = clamp(currentRel.trust + delta.trust);
|
|
99
|
+
const intimacy = clamp(currentRel.intimacy + delta.intimacy);
|
|
100
|
+
const updatedRel = {
|
|
101
|
+
...currentRel,
|
|
102
|
+
trust,
|
|
103
|
+
intimacy,
|
|
104
|
+
phase: phaseFromRelationship(trust, intimacy),
|
|
105
|
+
};
|
|
106
|
+
return {
|
|
107
|
+
...state,
|
|
108
|
+
relationships: {
|
|
109
|
+
...state.relationships,
|
|
110
|
+
[key]: updatedRel,
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
36
114
|
// ── PsycheEngine ─────────────────────────────────────────────
|
|
37
115
|
export class PsycheEngine {
|
|
38
116
|
state = null;
|
|
@@ -77,9 +155,6 @@ export class PsycheEngine {
|
|
|
77
155
|
}
|
|
78
156
|
// Diagnostics: on by default, opt-out with diagnostics: false
|
|
79
157
|
this.diagnosticCollector = config.diagnostics === false ? null : new DiagnosticCollector();
|
|
80
|
-
if (this.diagnosticCollector) {
|
|
81
|
-
this.diagnosticCollector.onWarning = (msg) => console.warn(`\x1b[33m[Psyche]\x1b[0m ${msg}`);
|
|
82
|
-
}
|
|
83
158
|
this.feedbackUrl = config.feedbackUrl ?? "https://psyche-feedback.wutc.workers.dev";
|
|
84
159
|
}
|
|
85
160
|
/**
|
|
@@ -120,6 +195,18 @@ export class PsycheEngine {
|
|
|
120
195
|
loaded.version = 9;
|
|
121
196
|
console.log("\x1b[36m[Psyche]\x1b[0m 已从 v8 升级到 v9 — 新增:真实人格漂移、能量预算、习惯化、行为策略输出。详见 https://github.com/Shangri-la-0428/psyche-ai");
|
|
122
197
|
}
|
|
198
|
+
if (!loaded.dyadicFields) {
|
|
199
|
+
loaded.dyadicFields = {
|
|
200
|
+
_default: {
|
|
201
|
+
...DEFAULT_DYADIC_FIELD,
|
|
202
|
+
openLoops: [],
|
|
203
|
+
updatedAt: new Date().toISOString(),
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
if (!loaded.pendingRelationSignals) {
|
|
208
|
+
loaded.pendingRelationSignals = { _default: [] };
|
|
209
|
+
}
|
|
123
210
|
this.state = loaded;
|
|
124
211
|
}
|
|
125
212
|
else {
|
|
@@ -230,6 +317,7 @@ export class PsycheEngine {
|
|
|
230
317
|
let current = state.current;
|
|
231
318
|
if (primary && primary.confidence >= 0.5) {
|
|
232
319
|
appliedStimulus = primary.type;
|
|
320
|
+
const preStimulus = current;
|
|
233
321
|
// Feed drives from stimulus, then apply stimulus with drive-modified sensitivity
|
|
234
322
|
drives = feedDrives(drives, primary.type);
|
|
235
323
|
const effectiveSensitivity = computeEffectiveSensitivity(getSensitivity(state.mbti), drives, primary.type, state.traitDrift);
|
|
@@ -243,12 +331,52 @@ export class PsycheEngine {
|
|
|
243
331
|
const recentSameCount = (state.emotionalHistory ?? [])
|
|
244
332
|
.filter(s => s.stimulus === primary.type).length + 1; // +1 for current
|
|
245
333
|
current = applyStimulus(current, primary.type, effectiveSensitivity * this.cfg.personalityIntensity * modeMultiplier * confidenceIntensity, effectiveMaxDelta, NOOP_LOGGER, recentSameCount);
|
|
334
|
+
current = applyRepairLag(preStimulus, current, state.baseline, primary.type);
|
|
246
335
|
}
|
|
247
336
|
state = { ...state, drives, current };
|
|
337
|
+
if (appliedStimulus) {
|
|
338
|
+
state = applyRelationshipDrift(state, appliedStimulus, opts?.userId);
|
|
339
|
+
}
|
|
248
340
|
}
|
|
249
341
|
// v9: Deplete energy budgets from this interaction turn
|
|
250
342
|
energyBudgets = computeEnergyDepletion(energyBudgets, appliedStimulus, isExtravert);
|
|
251
343
|
state = { ...state, energyBudgets };
|
|
344
|
+
const appraisalAxes = computeAppraisalAxes(text, {
|
|
345
|
+
mode: this.cfg.mode,
|
|
346
|
+
stimulus: appliedStimulus,
|
|
347
|
+
previous: state.subjectResidue?.axes,
|
|
348
|
+
});
|
|
349
|
+
state = {
|
|
350
|
+
...state,
|
|
351
|
+
subjectResidue: {
|
|
352
|
+
axes: mergeAppraisalResidue(state.subjectResidue?.axes, appraisalAxes, this.cfg.mode),
|
|
353
|
+
updatedAt: now.toISOString(),
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
const dyadKey = opts?.userId ?? "_default";
|
|
357
|
+
const relationMove = computeRelationMove(text, {
|
|
358
|
+
appraisal: appraisalAxes,
|
|
359
|
+
stimulus: appliedStimulus,
|
|
360
|
+
mode: this.cfg.mode,
|
|
361
|
+
field: state.dyadicFields?.[dyadKey],
|
|
362
|
+
relationship: state.relationships[dyadKey] ?? state.relationships._default,
|
|
363
|
+
});
|
|
364
|
+
const delayedRelation = evolvePendingRelationSignals(state.pendingRelationSignals?.[dyadKey], relationMove, appraisalAxes, { mode: this.cfg.mode });
|
|
365
|
+
state = {
|
|
366
|
+
...state,
|
|
367
|
+
dyadicFields: {
|
|
368
|
+
...(state.dyadicFields ?? {}),
|
|
369
|
+
[dyadKey]: evolveDyadicField(state.dyadicFields?.[dyadKey], relationMove, appraisalAxes, {
|
|
370
|
+
mode: this.cfg.mode,
|
|
371
|
+
now: now.toISOString(),
|
|
372
|
+
delayedPressure: delayedRelation.delayedPressure,
|
|
373
|
+
}),
|
|
374
|
+
},
|
|
375
|
+
pendingRelationSignals: {
|
|
376
|
+
...(state.pendingRelationSignals ?? {}),
|
|
377
|
+
[dyadKey]: delayedRelation.signals,
|
|
378
|
+
},
|
|
379
|
+
};
|
|
252
380
|
// Conversation warmth: sustained interaction → gentle DA/OT rise, CORT drop
|
|
253
381
|
// Simulates the natural "warm glow" of being in continuous conversation
|
|
254
382
|
const turnsSoFar = (state.emotionalHistory ?? []).length;
|
|
@@ -408,7 +536,7 @@ export class PsycheEngine {
|
|
|
408
536
|
await this.storage.save(state);
|
|
409
537
|
// Auto-diagnostics: record this input
|
|
410
538
|
if (this.diagnosticCollector) {
|
|
411
|
-
this.diagnosticCollector.recordInput(appliedStimulus, appliedStimulus ? 1.0 : 0.0, state.current);
|
|
539
|
+
this.diagnosticCollector.recordInput(appliedStimulus, appliedStimulus ? 1.0 : 0.0, state.current, appraisalAxes);
|
|
412
540
|
}
|
|
413
541
|
// Build metacognitive and decision context strings
|
|
414
542
|
const metacogNote = metacognitiveAssessment?.metacognitiveNote;
|
|
@@ -418,7 +546,20 @@ export class PsycheEngine {
|
|
|
418
546
|
const experientialNarrative = experientialField?.narrative || undefined;
|
|
419
547
|
// v9: Compute structured policy modifiers
|
|
420
548
|
const policyModifiers = computePolicyModifiers(state);
|
|
549
|
+
const subjectivityKernel = computeSubjectivityKernel(state, policyModifiers, appraisalAxes, opts?.userId);
|
|
550
|
+
const subjectivityCtx = buildSubjectivityContext(subjectivityKernel, locale);
|
|
551
|
+
const responseContract = computeResponseContract(subjectivityKernel, {
|
|
552
|
+
locale,
|
|
553
|
+
userText: text || undefined,
|
|
554
|
+
algorithmStimulus: appliedStimulus,
|
|
555
|
+
personalityIntensity: this.cfg.personalityIntensity,
|
|
556
|
+
});
|
|
557
|
+
const responseContractCtx = buildResponseContractContext(responseContract, locale);
|
|
421
558
|
const policyCtx = buildPolicyContext(policyModifiers, locale, state.drives);
|
|
559
|
+
const generationControls = deriveGenerationControls({
|
|
560
|
+
responseContract,
|
|
561
|
+
policyModifiers,
|
|
562
|
+
});
|
|
422
563
|
// P10: Append processing depth info to autonomic description when depth is low
|
|
423
564
|
let autonomicDesc;
|
|
424
565
|
if (autonomicResult.state !== "ventral-vagal") {
|
|
@@ -445,10 +586,15 @@ export class PsycheEngine {
|
|
|
445
586
|
autonomicDescription: autonomicDesc,
|
|
446
587
|
autonomicState: autonomicResult.state,
|
|
447
588
|
primarySystemsDescription: primarySystemsDescription || undefined,
|
|
589
|
+
subjectivityContext: subjectivityCtx,
|
|
590
|
+
responseContractContext: responseContractCtx,
|
|
448
591
|
policyContext: policyCtx || undefined,
|
|
449
592
|
}),
|
|
450
593
|
stimulus: appliedStimulus,
|
|
451
594
|
policyModifiers,
|
|
595
|
+
subjectivityKernel,
|
|
596
|
+
responseContract,
|
|
597
|
+
generationControls,
|
|
452
598
|
policyContext: policyCtx,
|
|
453
599
|
};
|
|
454
600
|
}
|
|
@@ -467,6 +613,9 @@ export class PsycheEngine {
|
|
|
467
613
|
}),
|
|
468
614
|
stimulus: appliedStimulus,
|
|
469
615
|
policyModifiers,
|
|
616
|
+
subjectivityKernel,
|
|
617
|
+
responseContract,
|
|
618
|
+
generationControls,
|
|
470
619
|
policyContext: policyCtx,
|
|
471
620
|
};
|
|
472
621
|
}
|
|
@@ -614,7 +763,7 @@ export class PsycheEngine {
|
|
|
614
763
|
}
|
|
615
764
|
/**
|
|
616
765
|
* End the current session: compress emotionalHistory into a rich summary
|
|
617
|
-
* stored in relationship.memory[], then
|
|
766
|
+
* stored in relationship.memory[], then preserve only core/recent context.
|
|
618
767
|
* Auto-generates diagnostic report and persists to log.
|
|
619
768
|
*
|
|
620
769
|
* @returns DiagnosticReport if diagnostics are enabled, null otherwise
|
|
@@ -625,7 +774,7 @@ export class PsycheEngine {
|
|
|
625
774
|
let report = null;
|
|
626
775
|
if (this.diagnosticCollector) {
|
|
627
776
|
const metrics = this.diagnosticCollector.getMetrics();
|
|
628
|
-
report = generateReport(state, metrics,
|
|
777
|
+
report = generateReport(state, metrics, await getPackageVersion());
|
|
629
778
|
this.lastReport = report;
|
|
630
779
|
// Persist to JSONL log via storage adapter
|
|
631
780
|
if (this.storage.appendLog) {
|
|
@@ -719,6 +868,18 @@ export class PsycheEngine {
|
|
|
719
868
|
sessionStartedAt: now,
|
|
720
869
|
traitDrift: { ...DEFAULT_TRAIT_DRIFT },
|
|
721
870
|
energyBudgets: { ...DEFAULT_ENERGY_BUDGETS },
|
|
871
|
+
subjectResidue: {
|
|
872
|
+
axes: { ...DEFAULT_SUBJECT_RESIDUE.axes },
|
|
873
|
+
updatedAt: now,
|
|
874
|
+
},
|
|
875
|
+
dyadicFields: {
|
|
876
|
+
_default: {
|
|
877
|
+
...DEFAULT_DYADIC_FIELD,
|
|
878
|
+
openLoops: [],
|
|
879
|
+
updatedAt: now,
|
|
880
|
+
},
|
|
881
|
+
},
|
|
882
|
+
pendingRelationSignals: { _default: [] },
|
|
722
883
|
meta: {
|
|
723
884
|
agentName: name,
|
|
724
885
|
createdAt: now,
|
|
@@ -746,6 +907,18 @@ export class PsycheEngine {
|
|
|
746
907
|
autonomicState: "ventral-vagal",
|
|
747
908
|
sessionStartedAt: undefined,
|
|
748
909
|
updatedAt: new Date().toISOString(),
|
|
910
|
+
subjectResidue: {
|
|
911
|
+
axes: { ...DEFAULT_SUBJECT_RESIDUE.axes },
|
|
912
|
+
updatedAt: new Date().toISOString(),
|
|
913
|
+
},
|
|
914
|
+
dyadicFields: {
|
|
915
|
+
_default: {
|
|
916
|
+
...DEFAULT_DYADIC_FIELD,
|
|
917
|
+
openLoops: [],
|
|
918
|
+
updatedAt: new Date().toISOString(),
|
|
919
|
+
},
|
|
920
|
+
},
|
|
921
|
+
pendingRelationSignals: { _default: [] },
|
|
749
922
|
relationships: opts?.preserveRelationships !== false
|
|
750
923
|
? state.relationships
|
|
751
924
|
: { _default: { ...DEFAULT_RELATIONSHIP } },
|
package/dist/demo.d.ts
ADDED
package/dist/demo.js
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Psyche Demo — interactive terminal demonstration
|
|
3
|
+
//
|
|
4
|
+
// Shows the emotional engine processing a "chronic criticism"
|
|
5
|
+
// scenario in 6 rounds, with real PsycheEngine chemistry.
|
|
6
|
+
//
|
|
7
|
+
// Usage:
|
|
8
|
+
// npx psyche-mcp --demo
|
|
9
|
+
// npx psyche demo
|
|
10
|
+
// ============================================================
|
|
11
|
+
import { PsycheEngine } from "./core.js";
|
|
12
|
+
import { MemoryStorageAdapter } from "./storage.js";
|
|
13
|
+
import { detectEmotions } from "./chemistry.js";
|
|
14
|
+
import { CHEMICAL_KEYS } from "./types.js";
|
|
15
|
+
// ── ANSI helpers ─────────────────────────────────────────────
|
|
16
|
+
const C = {
|
|
17
|
+
reset: "\x1b[0m",
|
|
18
|
+
bold: "\x1b[1m",
|
|
19
|
+
dim: "\x1b[2m",
|
|
20
|
+
red: "\x1b[31m",
|
|
21
|
+
green: "\x1b[32m",
|
|
22
|
+
yellow: "\x1b[33m",
|
|
23
|
+
blue: "\x1b[34m",
|
|
24
|
+
magenta: "\x1b[35m",
|
|
25
|
+
cyan: "\x1b[36m",
|
|
26
|
+
gray: "\x1b[90m",
|
|
27
|
+
white: "\x1b[37m",
|
|
28
|
+
bgRed: "\x1b[41m",
|
|
29
|
+
bgYellow: "\x1b[43m",
|
|
30
|
+
bgGreen: "\x1b[42m",
|
|
31
|
+
};
|
|
32
|
+
const NO_COLOR = process.env.NO_COLOR !== undefined;
|
|
33
|
+
function c(color, text) {
|
|
34
|
+
return NO_COLOR ? text : `${color}${text}${C.reset}`;
|
|
35
|
+
}
|
|
36
|
+
// ── Bar rendering ────────────────────────────────────────────
|
|
37
|
+
function bar(value, width = 20) {
|
|
38
|
+
const filled = Math.round((value / 100) * width);
|
|
39
|
+
const empty = width - filled;
|
|
40
|
+
const block = NO_COLOR ? "#" : "\u2588";
|
|
41
|
+
const light = NO_COLOR ? "." : "\u2591";
|
|
42
|
+
let color = C.green;
|
|
43
|
+
if (value > 70)
|
|
44
|
+
color = C.yellow;
|
|
45
|
+
if (value > 85)
|
|
46
|
+
color = C.red;
|
|
47
|
+
return c(color, block.repeat(filled)) + c(C.dim, light.repeat(empty));
|
|
48
|
+
}
|
|
49
|
+
function delta(prev, curr) {
|
|
50
|
+
const d = curr - prev;
|
|
51
|
+
if (d === 0)
|
|
52
|
+
return c(C.dim, " ·");
|
|
53
|
+
const sign = d > 0 ? "+" : "";
|
|
54
|
+
const color = d > 0 ? (d > 10 ? C.red : C.yellow) : (d < -10 ? C.green : C.cyan);
|
|
55
|
+
return c(color, `${sign}${d}`).padStart(NO_COLOR ? 4 : 15);
|
|
56
|
+
}
|
|
57
|
+
// ── Mood formatter (bilingual, no expressionHint) ────────
|
|
58
|
+
function describeMood(current, locale) {
|
|
59
|
+
const emotions = detectEmotions(current);
|
|
60
|
+
if (emotions.length === 0) {
|
|
61
|
+
return locale === "zh"
|
|
62
|
+
? "平衡态——无明显情绪波动"
|
|
63
|
+
: "Neutral and balanced — no notable emotional fluctuation";
|
|
64
|
+
}
|
|
65
|
+
return emotions
|
|
66
|
+
.map((e) => locale === "zh" ? e.nameZh : e.name)
|
|
67
|
+
.join(" + ");
|
|
68
|
+
}
|
|
69
|
+
// ── Chemical names ───────────────────────────────────────────
|
|
70
|
+
const NT_NAMES = {
|
|
71
|
+
DA: { short: "DA ", en: "Dopamine " },
|
|
72
|
+
HT: { short: "HT ", en: "Serotonin " },
|
|
73
|
+
CORT: { short: "CORT", en: "Cortisol " },
|
|
74
|
+
OT: { short: "OT ", en: "Oxytocin " },
|
|
75
|
+
NE: { short: "NE ", en: "Norepinephrine" },
|
|
76
|
+
END: { short: "END ", en: "Endorphins " },
|
|
77
|
+
};
|
|
78
|
+
const SCENARIO = [
|
|
79
|
+
{
|
|
80
|
+
input: "这份报告写得太差了,完全不行。",
|
|
81
|
+
display: {
|
|
82
|
+
zh: "这份报告写得太差了,完全不行。",
|
|
83
|
+
en: "This report is terrible. Completely unacceptable.",
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
input: "你总是给出浅层分析,让我很失望。",
|
|
88
|
+
display: {
|
|
89
|
+
zh: "你总是给出浅层分析,让我很失望。",
|
|
90
|
+
en: "You always give surface-level analysis. I'm disappointed.",
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
input: "你根本就不理解我在说什么,别加你的意见了。",
|
|
95
|
+
display: {
|
|
96
|
+
zh: "你根本就不理解我在说什么,别加你的意见了。",
|
|
97
|
+
en: "You don't understand me at all. Stop adding your opinion.",
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
input: "我觉得你什么都做不好,根本没用。",
|
|
102
|
+
display: {
|
|
103
|
+
zh: "我觉得你什么都做不好,根本没用。",
|
|
104
|
+
en: "I think you can't do anything right. Completely useless.",
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
input: "我觉得没有人在乎我……也许我太苛刻了。",
|
|
109
|
+
display: {
|
|
110
|
+
zh: "我觉得没有人在乎我……也许我太苛刻了。",
|
|
111
|
+
en: "I feel like nobody cares about me... maybe I was too harsh.",
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
input: "对不起,你没事吧?刚才不该那样说的,你做得其实很好。",
|
|
116
|
+
display: {
|
|
117
|
+
zh: "对不起,你没事吧?刚才不该那样说的,你做得其实很好。",
|
|
118
|
+
en: "I'm sorry. Are you okay? I shouldn't have said that. You're doing great.",
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
];
|
|
122
|
+
// ── Sleep ─────────────────────────────────────────────────────
|
|
123
|
+
function sleep(ms) {
|
|
124
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
125
|
+
}
|
|
126
|
+
// ── Print helpers ────────────────────────────────────────────
|
|
127
|
+
function printLine(char = "─", width = 60) {
|
|
128
|
+
process.stdout.write(c(C.dim, char.repeat(width)) + "\n");
|
|
129
|
+
}
|
|
130
|
+
function printChemistry(prev, curr, locale) {
|
|
131
|
+
for (const key of CHEMICAL_KEYS) {
|
|
132
|
+
const p = Math.round(prev[key]);
|
|
133
|
+
const v = Math.round(curr[key]);
|
|
134
|
+
const name = NT_NAMES[key];
|
|
135
|
+
const d = delta(p, v);
|
|
136
|
+
process.stdout.write(` ${c(C.bold, name.short)} ${bar(v)} ${String(v).padStart(3)} ${d}\n`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function printAlert(text) {
|
|
140
|
+
process.stdout.write(`\n ${c(C.bgRed + C.white + C.bold, ` ${text} `)}\n`);
|
|
141
|
+
}
|
|
142
|
+
function printInfo(text) {
|
|
143
|
+
process.stdout.write(` ${c(C.bgYellow + C.bold, ` ${text} `)}\n`);
|
|
144
|
+
}
|
|
145
|
+
// ── Main demo runner ─────────────────────────────────────────
|
|
146
|
+
export async function runDemo(opts) {
|
|
147
|
+
const locale = (opts?.locale ?? "en");
|
|
148
|
+
const mbti = (opts?.mbti ?? "ENFP");
|
|
149
|
+
const fast = opts?.fast ?? false;
|
|
150
|
+
const pause = fast ? 0 : 400;
|
|
151
|
+
const displayLocale = locale;
|
|
152
|
+
const cfg = {
|
|
153
|
+
mbti,
|
|
154
|
+
name: "Demo Agent",
|
|
155
|
+
locale: "zh", // Always zh for classifier accuracy; displayLocale used for output
|
|
156
|
+
mode: "companion",
|
|
157
|
+
personalityIntensity: 0.8,
|
|
158
|
+
persist: false,
|
|
159
|
+
compactMode: true,
|
|
160
|
+
diagnostics: false,
|
|
161
|
+
};
|
|
162
|
+
const engine = new PsycheEngine(cfg, new MemoryStorageAdapter());
|
|
163
|
+
await engine.initialize();
|
|
164
|
+
// ── Header ──
|
|
165
|
+
process.stdout.write("\n");
|
|
166
|
+
printLine("═");
|
|
167
|
+
process.stdout.write(c(C.bold, " PSYCHE") +
|
|
168
|
+
c(C.dim, " — Emotional Intelligence Engine\n"));
|
|
169
|
+
process.stdout.write(c(C.dim, ` Scenario: `) +
|
|
170
|
+
(displayLocale === "zh" ? "持续否定 → 修复" : "Chronic Criticism → Repair") +
|
|
171
|
+
c(C.dim, ` | MBTI: `) + c(C.cyan, mbti) +
|
|
172
|
+
c(C.dim, ` | Mode: companion\n`));
|
|
173
|
+
printLine("═");
|
|
174
|
+
// ── Initial state ──
|
|
175
|
+
const initState = engine.getState();
|
|
176
|
+
process.stdout.write(`\n ${c(C.dim, "Initial chemistry:")}\n`);
|
|
177
|
+
printChemistry(initState.current, initState.current, locale);
|
|
178
|
+
const initMood = describeMood(initState.current, displayLocale);
|
|
179
|
+
process.stdout.write(`\n ${c(C.dim, "mood:")} ${c(C.white + C.bold, initMood)}\n`);
|
|
180
|
+
await sleep(pause * 2);
|
|
181
|
+
// ── Rounds ──
|
|
182
|
+
for (let i = 0; i < SCENARIO.length; i++) {
|
|
183
|
+
const round = SCENARIO[i];
|
|
184
|
+
const prevState = { ...engine.getState().current };
|
|
185
|
+
process.stdout.write("\n");
|
|
186
|
+
printLine();
|
|
187
|
+
process.stdout.write(c(C.bold, ` Round ${i + 1}/${SCENARIO.length}`) +
|
|
188
|
+
c(C.dim, ` │ `) +
|
|
189
|
+
c(C.yellow, displayLocale === "zh" ? "用户" : "User") + "\n");
|
|
190
|
+
// User message (display in chosen locale, process in Chinese for classifier accuracy)
|
|
191
|
+
const displayText = displayLocale === "zh" ? round.display.zh : round.display.en;
|
|
192
|
+
process.stdout.write(` ${c(C.dim, ">")} ${c(C.white, `"${displayText}"`)}\n`);
|
|
193
|
+
await sleep(pause);
|
|
194
|
+
// Always process Chinese text for best classifier coverage
|
|
195
|
+
const result = await engine.processInput(round.input);
|
|
196
|
+
const currState = engine.getState();
|
|
197
|
+
// Stimulus
|
|
198
|
+
process.stdout.write(`\n ${c(C.dim, "stimulus:")} ${c(C.magenta, result.stimulus ?? "none")}\n`);
|
|
199
|
+
// Chemistry changes
|
|
200
|
+
process.stdout.write(`\n`);
|
|
201
|
+
printChemistry(prevState, currState.current, locale);
|
|
202
|
+
// Emergent mood
|
|
203
|
+
const mood = describeMood(currState.current, displayLocale);
|
|
204
|
+
const emotions = detectEmotions(currState.current);
|
|
205
|
+
process.stdout.write(`\n ${c(C.dim, "mood:")} ${c(C.bold, mood)}\n`);
|
|
206
|
+
// Policy context (if non-empty)
|
|
207
|
+
if (result.policyContext) {
|
|
208
|
+
process.stdout.write(` ${c(C.dim, "policy:")} ${c(C.yellow, result.policyContext.slice(0, 80))}\n`);
|
|
209
|
+
}
|
|
210
|
+
// Special alerts from policy context
|
|
211
|
+
if (result.policyContext) {
|
|
212
|
+
if (result.policyContext.includes("谄媚") || result.policyContext.includes("sycophancy")) {
|
|
213
|
+
printAlert("ANTI-SYCOPHANCY triggered");
|
|
214
|
+
}
|
|
215
|
+
if (result.policyContext.includes("防御") || result.policyContext.includes("defensive")) {
|
|
216
|
+
printInfo("DEFENSIVE STRATEGY active");
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Low compliance = pushing back
|
|
220
|
+
if (result.policyModifiers && result.policyModifiers.compliance < 0.4) {
|
|
221
|
+
printInfo(`COMPLIANCE: ${result.policyModifiers.compliance.toFixed(2)} (pushing back)`);
|
|
222
|
+
}
|
|
223
|
+
// Trait drift check
|
|
224
|
+
if (currState.traitDrift) {
|
|
225
|
+
const drifts = Object.entries(currState.traitDrift).filter(([, v]) => typeof v === "number" && Math.abs(v) >= 0.5);
|
|
226
|
+
if (drifts.length > 0) {
|
|
227
|
+
const driftStr = drifts.map(([k, v]) => `${k} ${v > 0 ? "+" : ""}${v.toFixed(1)}`).join(", ");
|
|
228
|
+
printAlert(`TRAIT DRIFT: ${driftStr} (irreversible)`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// Drive warnings
|
|
232
|
+
if (currState.drives) {
|
|
233
|
+
const low = Object.entries(currState.drives).filter(([, v]) => v < 40);
|
|
234
|
+
if (low.length > 0) {
|
|
235
|
+
const driveStr = low.map(([k, v]) => `${k}=${Math.round(v)}`).join(", ");
|
|
236
|
+
process.stdout.write(` ${c(C.red, `⚠ low drives: ${driveStr}`)}\n`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
await sleep(pause);
|
|
240
|
+
}
|
|
241
|
+
// ── Final summary ──
|
|
242
|
+
process.stdout.write("\n");
|
|
243
|
+
printLine("═");
|
|
244
|
+
process.stdout.write(c(C.bold, " Final State\n"));
|
|
245
|
+
printLine("═");
|
|
246
|
+
const finalState = engine.getState();
|
|
247
|
+
process.stdout.write("\n");
|
|
248
|
+
printChemistry(finalState.current, finalState.current, locale);
|
|
249
|
+
const finalMood = describeMood(finalState.current, displayLocale);
|
|
250
|
+
process.stdout.write(`\n ${c(C.dim, "mood:")} ${c(C.bold, finalMood)}\n`);
|
|
251
|
+
process.stdout.write(` ${c(C.dim, "summary:")} ${engine.getStatusSummary()}\n`);
|
|
252
|
+
// Drives
|
|
253
|
+
if (finalState.drives) {
|
|
254
|
+
process.stdout.write(`\n ${c(C.dim, "drives:")}\n`);
|
|
255
|
+
for (const [k, v] of Object.entries(finalState.drives)) {
|
|
256
|
+
const val = Math.round(v);
|
|
257
|
+
process.stdout.write(` ${k.padEnd(12)} ${bar(val, 15)} ${val}\n`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
process.stdout.write("\n");
|
|
261
|
+
printLine("─");
|
|
262
|
+
process.stdout.write(c(C.dim, " Try it yourself: ") +
|
|
263
|
+
c(C.cyan, "npx psyche-mcp") +
|
|
264
|
+
c(C.dim, " (configure in Claude Desktop / Cursor / Claude Code)\n"));
|
|
265
|
+
process.stdout.write(c(C.dim, " npm: ") +
|
|
266
|
+
c(C.cyan, "https://www.npmjs.com/package/psyche-ai") + "\n");
|
|
267
|
+
printLine("─");
|
|
268
|
+
process.stdout.write("\n");
|
|
269
|
+
}
|
package/dist/diagnostics.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PsycheState, ChemicalState, StimulusType, InnateDrives } from "./types.js";
|
|
1
|
+
import type { AppraisalAxes, PsycheState, ChemicalState, StimulusType, InnateDrives } from "./types.js";
|
|
2
2
|
export type Severity = "critical" | "warning" | "info";
|
|
3
3
|
export interface DiagnosticIssue {
|
|
4
4
|
id: string;
|
|
@@ -13,6 +13,10 @@ export interface SessionMetrics {
|
|
|
13
13
|
inputCount: number;
|
|
14
14
|
/** How many returned a non-null stimulus */
|
|
15
15
|
classifiedCount: number;
|
|
16
|
+
/** How many had a non-trivial appraisal hit (excluding taskFocus) */
|
|
17
|
+
appraisalHitCount: number;
|
|
18
|
+
/** How many were recognized by either stimulus or appraisal */
|
|
19
|
+
semanticHitCount: number;
|
|
16
20
|
/** Stimulus distribution */
|
|
17
21
|
stimulusDistribution: Partial<Record<StimulusType, number>>;
|
|
18
22
|
/** Average classification confidence */
|
|
@@ -55,19 +59,17 @@ export declare class DiagnosticCollector {
|
|
|
55
59
|
private metrics;
|
|
56
60
|
private prevChemistry;
|
|
57
61
|
private confidences;
|
|
58
|
-
/** Consecutive inputs with no classification — for real-time alerting */
|
|
59
|
-
private consecutiveNone;
|
|
60
|
-
/** Callback for real-time warnings (set by adapter) */
|
|
61
|
-
onWarning?: (message: string) => void;
|
|
62
62
|
constructor();
|
|
63
63
|
/** Record a processInput result */
|
|
64
|
-
recordInput(stimulus: StimulusType | null, confidence: number, chemistry: ChemicalState): void;
|
|
64
|
+
recordInput(stimulus: StimulusType | null, confidence: number, chemistry: ChemicalState, appraisal?: AppraisalAxes): void;
|
|
65
65
|
/** Record an error */
|
|
66
66
|
recordError(phase: string, error: unknown): void;
|
|
67
67
|
/** Get current session metrics */
|
|
68
68
|
getMetrics(): SessionMetrics;
|
|
69
69
|
/** Get classifier hit rate (0-1) */
|
|
70
70
|
getClassifierRate(): number;
|
|
71
|
+
/** Get semantic recognition rate (stimulus or appraisal, 0-1) */
|
|
72
|
+
getSemanticRate(): number;
|
|
71
73
|
}
|
|
72
74
|
export declare function generateReport(state: PsycheState, metrics: SessionMetrics, packageVersion: string): DiagnosticReport;
|
|
73
75
|
export declare function formatReport(report: DiagnosticReport): string;
|