@kinqs/brainrouter-mcp-server 0.3.5 → 0.3.7
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/.env.example +121 -71
- package/README.md +1 -1
- package/dist/__tests__/cognitive-extractor.test.js +112 -0
- package/dist/__tests__/crypto.test.js +8 -1
- package/dist/__tests__/working-memory.test.js +67 -0
- package/dist/api/auth/crypto.js +8 -3
- package/dist/index.js +1 -1
- package/dist/memory/engine.js +21 -1
- package/dist/memory/pipeline/cognitive-extractor.js +19 -1
- package/dist/memory/recall.d.ts +3 -1
- package/dist/memory/recall.js +48 -3
- package/dist/memory/store/relevance-judge.d.ts +51 -0
- package/dist/memory/store/relevance-judge.js +196 -0
- package/dist/memory/working/canvas.js +11 -0
- package/docs/specs/0.3.7-terminal-ui-redesign.md +259 -0
- package/package.json +2 -2
- package/dist/memory/config.d.ts +0 -2
- package/dist/memory/config.js +0 -3
- package/dist/memory/pipeline/l1-contradiction.d.ts +0 -7
- package/dist/memory/pipeline/l1-contradiction.js +0 -66
- package/dist/memory/pipeline/l1-dedup.d.ts +0 -23
- package/dist/memory/pipeline/l1-dedup.js +0 -39
- package/dist/memory/pipeline/l1-extractor.d.ts +0 -21
- package/dist/memory/pipeline/l1-extractor.js +0 -180
- package/dist/memory/pipeline/l2-direction-shift.d.ts +0 -10
- package/dist/memory/pipeline/l2-direction-shift.js +0 -27
- package/dist/memory/pipeline/l2-scene.d.ts +0 -15
- package/dist/memory/pipeline/l2-scene.js +0 -140
- package/dist/memory/pipeline/l3-distiller.d.ts +0 -15
- package/dist/memory/pipeline/l3-distiller.js +0 -40
- package/dist/memory/pipeline/task-queue.d.ts +0 -54
- package/dist/memory/pipeline/task-queue.js +0 -117
- package/dist/memory/prompts/graph-extraction-batch.d.ts +0 -14
- package/dist/memory/prompts/graph-extraction-batch.js +0 -54
- package/dist/memory/prompts/l1-contradiction-batch.d.ts +0 -16
- package/dist/memory/prompts/l1-contradiction-batch.js +0 -47
- package/dist/memory/prompts/l1-contradiction.d.ts +0 -1
- package/dist/memory/prompts/l1-contradiction.js +0 -25
- package/dist/memory/prompts/l1-extraction.d.ts +0 -10
- package/dist/memory/prompts/l1-extraction.js +0 -114
- package/dist/memory/prompts/l2-direction-shift.d.ts +0 -5
- package/dist/memory/prompts/l2-direction-shift.js +0 -32
- package/dist/memory/prompts/l2-scene-cluster.d.ts +0 -2
- package/dist/memory/prompts/l2-scene-cluster.js +0 -33
- package/dist/memory/prompts/l2-scene.d.ts +0 -7
- package/dist/memory/prompts/l2-scene.js +0 -40
- package/dist/memory/prompts/l3-persona.d.ts +0 -6
- package/dist/memory/prompts/l3-persona.js +0 -60
- package/dist/memory/store/types.d.ts +0 -101
- package/dist/memory/store/types.js +0 -1
- package/dist/memory/types.d.ts +0 -207
- package/dist/memory/types.js +0 -7
- package/dist/memory/validation.d.ts +0 -441
- package/dist/memory/validation.js +0 -129
- package/dist/tools/agent_memory_tools.d.ts +0 -485
- package/dist/tools/agent_memory_tools.js +0 -793
- package/dist/tools/get_doc.d.ts +0 -21
- package/dist/tools/get_doc.js +0 -24
- package/dist/tools/list_docs.d.ts +0 -15
- package/dist/tools/list_docs.js +0 -16
- package/dist/tools/update_doc.d.ts +0 -24
- package/dist/tools/update_doc.js +0 -35
- /package/dist/__tests__/{agent_mode.test.d.ts → cognitive-extractor.test.d.ts} +0 -0
|
@@ -1,793 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import crypto from "node:crypto";
|
|
3
|
-
import { memoryEngine } from "../memory/engine.js";
|
|
4
|
-
import { L1CommitPayloadSchema, ContradictionDecisionCommitSchema, GraphCommitSchema, L2CommitSchema, L3CommitSchema, normalizeSceneName, normalizeSkillTag, normalizeMemoryContent, normalizeEntityName, validateL0RecordOwnership, validateL1RecordOwnership, } from "../memory/validation.js";
|
|
5
|
-
import { EXTRACT_MEMORIES_SYSTEM_PROMPT, formatExtractionPrompt } from "../memory/prompts/l1-extraction.js";
|
|
6
|
-
import { L1_CONTRADICTION_PROMPT } from "../memory/prompts/l1-contradiction.js";
|
|
7
|
-
import { GRAPH_EXTRACTION_SYSTEM_PROMPT } from "../memory/prompts/graph-extraction.js";
|
|
8
|
-
import { L2_SCENE_SYSTEM_PROMPT } from "../memory/prompts/l2-scene.js";
|
|
9
|
-
import { L3_PERSONA_SYSTEM_PROMPT, formatL3PersonaPrompt } from "../memory/prompts/l3-persona.js";
|
|
10
|
-
import { deduplicateMemories } from "../memory/pipeline/l1-dedup.js";
|
|
11
|
-
import { L2_MAX_SCENES } from "../memory/scheduler.js";
|
|
12
|
-
// ============================
|
|
13
|
-
// Schemas for MCP Tool Registration
|
|
14
|
-
// ============================
|
|
15
|
-
export const memoryGetExtractionWorkSchema = {
|
|
16
|
-
name: "memory_get_extraction_work",
|
|
17
|
-
description: "Get pending conversational L0 messages that need to be distilled into L1 memories.",
|
|
18
|
-
inputSchema: {
|
|
19
|
-
type: "object",
|
|
20
|
-
properties: {
|
|
21
|
-
userId: { type: "string", description: "The ID of the user." },
|
|
22
|
-
sessionKey: { type: "string", description: "The session key." },
|
|
23
|
-
limit: { type: "number", description: "Maximum number of L0 messages to retrieve.", default: 10 },
|
|
24
|
-
activeSkill: { type: "string", description: "The currently active skill." }
|
|
25
|
-
},
|
|
26
|
-
required: ["userId", "sessionKey"]
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
export const memoryCommitExtractedL1Schema = {
|
|
30
|
-
name: "memory_commit_extracted_l1",
|
|
31
|
-
description: "Commit newly extracted L1 memories for a session, automatically generating vector embeddings and running deduplication.",
|
|
32
|
-
inputSchema: {
|
|
33
|
-
type: "object",
|
|
34
|
-
properties: {
|
|
35
|
-
userId: { type: "string" },
|
|
36
|
-
sessionKey: { type: "string" },
|
|
37
|
-
sessionId: { type: "string", default: "" },
|
|
38
|
-
sourceL0Ids: { type: "array", items: { type: "string" } },
|
|
39
|
-
scenes: {
|
|
40
|
-
type: "array",
|
|
41
|
-
items: {
|
|
42
|
-
type: "object",
|
|
43
|
-
properties: {
|
|
44
|
-
sceneName: { type: "string" },
|
|
45
|
-
memories: {
|
|
46
|
-
type: "array",
|
|
47
|
-
items: {
|
|
48
|
-
type: "object",
|
|
49
|
-
properties: {
|
|
50
|
-
content: { type: "string" },
|
|
51
|
-
type: { type: "string", enum: ["persona", "episodic", "instruction", "skill_context"] },
|
|
52
|
-
priority: { type: "number", minimum: 0, maximum: 100, default: 50 },
|
|
53
|
-
skillTag: { type: "string", default: "" },
|
|
54
|
-
metadata: { type: "object", default: {} }
|
|
55
|
-
},
|
|
56
|
-
required: ["content", "type"]
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
required: ["sceneName", "memories"]
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
required: ["userId", "sessionKey", "scenes"]
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
export const memoryGetContradictionWorkSchema = {
|
|
68
|
-
name: "memory_get_contradiction_work",
|
|
69
|
-
description: "Get pending L1 memories that need to be evaluated for semantic contradictions or temporal updates against existing memories.",
|
|
70
|
-
inputSchema: {
|
|
71
|
-
type: "object",
|
|
72
|
-
properties: {
|
|
73
|
-
userId: { type: "string" },
|
|
74
|
-
limit: { type: "number", default: 20 }
|
|
75
|
-
},
|
|
76
|
-
required: ["userId"]
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
export const memoryCommitContradictionDecisionsSchema = {
|
|
80
|
-
name: "memory_commit_contradiction_decisions",
|
|
81
|
-
description: "Commit decisions on contradiction candidates, invalidating/superseding old records or logging genuine conflicts.",
|
|
82
|
-
inputSchema: {
|
|
83
|
-
type: "object",
|
|
84
|
-
properties: {
|
|
85
|
-
userId: { type: "string" },
|
|
86
|
-
decisions: {
|
|
87
|
-
type: "array",
|
|
88
|
-
items: {
|
|
89
|
-
type: "object",
|
|
90
|
-
properties: {
|
|
91
|
-
newRecordId: { type: "string" },
|
|
92
|
-
existingRecordId: { type: "string" },
|
|
93
|
-
decision: { type: "string", enum: ["no_conflict", "temporal_update", "genuine_conflict"] },
|
|
94
|
-
reason: { type: "string" },
|
|
95
|
-
confidence: { type: "number", minimum: 0, maximum: 1, default: 1.0 }
|
|
96
|
-
},
|
|
97
|
-
required: ["newRecordId", "existingRecordId", "decision"]
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
required: ["userId", "decisions"]
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
export const memoryGetGraphWorkSchema = {
|
|
105
|
-
name: "memory_get_graph_work",
|
|
106
|
-
description: "Get pending L1 memories that have not yet had GraphRAG entities and relationships extracted from them.",
|
|
107
|
-
inputSchema: {
|
|
108
|
-
type: "object",
|
|
109
|
-
properties: {
|
|
110
|
-
userId: { type: "string" },
|
|
111
|
-
limit: { type: "number", default: 20 }
|
|
112
|
-
},
|
|
113
|
-
required: ["userId"]
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
export const memoryCommitGraphSchema = {
|
|
117
|
-
name: "memory_commit_graph",
|
|
118
|
-
description: "Commit entities and relations extracted from an L1 memory, updating the GraphRAG knowledge graph.",
|
|
119
|
-
inputSchema: {
|
|
120
|
-
type: "object",
|
|
121
|
-
properties: {
|
|
122
|
-
userId: { type: "string" },
|
|
123
|
-
sourceRecordId: { type: "string" },
|
|
124
|
-
entities: {
|
|
125
|
-
type: "array",
|
|
126
|
-
items: {
|
|
127
|
-
type: "object",
|
|
128
|
-
properties: {
|
|
129
|
-
entity: { type: "string" },
|
|
130
|
-
type: { type: "string", default: "concept" },
|
|
131
|
-
confidence: { type: "number", minimum: 0, maximum: 1, default: 1.0 },
|
|
132
|
-
skillTag: { type: "string", default: "" },
|
|
133
|
-
sourceRecordId: { type: "string" }
|
|
134
|
-
},
|
|
135
|
-
required: ["entity"]
|
|
136
|
-
}
|
|
137
|
-
},
|
|
138
|
-
relations: {
|
|
139
|
-
type: "array",
|
|
140
|
-
items: {
|
|
141
|
-
type: "object",
|
|
142
|
-
properties: {
|
|
143
|
-
from: { type: "string" },
|
|
144
|
-
to: { type: "string" },
|
|
145
|
-
relation: { type: "string", default: "relates_to" },
|
|
146
|
-
confidence: { type: "number", minimum: 0, maximum: 1, default: 1.0 },
|
|
147
|
-
skillTag: { type: "string", default: "" },
|
|
148
|
-
sourceRecordId: { type: "string" }
|
|
149
|
-
},
|
|
150
|
-
required: ["from", "to"]
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
},
|
|
154
|
-
required: ["userId"]
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
export const memoryGetL2WorkSchema = {
|
|
158
|
-
name: "memory_get_l2_work",
|
|
159
|
-
description: "Get L1 memories grouped by scenes to perform scene narrative summary distillation or cold scene merges.",
|
|
160
|
-
inputSchema: {
|
|
161
|
-
type: "object",
|
|
162
|
-
properties: {
|
|
163
|
-
userId: { type: "string" }
|
|
164
|
-
},
|
|
165
|
-
required: ["userId"]
|
|
166
|
-
}
|
|
167
|
-
};
|
|
168
|
-
export const memoryCommitL2Schema = {
|
|
169
|
-
name: "memory_commit_l2",
|
|
170
|
-
description: "Commit scene summaries, scene name renames (clustering), or cold scene merge distillations.",
|
|
171
|
-
inputSchema: {
|
|
172
|
-
type: "object",
|
|
173
|
-
properties: {
|
|
174
|
-
userId: { type: "string" },
|
|
175
|
-
renames: {
|
|
176
|
-
type: "array",
|
|
177
|
-
items: {
|
|
178
|
-
type: "object",
|
|
179
|
-
properties: {
|
|
180
|
-
oldName: { type: "string" },
|
|
181
|
-
newName: { type: "string" }
|
|
182
|
-
},
|
|
183
|
-
required: ["oldName", "newName"]
|
|
184
|
-
}
|
|
185
|
-
},
|
|
186
|
-
scenes: {
|
|
187
|
-
type: "array",
|
|
188
|
-
items: {
|
|
189
|
-
type: "object",
|
|
190
|
-
properties: {
|
|
191
|
-
sceneName: { type: "string" },
|
|
192
|
-
summaryMd: { type: "string" },
|
|
193
|
-
heatScore: { type: "number", minimum: 0, maximum: 100 }
|
|
194
|
-
},
|
|
195
|
-
required: ["sceneName", "summaryMd"]
|
|
196
|
-
}
|
|
197
|
-
},
|
|
198
|
-
merges: {
|
|
199
|
-
type: "array",
|
|
200
|
-
items: {
|
|
201
|
-
type: "object",
|
|
202
|
-
properties: {
|
|
203
|
-
sceneIds: { type: "array", items: { type: "string" } },
|
|
204
|
-
mergedSummaryMd: { type: "string" }
|
|
205
|
-
},
|
|
206
|
-
required: ["sceneIds", "mergedSummaryMd"]
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
},
|
|
210
|
-
required: ["userId"]
|
|
211
|
-
}
|
|
212
|
-
};
|
|
213
|
-
export const memoryGetL3WorkSchema = {
|
|
214
|
-
name: "memory_get_l3_work",
|
|
215
|
-
description: "Get L1 persona and instruction memories across all sessions to compile a synthesized L3 Narrative Profile.",
|
|
216
|
-
inputSchema: {
|
|
217
|
-
type: "object",
|
|
218
|
-
properties: {
|
|
219
|
-
userId: { type: "string" }
|
|
220
|
-
},
|
|
221
|
-
required: ["userId"]
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
export const memoryCommitL3Schema = {
|
|
225
|
-
name: "memory_commit_l3",
|
|
226
|
-
description: "Commit a synthesized L3 Narrative Profile summary, invalidating persona caches.",
|
|
227
|
-
inputSchema: {
|
|
228
|
-
type: "object",
|
|
229
|
-
properties: {
|
|
230
|
-
userId: { type: "string" },
|
|
231
|
-
personaMd: { type: "string" }
|
|
232
|
-
},
|
|
233
|
-
required: ["userId", "personaMd"]
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
// ============================
|
|
237
|
-
// Tool Dispatcher Handlers
|
|
238
|
-
// ============================
|
|
239
|
-
export async function handleMemoryGetExtractionWork(args) {
|
|
240
|
-
const params = z.object({
|
|
241
|
-
userId: z.string(),
|
|
242
|
-
sessionKey: z.string(),
|
|
243
|
-
limit: z.number().optional().default(10),
|
|
244
|
-
activeSkill: z.string().optional(),
|
|
245
|
-
}).parse(args);
|
|
246
|
-
const store = memoryEngine.store;
|
|
247
|
-
try {
|
|
248
|
-
const unextractedCount = store.getUnextractedL0Count(params.userId, params.sessionKey);
|
|
249
|
-
if (unextractedCount === 0) {
|
|
250
|
-
return {
|
|
251
|
-
content: [{
|
|
252
|
-
type: "text",
|
|
253
|
-
text: JSON.stringify({ status: "no_work", message: "No unextracted L0 messages in this session." }, null, 2)
|
|
254
|
-
}]
|
|
255
|
-
};
|
|
256
|
-
}
|
|
257
|
-
const messages = store.getRecentL0Messages(params.userId, params.sessionKey, params.limit, "", true);
|
|
258
|
-
const existingSceneNames = store.getTopL2Scenes(params.userId, 20).map((s) => s.sceneName);
|
|
259
|
-
const skillHints = params.activeSkill ? (store.getSkillHints(params.activeSkill) ?? undefined) : undefined;
|
|
260
|
-
// Build the instruction templates
|
|
261
|
-
const qualifiedMessages = messages.filter((m) => {
|
|
262
|
-
const clean = m.messageText.trim();
|
|
263
|
-
return clean.length >= 3 && !/^[^a-zA-Z\u4e00-\u9fa5]+$/.test(clean);
|
|
264
|
-
});
|
|
265
|
-
const userPrompt = formatExtractionPrompt({
|
|
266
|
-
newMessages: qualifiedMessages,
|
|
267
|
-
backgroundMessages: [],
|
|
268
|
-
previousSceneName: "None",
|
|
269
|
-
existingSceneNames,
|
|
270
|
-
activeSkill: params.activeSkill,
|
|
271
|
-
skillHints
|
|
272
|
-
});
|
|
273
|
-
return {
|
|
274
|
-
content: [{
|
|
275
|
-
type: "text",
|
|
276
|
-
text: JSON.stringify({
|
|
277
|
-
status: "work_available",
|
|
278
|
-
userId: params.userId,
|
|
279
|
-
sessionKey: params.sessionKey,
|
|
280
|
-
messages,
|
|
281
|
-
unextractedCount,
|
|
282
|
-
remainingUnextractedCount: Math.max(0, unextractedCount - messages.length),
|
|
283
|
-
existingSceneNames,
|
|
284
|
-
skillHints,
|
|
285
|
-
prompts: {
|
|
286
|
-
systemPrompt: EXTRACT_MEMORIES_SYSTEM_PROMPT,
|
|
287
|
-
userPrompt
|
|
288
|
-
}
|
|
289
|
-
}, null, 2)
|
|
290
|
-
}]
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
catch (err) {
|
|
294
|
-
return {
|
|
295
|
-
isError: true,
|
|
296
|
-
content: [{ type: "text", text: `Failed to retrieve extraction work: ${err.message}` }]
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
export async function handleMemoryCommitExtractedL1(args) {
|
|
301
|
-
const payload = L1CommitPayloadSchema.parse(args);
|
|
302
|
-
const store = memoryEngine.store;
|
|
303
|
-
const embeddingService = memoryEngine.capturePipeline.embeddingService;
|
|
304
|
-
try {
|
|
305
|
-
if (payload.sourceL0Ids.length > 0) {
|
|
306
|
-
validateL0RecordOwnership(store, payload.userId, payload.sourceL0Ids);
|
|
307
|
-
}
|
|
308
|
-
const records = [];
|
|
309
|
-
const now = new Date().toISOString();
|
|
310
|
-
for (const group of payload.scenes) {
|
|
311
|
-
const sceneName = normalizeSceneName(group.sceneName);
|
|
312
|
-
for (const mem of group.memories) {
|
|
313
|
-
records.push({
|
|
314
|
-
id: `l1_${payload.sessionKey}_${Date.now()}_${crypto.randomBytes(4).toString("hex")}`,
|
|
315
|
-
userId: payload.userId,
|
|
316
|
-
sessionKey: payload.sessionKey,
|
|
317
|
-
sessionId: payload.sessionId,
|
|
318
|
-
content: normalizeMemoryContent(mem.content),
|
|
319
|
-
type: mem.type,
|
|
320
|
-
priority: mem.priority,
|
|
321
|
-
sceneName,
|
|
322
|
-
skillTag: normalizeSkillTag(mem.skillTag),
|
|
323
|
-
halfLifeDays: mem.type === "instruction" ? null : (mem.type === "persona" ? 180 : (mem.type === "skill_context" ? 7 : 30)),
|
|
324
|
-
supersededBy: null,
|
|
325
|
-
timestampStr: "",
|
|
326
|
-
timestampStart: "",
|
|
327
|
-
timestampEnd: "",
|
|
328
|
-
createdTime: now,
|
|
329
|
-
updatedTime: now,
|
|
330
|
-
metadata: mem.metadata,
|
|
331
|
-
citationCount: 0,
|
|
332
|
-
lastCitedAt: null,
|
|
333
|
-
neverCitedCount: 0,
|
|
334
|
-
archived: false,
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
// Deduplication check
|
|
339
|
-
const { uniqueRecords, droppedCount } = await deduplicateMemories({
|
|
340
|
-
records,
|
|
341
|
-
store,
|
|
342
|
-
userId: payload.userId
|
|
343
|
-
});
|
|
344
|
-
const entries = [];
|
|
345
|
-
for (const record of uniqueRecords) {
|
|
346
|
-
let embedding;
|
|
347
|
-
if (embeddingService.isReady()) {
|
|
348
|
-
try {
|
|
349
|
-
embedding = await embeddingService.embed(record.content);
|
|
350
|
-
}
|
|
351
|
-
catch (err) {
|
|
352
|
-
console.error(`[BrainRouter] Embedding failed during commit for ${record.id}:`, err.message);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
entries.push({ record, embedding });
|
|
356
|
-
}
|
|
357
|
-
if (entries.length > 0) {
|
|
358
|
-
store.upsertL1Batch(entries);
|
|
359
|
-
store.incrementSchedulerL1Count(payload.userId, entries.length);
|
|
360
|
-
}
|
|
361
|
-
if (payload.sourceL0Ids.length > 0) {
|
|
362
|
-
store.markL0ExtractedByIds(payload.userId, payload.sourceL0Ids);
|
|
363
|
-
}
|
|
364
|
-
return {
|
|
365
|
-
content: [{
|
|
366
|
-
type: "text",
|
|
367
|
-
text: JSON.stringify({
|
|
368
|
-
committedCount: entries.length,
|
|
369
|
-
committedIds: uniqueRecords.map(r => r.id),
|
|
370
|
-
droppedDuplicateCount: droppedCount,
|
|
371
|
-
validationErrors: []
|
|
372
|
-
}, null, 2)
|
|
373
|
-
}]
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
catch (err) {
|
|
377
|
-
return {
|
|
378
|
-
isError: true,
|
|
379
|
-
content: [{ type: "text", text: `Failed to commit L1 memories: ${err.message}` }]
|
|
380
|
-
};
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
export async function handleMemoryGetContradictionWork(args) {
|
|
384
|
-
const params = z.object({
|
|
385
|
-
userId: z.string(),
|
|
386
|
-
limit: z.number().optional().default(20),
|
|
387
|
-
}).parse(args);
|
|
388
|
-
const store = memoryEngine.store;
|
|
389
|
-
try {
|
|
390
|
-
const pendingL1s = store.getPendingContradictionL1s(params.userId, params.limit);
|
|
391
|
-
if (pendingL1s.length === 0) {
|
|
392
|
-
return {
|
|
393
|
-
content: [{
|
|
394
|
-
type: "text",
|
|
395
|
-
text: JSON.stringify({ status: "no_work", message: "No pending contradiction checks." }, null, 2)
|
|
396
|
-
}]
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
const pairs = [];
|
|
400
|
-
for (const newRecord of pendingL1s) {
|
|
401
|
-
const candidates = store.searchL1Fts(params.userId, newRecord.content, 5);
|
|
402
|
-
for (const cand of candidates) {
|
|
403
|
-
if (cand.record_id === newRecord.id)
|
|
404
|
-
continue;
|
|
405
|
-
pairs.push({
|
|
406
|
-
newRecord: {
|
|
407
|
-
id: newRecord.id,
|
|
408
|
-
content: newRecord.content,
|
|
409
|
-
type: newRecord.type
|
|
410
|
-
},
|
|
411
|
-
existingRecord: {
|
|
412
|
-
id: cand.record_id,
|
|
413
|
-
content: cand.content,
|
|
414
|
-
type: cand.type
|
|
415
|
-
}
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
return {
|
|
420
|
-
content: [{
|
|
421
|
-
type: "text",
|
|
422
|
-
text: JSON.stringify({
|
|
423
|
-
status: pairs.length > 0 ? "work_available" : "no_work",
|
|
424
|
-
userId: params.userId,
|
|
425
|
-
pairs,
|
|
426
|
-
prompts: {
|
|
427
|
-
systemPrompt: L1_CONTRADICTION_PROMPT
|
|
428
|
-
}
|
|
429
|
-
}, null, 2)
|
|
430
|
-
}]
|
|
431
|
-
};
|
|
432
|
-
}
|
|
433
|
-
catch (err) {
|
|
434
|
-
return {
|
|
435
|
-
isError: true,
|
|
436
|
-
content: [{ type: "text", text: `Failed to retrieve contradiction work: ${err.message}` }]
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
export async function handleMemoryCommitContradictionDecisions(args) {
|
|
441
|
-
const payload = ContradictionDecisionCommitSchema.parse(args);
|
|
442
|
-
const store = memoryEngine.store;
|
|
443
|
-
try {
|
|
444
|
-
const allIds = payload.decisions.flatMap(d => [d.newRecordId, d.existingRecordId]);
|
|
445
|
-
validateL1RecordOwnership(store, payload.userId, [...new Set(allIds)]);
|
|
446
|
-
let processedCount = 0;
|
|
447
|
-
const processedIds = [];
|
|
448
|
-
for (const decision of payload.decisions) {
|
|
449
|
-
const newRec = store.getL1Record(payload.userId, decision.newRecordId);
|
|
450
|
-
if (!newRec)
|
|
451
|
-
continue;
|
|
452
|
-
if (decision.decision === "temporal_update") {
|
|
453
|
-
store.invalidateL1Record(payload.userId, decision.existingRecordId, decision.newRecordId, newRec.createdTime);
|
|
454
|
-
processedCount++;
|
|
455
|
-
processedIds.push(decision.newRecordId);
|
|
456
|
-
}
|
|
457
|
-
else if (decision.decision === "genuine_conflict") {
|
|
458
|
-
store.upsertContradiction({
|
|
459
|
-
id: `conflict_${crypto.randomBytes(4).toString("hex")}`,
|
|
460
|
-
userId: payload.userId,
|
|
461
|
-
recordIdA: decision.existingRecordId,
|
|
462
|
-
recordIdB: decision.newRecordId,
|
|
463
|
-
reason: decision.reason || "Genuine contradiction",
|
|
464
|
-
confidence: decision.confidence
|
|
465
|
-
});
|
|
466
|
-
processedCount++;
|
|
467
|
-
processedIds.push(decision.newRecordId);
|
|
468
|
-
}
|
|
469
|
-
else {
|
|
470
|
-
// no_conflict
|
|
471
|
-
processedCount++;
|
|
472
|
-
processedIds.push(decision.newRecordId);
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
const uniqueNewIds = [...new Set(payload.decisions.map(d => d.newRecordId))];
|
|
476
|
-
store.markL1ContradictionChecked(payload.userId, uniqueNewIds);
|
|
477
|
-
return {
|
|
478
|
-
content: [{
|
|
479
|
-
type: "text",
|
|
480
|
-
text: JSON.stringify({
|
|
481
|
-
committedCount: processedCount,
|
|
482
|
-
committedIds: processedIds,
|
|
483
|
-
droppedDuplicateCount: 0,
|
|
484
|
-
validationErrors: []
|
|
485
|
-
}, null, 2)
|
|
486
|
-
}]
|
|
487
|
-
};
|
|
488
|
-
}
|
|
489
|
-
catch (err) {
|
|
490
|
-
return {
|
|
491
|
-
isError: true,
|
|
492
|
-
content: [{ type: "text", text: `Failed to commit contradiction decisions: ${err.message}` }]
|
|
493
|
-
};
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
export async function handleMemoryGetGraphWork(args) {
|
|
497
|
-
const params = z.object({
|
|
498
|
-
userId: z.string(),
|
|
499
|
-
limit: z.number().optional().default(20),
|
|
500
|
-
}).parse(args);
|
|
501
|
-
const store = memoryEngine.store;
|
|
502
|
-
try {
|
|
503
|
-
const pendingL1s = store.getPendingGraphL1s(params.userId, params.limit);
|
|
504
|
-
if (pendingL1s.length === 0) {
|
|
505
|
-
return {
|
|
506
|
-
content: [{
|
|
507
|
-
type: "text",
|
|
508
|
-
text: JSON.stringify({ status: "no_work", message: "No pending graph extractions." }, null, 2)
|
|
509
|
-
}]
|
|
510
|
-
};
|
|
511
|
-
}
|
|
512
|
-
return {
|
|
513
|
-
content: [{
|
|
514
|
-
type: "text",
|
|
515
|
-
text: JSON.stringify({
|
|
516
|
-
status: "work_available",
|
|
517
|
-
userId: params.userId,
|
|
518
|
-
records: pendingL1s.map((r) => ({
|
|
519
|
-
id: r.id,
|
|
520
|
-
content: r.content,
|
|
521
|
-
skillTag: r.skillTag
|
|
522
|
-
})),
|
|
523
|
-
prompts: {
|
|
524
|
-
systemPrompt: GRAPH_EXTRACTION_SYSTEM_PROMPT
|
|
525
|
-
}
|
|
526
|
-
}, null, 2)
|
|
527
|
-
}]
|
|
528
|
-
};
|
|
529
|
-
}
|
|
530
|
-
catch (err) {
|
|
531
|
-
return {
|
|
532
|
-
isError: true,
|
|
533
|
-
content: [{ type: "text", text: `Failed to retrieve graph work: ${err.message}` }]
|
|
534
|
-
};
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
export async function handleMemoryCommitGraph(args) {
|
|
538
|
-
const payload = GraphCommitSchema.parse(args);
|
|
539
|
-
const store = memoryEngine.store;
|
|
540
|
-
try {
|
|
541
|
-
if (payload.sourceRecordId) {
|
|
542
|
-
validateL1RecordOwnership(store, payload.userId, [payload.sourceRecordId]);
|
|
543
|
-
}
|
|
544
|
-
const entityMap = new Map();
|
|
545
|
-
const committedIds = [];
|
|
546
|
-
// 1. Upsert Nodes
|
|
547
|
-
for (const ent of payload.entities) {
|
|
548
|
-
const entityName = normalizeEntityName(ent.entity);
|
|
549
|
-
const existing = store.getGraphNodeByEntity(payload.userId, entityName);
|
|
550
|
-
const nodeId = existing?.id ?? `gn_${crypto.randomBytes(6).toString("hex")}`;
|
|
551
|
-
entityMap.set(entityName.toLowerCase(), nodeId);
|
|
552
|
-
const node = {
|
|
553
|
-
id: nodeId,
|
|
554
|
-
userId: payload.userId,
|
|
555
|
-
entity: ent.entity,
|
|
556
|
-
entityType: ent.type || "concept",
|
|
557
|
-
skillTag: ent.skillTag || "",
|
|
558
|
-
confidence: ent.confidence || 1.0,
|
|
559
|
-
sourceRecordId: ent.sourceRecordId || payload.sourceRecordId || "",
|
|
560
|
-
createdTime: new Date().toISOString()
|
|
561
|
-
};
|
|
562
|
-
store.upsertGraphNode(node);
|
|
563
|
-
committedIds.push(nodeId);
|
|
564
|
-
}
|
|
565
|
-
// 2. Upsert Edges
|
|
566
|
-
for (const rel of payload.relations) {
|
|
567
|
-
const fromName = normalizeEntityName(rel.from);
|
|
568
|
-
const toName = normalizeEntityName(rel.to);
|
|
569
|
-
let fromNodeId = entityMap.get(fromName.toLowerCase());
|
|
570
|
-
if (!fromNodeId) {
|
|
571
|
-
fromNodeId = store.getGraphNodeByEntity(payload.userId, fromName)?.id;
|
|
572
|
-
}
|
|
573
|
-
let toNodeId = entityMap.get(toName.toLowerCase());
|
|
574
|
-
if (!toNodeId) {
|
|
575
|
-
toNodeId = store.getGraphNodeByEntity(payload.userId, toName)?.id;
|
|
576
|
-
}
|
|
577
|
-
if (!fromNodeId || !toNodeId)
|
|
578
|
-
continue;
|
|
579
|
-
const edgeId = `ge_${crypto.randomBytes(6).toString("hex")}`;
|
|
580
|
-
const edge = {
|
|
581
|
-
id: edgeId,
|
|
582
|
-
userId: payload.userId,
|
|
583
|
-
fromNodeId,
|
|
584
|
-
toNodeId,
|
|
585
|
-
relation: rel.relation || "relates_to",
|
|
586
|
-
skillTag: rel.skillTag || "",
|
|
587
|
-
confidence: rel.confidence || 1.0,
|
|
588
|
-
sourceRecordId: rel.sourceRecordId || payload.sourceRecordId || "",
|
|
589
|
-
createdTime: new Date().toISOString()
|
|
590
|
-
};
|
|
591
|
-
store.upsertGraphEdge(edge);
|
|
592
|
-
committedIds.push(edgeId);
|
|
593
|
-
}
|
|
594
|
-
if (payload.sourceRecordId) {
|
|
595
|
-
store.markL1GraphExtracted(payload.userId, [payload.sourceRecordId]);
|
|
596
|
-
}
|
|
597
|
-
return {
|
|
598
|
-
content: [{
|
|
599
|
-
type: "text",
|
|
600
|
-
text: JSON.stringify({
|
|
601
|
-
committedCount: committedIds.length,
|
|
602
|
-
committedIds,
|
|
603
|
-
droppedDuplicateCount: 0,
|
|
604
|
-
validationErrors: []
|
|
605
|
-
}, null, 2)
|
|
606
|
-
}]
|
|
607
|
-
};
|
|
608
|
-
}
|
|
609
|
-
catch (err) {
|
|
610
|
-
return {
|
|
611
|
-
isError: true,
|
|
612
|
-
content: [{ type: "text", text: `Failed to commit graph elements: ${err.message}` }]
|
|
613
|
-
};
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
export async function handleMemoryGetL2Work(args) {
|
|
617
|
-
const params = z.object({
|
|
618
|
-
userId: z.string(),
|
|
619
|
-
}).parse(args);
|
|
620
|
-
const store = memoryEngine.store;
|
|
621
|
-
try {
|
|
622
|
-
const countState = store.getSchedulerState(params.userId);
|
|
623
|
-
const sceneNames = store.getDistinctSceneNames(params.userId);
|
|
624
|
-
const topL2Scenes = store.getTopL2Scenes(params.userId, 50);
|
|
625
|
-
const scenes = [];
|
|
626
|
-
for (const name of sceneNames) {
|
|
627
|
-
const l1s = store.getL1sByScene(params.userId, name, 30);
|
|
628
|
-
if (l1s.length > 0) {
|
|
629
|
-
scenes.push({ sceneName: name, memories: l1s });
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
const sceneCount = store.getL2SceneCount(params.userId);
|
|
633
|
-
const overflow = sceneCount - L2_MAX_SCENES + 1;
|
|
634
|
-
const coldScenes = overflow > 0 ? store.getColdL2Scenes(params.userId, overflow + 3) : [];
|
|
635
|
-
return {
|
|
636
|
-
content: [{
|
|
637
|
-
type: "text",
|
|
638
|
-
text: JSON.stringify({
|
|
639
|
-
status: scenes.length > 0 ? "work_available" : "no_work",
|
|
640
|
-
userId: params.userId,
|
|
641
|
-
schedulerState: countState,
|
|
642
|
-
scenes,
|
|
643
|
-
existingL2Scenes: topL2Scenes,
|
|
644
|
-
coldScenesToMerge: coldScenes,
|
|
645
|
-
prompts: {
|
|
646
|
-
systemPrompt: L2_SCENE_SYSTEM_PROMPT
|
|
647
|
-
}
|
|
648
|
-
}, null, 2)
|
|
649
|
-
}]
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
catch (err) {
|
|
653
|
-
return {
|
|
654
|
-
isError: true,
|
|
655
|
-
content: [{ type: "text", text: `Failed to retrieve L2 work: ${err.message}` }]
|
|
656
|
-
};
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
export async function handleMemoryCommitL2(args) {
|
|
660
|
-
const payload = L2CommitSchema.parse(args);
|
|
661
|
-
const store = memoryEngine.store;
|
|
662
|
-
try {
|
|
663
|
-
const committedIds = [];
|
|
664
|
-
const now = new Date().toISOString();
|
|
665
|
-
// 1. Renames (clustering canonicalization)
|
|
666
|
-
for (const r of payload.renames) {
|
|
667
|
-
store.renameSceneInL1Records(payload.userId, r.oldName, r.newName);
|
|
668
|
-
}
|
|
669
|
-
// 2. Main distilled scenes
|
|
670
|
-
for (const scene of payload.scenes) {
|
|
671
|
-
const existing = store.getL2SceneByName(payload.userId, scene.sceneName);
|
|
672
|
-
const record = {
|
|
673
|
-
id: existing?.id ?? `l2_${crypto.randomBytes(6).toString("hex")}`,
|
|
674
|
-
userId: payload.userId,
|
|
675
|
-
sceneName: scene.sceneName,
|
|
676
|
-
summaryMd: scene.summaryMd,
|
|
677
|
-
heatScore: scene.heatScore !== undefined ? scene.heatScore : (existing ? Math.min(100, existing.heatScore + 30) : 100),
|
|
678
|
-
lastActiveTime: now,
|
|
679
|
-
createdTime: existing?.createdTime ?? now,
|
|
680
|
-
updatedTime: now
|
|
681
|
-
};
|
|
682
|
-
store.upsertL2Scene(record);
|
|
683
|
-
committedIds.push(record.id);
|
|
684
|
-
}
|
|
685
|
-
// 3. Merges into [Archived]
|
|
686
|
-
for (const merge of payload.merges) {
|
|
687
|
-
const existingArchive = store.getL2SceneByName(payload.userId, "[Archived]");
|
|
688
|
-
const record = {
|
|
689
|
-
id: existingArchive?.id ?? `l2_${crypto.randomBytes(6).toString("hex")}`,
|
|
690
|
-
userId: payload.userId,
|
|
691
|
-
sceneName: "[Archived]",
|
|
692
|
-
summaryMd: merge.mergedSummaryMd,
|
|
693
|
-
heatScore: 10,
|
|
694
|
-
lastActiveTime: now,
|
|
695
|
-
createdTime: existingArchive?.createdTime ?? now,
|
|
696
|
-
updatedTime: now
|
|
697
|
-
};
|
|
698
|
-
store.upsertL2Scene(record);
|
|
699
|
-
store.deleteL2Scenes(payload.userId, merge.sceneIds);
|
|
700
|
-
committedIds.push(record.id);
|
|
701
|
-
}
|
|
702
|
-
store.resetSchedulerL2Count(payload.userId);
|
|
703
|
-
store.decayL2HeatScores(payload.userId);
|
|
704
|
-
return {
|
|
705
|
-
content: [{
|
|
706
|
-
type: "text",
|
|
707
|
-
text: JSON.stringify({
|
|
708
|
-
committedCount: committedIds.length,
|
|
709
|
-
committedIds,
|
|
710
|
-
droppedDuplicateCount: 0,
|
|
711
|
-
validationErrors: []
|
|
712
|
-
}, null, 2)
|
|
713
|
-
}]
|
|
714
|
-
};
|
|
715
|
-
}
|
|
716
|
-
catch (err) {
|
|
717
|
-
return {
|
|
718
|
-
isError: true,
|
|
719
|
-
content: [{ type: "text", text: `Failed to commit L2 scenes: ${err.message}` }]
|
|
720
|
-
};
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
export async function handleMemoryGetL3Work(args) {
|
|
724
|
-
const params = z.object({
|
|
725
|
-
userId: z.string(),
|
|
726
|
-
}).parse(args);
|
|
727
|
-
const store = memoryEngine.store;
|
|
728
|
-
try {
|
|
729
|
-
const memories = store.getPersonaAndInstructionL1s(params.userId, 100);
|
|
730
|
-
const existing = store.getL3Persona(params.userId);
|
|
731
|
-
return {
|
|
732
|
-
content: [{
|
|
733
|
-
type: "text",
|
|
734
|
-
text: JSON.stringify({
|
|
735
|
-
status: memories.length > 0 ? "work_available" : "no_work",
|
|
736
|
-
userId: params.userId,
|
|
737
|
-
memories,
|
|
738
|
-
existingPersona: existing,
|
|
739
|
-
prompts: {
|
|
740
|
-
systemPrompt: L3_PERSONA_SYSTEM_PROMPT,
|
|
741
|
-
userPrompt: formatL3PersonaPrompt(memories)
|
|
742
|
-
}
|
|
743
|
-
}, null, 2)
|
|
744
|
-
}]
|
|
745
|
-
};
|
|
746
|
-
}
|
|
747
|
-
catch (err) {
|
|
748
|
-
return {
|
|
749
|
-
isError: true,
|
|
750
|
-
content: [{ type: "text", text: `Failed to retrieve L3 work: ${err.message}` }]
|
|
751
|
-
};
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
export async function handleMemoryCommitL3(args) {
|
|
755
|
-
const payload = L3CommitSchema.parse(args);
|
|
756
|
-
const store = memoryEngine.store;
|
|
757
|
-
try {
|
|
758
|
-
const memories = store.getPersonaAndInstructionL1s(payload.userId, 100);
|
|
759
|
-
const existing = store.getL3Persona(payload.userId);
|
|
760
|
-
const now = new Date().toISOString();
|
|
761
|
-
const record = {
|
|
762
|
-
userId: payload.userId,
|
|
763
|
-
personaMd: payload.personaMd,
|
|
764
|
-
l1CountAtGeneration: memories.length,
|
|
765
|
-
createdTime: existing?.createdTime ?? now,
|
|
766
|
-
updatedTime: now
|
|
767
|
-
};
|
|
768
|
-
store.upsertL3Persona(record);
|
|
769
|
-
// Invalidate local in-memory cache
|
|
770
|
-
const cache = memoryEngine.personaCache;
|
|
771
|
-
if (cache) {
|
|
772
|
-
cache.set(payload.userId, { personaMd: payload.personaMd, cachedAt: Date.now() });
|
|
773
|
-
}
|
|
774
|
-
store.resetSchedulerL3Count(payload.userId);
|
|
775
|
-
return {
|
|
776
|
-
content: [{
|
|
777
|
-
type: "text",
|
|
778
|
-
text: JSON.stringify({
|
|
779
|
-
committedCount: 1,
|
|
780
|
-
committedIds: [payload.userId],
|
|
781
|
-
droppedDuplicateCount: 0,
|
|
782
|
-
validationErrors: []
|
|
783
|
-
}, null, 2)
|
|
784
|
-
}]
|
|
785
|
-
};
|
|
786
|
-
}
|
|
787
|
-
catch (err) {
|
|
788
|
-
return {
|
|
789
|
-
isError: true,
|
|
790
|
-
content: [{ type: "text", text: `Failed to commit L3 persona: ${err.message}` }]
|
|
791
|
-
};
|
|
792
|
-
}
|
|
793
|
-
}
|