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/README.md +75 -29
- package/examples/config.advanced.json +61 -59
- package/package.json +61 -58
- package/src/config.ts +262 -251
- package/src/mcp-client.ts +401 -361
- package/src/project-detect.ts +96 -94
- package/src/recall.ts +283 -254
- package/src/tools/memory-tools.ts +307 -307
- package/src/tools/relationship-tools.ts +121 -114
- package/src/write-policy.ts +148 -142
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
// ---------------------------------------------------------------------------
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
if (
|
|
234
|
-
console.
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
+
}
|