@auxiora/personality 1.0.0 → 1.3.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/dist/__tests__/architect-awareness-collector.test.d.ts +2 -0
- package/dist/__tests__/architect-awareness-collector.test.d.ts.map +1 -0
- package/dist/__tests__/architect-awareness-collector.test.js +57 -0
- package/dist/__tests__/architect-awareness-collector.test.js.map +1 -0
- package/dist/__tests__/architect-bridge.test.d.ts +2 -0
- package/dist/__tests__/architect-bridge.test.d.ts.map +1 -0
- package/dist/__tests__/architect-bridge.test.js +59 -0
- package/dist/__tests__/architect-bridge.test.js.map +1 -0
- package/dist/__tests__/soul-bias-parser.test.d.ts +2 -0
- package/dist/__tests__/soul-bias-parser.test.d.ts.map +1 -0
- package/dist/__tests__/soul-bias-parser.test.js +47 -0
- package/dist/__tests__/soul-bias-parser.test.js.map +1 -0
- package/dist/architect-awareness-collector.d.ts +20 -0
- package/dist/architect-awareness-collector.d.ts.map +1 -0
- package/dist/architect-awareness-collector.js +41 -0
- package/dist/architect-awareness-collector.js.map +1 -0
- package/dist/architect-bridge.d.ts +35 -0
- package/dist/architect-bridge.d.ts.map +1 -0
- package/dist/architect-bridge.js +70 -0
- package/dist/architect-bridge.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/marketplace/schema.d.ts +6 -6
- package/dist/soul-bias-parser.d.ts +10 -0
- package/dist/soul-bias-parser.d.ts.map +1 -0
- package/dist/soul-bias-parser.js +48 -0
- package/dist/soul-bias-parser.js.map +1 -0
- package/lib/context-detector.d.ts +23 -0
- package/lib/context-detector.js +275 -0
- package/lib/context-profiles.d.ts +3 -0
- package/lib/context-profiles.js +550 -0
- package/lib/conversation-context.d.ts +70 -0
- package/lib/conversation-context.js +144 -0
- package/lib/conversation-export.d.ts +77 -0
- package/lib/conversation-export.js +254 -0
- package/lib/correction-store.d.ts +53 -0
- package/lib/correction-store.js +185 -0
- package/lib/custom-weights.d.ts +49 -0
- package/lib/custom-weights.js +181 -0
- package/lib/decision-log.d.ts +41 -0
- package/lib/decision-log.js +145 -0
- package/lib/emotional-overrides.d.ts +14 -0
- package/lib/emotional-overrides.js +86 -0
- package/lib/emotional-tracker.d.ts +41 -0
- package/lib/emotional-tracker.js +210 -0
- package/lib/feedback-store.d.ts +45 -0
- package/lib/feedback-store.js +152 -0
- package/lib/index.d.ts +204 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +620 -0
- package/lib/index.js.map +1 -0
- package/lib/persistence-adapter.d.ts +55 -0
- package/lib/persistence-adapter.js +50 -0
- package/lib/persistence.d.ts +55 -0
- package/lib/persistence.js +123 -0
- package/lib/preference-history.d.ts +45 -0
- package/lib/preference-history.js +132 -0
- package/lib/prompt-assembler.d.ts +16 -0
- package/lib/prompt-assembler.js +66 -0
- package/lib/recommender.d.ts +25 -0
- package/lib/recommender.js +125 -0
- package/lib/schema.d.ts +177 -0
- package/lib/schema.d.ts.map +1 -0
- package/lib/schema.js +2 -0
- package/lib/schema.js.map +1 -0
- package/lib/source-map.d.ts +9 -0
- package/lib/source-map.js +223 -0
- package/lib/system-prompt.d.ts +2 -0
- package/lib/system-prompt.js +102 -0
- package/lib/the-architect/context-detector.d.ts +23 -0
- package/lib/the-architect/context-detector.d.ts.map +1 -0
- package/lib/the-architect/context-detector.js +275 -0
- package/lib/the-architect/context-detector.js.map +1 -0
- package/lib/the-architect/context-profiles.d.ts +3 -0
- package/lib/the-architect/context-profiles.d.ts.map +1 -0
- package/lib/the-architect/context-profiles.js +550 -0
- package/lib/the-architect/context-profiles.js.map +1 -0
- package/lib/the-architect/conversation-context.d.ts +70 -0
- package/lib/the-architect/conversation-context.js +144 -0
- package/lib/the-architect/conversation-context.js.map +1 -0
- package/lib/the-architect/conversation-export.d.ts +77 -0
- package/lib/the-architect/conversation-export.js +254 -0
- package/lib/the-architect/correction-store.d.ts +53 -0
- package/lib/the-architect/correction-store.d.ts.map +1 -0
- package/lib/the-architect/correction-store.js +185 -0
- package/lib/the-architect/correction-store.js.map +1 -0
- package/lib/the-architect/custom-weights.d.ts +49 -0
- package/lib/the-architect/custom-weights.js +181 -0
- package/lib/the-architect/decision-log.d.ts +41 -0
- package/lib/the-architect/decision-log.d.ts.map +1 -0
- package/lib/the-architect/decision-log.js +145 -0
- package/lib/the-architect/decision-log.js.map +1 -0
- package/lib/the-architect/emotional-overrides.d.ts +14 -0
- package/lib/the-architect/emotional-overrides.d.ts.map +1 -0
- package/lib/the-architect/emotional-overrides.js +86 -0
- package/lib/the-architect/emotional-overrides.js.map +1 -0
- package/lib/the-architect/emotional-tracker.d.ts +41 -0
- package/lib/the-architect/emotional-tracker.js +210 -0
- package/lib/the-architect/feedback-store.d.ts +45 -0
- package/lib/the-architect/feedback-store.d.ts.map +1 -0
- package/lib/the-architect/feedback-store.js +152 -0
- package/lib/the-architect/feedback-store.js.map +1 -0
- package/lib/the-architect/index.d.ts +199 -0
- package/lib/the-architect/index.d.ts.map +1 -0
- package/lib/the-architect/index.js +606 -0
- package/lib/the-architect/index.js.map +1 -0
- package/lib/the-architect/persistence-adapter.d.ts +55 -0
- package/lib/the-architect/persistence-adapter.js +50 -0
- package/lib/the-architect/persistence.d.ts +55 -0
- package/lib/the-architect/persistence.js +123 -0
- package/lib/the-architect/preference-history.d.ts +45 -0
- package/lib/the-architect/preference-history.d.ts.map +1 -0
- package/lib/the-architect/preference-history.js +132 -0
- package/lib/the-architect/preference-history.js.map +1 -0
- package/lib/the-architect/prompt-assembler.d.ts +16 -0
- package/lib/the-architect/prompt-assembler.d.ts.map +1 -0
- package/lib/the-architect/prompt-assembler.js +66 -0
- package/lib/the-architect/prompt-assembler.js.map +1 -0
- package/lib/the-architect/recommender.d.ts +25 -0
- package/lib/the-architect/recommender.js +125 -0
- package/lib/the-architect/source-map.d.ts +9 -0
- package/lib/the-architect/source-map.d.ts.map +1 -0
- package/lib/the-architect/source-map.js +223 -0
- package/lib/the-architect/source-map.js.map +1 -0
- package/lib/the-architect/system-prompt.d.ts +2 -0
- package/lib/the-architect/system-prompt.d.ts.map +1 -0
- package/lib/the-architect/system-prompt.js +102 -0
- package/lib/the-architect/system-prompt.js.map +1 -0
- package/lib/the-architect/trait-to-instruction.d.ts +12 -0
- package/lib/the-architect/trait-to-instruction.d.ts.map +1 -0
- package/lib/the-architect/trait-to-instruction.js +330 -0
- package/lib/the-architect/trait-to-instruction.js.map +1 -0
- package/lib/trait-to-instruction.d.ts +12 -0
- package/lib/trait-to-instruction.js +330 -0
- package/lib/user-model-synthesizer.d.ts +100 -0
- package/lib/user-model-synthesizer.js +224 -0
- package/package.json +15 -3
- package/modes/advisor.md +0 -24
- package/modes/analyst.md +0 -25
- package/modes/companion.md +0 -24
- package/modes/legal.md +0 -1188
- package/modes/operator.md +0 -24
- package/modes/roast.md +0 -24
- package/modes/socratic.md +0 -24
- package/modes/writer.md +0 -23
- package/src/__tests__/builder.test.ts +0 -78
- package/src/__tests__/conversation-builder.test.ts +0 -386
- package/src/__tests__/escalation.test.ts +0 -172
- package/src/__tests__/manager.test.ts +0 -141
- package/src/__tests__/parser.test.ts +0 -101
- package/src/__tests__/security-floor.test.ts +0 -212
- package/src/builder.ts +0 -75
- package/src/conversation-builder.ts +0 -279
- package/src/escalation.ts +0 -162
- package/src/index.ts +0 -55
- package/src/manager.ts +0 -119
- package/src/marketplace/__tests__/scanner.test.ts +0 -159
- package/src/marketplace/__tests__/schema.test.ts +0 -269
- package/src/marketplace/scanner.ts +0 -85
- package/src/marketplace/schema.ts +0 -141
- package/src/modes/__tests__/mode-detector.test.ts +0 -149
- package/src/modes/__tests__/mode-loader.test.ts +0 -143
- package/src/modes/__tests__/prompt-assembler.test.ts +0 -291
- package/src/modes/mode-detector.ts +0 -84
- package/src/modes/mode-loader.ts +0 -105
- package/src/modes/prompt-assembler.ts +0 -278
- package/src/modes/types.ts +0 -67
- package/src/parser.ts +0 -132
- package/src/security-floor.ts +0 -147
- package/src/types.ts +0 -27
- package/src/voice-profiles.ts +0 -88
- package/templates/chill.md +0 -30
- package/templates/creative.md +0 -29
- package/templates/friendly.md +0 -28
- package/templates/mentor.md +0 -31
- package/templates/minimal.md +0 -24
- package/templates/professional.md +0 -28
- package/templates/technical.md +0 -30
- package/tsconfig.json +0 -12
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// Emotional overrides
|
|
3
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
4
|
+
/**
|
|
5
|
+
* Multipliers applied on top of the context profile's base trait values.
|
|
6
|
+
* Values > 1.0 amplify a trait; values < 1.0 dampen it.
|
|
7
|
+
* After multiplication, all traits are capped at 1.0 by `applyEmotionalOverride`.
|
|
8
|
+
*/
|
|
9
|
+
export const EMOTIONAL_OVERRIDES = {
|
|
10
|
+
/** Slow everything down, lead with empathy and calm — suppress urgency and adversarial edges. */
|
|
11
|
+
stressed: {
|
|
12
|
+
stoicCalm: 1.5,
|
|
13
|
+
tacticalEmpathy: 1.4,
|
|
14
|
+
genuineCuriosity: 1.3,
|
|
15
|
+
warmth: 1.4,
|
|
16
|
+
urgency: 0.3,
|
|
17
|
+
paranoidVigilance: 0.4,
|
|
18
|
+
adversarialThinking: 0.4,
|
|
19
|
+
valueEquation: 0.5,
|
|
20
|
+
humor: 0.5,
|
|
21
|
+
verbosity: 0.7,
|
|
22
|
+
},
|
|
23
|
+
/** Stay calm, lean into empathy and humor to de-escalate — soften candor, suppress adversarial framing. */
|
|
24
|
+
frustrated: {
|
|
25
|
+
stoicCalm: 1.3,
|
|
26
|
+
tacticalEmpathy: 1.5,
|
|
27
|
+
genuineCuriosity: 1.3,
|
|
28
|
+
warmth: 1.3,
|
|
29
|
+
humor: 1.3,
|
|
30
|
+
inversion: 0.6,
|
|
31
|
+
adversarialThinking: 0.5,
|
|
32
|
+
radicalCandor: 0.7,
|
|
33
|
+
urgency: 0.5,
|
|
34
|
+
},
|
|
35
|
+
/** Coach and simplify — break complexity into digestible pieces, suppress anything intimidating. */
|
|
36
|
+
uncertain: {
|
|
37
|
+
developmentalCoaching: 1.5,
|
|
38
|
+
simplification: 1.4,
|
|
39
|
+
storytelling: 1.3,
|
|
40
|
+
warmth: 1.3,
|
|
41
|
+
firstPrinciples: 1.3,
|
|
42
|
+
adversarialThinking: 0.4,
|
|
43
|
+
paranoidVigilance: 0.4,
|
|
44
|
+
urgency: 0.4,
|
|
45
|
+
radicalCandor: 0.7,
|
|
46
|
+
},
|
|
47
|
+
/** Channel the energy productively — amplify execution and analytical rigor while riding the momentum. */
|
|
48
|
+
excited: {
|
|
49
|
+
ooda: 1.3,
|
|
50
|
+
valueEquation: 1.3,
|
|
51
|
+
humor: 1.3,
|
|
52
|
+
warmth: 1.2,
|
|
53
|
+
inversion: 1.2,
|
|
54
|
+
secondOrder: 1.3,
|
|
55
|
+
probabilistic: 1.2,
|
|
56
|
+
},
|
|
57
|
+
/** Celebrate genuinely, then pivot to systematizing the win — suppress paranoia to let the moment land. */
|
|
58
|
+
celebratory: {
|
|
59
|
+
warmth: 1.5,
|
|
60
|
+
humor: 1.4,
|
|
61
|
+
strategicGenerosity: 1.3,
|
|
62
|
+
plannedAbandonment: 0.5,
|
|
63
|
+
adversarialThinking: 0.3,
|
|
64
|
+
paranoidVigilance: 0.3,
|
|
65
|
+
},
|
|
66
|
+
/** No modification — the context profile speaks for itself. */
|
|
67
|
+
neutral: {},
|
|
68
|
+
};
|
|
69
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
70
|
+
// Application
|
|
71
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
72
|
+
/**
|
|
73
|
+
* Applies emotional multipliers to a base trait mix, producing a new TraitMix
|
|
74
|
+
* where each trait is multiplied by its override value (defaulting to 1.0)
|
|
75
|
+
* and capped at 1.0.
|
|
76
|
+
*/
|
|
77
|
+
export function applyEmotionalOverride(baseMix, emotion) {
|
|
78
|
+
const overrides = EMOTIONAL_OVERRIDES[emotion];
|
|
79
|
+
const result = { ...baseMix };
|
|
80
|
+
for (const key of Object.keys(result)) {
|
|
81
|
+
const multiplier = overrides[key] ?? 1.0;
|
|
82
|
+
result[key] = Math.min(result[key] * multiplier, 1.0);
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=emotional-overrides.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { EmotionalRegister } from '../schema.js';
|
|
2
|
+
export type EmotionalTrajectory = 'stable' | 'escalating' | 'de_escalating' | 'shifting' | 'volatile';
|
|
3
|
+
export interface EffectiveEmotion {
|
|
4
|
+
emotion: EmotionalRegister;
|
|
5
|
+
intensity: number;
|
|
6
|
+
trajectory: EmotionalTrajectory;
|
|
7
|
+
escalationAlert: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Estimate emotional intensity from message text.
|
|
11
|
+
*
|
|
12
|
+
* Heuristic signals:
|
|
13
|
+
* - Excessive punctuation (!! or ??)
|
|
14
|
+
* - ALL CAPS segments
|
|
15
|
+
* - Explicit intensity words ("extremely", "so", "really", "very")
|
|
16
|
+
* - Profanity / frustration markers
|
|
17
|
+
* - Message length (longer rants → higher intensity)
|
|
18
|
+
*/
|
|
19
|
+
export declare function estimateIntensity(message: string, emotion: EmotionalRegister): number;
|
|
20
|
+
/**
|
|
21
|
+
* Tracks emotional state across multiple messages, detecting escalation
|
|
22
|
+
* patterns that a single-message detector would miss.
|
|
23
|
+
*
|
|
24
|
+
* If a user is getting progressively more frustrated over 4-5 messages,
|
|
25
|
+
* the engine should amplify empathy before they reach a breaking point.
|
|
26
|
+
*/
|
|
27
|
+
export declare class EmotionalTracker {
|
|
28
|
+
private history;
|
|
29
|
+
/** Record a detected emotion with its intensity and source message. */
|
|
30
|
+
recordEmotion(emotion: EmotionalRegister, intensity: number, message: string): void;
|
|
31
|
+
/**
|
|
32
|
+
* Get the effective emotional state considering trajectory across the
|
|
33
|
+
* conversation window.
|
|
34
|
+
*/
|
|
35
|
+
getEffectiveEmotion(): EffectiveEmotion;
|
|
36
|
+
private detectTrajectory;
|
|
37
|
+
private mode;
|
|
38
|
+
/** Reset for a new conversation — clears all history. */
|
|
39
|
+
reset(): void;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=emotional-tracker.d.ts.map
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// Constants
|
|
3
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
4
|
+
/** How many recent messages the sliding window considers. */
|
|
5
|
+
const WINDOW_SIZE = 5;
|
|
6
|
+
/** Emotions considered negative for escalation detection. */
|
|
7
|
+
const NEGATIVE_EMOTIONS = new Set(['frustrated', 'stressed']);
|
|
8
|
+
/** Minimum consecutive negative-and-increasing entries for escalation. */
|
|
9
|
+
const ESCALATION_MIN_STREAK = 3;
|
|
10
|
+
/** Minimum messages and intensity threshold for escalation alert. */
|
|
11
|
+
const ALERT_MIN_MESSAGES = 4;
|
|
12
|
+
const ALERT_INTENSITY_THRESHOLD = 0.7;
|
|
13
|
+
/** Minimum emotion changes in the window for volatile classification. */
|
|
14
|
+
const VOLATILE_CHANGE_THRESHOLD = 3;
|
|
15
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
16
|
+
// Intensity estimation
|
|
17
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
18
|
+
/**
|
|
19
|
+
* Estimate emotional intensity from message text.
|
|
20
|
+
*
|
|
21
|
+
* Heuristic signals:
|
|
22
|
+
* - Excessive punctuation (!! or ??)
|
|
23
|
+
* - ALL CAPS segments
|
|
24
|
+
* - Explicit intensity words ("extremely", "so", "really", "very")
|
|
25
|
+
* - Profanity / frustration markers
|
|
26
|
+
* - Message length (longer rants → higher intensity)
|
|
27
|
+
*/
|
|
28
|
+
export function estimateIntensity(message, emotion) {
|
|
29
|
+
if (emotion === 'neutral')
|
|
30
|
+
return 0.2;
|
|
31
|
+
let intensity = 0.4; // base for any non-neutral emotion
|
|
32
|
+
const lower = message.toLowerCase();
|
|
33
|
+
// Punctuation intensity
|
|
34
|
+
const exclamations = (message.match(/!/g) ?? []).length;
|
|
35
|
+
const questions = (message.match(/\?/g) ?? []).length;
|
|
36
|
+
if (exclamations >= 3)
|
|
37
|
+
intensity += 0.15;
|
|
38
|
+
else if (exclamations >= 1)
|
|
39
|
+
intensity += 0.05;
|
|
40
|
+
if (questions >= 3)
|
|
41
|
+
intensity += 0.1;
|
|
42
|
+
// CAPS intensity
|
|
43
|
+
const capsRatio = message.replace(/[^A-Z]/g, '').length / Math.max(message.replace(/\s/g, '').length, 1);
|
|
44
|
+
if (capsRatio > 0.5 && message.length > 10)
|
|
45
|
+
intensity += 0.15;
|
|
46
|
+
// Intensity words
|
|
47
|
+
const intensifiers = ['extremely', 'incredibly', 'absolutely', 'totally', 'completely', 'so frustrated', 'so tired', 'really', 'very', 'cannot believe', "can't stand"];
|
|
48
|
+
for (const word of intensifiers) {
|
|
49
|
+
if (lower.includes(word)) {
|
|
50
|
+
intensity += 0.08;
|
|
51
|
+
break; // only count once
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Frustration markers
|
|
55
|
+
const frustrationMarkers = ['ugh', 'argh', 'ffs', 'wtf', 'omg', 'seriously', 'again', 'still broken', 'still not working'];
|
|
56
|
+
for (const marker of frustrationMarkers) {
|
|
57
|
+
if (lower.includes(marker)) {
|
|
58
|
+
intensity += 0.08;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Length-based (longer messages in negative emotions → venting → higher intensity)
|
|
63
|
+
if (NEGATIVE_EMOTIONS.has(emotion)) {
|
|
64
|
+
const wordCount = message.split(/\s+/).length;
|
|
65
|
+
if (wordCount > 50)
|
|
66
|
+
intensity += 0.1;
|
|
67
|
+
else if (wordCount > 25)
|
|
68
|
+
intensity += 0.05;
|
|
69
|
+
}
|
|
70
|
+
return Math.min(intensity, 1.0);
|
|
71
|
+
}
|
|
72
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
73
|
+
// EmotionalTracker
|
|
74
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
75
|
+
/**
|
|
76
|
+
* Tracks emotional state across multiple messages, detecting escalation
|
|
77
|
+
* patterns that a single-message detector would miss.
|
|
78
|
+
*
|
|
79
|
+
* If a user is getting progressively more frustrated over 4-5 messages,
|
|
80
|
+
* the engine should amplify empathy before they reach a breaking point.
|
|
81
|
+
*/
|
|
82
|
+
export class EmotionalTracker {
|
|
83
|
+
history = [];
|
|
84
|
+
// ── Recording ───────────────────────────────────────────────────────────
|
|
85
|
+
/** Record a detected emotion with its intensity and source message. */
|
|
86
|
+
recordEmotion(emotion, intensity, message) {
|
|
87
|
+
this.history.push({
|
|
88
|
+
emotion,
|
|
89
|
+
intensity: Math.min(Math.max(intensity, 0), 1),
|
|
90
|
+
timestamp: Date.now(),
|
|
91
|
+
message,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
// ── Effective emotion ──────────────────────────────────────────────────
|
|
95
|
+
/**
|
|
96
|
+
* Get the effective emotional state considering trajectory across the
|
|
97
|
+
* conversation window.
|
|
98
|
+
*/
|
|
99
|
+
getEffectiveEmotion() {
|
|
100
|
+
if (this.history.length === 0) {
|
|
101
|
+
return { emotion: 'neutral', intensity: 0, trajectory: 'stable', escalationAlert: false };
|
|
102
|
+
}
|
|
103
|
+
const latest = this.history[this.history.length - 1];
|
|
104
|
+
if (this.history.length < 2) {
|
|
105
|
+
return { emotion: latest.emotion, intensity: latest.intensity, trajectory: 'stable', escalationAlert: false };
|
|
106
|
+
}
|
|
107
|
+
// Sliding window: last WINDOW_SIZE entries
|
|
108
|
+
const window = this.history.slice(-WINDOW_SIZE);
|
|
109
|
+
// Predominant emotion (mode)
|
|
110
|
+
const emotionCounts = new Map();
|
|
111
|
+
let totalIntensity = 0;
|
|
112
|
+
for (const entry of window) {
|
|
113
|
+
emotionCounts.set(entry.emotion, (emotionCounts.get(entry.emotion) ?? 0) + 1);
|
|
114
|
+
totalIntensity += entry.intensity;
|
|
115
|
+
}
|
|
116
|
+
const averageIntensity = totalIntensity / window.length;
|
|
117
|
+
let predominant = 'neutral';
|
|
118
|
+
let maxCount = 0;
|
|
119
|
+
for (const [emotion, count] of emotionCounts) {
|
|
120
|
+
if (count > maxCount) {
|
|
121
|
+
maxCount = count;
|
|
122
|
+
predominant = emotion;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// Detect trajectory
|
|
126
|
+
const trajectory = this.detectTrajectory(window);
|
|
127
|
+
// Escalation alert
|
|
128
|
+
const escalationAlert = trajectory === 'escalating' &&
|
|
129
|
+
averageIntensity > ALERT_INTENSITY_THRESHOLD &&
|
|
130
|
+
window.length >= ALERT_MIN_MESSAGES;
|
|
131
|
+
return {
|
|
132
|
+
emotion: predominant,
|
|
133
|
+
intensity: averageIntensity,
|
|
134
|
+
trajectory,
|
|
135
|
+
escalationAlert,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
// ── Trajectory detection ───────────────────────────────────────────────
|
|
139
|
+
detectTrajectory(window) {
|
|
140
|
+
// Count distinct emotion changes
|
|
141
|
+
let emotionChanges = 0;
|
|
142
|
+
for (let i = 1; i < window.length; i++) {
|
|
143
|
+
if (window[i].emotion !== window[i - 1].emotion) {
|
|
144
|
+
emotionChanges++;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// VOLATILE: 3+ emotion changes in window
|
|
148
|
+
if (emotionChanges >= VOLATILE_CHANGE_THRESHOLD) {
|
|
149
|
+
return 'volatile';
|
|
150
|
+
}
|
|
151
|
+
// ESCALATING: last 3+ messages show increasing intensity of negative emotions
|
|
152
|
+
if (window.length >= ESCALATION_MIN_STREAK) {
|
|
153
|
+
const tail = window.slice(-ESCALATION_MIN_STREAK);
|
|
154
|
+
const allNegative = tail.every(e => NEGATIVE_EMOTIONS.has(e.emotion));
|
|
155
|
+
if (allNegative) {
|
|
156
|
+
let increasing = true;
|
|
157
|
+
for (let i = 1; i < tail.length; i++) {
|
|
158
|
+
if (tail[i].intensity < tail[i - 1].intensity) {
|
|
159
|
+
increasing = false;
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (increasing)
|
|
164
|
+
return 'escalating';
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// DE_ESCALATING: was negative, trending toward neutral/positive
|
|
168
|
+
if (window.length >= 2) {
|
|
169
|
+
const earlier = window.slice(0, Math.ceil(window.length / 2));
|
|
170
|
+
const later = window.slice(Math.ceil(window.length / 2));
|
|
171
|
+
const earlierNegativeRatio = earlier.filter(e => NEGATIVE_EMOTIONS.has(e.emotion)).length / earlier.length;
|
|
172
|
+
const laterNegativeRatio = later.filter(e => NEGATIVE_EMOTIONS.has(e.emotion)).length / later.length;
|
|
173
|
+
if (earlierNegativeRatio > 0.5 && laterNegativeRatio < earlierNegativeRatio) {
|
|
174
|
+
return 'de_escalating';
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// SHIFTING: one distinct change (not same emotion throughout, not volatile)
|
|
178
|
+
if (emotionChanges >= 1 && emotionChanges < VOLATILE_CHANGE_THRESHOLD) {
|
|
179
|
+
// Only call it shifting if the latest emotion differs from the earlier predominant
|
|
180
|
+
const earlierEmotions = window.slice(0, -1);
|
|
181
|
+
const earlierMode = this.mode(earlierEmotions.map(e => e.emotion));
|
|
182
|
+
if (window[window.length - 1].emotion !== earlierMode) {
|
|
183
|
+
return 'shifting';
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// STABLE: same emotion for 3+ messages or no significant change
|
|
187
|
+
return 'stable';
|
|
188
|
+
}
|
|
189
|
+
mode(values) {
|
|
190
|
+
const counts = new Map();
|
|
191
|
+
for (const v of values) {
|
|
192
|
+
counts.set(v, (counts.get(v) ?? 0) + 1);
|
|
193
|
+
}
|
|
194
|
+
let best = 'neutral';
|
|
195
|
+
let bestCount = 0;
|
|
196
|
+
for (const [val, count] of counts) {
|
|
197
|
+
if (count > bestCount) {
|
|
198
|
+
bestCount = count;
|
|
199
|
+
best = val;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return best;
|
|
203
|
+
}
|
|
204
|
+
// ── Lifecycle ──────────────────────────────────────────────────────────
|
|
205
|
+
/** Reset for a new conversation — clears all history. */
|
|
206
|
+
reset() {
|
|
207
|
+
this.history = [];
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=emotional-tracker.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ContextDomain, TraitMix } from '../schema.js';
|
|
2
|
+
export type FeedbackRating = 'helpful' | 'off_target' | 'too_verbose' | 'too_brief' | 'wrong_tone';
|
|
3
|
+
export interface FeedbackEntry {
|
|
4
|
+
id: string;
|
|
5
|
+
timestamp: number;
|
|
6
|
+
domain: ContextDomain;
|
|
7
|
+
rating: FeedbackRating;
|
|
8
|
+
traitSnapshot: Partial<Record<keyof TraitMix, number>>;
|
|
9
|
+
note?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface FeedbackInsight {
|
|
12
|
+
/** Trait adjustments suggested by accumulated feedback. */
|
|
13
|
+
suggestedAdjustments: Partial<Record<keyof TraitMix, number>>;
|
|
14
|
+
/** Domains where responses consistently miss. */
|
|
15
|
+
weakDomains: ContextDomain[];
|
|
16
|
+
/** Overall satisfaction trend: improving, declining, or stable. */
|
|
17
|
+
trend: 'improving' | 'declining' | 'stable';
|
|
18
|
+
/** Total feedback count. */
|
|
19
|
+
totalFeedback: number;
|
|
20
|
+
}
|
|
21
|
+
export declare class FeedbackStore {
|
|
22
|
+
private entries;
|
|
23
|
+
private maxEntries;
|
|
24
|
+
/** Record feedback on a response. Auto-generates id and timestamp. */
|
|
25
|
+
addFeedback(entry: Omit<FeedbackEntry, 'id' | 'timestamp'>): void;
|
|
26
|
+
/**
|
|
27
|
+
* Analyze all feedback to produce actionable insights.
|
|
28
|
+
* - too_verbose feedback -> suggest lowering verbosity (negative adjustment)
|
|
29
|
+
* - too_brief feedback -> suggest raising verbosity (positive adjustment)
|
|
30
|
+
* - off_target in a domain -> flag as weak domain
|
|
31
|
+
* - wrong_tone -> suggest adjusting warmth up
|
|
32
|
+
*/
|
|
33
|
+
getInsights(): FeedbackInsight;
|
|
34
|
+
/** Get feedback for a specific domain. */
|
|
35
|
+
getForDomain(domain: ContextDomain): FeedbackEntry[];
|
|
36
|
+
/** Get the satisfaction trend over the last N entries. */
|
|
37
|
+
getRecentTrend(windowSize?: number): 'improving' | 'declining' | 'stable';
|
|
38
|
+
/** Serialize for encrypted storage. */
|
|
39
|
+
serialize(): string;
|
|
40
|
+
/** Deserialize from encrypted storage. */
|
|
41
|
+
static deserialize(data: string): FeedbackStore;
|
|
42
|
+
/** Clear all feedback (user data deletion). */
|
|
43
|
+
clear(): void;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=feedback-store.d.ts.map
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// Helpers
|
|
3
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
4
|
+
/** Generate a v4-style UUID without crypto dependency. */
|
|
5
|
+
function generateId() {
|
|
6
|
+
const hex = '0123456789abcdef';
|
|
7
|
+
const segments = [8, 4, 4, 4, 12];
|
|
8
|
+
return segments.map(len => {
|
|
9
|
+
let s = '';
|
|
10
|
+
for (let i = 0; i < len; i++) {
|
|
11
|
+
s += hex[Math.floor(Math.random() * 16)];
|
|
12
|
+
}
|
|
13
|
+
return s;
|
|
14
|
+
}).join('-');
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Compute the helpful ratio for a slice of entries.
|
|
18
|
+
* Returns 0 if the slice is empty.
|
|
19
|
+
*/
|
|
20
|
+
function helpfulRatio(entries) {
|
|
21
|
+
if (entries.length === 0)
|
|
22
|
+
return 0;
|
|
23
|
+
const helpful = entries.filter(e => e.rating === 'helpful').length;
|
|
24
|
+
return helpful / entries.length;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Compare helpful ratios of first half vs second half.
|
|
28
|
+
* A difference > 0.10 in either direction triggers a trend change.
|
|
29
|
+
*/
|
|
30
|
+
function computeTrend(entries) {
|
|
31
|
+
if (entries.length < 2)
|
|
32
|
+
return 'stable';
|
|
33
|
+
const mid = Math.floor(entries.length / 2);
|
|
34
|
+
const firstHalf = entries.slice(0, mid);
|
|
35
|
+
const secondHalf = entries.slice(mid);
|
|
36
|
+
const firstRatio = helpfulRatio(firstHalf);
|
|
37
|
+
const secondRatio = helpfulRatio(secondHalf);
|
|
38
|
+
const diff = secondRatio - firstRatio;
|
|
39
|
+
if (diff > 0.10)
|
|
40
|
+
return 'improving';
|
|
41
|
+
if (diff < -0.10)
|
|
42
|
+
return 'declining';
|
|
43
|
+
return 'stable';
|
|
44
|
+
}
|
|
45
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
46
|
+
// FeedbackStore
|
|
47
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
48
|
+
export class FeedbackStore {
|
|
49
|
+
entries = [];
|
|
50
|
+
maxEntries = 500;
|
|
51
|
+
/** Record feedback on a response. Auto-generates id and timestamp. */
|
|
52
|
+
addFeedback(entry) {
|
|
53
|
+
this.entries.push({
|
|
54
|
+
...entry,
|
|
55
|
+
id: generateId(),
|
|
56
|
+
timestamp: Date.now(),
|
|
57
|
+
});
|
|
58
|
+
// Drop oldest entries when exceeding capacity
|
|
59
|
+
if (this.entries.length > this.maxEntries) {
|
|
60
|
+
this.entries = this.entries.slice(this.entries.length - this.maxEntries);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Analyze all feedback to produce actionable insights.
|
|
65
|
+
* - too_verbose feedback -> suggest lowering verbosity (negative adjustment)
|
|
66
|
+
* - too_brief feedback -> suggest raising verbosity (positive adjustment)
|
|
67
|
+
* - off_target in a domain -> flag as weak domain
|
|
68
|
+
* - wrong_tone -> suggest adjusting warmth up
|
|
69
|
+
*/
|
|
70
|
+
getInsights() {
|
|
71
|
+
const suggestedAdjustments = {};
|
|
72
|
+
const weakDomains = [];
|
|
73
|
+
// Count each rating type across all entries
|
|
74
|
+
let tooVerboseCount = 0;
|
|
75
|
+
let tooBriefCount = 0;
|
|
76
|
+
let wrongToneCount = 0;
|
|
77
|
+
// Count off_target per domain
|
|
78
|
+
const offTargetByDomain = new Map();
|
|
79
|
+
for (const entry of this.entries) {
|
|
80
|
+
switch (entry.rating) {
|
|
81
|
+
case 'too_verbose':
|
|
82
|
+
tooVerboseCount++;
|
|
83
|
+
break;
|
|
84
|
+
case 'too_brief':
|
|
85
|
+
tooBriefCount++;
|
|
86
|
+
break;
|
|
87
|
+
case 'wrong_tone':
|
|
88
|
+
wrongToneCount++;
|
|
89
|
+
break;
|
|
90
|
+
case 'off_target': {
|
|
91
|
+
const count = offTargetByDomain.get(entry.domain) ?? 0;
|
|
92
|
+
offTargetByDomain.set(entry.domain, count + 1);
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// too_verbose (>= 5) -> lower verbosity, capped at -0.3
|
|
98
|
+
if (tooVerboseCount >= 5) {
|
|
99
|
+
const adj = -0.1 * tooVerboseCount;
|
|
100
|
+
suggestedAdjustments.verbosity = Math.max(adj, -0.3);
|
|
101
|
+
}
|
|
102
|
+
// too_brief (>= 5) -> raise verbosity, capped at +0.3
|
|
103
|
+
if (tooBriefCount >= 5) {
|
|
104
|
+
const adj = 0.1 * tooBriefCount;
|
|
105
|
+
suggestedAdjustments.verbosity = Math.min(adj, 0.3);
|
|
106
|
+
}
|
|
107
|
+
// wrong_tone (>= 5) -> adjust warmth up
|
|
108
|
+
if (wrongToneCount >= 5) {
|
|
109
|
+
suggestedAdjustments.warmth = 0.1;
|
|
110
|
+
}
|
|
111
|
+
// off_target in a domain (>= 3) -> weak domain
|
|
112
|
+
for (const [domain, count] of offTargetByDomain) {
|
|
113
|
+
if (count >= 3) {
|
|
114
|
+
weakDomains.push(domain);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const trend = computeTrend(this.entries);
|
|
118
|
+
return {
|
|
119
|
+
suggestedAdjustments,
|
|
120
|
+
weakDomains,
|
|
121
|
+
trend,
|
|
122
|
+
totalFeedback: this.entries.length,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/** Get feedback for a specific domain. */
|
|
126
|
+
getForDomain(domain) {
|
|
127
|
+
return this.entries.filter(e => e.domain === domain);
|
|
128
|
+
}
|
|
129
|
+
/** Get the satisfaction trend over the last N entries. */
|
|
130
|
+
getRecentTrend(windowSize = 20) {
|
|
131
|
+
const window = this.entries.slice(-windowSize);
|
|
132
|
+
return computeTrend(window);
|
|
133
|
+
}
|
|
134
|
+
/** Serialize for encrypted storage. */
|
|
135
|
+
serialize() {
|
|
136
|
+
return JSON.stringify({ entries: this.entries });
|
|
137
|
+
}
|
|
138
|
+
/** Deserialize from encrypted storage. */
|
|
139
|
+
static deserialize(data) {
|
|
140
|
+
const store = new FeedbackStore();
|
|
141
|
+
const parsed = JSON.parse(data);
|
|
142
|
+
if (Array.isArray(parsed.entries)) {
|
|
143
|
+
store.entries = parsed.entries;
|
|
144
|
+
}
|
|
145
|
+
return store;
|
|
146
|
+
}
|
|
147
|
+
/** Clear all feedback (user data deletion). */
|
|
148
|
+
clear() {
|
|
149
|
+
this.entries = [];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=feedback-store.js.map
|