anno-extension 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -0
- package/README.md +30 -0
- package/THIRD_PARTY_NOTICES.md +8 -0
- package/dist/browser-fs.d.ts +14 -0
- package/dist/browser-fs.d.ts.map +1 -0
- package/dist/browser-fs.js +69 -0
- package/dist/browser-fs.js.map +1 -0
- package/dist/config.d.ts +81 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +82 -0
- package/dist/config.js.map +1 -0
- package/dist/consolidate-v2.d.ts +15 -0
- package/dist/consolidate-v2.d.ts.map +1 -0
- package/dist/consolidate-v2.js +125 -0
- package/dist/consolidate-v2.js.map +1 -0
- package/dist/consolidation.d.ts +12 -0
- package/dist/consolidation.d.ts.map +1 -0
- package/dist/consolidation.js +199 -0
- package/dist/consolidation.js.map +1 -0
- package/dist/dedup.d.ts +18 -0
- package/dist/dedup.d.ts.map +1 -0
- package/dist/dedup.js +85 -0
- package/dist/dedup.js.map +1 -0
- package/dist/diagnostics.d.ts +24 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +53 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/embedding-index.d.ts +27 -0
- package/dist/embedding-index.d.ts.map +1 -0
- package/dist/embedding-index.js +111 -0
- package/dist/embedding-index.js.map +1 -0
- package/dist/engine-archive.d.ts +37 -0
- package/dist/engine-archive.d.ts.map +1 -0
- package/dist/engine-archive.js +155 -0
- package/dist/engine-archive.js.map +1 -0
- package/dist/engine-episode-sync.d.ts +19 -0
- package/dist/engine-episode-sync.d.ts.map +1 -0
- package/dist/engine-episode-sync.js +186 -0
- package/dist/engine-episode-sync.js.map +1 -0
- package/dist/engine-injection-text.d.ts +46 -0
- package/dist/engine-injection-text.d.ts.map +1 -0
- package/dist/engine-injection-text.js +271 -0
- package/dist/engine-injection-text.js.map +1 -0
- package/dist/engine-insights.d.ts +22 -0
- package/dist/engine-insights.d.ts.map +1 -0
- package/dist/engine-insights.js +127 -0
- package/dist/engine-insights.js.map +1 -0
- package/dist/engine-links.d.ts +43 -0
- package/dist/engine-links.d.ts.map +1 -0
- package/dist/engine-links.js +223 -0
- package/dist/engine-links.js.map +1 -0
- package/dist/engine-recall-select.d.ts +84 -0
- package/dist/engine-recall-select.d.ts.map +1 -0
- package/dist/engine-recall-select.js +127 -0
- package/dist/engine-recall-select.js.map +1 -0
- package/dist/engine-reinforce.d.ts +18 -0
- package/dist/engine-reinforce.d.ts.map +1 -0
- package/dist/engine-reinforce.js +116 -0
- package/dist/engine-reinforce.js.map +1 -0
- package/dist/engine-scoring-v2.d.ts +35 -0
- package/dist/engine-scoring-v2.d.ts.map +1 -0
- package/dist/engine-scoring-v2.js +178 -0
- package/dist/engine-scoring-v2.js.map +1 -0
- package/dist/engine-v2-mapping.d.ts +17 -0
- package/dist/engine-v2-mapping.d.ts.map +1 -0
- package/dist/engine-v2-mapping.js +190 -0
- package/dist/engine-v2-mapping.js.map +1 -0
- package/dist/engine.d.ts +339 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +1964 -0
- package/dist/engine.js.map +1 -0
- package/dist/eviction.d.ts +14 -0
- package/dist/eviction.d.ts.map +1 -0
- package/dist/eviction.js +26 -0
- package/dist/eviction.js.map +1 -0
- package/dist/extraction.d.ts +11 -0
- package/dist/extraction.d.ts.map +1 -0
- package/dist/extraction.js +261 -0
- package/dist/extraction.js.map +1 -0
- package/dist/full-insights-html.d.ts +9 -0
- package/dist/full-insights-html.d.ts.map +1 -0
- package/dist/full-insights-html.js +422 -0
- package/dist/full-insights-html.js.map +1 -0
- package/dist/full-insights-sections.d.ts +48 -0
- package/dist/full-insights-sections.d.ts.map +1 -0
- package/dist/full-insights-sections.js +231 -0
- package/dist/full-insights-sections.js.map +1 -0
- package/dist/full-insights.d.ts +22 -0
- package/dist/full-insights.d.ts.map +1 -0
- package/dist/full-insights.js +370 -0
- package/dist/full-insights.js.map +1 -0
- package/dist/hash-embedding.d.ts +9 -0
- package/dist/hash-embedding.d.ts.map +1 -0
- package/dist/hash-embedding.js +44 -0
- package/dist/hash-embedding.js.map +1 -0
- package/dist/human-insights.d.ts +28 -0
- package/dist/human-insights.d.ts.map +1 -0
- package/dist/human-insights.js +260 -0
- package/dist/human-insights.js.map +1 -0
- package/dist/i18n.d.ts +64 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +367 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/insights-html.d.ts +9 -0
- package/dist/insights-html.d.ts.map +1 -0
- package/dist/insights-html.js +432 -0
- package/dist/insights-html.js.map +1 -0
- package/dist/linking.d.ts +22 -0
- package/dist/linking.d.ts.map +1 -0
- package/dist/linking.js +192 -0
- package/dist/linking.js.map +1 -0
- package/dist/llm-json.d.ts +10 -0
- package/dist/llm-json.d.ts.map +1 -0
- package/dist/llm-json.js +193 -0
- package/dist/llm-json.js.map +1 -0
- package/dist/privacy.d.ts +17 -0
- package/dist/privacy.d.ts.map +1 -0
- package/dist/privacy.js +53 -0
- package/dist/privacy.js.map +1 -0
- package/dist/procedural-v2.d.ts +9 -0
- package/dist/procedural-v2.d.ts.map +1 -0
- package/dist/procedural-v2.js +123 -0
- package/dist/procedural-v2.js.map +1 -0
- package/dist/reconsolidate-v2.d.ts +19 -0
- package/dist/reconsolidate-v2.d.ts.map +1 -0
- package/dist/reconsolidate-v2.js +115 -0
- package/dist/reconsolidate-v2.js.map +1 -0
- package/dist/scoring.d.ts +32 -0
- package/dist/scoring.d.ts.map +1 -0
- package/dist/scoring.js +108 -0
- package/dist/scoring.js.map +1 -0
- package/dist/store-v2.d.ts +35 -0
- package/dist/store-v2.d.ts.map +1 -0
- package/dist/store-v2.js +98 -0
- package/dist/store-v2.js.map +1 -0
- package/dist/store.d.ts +22 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +116 -0
- package/dist/store.js.map +1 -0
- package/dist/turn-context.d.ts +37 -0
- package/dist/turn-context.d.ts.map +1 -0
- package/dist/turn-context.js +31 -0
- package/dist/turn-context.js.map +1 -0
- package/dist/types-v2.d.ts +190 -0
- package/dist/types-v2.d.ts.map +1 -0
- package/dist/types-v2.js +8 -0
- package/dist/types-v2.js.map +1 -0
- package/dist/types.d.ts +330 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/update.d.ts +31 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +250 -0
- package/dist/update.js.map +1 -0
- package/package.json +34 -0
- package/src/browser-fs.ts +67 -0
- package/src/config.ts +138 -0
- package/src/consolidate-v2.ts +158 -0
- package/src/consolidation.ts +229 -0
- package/src/dedup.ts +91 -0
- package/src/diagnostics.ts +81 -0
- package/src/embedding-index.ts +138 -0
- package/src/engine-archive.ts +167 -0
- package/src/engine-episode-sync.ts +237 -0
- package/src/engine-injection-text.ts +334 -0
- package/src/engine-insights.ts +182 -0
- package/src/engine-links.ts +311 -0
- package/src/engine-recall-select.ts +298 -0
- package/src/engine-reinforce.ts +154 -0
- package/src/engine-scoring-v2.ts +188 -0
- package/src/engine-v2-mapping.ts +204 -0
- package/src/engine.ts +2499 -0
- package/src/eviction.ts +38 -0
- package/src/extraction.ts +300 -0
- package/src/full-insights-html.ts +474 -0
- package/src/full-insights-sections.ts +305 -0
- package/src/full-insights.ts +423 -0
- package/src/hash-embedding.ts +48 -0
- package/src/human-insights.ts +345 -0
- package/src/i18n.ts +455 -0
- package/src/index.ts +117 -0
- package/src/insights-html.ts +478 -0
- package/src/linking.ts +228 -0
- package/src/llm-json.ts +194 -0
- package/src/privacy.ts +54 -0
- package/src/procedural-v2.ts +142 -0
- package/src/reconsolidate-v2.ts +145 -0
- package/src/scoring.ts +156 -0
- package/src/store-v2.ts +138 -0
- package/src/store.ts +137 -0
- package/src/turn-context.ts +56 -0
- package/src/types-v2.ts +244 -0
- package/src/types.ts +372 -0
- package/src/update.ts +271 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [WHO]: Provides buildProgressiveInjectionText, CONVERSATION_PREFERENCE_PATTERNS, isConversationPreference, selectConversationPreferences, rankConversationPreference, mergeUniqueEntries
|
|
3
|
+
* [FROM]: Depends on ./i18n.js for PromptSet; ./linking.js for getGraphContextSummaries, GraphNeighbor; ./scoring.js for daysSince; ./types.js, ./types-v2.js for memory types
|
|
4
|
+
* [TO]: Consumed by engine.ts (getMemoryInjection)
|
|
5
|
+
* [HERE]: packages/mem-core/src/engine-injection-text.ts - injection text formatting and conversation preference detection
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { PromptSet } from "./i18n.js";
|
|
9
|
+
import { getGraphContextSummaries, type GraphNeighbor } from "./linking.js";
|
|
10
|
+
import { daysSince } from "./scoring.js";
|
|
11
|
+
import type { Episode, MemoryEntry, WorkEntry } from "./types.js";
|
|
12
|
+
import type { EpisodeFacet, EpisodeMemory, ProceduralMemory, SemanticMemory } from "./types-v2.js";
|
|
13
|
+
|
|
14
|
+
export const CONVERSATION_PREFERENCE_PATTERNS = [
|
|
15
|
+
/\bcall me\b/i,
|
|
16
|
+
/\baddress me\b/i,
|
|
17
|
+
/\bmy name is\b/i,
|
|
18
|
+
/\bi am called\b/i,
|
|
19
|
+
/\bspeak (?:to me )?(?:like|in)\b/i,
|
|
20
|
+
/\btalk (?:to me )?(?:like|in)\b/i,
|
|
21
|
+
/\buse (?:a |the )?(?:tone|style|voice)\b/i,
|
|
22
|
+
/\b(?:tone|style|voice|persona)\b/i,
|
|
23
|
+
/call me/,
|
|
24
|
+
/address me as/,
|
|
25
|
+
/address user/,
|
|
26
|
+
/tone/,
|
|
27
|
+
/style of speaking/,
|
|
28
|
+
/speaking style/,
|
|
29
|
+
/communication style/,
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
export function isConversationPreference(entry: MemoryEntry): boolean {
|
|
33
|
+
if (entry.type !== "preference") return false;
|
|
34
|
+
const text = `${entry.name || ""}\n${entry.summary || ""}\n${entry.detail || ""}\n${entry.content || ""}`;
|
|
35
|
+
return CONVERSATION_PREFERENCE_PATTERNS.some((pattern) => pattern.test(text));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function rankConversationPreference(entry: MemoryEntry): number {
|
|
39
|
+
return (entry.salience ?? entry.importance ?? 0) * 10 + (entry.accessCount ?? 0) * 2 - daysSince(entry.created);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function selectConversationPreferences(entries: MemoryEntry[]): MemoryEntry[] {
|
|
43
|
+
return entries
|
|
44
|
+
.filter((entry) => isConversationPreference(entry))
|
|
45
|
+
.filter((entry) => entry.stability !== "situational")
|
|
46
|
+
.sort((a, b) => rankConversationPreference(b) - rankConversationPreference(a))
|
|
47
|
+
.slice(0, 3);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function mergeUniqueEntries(preferred: MemoryEntry[], fallback: MemoryEntry[]): MemoryEntry[] {
|
|
51
|
+
const merged: MemoryEntry[] = [];
|
|
52
|
+
const seen = new Set<string>();
|
|
53
|
+
for (const entry of [...preferred, ...fallback]) {
|
|
54
|
+
if (seen.has(entry.id)) continue;
|
|
55
|
+
seen.add(entry.id);
|
|
56
|
+
merged.push(entry);
|
|
57
|
+
}
|
|
58
|
+
return merged;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface ActiveInjectionData {
|
|
62
|
+
knowledge: MemoryEntry[];
|
|
63
|
+
lessons: MemoryEntry[];
|
|
64
|
+
events: MemoryEntry[];
|
|
65
|
+
preferences: MemoryEntry[];
|
|
66
|
+
facets: MemoryEntry[];
|
|
67
|
+
episodeMemories: EpisodeMemory[];
|
|
68
|
+
episodeFacets: EpisodeFacet[];
|
|
69
|
+
semanticMemories: SemanticMemory[];
|
|
70
|
+
procedural: ProceduralMemory[];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface CueInjectionData {
|
|
74
|
+
knowledge: MemoryEntry[];
|
|
75
|
+
lessons: MemoryEntry[];
|
|
76
|
+
events: MemoryEntry[];
|
|
77
|
+
preferences: MemoryEntry[];
|
|
78
|
+
facets: MemoryEntry[];
|
|
79
|
+
episodes: Episode[];
|
|
80
|
+
work: WorkEntry[];
|
|
81
|
+
episodeMemories: EpisodeMemory[];
|
|
82
|
+
episodeFacets: EpisodeFacet[];
|
|
83
|
+
semanticMemories: SemanticMemory[];
|
|
84
|
+
procedural: ProceduralMemory[];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface InjectedMemoryOrderRecord {
|
|
88
|
+
memoryId: string;
|
|
89
|
+
memoryKind: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function toLegacyMemoryKind(entry: MemoryEntry): string {
|
|
93
|
+
switch (entry.type) {
|
|
94
|
+
case "fact":
|
|
95
|
+
case "decision":
|
|
96
|
+
case "entity":
|
|
97
|
+
return "knowledge";
|
|
98
|
+
case "lesson":
|
|
99
|
+
case "preference":
|
|
100
|
+
case "event":
|
|
101
|
+
return entry.type;
|
|
102
|
+
case "pattern":
|
|
103
|
+
case "struggle":
|
|
104
|
+
return "facet";
|
|
105
|
+
default:
|
|
106
|
+
return "memory";
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function buildInjectedMemoryOrder(
|
|
111
|
+
active: ActiveInjectionData,
|
|
112
|
+
cue: CueInjectionData,
|
|
113
|
+
): InjectedMemoryOrderRecord[] {
|
|
114
|
+
const conversationPreferenceEntries: InjectedMemoryOrderRecord[] = [];
|
|
115
|
+
const activeEntries: InjectedMemoryOrderRecord[] = [];
|
|
116
|
+
const keyEventEntries: InjectedMemoryOrderRecord[] = [];
|
|
117
|
+
const stateEntries: InjectedMemoryOrderRecord[] = [];
|
|
118
|
+
|
|
119
|
+
const pushActiveLegacyEntry = (entry: MemoryEntry) => {
|
|
120
|
+
const record = {
|
|
121
|
+
memoryId: entry.id,
|
|
122
|
+
memoryKind: toLegacyMemoryKind(entry),
|
|
123
|
+
};
|
|
124
|
+
if (isConversationPreference(entry)) {
|
|
125
|
+
conversationPreferenceEntries.push(record);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (entry.stability === "situational" || entry.stateData) {
|
|
129
|
+
stateEntries.push(record);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
activeEntries.push(record);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
for (const entry of active.lessons) pushActiveLegacyEntry(entry);
|
|
136
|
+
for (const entry of active.knowledge) pushActiveLegacyEntry(entry);
|
|
137
|
+
for (const entry of active.preferences) pushActiveLegacyEntry(entry);
|
|
138
|
+
for (const entry of active.facets) {
|
|
139
|
+
activeEntries.push({ memoryId: entry.id, memoryKind: "facet" });
|
|
140
|
+
}
|
|
141
|
+
for (const entry of active.events) {
|
|
142
|
+
keyEventEntries.push({ memoryId: entry.id, memoryKind: "event" });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return [
|
|
146
|
+
...conversationPreferenceEntries,
|
|
147
|
+
...activeEntries,
|
|
148
|
+
...active.episodeMemories.map((entry) => ({ memoryId: entry.id, memoryKind: "episode" })),
|
|
149
|
+
...active.episodeFacets.map((entry) => ({ memoryId: entry.id, memoryKind: "episodeFacet" })),
|
|
150
|
+
...active.semanticMemories.map((entry) => ({ memoryId: entry.id, memoryKind: "semantic" })),
|
|
151
|
+
...active.procedural.map((entry) => ({ memoryId: entry.id, memoryKind: "procedural" })),
|
|
152
|
+
...keyEventEntries,
|
|
153
|
+
...stateEntries,
|
|
154
|
+
...cue.lessons.map((entry) => ({ memoryId: entry.id, memoryKind: "lesson" })),
|
|
155
|
+
...cue.knowledge.map((entry) => ({ memoryId: entry.id, memoryKind: "knowledge" })),
|
|
156
|
+
...cue.events.map((entry) => ({ memoryId: entry.id, memoryKind: "event" })),
|
|
157
|
+
...cue.preferences.map((entry) => ({ memoryId: entry.id, memoryKind: "preference" })),
|
|
158
|
+
...cue.facets.map((entry) => ({ memoryId: entry.id, memoryKind: "facet" })),
|
|
159
|
+
...cue.episodeMemories.map((entry) => ({ memoryId: entry.id, memoryKind: "episode" })),
|
|
160
|
+
...cue.episodeFacets.map((entry) => ({ memoryId: entry.id, memoryKind: "episodeFacet" })),
|
|
161
|
+
...cue.semanticMemories.map((entry) => ({ memoryId: entry.id, memoryKind: "semantic" })),
|
|
162
|
+
...cue.procedural.map((entry) => ({ memoryId: entry.id, memoryKind: "procedural" })),
|
|
163
|
+
];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function buildProgressiveInjectionText(
|
|
167
|
+
active: ActiveInjectionData,
|
|
168
|
+
cue: CueInjectionData,
|
|
169
|
+
allEntries: MemoryEntry[],
|
|
170
|
+
graphContext: GraphNeighbor[],
|
|
171
|
+
p: PromptSet,
|
|
172
|
+
): string {
|
|
173
|
+
const sections: string[] = [];
|
|
174
|
+
const proceduralSectionTitle = "Procedures";
|
|
175
|
+
const episodicSectionTitle = "Episode Threads";
|
|
176
|
+
const semanticSectionTitle = "Semantic Abstractions";
|
|
177
|
+
|
|
178
|
+
// ── Active tier: full detail ──
|
|
179
|
+
const activeLines: string[] = [];
|
|
180
|
+
const conversationPreferenceLines: string[] = [];
|
|
181
|
+
const keyEventLines: string[] = [];
|
|
182
|
+
const stateLines: string[] = [];
|
|
183
|
+
|
|
184
|
+
const formatActiveEntry = (e: MemoryEntry): string => {
|
|
185
|
+
const related = getGraphContextSummaries(e, allEntries, 3);
|
|
186
|
+
const suffix = related.length ? ` [→ ${related.join("; ")}]` : "";
|
|
187
|
+
return `- [ID: ${e.id}] **${e.name || "—"}**: ${e.summary || ""}\n ${e.detail || ""}${suffix}`;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const formatActiveEvent = (e: MemoryEntry): string => {
|
|
191
|
+
const related = getGraphContextSummaries(e, allEntries, 4);
|
|
192
|
+
const outcome = e.eventData?.outcome ? `\n Outcome: ${e.eventData.outcome}` : "";
|
|
193
|
+
const suffix = related.length ? ` [→ ${related.join("; ")}]` : "";
|
|
194
|
+
return `- [ID: ${e.id}] **${e.name || "—"}**: ${e.summary || ""}\n ${e.detail || ""}${outcome}${suffix}`;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const formatActiveFacet = (e: MemoryEntry): string => {
|
|
198
|
+
if (e.facetData?.kind === "pattern") {
|
|
199
|
+
return `- [ID: ${e.id}] **${e.name || "—"}**: When ${e.facetData.trigger} → ${e.facetData.behavior}\n ${e.detail || ""}`;
|
|
200
|
+
}
|
|
201
|
+
if (e.facetData?.kind === "struggle") {
|
|
202
|
+
return `- [ID: ${e.id}] **${e.name || "—"}**: ${e.facetData.problem}\n Tried: ${e.facetData.attempts.join(", ")} | Solved: ${e.facetData.solution}\n ${e.detail || ""}`;
|
|
203
|
+
}
|
|
204
|
+
return formatActiveEntry(e);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const pushActiveEntry = (entry: MemoryEntry) => {
|
|
208
|
+
if (isConversationPreference(entry)) {
|
|
209
|
+
conversationPreferenceLines.push(formatActiveEntry(entry));
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (entry.stability === "situational" || entry.stateData) {
|
|
213
|
+
const mood = entry.stateData?.mood ? ` (${entry.stateData.mood})` : "";
|
|
214
|
+
stateLines.push(`- [ID: ${entry.id}] **${entry.name || "Unknown"}**${mood}: ${entry.summary || ""}`);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
activeLines.push(formatActiveEntry(entry));
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
for (const e of active.lessons) pushActiveEntry(e);
|
|
221
|
+
for (const e of active.knowledge) pushActiveEntry(e);
|
|
222
|
+
for (const e of active.preferences) pushActiveEntry(e);
|
|
223
|
+
for (const e of active.facets) activeLines.push(formatActiveFacet(e));
|
|
224
|
+
for (const e of active.events) keyEventLines.push(formatActiveEvent(e));
|
|
225
|
+
const episodicLines = active.episodeMemories.map((entry) => {
|
|
226
|
+
const goal = entry.userGoal ? ` | Goal: ${entry.userGoal}` : "";
|
|
227
|
+
const outcome = entry.outcome ? ` | Outcome: ${entry.outcome}` : "";
|
|
228
|
+
return `- [ID: ${entry.id}] **${entry.title || "Episode"}**: ${entry.summary}${goal}${outcome}`;
|
|
229
|
+
});
|
|
230
|
+
const episodicFacetLines = active.episodeFacets.map((entry) => {
|
|
231
|
+
const detail = entry.anchorText ? `\n ${entry.anchorText}` : "";
|
|
232
|
+
return `- [ID: ${entry.id}] [${entry.facetType}] **${entry.searchText}**${detail}`;
|
|
233
|
+
});
|
|
234
|
+
const semanticLines = active.semanticMemories.map((entry) => {
|
|
235
|
+
const detail = entry.detail ? `\n ${entry.detail}` : "";
|
|
236
|
+
return `- [ID: ${entry.id}] [${entry.semanticType}] **${entry.name}**: ${entry.summary}${detail}`;
|
|
237
|
+
});
|
|
238
|
+
const proceduralLines = active.procedural.map((entry) => {
|
|
239
|
+
const steps = entry.steps.slice(0, 4).map((step, index) => `${index + 1}. ${step.text}`).join("\n ");
|
|
240
|
+
const boundaries = entry.boundaries ? `\n Boundaries: ${entry.boundaries}` : "";
|
|
241
|
+
return `- [ID: ${entry.id}] **${entry.name}**: ${entry.summary}\n ${steps}${boundaries}`;
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
if (conversationPreferenceLines.length) {
|
|
245
|
+
sections.push(`### ${p.sectionConversationPreferences}\n${conversationPreferenceLines.join("\n")}`);
|
|
246
|
+
}
|
|
247
|
+
if (activeLines.length) {
|
|
248
|
+
sections.push(`### ${p.sectionActiveMemories}\n${activeLines.join("\n")}`);
|
|
249
|
+
}
|
|
250
|
+
if (episodicLines.length || episodicFacetLines.length) {
|
|
251
|
+
sections.push(`### ${episodicSectionTitle}\n${[...episodicLines, ...episodicFacetLines].join("\n")}`);
|
|
252
|
+
}
|
|
253
|
+
if (semanticLines.length) {
|
|
254
|
+
sections.push(`### ${semanticSectionTitle}\n${semanticLines.join("\n")}`);
|
|
255
|
+
}
|
|
256
|
+
if (proceduralLines.length) {
|
|
257
|
+
sections.push(`### ${proceduralSectionTitle}\n${proceduralLines.join("\n")}`);
|
|
258
|
+
}
|
|
259
|
+
if (keyEventLines.length) {
|
|
260
|
+
sections.push(`### ${p.sectionKeyEvents ?? "Key Events"}\n${keyEventLines.join("\n")}`);
|
|
261
|
+
}
|
|
262
|
+
if (stateLines.length) {
|
|
263
|
+
sections.push(`### ${p.sectionCurrentState ?? "Current State Signals"}\n${stateLines.join("\n")}`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const relatedLines = graphContext.map(
|
|
267
|
+
(neighbor) =>
|
|
268
|
+
`- [${neighbor.relation}] [ID: ${neighbor.entry.id}] [${neighbor.entry.type}] **${
|
|
269
|
+
neighbor.entry.name || "Unknown"
|
|
270
|
+
}**: ${neighbor.entry.summary || ""}`,
|
|
271
|
+
);
|
|
272
|
+
if (relatedLines.length) {
|
|
273
|
+
sections.push(`### ${p.sectionRelatedContext ?? "Related Context"}\n${relatedLines.join("\n")}`);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ── Cue tier: name + summary + id ──
|
|
277
|
+
const cueLines: string[] = [];
|
|
278
|
+
|
|
279
|
+
const formatCueEntry = (e: MemoryEntry): string =>
|
|
280
|
+
`- [ID: ${e.id}] [${e.type}] **${e.name || "—"}**: ${e.summary || ""}`;
|
|
281
|
+
|
|
282
|
+
const formatCueFacet = (e: MemoryEntry): string => {
|
|
283
|
+
if (e.facetData?.kind === "pattern") {
|
|
284
|
+
return `- [ID: ${e.id}] [pattern] **${e.name || "—"}**: When ${e.facetData.trigger} → ${e.facetData.behavior}`;
|
|
285
|
+
}
|
|
286
|
+
if (e.facetData?.kind === "struggle") {
|
|
287
|
+
return `- [ID: ${e.id}] [struggle] **${e.name || "—"}**: ${e.facetData.problem}`;
|
|
288
|
+
}
|
|
289
|
+
return formatCueEntry(e);
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const formatCueEvent = (e: MemoryEntry): string =>
|
|
293
|
+
`- [ID: ${e.id}] [event] **${e.name || "—"}**: ${e.summary || ""}`;
|
|
294
|
+
|
|
295
|
+
for (const e of cue.lessons) cueLines.push(formatCueEntry(e));
|
|
296
|
+
for (const e of cue.knowledge) cueLines.push(formatCueEntry(e));
|
|
297
|
+
for (const e of cue.events) cueLines.push(formatCueEvent(e));
|
|
298
|
+
for (const e of cue.preferences) cueLines.push(formatCueEntry(e));
|
|
299
|
+
for (const e of cue.facets) cueLines.push(formatCueFacet(e));
|
|
300
|
+
for (const episode of cue.episodeMemories) {
|
|
301
|
+
cueLines.push(`- [ID: ${episode.id}] [episode] **${episode.title || "Episode"}**: ${episode.summary}`);
|
|
302
|
+
}
|
|
303
|
+
for (const facet of cue.episodeFacets) {
|
|
304
|
+
cueLines.push(`- [ID: ${facet.id}] [episode-${facet.facetType}] **${facet.searchText}**: ${facet.summary || facet.anchorText || ""}`);
|
|
305
|
+
}
|
|
306
|
+
for (const semantic of cue.semanticMemories) {
|
|
307
|
+
cueLines.push(`- [ID: ${semantic.id}] [semantic-${semantic.semanticType}] **${semantic.name}**: ${semantic.summary}`);
|
|
308
|
+
}
|
|
309
|
+
for (const procedure of cue.procedural) {
|
|
310
|
+
cueLines.push(`- [ID: ${procedure.id}] [procedural] **${procedure.name}**: ${procedure.summary}`);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Episodes in cue layer (no ID-based recall)
|
|
314
|
+
for (const ep of cue.episodes) {
|
|
315
|
+
cueLines.push(
|
|
316
|
+
`- [${ep.date}] ${ep.project}: ${ep.summary}${ep.userGoal ? ` (Goal: ${ep.userGoal})` : ""}`,
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
// Work in cue layer
|
|
320
|
+
for (const w of cue.work) {
|
|
321
|
+
cueLines.push(`- [${w.created.slice(0, 10)}] ${w.goal}: ${w.summary}`);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (cueLines.length) {
|
|
325
|
+
sections.push(`### ${p.sectionMemoryCues}\n*${p.memoryCueHint}*\n${cueLines.join("\n")}`);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (!sections.length) return "";
|
|
329
|
+
const soulStyle = undefined;
|
|
330
|
+
const behaviorBlock = soulStyle
|
|
331
|
+
? `${p.memoryBehavior}\n\n${soulStyle}`
|
|
332
|
+
: p.memoryBehavior;
|
|
333
|
+
return `## ${p.injectionHeader}\n\n${sections.join("\n\n")}\n\n---\n${behaviorBlock}`;
|
|
334
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [WHO]: Provides generateInsightsReport, generateRecommendations, generateRulesBasedRecommendations
|
|
3
|
+
* [FROM]: Depends on ./i18n.js for PROMPTS; ./llm-json.js for tolerant structured parsing; ./types.js for memory types and insights
|
|
4
|
+
* [TO]: Consumed by engine.ts (generateInsights)
|
|
5
|
+
* [HERE]: packages/mem-core/src/engine-insights.ts - insights report generation (LLM + rules-based)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { PROMPTS } from "./i18n.js";
|
|
9
|
+
import { parseLlmJson } from "./llm-json.js";
|
|
10
|
+
import type { InsightsReport, LlmFn, MemoryEntry, PatternInsight, StruggleInsight, Meta, WorkEntry, Episode } from "./types.js";
|
|
11
|
+
|
|
12
|
+
export interface RuntimeMemoryView {
|
|
13
|
+
knowledge: MemoryEntry[];
|
|
14
|
+
lessons: MemoryEntry[];
|
|
15
|
+
events: MemoryEntry[];
|
|
16
|
+
preferences: MemoryEntry[];
|
|
17
|
+
facets: MemoryEntry[];
|
|
18
|
+
work: WorkEntry[];
|
|
19
|
+
episodes: Episode[];
|
|
20
|
+
meta: Meta;
|
|
21
|
+
v2SearchEntries: MemoryEntry[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function generateInsightsReport(
|
|
25
|
+
all: RuntimeMemoryView,
|
|
26
|
+
locale: string,
|
|
27
|
+
llmFn?: LlmFn,
|
|
28
|
+
): Promise<InsightsReport> {
|
|
29
|
+
const stats = {
|
|
30
|
+
knowledge: all.knowledge.length,
|
|
31
|
+
lessons: all.lessons.length,
|
|
32
|
+
preferences: all.preferences.length,
|
|
33
|
+
facets: all.facets.length,
|
|
34
|
+
episodes: all.episodes.length,
|
|
35
|
+
work: all.work.length,
|
|
36
|
+
totalSessions: all.meta.totalSessions,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Separate patterns and struggles from facets
|
|
40
|
+
const patternEntries = all.facets.filter((e) => e.type === "pattern");
|
|
41
|
+
const struggleEntries = all.facets.filter((e) => e.type === "struggle");
|
|
42
|
+
|
|
43
|
+
// Weight calculation: (accessCount + 1) × (importance / 10)
|
|
44
|
+
const calcWeight = (e: MemoryEntry, unresolvedBonus = false): number => {
|
|
45
|
+
const base = (e.accessCount + 1) * (e.importance / 10);
|
|
46
|
+
return unresolvedBonus ? base * 1.5 : base;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Build PatternInsight[]
|
|
50
|
+
const patterns: PatternInsight[] = patternEntries
|
|
51
|
+
.map((e) => ({
|
|
52
|
+
entry: e,
|
|
53
|
+
weight: calcWeight(e),
|
|
54
|
+
trigger: e.facetData?.kind === "pattern" ? e.facetData.trigger : (e.summary || e.detail || "").slice(0, 50),
|
|
55
|
+
behavior: e.facetData?.kind === "pattern" ? e.facetData.behavior : (e.summary || e.detail || ""),
|
|
56
|
+
}))
|
|
57
|
+
.sort((a, b) => b.weight - a.weight);
|
|
58
|
+
|
|
59
|
+
// Build StruggleInsight[]
|
|
60
|
+
const struggles: StruggleInsight[] = struggleEntries
|
|
61
|
+
.map((e) => {
|
|
62
|
+
const isResolved = e.facetData?.kind === "struggle" ? !!e.facetData.solution : false;
|
|
63
|
+
return {
|
|
64
|
+
entry: e,
|
|
65
|
+
weight: calcWeight(e, !isResolved),
|
|
66
|
+
problem: e.facetData?.kind === "struggle" ? e.facetData.problem : (e.summary || e.detail || ""),
|
|
67
|
+
attempts: e.facetData?.kind === "struggle" ? e.facetData.attempts : [],
|
|
68
|
+
solution: e.facetData?.kind === "struggle" ? e.facetData.solution : "",
|
|
69
|
+
resolved: isResolved,
|
|
70
|
+
};
|
|
71
|
+
})
|
|
72
|
+
.sort((a, b) => {
|
|
73
|
+
// Unresolved first, then by weight
|
|
74
|
+
if (a.resolved !== b.resolved) return a.resolved ? 1 : -1;
|
|
75
|
+
return b.weight - a.weight;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Top lessons and knowledge by importance × accessCount
|
|
79
|
+
const sortByRelevance = (arr: MemoryEntry[]) =>
|
|
80
|
+
[...arr].sort((a, b) => b.importance * (b.accessCount + 1) - a.importance * (a.accessCount + 1));
|
|
81
|
+
|
|
82
|
+
const topLessons = sortByRelevance(all.lessons).slice(0, 10);
|
|
83
|
+
const topKnowledge = sortByRelevance(all.knowledge).slice(0, 10);
|
|
84
|
+
|
|
85
|
+
// Generate recommendations (LLM or rules-based fallback)
|
|
86
|
+
const recommendations = await generateRecommendations(patterns, struggles, topLessons, locale, llmFn);
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
patterns,
|
|
90
|
+
struggles,
|
|
91
|
+
topLessons,
|
|
92
|
+
topKnowledge,
|
|
93
|
+
preferences: all.preferences,
|
|
94
|
+
stats,
|
|
95
|
+
recommendations,
|
|
96
|
+
generatedAt: new Date().toISOString(),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function generateRecommendations(
|
|
101
|
+
patterns: PatternInsight[],
|
|
102
|
+
struggles: StruggleInsight[],
|
|
103
|
+
lessons: MemoryEntry[],
|
|
104
|
+
locale: string,
|
|
105
|
+
llmFn?: LlmFn,
|
|
106
|
+
): Promise<string[]> {
|
|
107
|
+
// Try LLM-based recommendations if available
|
|
108
|
+
if (llmFn) {
|
|
109
|
+
try {
|
|
110
|
+
const p = PROMPTS[locale] ?? PROMPTS.en;
|
|
111
|
+
const input = JSON.stringify({
|
|
112
|
+
patterns: patterns.slice(0, 5).map((pa) => ({ trigger: pa.trigger, behavior: pa.behavior })),
|
|
113
|
+
struggles: struggles.slice(0, 5).map((s) => ({ problem: s.problem, resolved: s.resolved })),
|
|
114
|
+
lessons: lessons.slice(0, 5).map((l) => l.summary || l.detail || l.content || ""),
|
|
115
|
+
});
|
|
116
|
+
const raw = await llmFn(p.insightsRecommendationSystem, input);
|
|
117
|
+
const result = parseLlmJson<string[]>(raw);
|
|
118
|
+
if (Array.isArray(result) && result.length > 0) {
|
|
119
|
+
return result.slice(0, 5);
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
// Fall through to rules-based
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Rules-based fallback recommendations
|
|
127
|
+
return generateRulesBasedRecommendations(patterns, struggles, lessons);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function generateRulesBasedRecommendations(
|
|
131
|
+
patterns: PatternInsight[],
|
|
132
|
+
struggles: StruggleInsight[],
|
|
133
|
+
lessons: MemoryEntry[],
|
|
134
|
+
): string[] {
|
|
135
|
+
const recommendations: string[] = [];
|
|
136
|
+
|
|
137
|
+
// High-weight patterns → automation suggestion
|
|
138
|
+
if (patterns.length > 0) {
|
|
139
|
+
const top = patterns[0]!;
|
|
140
|
+
recommendations.push(
|
|
141
|
+
`You consistently ${top.behavior} when ${top.trigger}. Consider automating this behavior.`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Unresolved struggles → systematic review suggestion
|
|
146
|
+
const unresolved = struggles.filter((s) => !s.resolved);
|
|
147
|
+
if (unresolved.length >= 2) {
|
|
148
|
+
recommendations.push(
|
|
149
|
+
`You have ${unresolved.length} unresolved issues. Consider tackling them systematically.`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Recurring tag patterns in struggles → domain-specific review
|
|
154
|
+
const struggleTags = struggles.flatMap((s) => s.entry.tags);
|
|
155
|
+
const tagCounts = struggleTags.reduce(
|
|
156
|
+
(acc, tag) => {
|
|
157
|
+
acc[tag] = (acc[tag] || 0) + 1;
|
|
158
|
+
return acc;
|
|
159
|
+
},
|
|
160
|
+
{} as Record<string, number>,
|
|
161
|
+
);
|
|
162
|
+
const topTag = Object.entries(tagCounts).sort((a, b) => b[1] - a[1])[0];
|
|
163
|
+
if (topTag && topTag[1] >= 3) {
|
|
164
|
+
recommendations.push(
|
|
165
|
+
`Issues related to "${topTag[0]}" appear frequently. Consider deeper learning in this area.`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Lessons accumulation → expertise recognition
|
|
170
|
+
if (lessons.length >= 5) {
|
|
171
|
+
recommendations.push(
|
|
172
|
+
`You've accumulated ${lessons.length} lessons. This is valuable expertise.`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// No data → encouragement
|
|
177
|
+
if (patterns.length === 0 && struggles.length === 0 && lessons.length === 0) {
|
|
178
|
+
recommendations.push(`Keep using the system to let it learn your work habits.`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return recommendations.slice(0, 5);
|
|
182
|
+
}
|