@pencil-agent/nano-mem 0.0.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/CLAUDE.md +258 -0
- package/README.md +146 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +90 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +46 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +48 -0
- package/dist/config.js.map +1 -0
- package/dist/consolidation.d.ts +13 -0
- package/dist/consolidation.d.ts.map +1 -0
- package/dist/consolidation.js +111 -0
- package/dist/consolidation.js.map +1 -0
- package/dist/engine.d.ts +67 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +492 -0
- package/dist/engine.js.map +1 -0
- package/dist/eviction.d.ts +16 -0
- package/dist/eviction.d.ts.map +1 -0
- package/dist/eviction.js +22 -0
- package/dist/eviction.js.map +1 -0
- package/dist/extension.d.ts +11 -0
- package/dist/extension.d.ts.map +1 -0
- package/dist/extension.js +264 -0
- package/dist/extension.js.map +1 -0
- package/dist/extraction.d.ts +10 -0
- package/dist/extraction.d.ts.map +1 -0
- package/dist/extraction.js +136 -0
- package/dist/extraction.js.map +1 -0
- package/dist/full-insights-html.d.ts +8 -0
- package/dist/full-insights-html.d.ts.map +1 -0
- package/dist/full-insights-html.js +311 -0
- package/dist/full-insights-html.js.map +1 -0
- package/dist/full-insights.d.ts +21 -0
- package/dist/full-insights.d.ts.map +1 -0
- package/dist/full-insights.js +327 -0
- package/dist/full-insights.js.map +1 -0
- package/dist/i18n.d.ts +50 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +169 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/insights-html.d.ts +8 -0
- package/dist/insights-html.d.ts.map +1 -0
- package/dist/insights-html.js +431 -0
- package/dist/insights-html.js.map +1 -0
- package/dist/linking.d.ts +11 -0
- package/dist/linking.d.ts.map +1 -0
- package/dist/linking.js +40 -0
- package/dist/linking.js.map +1 -0
- package/dist/privacy.d.ts +16 -0
- package/dist/privacy.d.ts.map +1 -0
- package/dist/privacy.js +52 -0
- package/dist/privacy.js.map +1 -0
- package/dist/scoring.d.ts +25 -0
- package/dist/scoring.d.ts.map +1 -0
- package/dist/scoring.js +63 -0
- package/dist/scoring.js.map +1 -0
- package/dist/store.d.ts +16 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +68 -0
- package/dist/store.js.map +1 -0
- package/dist/types.d.ts +191 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/update.d.ts +14 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +126 -0
- package/dist/update.js.map +1 -0
- package/package.json +60 -0
- package/src/cli.ts +99 -0
- package/src/config.ts +72 -0
- package/src/consolidation.ts +127 -0
- package/src/engine.ts +699 -0
- package/src/eviction.ts +30 -0
- package/src/extension.ts +290 -0
- package/src/extraction.ts +152 -0
- package/src/full-insights-html.ts +342 -0
- package/src/full-insights.ts +396 -0
- package/src/i18n.ts +233 -0
- package/src/index.ts +50 -0
- package/src/insights-html.ts +476 -0
- package/src/linking.ts +43 -0
- package/src/privacy.ts +52 -0
- package/src/scoring.ts +94 -0
- package/src/store.ts +84 -0
- package/src/types.ts +209 -0
- package/src/update.ts +141 -0
package/src/store.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [INPUT]: file paths, data objects
|
|
3
|
+
* [OUTPUT]: async JSON read/write with directory auto-creation
|
|
4
|
+
* [POS]: Persistence layer — all other modules go through here
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
8
|
+
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
9
|
+
import { dirname, join } from "node:path";
|
|
10
|
+
import type { Episode, MemoryEntry, Meta, WorkEntry } from "./types.js";
|
|
11
|
+
|
|
12
|
+
async function ensureDir(dir: string): Promise<void> {
|
|
13
|
+
if (!existsSync(dir)) await mkdir(dir, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function readJson<T>(path: string, fallback: T): Promise<T> {
|
|
17
|
+
try {
|
|
18
|
+
if (!existsSync(path)) return fallback;
|
|
19
|
+
const raw = await readFile(path, "utf-8");
|
|
20
|
+
return JSON.parse(raw) as T;
|
|
21
|
+
} catch {
|
|
22
|
+
return fallback;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function writeJson(path: string, data: unknown): Promise<void> {
|
|
27
|
+
await ensureDir(dirname(path));
|
|
28
|
+
await writeFile(path, JSON.stringify(data, null, 2), "utf-8");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function loadEntries(path: string): Promise<MemoryEntry[]> {
|
|
32
|
+
return readJson<MemoryEntry[]>(path, []);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function saveEntries(
|
|
36
|
+
path: string,
|
|
37
|
+
entries: MemoryEntry[],
|
|
38
|
+
max: number,
|
|
39
|
+
utilityFn: (e: MemoryEntry) => number,
|
|
40
|
+
): Promise<void> {
|
|
41
|
+
if (entries.length > max) {
|
|
42
|
+
entries.sort((a, b) => utilityFn(b) - utilityFn(a));
|
|
43
|
+
entries.length = max;
|
|
44
|
+
}
|
|
45
|
+
await writeJson(path, entries);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function loadEpisodes(episodesDir: string): Promise<Episode[]> {
|
|
49
|
+
await ensureDir(episodesDir);
|
|
50
|
+
const files = await readdir(episodesDir);
|
|
51
|
+
const results: Episode[] = [];
|
|
52
|
+
for (const f of files) {
|
|
53
|
+
if (!f.endsWith(".json")) continue;
|
|
54
|
+
const ep = await readJson<Episode | null>(join(episodesDir, f), null);
|
|
55
|
+
if (ep) results.push(ep);
|
|
56
|
+
}
|
|
57
|
+
return results;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function saveEpisode(episodesDir: string, ep: Episode): Promise<void> {
|
|
61
|
+
await ensureDir(episodesDir);
|
|
62
|
+
await writeJson(join(episodesDir, `${ep.date}-${ep.sessionId.slice(0, 8)}.json`), ep);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function loadWork(path: string): Promise<WorkEntry[]> {
|
|
66
|
+
return readJson<WorkEntry[]>(path, []);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function saveWork(
|
|
70
|
+
path: string,
|
|
71
|
+
entries: WorkEntry[],
|
|
72
|
+
max: number,
|
|
73
|
+
utilityFn: (w: WorkEntry) => number,
|
|
74
|
+
): Promise<void> {
|
|
75
|
+
if (entries.length > max) {
|
|
76
|
+
entries.sort((a, b) => utilityFn(b) - utilityFn(a));
|
|
77
|
+
entries.length = max;
|
|
78
|
+
}
|
|
79
|
+
await writeJson(path, entries);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function loadMeta(path: string): Promise<Meta> {
|
|
83
|
+
return readJson<Meta>(path, { totalSessions: 0, version: 1 });
|
|
84
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [INPUT]: None (pure type definitions)
|
|
3
|
+
* [OUTPUT]: All core data types for NanoMem
|
|
4
|
+
* [POS]: Foundation layer — every other module imports from here
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Pluggable LLM function: system prompt + user message → raw text response */
|
|
8
|
+
export type LlmFn = (systemPrompt: string, userMessage: string) => Promise<string>;
|
|
9
|
+
|
|
10
|
+
export interface MemoryScope {
|
|
11
|
+
userId?: string;
|
|
12
|
+
agentId?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Structured data for Facets (Pattern/Struggle) memory types */
|
|
16
|
+
export type FacetData =
|
|
17
|
+
| { kind: "pattern"; trigger: string; behavior: string }
|
|
18
|
+
| { kind: "struggle"; problem: string; attempts: string[]; solution: string };
|
|
19
|
+
|
|
20
|
+
export interface MemoryEntry {
|
|
21
|
+
id: string;
|
|
22
|
+
type: "fact" | "lesson" | "preference" | "decision" | "entity" | "pattern" | "struggle";
|
|
23
|
+
content: string;
|
|
24
|
+
tags: string[];
|
|
25
|
+
project: string;
|
|
26
|
+
importance: number;
|
|
27
|
+
/** Adaptive memory strength (days). Grows with each successful recall — Ebbinghaus spaced repetition. */
|
|
28
|
+
strength?: number;
|
|
29
|
+
/** Ingestion time: when the system recorded this entry */
|
|
30
|
+
created: string;
|
|
31
|
+
/** Event time: when the fact actually occurred (bi-temporal, defaults to created) */
|
|
32
|
+
eventTime?: string;
|
|
33
|
+
lastAccessed?: string;
|
|
34
|
+
accessCount: number;
|
|
35
|
+
/** A-MEM style links to related memory entries */
|
|
36
|
+
relatedIds?: string[];
|
|
37
|
+
/** TTL in days — auto-evicted after expiry. undefined = permanent */
|
|
38
|
+
ttl?: number;
|
|
39
|
+
scope?: MemoryScope;
|
|
40
|
+
/** Structured data for pattern/struggle types (Facets) */
|
|
41
|
+
facetData?: FacetData;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface Episode {
|
|
45
|
+
sessionId: string;
|
|
46
|
+
project: string;
|
|
47
|
+
date: string;
|
|
48
|
+
summary: string;
|
|
49
|
+
userGoal?: string;
|
|
50
|
+
filesModified: string[];
|
|
51
|
+
toolsUsed: Record<string, number>;
|
|
52
|
+
keyObservations: string[];
|
|
53
|
+
errors: string[];
|
|
54
|
+
tags: string[];
|
|
55
|
+
importance: number;
|
|
56
|
+
consolidated: boolean;
|
|
57
|
+
scope?: MemoryScope;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface WorkEntry {
|
|
61
|
+
id: string;
|
|
62
|
+
goal: string;
|
|
63
|
+
summary: string;
|
|
64
|
+
project: string;
|
|
65
|
+
tags: string[];
|
|
66
|
+
importance: number;
|
|
67
|
+
strength?: number;
|
|
68
|
+
created: string;
|
|
69
|
+
eventTime?: string;
|
|
70
|
+
lastAccessed?: string;
|
|
71
|
+
accessCount: number;
|
|
72
|
+
relatedIds?: string[];
|
|
73
|
+
ttl?: number;
|
|
74
|
+
scope?: MemoryScope;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface Meta {
|
|
78
|
+
totalSessions: number;
|
|
79
|
+
lastConsolidation?: string;
|
|
80
|
+
version: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Mem0-style update operations */
|
|
84
|
+
export type UpdateAction = "add" | "update" | "delete" | "noop";
|
|
85
|
+
|
|
86
|
+
export interface ExtractedItem {
|
|
87
|
+
type: "preference" | "fact" | "lesson" | "decision" | "retract" | "pattern" | "struggle";
|
|
88
|
+
content: string;
|
|
89
|
+
/** Structured data for pattern/struggle types (populated by LLM extraction) */
|
|
90
|
+
facetData?: FacetData;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface ExtractedWork {
|
|
94
|
+
goal: string;
|
|
95
|
+
summary: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ─── Insights Types ──────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
export interface PatternInsight {
|
|
101
|
+
entry: MemoryEntry;
|
|
102
|
+
weight: number;
|
|
103
|
+
trigger: string;
|
|
104
|
+
behavior: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface StruggleInsight {
|
|
108
|
+
entry: MemoryEntry;
|
|
109
|
+
weight: number;
|
|
110
|
+
problem: string;
|
|
111
|
+
attempts: string[];
|
|
112
|
+
solution: string;
|
|
113
|
+
resolved: boolean;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface InsightsReport {
|
|
117
|
+
patterns: PatternInsight[];
|
|
118
|
+
struggles: StruggleInsight[];
|
|
119
|
+
topLessons: MemoryEntry[];
|
|
120
|
+
topKnowledge: MemoryEntry[];
|
|
121
|
+
preferences: MemoryEntry[];
|
|
122
|
+
stats: {
|
|
123
|
+
knowledge: number;
|
|
124
|
+
lessons: number;
|
|
125
|
+
preferences: number;
|
|
126
|
+
facets: number;
|
|
127
|
+
episodes: number;
|
|
128
|
+
work: number;
|
|
129
|
+
totalSessions: number;
|
|
130
|
+
};
|
|
131
|
+
recommendations: string[];
|
|
132
|
+
generatedAt: string;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ─── Full Insights Report (rich narrative + charts) ──────────────────────
|
|
136
|
+
|
|
137
|
+
export interface FullInsightsAtAGlance {
|
|
138
|
+
working: string;
|
|
139
|
+
hindering: string;
|
|
140
|
+
quickWins: string;
|
|
141
|
+
ambitious: string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface FullInsightsProjectArea {
|
|
145
|
+
name: string;
|
|
146
|
+
sessionCount: number;
|
|
147
|
+
description: string;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface FullInsightsChartRow {
|
|
151
|
+
label: string;
|
|
152
|
+
value: number;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface FullInsightsChart {
|
|
156
|
+
id: string;
|
|
157
|
+
title: string;
|
|
158
|
+
rows: FullInsightsChartRow[];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface FullInsightsWin {
|
|
162
|
+
title: string;
|
|
163
|
+
description: string;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface FullInsightsFriction {
|
|
167
|
+
title: string;
|
|
168
|
+
description: string;
|
|
169
|
+
examples?: string[];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface FullInsightsFeatureToTry {
|
|
173
|
+
title: string;
|
|
174
|
+
oneLiner: string;
|
|
175
|
+
whyForYou: string;
|
|
176
|
+
exampleCode?: string;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export interface FullInsightsUsagePattern {
|
|
180
|
+
title: string;
|
|
181
|
+
summary: string;
|
|
182
|
+
detail: string;
|
|
183
|
+
pastePrompt?: string;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export interface FullInsightsReport {
|
|
187
|
+
stats: {
|
|
188
|
+
knowledge: number;
|
|
189
|
+
lessons: number;
|
|
190
|
+
preferences: number;
|
|
191
|
+
facets: number;
|
|
192
|
+
episodes: number;
|
|
193
|
+
work: number;
|
|
194
|
+
totalSessions: number;
|
|
195
|
+
aggregateToolCount?: number;
|
|
196
|
+
aggregateFileCount?: number;
|
|
197
|
+
};
|
|
198
|
+
atAGlance: FullInsightsAtAGlance;
|
|
199
|
+
projectAreas: FullInsightsProjectArea[];
|
|
200
|
+
charts: FullInsightsChart[];
|
|
201
|
+
wins: FullInsightsWin[];
|
|
202
|
+
frictions: FullInsightsFriction[];
|
|
203
|
+
patterns: PatternInsight[];
|
|
204
|
+
recommendations: string[];
|
|
205
|
+
featuresToTry: FullInsightsFeatureToTry[];
|
|
206
|
+
usagePatterns: FullInsightsUsagePattern[];
|
|
207
|
+
generatedAt: string;
|
|
208
|
+
locale: string;
|
|
209
|
+
}
|
package/src/update.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [INPUT]: extracted items, existing entries
|
|
3
|
+
* [OUTPUT]: entries mutated via Mem0 four-operation pipeline (ADD/UPDATE/DELETE/NOOP)
|
|
4
|
+
* [POS]: Implements Mem0 update semantics — slot-based, tag-overlap, and retract
|
|
5
|
+
*
|
|
6
|
+
* Key improvements over basic approach:
|
|
7
|
+
* - UPDATE generalized: any entry type, tag overlap > 0.7 triggers content replacement
|
|
8
|
+
* - DELETE: retract-type extractions find and remove matching entries
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { NanomemConfig } from "./config.js";
|
|
12
|
+
import { linkNewEntry } from "./linking.js";
|
|
13
|
+
import { filterPII } from "./privacy.js";
|
|
14
|
+
import { extractTags, tagOverlap } from "./scoring.js";
|
|
15
|
+
import type { ExtractedItem, MemoryEntry } from "./types.js";
|
|
16
|
+
|
|
17
|
+
const UPDATE_OVERLAP_THRESHOLD = 0.7;
|
|
18
|
+
const NOOP_SIMILARITY_THRESHOLD = 0.8;
|
|
19
|
+
const CONTENT_SIMILARITY_THRESHOLD = 0.85;
|
|
20
|
+
|
|
21
|
+
function makeId(): string {
|
|
22
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Simple content similarity check using character overlap */
|
|
26
|
+
function contentSimilarity(a: string, b: string): number {
|
|
27
|
+
const aLower = a.toLowerCase().trim();
|
|
28
|
+
const bLower = b.toLowerCase().trim();
|
|
29
|
+
if (aLower === bLower) return 1;
|
|
30
|
+
const shorter = aLower.length < bLower.length ? aLower : bLower;
|
|
31
|
+
const longer = aLower.length >= bLower.length ? aLower : bLower;
|
|
32
|
+
if (longer.includes(shorter)) return shorter.length / longer.length;
|
|
33
|
+
// Simple word overlap
|
|
34
|
+
const aWords = new Set(aLower.split(/\s+/).filter((w) => w.length > 2));
|
|
35
|
+
const bWords = new Set(bLower.split(/\s+/).filter((w) => w.length > 2));
|
|
36
|
+
if (aWords.size === 0 || bWords.size === 0) return 0;
|
|
37
|
+
let overlap = 0;
|
|
38
|
+
for (const w of aWords) if (bWords.has(w)) overlap++;
|
|
39
|
+
return (2 * overlap) / (aWords.size + bWords.size);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Check if new content is too similar to an existing entry of same type */
|
|
43
|
+
function isDuplicate(entries: MemoryEntry[], type: string, tags: string[], content: string): boolean {
|
|
44
|
+
return entries.some((e) => {
|
|
45
|
+
if (e.type !== type) return false;
|
|
46
|
+
// Check both tag overlap and content similarity
|
|
47
|
+
const tagMatch = tagOverlap(e.tags, tags) >= NOOP_SIMILARITY_THRESHOLD;
|
|
48
|
+
const contentMatch = contentSimilarity(e.content, content) >= CONTENT_SIMILARITY_THRESHOLD;
|
|
49
|
+
return tagMatch || contentMatch;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Find existing entry with high tag overlap for UPDATE */
|
|
54
|
+
function findUpdateCandidate(entries: MemoryEntry[], type: string, tags: string[]): number {
|
|
55
|
+
return entries.findIndex((e) => e.type === type && tagOverlap(e.tags, tags) >= UPDATE_OVERLAP_THRESHOLD);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Process a single extracted item through the Mem0 pipeline */
|
|
59
|
+
export function applyExtraction(
|
|
60
|
+
entries: MemoryEntry[],
|
|
61
|
+
item: ExtractedItem,
|
|
62
|
+
project: string,
|
|
63
|
+
cfg: NanomemConfig,
|
|
64
|
+
): void {
|
|
65
|
+
if (item.type === "retract") {
|
|
66
|
+
applyDelete(entries, item.content);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const memType = mapType(item.type);
|
|
71
|
+
const content = filterPII(item.content);
|
|
72
|
+
const tags = extractTags(content);
|
|
73
|
+
|
|
74
|
+
if (isDuplicate(entries, memType, tags, content)) return;
|
|
75
|
+
|
|
76
|
+
const updateIdx = findUpdateCandidate(entries, memType, tags);
|
|
77
|
+
if (updateIdx >= 0) {
|
|
78
|
+
const existing = entries[updateIdx]!;
|
|
79
|
+
entries[updateIdx] = {
|
|
80
|
+
...existing,
|
|
81
|
+
content,
|
|
82
|
+
tags,
|
|
83
|
+
lastAccessed: new Date().toISOString(),
|
|
84
|
+
};
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const now = new Date().toISOString();
|
|
89
|
+
const newEntry: MemoryEntry = {
|
|
90
|
+
id: makeId(),
|
|
91
|
+
type: memType,
|
|
92
|
+
content,
|
|
93
|
+
tags,
|
|
94
|
+
project,
|
|
95
|
+
importance:
|
|
96
|
+
item.type === "struggle"
|
|
97
|
+
? 9
|
|
98
|
+
: item.type === "lesson"
|
|
99
|
+
? 8
|
|
100
|
+
: item.type === "pattern"
|
|
101
|
+
? 7
|
|
102
|
+
: item.type === "preference"
|
|
103
|
+
? 6
|
|
104
|
+
: 5,
|
|
105
|
+
strength: cfg.halfLife[memType] ?? 30,
|
|
106
|
+
created: now,
|
|
107
|
+
eventTime: now,
|
|
108
|
+
accessCount: 0,
|
|
109
|
+
relatedIds: [],
|
|
110
|
+
scope: cfg.defaultScope,
|
|
111
|
+
facetData: item.facetData,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
linkNewEntry(newEntry, entries);
|
|
115
|
+
entries.push(newEntry);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function applyDelete(entries: MemoryEntry[], content: string): void {
|
|
119
|
+
const tags = extractTags(content);
|
|
120
|
+
const idx = entries.findIndex(
|
|
121
|
+
(e) => (e.type === "fact" || e.type === "preference") && tagOverlap(e.tags, tags) >= UPDATE_OVERLAP_THRESHOLD,
|
|
122
|
+
);
|
|
123
|
+
if (idx >= 0) entries.splice(idx, 1);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function mapType(t: ExtractedItem["type"]): MemoryEntry["type"] {
|
|
127
|
+
switch (t) {
|
|
128
|
+
case "preference":
|
|
129
|
+
return "preference";
|
|
130
|
+
case "lesson":
|
|
131
|
+
return "lesson";
|
|
132
|
+
case "decision":
|
|
133
|
+
return "decision";
|
|
134
|
+
case "pattern":
|
|
135
|
+
return "pattern";
|
|
136
|
+
case "struggle":
|
|
137
|
+
return "struggle";
|
|
138
|
+
default:
|
|
139
|
+
return "fact";
|
|
140
|
+
}
|
|
141
|
+
}
|