@imdigitalashish/zpi 0.1.2 → 0.1.3
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/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +5 -3
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/memory.d.ts +31 -6
- package/dist/core/memory.d.ts.map +1 -1
- package/dist/core/memory.js +63 -17
- package/dist/core/memory.js.map +1 -1
- package/dist/core/settings-manager.d.ts +3 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +11 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts +3 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +3 -3
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/memory.d.ts +2 -0
- package/dist/core/tools/memory.d.ts.map +1 -1
- package/dist/core/tools/memory.js +27 -27
- package/dist/core/tools/memory.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +12 -0
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +23 -14
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/package.json +1 -1
package/dist/core/memory.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Persistent memory system — procedural, episodic, and semantic memories
|
|
3
|
-
* stored per-project in .pi/memory/ as JSON files.
|
|
3
|
+
* stored per-project in .pi/memory/ or globally in ~/.pi/agent/memory/ as JSON files.
|
|
4
|
+
* Scope is configured via settings (memory.scope: "project" | "global")
|
|
5
|
+
* or overridden per-project via a .zpi config file.
|
|
4
6
|
*/
|
|
5
7
|
export interface ProceduralMemory {
|
|
6
8
|
id: string;
|
|
@@ -36,9 +38,32 @@ export interface MemoryStore<T> {
|
|
|
36
38
|
memories: T[];
|
|
37
39
|
}
|
|
38
40
|
export type MemoryType = "procedural" | "episodic" | "semantic";
|
|
39
|
-
export
|
|
40
|
-
export
|
|
41
|
-
|
|
41
|
+
export type MemoryScope = "project" | "global";
|
|
42
|
+
export interface ZpiConfig {
|
|
43
|
+
memory?: {
|
|
44
|
+
scope?: MemoryScope;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Read .zpi config file from the project root.
|
|
49
|
+
* Returns undefined if file doesn't exist or is invalid.
|
|
50
|
+
*/
|
|
51
|
+
export declare function readZpiConfig(cwd: string): ZpiConfig | undefined;
|
|
52
|
+
/** Get the global memory directory (~/.pi/agent/memory/) */
|
|
53
|
+
export declare function getGlobalMemoryDir(): string;
|
|
54
|
+
/** Get the project memory directory (<cwd>/.pi/memory/) */
|
|
55
|
+
export declare function getProjectMemoryDir(cwd: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* Resolve the effective memory scope for a project.
|
|
58
|
+
* Priority: .zpi config > settings > default ("project")
|
|
59
|
+
*/
|
|
60
|
+
export declare function resolveMemoryScope(cwd: string, settingsScope: MemoryScope): MemoryScope;
|
|
61
|
+
/**
|
|
62
|
+
* Get the memory directory based on scope.
|
|
63
|
+
*/
|
|
64
|
+
export declare function getMemoryDir(cwd: string, scope?: MemoryScope): string;
|
|
65
|
+
export declare function loadStore<T>(cwd: string, filename: string, scope?: MemoryScope): MemoryStore<T>;
|
|
66
|
+
export declare function saveStore<T>(cwd: string, filename: string, store: MemoryStore<T>, scope?: MemoryScope): void;
|
|
42
67
|
export declare function generateId(prefix: string, store: MemoryStore<{
|
|
43
68
|
id: string;
|
|
44
69
|
}>): string;
|
|
@@ -52,7 +77,7 @@ export declare function compactEpisodicMemories(memories: EpisodicMemory[]): Epi
|
|
|
52
77
|
export declare function formatProceduralForDisplay(m: ProceduralMemory): string;
|
|
53
78
|
export declare function formatEpisodicForDisplay(m: EpisodicMemory): string;
|
|
54
79
|
export declare function formatSemanticForDisplay(m: SemanticMemory): string;
|
|
55
|
-
export declare function buildMemoryPromptSection(cwd: string): string;
|
|
80
|
+
export declare function buildMemoryPromptSection(cwd: string, scope?: MemoryScope): string;
|
|
56
81
|
export declare function formatTimestamp(ts: number): string;
|
|
57
82
|
export interface MemoryCounts {
|
|
58
83
|
semantic: number;
|
|
@@ -60,5 +85,5 @@ export interface MemoryCounts {
|
|
|
60
85
|
episodic: number;
|
|
61
86
|
total: number;
|
|
62
87
|
}
|
|
63
|
-
export declare function getMemoryCounts(cwd: string): MemoryCounts;
|
|
88
|
+
export declare function getMemoryCounts(cwd: string, scope?: MemoryScope): MemoryCounts;
|
|
64
89
|
//# sourceMappingURL=memory.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/core/memory.ts"],"names":[],"mappings":"AAAA;;;GAGG;AASH,MAAM,WAAW,gBAAgB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,CAAC,EAAE;QACZ,QAAQ,EAAE,MAAM,EAAE,CAAC;QACnB,OAAO,EAAE,MAAM,EAAE,CAAC;KAClB,CAAC;IACF,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,YAAY,GAAG,cAAc,GAAG,YAAY,GAAG,MAAM,CAAC;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW,CAAC,CAAC;IAC7B,QAAQ,EAAE,CAAC,EAAE,CAAC;CACd;AAED,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,UAAU,GAAG,UAAU,CAAC;AAMhE,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEhD;AAUD,wBAAgB,SAAS,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAW1E;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,IAAI,CAIvF;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,CAAC;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,MAAM,CAOrF;AAED,wBAAgB,MAAM,IAAI,MAAM,CAE/B;AAED,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAQD,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAErD;AAMD,wBAAgB,cAAc,CAAC,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,EAC1D,QAAQ,EAAE,CAAC,EAAE,EACb,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,GACvB,CAAC,EAAE,CASL;AAMD,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,cAAc,EAAE,CAgDpF;AAMD,wBAAgB,0BAA0B,CAAC,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAGtE;AAED,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,cAAc,GAAG,MAAM,CAclE;AAED,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,cAAc,GAAG,MAAM,CAElE;AAMD,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA8E5D;AAeD,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAWlD;AAMD,MAAM,WAAW,YAAY;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAKzD","sourcesContent":["/**\r\n * Persistent memory system — procedural, episodic, and semantic memories\r\n * stored per-project in .pi/memory/ as JSON files.\r\n */\r\n\r\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\r\nimport { join } from \"node:path\";\r\n\r\n// ---------------------------------------------------------------------------\r\n// Types\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface ProceduralMemory {\r\n\tid: string;\r\n\tname: string;\r\n\ttrigger: string;\r\n\tsteps: string[];\r\n\ttags: string[];\r\n\tcreated: string;\r\n\tupdated: string;\r\n\tsourceSession: string;\r\n}\r\n\r\nexport interface EpisodicMemory {\r\n\tid: string;\r\n\tsummary: string;\r\n\tdetails: string[];\r\n\treflection?: {\r\n\t\tmistakes: string[];\r\n\t\tlessons: string[];\r\n\t};\r\n\ttags: string[];\r\n\tdate: string;\r\n\tsourceSession: string;\r\n}\r\n\r\nexport interface SemanticMemory {\r\n\tid: string;\r\n\tcategory: \"preference\" | \"architecture\" | \"convention\" | \"fact\";\r\n\ttext: string;\r\n\ttags: string[];\r\n\tcreated: string;\r\n\tsourceSession: string;\r\n}\r\n\r\nexport interface MemoryStore<T> {\r\n\tmemories: T[];\r\n}\r\n\r\nexport type MemoryType = \"procedural\" | \"episodic\" | \"semantic\";\r\n\r\n// ---------------------------------------------------------------------------\r\n// Storage\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function getMemoryDir(cwd: string): string {\r\n\treturn join(cwd, \".pi\", \"memory\");\r\n}\r\n\r\nfunction ensureMemoryDir(cwd: string): string {\r\n\tconst dir = getMemoryDir(cwd);\r\n\tif (!existsSync(dir)) {\r\n\t\tmkdirSync(dir, { recursive: true });\r\n\t}\r\n\treturn dir;\r\n}\r\n\r\nexport function loadStore<T>(cwd: string, filename: string): MemoryStore<T> {\r\n\tconst filepath = join(getMemoryDir(cwd), filename);\r\n\tif (!existsSync(filepath)) {\r\n\t\treturn { memories: [] };\r\n\t}\r\n\ttry {\r\n\t\tconst raw = readFileSync(filepath, \"utf-8\");\r\n\t\treturn JSON.parse(raw) as MemoryStore<T>;\r\n\t} catch {\r\n\t\treturn { memories: [] };\r\n\t}\r\n}\r\n\r\nexport function saveStore<T>(cwd: string, filename: string, store: MemoryStore<T>): void {\r\n\tconst dir = ensureMemoryDir(cwd);\r\n\tconst filepath = join(dir, filename);\r\n\twriteFileSync(filepath, JSON.stringify(store, null, 2), \"utf-8\");\r\n}\r\n\r\nexport function generateId(prefix: string, store: MemoryStore<{ id: string }>): string {\r\n\tlet max = 0;\r\n\tfor (const m of store.memories) {\r\n\t\tconst num = parseInt(m.id.replace(`${prefix}_`, \"\"), 10);\r\n\t\tif (!Number.isNaN(num) && num > max) max = num;\r\n\t}\r\n\treturn `${prefix}_${String(max + 1).padStart(3, \"0\")}`;\r\n}\r\n\r\nexport function nowISO(): string {\r\n\treturn new Date().toISOString();\r\n}\r\n\r\nexport function getSessionId(): string {\r\n\treturn `session_${Date.now()}`;\r\n}\r\n\r\nconst STORE_FILES: Record<MemoryType, string> = {\r\n\tprocedural: \"procedural.json\",\r\n\tepisodic: \"episodic.json\",\r\n\tsemantic: \"semantic.json\",\r\n};\r\n\r\nexport function getStoreFile(type: MemoryType): string {\r\n\treturn STORE_FILES[type];\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Search\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function searchMemories<T extends { tags: string[] }>(\r\n\tmemories: T[],\r\n\tquery: string,\r\n\tgetText: (m: T) => string,\r\n): T[] {\r\n\tconst lower = query.toLowerCase();\r\n\tconst queryWords = lower.split(/\\s+/).filter(Boolean);\r\n\treturn memories.filter((m) => {\r\n\t\tconst text = getText(m).toLowerCase();\r\n\t\tconst tagText = m.tags.join(\" \").toLowerCase();\r\n\t\tconst combined = `${text} ${tagText}`;\r\n\t\treturn queryWords.every((w) => combined.includes(w));\r\n\t});\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Compaction\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function compactEpisodicMemories(memories: EpisodicMemory[]): EpisodicMemory[] {\r\n\tif (memories.length <= 1) return memories;\r\n\r\n\tconst tagGroups = new Map<string, EpisodicMemory[]>();\r\n\tfor (const m of memories) {\r\n\t\tlet placed = false;\r\n\t\tfor (const [key, group] of tagGroups) {\r\n\t\t\tconst groupTags = new Set(key.split(\",\"));\r\n\t\t\tif (m.tags.some((t) => groupTags.has(t))) {\r\n\t\t\t\tgroup.push(m);\r\n\t\t\t\tconst mergedTags = new Set([...groupTags, ...m.tags]);\r\n\t\t\t\ttagGroups.delete(key);\r\n\t\t\t\ttagGroups.set([...mergedTags].join(\",\"), group);\r\n\t\t\t\tplaced = true;\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (!placed) {\r\n\t\t\ttagGroups.set(m.tags.join(\",\") || m.id, [m]);\r\n\t\t}\r\n\t}\r\n\r\n\tconst compacted: EpisodicMemory[] = [];\r\n\tfor (const [, group] of tagGroups) {\r\n\t\tif (group.length <= 1) {\r\n\t\t\tcompacted.push(...group);\r\n\t\t\tcontinue;\r\n\t\t}\r\n\t\tconst allDetails = group.flatMap((m) => m.details);\r\n\t\tconst allTags = [...new Set(group.flatMap((m) => m.tags))];\r\n\t\tconst allMistakes = group.flatMap((m) => m.reflection?.mistakes ?? []);\r\n\t\tconst allLessons = group.flatMap((m) => m.reflection?.lessons ?? []);\r\n\t\tconst summaries = group.map((m) => m.summary);\r\n\r\n\t\tcompacted.push({\r\n\t\t\tid: group[0].id,\r\n\t\t\tsummary: `Consolidated: ${summaries.join(\"; \")}`,\r\n\t\t\tdetails: [...new Set(allDetails)],\r\n\t\t\treflection:\r\n\t\t\t\tallMistakes.length > 0 || allLessons.length > 0\r\n\t\t\t\t\t? { mistakes: [...new Set(allMistakes)], lessons: [...new Set(allLessons)] }\r\n\t\t\t\t\t: undefined,\r\n\t\t\ttags: allTags,\r\n\t\t\tdate: group[group.length - 1].date,\r\n\t\t\tsourceSession: group[group.length - 1].sourceSession,\r\n\t\t});\r\n\t}\r\n\treturn compacted;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Display formatters\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function formatProceduralForDisplay(m: ProceduralMemory): string {\r\n\tconst steps = m.steps.map((s, i) => ` ${i + 1}. ${s}`).join(\"\\n\");\r\n\treturn `[${m.id}] ${m.name}\\n Trigger: ${m.trigger}\\n Tags: ${m.tags.join(\", \")}\\n Steps:\\n${steps}\\n Updated: ${m.updated}`;\r\n}\r\n\r\nexport function formatEpisodicForDisplay(m: EpisodicMemory): string {\r\n\tlet text = `[${m.id}] ${m.summary}\\n Date: ${m.date}\\n Tags: ${m.tags.join(\", \")}`;\r\n\tif (m.details.length > 0) {\r\n\t\ttext += `\\n Details:\\n${m.details.map((d) => ` - ${d}`).join(\"\\n\")}`;\r\n\t}\r\n\tif (m.reflection) {\r\n\t\tif (m.reflection.mistakes.length > 0) {\r\n\t\t\ttext += `\\n Mistakes:\\n${m.reflection.mistakes.map((x) => ` - ${x}`).join(\"\\n\")}`;\r\n\t\t}\r\n\t\tif (m.reflection.lessons.length > 0) {\r\n\t\t\ttext += `\\n Lessons:\\n${m.reflection.lessons.map((x) => ` - ${x}`).join(\"\\n\")}`;\r\n\t\t}\r\n\t}\r\n\treturn text;\r\n}\r\n\r\nexport function formatSemanticForDisplay(m: SemanticMemory): string {\r\n\treturn `[${m.id}] (${m.category}) ${m.text}\\n Tags: ${m.tags.join(\", \")}\\n Created: ${m.created}`;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// System prompt section\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function buildMemoryPromptSection(cwd: string): string {\r\n\tconst semantic = loadStore<SemanticMemory>(cwd, \"semantic.json\");\r\n\tconst procedural = loadStore<ProceduralMemory>(cwd, \"procedural.json\");\r\n\tconst episodic = loadStore<EpisodicMemory>(cwd, \"episodic.json\");\r\n\r\n\tif (semantic.memories.length === 0 && procedural.memories.length === 0 && episodic.memories.length === 0) {\r\n\t\treturn `\\n\\n<memory_system>\r\nYou have a persistent memory system that stores knowledge across sessions.\r\nCurrently no memories are stored. Use the memory_write tool to save:\r\n1. Procedural memories when the user guides you through a multi-step workflow.\r\n2. Semantic memories when the user states a preference, rule, correction, or fact.\r\n3. Episodic memories when a significant task completes.\r\n</memory_system>`;\r\n\t}\r\n\r\n\tconst parts: string[] = [];\r\n\r\n\tparts.push(`\\n\\n<memory_system>\r\nYou have a persistent memory system that stores knowledge across sessions.\r\nYou MUST use these memories to improve your responses. Do not ask the user\r\nto repeat things they have already taught you.\r\n\r\nAUTOMATIC MEMORY CAPTURE RULES:\r\n1. When the user guides you through a multi-step workflow (correcting steps,\r\n adding steps, reordering), save it as a PROCEDURAL memory using the\r\n memory_write tool. Extract the general workflow, not the specific instance.\r\n2. When the user states a preference or rule (\"always do X\", \"never do Y\",\r\n \"I prefer X\"), save it as a SEMANTIC memory immediately.\r\n3. When the user corrects you or you make a mistake, save the lesson as a\r\n SEMANTIC memory with category \"convention\".\r\n4. When a significant task completes, save a summary as an EPISODIC memory.\r\n5. If you detect a conflict with an existing memory, ask the user: \"You\r\n previously told me [old]. You are now saying [new]. Should I update this?\"\r\n Only update after confirmation.\r\n\r\nTIMESTAMP AWARENESS:\r\nEach user message includes a timestamp. Use these to detect time gaps.\r\nIf the user returns after a significant gap (>10 minutes), acknowledge it\r\nnaturally if relevant. Do not force it if the gap is not relevant.`);\r\n\r\n\tif (semantic.memories.length > 0) {\r\n\t\tparts.push(\"\\n<semantic_memories>\");\r\n\t\tfor (const m of semantic.memories) {\r\n\t\t\tparts.push(` <memory id=\"${m.id}\" category=\"${m.category}\">${escapeXml(m.text)}</memory>`);\r\n\t\t}\r\n\t\tparts.push(\"</semantic_memories>\");\r\n\t}\r\n\r\n\tif (procedural.memories.length > 0) {\r\n\t\tparts.push(\"\\n<procedural_memories>\");\r\n\t\tfor (const m of procedural.memories) {\r\n\t\t\tconst steps = m.steps.map((s, i) => `${i + 1}. ${s}`).join(\"; \");\r\n\t\t\tparts.push(\r\n\t\t\t\t` <procedure id=\"${m.id}\" name=\"${escapeXml(m.name)}\" trigger=\"${escapeXml(m.trigger)}\">${escapeXml(steps)}</procedure>`,\r\n\t\t\t);\r\n\t\t}\r\n\t\tparts.push(\"</procedural_memories>\");\r\n\t}\r\n\r\n\tif (episodic.memories.length > 0) {\r\n\t\tconst recent = episodic.memories.slice(-10);\r\n\t\tparts.push(\"\\n<recent_episodic_memories>\");\r\n\t\tfor (const m of recent) {\r\n\t\t\tconst details = m.details.join(\"; \");\r\n\t\t\tlet text = details;\r\n\t\t\tif (m.reflection?.lessons.length) {\r\n\t\t\t\ttext += ` | Lessons: ${m.reflection.lessons.join(\"; \")}`;\r\n\t\t\t}\r\n\t\t\tparts.push(\r\n\t\t\t\t` <episode id=\"${m.id}\" date=\"${m.date}\" summary=\"${escapeXml(m.summary)}\">${escapeXml(text)}</episode>`,\r\n\t\t\t);\r\n\t\t}\r\n\t\tparts.push(\"</recent_episodic_memories>\");\r\n\t}\r\n\r\n\tparts.push(\"\\n</memory_system>\");\r\n\r\n\treturn parts.join(\"\\n\");\r\n}\r\n\r\nfunction escapeXml(str: string): string {\r\n\treturn str\r\n\t\t.replace(/&/g, \"&\")\r\n\t\t.replace(/</g, \"<\")\r\n\t\t.replace(/>/g, \">\")\r\n\t\t.replace(/\"/g, \""\")\r\n\t\t.replace(/'/g, \"'\");\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Timestamp formatting\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function formatTimestamp(ts: number): string {\r\n\tconst d = new Date(ts);\r\n\treturn d.toLocaleString(\"en-US\", {\r\n\t\tyear: \"numeric\",\r\n\t\tmonth: \"short\",\r\n\t\tday: \"numeric\",\r\n\t\thour: \"2-digit\",\r\n\t\tminute: \"2-digit\",\r\n\t\tsecond: \"2-digit\",\r\n\t\thour12: false,\r\n\t});\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Memory counts (for status display)\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface MemoryCounts {\r\n\tsemantic: number;\r\n\tprocedural: number;\r\n\tepisodic: number;\r\n\ttotal: number;\r\n}\r\n\r\nexport function getMemoryCounts(cwd: string): MemoryCounts {\r\n\tconst semantic = loadStore<SemanticMemory>(cwd, \"semantic.json\").memories.length;\r\n\tconst procedural = loadStore<ProceduralMemory>(cwd, \"procedural.json\").memories.length;\r\n\tconst episodic = loadStore<EpisodicMemory>(cwd, \"episodic.json\").memories.length;\r\n\treturn { semantic, procedural, episodic, total: semantic + procedural + episodic };\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/core/memory.ts"],"names":[],"mappings":"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","sourcesContent":["/**\r\n * Persistent memory system — procedural, episodic, and semantic memories\r\n * stored per-project in .pi/memory/ or globally in ~/.pi/agent/memory/ as JSON files.\r\n * Scope is configured via settings (memory.scope: \"project\" | \"global\")\r\n * or overridden per-project via a .zpi config file.\r\n */\r\n\r\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\r\nimport { homedir } from \"node:os\";\r\nimport { join } from \"node:path\";\r\nimport { CONFIG_DIR_NAME } from \"../config.js\";\r\n\r\n// ---------------------------------------------------------------------------\r\n// Types\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface ProceduralMemory {\r\n\tid: string;\r\n\tname: string;\r\n\ttrigger: string;\r\n\tsteps: string[];\r\n\ttags: string[];\r\n\tcreated: string;\r\n\tupdated: string;\r\n\tsourceSession: string;\r\n}\r\n\r\nexport interface EpisodicMemory {\r\n\tid: string;\r\n\tsummary: string;\r\n\tdetails: string[];\r\n\treflection?: {\r\n\t\tmistakes: string[];\r\n\t\tlessons: string[];\r\n\t};\r\n\ttags: string[];\r\n\tdate: string;\r\n\tsourceSession: string;\r\n}\r\n\r\nexport interface SemanticMemory {\r\n\tid: string;\r\n\tcategory: \"preference\" | \"architecture\" | \"convention\" | \"fact\";\r\n\ttext: string;\r\n\ttags: string[];\r\n\tcreated: string;\r\n\tsourceSession: string;\r\n}\r\n\r\nexport interface MemoryStore<T> {\r\n\tmemories: T[];\r\n}\r\n\r\nexport type MemoryType = \"procedural\" | \"episodic\" | \"semantic\";\r\nexport type MemoryScope = \"project\" | \"global\";\r\n\r\n// ---------------------------------------------------------------------------\r\n// .zpi config file\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface ZpiConfig {\r\n\tmemory?: {\r\n\t\tscope?: MemoryScope;\r\n\t};\r\n}\r\n\r\n/**\r\n * Read .zpi config file from the project root.\r\n * Returns undefined if file doesn't exist or is invalid.\r\n */\r\nexport function readZpiConfig(cwd: string): ZpiConfig | undefined {\r\n\tconst configPath = join(cwd, \".zpi\");\r\n\tif (!existsSync(configPath)) {\r\n\t\treturn undefined;\r\n\t}\r\n\ttry {\r\n\t\tconst raw = readFileSync(configPath, \"utf-8\");\r\n\t\treturn JSON.parse(raw) as ZpiConfig;\r\n\t} catch {\r\n\t\treturn undefined;\r\n\t}\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Storage\r\n// ---------------------------------------------------------------------------\r\n\r\n/** Get the global memory directory (~/.pi/agent/memory/) */\r\nexport function getGlobalMemoryDir(): string {\r\n\treturn join(homedir(), CONFIG_DIR_NAME, \"agent\", \"memory\");\r\n}\r\n\r\n/** Get the project memory directory (<cwd>/.pi/memory/) */\r\nexport function getProjectMemoryDir(cwd: string): string {\r\n\treturn join(cwd, CONFIG_DIR_NAME, \"memory\");\r\n}\r\n\r\n/**\r\n * Resolve the effective memory scope for a project.\r\n * Priority: .zpi config > settings > default (\"project\")\r\n */\r\nexport function resolveMemoryScope(cwd: string, settingsScope: MemoryScope): MemoryScope {\r\n\tconst zpiConfig = readZpiConfig(cwd);\r\n\tif (zpiConfig?.memory?.scope) {\r\n\t\treturn zpiConfig.memory.scope;\r\n\t}\r\n\treturn settingsScope;\r\n}\r\n\r\n/**\r\n * Get the memory directory based on scope.\r\n */\r\nexport function getMemoryDir(cwd: string, scope: MemoryScope = \"global\"): string {\r\n\tif (scope === \"global\") {\r\n\t\treturn getGlobalMemoryDir();\r\n\t}\r\n\treturn getProjectMemoryDir(cwd);\r\n}\r\n\r\nfunction ensureMemoryDir(cwd: string, scope: MemoryScope = \"global\"): string {\r\n\tconst dir = getMemoryDir(cwd, scope);\r\n\tif (!existsSync(dir)) {\r\n\t\tmkdirSync(dir, { recursive: true });\r\n\t}\r\n\treturn dir;\r\n}\r\n\r\nexport function loadStore<T>(cwd: string, filename: string, scope: MemoryScope = \"global\"): MemoryStore<T> {\r\n\tconst filepath = join(getMemoryDir(cwd, scope), filename);\r\n\tif (!existsSync(filepath)) {\r\n\t\treturn { memories: [] };\r\n\t}\r\n\ttry {\r\n\t\tconst raw = readFileSync(filepath, \"utf-8\");\r\n\t\treturn JSON.parse(raw) as MemoryStore<T>;\r\n\t} catch {\r\n\t\treturn { memories: [] };\r\n\t}\r\n}\r\n\r\nexport function saveStore<T>(cwd: string, filename: string, store: MemoryStore<T>, scope: MemoryScope = \"global\"): void {\r\n\tconst dir = ensureMemoryDir(cwd, scope);\r\n\tconst filepath = join(dir, filename);\r\n\twriteFileSync(filepath, JSON.stringify(store, null, 2), \"utf-8\");\r\n}\r\n\r\nexport function generateId(prefix: string, store: MemoryStore<{ id: string }>): string {\r\n\tlet max = 0;\r\n\tfor (const m of store.memories) {\r\n\t\tconst num = parseInt(m.id.replace(`${prefix}_`, \"\"), 10);\r\n\t\tif (!Number.isNaN(num) && num > max) max = num;\r\n\t}\r\n\treturn `${prefix}_${String(max + 1).padStart(3, \"0\")}`;\r\n}\r\n\r\nexport function nowISO(): string {\r\n\treturn new Date().toISOString();\r\n}\r\n\r\nexport function getSessionId(): string {\r\n\treturn `session_${Date.now()}`;\r\n}\r\n\r\nconst STORE_FILES: Record<MemoryType, string> = {\r\n\tprocedural: \"procedural.json\",\r\n\tepisodic: \"episodic.json\",\r\n\tsemantic: \"semantic.json\",\r\n};\r\n\r\nexport function getStoreFile(type: MemoryType): string {\r\n\treturn STORE_FILES[type];\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Search\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function searchMemories<T extends { tags: string[] }>(\r\n\tmemories: T[],\r\n\tquery: string,\r\n\tgetText: (m: T) => string,\r\n): T[] {\r\n\tconst lower = query.toLowerCase();\r\n\tconst queryWords = lower.split(/\\s+/).filter(Boolean);\r\n\treturn memories.filter((m) => {\r\n\t\tconst text = getText(m).toLowerCase();\r\n\t\tconst tagText = m.tags.join(\" \").toLowerCase();\r\n\t\tconst combined = `${text} ${tagText}`;\r\n\t\treturn queryWords.every((w) => combined.includes(w));\r\n\t});\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Compaction\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function compactEpisodicMemories(memories: EpisodicMemory[]): EpisodicMemory[] {\r\n\tif (memories.length <= 1) return memories;\r\n\r\n\tconst tagGroups = new Map<string, EpisodicMemory[]>();\r\n\tfor (const m of memories) {\r\n\t\tlet placed = false;\r\n\t\tfor (const [key, group] of tagGroups) {\r\n\t\t\tconst groupTags = new Set(key.split(\",\"));\r\n\t\t\tif (m.tags.some((t) => groupTags.has(t))) {\r\n\t\t\t\tgroup.push(m);\r\n\t\t\t\tconst mergedTags = new Set([...groupTags, ...m.tags]);\r\n\t\t\t\ttagGroups.delete(key);\r\n\t\t\t\ttagGroups.set([...mergedTags].join(\",\"), group);\r\n\t\t\t\tplaced = true;\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (!placed) {\r\n\t\t\ttagGroups.set(m.tags.join(\",\") || m.id, [m]);\r\n\t\t}\r\n\t}\r\n\r\n\tconst compacted: EpisodicMemory[] = [];\r\n\tfor (const [, group] of tagGroups) {\r\n\t\tif (group.length <= 1) {\r\n\t\t\tcompacted.push(...group);\r\n\t\t\tcontinue;\r\n\t\t}\r\n\t\tconst allDetails = group.flatMap((m) => m.details);\r\n\t\tconst allTags = [...new Set(group.flatMap((m) => m.tags))];\r\n\t\tconst allMistakes = group.flatMap((m) => m.reflection?.mistakes ?? []);\r\n\t\tconst allLessons = group.flatMap((m) => m.reflection?.lessons ?? []);\r\n\t\tconst summaries = group.map((m) => m.summary);\r\n\r\n\t\tcompacted.push({\r\n\t\t\tid: group[0].id,\r\n\t\t\tsummary: `Consolidated: ${summaries.join(\"; \")}`,\r\n\t\t\tdetails: [...new Set(allDetails)],\r\n\t\t\treflection:\r\n\t\t\t\tallMistakes.length > 0 || allLessons.length > 0\r\n\t\t\t\t\t? { mistakes: [...new Set(allMistakes)], lessons: [...new Set(allLessons)] }\r\n\t\t\t\t\t: undefined,\r\n\t\t\ttags: allTags,\r\n\t\t\tdate: group[group.length - 1].date,\r\n\t\t\tsourceSession: group[group.length - 1].sourceSession,\r\n\t\t});\r\n\t}\r\n\treturn compacted;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Display formatters\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function formatProceduralForDisplay(m: ProceduralMemory): string {\r\n\tconst steps = m.steps.map((s, i) => ` ${i + 1}. ${s}`).join(\"\\n\");\r\n\treturn `[${m.id}] ${m.name}\\n Trigger: ${m.trigger}\\n Tags: ${m.tags.join(\", \")}\\n Steps:\\n${steps}\\n Updated: ${m.updated}`;\r\n}\r\n\r\nexport function formatEpisodicForDisplay(m: EpisodicMemory): string {\r\n\tlet text = `[${m.id}] ${m.summary}\\n Date: ${m.date}\\n Tags: ${m.tags.join(\", \")}`;\r\n\tif (m.details.length > 0) {\r\n\t\ttext += `\\n Details:\\n${m.details.map((d) => ` - ${d}`).join(\"\\n\")}`;\r\n\t}\r\n\tif (m.reflection) {\r\n\t\tif (m.reflection.mistakes.length > 0) {\r\n\t\t\ttext += `\\n Mistakes:\\n${m.reflection.mistakes.map((x) => ` - ${x}`).join(\"\\n\")}`;\r\n\t\t}\r\n\t\tif (m.reflection.lessons.length > 0) {\r\n\t\t\ttext += `\\n Lessons:\\n${m.reflection.lessons.map((x) => ` - ${x}`).join(\"\\n\")}`;\r\n\t\t}\r\n\t}\r\n\treturn text;\r\n}\r\n\r\nexport function formatSemanticForDisplay(m: SemanticMemory): string {\r\n\treturn `[${m.id}] (${m.category}) ${m.text}\\n Tags: ${m.tags.join(\", \")}\\n Created: ${m.created}`;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// System prompt section\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function buildMemoryPromptSection(cwd: string, scope: MemoryScope = \"global\"): string {\r\n\tconst semantic = loadStore<SemanticMemory>(cwd, \"semantic.json\", scope);\r\n\tconst procedural = loadStore<ProceduralMemory>(cwd, \"procedural.json\", scope);\r\n\tconst episodic = loadStore<EpisodicMemory>(cwd, \"episodic.json\", scope);\r\n\r\n\tif (semantic.memories.length === 0 && procedural.memories.length === 0 && episodic.memories.length === 0) {\r\n\t\treturn `\\n\\n<memory_system>\r\nYou have a persistent memory system that stores knowledge across sessions.\r\nCurrently no memories are stored. Use the memory_write tool to save:\r\n1. Procedural memories when the user guides you through a multi-step workflow.\r\n2. Semantic memories when the user states a preference, rule, correction, or fact.\r\n3. Episodic memories when a significant task completes.\r\n</memory_system>`;\r\n\t}\r\n\r\n\tconst parts: string[] = [];\r\n\r\n\tparts.push(`\\n\\n<memory_system>\r\nYou have a persistent memory system that stores knowledge across sessions.\r\nYou MUST use these memories to improve your responses. Do not ask the user\r\nto repeat things they have already taught you.\r\n\r\nAUTOMATIC MEMORY CAPTURE RULES:\r\n1. When the user guides you through a multi-step workflow (correcting steps,\r\n adding steps, reordering), save it as a PROCEDURAL memory using the\r\n memory_write tool. Extract the general workflow, not the specific instance.\r\n2. When the user states a preference or rule (\"always do X\", \"never do Y\",\r\n \"I prefer X\"), save it as a SEMANTIC memory immediately.\r\n3. When the user corrects you or you make a mistake, save the lesson as a\r\n SEMANTIC memory with category \"convention\".\r\n4. When a significant task completes, save a summary as an EPISODIC memory.\r\n5. If you detect a conflict with an existing memory, ask the user: \"You\r\n previously told me [old]. You are now saying [new]. Should I update this?\"\r\n Only update after confirmation.\r\n\r\nTIMESTAMP AWARENESS:\r\nEach user message includes a timestamp. Use these to detect time gaps.\r\nIf the user returns after a significant gap (>10 minutes), acknowledge it\r\nnaturally if relevant. Do not force it if the gap is not relevant.`);\r\n\r\n\tif (semantic.memories.length > 0) {\r\n\t\tparts.push(\"\\n<semantic_memories>\");\r\n\t\tfor (const m of semantic.memories) {\r\n\t\t\tparts.push(` <memory id=\"${m.id}\" category=\"${m.category}\">${escapeXml(m.text)}</memory>`);\r\n\t\t}\r\n\t\tparts.push(\"</semantic_memories>\");\r\n\t}\r\n\r\n\tif (procedural.memories.length > 0) {\r\n\t\tparts.push(\"\\n<procedural_memories>\");\r\n\t\tfor (const m of procedural.memories) {\r\n\t\t\tconst steps = m.steps.map((s, i) => `${i + 1}. ${s}`).join(\"; \");\r\n\t\t\tparts.push(\r\n\t\t\t\t` <procedure id=\"${m.id}\" name=\"${escapeXml(m.name)}\" trigger=\"${escapeXml(m.trigger)}\">${escapeXml(steps)}</procedure>`,\r\n\t\t\t);\r\n\t\t}\r\n\t\tparts.push(\"</procedural_memories>\");\r\n\t}\r\n\r\n\tif (episodic.memories.length > 0) {\r\n\t\tconst recent = episodic.memories.slice(-10);\r\n\t\tparts.push(\"\\n<recent_episodic_memories>\");\r\n\t\tfor (const m of recent) {\r\n\t\t\tconst details = m.details.join(\"; \");\r\n\t\t\tlet text = details;\r\n\t\t\tif (m.reflection?.lessons.length) {\r\n\t\t\t\ttext += ` | Lessons: ${m.reflection.lessons.join(\"; \")}`;\r\n\t\t\t}\r\n\t\t\tparts.push(\r\n\t\t\t\t` <episode id=\"${m.id}\" date=\"${m.date}\" summary=\"${escapeXml(m.summary)}\">${escapeXml(text)}</episode>`,\r\n\t\t\t);\r\n\t\t}\r\n\t\tparts.push(\"</recent_episodic_memories>\");\r\n\t}\r\n\r\n\tparts.push(\"\\n</memory_system>\");\r\n\r\n\treturn parts.join(\"\\n\");\r\n}\r\n\r\nfunction escapeXml(str: string): string {\r\n\treturn str\r\n\t\t.replace(/&/g, \"&\")\r\n\t\t.replace(/</g, \"<\")\r\n\t\t.replace(/>/g, \">\")\r\n\t\t.replace(/\"/g, \""\")\r\n\t\t.replace(/'/g, \"'\");\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Timestamp formatting\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function formatTimestamp(ts: number): string {\r\n\tconst d = new Date(ts);\r\n\treturn d.toLocaleString(\"en-US\", {\r\n\t\tyear: \"numeric\",\r\n\t\tmonth: \"short\",\r\n\t\tday: \"numeric\",\r\n\t\thour: \"2-digit\",\r\n\t\tminute: \"2-digit\",\r\n\t\tsecond: \"2-digit\",\r\n\t\thour12: false,\r\n\t});\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Memory counts (for status display)\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface MemoryCounts {\r\n\tsemantic: number;\r\n\tprocedural: number;\r\n\tepisodic: number;\r\n\ttotal: number;\r\n}\r\n\r\nexport function getMemoryCounts(cwd: string, scope: MemoryScope = \"global\"): MemoryCounts {\r\n\tconst semantic = loadStore<SemanticMemory>(cwd, \"semantic.json\", scope).memories.length;\r\n\tconst procedural = loadStore<ProceduralMemory>(cwd, \"procedural.json\", scope).memories.length;\r\n\tconst episodic = loadStore<EpisodicMemory>(cwd, \"episodic.json\", scope).memories.length;\r\n\treturn { semantic, procedural, episodic, total: semantic + procedural + episodic };\r\n}\r\n"]}
|
package/dist/core/memory.js
CHANGED
|
@@ -1,24 +1,70 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Persistent memory system — procedural, episodic, and semantic memories
|
|
3
|
-
* stored per-project in .pi/memory/ as JSON files.
|
|
3
|
+
* stored per-project in .pi/memory/ or globally in ~/.pi/agent/memory/ as JSON files.
|
|
4
|
+
* Scope is configured via settings (memory.scope: "project" | "global")
|
|
5
|
+
* or overridden per-project via a .zpi config file.
|
|
4
6
|
*/
|
|
5
7
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { homedir } from "node:os";
|
|
6
9
|
import { join } from "node:path";
|
|
10
|
+
import { CONFIG_DIR_NAME } from "../config.js";
|
|
11
|
+
/**
|
|
12
|
+
* Read .zpi config file from the project root.
|
|
13
|
+
* Returns undefined if file doesn't exist or is invalid.
|
|
14
|
+
*/
|
|
15
|
+
export function readZpiConfig(cwd) {
|
|
16
|
+
const configPath = join(cwd, ".zpi");
|
|
17
|
+
if (!existsSync(configPath)) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
22
|
+
return JSON.parse(raw);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
7
28
|
// ---------------------------------------------------------------------------
|
|
8
29
|
// Storage
|
|
9
30
|
// ---------------------------------------------------------------------------
|
|
10
|
-
|
|
11
|
-
|
|
31
|
+
/** Get the global memory directory (~/.pi/agent/memory/) */
|
|
32
|
+
export function getGlobalMemoryDir() {
|
|
33
|
+
return join(homedir(), CONFIG_DIR_NAME, "agent", "memory");
|
|
34
|
+
}
|
|
35
|
+
/** Get the project memory directory (<cwd>/.pi/memory/) */
|
|
36
|
+
export function getProjectMemoryDir(cwd) {
|
|
37
|
+
return join(cwd, CONFIG_DIR_NAME, "memory");
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Resolve the effective memory scope for a project.
|
|
41
|
+
* Priority: .zpi config > settings > default ("project")
|
|
42
|
+
*/
|
|
43
|
+
export function resolveMemoryScope(cwd, settingsScope) {
|
|
44
|
+
const zpiConfig = readZpiConfig(cwd);
|
|
45
|
+
if (zpiConfig?.memory?.scope) {
|
|
46
|
+
return zpiConfig.memory.scope;
|
|
47
|
+
}
|
|
48
|
+
return settingsScope;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get the memory directory based on scope.
|
|
52
|
+
*/
|
|
53
|
+
export function getMemoryDir(cwd, scope = "global") {
|
|
54
|
+
if (scope === "global") {
|
|
55
|
+
return getGlobalMemoryDir();
|
|
56
|
+
}
|
|
57
|
+
return getProjectMemoryDir(cwd);
|
|
12
58
|
}
|
|
13
|
-
function ensureMemoryDir(cwd) {
|
|
14
|
-
const dir = getMemoryDir(cwd);
|
|
59
|
+
function ensureMemoryDir(cwd, scope = "global") {
|
|
60
|
+
const dir = getMemoryDir(cwd, scope);
|
|
15
61
|
if (!existsSync(dir)) {
|
|
16
62
|
mkdirSync(dir, { recursive: true });
|
|
17
63
|
}
|
|
18
64
|
return dir;
|
|
19
65
|
}
|
|
20
|
-
export function loadStore(cwd, filename) {
|
|
21
|
-
const filepath = join(getMemoryDir(cwd), filename);
|
|
66
|
+
export function loadStore(cwd, filename, scope = "global") {
|
|
67
|
+
const filepath = join(getMemoryDir(cwd, scope), filename);
|
|
22
68
|
if (!existsSync(filepath)) {
|
|
23
69
|
return { memories: [] };
|
|
24
70
|
}
|
|
@@ -30,8 +76,8 @@ export function loadStore(cwd, filename) {
|
|
|
30
76
|
return { memories: [] };
|
|
31
77
|
}
|
|
32
78
|
}
|
|
33
|
-
export function saveStore(cwd, filename, store) {
|
|
34
|
-
const dir = ensureMemoryDir(cwd);
|
|
79
|
+
export function saveStore(cwd, filename, store, scope = "global") {
|
|
80
|
+
const dir = ensureMemoryDir(cwd, scope);
|
|
35
81
|
const filepath = join(dir, filename);
|
|
36
82
|
writeFileSync(filepath, JSON.stringify(store, null, 2), "utf-8");
|
|
37
83
|
}
|
|
@@ -148,10 +194,10 @@ export function formatSemanticForDisplay(m) {
|
|
|
148
194
|
// ---------------------------------------------------------------------------
|
|
149
195
|
// System prompt section
|
|
150
196
|
// ---------------------------------------------------------------------------
|
|
151
|
-
export function buildMemoryPromptSection(cwd) {
|
|
152
|
-
const semantic = loadStore(cwd, "semantic.json");
|
|
153
|
-
const procedural = loadStore(cwd, "procedural.json");
|
|
154
|
-
const episodic = loadStore(cwd, "episodic.json");
|
|
197
|
+
export function buildMemoryPromptSection(cwd, scope = "global") {
|
|
198
|
+
const semantic = loadStore(cwd, "semantic.json", scope);
|
|
199
|
+
const procedural = loadStore(cwd, "procedural.json", scope);
|
|
200
|
+
const episodic = loadStore(cwd, "episodic.json", scope);
|
|
155
201
|
if (semantic.memories.length === 0 && procedural.memories.length === 0 && episodic.memories.length === 0) {
|
|
156
202
|
return `\n\n<memory_system>
|
|
157
203
|
You have a persistent memory system that stores knowledge across sessions.
|
|
@@ -238,10 +284,10 @@ export function formatTimestamp(ts) {
|
|
|
238
284
|
hour12: false,
|
|
239
285
|
});
|
|
240
286
|
}
|
|
241
|
-
export function getMemoryCounts(cwd) {
|
|
242
|
-
const semantic = loadStore(cwd, "semantic.json").memories.length;
|
|
243
|
-
const procedural = loadStore(cwd, "procedural.json").memories.length;
|
|
244
|
-
const episodic = loadStore(cwd, "episodic.json").memories.length;
|
|
287
|
+
export function getMemoryCounts(cwd, scope = "global") {
|
|
288
|
+
const semantic = loadStore(cwd, "semantic.json", scope).memories.length;
|
|
289
|
+
const procedural = loadStore(cwd, "procedural.json", scope).memories.length;
|
|
290
|
+
const episodic = loadStore(cwd, "episodic.json", scope).memories.length;
|
|
245
291
|
return { semantic, procedural, episodic, total: semantic + procedural + episodic };
|
|
246
292
|
}
|
|
247
293
|
//# sourceMappingURL=memory.js.map
|
package/dist/core/memory.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/core/memory.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AA6CjC,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E,MAAM,UAAU,YAAY,CAAC,GAAW,EAAU;IACjD,OAAO,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;AAAA,CAClC;AAED,SAAS,eAAe,CAAC,GAAW,EAAU;IAC7C,MAAM,GAAG,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACtB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrC,CAAC;IACD,OAAO,GAAG,CAAC;AAAA,CACX;AAED,MAAM,UAAU,SAAS,CAAI,GAAW,EAAE,QAAgB,EAAkB;IAC3E,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAC;IACnD,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IACzB,CAAC;IACD,IAAI,CAAC;QACJ,MAAM,GAAG,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC5C,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAmB,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IACzB,CAAC;AAAA,CACD;AAED,MAAM,UAAU,SAAS,CAAI,GAAW,EAAE,QAAgB,EAAE,KAAqB,EAAQ;IACxF,MAAM,GAAG,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IACjC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACrC,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AAAA,CACjE;AAED,MAAM,UAAU,UAAU,CAAC,MAAc,EAAE,KAAkC,EAAU;IACtF,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QAChC,MAAM,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,MAAM,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QACzD,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,GAAG;YAAE,GAAG,GAAG,GAAG,CAAC;IAChD,CAAC;IACD,OAAO,GAAG,MAAM,IAAI,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;AAAA,CACvD;AAED,MAAM,UAAU,MAAM,GAAW;IAChC,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAAA,CAChC;AAED,MAAM,UAAU,YAAY,GAAW;IACtC,OAAO,WAAW,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;AAAA,CAC/B;AAED,MAAM,WAAW,GAA+B;IAC/C,UAAU,EAAE,iBAAiB;IAC7B,QAAQ,EAAE,eAAe;IACzB,QAAQ,EAAE,eAAe;CACzB,CAAC;AAEF,MAAM,UAAU,YAAY,CAAC,IAAgB,EAAU;IACtD,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC;AAAA,CACzB;AAED,8EAA8E;AAC9E,SAAS;AACT,8EAA8E;AAE9E,MAAM,UAAU,cAAc,CAC7B,QAAa,EACb,KAAa,EACb,OAAyB,EACnB;IACN,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IAClC,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACtD,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QACtC,MAAM,OAAO,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;QAC/C,MAAM,QAAQ,GAAG,GAAG,IAAI,IAAI,OAAO,EAAE,CAAC;QACtC,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IAAA,CACrD,CAAC,CAAC;AAAA,CACH;AAED,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E,MAAM,UAAU,uBAAuB,CAAC,QAA0B,EAAoB;IACrF,IAAI,QAAQ,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,QAAQ,CAAC;IAE1C,MAAM,SAAS,GAAG,IAAI,GAAG,EAA4B,CAAC;IACtD,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QAC1B,IAAI,MAAM,GAAG,KAAK,CAAC;QACnB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,SAAS,EAAE,CAAC;YACtC,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;YAC1C,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC1C,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBACd,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,SAAS,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;gBACtD,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACtB,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;gBAChD,MAAM,GAAG,IAAI,CAAC;gBACd,MAAM;YACP,CAAC;QACF,CAAC;QACD,IAAI,CAAC,MAAM,EAAE,CAAC;YACb,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9C,CAAC;IACF,CAAC;IAED,MAAM,SAAS,GAAqB,EAAE,CAAC;IACvC,KAAK,MAAM,CAAC,EAAE,KAAK,CAAC,IAAI,SAAS,EAAE,CAAC;QACnC,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;YACvB,SAAS,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC;YACzB,SAAS;QACV,CAAC;QACD,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QACnD,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC3D,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,EAAE,QAAQ,IAAI,EAAE,CAAC,CAAC;QACvE,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC;QACrE,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAE9C,SAAS,CAAC,IAAI,CAAC;YACd,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE;YACf,OAAO,EAAE,iBAAiB,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;YAChD,OAAO,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC;YACjC,UAAU,EACT,WAAW,CAAC,MAAM,GAAG,CAAC,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;gBAC9C,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC,EAAE;gBAC5E,CAAC,CAAC,SAAS;YACb,IAAI,EAAE,OAAO;YACb,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI;YAClC,aAAa,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,aAAa;SACpD,CAAC,CAAC;IACJ,CAAC;IACD,OAAO,SAAS,CAAC;AAAA,CACjB;AAED,8EAA8E;AAC9E,qBAAqB;AACrB,8EAA8E;AAE9E,MAAM,UAAU,0BAA0B,CAAC,CAAmB,EAAU;IACvE,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnE,OAAO,IAAI,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,gBAAgB,CAAC,CAAC,OAAO,aAAa,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,KAAK,gBAAgB,CAAC,CAAC,OAAO,EAAE,CAAC;AAAA,CACjI;AAED,MAAM,UAAU,wBAAwB,CAAC,CAAiB,EAAU;IACnE,IAAI,IAAI,GAAG,IAAI,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,OAAO,aAAa,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IACrF,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,IAAI,IAAI,iBAAiB,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IAC1E,CAAC;IACD,IAAI,CAAC,CAAC,UAAU,EAAE,CAAC;QAClB,IAAI,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtC,IAAI,IAAI,kBAAkB,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACvF,CAAC;QACD,IAAI,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrC,IAAI,IAAI,iBAAiB,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACrF,CAAC;IACF,CAAC;IACD,OAAO,IAAI,CAAC;AAAA,CACZ;AAED,MAAM,UAAU,wBAAwB,CAAC,CAAiB,EAAU;IACnE,OAAO,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,OAAO,EAAE,CAAC;AAAA,CACpG;AAED,8EAA8E;AAC9E,wBAAwB;AACxB,8EAA8E;AAE9E,MAAM,UAAU,wBAAwB,CAAC,GAAW,EAAU;IAC7D,MAAM,QAAQ,GAAG,SAAS,CAAiB,GAAG,EAAE,eAAe,CAAC,CAAC;IACjE,MAAM,UAAU,GAAG,SAAS,CAAmB,GAAG,EAAE,iBAAiB,CAAC,CAAC;IACvE,MAAM,QAAQ,GAAG,SAAS,CAAiB,GAAG,EAAE,eAAe,CAAC,CAAC;IAEjE,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,UAAU,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1G,OAAO;;;;;;iBAMQ,CAAC;IACjB,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;mEAqBuD,CAAC,CAAC;IAEpE,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClC,KAAK,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;QACpC,KAAK,MAAM,CAAC,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;YACnC,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC7F,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IACpC,CAAC;IAED,IAAI,UAAU,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpC,KAAK,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QACtC,KAAK,MAAM,CAAC,IAAI,UAAU,CAAC,QAAQ,EAAE,CAAC;YACrC,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjE,KAAK,CAAC,IAAI,CACT,oBAAoB,CAAC,CAAC,EAAE,WAAW,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,SAAS,CAAC,KAAK,CAAC,cAAc,CACzH,CAAC;QACH,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;IACtC,CAAC;IAED,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;QAC5C,KAAK,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;QAC3C,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;YACxB,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrC,IAAI,IAAI,GAAG,OAAO,CAAC;YACnB,IAAI,CAAC,CAAC,UAAU,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;gBAClC,IAAI,IAAI,eAAe,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1D,CAAC;YACD,KAAK,CAAC,IAAI,CACT,kBAAkB,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,IAAI,cAAc,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,SAAS,CAAC,IAAI,CAAC,YAAY,CACzG,CAAC;QACH,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;IAC3C,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;IAEjC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAAA,CACxB;AAED,SAAS,SAAS,CAAC,GAAW,EAAU;IACvC,OAAO,GAAG;SACR,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAAA,CAC1B;AAED,8EAA8E;AAC9E,uBAAuB;AACvB,8EAA8E;AAE9E,MAAM,UAAU,eAAe,CAAC,EAAU,EAAU;IACnD,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC;IACvB,OAAO,CAAC,CAAC,cAAc,CAAC,OAAO,EAAE;QAChC,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,OAAO;QACd,GAAG,EAAE,SAAS;QACd,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,SAAS;QACjB,MAAM,EAAE,SAAS;QACjB,MAAM,EAAE,KAAK;KACb,CAAC,CAAC;AAAA,CACH;AAaD,MAAM,UAAU,eAAe,CAAC,GAAW,EAAgB;IAC1D,MAAM,QAAQ,GAAG,SAAS,CAAiB,GAAG,EAAE,eAAe,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;IACjF,MAAM,UAAU,GAAG,SAAS,CAAmB,GAAG,EAAE,iBAAiB,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;IACvF,MAAM,QAAQ,GAAG,SAAS,CAAiB,GAAG,EAAE,eAAe,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;IACjF,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,GAAG,UAAU,GAAG,QAAQ,EAAE,CAAC;AAAA,CACnF","sourcesContent":["/**\r\n * Persistent memory system — procedural, episodic, and semantic memories\r\n * stored per-project in .pi/memory/ as JSON files.\r\n */\r\n\r\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\r\nimport { join } from \"node:path\";\r\n\r\n// ---------------------------------------------------------------------------\r\n// Types\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface ProceduralMemory {\r\n\tid: string;\r\n\tname: string;\r\n\ttrigger: string;\r\n\tsteps: string[];\r\n\ttags: string[];\r\n\tcreated: string;\r\n\tupdated: string;\r\n\tsourceSession: string;\r\n}\r\n\r\nexport interface EpisodicMemory {\r\n\tid: string;\r\n\tsummary: string;\r\n\tdetails: string[];\r\n\treflection?: {\r\n\t\tmistakes: string[];\r\n\t\tlessons: string[];\r\n\t};\r\n\ttags: string[];\r\n\tdate: string;\r\n\tsourceSession: string;\r\n}\r\n\r\nexport interface SemanticMemory {\r\n\tid: string;\r\n\tcategory: \"preference\" | \"architecture\" | \"convention\" | \"fact\";\r\n\ttext: string;\r\n\ttags: string[];\r\n\tcreated: string;\r\n\tsourceSession: string;\r\n}\r\n\r\nexport interface MemoryStore<T> {\r\n\tmemories: T[];\r\n}\r\n\r\nexport type MemoryType = \"procedural\" | \"episodic\" | \"semantic\";\r\n\r\n// ---------------------------------------------------------------------------\r\n// Storage\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function getMemoryDir(cwd: string): string {\r\n\treturn join(cwd, \".pi\", \"memory\");\r\n}\r\n\r\nfunction ensureMemoryDir(cwd: string): string {\r\n\tconst dir = getMemoryDir(cwd);\r\n\tif (!existsSync(dir)) {\r\n\t\tmkdirSync(dir, { recursive: true });\r\n\t}\r\n\treturn dir;\r\n}\r\n\r\nexport function loadStore<T>(cwd: string, filename: string): MemoryStore<T> {\r\n\tconst filepath = join(getMemoryDir(cwd), filename);\r\n\tif (!existsSync(filepath)) {\r\n\t\treturn { memories: [] };\r\n\t}\r\n\ttry {\r\n\t\tconst raw = readFileSync(filepath, \"utf-8\");\r\n\t\treturn JSON.parse(raw) as MemoryStore<T>;\r\n\t} catch {\r\n\t\treturn { memories: [] };\r\n\t}\r\n}\r\n\r\nexport function saveStore<T>(cwd: string, filename: string, store: MemoryStore<T>): void {\r\n\tconst dir = ensureMemoryDir(cwd);\r\n\tconst filepath = join(dir, filename);\r\n\twriteFileSync(filepath, JSON.stringify(store, null, 2), \"utf-8\");\r\n}\r\n\r\nexport function generateId(prefix: string, store: MemoryStore<{ id: string }>): string {\r\n\tlet max = 0;\r\n\tfor (const m of store.memories) {\r\n\t\tconst num = parseInt(m.id.replace(`${prefix}_`, \"\"), 10);\r\n\t\tif (!Number.isNaN(num) && num > max) max = num;\r\n\t}\r\n\treturn `${prefix}_${String(max + 1).padStart(3, \"0\")}`;\r\n}\r\n\r\nexport function nowISO(): string {\r\n\treturn new Date().toISOString();\r\n}\r\n\r\nexport function getSessionId(): string {\r\n\treturn `session_${Date.now()}`;\r\n}\r\n\r\nconst STORE_FILES: Record<MemoryType, string> = {\r\n\tprocedural: \"procedural.json\",\r\n\tepisodic: \"episodic.json\",\r\n\tsemantic: \"semantic.json\",\r\n};\r\n\r\nexport function getStoreFile(type: MemoryType): string {\r\n\treturn STORE_FILES[type];\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Search\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function searchMemories<T extends { tags: string[] }>(\r\n\tmemories: T[],\r\n\tquery: string,\r\n\tgetText: (m: T) => string,\r\n): T[] {\r\n\tconst lower = query.toLowerCase();\r\n\tconst queryWords = lower.split(/\\s+/).filter(Boolean);\r\n\treturn memories.filter((m) => {\r\n\t\tconst text = getText(m).toLowerCase();\r\n\t\tconst tagText = m.tags.join(\" \").toLowerCase();\r\n\t\tconst combined = `${text} ${tagText}`;\r\n\t\treturn queryWords.every((w) => combined.includes(w));\r\n\t});\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Compaction\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function compactEpisodicMemories(memories: EpisodicMemory[]): EpisodicMemory[] {\r\n\tif (memories.length <= 1) return memories;\r\n\r\n\tconst tagGroups = new Map<string, EpisodicMemory[]>();\r\n\tfor (const m of memories) {\r\n\t\tlet placed = false;\r\n\t\tfor (const [key, group] of tagGroups) {\r\n\t\t\tconst groupTags = new Set(key.split(\",\"));\r\n\t\t\tif (m.tags.some((t) => groupTags.has(t))) {\r\n\t\t\t\tgroup.push(m);\r\n\t\t\t\tconst mergedTags = new Set([...groupTags, ...m.tags]);\r\n\t\t\t\ttagGroups.delete(key);\r\n\t\t\t\ttagGroups.set([...mergedTags].join(\",\"), group);\r\n\t\t\t\tplaced = true;\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (!placed) {\r\n\t\t\ttagGroups.set(m.tags.join(\",\") || m.id, [m]);\r\n\t\t}\r\n\t}\r\n\r\n\tconst compacted: EpisodicMemory[] = [];\r\n\tfor (const [, group] of tagGroups) {\r\n\t\tif (group.length <= 1) {\r\n\t\t\tcompacted.push(...group);\r\n\t\t\tcontinue;\r\n\t\t}\r\n\t\tconst allDetails = group.flatMap((m) => m.details);\r\n\t\tconst allTags = [...new Set(group.flatMap((m) => m.tags))];\r\n\t\tconst allMistakes = group.flatMap((m) => m.reflection?.mistakes ?? []);\r\n\t\tconst allLessons = group.flatMap((m) => m.reflection?.lessons ?? []);\r\n\t\tconst summaries = group.map((m) => m.summary);\r\n\r\n\t\tcompacted.push({\r\n\t\t\tid: group[0].id,\r\n\t\t\tsummary: `Consolidated: ${summaries.join(\"; \")}`,\r\n\t\t\tdetails: [...new Set(allDetails)],\r\n\t\t\treflection:\r\n\t\t\t\tallMistakes.length > 0 || allLessons.length > 0\r\n\t\t\t\t\t? { mistakes: [...new Set(allMistakes)], lessons: [...new Set(allLessons)] }\r\n\t\t\t\t\t: undefined,\r\n\t\t\ttags: allTags,\r\n\t\t\tdate: group[group.length - 1].date,\r\n\t\t\tsourceSession: group[group.length - 1].sourceSession,\r\n\t\t});\r\n\t}\r\n\treturn compacted;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Display formatters\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function formatProceduralForDisplay(m: ProceduralMemory): string {\r\n\tconst steps = m.steps.map((s, i) => ` ${i + 1}. ${s}`).join(\"\\n\");\r\n\treturn `[${m.id}] ${m.name}\\n Trigger: ${m.trigger}\\n Tags: ${m.tags.join(\", \")}\\n Steps:\\n${steps}\\n Updated: ${m.updated}`;\r\n}\r\n\r\nexport function formatEpisodicForDisplay(m: EpisodicMemory): string {\r\n\tlet text = `[${m.id}] ${m.summary}\\n Date: ${m.date}\\n Tags: ${m.tags.join(\", \")}`;\r\n\tif (m.details.length > 0) {\r\n\t\ttext += `\\n Details:\\n${m.details.map((d) => ` - ${d}`).join(\"\\n\")}`;\r\n\t}\r\n\tif (m.reflection) {\r\n\t\tif (m.reflection.mistakes.length > 0) {\r\n\t\t\ttext += `\\n Mistakes:\\n${m.reflection.mistakes.map((x) => ` - ${x}`).join(\"\\n\")}`;\r\n\t\t}\r\n\t\tif (m.reflection.lessons.length > 0) {\r\n\t\t\ttext += `\\n Lessons:\\n${m.reflection.lessons.map((x) => ` - ${x}`).join(\"\\n\")}`;\r\n\t\t}\r\n\t}\r\n\treturn text;\r\n}\r\n\r\nexport function formatSemanticForDisplay(m: SemanticMemory): string {\r\n\treturn `[${m.id}] (${m.category}) ${m.text}\\n Tags: ${m.tags.join(\", \")}\\n Created: ${m.created}`;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// System prompt section\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function buildMemoryPromptSection(cwd: string): string {\r\n\tconst semantic = loadStore<SemanticMemory>(cwd, \"semantic.json\");\r\n\tconst procedural = loadStore<ProceduralMemory>(cwd, \"procedural.json\");\r\n\tconst episodic = loadStore<EpisodicMemory>(cwd, \"episodic.json\");\r\n\r\n\tif (semantic.memories.length === 0 && procedural.memories.length === 0 && episodic.memories.length === 0) {\r\n\t\treturn `\\n\\n<memory_system>\r\nYou have a persistent memory system that stores knowledge across sessions.\r\nCurrently no memories are stored. Use the memory_write tool to save:\r\n1. Procedural memories when the user guides you through a multi-step workflow.\r\n2. Semantic memories when the user states a preference, rule, correction, or fact.\r\n3. Episodic memories when a significant task completes.\r\n</memory_system>`;\r\n\t}\r\n\r\n\tconst parts: string[] = [];\r\n\r\n\tparts.push(`\\n\\n<memory_system>\r\nYou have a persistent memory system that stores knowledge across sessions.\r\nYou MUST use these memories to improve your responses. Do not ask the user\r\nto repeat things they have already taught you.\r\n\r\nAUTOMATIC MEMORY CAPTURE RULES:\r\n1. When the user guides you through a multi-step workflow (correcting steps,\r\n adding steps, reordering), save it as a PROCEDURAL memory using the\r\n memory_write tool. Extract the general workflow, not the specific instance.\r\n2. When the user states a preference or rule (\"always do X\", \"never do Y\",\r\n \"I prefer X\"), save it as a SEMANTIC memory immediately.\r\n3. When the user corrects you or you make a mistake, save the lesson as a\r\n SEMANTIC memory with category \"convention\".\r\n4. When a significant task completes, save a summary as an EPISODIC memory.\r\n5. If you detect a conflict with an existing memory, ask the user: \"You\r\n previously told me [old]. You are now saying [new]. Should I update this?\"\r\n Only update after confirmation.\r\n\r\nTIMESTAMP AWARENESS:\r\nEach user message includes a timestamp. Use these to detect time gaps.\r\nIf the user returns after a significant gap (>10 minutes), acknowledge it\r\nnaturally if relevant. Do not force it if the gap is not relevant.`);\r\n\r\n\tif (semantic.memories.length > 0) {\r\n\t\tparts.push(\"\\n<semantic_memories>\");\r\n\t\tfor (const m of semantic.memories) {\r\n\t\t\tparts.push(` <memory id=\"${m.id}\" category=\"${m.category}\">${escapeXml(m.text)}</memory>`);\r\n\t\t}\r\n\t\tparts.push(\"</semantic_memories>\");\r\n\t}\r\n\r\n\tif (procedural.memories.length > 0) {\r\n\t\tparts.push(\"\\n<procedural_memories>\");\r\n\t\tfor (const m of procedural.memories) {\r\n\t\t\tconst steps = m.steps.map((s, i) => `${i + 1}. ${s}`).join(\"; \");\r\n\t\t\tparts.push(\r\n\t\t\t\t` <procedure id=\"${m.id}\" name=\"${escapeXml(m.name)}\" trigger=\"${escapeXml(m.trigger)}\">${escapeXml(steps)}</procedure>`,\r\n\t\t\t);\r\n\t\t}\r\n\t\tparts.push(\"</procedural_memories>\");\r\n\t}\r\n\r\n\tif (episodic.memories.length > 0) {\r\n\t\tconst recent = episodic.memories.slice(-10);\r\n\t\tparts.push(\"\\n<recent_episodic_memories>\");\r\n\t\tfor (const m of recent) {\r\n\t\t\tconst details = m.details.join(\"; \");\r\n\t\t\tlet text = details;\r\n\t\t\tif (m.reflection?.lessons.length) {\r\n\t\t\t\ttext += ` | Lessons: ${m.reflection.lessons.join(\"; \")}`;\r\n\t\t\t}\r\n\t\t\tparts.push(\r\n\t\t\t\t` <episode id=\"${m.id}\" date=\"${m.date}\" summary=\"${escapeXml(m.summary)}\">${escapeXml(text)}</episode>`,\r\n\t\t\t);\r\n\t\t}\r\n\t\tparts.push(\"</recent_episodic_memories>\");\r\n\t}\r\n\r\n\tparts.push(\"\\n</memory_system>\");\r\n\r\n\treturn parts.join(\"\\n\");\r\n}\r\n\r\nfunction escapeXml(str: string): string {\r\n\treturn str\r\n\t\t.replace(/&/g, \"&\")\r\n\t\t.replace(/</g, \"<\")\r\n\t\t.replace(/>/g, \">\")\r\n\t\t.replace(/\"/g, \""\")\r\n\t\t.replace(/'/g, \"'\");\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Timestamp formatting\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function formatTimestamp(ts: number): string {\r\n\tconst d = new Date(ts);\r\n\treturn d.toLocaleString(\"en-US\", {\r\n\t\tyear: \"numeric\",\r\n\t\tmonth: \"short\",\r\n\t\tday: \"numeric\",\r\n\t\thour: \"2-digit\",\r\n\t\tminute: \"2-digit\",\r\n\t\tsecond: \"2-digit\",\r\n\t\thour12: false,\r\n\t});\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Memory counts (for status display)\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface MemoryCounts {\r\n\tsemantic: number;\r\n\tprocedural: number;\r\n\tepisodic: number;\r\n\ttotal: number;\r\n}\r\n\r\nexport function getMemoryCounts(cwd: string): MemoryCounts {\r\n\tconst semantic = loadStore<SemanticMemory>(cwd, \"semantic.json\").memories.length;\r\n\tconst procedural = loadStore<ProceduralMemory>(cwd, \"procedural.json\").memories.length;\r\n\tconst episodic = loadStore<EpisodicMemory>(cwd, \"episodic.json\").memories.length;\r\n\treturn { semantic, procedural, episodic, total: semantic + procedural + episodic };\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/core/memory.ts"],"names":[],"mappings":"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","sourcesContent":["/**\r\n * Persistent memory system — procedural, episodic, and semantic memories\r\n * stored per-project in .pi/memory/ or globally in ~/.pi/agent/memory/ as JSON files.\r\n * Scope is configured via settings (memory.scope: \"project\" | \"global\")\r\n * or overridden per-project via a .zpi config file.\r\n */\r\n\r\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\r\nimport { homedir } from \"node:os\";\r\nimport { join } from \"node:path\";\r\nimport { CONFIG_DIR_NAME } from \"../config.js\";\r\n\r\n// ---------------------------------------------------------------------------\r\n// Types\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface ProceduralMemory {\r\n\tid: string;\r\n\tname: string;\r\n\ttrigger: string;\r\n\tsteps: string[];\r\n\ttags: string[];\r\n\tcreated: string;\r\n\tupdated: string;\r\n\tsourceSession: string;\r\n}\r\n\r\nexport interface EpisodicMemory {\r\n\tid: string;\r\n\tsummary: string;\r\n\tdetails: string[];\r\n\treflection?: {\r\n\t\tmistakes: string[];\r\n\t\tlessons: string[];\r\n\t};\r\n\ttags: string[];\r\n\tdate: string;\r\n\tsourceSession: string;\r\n}\r\n\r\nexport interface SemanticMemory {\r\n\tid: string;\r\n\tcategory: \"preference\" | \"architecture\" | \"convention\" | \"fact\";\r\n\ttext: string;\r\n\ttags: string[];\r\n\tcreated: string;\r\n\tsourceSession: string;\r\n}\r\n\r\nexport interface MemoryStore<T> {\r\n\tmemories: T[];\r\n}\r\n\r\nexport type MemoryType = \"procedural\" | \"episodic\" | \"semantic\";\r\nexport type MemoryScope = \"project\" | \"global\";\r\n\r\n// ---------------------------------------------------------------------------\r\n// .zpi config file\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface ZpiConfig {\r\n\tmemory?: {\r\n\t\tscope?: MemoryScope;\r\n\t};\r\n}\r\n\r\n/**\r\n * Read .zpi config file from the project root.\r\n * Returns undefined if file doesn't exist or is invalid.\r\n */\r\nexport function readZpiConfig(cwd: string): ZpiConfig | undefined {\r\n\tconst configPath = join(cwd, \".zpi\");\r\n\tif (!existsSync(configPath)) {\r\n\t\treturn undefined;\r\n\t}\r\n\ttry {\r\n\t\tconst raw = readFileSync(configPath, \"utf-8\");\r\n\t\treturn JSON.parse(raw) as ZpiConfig;\r\n\t} catch {\r\n\t\treturn undefined;\r\n\t}\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Storage\r\n// ---------------------------------------------------------------------------\r\n\r\n/** Get the global memory directory (~/.pi/agent/memory/) */\r\nexport function getGlobalMemoryDir(): string {\r\n\treturn join(homedir(), CONFIG_DIR_NAME, \"agent\", \"memory\");\r\n}\r\n\r\n/** Get the project memory directory (<cwd>/.pi/memory/) */\r\nexport function getProjectMemoryDir(cwd: string): string {\r\n\treturn join(cwd, CONFIG_DIR_NAME, \"memory\");\r\n}\r\n\r\n/**\r\n * Resolve the effective memory scope for a project.\r\n * Priority: .zpi config > settings > default (\"project\")\r\n */\r\nexport function resolveMemoryScope(cwd: string, settingsScope: MemoryScope): MemoryScope {\r\n\tconst zpiConfig = readZpiConfig(cwd);\r\n\tif (zpiConfig?.memory?.scope) {\r\n\t\treturn zpiConfig.memory.scope;\r\n\t}\r\n\treturn settingsScope;\r\n}\r\n\r\n/**\r\n * Get the memory directory based on scope.\r\n */\r\nexport function getMemoryDir(cwd: string, scope: MemoryScope = \"global\"): string {\r\n\tif (scope === \"global\") {\r\n\t\treturn getGlobalMemoryDir();\r\n\t}\r\n\treturn getProjectMemoryDir(cwd);\r\n}\r\n\r\nfunction ensureMemoryDir(cwd: string, scope: MemoryScope = \"global\"): string {\r\n\tconst dir = getMemoryDir(cwd, scope);\r\n\tif (!existsSync(dir)) {\r\n\t\tmkdirSync(dir, { recursive: true });\r\n\t}\r\n\treturn dir;\r\n}\r\n\r\nexport function loadStore<T>(cwd: string, filename: string, scope: MemoryScope = \"global\"): MemoryStore<T> {\r\n\tconst filepath = join(getMemoryDir(cwd, scope), filename);\r\n\tif (!existsSync(filepath)) {\r\n\t\treturn { memories: [] };\r\n\t}\r\n\ttry {\r\n\t\tconst raw = readFileSync(filepath, \"utf-8\");\r\n\t\treturn JSON.parse(raw) as MemoryStore<T>;\r\n\t} catch {\r\n\t\treturn { memories: [] };\r\n\t}\r\n}\r\n\r\nexport function saveStore<T>(cwd: string, filename: string, store: MemoryStore<T>, scope: MemoryScope = \"global\"): void {\r\n\tconst dir = ensureMemoryDir(cwd, scope);\r\n\tconst filepath = join(dir, filename);\r\n\twriteFileSync(filepath, JSON.stringify(store, null, 2), \"utf-8\");\r\n}\r\n\r\nexport function generateId(prefix: string, store: MemoryStore<{ id: string }>): string {\r\n\tlet max = 0;\r\n\tfor (const m of store.memories) {\r\n\t\tconst num = parseInt(m.id.replace(`${prefix}_`, \"\"), 10);\r\n\t\tif (!Number.isNaN(num) && num > max) max = num;\r\n\t}\r\n\treturn `${prefix}_${String(max + 1).padStart(3, \"0\")}`;\r\n}\r\n\r\nexport function nowISO(): string {\r\n\treturn new Date().toISOString();\r\n}\r\n\r\nexport function getSessionId(): string {\r\n\treturn `session_${Date.now()}`;\r\n}\r\n\r\nconst STORE_FILES: Record<MemoryType, string> = {\r\n\tprocedural: \"procedural.json\",\r\n\tepisodic: \"episodic.json\",\r\n\tsemantic: \"semantic.json\",\r\n};\r\n\r\nexport function getStoreFile(type: MemoryType): string {\r\n\treturn STORE_FILES[type];\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Search\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function searchMemories<T extends { tags: string[] }>(\r\n\tmemories: T[],\r\n\tquery: string,\r\n\tgetText: (m: T) => string,\r\n): T[] {\r\n\tconst lower = query.toLowerCase();\r\n\tconst queryWords = lower.split(/\\s+/).filter(Boolean);\r\n\treturn memories.filter((m) => {\r\n\t\tconst text = getText(m).toLowerCase();\r\n\t\tconst tagText = m.tags.join(\" \").toLowerCase();\r\n\t\tconst combined = `${text} ${tagText}`;\r\n\t\treturn queryWords.every((w) => combined.includes(w));\r\n\t});\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Compaction\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function compactEpisodicMemories(memories: EpisodicMemory[]): EpisodicMemory[] {\r\n\tif (memories.length <= 1) return memories;\r\n\r\n\tconst tagGroups = new Map<string, EpisodicMemory[]>();\r\n\tfor (const m of memories) {\r\n\t\tlet placed = false;\r\n\t\tfor (const [key, group] of tagGroups) {\r\n\t\t\tconst groupTags = new Set(key.split(\",\"));\r\n\t\t\tif (m.tags.some((t) => groupTags.has(t))) {\r\n\t\t\t\tgroup.push(m);\r\n\t\t\t\tconst mergedTags = new Set([...groupTags, ...m.tags]);\r\n\t\t\t\ttagGroups.delete(key);\r\n\t\t\t\ttagGroups.set([...mergedTags].join(\",\"), group);\r\n\t\t\t\tplaced = true;\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (!placed) {\r\n\t\t\ttagGroups.set(m.tags.join(\",\") || m.id, [m]);\r\n\t\t}\r\n\t}\r\n\r\n\tconst compacted: EpisodicMemory[] = [];\r\n\tfor (const [, group] of tagGroups) {\r\n\t\tif (group.length <= 1) {\r\n\t\t\tcompacted.push(...group);\r\n\t\t\tcontinue;\r\n\t\t}\r\n\t\tconst allDetails = group.flatMap((m) => m.details);\r\n\t\tconst allTags = [...new Set(group.flatMap((m) => m.tags))];\r\n\t\tconst allMistakes = group.flatMap((m) => m.reflection?.mistakes ?? []);\r\n\t\tconst allLessons = group.flatMap((m) => m.reflection?.lessons ?? []);\r\n\t\tconst summaries = group.map((m) => m.summary);\r\n\r\n\t\tcompacted.push({\r\n\t\t\tid: group[0].id,\r\n\t\t\tsummary: `Consolidated: ${summaries.join(\"; \")}`,\r\n\t\t\tdetails: [...new Set(allDetails)],\r\n\t\t\treflection:\r\n\t\t\t\tallMistakes.length > 0 || allLessons.length > 0\r\n\t\t\t\t\t? { mistakes: [...new Set(allMistakes)], lessons: [...new Set(allLessons)] }\r\n\t\t\t\t\t: undefined,\r\n\t\t\ttags: allTags,\r\n\t\t\tdate: group[group.length - 1].date,\r\n\t\t\tsourceSession: group[group.length - 1].sourceSession,\r\n\t\t});\r\n\t}\r\n\treturn compacted;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Display formatters\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function formatProceduralForDisplay(m: ProceduralMemory): string {\r\n\tconst steps = m.steps.map((s, i) => ` ${i + 1}. ${s}`).join(\"\\n\");\r\n\treturn `[${m.id}] ${m.name}\\n Trigger: ${m.trigger}\\n Tags: ${m.tags.join(\", \")}\\n Steps:\\n${steps}\\n Updated: ${m.updated}`;\r\n}\r\n\r\nexport function formatEpisodicForDisplay(m: EpisodicMemory): string {\r\n\tlet text = `[${m.id}] ${m.summary}\\n Date: ${m.date}\\n Tags: ${m.tags.join(\", \")}`;\r\n\tif (m.details.length > 0) {\r\n\t\ttext += `\\n Details:\\n${m.details.map((d) => ` - ${d}`).join(\"\\n\")}`;\r\n\t}\r\n\tif (m.reflection) {\r\n\t\tif (m.reflection.mistakes.length > 0) {\r\n\t\t\ttext += `\\n Mistakes:\\n${m.reflection.mistakes.map((x) => ` - ${x}`).join(\"\\n\")}`;\r\n\t\t}\r\n\t\tif (m.reflection.lessons.length > 0) {\r\n\t\t\ttext += `\\n Lessons:\\n${m.reflection.lessons.map((x) => ` - ${x}`).join(\"\\n\")}`;\r\n\t\t}\r\n\t}\r\n\treturn text;\r\n}\r\n\r\nexport function formatSemanticForDisplay(m: SemanticMemory): string {\r\n\treturn `[${m.id}] (${m.category}) ${m.text}\\n Tags: ${m.tags.join(\", \")}\\n Created: ${m.created}`;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// System prompt section\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function buildMemoryPromptSection(cwd: string, scope: MemoryScope = \"global\"): string {\r\n\tconst semantic = loadStore<SemanticMemory>(cwd, \"semantic.json\", scope);\r\n\tconst procedural = loadStore<ProceduralMemory>(cwd, \"procedural.json\", scope);\r\n\tconst episodic = loadStore<EpisodicMemory>(cwd, \"episodic.json\", scope);\r\n\r\n\tif (semantic.memories.length === 0 && procedural.memories.length === 0 && episodic.memories.length === 0) {\r\n\t\treturn `\\n\\n<memory_system>\r\nYou have a persistent memory system that stores knowledge across sessions.\r\nCurrently no memories are stored. Use the memory_write tool to save:\r\n1. Procedural memories when the user guides you through a multi-step workflow.\r\n2. Semantic memories when the user states a preference, rule, correction, or fact.\r\n3. Episodic memories when a significant task completes.\r\n</memory_system>`;\r\n\t}\r\n\r\n\tconst parts: string[] = [];\r\n\r\n\tparts.push(`\\n\\n<memory_system>\r\nYou have a persistent memory system that stores knowledge across sessions.\r\nYou MUST use these memories to improve your responses. Do not ask the user\r\nto repeat things they have already taught you.\r\n\r\nAUTOMATIC MEMORY CAPTURE RULES:\r\n1. When the user guides you through a multi-step workflow (correcting steps,\r\n adding steps, reordering), save it as a PROCEDURAL memory using the\r\n memory_write tool. Extract the general workflow, not the specific instance.\r\n2. When the user states a preference or rule (\"always do X\", \"never do Y\",\r\n \"I prefer X\"), save it as a SEMANTIC memory immediately.\r\n3. When the user corrects you or you make a mistake, save the lesson as a\r\n SEMANTIC memory with category \"convention\".\r\n4. When a significant task completes, save a summary as an EPISODIC memory.\r\n5. If you detect a conflict with an existing memory, ask the user: \"You\r\n previously told me [old]. You are now saying [new]. Should I update this?\"\r\n Only update after confirmation.\r\n\r\nTIMESTAMP AWARENESS:\r\nEach user message includes a timestamp. Use these to detect time gaps.\r\nIf the user returns after a significant gap (>10 minutes), acknowledge it\r\nnaturally if relevant. Do not force it if the gap is not relevant.`);\r\n\r\n\tif (semantic.memories.length > 0) {\r\n\t\tparts.push(\"\\n<semantic_memories>\");\r\n\t\tfor (const m of semantic.memories) {\r\n\t\t\tparts.push(` <memory id=\"${m.id}\" category=\"${m.category}\">${escapeXml(m.text)}</memory>`);\r\n\t\t}\r\n\t\tparts.push(\"</semantic_memories>\");\r\n\t}\r\n\r\n\tif (procedural.memories.length > 0) {\r\n\t\tparts.push(\"\\n<procedural_memories>\");\r\n\t\tfor (const m of procedural.memories) {\r\n\t\t\tconst steps = m.steps.map((s, i) => `${i + 1}. ${s}`).join(\"; \");\r\n\t\t\tparts.push(\r\n\t\t\t\t` <procedure id=\"${m.id}\" name=\"${escapeXml(m.name)}\" trigger=\"${escapeXml(m.trigger)}\">${escapeXml(steps)}</procedure>`,\r\n\t\t\t);\r\n\t\t}\r\n\t\tparts.push(\"</procedural_memories>\");\r\n\t}\r\n\r\n\tif (episodic.memories.length > 0) {\r\n\t\tconst recent = episodic.memories.slice(-10);\r\n\t\tparts.push(\"\\n<recent_episodic_memories>\");\r\n\t\tfor (const m of recent) {\r\n\t\t\tconst details = m.details.join(\"; \");\r\n\t\t\tlet text = details;\r\n\t\t\tif (m.reflection?.lessons.length) {\r\n\t\t\t\ttext += ` | Lessons: ${m.reflection.lessons.join(\"; \")}`;\r\n\t\t\t}\r\n\t\t\tparts.push(\r\n\t\t\t\t` <episode id=\"${m.id}\" date=\"${m.date}\" summary=\"${escapeXml(m.summary)}\">${escapeXml(text)}</episode>`,\r\n\t\t\t);\r\n\t\t}\r\n\t\tparts.push(\"</recent_episodic_memories>\");\r\n\t}\r\n\r\n\tparts.push(\"\\n</memory_system>\");\r\n\r\n\treturn parts.join(\"\\n\");\r\n}\r\n\r\nfunction escapeXml(str: string): string {\r\n\treturn str\r\n\t\t.replace(/&/g, \"&\")\r\n\t\t.replace(/</g, \"<\")\r\n\t\t.replace(/>/g, \">\")\r\n\t\t.replace(/\"/g, \""\")\r\n\t\t.replace(/'/g, \"'\");\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Timestamp formatting\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function formatTimestamp(ts: number): string {\r\n\tconst d = new Date(ts);\r\n\treturn d.toLocaleString(\"en-US\", {\r\n\t\tyear: \"numeric\",\r\n\t\tmonth: \"short\",\r\n\t\tday: \"numeric\",\r\n\t\thour: \"2-digit\",\r\n\t\tminute: \"2-digit\",\r\n\t\tsecond: \"2-digit\",\r\n\t\thour12: false,\r\n\t});\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Memory counts (for status display)\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface MemoryCounts {\r\n\tsemantic: number;\r\n\tprocedural: number;\r\n\tepisodic: number;\r\n\ttotal: number;\r\n}\r\n\r\nexport function getMemoryCounts(cwd: string, scope: MemoryScope = \"global\"): MemoryCounts {\r\n\tconst semantic = loadStore<SemanticMemory>(cwd, \"semantic.json\", scope).memories.length;\r\n\tconst procedural = loadStore<ProceduralMemory>(cwd, \"procedural.json\", scope).memories.length;\r\n\tconst episodic = loadStore<EpisodicMemory>(cwd, \"episodic.json\", scope).memories.length;\r\n\treturn { semantic, procedural, episodic, total: semantic + procedural + episodic };\r\n}\r\n"]}
|
|
@@ -77,6 +77,7 @@ export interface Settings {
|
|
|
77
77
|
markdown?: MarkdownSettings;
|
|
78
78
|
memory?: {
|
|
79
79
|
enabled?: boolean;
|
|
80
|
+
scope?: "project" | "global";
|
|
80
81
|
};
|
|
81
82
|
}
|
|
82
83
|
export type SettingsScope = "global" | "project";
|
|
@@ -226,5 +227,7 @@ export declare class SettingsManager {
|
|
|
226
227
|
getCodeBlockIndent(): string;
|
|
227
228
|
getMemoryEnabled(): boolean;
|
|
228
229
|
setMemoryEnabled(enabled: boolean): void;
|
|
230
|
+
getMemoryScope(): "project" | "global";
|
|
231
|
+
setMemoryScope(scope: "project" | "global"): void;
|
|
229
232
|
}
|
|
230
233
|
//# sourceMappingURL=settings-manager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings-manager.d.ts","sourceRoot":"","sources":["../../src/core/settings-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Transport } from \"@mariozechner/pi-ai\";\r\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\r\nimport { dirname, join } from \"path\";\r\nimport lockfile from \"proper-lockfile\";\r\nimport { CONFIG_DIR_NAME, getAgentDir } from \"../config.js\";\r\n\r\nexport interface CompactionSettings {\r\n\tenabled?: boolean; // default: true\r\n\treserveTokens?: number; // default: 16384\r\n\tkeepRecentTokens?: number; // default: 20000\r\n}\r\n\r\nexport interface BranchSummarySettings {\r\n\treserveTokens?: number; // default: 16384 (tokens reserved for prompt + LLM response)\r\n}\r\n\r\nexport interface RetrySettings {\r\n\tenabled?: boolean; // default: true\r\n\tmaxRetries?: number; // default: 3\r\n\tbaseDelayMs?: number; // default: 2000 (exponential backoff: 2s, 4s, 8s)\r\n\tmaxDelayMs?: number; // default: 60000 (max server-requested delay before failing)\r\n}\r\n\r\nexport interface TerminalSettings {\r\n\tshowImages?: boolean; // default: true (only relevant if terminal supports images)\r\n\tclearOnShrink?: boolean; // default: false (clear empty rows when content shrinks)\r\n}\r\n\r\nexport interface ImageSettings {\r\n\tautoResize?: boolean; // default: true (resize images to 2000x2000 max for better model compatibility)\r\n\tblockImages?: boolean; // default: false - when true, prevents all images from being sent to LLM providers\r\n}\r\n\r\nexport interface ThinkingBudgetsSettings {\r\n\tminimal?: number;\r\n\tlow?: number;\r\n\tmedium?: number;\r\n\thigh?: number;\r\n}\r\n\r\nexport interface MarkdownSettings {\r\n\tcodeBlockIndent?: string; // default: \" \"\r\n}\r\n\r\nexport type TransportSetting = Transport;\r\n\r\n/**\r\n * Package source for npm/git packages.\r\n * - String form: load all resources from the package\r\n * - Object form: filter which resources to load\r\n */\r\nexport type PackageSource =\r\n\t| string\r\n\t| {\r\n\t\t\tsource: string;\r\n\t\t\textensions?: string[];\r\n\t\t\tskills?: string[];\r\n\t\t\tprompts?: string[];\r\n\t\t\tthemes?: string[];\r\n\t };\r\n\r\nexport interface Settings {\r\n\tlastChangelogVersion?: string;\r\n\tdefaultProvider?: string;\r\n\tdefaultModel?: string;\r\n\tdefaultThinkingLevel?: \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\";\r\n\ttransport?: TransportSetting; // default: \"sse\"\r\n\tsteeringMode?: \"all\" | \"one-at-a-time\";\r\n\tfollowUpMode?: \"all\" | \"one-at-a-time\";\r\n\ttheme?: string;\r\n\tcompaction?: CompactionSettings;\r\n\tbranchSummary?: BranchSummarySettings;\r\n\tretry?: RetrySettings;\r\n\thideThinkingBlock?: boolean;\r\n\tshellPath?: string; // Custom shell path (e.g., for Cygwin users on Windows)\r\n\tquietStartup?: boolean;\r\n\tshellCommandPrefix?: string; // Prefix prepended to every bash command (e.g., \"shopt -s expand_aliases\" for alias support)\r\n\tcollapseChangelog?: boolean; // Show condensed changelog after update (use /changelog for full)\r\n\tpackages?: PackageSource[]; // Array of npm/git package sources (string or object with filtering)\r\n\textensions?: string[]; // Array of local extension file paths or directories\r\n\tskills?: string[]; // Array of local skill file paths or directories\r\n\tprompts?: string[]; // Array of local prompt template paths or directories\r\n\tthemes?: string[]; // Array of local theme file paths or directories\r\n\tenableSkillCommands?: boolean; // default: true - register skills as /skill:name commands\r\n\tterminal?: TerminalSettings;\r\n\timages?: ImageSettings;\r\n\tenabledModels?: string[]; // Model patterns for cycling (same format as --models CLI flag)\r\n\tdoubleEscapeAction?: \"fork\" | \"tree\" | \"none\"; // Action for double-escape with empty editor (default: \"tree\")\r\n\tthinkingBudgets?: ThinkingBudgetsSettings; // Custom token budgets for thinking levels\r\n\teditorPaddingX?: number; // Horizontal padding for input editor (default: 0)\r\n\tautocompleteMaxVisible?: number; // Max visible items in autocomplete dropdown (default: 5)\r\n\tshowHardwareCursor?: boolean; // Show terminal cursor while still positioning it for IME\r\n\tmarkdown?: MarkdownSettings;\r\n\tmemory?: { enabled?: boolean }; // default: true - persistent memory system (procedural, episodic, semantic)\r\n}\r\n\r\n/** Deep merge settings: project/overrides take precedence, nested objects merge recursively */\r\nfunction deepMergeSettings(base: Settings, overrides: Settings): Settings {\r\n\tconst result: Settings = { ...base };\r\n\r\n\tfor (const key of Object.keys(overrides) as (keyof Settings)[]) {\r\n\t\tconst overrideValue = overrides[key];\r\n\t\tconst baseValue = base[key];\r\n\r\n\t\tif (overrideValue === undefined) {\r\n\t\t\tcontinue;\r\n\t\t}\r\n\r\n\t\t// For nested objects, merge recursively\r\n\t\tif (\r\n\t\t\ttypeof overrideValue === \"object\" &&\r\n\t\t\toverrideValue !== null &&\r\n\t\t\t!Array.isArray(overrideValue) &&\r\n\t\t\ttypeof baseValue === \"object\" &&\r\n\t\t\tbaseValue !== null &&\r\n\t\t\t!Array.isArray(baseValue)\r\n\t\t) {\r\n\t\t\t(result as Record<string, unknown>)[key] = { ...baseValue, ...overrideValue };\r\n\t\t} else {\r\n\t\t\t// For primitives and arrays, override value wins\r\n\t\t\t(result as Record<string, unknown>)[key] = overrideValue;\r\n\t\t}\r\n\t}\r\n\r\n\treturn result;\r\n}\r\n\r\nexport type SettingsScope = \"global\" | \"project\";\r\n\r\nexport interface SettingsStorage {\r\n\twithLock(scope: SettingsScope, fn: (current: string | undefined) => string | undefined): void;\r\n}\r\n\r\nexport interface SettingsError {\r\n\tscope: SettingsScope;\r\n\terror: Error;\r\n}\r\n\r\nexport class FileSettingsStorage implements SettingsStorage {\r\n\tprivate globalSettingsPath: string;\r\n\tprivate projectSettingsPath: string;\r\n\r\n\tconstructor(cwd: string = process.cwd(), agentDir: string = getAgentDir()) {\r\n\t\tthis.globalSettingsPath = join(agentDir, \"settings.json\");\r\n\t\tthis.projectSettingsPath = join(cwd, CONFIG_DIR_NAME, \"settings.json\");\r\n\t}\r\n\r\n\twithLock(scope: SettingsScope, fn: (current: string | undefined) => string | undefined): void {\r\n\t\tconst path = scope === \"global\" ? this.globalSettingsPath : this.projectSettingsPath;\r\n\t\tconst dir = dirname(path);\r\n\t\tif (!existsSync(dir)) {\r\n\t\t\tmkdirSync(dir, { recursive: true });\r\n\t\t}\r\n\r\n\t\tlet release: (() => void) | undefined;\r\n\t\ttry {\r\n\t\t\trelease = lockfile.lockSync(path, { realpath: false });\r\n\t\t\tconst current = existsSync(path) ? readFileSync(path, \"utf-8\") : undefined;\r\n\t\t\tconst next = fn(current);\r\n\t\t\tif (next !== undefined) {\r\n\t\t\t\twriteFileSync(path, next, \"utf-8\");\r\n\t\t\t}\r\n\t\t} finally {\r\n\t\t\tif (release) {\r\n\t\t\t\trelease();\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\nexport class InMemorySettingsStorage implements SettingsStorage {\r\n\tprivate global: string | undefined;\r\n\tprivate project: string | undefined;\r\n\r\n\twithLock(scope: SettingsScope, fn: (current: string | undefined) => string | undefined): void {\r\n\t\tconst current = scope === \"global\" ? this.global : this.project;\r\n\t\tconst next = fn(current);\r\n\t\tif (next !== undefined) {\r\n\t\t\tif (scope === \"global\") {\r\n\t\t\t\tthis.global = next;\r\n\t\t\t} else {\r\n\t\t\t\tthis.project = next;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\nexport class SettingsManager {\r\n\tprivate storage: SettingsStorage;\r\n\tprivate globalSettings: Settings;\r\n\tprivate projectSettings: Settings;\r\n\tprivate settings: Settings;\r\n\tprivate modifiedFields = new Set<keyof Settings>(); // Track global fields modified during session\r\n\tprivate modifiedNestedFields = new Map<keyof Settings, Set<string>>(); // Track global nested field modifications\r\n\tprivate modifiedProjectFields = new Set<keyof Settings>(); // Track project fields modified during session\r\n\tprivate modifiedProjectNestedFields = new Map<keyof Settings, Set<string>>(); // Track project nested field modifications\r\n\tprivate globalSettingsLoadError: Error | null = null; // Track if global settings file had parse errors\r\n\tprivate projectSettingsLoadError: Error | null = null; // Track if project settings file had parse errors\r\n\tprivate writeQueue: Promise<void> = Promise.resolve();\r\n\tprivate errors: SettingsError[];\r\n\r\n\tprivate constructor(\r\n\t\tstorage: SettingsStorage,\r\n\t\tinitialGlobal: Settings,\r\n\t\tinitialProject: Settings,\r\n\t\tglobalLoadError: Error | null = null,\r\n\t\tprojectLoadError: Error | null = null,\r\n\t\tinitialErrors: SettingsError[] = [],\r\n\t) {\r\n\t\tthis.storage = storage;\r\n\t\tthis.globalSettings = initialGlobal;\r\n\t\tthis.projectSettings = initialProject;\r\n\t\tthis.globalSettingsLoadError = globalLoadError;\r\n\t\tthis.projectSettingsLoadError = projectLoadError;\r\n\t\tthis.errors = [...initialErrors];\r\n\t\tthis.settings = deepMergeSettings(this.globalSettings, this.projectSettings);\r\n\t}\r\n\r\n\t/** Create a SettingsManager that loads from files */\r\n\tstatic create(cwd: string = process.cwd(), agentDir: string = getAgentDir()): SettingsManager {\r\n\t\tconst storage = new FileSettingsStorage(cwd, agentDir);\r\n\t\treturn SettingsManager.fromStorage(storage);\r\n\t}\r\n\r\n\t/** Create a SettingsManager from an arbitrary storage backend */\r\n\tstatic fromStorage(storage: SettingsStorage): SettingsManager {\r\n\t\tconst globalLoad = SettingsManager.tryLoadFromStorage(storage, \"global\");\r\n\t\tconst projectLoad = SettingsManager.tryLoadFromStorage(storage, \"project\");\r\n\t\tconst initialErrors: SettingsError[] = [];\r\n\t\tif (globalLoad.error) {\r\n\t\t\tinitialErrors.push({ scope: \"global\", error: globalLoad.error });\r\n\t\t}\r\n\t\tif (projectLoad.error) {\r\n\t\t\tinitialErrors.push({ scope: \"project\", error: projectLoad.error });\r\n\t\t}\r\n\r\n\t\treturn new SettingsManager(\r\n\t\t\tstorage,\r\n\t\t\tglobalLoad.settings,\r\n\t\t\tprojectLoad.settings,\r\n\t\t\tglobalLoad.error,\r\n\t\t\tprojectLoad.error,\r\n\t\t\tinitialErrors,\r\n\t\t);\r\n\t}\r\n\r\n\t/** Create an in-memory SettingsManager (no file I/O) */\r\n\tstatic inMemory(settings: Partial<Settings> = {}): SettingsManager {\r\n\t\tconst storage = new InMemorySettingsStorage();\r\n\t\treturn new SettingsManager(storage, settings, {});\r\n\t}\r\n\r\n\tprivate static loadFromStorage(storage: SettingsStorage, scope: SettingsScope): Settings {\r\n\t\tlet content: string | undefined;\r\n\t\tstorage.withLock(scope, (current) => {\r\n\t\t\tcontent = current;\r\n\t\t\treturn undefined;\r\n\t\t});\r\n\r\n\t\tif (!content) {\r\n\t\t\treturn {};\r\n\t\t}\r\n\t\tconst settings = JSON.parse(content);\r\n\t\treturn SettingsManager.migrateSettings(settings);\r\n\t}\r\n\r\n\tprivate static tryLoadFromStorage(\r\n\t\tstorage: SettingsStorage,\r\n\t\tscope: SettingsScope,\r\n\t): { settings: Settings; error: Error | null } {\r\n\t\ttry {\r\n\t\t\treturn { settings: SettingsManager.loadFromStorage(storage, scope), error: null };\r\n\t\t} catch (error) {\r\n\t\t\treturn { settings: {}, error: error as Error };\r\n\t\t}\r\n\t}\r\n\r\n\t/** Migrate old settings format to new format */\r\n\tprivate static migrateSettings(settings: Record<string, unknown>): Settings {\r\n\t\t// Migrate queueMode -> steeringMode\r\n\t\tif (\"queueMode\" in settings && !(\"steeringMode\" in settings)) {\r\n\t\t\tsettings.steeringMode = settings.queueMode;\r\n\t\t\tdelete settings.queueMode;\r\n\t\t}\r\n\r\n\t\t// Migrate legacy websockets boolean -> transport enum\r\n\t\tif (!(\"transport\" in settings) && typeof settings.websockets === \"boolean\") {\r\n\t\t\tsettings.transport = settings.websockets ? \"websocket\" : \"sse\";\r\n\t\t\tdelete settings.websockets;\r\n\t\t}\r\n\r\n\t\t// Migrate old skills object format to new array format\r\n\t\tif (\r\n\t\t\t\"skills\" in settings &&\r\n\t\t\ttypeof settings.skills === \"object\" &&\r\n\t\t\tsettings.skills !== null &&\r\n\t\t\t!Array.isArray(settings.skills)\r\n\t\t) {\r\n\t\t\tconst skillsSettings = settings.skills as {\r\n\t\t\t\tenableSkillCommands?: boolean;\r\n\t\t\t\tcustomDirectories?: unknown;\r\n\t\t\t};\r\n\t\t\tif (skillsSettings.enableSkillCommands !== undefined && settings.enableSkillCommands === undefined) {\r\n\t\t\t\tsettings.enableSkillCommands = skillsSettings.enableSkillCommands;\r\n\t\t\t}\r\n\t\t\tif (Array.isArray(skillsSettings.customDirectories) && skillsSettings.customDirectories.length > 0) {\r\n\t\t\t\tsettings.skills = skillsSettings.customDirectories;\r\n\t\t\t} else {\r\n\t\t\t\tdelete settings.skills;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn settings as Settings;\r\n\t}\r\n\r\n\tgetGlobalSettings(): Settings {\r\n\t\treturn structuredClone(this.globalSettings);\r\n\t}\r\n\r\n\tgetProjectSettings(): Settings {\r\n\t\treturn structuredClone(this.projectSettings);\r\n\t}\r\n\r\n\treload(): void {\r\n\t\tconst globalLoad = SettingsManager.tryLoadFromStorage(this.storage, \"global\");\r\n\t\tif (!globalLoad.error) {\r\n\t\t\tthis.globalSettings = globalLoad.settings;\r\n\t\t\tthis.globalSettingsLoadError = null;\r\n\t\t} else {\r\n\t\t\tthis.globalSettingsLoadError = globalLoad.error;\r\n\t\t\tthis.recordError(\"global\", globalLoad.error);\r\n\t\t}\r\n\r\n\t\tthis.modifiedFields.clear();\r\n\t\tthis.modifiedNestedFields.clear();\r\n\t\tthis.modifiedProjectFields.clear();\r\n\t\tthis.modifiedProjectNestedFields.clear();\r\n\r\n\t\tconst projectLoad = SettingsManager.tryLoadFromStorage(this.storage, \"project\");\r\n\t\tif (!projectLoad.error) {\r\n\t\t\tthis.projectSettings = projectLoad.settings;\r\n\t\t\tthis.projectSettingsLoadError = null;\r\n\t\t} else {\r\n\t\t\tthis.projectSettingsLoadError = projectLoad.error;\r\n\t\t\tthis.recordError(\"project\", projectLoad.error);\r\n\t\t}\r\n\r\n\t\tthis.settings = deepMergeSettings(this.globalSettings, this.projectSettings);\r\n\t}\r\n\r\n\t/** Apply additional overrides on top of current settings */\r\n\tapplyOverrides(overrides: Partial<Settings>): void {\r\n\t\tthis.settings = deepMergeSettings(this.settings, overrides);\r\n\t}\r\n\r\n\t/** Mark a global field as modified during this session */\r\n\tprivate markModified(field: keyof Settings, nestedKey?: string): void {\r\n\t\tthis.modifiedFields.add(field);\r\n\t\tif (nestedKey) {\r\n\t\t\tif (!this.modifiedNestedFields.has(field)) {\r\n\t\t\t\tthis.modifiedNestedFields.set(field, new Set());\r\n\t\t\t}\r\n\t\t\tthis.modifiedNestedFields.get(field)!.add(nestedKey);\r\n\t\t}\r\n\t}\r\n\r\n\t/** Mark a project field as modified during this session */\r\n\tprivate markProjectModified(field: keyof Settings, nestedKey?: string): void {\r\n\t\tthis.modifiedProjectFields.add(field);\r\n\t\tif (nestedKey) {\r\n\t\t\tif (!this.modifiedProjectNestedFields.has(field)) {\r\n\t\t\t\tthis.modifiedProjectNestedFields.set(field, new Set());\r\n\t\t\t}\r\n\t\t\tthis.modifiedProjectNestedFields.get(field)!.add(nestedKey);\r\n\t\t}\r\n\t}\r\n\r\n\tprivate recordError(scope: SettingsScope, error: unknown): void {\r\n\t\tconst normalizedError = error instanceof Error ? error : new Error(String(error));\r\n\t\tthis.errors.push({ scope, error: normalizedError });\r\n\t}\r\n\r\n\tprivate clearModifiedScope(scope: SettingsScope): void {\r\n\t\tif (scope === \"global\") {\r\n\t\t\tthis.modifiedFields.clear();\r\n\t\t\tthis.modifiedNestedFields.clear();\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tthis.modifiedProjectFields.clear();\r\n\t\tthis.modifiedProjectNestedFields.clear();\r\n\t}\r\n\r\n\tprivate enqueueWrite(scope: SettingsScope, task: () => void): void {\r\n\t\tthis.writeQueue = this.writeQueue\r\n\t\t\t.then(() => {\r\n\t\t\t\ttask();\r\n\t\t\t\tthis.clearModifiedScope(scope);\r\n\t\t\t})\r\n\t\t\t.catch((error) => {\r\n\t\t\t\tthis.recordError(scope, error);\r\n\t\t\t});\r\n\t}\r\n\r\n\tprivate cloneModifiedNestedFields(source: Map<keyof Settings, Set<string>>): Map<keyof Settings, Set<string>> {\r\n\t\tconst snapshot = new Map<keyof Settings, Set<string>>();\r\n\t\tfor (const [key, value] of source.entries()) {\r\n\t\t\tsnapshot.set(key, new Set(value));\r\n\t\t}\r\n\t\treturn snapshot;\r\n\t}\r\n\r\n\tprivate persistScopedSettings(\r\n\t\tscope: SettingsScope,\r\n\t\tsnapshotSettings: Settings,\r\n\t\tmodifiedFields: Set<keyof Settings>,\r\n\t\tmodifiedNestedFields: Map<keyof Settings, Set<string>>,\r\n\t): void {\r\n\t\tthis.storage.withLock(scope, (current) => {\r\n\t\t\tconst currentFileSettings = current\r\n\t\t\t\t? SettingsManager.migrateSettings(JSON.parse(current) as Record<string, unknown>)\r\n\t\t\t\t: {};\r\n\t\t\tconst mergedSettings: Settings = { ...currentFileSettings };\r\n\t\t\tfor (const field of modifiedFields) {\r\n\t\t\t\tconst value = snapshotSettings[field];\r\n\t\t\t\tif (modifiedNestedFields.has(field) && typeof value === \"object\" && value !== null) {\r\n\t\t\t\t\tconst nestedModified = modifiedNestedFields.get(field)!;\r\n\t\t\t\t\tconst baseNested = (currentFileSettings[field] as Record<string, unknown>) ?? {};\r\n\t\t\t\t\tconst inMemoryNested = value as Record<string, unknown>;\r\n\t\t\t\t\tconst mergedNested = { ...baseNested };\r\n\t\t\t\t\tfor (const nestedKey of nestedModified) {\r\n\t\t\t\t\t\tmergedNested[nestedKey] = inMemoryNested[nestedKey];\r\n\t\t\t\t\t}\r\n\t\t\t\t\t(mergedSettings as Record<string, unknown>)[field] = mergedNested;\r\n\t\t\t\t} else {\r\n\t\t\t\t\t(mergedSettings as Record<string, unknown>)[field] = value;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\treturn JSON.stringify(mergedSettings, null, 2);\r\n\t\t});\r\n\t}\r\n\r\n\tprivate save(): void {\r\n\t\tthis.settings = deepMergeSettings(this.globalSettings, this.projectSettings);\r\n\r\n\t\tif (this.globalSettingsLoadError) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tconst snapshotGlobalSettings = structuredClone(this.globalSettings);\r\n\t\tconst modifiedFields = new Set(this.modifiedFields);\r\n\t\tconst modifiedNestedFields = this.cloneModifiedNestedFields(this.modifiedNestedFields);\r\n\r\n\t\tthis.enqueueWrite(\"global\", () => {\r\n\t\t\tthis.persistScopedSettings(\"global\", snapshotGlobalSettings, modifiedFields, modifiedNestedFields);\r\n\t\t});\r\n\t}\r\n\r\n\tprivate saveProjectSettings(settings: Settings): void {\r\n\t\tthis.projectSettings = structuredClone(settings);\r\n\t\tthis.settings = deepMergeSettings(this.globalSettings, this.projectSettings);\r\n\r\n\t\tif (this.projectSettingsLoadError) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tconst snapshotProjectSettings = structuredClone(this.projectSettings);\r\n\t\tconst modifiedFields = new Set(this.modifiedProjectFields);\r\n\t\tconst modifiedNestedFields = this.cloneModifiedNestedFields(this.modifiedProjectNestedFields);\r\n\t\tthis.enqueueWrite(\"project\", () => {\r\n\t\t\tthis.persistScopedSettings(\"project\", snapshotProjectSettings, modifiedFields, modifiedNestedFields);\r\n\t\t});\r\n\t}\r\n\r\n\tasync flush(): Promise<void> {\r\n\t\tawait this.writeQueue;\r\n\t}\r\n\r\n\tdrainErrors(): SettingsError[] {\r\n\t\tconst drained = [...this.errors];\r\n\t\tthis.errors = [];\r\n\t\treturn drained;\r\n\t}\r\n\r\n\tgetLastChangelogVersion(): string | undefined {\r\n\t\treturn this.settings.lastChangelogVersion;\r\n\t}\r\n\r\n\tsetLastChangelogVersion(version: string): void {\r\n\t\tthis.globalSettings.lastChangelogVersion = version;\r\n\t\tthis.markModified(\"lastChangelogVersion\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetDefaultProvider(): string | undefined {\r\n\t\treturn this.settings.defaultProvider;\r\n\t}\r\n\r\n\tgetDefaultModel(): string | undefined {\r\n\t\treturn this.settings.defaultModel;\r\n\t}\r\n\r\n\tsetDefaultProvider(provider: string): void {\r\n\t\tthis.globalSettings.defaultProvider = provider;\r\n\t\tthis.markModified(\"defaultProvider\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tsetDefaultModel(modelId: string): void {\r\n\t\tthis.globalSettings.defaultModel = modelId;\r\n\t\tthis.markModified(\"defaultModel\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tsetDefaultModelAndProvider(provider: string, modelId: string): void {\r\n\t\tthis.globalSettings.defaultProvider = provider;\r\n\t\tthis.globalSettings.defaultModel = modelId;\r\n\t\tthis.markModified(\"defaultProvider\");\r\n\t\tthis.markModified(\"defaultModel\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetSteeringMode(): \"all\" | \"one-at-a-time\" {\r\n\t\treturn this.settings.steeringMode || \"one-at-a-time\";\r\n\t}\r\n\r\n\tsetSteeringMode(mode: \"all\" | \"one-at-a-time\"): void {\r\n\t\tthis.globalSettings.steeringMode = mode;\r\n\t\tthis.markModified(\"steeringMode\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetFollowUpMode(): \"all\" | \"one-at-a-time\" {\r\n\t\treturn this.settings.followUpMode || \"one-at-a-time\";\r\n\t}\r\n\r\n\tsetFollowUpMode(mode: \"all\" | \"one-at-a-time\"): void {\r\n\t\tthis.globalSettings.followUpMode = mode;\r\n\t\tthis.markModified(\"followUpMode\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetTheme(): string | undefined {\r\n\t\treturn this.settings.theme;\r\n\t}\r\n\r\n\tsetTheme(theme: string): void {\r\n\t\tthis.globalSettings.theme = theme;\r\n\t\tthis.markModified(\"theme\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetDefaultThinkingLevel(): \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\" | undefined {\r\n\t\treturn this.settings.defaultThinkingLevel;\r\n\t}\r\n\r\n\tsetDefaultThinkingLevel(level: \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\"): void {\r\n\t\tthis.globalSettings.defaultThinkingLevel = level;\r\n\t\tthis.markModified(\"defaultThinkingLevel\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetTransport(): TransportSetting {\r\n\t\treturn this.settings.transport ?? \"sse\";\r\n\t}\r\n\r\n\tsetTransport(transport: TransportSetting): void {\r\n\t\tthis.globalSettings.transport = transport;\r\n\t\tthis.markModified(\"transport\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetCompactionEnabled(): boolean {\r\n\t\treturn this.settings.compaction?.enabled ?? true;\r\n\t}\r\n\r\n\tsetCompactionEnabled(enabled: boolean): void {\r\n\t\tif (!this.globalSettings.compaction) {\r\n\t\t\tthis.globalSettings.compaction = {};\r\n\t\t}\r\n\t\tthis.globalSettings.compaction.enabled = enabled;\r\n\t\tthis.markModified(\"compaction\", \"enabled\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetCompactionReserveTokens(): number {\r\n\t\treturn this.settings.compaction?.reserveTokens ?? 16384;\r\n\t}\r\n\r\n\tgetCompactionKeepRecentTokens(): number {\r\n\t\treturn this.settings.compaction?.keepRecentTokens ?? 20000;\r\n\t}\r\n\r\n\tgetCompactionSettings(): { enabled: boolean; reserveTokens: number; keepRecentTokens: number } {\r\n\t\treturn {\r\n\t\t\tenabled: this.getCompactionEnabled(),\r\n\t\t\treserveTokens: this.getCompactionReserveTokens(),\r\n\t\t\tkeepRecentTokens: this.getCompactionKeepRecentTokens(),\r\n\t\t};\r\n\t}\r\n\r\n\tgetBranchSummarySettings(): { reserveTokens: number } {\r\n\t\treturn {\r\n\t\t\treserveTokens: this.settings.branchSummary?.reserveTokens ?? 16384,\r\n\t\t};\r\n\t}\r\n\r\n\tgetRetryEnabled(): boolean {\r\n\t\treturn this.settings.retry?.enabled ?? true;\r\n\t}\r\n\r\n\tsetRetryEnabled(enabled: boolean): void {\r\n\t\tif (!this.globalSettings.retry) {\r\n\t\t\tthis.globalSettings.retry = {};\r\n\t\t}\r\n\t\tthis.globalSettings.retry.enabled = enabled;\r\n\t\tthis.markModified(\"retry\", \"enabled\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetRetrySettings(): { enabled: boolean; maxRetries: number; baseDelayMs: number; maxDelayMs: number } {\r\n\t\treturn {\r\n\t\t\tenabled: this.getRetryEnabled(),\r\n\t\t\tmaxRetries: this.settings.retry?.maxRetries ?? 3,\r\n\t\t\tbaseDelayMs: this.settings.retry?.baseDelayMs ?? 2000,\r\n\t\t\tmaxDelayMs: this.settings.retry?.maxDelayMs ?? 60000,\r\n\t\t};\r\n\t}\r\n\r\n\tgetHideThinkingBlock(): boolean {\r\n\t\treturn this.settings.hideThinkingBlock ?? false;\r\n\t}\r\n\r\n\tsetHideThinkingBlock(hide: boolean): void {\r\n\t\tthis.globalSettings.hideThinkingBlock = hide;\r\n\t\tthis.markModified(\"hideThinkingBlock\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetShellPath(): string | undefined {\r\n\t\treturn this.settings.shellPath;\r\n\t}\r\n\r\n\tsetShellPath(path: string | undefined): void {\r\n\t\tthis.globalSettings.shellPath = path;\r\n\t\tthis.markModified(\"shellPath\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetQuietStartup(): boolean {\r\n\t\treturn this.settings.quietStartup ?? false;\r\n\t}\r\n\r\n\tsetQuietStartup(quiet: boolean): void {\r\n\t\tthis.globalSettings.quietStartup = quiet;\r\n\t\tthis.markModified(\"quietStartup\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetShellCommandPrefix(): string | undefined {\r\n\t\treturn this.settings.shellCommandPrefix;\r\n\t}\r\n\r\n\tsetShellCommandPrefix(prefix: string | undefined): void {\r\n\t\tthis.globalSettings.shellCommandPrefix = prefix;\r\n\t\tthis.markModified(\"shellCommandPrefix\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetCollapseChangelog(): boolean {\r\n\t\treturn this.settings.collapseChangelog ?? false;\r\n\t}\r\n\r\n\tsetCollapseChangelog(collapse: boolean): void {\r\n\t\tthis.globalSettings.collapseChangelog = collapse;\r\n\t\tthis.markModified(\"collapseChangelog\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetPackages(): PackageSource[] {\r\n\t\treturn [...(this.settings.packages ?? [])];\r\n\t}\r\n\r\n\tsetPackages(packages: PackageSource[]): void {\r\n\t\tthis.globalSettings.packages = packages;\r\n\t\tthis.markModified(\"packages\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tsetProjectPackages(packages: PackageSource[]): void {\r\n\t\tconst projectSettings = structuredClone(this.projectSettings);\r\n\t\tprojectSettings.packages = packages;\r\n\t\tthis.markProjectModified(\"packages\");\r\n\t\tthis.saveProjectSettings(projectSettings);\r\n\t}\r\n\r\n\tgetExtensionPaths(): string[] {\r\n\t\treturn [...(this.settings.extensions ?? [])];\r\n\t}\r\n\r\n\tsetExtensionPaths(paths: string[]): void {\r\n\t\tthis.globalSettings.extensions = paths;\r\n\t\tthis.markModified(\"extensions\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tsetProjectExtensionPaths(paths: string[]): void {\r\n\t\tconst projectSettings = structuredClone(this.projectSettings);\r\n\t\tprojectSettings.extensions = paths;\r\n\t\tthis.markProjectModified(\"extensions\");\r\n\t\tthis.saveProjectSettings(projectSettings);\r\n\t}\r\n\r\n\tgetSkillPaths(): string[] {\r\n\t\treturn [...(this.settings.skills ?? [])];\r\n\t}\r\n\r\n\tsetSkillPaths(paths: string[]): void {\r\n\t\tthis.globalSettings.skills = paths;\r\n\t\tthis.markModified(\"skills\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tsetProjectSkillPaths(paths: string[]): void {\r\n\t\tconst projectSettings = structuredClone(this.projectSettings);\r\n\t\tprojectSettings.skills = paths;\r\n\t\tthis.markProjectModified(\"skills\");\r\n\t\tthis.saveProjectSettings(projectSettings);\r\n\t}\r\n\r\n\tgetPromptTemplatePaths(): string[] {\r\n\t\treturn [...(this.settings.prompts ?? [])];\r\n\t}\r\n\r\n\tsetPromptTemplatePaths(paths: string[]): void {\r\n\t\tthis.globalSettings.prompts = paths;\r\n\t\tthis.markModified(\"prompts\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tsetProjectPromptTemplatePaths(paths: string[]): void {\r\n\t\tconst projectSettings = structuredClone(this.projectSettings);\r\n\t\tprojectSettings.prompts = paths;\r\n\t\tthis.markProjectModified(\"prompts\");\r\n\t\tthis.saveProjectSettings(projectSettings);\r\n\t}\r\n\r\n\tgetThemePaths(): string[] {\r\n\t\treturn [...(this.settings.themes ?? [])];\r\n\t}\r\n\r\n\tsetThemePaths(paths: string[]): void {\r\n\t\tthis.globalSettings.themes = paths;\r\n\t\tthis.markModified(\"themes\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tsetProjectThemePaths(paths: string[]): void {\r\n\t\tconst projectSettings = structuredClone(this.projectSettings);\r\n\t\tprojectSettings.themes = paths;\r\n\t\tthis.markProjectModified(\"themes\");\r\n\t\tthis.saveProjectSettings(projectSettings);\r\n\t}\r\n\r\n\tgetEnableSkillCommands(): boolean {\r\n\t\treturn this.settings.enableSkillCommands ?? true;\r\n\t}\r\n\r\n\tsetEnableSkillCommands(enabled: boolean): void {\r\n\t\tthis.globalSettings.enableSkillCommands = enabled;\r\n\t\tthis.markModified(\"enableSkillCommands\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetThinkingBudgets(): ThinkingBudgetsSettings | undefined {\r\n\t\treturn this.settings.thinkingBudgets;\r\n\t}\r\n\r\n\tgetShowImages(): boolean {\r\n\t\treturn this.settings.terminal?.showImages ?? true;\r\n\t}\r\n\r\n\tsetShowImages(show: boolean): void {\r\n\t\tif (!this.globalSettings.terminal) {\r\n\t\t\tthis.globalSettings.terminal = {};\r\n\t\t}\r\n\t\tthis.globalSettings.terminal.showImages = show;\r\n\t\tthis.markModified(\"terminal\", \"showImages\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetClearOnShrink(): boolean {\r\n\t\t// Settings takes precedence, then env var, then default false\r\n\t\tif (this.settings.terminal?.clearOnShrink !== undefined) {\r\n\t\t\treturn this.settings.terminal.clearOnShrink;\r\n\t\t}\r\n\t\treturn process.env.PI_CLEAR_ON_SHRINK === \"1\";\r\n\t}\r\n\r\n\tsetClearOnShrink(enabled: boolean): void {\r\n\t\tif (!this.globalSettings.terminal) {\r\n\t\t\tthis.globalSettings.terminal = {};\r\n\t\t}\r\n\t\tthis.globalSettings.terminal.clearOnShrink = enabled;\r\n\t\tthis.markModified(\"terminal\", \"clearOnShrink\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetImageAutoResize(): boolean {\r\n\t\treturn this.settings.images?.autoResize ?? true;\r\n\t}\r\n\r\n\tsetImageAutoResize(enabled: boolean): void {\r\n\t\tif (!this.globalSettings.images) {\r\n\t\t\tthis.globalSettings.images = {};\r\n\t\t}\r\n\t\tthis.globalSettings.images.autoResize = enabled;\r\n\t\tthis.markModified(\"images\", \"autoResize\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetBlockImages(): boolean {\r\n\t\treturn this.settings.images?.blockImages ?? false;\r\n\t}\r\n\r\n\tsetBlockImages(blocked: boolean): void {\r\n\t\tif (!this.globalSettings.images) {\r\n\t\t\tthis.globalSettings.images = {};\r\n\t\t}\r\n\t\tthis.globalSettings.images.blockImages = blocked;\r\n\t\tthis.markModified(\"images\", \"blockImages\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetEnabledModels(): string[] | undefined {\r\n\t\treturn this.settings.enabledModels;\r\n\t}\r\n\r\n\tsetEnabledModels(patterns: string[] | undefined): void {\r\n\t\tthis.globalSettings.enabledModels = patterns;\r\n\t\tthis.markModified(\"enabledModels\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetDoubleEscapeAction(): \"fork\" | \"tree\" | \"none\" {\r\n\t\treturn this.settings.doubleEscapeAction ?? \"tree\";\r\n\t}\r\n\r\n\tsetDoubleEscapeAction(action: \"fork\" | \"tree\" | \"none\"): void {\r\n\t\tthis.globalSettings.doubleEscapeAction = action;\r\n\t\tthis.markModified(\"doubleEscapeAction\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetShowHardwareCursor(): boolean {\r\n\t\treturn this.settings.showHardwareCursor ?? process.env.PI_HARDWARE_CURSOR === \"1\";\r\n\t}\r\n\r\n\tsetShowHardwareCursor(enabled: boolean): void {\r\n\t\tthis.globalSettings.showHardwareCursor = enabled;\r\n\t\tthis.markModified(\"showHardwareCursor\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetEditorPaddingX(): number {\r\n\t\treturn this.settings.editorPaddingX ?? 0;\r\n\t}\r\n\r\n\tsetEditorPaddingX(padding: number): void {\r\n\t\tthis.globalSettings.editorPaddingX = Math.max(0, Math.min(3, Math.floor(padding)));\r\n\t\tthis.markModified(\"editorPaddingX\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetAutocompleteMaxVisible(): number {\r\n\t\treturn this.settings.autocompleteMaxVisible ?? 5;\r\n\t}\r\n\r\n\tsetAutocompleteMaxVisible(maxVisible: number): void {\r\n\t\tthis.globalSettings.autocompleteMaxVisible = Math.max(3, Math.min(20, Math.floor(maxVisible)));\r\n\t\tthis.markModified(\"autocompleteMaxVisible\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetCodeBlockIndent(): string {\r\n\t\treturn this.settings.markdown?.codeBlockIndent ?? \" \";\r\n\t}\r\n\r\n\tgetMemoryEnabled(): boolean {\r\n\t\treturn this.settings.memory?.enabled ?? true;\r\n\t}\r\n\r\n\tsetMemoryEnabled(enabled: boolean): void {\r\n\t\tif (!this.globalSettings.memory) {\r\n\t\t\tthis.globalSettings.memory = {};\r\n\t\t}\r\n\t\tthis.globalSettings.memory.enabled = enabled;\r\n\t\tthis.markModified(\"memory\", \"enabled\");\r\n\t\tthis.save();\r\n\t}\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"file":"settings-manager.d.ts","sourceRoot":"","sources":["../../src/core/settings-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Transport } from \"@mariozechner/pi-ai\";\r\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\r\nimport { dirname, join } from \"path\";\r\nimport lockfile from \"proper-lockfile\";\r\nimport { CONFIG_DIR_NAME, getAgentDir } from \"../config.js\";\r\n\r\nexport interface CompactionSettings {\r\n\tenabled?: boolean; // default: true\r\n\treserveTokens?: number; // default: 16384\r\n\tkeepRecentTokens?: number; // default: 20000\r\n}\r\n\r\nexport interface BranchSummarySettings {\r\n\treserveTokens?: number; // default: 16384 (tokens reserved for prompt + LLM response)\r\n}\r\n\r\nexport interface RetrySettings {\r\n\tenabled?: boolean; // default: true\r\n\tmaxRetries?: number; // default: 3\r\n\tbaseDelayMs?: number; // default: 2000 (exponential backoff: 2s, 4s, 8s)\r\n\tmaxDelayMs?: number; // default: 60000 (max server-requested delay before failing)\r\n}\r\n\r\nexport interface TerminalSettings {\r\n\tshowImages?: boolean; // default: true (only relevant if terminal supports images)\r\n\tclearOnShrink?: boolean; // default: false (clear empty rows when content shrinks)\r\n}\r\n\r\nexport interface ImageSettings {\r\n\tautoResize?: boolean; // default: true (resize images to 2000x2000 max for better model compatibility)\r\n\tblockImages?: boolean; // default: false - when true, prevents all images from being sent to LLM providers\r\n}\r\n\r\nexport interface ThinkingBudgetsSettings {\r\n\tminimal?: number;\r\n\tlow?: number;\r\n\tmedium?: number;\r\n\thigh?: number;\r\n}\r\n\r\nexport interface MarkdownSettings {\r\n\tcodeBlockIndent?: string; // default: \" \"\r\n}\r\n\r\nexport type TransportSetting = Transport;\r\n\r\n/**\r\n * Package source for npm/git packages.\r\n * - String form: load all resources from the package\r\n * - Object form: filter which resources to load\r\n */\r\nexport type PackageSource =\r\n\t| string\r\n\t| {\r\n\t\t\tsource: string;\r\n\t\t\textensions?: string[];\r\n\t\t\tskills?: string[];\r\n\t\t\tprompts?: string[];\r\n\t\t\tthemes?: string[];\r\n\t };\r\n\r\nexport interface Settings {\r\n\tlastChangelogVersion?: string;\r\n\tdefaultProvider?: string;\r\n\tdefaultModel?: string;\r\n\tdefaultThinkingLevel?: \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\";\r\n\ttransport?: TransportSetting; // default: \"sse\"\r\n\tsteeringMode?: \"all\" | \"one-at-a-time\";\r\n\tfollowUpMode?: \"all\" | \"one-at-a-time\";\r\n\ttheme?: string;\r\n\tcompaction?: CompactionSettings;\r\n\tbranchSummary?: BranchSummarySettings;\r\n\tretry?: RetrySettings;\r\n\thideThinkingBlock?: boolean;\r\n\tshellPath?: string; // Custom shell path (e.g., for Cygwin users on Windows)\r\n\tquietStartup?: boolean;\r\n\tshellCommandPrefix?: string; // Prefix prepended to every bash command (e.g., \"shopt -s expand_aliases\" for alias support)\r\n\tcollapseChangelog?: boolean; // Show condensed changelog after update (use /changelog for full)\r\n\tpackages?: PackageSource[]; // Array of npm/git package sources (string or object with filtering)\r\n\textensions?: string[]; // Array of local extension file paths or directories\r\n\tskills?: string[]; // Array of local skill file paths or directories\r\n\tprompts?: string[]; // Array of local prompt template paths or directories\r\n\tthemes?: string[]; // Array of local theme file paths or directories\r\n\tenableSkillCommands?: boolean; // default: true - register skills as /skill:name commands\r\n\tterminal?: TerminalSettings;\r\n\timages?: ImageSettings;\r\n\tenabledModels?: string[]; // Model patterns for cycling (same format as --models CLI flag)\r\n\tdoubleEscapeAction?: \"fork\" | \"tree\" | \"none\"; // Action for double-escape with empty editor (default: \"tree\")\r\n\tthinkingBudgets?: ThinkingBudgetsSettings; // Custom token budgets for thinking levels\r\n\teditorPaddingX?: number; // Horizontal padding for input editor (default: 0)\r\n\tautocompleteMaxVisible?: number; // Max visible items in autocomplete dropdown (default: 5)\r\n\tshowHardwareCursor?: boolean; // Show terminal cursor while still positioning it for IME\r\n\tmarkdown?: MarkdownSettings;\r\n\tmemory?: {\r\n\t\tenabled?: boolean; // default: true - persistent memory system (procedural, episodic, semantic)\r\n\t\tscope?: \"project\" | \"global\"; // default: \"project\" - where memories are stored\r\n\t};\r\n}\r\n\r\n/** Deep merge settings: project/overrides take precedence, nested objects merge recursively */\r\nfunction deepMergeSettings(base: Settings, overrides: Settings): Settings {\r\n\tconst result: Settings = { ...base };\r\n\r\n\tfor (const key of Object.keys(overrides) as (keyof Settings)[]) {\r\n\t\tconst overrideValue = overrides[key];\r\n\t\tconst baseValue = base[key];\r\n\r\n\t\tif (overrideValue === undefined) {\r\n\t\t\tcontinue;\r\n\t\t}\r\n\r\n\t\t// For nested objects, merge recursively\r\n\t\tif (\r\n\t\t\ttypeof overrideValue === \"object\" &&\r\n\t\t\toverrideValue !== null &&\r\n\t\t\t!Array.isArray(overrideValue) &&\r\n\t\t\ttypeof baseValue === \"object\" &&\r\n\t\t\tbaseValue !== null &&\r\n\t\t\t!Array.isArray(baseValue)\r\n\t\t) {\r\n\t\t\t(result as Record<string, unknown>)[key] = { ...baseValue, ...overrideValue };\r\n\t\t} else {\r\n\t\t\t// For primitives and arrays, override value wins\r\n\t\t\t(result as Record<string, unknown>)[key] = overrideValue;\r\n\t\t}\r\n\t}\r\n\r\n\treturn result;\r\n}\r\n\r\nexport type SettingsScope = \"global\" | \"project\";\r\n\r\nexport interface SettingsStorage {\r\n\twithLock(scope: SettingsScope, fn: (current: string | undefined) => string | undefined): void;\r\n}\r\n\r\nexport interface SettingsError {\r\n\tscope: SettingsScope;\r\n\terror: Error;\r\n}\r\n\r\nexport class FileSettingsStorage implements SettingsStorage {\r\n\tprivate globalSettingsPath: string;\r\n\tprivate projectSettingsPath: string;\r\n\r\n\tconstructor(cwd: string = process.cwd(), agentDir: string = getAgentDir()) {\r\n\t\tthis.globalSettingsPath = join(agentDir, \"settings.json\");\r\n\t\tthis.projectSettingsPath = join(cwd, CONFIG_DIR_NAME, \"settings.json\");\r\n\t}\r\n\r\n\twithLock(scope: SettingsScope, fn: (current: string | undefined) => string | undefined): void {\r\n\t\tconst path = scope === \"global\" ? this.globalSettingsPath : this.projectSettingsPath;\r\n\t\tconst dir = dirname(path);\r\n\t\tif (!existsSync(dir)) {\r\n\t\t\tmkdirSync(dir, { recursive: true });\r\n\t\t}\r\n\r\n\t\tlet release: (() => void) | undefined;\r\n\t\ttry {\r\n\t\t\trelease = lockfile.lockSync(path, { realpath: false });\r\n\t\t\tconst current = existsSync(path) ? readFileSync(path, \"utf-8\") : undefined;\r\n\t\t\tconst next = fn(current);\r\n\t\t\tif (next !== undefined) {\r\n\t\t\t\twriteFileSync(path, next, \"utf-8\");\r\n\t\t\t}\r\n\t\t} finally {\r\n\t\t\tif (release) {\r\n\t\t\t\trelease();\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\nexport class InMemorySettingsStorage implements SettingsStorage {\r\n\tprivate global: string | undefined;\r\n\tprivate project: string | undefined;\r\n\r\n\twithLock(scope: SettingsScope, fn: (current: string | undefined) => string | undefined): void {\r\n\t\tconst current = scope === \"global\" ? this.global : this.project;\r\n\t\tconst next = fn(current);\r\n\t\tif (next !== undefined) {\r\n\t\t\tif (scope === \"global\") {\r\n\t\t\t\tthis.global = next;\r\n\t\t\t} else {\r\n\t\t\t\tthis.project = next;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\nexport class SettingsManager {\r\n\tprivate storage: SettingsStorage;\r\n\tprivate globalSettings: Settings;\r\n\tprivate projectSettings: Settings;\r\n\tprivate settings: Settings;\r\n\tprivate modifiedFields = new Set<keyof Settings>(); // Track global fields modified during session\r\n\tprivate modifiedNestedFields = new Map<keyof Settings, Set<string>>(); // Track global nested field modifications\r\n\tprivate modifiedProjectFields = new Set<keyof Settings>(); // Track project fields modified during session\r\n\tprivate modifiedProjectNestedFields = new Map<keyof Settings, Set<string>>(); // Track project nested field modifications\r\n\tprivate globalSettingsLoadError: Error | null = null; // Track if global settings file had parse errors\r\n\tprivate projectSettingsLoadError: Error | null = null; // Track if project settings file had parse errors\r\n\tprivate writeQueue: Promise<void> = Promise.resolve();\r\n\tprivate errors: SettingsError[];\r\n\r\n\tprivate constructor(\r\n\t\tstorage: SettingsStorage,\r\n\t\tinitialGlobal: Settings,\r\n\t\tinitialProject: Settings,\r\n\t\tglobalLoadError: Error | null = null,\r\n\t\tprojectLoadError: Error | null = null,\r\n\t\tinitialErrors: SettingsError[] = [],\r\n\t) {\r\n\t\tthis.storage = storage;\r\n\t\tthis.globalSettings = initialGlobal;\r\n\t\tthis.projectSettings = initialProject;\r\n\t\tthis.globalSettingsLoadError = globalLoadError;\r\n\t\tthis.projectSettingsLoadError = projectLoadError;\r\n\t\tthis.errors = [...initialErrors];\r\n\t\tthis.settings = deepMergeSettings(this.globalSettings, this.projectSettings);\r\n\t}\r\n\r\n\t/** Create a SettingsManager that loads from files */\r\n\tstatic create(cwd: string = process.cwd(), agentDir: string = getAgentDir()): SettingsManager {\r\n\t\tconst storage = new FileSettingsStorage(cwd, agentDir);\r\n\t\treturn SettingsManager.fromStorage(storage);\r\n\t}\r\n\r\n\t/** Create a SettingsManager from an arbitrary storage backend */\r\n\tstatic fromStorage(storage: SettingsStorage): SettingsManager {\r\n\t\tconst globalLoad = SettingsManager.tryLoadFromStorage(storage, \"global\");\r\n\t\tconst projectLoad = SettingsManager.tryLoadFromStorage(storage, \"project\");\r\n\t\tconst initialErrors: SettingsError[] = [];\r\n\t\tif (globalLoad.error) {\r\n\t\t\tinitialErrors.push({ scope: \"global\", error: globalLoad.error });\r\n\t\t}\r\n\t\tif (projectLoad.error) {\r\n\t\t\tinitialErrors.push({ scope: \"project\", error: projectLoad.error });\r\n\t\t}\r\n\r\n\t\treturn new SettingsManager(\r\n\t\t\tstorage,\r\n\t\t\tglobalLoad.settings,\r\n\t\t\tprojectLoad.settings,\r\n\t\t\tglobalLoad.error,\r\n\t\t\tprojectLoad.error,\r\n\t\t\tinitialErrors,\r\n\t\t);\r\n\t}\r\n\r\n\t/** Create an in-memory SettingsManager (no file I/O) */\r\n\tstatic inMemory(settings: Partial<Settings> = {}): SettingsManager {\r\n\t\tconst storage = new InMemorySettingsStorage();\r\n\t\treturn new SettingsManager(storage, settings, {});\r\n\t}\r\n\r\n\tprivate static loadFromStorage(storage: SettingsStorage, scope: SettingsScope): Settings {\r\n\t\tlet content: string | undefined;\r\n\t\tstorage.withLock(scope, (current) => {\r\n\t\t\tcontent = current;\r\n\t\t\treturn undefined;\r\n\t\t});\r\n\r\n\t\tif (!content) {\r\n\t\t\treturn {};\r\n\t\t}\r\n\t\tconst settings = JSON.parse(content);\r\n\t\treturn SettingsManager.migrateSettings(settings);\r\n\t}\r\n\r\n\tprivate static tryLoadFromStorage(\r\n\t\tstorage: SettingsStorage,\r\n\t\tscope: SettingsScope,\r\n\t): { settings: Settings; error: Error | null } {\r\n\t\ttry {\r\n\t\t\treturn { settings: SettingsManager.loadFromStorage(storage, scope), error: null };\r\n\t\t} catch (error) {\r\n\t\t\treturn { settings: {}, error: error as Error };\r\n\t\t}\r\n\t}\r\n\r\n\t/** Migrate old settings format to new format */\r\n\tprivate static migrateSettings(settings: Record<string, unknown>): Settings {\r\n\t\t// Migrate queueMode -> steeringMode\r\n\t\tif (\"queueMode\" in settings && !(\"steeringMode\" in settings)) {\r\n\t\t\tsettings.steeringMode = settings.queueMode;\r\n\t\t\tdelete settings.queueMode;\r\n\t\t}\r\n\r\n\t\t// Migrate legacy websockets boolean -> transport enum\r\n\t\tif (!(\"transport\" in settings) && typeof settings.websockets === \"boolean\") {\r\n\t\t\tsettings.transport = settings.websockets ? \"websocket\" : \"sse\";\r\n\t\t\tdelete settings.websockets;\r\n\t\t}\r\n\r\n\t\t// Migrate old skills object format to new array format\r\n\t\tif (\r\n\t\t\t\"skills\" in settings &&\r\n\t\t\ttypeof settings.skills === \"object\" &&\r\n\t\t\tsettings.skills !== null &&\r\n\t\t\t!Array.isArray(settings.skills)\r\n\t\t) {\r\n\t\t\tconst skillsSettings = settings.skills as {\r\n\t\t\t\tenableSkillCommands?: boolean;\r\n\t\t\t\tcustomDirectories?: unknown;\r\n\t\t\t};\r\n\t\t\tif (skillsSettings.enableSkillCommands !== undefined && settings.enableSkillCommands === undefined) {\r\n\t\t\t\tsettings.enableSkillCommands = skillsSettings.enableSkillCommands;\r\n\t\t\t}\r\n\t\t\tif (Array.isArray(skillsSettings.customDirectories) && skillsSettings.customDirectories.length > 0) {\r\n\t\t\t\tsettings.skills = skillsSettings.customDirectories;\r\n\t\t\t} else {\r\n\t\t\t\tdelete settings.skills;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn settings as Settings;\r\n\t}\r\n\r\n\tgetGlobalSettings(): Settings {\r\n\t\treturn structuredClone(this.globalSettings);\r\n\t}\r\n\r\n\tgetProjectSettings(): Settings {\r\n\t\treturn structuredClone(this.projectSettings);\r\n\t}\r\n\r\n\treload(): void {\r\n\t\tconst globalLoad = SettingsManager.tryLoadFromStorage(this.storage, \"global\");\r\n\t\tif (!globalLoad.error) {\r\n\t\t\tthis.globalSettings = globalLoad.settings;\r\n\t\t\tthis.globalSettingsLoadError = null;\r\n\t\t} else {\r\n\t\t\tthis.globalSettingsLoadError = globalLoad.error;\r\n\t\t\tthis.recordError(\"global\", globalLoad.error);\r\n\t\t}\r\n\r\n\t\tthis.modifiedFields.clear();\r\n\t\tthis.modifiedNestedFields.clear();\r\n\t\tthis.modifiedProjectFields.clear();\r\n\t\tthis.modifiedProjectNestedFields.clear();\r\n\r\n\t\tconst projectLoad = SettingsManager.tryLoadFromStorage(this.storage, \"project\");\r\n\t\tif (!projectLoad.error) {\r\n\t\t\tthis.projectSettings = projectLoad.settings;\r\n\t\t\tthis.projectSettingsLoadError = null;\r\n\t\t} else {\r\n\t\t\tthis.projectSettingsLoadError = projectLoad.error;\r\n\t\t\tthis.recordError(\"project\", projectLoad.error);\r\n\t\t}\r\n\r\n\t\tthis.settings = deepMergeSettings(this.globalSettings, this.projectSettings);\r\n\t}\r\n\r\n\t/** Apply additional overrides on top of current settings */\r\n\tapplyOverrides(overrides: Partial<Settings>): void {\r\n\t\tthis.settings = deepMergeSettings(this.settings, overrides);\r\n\t}\r\n\r\n\t/** Mark a global field as modified during this session */\r\n\tprivate markModified(field: keyof Settings, nestedKey?: string): void {\r\n\t\tthis.modifiedFields.add(field);\r\n\t\tif (nestedKey) {\r\n\t\t\tif (!this.modifiedNestedFields.has(field)) {\r\n\t\t\t\tthis.modifiedNestedFields.set(field, new Set());\r\n\t\t\t}\r\n\t\t\tthis.modifiedNestedFields.get(field)!.add(nestedKey);\r\n\t\t}\r\n\t}\r\n\r\n\t/** Mark a project field as modified during this session */\r\n\tprivate markProjectModified(field: keyof Settings, nestedKey?: string): void {\r\n\t\tthis.modifiedProjectFields.add(field);\r\n\t\tif (nestedKey) {\r\n\t\t\tif (!this.modifiedProjectNestedFields.has(field)) {\r\n\t\t\t\tthis.modifiedProjectNestedFields.set(field, new Set());\r\n\t\t\t}\r\n\t\t\tthis.modifiedProjectNestedFields.get(field)!.add(nestedKey);\r\n\t\t}\r\n\t}\r\n\r\n\tprivate recordError(scope: SettingsScope, error: unknown): void {\r\n\t\tconst normalizedError = error instanceof Error ? error : new Error(String(error));\r\n\t\tthis.errors.push({ scope, error: normalizedError });\r\n\t}\r\n\r\n\tprivate clearModifiedScope(scope: SettingsScope): void {\r\n\t\tif (scope === \"global\") {\r\n\t\t\tthis.modifiedFields.clear();\r\n\t\t\tthis.modifiedNestedFields.clear();\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tthis.modifiedProjectFields.clear();\r\n\t\tthis.modifiedProjectNestedFields.clear();\r\n\t}\r\n\r\n\tprivate enqueueWrite(scope: SettingsScope, task: () => void): void {\r\n\t\tthis.writeQueue = this.writeQueue\r\n\t\t\t.then(() => {\r\n\t\t\t\ttask();\r\n\t\t\t\tthis.clearModifiedScope(scope);\r\n\t\t\t})\r\n\t\t\t.catch((error) => {\r\n\t\t\t\tthis.recordError(scope, error);\r\n\t\t\t});\r\n\t}\r\n\r\n\tprivate cloneModifiedNestedFields(source: Map<keyof Settings, Set<string>>): Map<keyof Settings, Set<string>> {\r\n\t\tconst snapshot = new Map<keyof Settings, Set<string>>();\r\n\t\tfor (const [key, value] of source.entries()) {\r\n\t\t\tsnapshot.set(key, new Set(value));\r\n\t\t}\r\n\t\treturn snapshot;\r\n\t}\r\n\r\n\tprivate persistScopedSettings(\r\n\t\tscope: SettingsScope,\r\n\t\tsnapshotSettings: Settings,\r\n\t\tmodifiedFields: Set<keyof Settings>,\r\n\t\tmodifiedNestedFields: Map<keyof Settings, Set<string>>,\r\n\t): void {\r\n\t\tthis.storage.withLock(scope, (current) => {\r\n\t\t\tconst currentFileSettings = current\r\n\t\t\t\t? SettingsManager.migrateSettings(JSON.parse(current) as Record<string, unknown>)\r\n\t\t\t\t: {};\r\n\t\t\tconst mergedSettings: Settings = { ...currentFileSettings };\r\n\t\t\tfor (const field of modifiedFields) {\r\n\t\t\t\tconst value = snapshotSettings[field];\r\n\t\t\t\tif (modifiedNestedFields.has(field) && typeof value === \"object\" && value !== null) {\r\n\t\t\t\t\tconst nestedModified = modifiedNestedFields.get(field)!;\r\n\t\t\t\t\tconst baseNested = (currentFileSettings[field] as Record<string, unknown>) ?? {};\r\n\t\t\t\t\tconst inMemoryNested = value as Record<string, unknown>;\r\n\t\t\t\t\tconst mergedNested = { ...baseNested };\r\n\t\t\t\t\tfor (const nestedKey of nestedModified) {\r\n\t\t\t\t\t\tmergedNested[nestedKey] = inMemoryNested[nestedKey];\r\n\t\t\t\t\t}\r\n\t\t\t\t\t(mergedSettings as Record<string, unknown>)[field] = mergedNested;\r\n\t\t\t\t} else {\r\n\t\t\t\t\t(mergedSettings as Record<string, unknown>)[field] = value;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\treturn JSON.stringify(mergedSettings, null, 2);\r\n\t\t});\r\n\t}\r\n\r\n\tprivate save(): void {\r\n\t\tthis.settings = deepMergeSettings(this.globalSettings, this.projectSettings);\r\n\r\n\t\tif (this.globalSettingsLoadError) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tconst snapshotGlobalSettings = structuredClone(this.globalSettings);\r\n\t\tconst modifiedFields = new Set(this.modifiedFields);\r\n\t\tconst modifiedNestedFields = this.cloneModifiedNestedFields(this.modifiedNestedFields);\r\n\r\n\t\tthis.enqueueWrite(\"global\", () => {\r\n\t\t\tthis.persistScopedSettings(\"global\", snapshotGlobalSettings, modifiedFields, modifiedNestedFields);\r\n\t\t});\r\n\t}\r\n\r\n\tprivate saveProjectSettings(settings: Settings): void {\r\n\t\tthis.projectSettings = structuredClone(settings);\r\n\t\tthis.settings = deepMergeSettings(this.globalSettings, this.projectSettings);\r\n\r\n\t\tif (this.projectSettingsLoadError) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tconst snapshotProjectSettings = structuredClone(this.projectSettings);\r\n\t\tconst modifiedFields = new Set(this.modifiedProjectFields);\r\n\t\tconst modifiedNestedFields = this.cloneModifiedNestedFields(this.modifiedProjectNestedFields);\r\n\t\tthis.enqueueWrite(\"project\", () => {\r\n\t\t\tthis.persistScopedSettings(\"project\", snapshotProjectSettings, modifiedFields, modifiedNestedFields);\r\n\t\t});\r\n\t}\r\n\r\n\tasync flush(): Promise<void> {\r\n\t\tawait this.writeQueue;\r\n\t}\r\n\r\n\tdrainErrors(): SettingsError[] {\r\n\t\tconst drained = [...this.errors];\r\n\t\tthis.errors = [];\r\n\t\treturn drained;\r\n\t}\r\n\r\n\tgetLastChangelogVersion(): string | undefined {\r\n\t\treturn this.settings.lastChangelogVersion;\r\n\t}\r\n\r\n\tsetLastChangelogVersion(version: string): void {\r\n\t\tthis.globalSettings.lastChangelogVersion = version;\r\n\t\tthis.markModified(\"lastChangelogVersion\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetDefaultProvider(): string | undefined {\r\n\t\treturn this.settings.defaultProvider;\r\n\t}\r\n\r\n\tgetDefaultModel(): string | undefined {\r\n\t\treturn this.settings.defaultModel;\r\n\t}\r\n\r\n\tsetDefaultProvider(provider: string): void {\r\n\t\tthis.globalSettings.defaultProvider = provider;\r\n\t\tthis.markModified(\"defaultProvider\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tsetDefaultModel(modelId: string): void {\r\n\t\tthis.globalSettings.defaultModel = modelId;\r\n\t\tthis.markModified(\"defaultModel\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tsetDefaultModelAndProvider(provider: string, modelId: string): void {\r\n\t\tthis.globalSettings.defaultProvider = provider;\r\n\t\tthis.globalSettings.defaultModel = modelId;\r\n\t\tthis.markModified(\"defaultProvider\");\r\n\t\tthis.markModified(\"defaultModel\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetSteeringMode(): \"all\" | \"one-at-a-time\" {\r\n\t\treturn this.settings.steeringMode || \"one-at-a-time\";\r\n\t}\r\n\r\n\tsetSteeringMode(mode: \"all\" | \"one-at-a-time\"): void {\r\n\t\tthis.globalSettings.steeringMode = mode;\r\n\t\tthis.markModified(\"steeringMode\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetFollowUpMode(): \"all\" | \"one-at-a-time\" {\r\n\t\treturn this.settings.followUpMode || \"one-at-a-time\";\r\n\t}\r\n\r\n\tsetFollowUpMode(mode: \"all\" | \"one-at-a-time\"): void {\r\n\t\tthis.globalSettings.followUpMode = mode;\r\n\t\tthis.markModified(\"followUpMode\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetTheme(): string | undefined {\r\n\t\treturn this.settings.theme;\r\n\t}\r\n\r\n\tsetTheme(theme: string): void {\r\n\t\tthis.globalSettings.theme = theme;\r\n\t\tthis.markModified(\"theme\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetDefaultThinkingLevel(): \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\" | undefined {\r\n\t\treturn this.settings.defaultThinkingLevel;\r\n\t}\r\n\r\n\tsetDefaultThinkingLevel(level: \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\"): void {\r\n\t\tthis.globalSettings.defaultThinkingLevel = level;\r\n\t\tthis.markModified(\"defaultThinkingLevel\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetTransport(): TransportSetting {\r\n\t\treturn this.settings.transport ?? \"sse\";\r\n\t}\r\n\r\n\tsetTransport(transport: TransportSetting): void {\r\n\t\tthis.globalSettings.transport = transport;\r\n\t\tthis.markModified(\"transport\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetCompactionEnabled(): boolean {\r\n\t\treturn this.settings.compaction?.enabled ?? true;\r\n\t}\r\n\r\n\tsetCompactionEnabled(enabled: boolean): void {\r\n\t\tif (!this.globalSettings.compaction) {\r\n\t\t\tthis.globalSettings.compaction = {};\r\n\t\t}\r\n\t\tthis.globalSettings.compaction.enabled = enabled;\r\n\t\tthis.markModified(\"compaction\", \"enabled\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetCompactionReserveTokens(): number {\r\n\t\treturn this.settings.compaction?.reserveTokens ?? 16384;\r\n\t}\r\n\r\n\tgetCompactionKeepRecentTokens(): number {\r\n\t\treturn this.settings.compaction?.keepRecentTokens ?? 20000;\r\n\t}\r\n\r\n\tgetCompactionSettings(): { enabled: boolean; reserveTokens: number; keepRecentTokens: number } {\r\n\t\treturn {\r\n\t\t\tenabled: this.getCompactionEnabled(),\r\n\t\t\treserveTokens: this.getCompactionReserveTokens(),\r\n\t\t\tkeepRecentTokens: this.getCompactionKeepRecentTokens(),\r\n\t\t};\r\n\t}\r\n\r\n\tgetBranchSummarySettings(): { reserveTokens: number } {\r\n\t\treturn {\r\n\t\t\treserveTokens: this.settings.branchSummary?.reserveTokens ?? 16384,\r\n\t\t};\r\n\t}\r\n\r\n\tgetRetryEnabled(): boolean {\r\n\t\treturn this.settings.retry?.enabled ?? true;\r\n\t}\r\n\r\n\tsetRetryEnabled(enabled: boolean): void {\r\n\t\tif (!this.globalSettings.retry) {\r\n\t\t\tthis.globalSettings.retry = {};\r\n\t\t}\r\n\t\tthis.globalSettings.retry.enabled = enabled;\r\n\t\tthis.markModified(\"retry\", \"enabled\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetRetrySettings(): { enabled: boolean; maxRetries: number; baseDelayMs: number; maxDelayMs: number } {\r\n\t\treturn {\r\n\t\t\tenabled: this.getRetryEnabled(),\r\n\t\t\tmaxRetries: this.settings.retry?.maxRetries ?? 3,\r\n\t\t\tbaseDelayMs: this.settings.retry?.baseDelayMs ?? 2000,\r\n\t\t\tmaxDelayMs: this.settings.retry?.maxDelayMs ?? 60000,\r\n\t\t};\r\n\t}\r\n\r\n\tgetHideThinkingBlock(): boolean {\r\n\t\treturn this.settings.hideThinkingBlock ?? false;\r\n\t}\r\n\r\n\tsetHideThinkingBlock(hide: boolean): void {\r\n\t\tthis.globalSettings.hideThinkingBlock = hide;\r\n\t\tthis.markModified(\"hideThinkingBlock\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetShellPath(): string | undefined {\r\n\t\treturn this.settings.shellPath;\r\n\t}\r\n\r\n\tsetShellPath(path: string | undefined): void {\r\n\t\tthis.globalSettings.shellPath = path;\r\n\t\tthis.markModified(\"shellPath\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetQuietStartup(): boolean {\r\n\t\treturn this.settings.quietStartup ?? false;\r\n\t}\r\n\r\n\tsetQuietStartup(quiet: boolean): void {\r\n\t\tthis.globalSettings.quietStartup = quiet;\r\n\t\tthis.markModified(\"quietStartup\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetShellCommandPrefix(): string | undefined {\r\n\t\treturn this.settings.shellCommandPrefix;\r\n\t}\r\n\r\n\tsetShellCommandPrefix(prefix: string | undefined): void {\r\n\t\tthis.globalSettings.shellCommandPrefix = prefix;\r\n\t\tthis.markModified(\"shellCommandPrefix\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetCollapseChangelog(): boolean {\r\n\t\treturn this.settings.collapseChangelog ?? false;\r\n\t}\r\n\r\n\tsetCollapseChangelog(collapse: boolean): void {\r\n\t\tthis.globalSettings.collapseChangelog = collapse;\r\n\t\tthis.markModified(\"collapseChangelog\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetPackages(): PackageSource[] {\r\n\t\treturn [...(this.settings.packages ?? [])];\r\n\t}\r\n\r\n\tsetPackages(packages: PackageSource[]): void {\r\n\t\tthis.globalSettings.packages = packages;\r\n\t\tthis.markModified(\"packages\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tsetProjectPackages(packages: PackageSource[]): void {\r\n\t\tconst projectSettings = structuredClone(this.projectSettings);\r\n\t\tprojectSettings.packages = packages;\r\n\t\tthis.markProjectModified(\"packages\");\r\n\t\tthis.saveProjectSettings(projectSettings);\r\n\t}\r\n\r\n\tgetExtensionPaths(): string[] {\r\n\t\treturn [...(this.settings.extensions ?? [])];\r\n\t}\r\n\r\n\tsetExtensionPaths(paths: string[]): void {\r\n\t\tthis.globalSettings.extensions = paths;\r\n\t\tthis.markModified(\"extensions\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tsetProjectExtensionPaths(paths: string[]): void {\r\n\t\tconst projectSettings = structuredClone(this.projectSettings);\r\n\t\tprojectSettings.extensions = paths;\r\n\t\tthis.markProjectModified(\"extensions\");\r\n\t\tthis.saveProjectSettings(projectSettings);\r\n\t}\r\n\r\n\tgetSkillPaths(): string[] {\r\n\t\treturn [...(this.settings.skills ?? [])];\r\n\t}\r\n\r\n\tsetSkillPaths(paths: string[]): void {\r\n\t\tthis.globalSettings.skills = paths;\r\n\t\tthis.markModified(\"skills\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tsetProjectSkillPaths(paths: string[]): void {\r\n\t\tconst projectSettings = structuredClone(this.projectSettings);\r\n\t\tprojectSettings.skills = paths;\r\n\t\tthis.markProjectModified(\"skills\");\r\n\t\tthis.saveProjectSettings(projectSettings);\r\n\t}\r\n\r\n\tgetPromptTemplatePaths(): string[] {\r\n\t\treturn [...(this.settings.prompts ?? [])];\r\n\t}\r\n\r\n\tsetPromptTemplatePaths(paths: string[]): void {\r\n\t\tthis.globalSettings.prompts = paths;\r\n\t\tthis.markModified(\"prompts\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tsetProjectPromptTemplatePaths(paths: string[]): void {\r\n\t\tconst projectSettings = structuredClone(this.projectSettings);\r\n\t\tprojectSettings.prompts = paths;\r\n\t\tthis.markProjectModified(\"prompts\");\r\n\t\tthis.saveProjectSettings(projectSettings);\r\n\t}\r\n\r\n\tgetThemePaths(): string[] {\r\n\t\treturn [...(this.settings.themes ?? [])];\r\n\t}\r\n\r\n\tsetThemePaths(paths: string[]): void {\r\n\t\tthis.globalSettings.themes = paths;\r\n\t\tthis.markModified(\"themes\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tsetProjectThemePaths(paths: string[]): void {\r\n\t\tconst projectSettings = structuredClone(this.projectSettings);\r\n\t\tprojectSettings.themes = paths;\r\n\t\tthis.markProjectModified(\"themes\");\r\n\t\tthis.saveProjectSettings(projectSettings);\r\n\t}\r\n\r\n\tgetEnableSkillCommands(): boolean {\r\n\t\treturn this.settings.enableSkillCommands ?? true;\r\n\t}\r\n\r\n\tsetEnableSkillCommands(enabled: boolean): void {\r\n\t\tthis.globalSettings.enableSkillCommands = enabled;\r\n\t\tthis.markModified(\"enableSkillCommands\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetThinkingBudgets(): ThinkingBudgetsSettings | undefined {\r\n\t\treturn this.settings.thinkingBudgets;\r\n\t}\r\n\r\n\tgetShowImages(): boolean {\r\n\t\treturn this.settings.terminal?.showImages ?? true;\r\n\t}\r\n\r\n\tsetShowImages(show: boolean): void {\r\n\t\tif (!this.globalSettings.terminal) {\r\n\t\t\tthis.globalSettings.terminal = {};\r\n\t\t}\r\n\t\tthis.globalSettings.terminal.showImages = show;\r\n\t\tthis.markModified(\"terminal\", \"showImages\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetClearOnShrink(): boolean {\r\n\t\t// Settings takes precedence, then env var, then default false\r\n\t\tif (this.settings.terminal?.clearOnShrink !== undefined) {\r\n\t\t\treturn this.settings.terminal.clearOnShrink;\r\n\t\t}\r\n\t\treturn process.env.PI_CLEAR_ON_SHRINK === \"1\";\r\n\t}\r\n\r\n\tsetClearOnShrink(enabled: boolean): void {\r\n\t\tif (!this.globalSettings.terminal) {\r\n\t\t\tthis.globalSettings.terminal = {};\r\n\t\t}\r\n\t\tthis.globalSettings.terminal.clearOnShrink = enabled;\r\n\t\tthis.markModified(\"terminal\", \"clearOnShrink\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetImageAutoResize(): boolean {\r\n\t\treturn this.settings.images?.autoResize ?? true;\r\n\t}\r\n\r\n\tsetImageAutoResize(enabled: boolean): void {\r\n\t\tif (!this.globalSettings.images) {\r\n\t\t\tthis.globalSettings.images = {};\r\n\t\t}\r\n\t\tthis.globalSettings.images.autoResize = enabled;\r\n\t\tthis.markModified(\"images\", \"autoResize\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetBlockImages(): boolean {\r\n\t\treturn this.settings.images?.blockImages ?? false;\r\n\t}\r\n\r\n\tsetBlockImages(blocked: boolean): void {\r\n\t\tif (!this.globalSettings.images) {\r\n\t\t\tthis.globalSettings.images = {};\r\n\t\t}\r\n\t\tthis.globalSettings.images.blockImages = blocked;\r\n\t\tthis.markModified(\"images\", \"blockImages\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetEnabledModels(): string[] | undefined {\r\n\t\treturn this.settings.enabledModels;\r\n\t}\r\n\r\n\tsetEnabledModels(patterns: string[] | undefined): void {\r\n\t\tthis.globalSettings.enabledModels = patterns;\r\n\t\tthis.markModified(\"enabledModels\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetDoubleEscapeAction(): \"fork\" | \"tree\" | \"none\" {\r\n\t\treturn this.settings.doubleEscapeAction ?? \"tree\";\r\n\t}\r\n\r\n\tsetDoubleEscapeAction(action: \"fork\" | \"tree\" | \"none\"): void {\r\n\t\tthis.globalSettings.doubleEscapeAction = action;\r\n\t\tthis.markModified(\"doubleEscapeAction\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetShowHardwareCursor(): boolean {\r\n\t\treturn this.settings.showHardwareCursor ?? process.env.PI_HARDWARE_CURSOR === \"1\";\r\n\t}\r\n\r\n\tsetShowHardwareCursor(enabled: boolean): void {\r\n\t\tthis.globalSettings.showHardwareCursor = enabled;\r\n\t\tthis.markModified(\"showHardwareCursor\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetEditorPaddingX(): number {\r\n\t\treturn this.settings.editorPaddingX ?? 0;\r\n\t}\r\n\r\n\tsetEditorPaddingX(padding: number): void {\r\n\t\tthis.globalSettings.editorPaddingX = Math.max(0, Math.min(3, Math.floor(padding)));\r\n\t\tthis.markModified(\"editorPaddingX\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetAutocompleteMaxVisible(): number {\r\n\t\treturn this.settings.autocompleteMaxVisible ?? 5;\r\n\t}\r\n\r\n\tsetAutocompleteMaxVisible(maxVisible: number): void {\r\n\t\tthis.globalSettings.autocompleteMaxVisible = Math.max(3, Math.min(20, Math.floor(maxVisible)));\r\n\t\tthis.markModified(\"autocompleteMaxVisible\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetCodeBlockIndent(): string {\r\n\t\treturn this.settings.markdown?.codeBlockIndent ?? \" \";\r\n\t}\r\n\r\n\tgetMemoryEnabled(): boolean {\r\n\t\treturn this.settings.memory?.enabled ?? true;\r\n\t}\r\n\r\n\tsetMemoryEnabled(enabled: boolean): void {\r\n\t\tif (!this.globalSettings.memory) {\r\n\t\t\tthis.globalSettings.memory = {};\r\n\t\t}\r\n\t\tthis.globalSettings.memory.enabled = enabled;\r\n\t\tthis.markModified(\"memory\", \"enabled\");\r\n\t\tthis.save();\r\n\t}\r\n\r\n\tgetMemoryScope(): \"project\" | \"global\" {\r\n\t\treturn this.settings.memory?.scope ?? \"global\";\r\n\t}\r\n\r\n\tsetMemoryScope(scope: \"project\" | \"global\"): void {\r\n\t\tif (!this.globalSettings.memory) {\r\n\t\t\tthis.globalSettings.memory = {};\r\n\t\t}\r\n\t\tthis.globalSettings.memory.scope = scope;\r\n\t\tthis.markModified(\"memory\", \"scope\");\r\n\t\tthis.save();\r\n\t}\r\n}\r\n"]}
|
|
@@ -652,5 +652,16 @@ export class SettingsManager {
|
|
|
652
652
|
this.markModified("memory", "enabled");
|
|
653
653
|
this.save();
|
|
654
654
|
}
|
|
655
|
+
getMemoryScope() {
|
|
656
|
+
return this.settings.memory?.scope ?? "global";
|
|
657
|
+
}
|
|
658
|
+
setMemoryScope(scope) {
|
|
659
|
+
if (!this.globalSettings.memory) {
|
|
660
|
+
this.globalSettings.memory = {};
|
|
661
|
+
}
|
|
662
|
+
this.globalSettings.memory.scope = scope;
|
|
663
|
+
this.markModified("memory", "scope");
|
|
664
|
+
this.save();
|
|
665
|
+
}
|
|
655
666
|
}
|
|
656
667
|
//# sourceMappingURL=settings-manager.js.map
|