pi-automem-bridge 0.2.0 → 0.2.2

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/src/config.ts CHANGED
@@ -1,251 +1,262 @@
1
- /**
2
- * config.ts - Config loading, defaults, validation, and env-var resolution.
3
- */
4
-
5
- import { readFileSync, existsSync } from "node:fs";
6
- import { homedir } from "node:os";
7
- import { resolve } from "node:path";
8
-
9
- // ---------------------------------------------------------------------------
10
- // Types
11
- // ---------------------------------------------------------------------------
12
-
13
- export type MemoryType =
14
- | "Decision"
15
- | "Pattern"
16
- | "Preference"
17
- | "Style"
18
- | "Habit"
19
- | "Insight"
20
- | "Context";
21
-
22
- export type RelationshipType =
23
- | "RELATES_TO"
24
- | "LEADS_TO"
25
- | "OCCURRED_BEFORE"
26
- | "PREFERS_OVER"
27
- | "EXEMPLIFIES"
28
- | "CONTRADICTS"
29
- | "REINFORCES"
30
- | "INVALIDATED_BY"
31
- | "EVOLVED_INTO"
32
- | "DERIVED_FROM"
33
- | "PART_OF";
34
-
35
- export interface ProjectRecallOverride {
36
- limit?: number;
37
- maxBytes?: number;
38
- contextTypes?: MemoryType[];
39
- expandRelations?: boolean;
40
- expandEntities?: boolean;
41
- }
42
-
43
- export interface AutoMemConfig {
44
- mcpServerName: string;
45
- startupRecall: {
46
- enabled: boolean;
47
- queries: string[];
48
- tags: string[];
49
- tagMode: "any" | "all";
50
- limit: number;
51
- maxBytes: number;
52
- showStatus: boolean;
53
- };
54
- turnRecall: {
55
- enabled: boolean;
56
- limit: number;
57
- maxBytes: number;
58
- contextTypes: MemoryType[];
59
- expandRelations: boolean;
60
- expandEntities: boolean;
61
- };
62
- projectDetection: {
63
- enabled: boolean;
64
- tagPrefix: string;
65
- folderTags: Record<string, string[]>;
66
- gitRepoToTag: Record<string, string>;
67
- };
68
- projectOverrides?: Record<string, ProjectRecallOverride>;
69
- writePolicy: {
70
- mode: "off" | "propose" | "safe-auto" | "confirm-all";
71
- autoWriteCategories: string[];
72
- confirmCategories: string[];
73
- blockedCategories: string[];
74
- defaultSource: string;
75
- machineTag: boolean;
76
- alwaysTag: string[];
77
- minImportanceToWrite: number;
78
- dedupeBeforeWrite: boolean;
79
- dedupeLimit: number;
80
- };
81
- behavior: {
82
- injectSystemPrompt: boolean;
83
- displayRecall: "full" | "summary" | "hidden";
84
- maxContentLength: number;
85
- preferredContentLength: number;
86
- };
87
- viewer: {
88
- enabled: boolean;
89
- mode: "standalone" | "embedded";
90
- port: number;
91
- };
92
- }
93
-
94
- // ---------------------------------------------------------------------------
95
- // Defaults
96
- // ---------------------------------------------------------------------------
97
-
98
- export const DEFAULT_CONFIG: AutoMemConfig = {
99
- mcpServerName: "automem",
100
- startupRecall: {
101
- enabled: true,
102
- queries: ["user preferences working style environment"],
103
- tags: [],
104
- tagMode: "any",
105
- limit: 8,
106
- maxBytes: 6000,
107
- showStatus: true,
108
- },
109
- turnRecall: {
110
- enabled: true,
111
- limit: 6,
112
- maxBytes: 4000,
113
- contextTypes: ["Preference", "Decision", "Pattern", "Insight", "Context"],
114
- expandRelations: true,
115
- expandEntities: true,
116
- },
117
- projectDetection: {
118
- enabled: true,
119
- tagPrefix: "project:",
120
- folderTags: {},
121
- gitRepoToTag: {},
122
- },
123
- writePolicy: {
124
- mode: "safe-auto",
125
- autoWriteCategories: ["technical-decision", "agent-pattern", "bug-fix", "tooling-lesson"],
126
- confirmCategories: ["personal", "financial", "private", "identity"],
127
- blockedCategories: ["secret", "credential", "api-key", "raw-transcript"],
128
- defaultSource: "pi-session",
129
- machineTag: true,
130
- alwaysTag: ["source:pi"],
131
- minImportanceToWrite: 0.7,
132
- dedupeBeforeWrite: true,
133
- dedupeLimit: 3,
134
- },
135
- behavior: {
136
- injectSystemPrompt: true,
137
- displayRecall: "summary",
138
- maxContentLength: 2000,
139
- preferredContentLength: 500,
140
- },
141
- viewer: {
142
- enabled: false,
143
- mode: "standalone",
144
- port: 3000,
145
- },
146
- };
147
-
148
- // ---------------------------------------------------------------------------
149
- // Config path resolution
150
- // ---------------------------------------------------------------------------
151
-
152
- export function resolveConfigPath(): string {
153
- const envPath = process.env.AUTOMEM_CONFIG_PATH;
154
- if (envPath) return resolve(envPath);
155
- return resolve(homedir(), ".pi", "agent", "automem.json");
156
- }
157
-
158
- // ---------------------------------------------------------------------------
159
- // Env-var interpolation
160
- // ---------------------------------------------------------------------------
161
-
162
- export function resolveEnvVars(value: string): string {
163
- return value.replace(/\$\{([^}]+)\}/g, function(_match: string, name: string) {
164
- const v = process.env[name];
165
- if (v === undefined) {
166
- console.warn('[automem] env var "' + name + '" referenced in config but not set');
167
- return "";
168
- }
169
- return v;
170
- });
171
- }
172
-
173
- // ---------------------------------------------------------------------------
174
- // Deep merge
175
- // ---------------------------------------------------------------------------
176
-
177
- function deepMerge(base: any, override: any): any {
178
- const result = Object.assign({}, base);
179
- const keys = Object.keys(override);
180
- for (let i = 0; i < keys.length; i++) {
181
- const key = keys[i];
182
- const bVal = (base as any)[key];
183
- const oVal = override[key];
184
- if (
185
- oVal !== undefined &&
186
- typeof oVal === "object" &&
187
- oVal !== null &&
188
- !Array.isArray(oVal) &&
189
- typeof bVal === "object" &&
190
- bVal !== null &&
191
- !Array.isArray(bVal)
192
- ) {
193
- (result as any)[key] = deepMerge(bVal, oVal);
194
- } else if (oVal !== undefined) {
195
- (result as any)[key] = oVal;
196
- }
197
- }
198
- return result;
199
- }
200
-
201
- // ---------------------------------------------------------------------------
202
- // Load + validate
203
- // ---------------------------------------------------------------------------
204
-
205
- export function loadConfig(): AutoMemConfig {
206
- const configPath = resolveConfigPath();
207
-
208
- if (!existsSync(configPath)) {
209
- console.log("[automem] no config at " + configPath + ", using defaults");
210
- return DEFAULT_CONFIG;
211
- }
212
-
213
- let raw: any;
214
- try {
215
- const text = readFileSync(configPath, "utf8");
216
- raw = JSON.parse(text);
217
- } catch (err) {
218
- console.error("[automem] failed to read/parse config: " + err);
219
- return DEFAULT_CONFIG;
220
- }
221
-
222
- if (typeof raw !== "object" || raw === null) {
223
- console.error("[automem] config root must be an object, using defaults");
224
- return DEFAULT_CONFIG;
225
- }
226
-
227
- const config = deepMerge(DEFAULT_CONFIG, raw) as AutoMemConfig;
228
-
229
- if (config.startupRecall.limit < 1 || config.startupRecall.limit > 20) {
230
- console.warn("[automem] startupRecall.limit out of range (1-20), clamping to 8");
231
- config.startupRecall.limit = 8;
232
- }
233
- if (config.turnRecall.limit < 1 || config.turnRecall.limit > 20) {
234
- console.warn("[automem] turnRecall.limit out of range (1-20), clamping to 6");
235
- config.turnRecall.limit = 6;
236
- }
237
-
238
- const validDisplayModes = ["full", "summary", "hidden"];
239
- if (!validDisplayModes.includes(config.behavior.displayRecall)) {
240
- console.warn("[automem] unknown behavior.displayRecall \"" + config.behavior.displayRecall + "\", valid values: full, summary, hidden. Defaulting to \"summary\"");
241
- config.behavior.displayRecall = "summary";
242
- }
243
-
244
- const validWriteModes = ["off", "propose", "safe-auto", "confirm-all"];
245
- if (!validWriteModes.includes(config.writePolicy.mode)) {
246
- console.warn("[automem] unknown writePolicy.mode \"" + config.writePolicy.mode + "\", valid values: off, propose, safe-auto, confirm-all. Defaulting to \"propose\"");
247
- config.writePolicy.mode = "propose";
248
- }
249
-
250
- return config;
251
- }
1
+ /**
2
+ * config.ts - Config loading, defaults, validation, and env-var resolution.
3
+ */
4
+
5
+ import { readFileSync, existsSync } from "node:fs";
6
+ import { homedir } from "node:os";
7
+ import { resolve } from "node:path";
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // Types
11
+ // ---------------------------------------------------------------------------
12
+
13
+ export type MemoryType =
14
+ | "Decision"
15
+ | "Pattern"
16
+ | "Preference"
17
+ | "Style"
18
+ | "Habit"
19
+ | "Insight"
20
+ | "Context";
21
+
22
+ export type RelationshipType =
23
+ | "RELATES_TO"
24
+ | "LEADS_TO"
25
+ | "OCCURRED_BEFORE"
26
+ | "PREFERS_OVER"
27
+ | "EXEMPLIFIES"
28
+ | "CONTRADICTS"
29
+ | "REINFORCES"
30
+ | "INVALIDATED_BY"
31
+ | "EVOLVED_INTO"
32
+ | "DERIVED_FROM"
33
+ | "PART_OF";
34
+
35
+ export interface ProjectRecallOverride {
36
+ limit?: number;
37
+ maxBytes?: number;
38
+ contextTypes?: MemoryType[];
39
+ expandRelations?: boolean;
40
+ expandEntities?: boolean;
41
+ }
42
+
43
+ export interface AutoMemConfig {
44
+ mcpServerName: string;
45
+ startupRecall: {
46
+ enabled: boolean;
47
+ queries: string[];
48
+ tags: string[];
49
+ tagMode: "any" | "all";
50
+ limit: number;
51
+ maxBytes: number;
52
+ showStatus: boolean;
53
+ timeoutMs: number;
54
+ };
55
+ turnRecall: {
56
+ enabled: boolean;
57
+ limit: number;
58
+ maxBytes: number;
59
+ contextTypes: MemoryType[];
60
+ expandRelations: boolean;
61
+ expandEntities: boolean;
62
+ timeoutMs: number;
63
+ };
64
+ projectDetection: {
65
+ enabled: boolean;
66
+ tagPrefix: string;
67
+ folderTags: Record<string, string[]>;
68
+ gitRepoToTag: Record<string, string>;
69
+ };
70
+ projectOverrides?: Record<string, ProjectRecallOverride>;
71
+ writePolicy: {
72
+ mode: "off" | "propose" | "safe-auto" | "confirm-all";
73
+ autoWriteCategories: string[];
74
+ confirmCategories: string[];
75
+ blockedCategories: string[];
76
+ defaultSource: string;
77
+ machineTag: boolean;
78
+ alwaysTag: string[];
79
+ minImportanceToWrite: number;
80
+ dedupeBeforeWrite: boolean;
81
+ dedupeLimit: number;
82
+ };
83
+ behavior: {
84
+ injectSystemPrompt: boolean;
85
+ displayRecall: "full" | "summary" | "hidden";
86
+ maxContentLength: number;
87
+ preferredContentLength: number;
88
+ };
89
+ viewer: {
90
+ enabled: boolean;
91
+ mode: "standalone" | "embedded";
92
+ port: number;
93
+ };
94
+ }
95
+
96
+ // ---------------------------------------------------------------------------
97
+ // Defaults
98
+ // ---------------------------------------------------------------------------
99
+
100
+ export const DEFAULT_CONFIG: AutoMemConfig = {
101
+ mcpServerName: "automem",
102
+ startupRecall: {
103
+ enabled: true,
104
+ queries: ["user preferences working style environment"],
105
+ tags: [],
106
+ tagMode: "any",
107
+ limit: 8,
108
+ maxBytes: 6000,
109
+ showStatus: true,
110
+ // Bound startup recall so an unreachable sidecar can't stall session start
111
+ // for the full 30s MCP timeout per query.
112
+ timeoutMs: 15000,
113
+ },
114
+ turnRecall: {
115
+ enabled: true,
116
+ limit: 6,
117
+ maxBytes: 4000,
118
+ contextTypes: ["Preference", "Decision", "Pattern", "Insight", "Context"],
119
+ expandRelations: true,
120
+ expandEntities: true,
121
+ // Turn recall is best-effort enrichment on the prompt hot path; bound it
122
+ // tightly so a slow/stalled sidecar can't block every prompt for the full
123
+ // 30s MCP timeout. Recall failure degrades gracefully to no injection.
124
+ timeoutMs: 8000,
125
+ },
126
+ projectDetection: {
127
+ enabled: true,
128
+ tagPrefix: "project:",
129
+ folderTags: {},
130
+ gitRepoToTag: {},
131
+ },
132
+ writePolicy: {
133
+ mode: "safe-auto",
134
+ autoWriteCategories: ["technical-decision", "agent-pattern", "bug-fix", "tooling-lesson"],
135
+ confirmCategories: ["personal", "financial", "private", "identity"],
136
+ blockedCategories: ["secret", "credential", "api-key", "raw-transcript"],
137
+ defaultSource: "pi-session",
138
+ machineTag: true,
139
+ alwaysTag: ["source:pi"],
140
+ minImportanceToWrite: 0.7,
141
+ dedupeBeforeWrite: true,
142
+ dedupeLimit: 3,
143
+ },
144
+ behavior: {
145
+ injectSystemPrompt: true,
146
+ displayRecall: "summary",
147
+ maxContentLength: 2000,
148
+ preferredContentLength: 500,
149
+ },
150
+ viewer: {
151
+ enabled: false,
152
+ mode: "standalone",
153
+ port: 3000,
154
+ },
155
+ };
156
+
157
+ // ---------------------------------------------------------------------------
158
+ // Config path resolution
159
+ // ---------------------------------------------------------------------------
160
+
161
+ export function resolveConfigPath(): string {
162
+ const envPath = process.env.AUTOMEM_CONFIG_PATH;
163
+ if (envPath) return resolve(envPath);
164
+ return resolve(homedir(), ".pi", "agent", "automem.json");
165
+ }
166
+
167
+ // ---------------------------------------------------------------------------
168
+ // Env-var interpolation
169
+ // ---------------------------------------------------------------------------
170
+
171
+ export function resolveEnvVars(value: string): string {
172
+ return value.replace(/\$\{([^}]+)\}/g, function(_match: string, name: string) {
173
+ const v = process.env[name];
174
+ if (v === undefined) {
175
+ console.warn('[automem] env var "' + name + '" referenced in config but not set');
176
+ return "";
177
+ }
178
+ return v;
179
+ });
180
+ }
181
+
182
+ // ---------------------------------------------------------------------------
183
+ // Deep merge
184
+ // ---------------------------------------------------------------------------
185
+
186
+ function deepMerge(base: any, override: any): any {
187
+ const result = Object.assign({}, base);
188
+ const keys = Object.keys(override);
189
+ for (let i = 0; i < keys.length; i++) {
190
+ const key = keys[i];
191
+ // Never merge prototype-mutating keys from an untrusted config file.
192
+ if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
193
+ const bVal = (base as any)[key];
194
+ const oVal = override[key];
195
+ if (
196
+ oVal !== undefined &&
197
+ typeof oVal === "object" &&
198
+ oVal !== null &&
199
+ !Array.isArray(oVal) &&
200
+ typeof bVal === "object" &&
201
+ bVal !== null &&
202
+ !Array.isArray(bVal)
203
+ ) {
204
+ (result as any)[key] = deepMerge(bVal, oVal);
205
+ } else if (oVal !== undefined) {
206
+ (result as any)[key] = oVal;
207
+ }
208
+ }
209
+ return result;
210
+ }
211
+
212
+ // ---------------------------------------------------------------------------
213
+ // Load + validate
214
+ // ---------------------------------------------------------------------------
215
+
216
+ export function loadConfig(): AutoMemConfig {
217
+ const configPath = resolveConfigPath();
218
+
219
+ if (!existsSync(configPath)) {
220
+ console.log("[automem] no config at " + configPath + ", using defaults");
221
+ return DEFAULT_CONFIG;
222
+ }
223
+
224
+ let raw: any;
225
+ try {
226
+ const text = readFileSync(configPath, "utf8");
227
+ raw = JSON.parse(text);
228
+ } catch (err) {
229
+ console.error("[automem] failed to read/parse config: " + err);
230
+ return DEFAULT_CONFIG;
231
+ }
232
+
233
+ if (typeof raw !== "object" || raw === null) {
234
+ console.error("[automem] config root must be an object, using defaults");
235
+ return DEFAULT_CONFIG;
236
+ }
237
+
238
+ const config = deepMerge(DEFAULT_CONFIG, raw) as AutoMemConfig;
239
+
240
+ if (config.startupRecall.limit < 1 || config.startupRecall.limit > 20) {
241
+ console.warn("[automem] startupRecall.limit out of range (1-20), clamping to 8");
242
+ config.startupRecall.limit = 8;
243
+ }
244
+ if (config.turnRecall.limit < 1 || config.turnRecall.limit > 20) {
245
+ console.warn("[automem] turnRecall.limit out of range (1-20), clamping to 6");
246
+ config.turnRecall.limit = 6;
247
+ }
248
+
249
+ const validDisplayModes = ["full", "summary", "hidden"];
250
+ if (!validDisplayModes.includes(config.behavior.displayRecall)) {
251
+ console.warn("[automem] unknown behavior.displayRecall \"" + config.behavior.displayRecall + "\", valid values: full, summary, hidden. Defaulting to \"summary\"");
252
+ config.behavior.displayRecall = "summary";
253
+ }
254
+
255
+ const validWriteModes = ["off", "propose", "safe-auto", "confirm-all"];
256
+ if (!validWriteModes.includes(config.writePolicy.mode)) {
257
+ console.warn("[automem] unknown writePolicy.mode \"" + config.writePolicy.mode + "\", valid values: off, propose, safe-auto, confirm-all. Defaulting to \"propose\"");
258
+ config.writePolicy.mode = "propose";
259
+ }
260
+
261
+ return config;
262
+ }