@gethmy/mcp 2.3.2 → 2.3.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/lib/api-client.js +1 -433
- package/dist/lib/config.js +0 -18
- package/package.json +9 -5
- package/dist/lib/active-learning.js +0 -974
- package/dist/lib/auto-session.js +0 -195
- package/dist/lib/cli.js +0 -34964
- package/dist/lib/consolidation.js +0 -388
- package/dist/lib/context-assembly.js +0 -1311
- package/dist/lib/graph-expansion.js +0 -147
- package/dist/lib/http.js +0 -1962
- package/dist/lib/index.js +0 -29527
- package/dist/lib/lifecycle-maintenance.js +0 -672
- package/dist/lib/memory-cleanup.js +0 -1361
- package/dist/lib/onboard.js +0 -2592
- package/dist/lib/prompt-builder.js +0 -481
- package/dist/lib/remote.js +0 -31756
- package/dist/lib/server.js +0 -29524
- package/dist/lib/skills.js +0 -776
- package/dist/lib/tui/agents.js +0 -137
- package/dist/lib/tui/docs.js +0 -1647
- package/dist/lib/tui/setup.js +0 -5828
- package/dist/lib/tui/theme.js +0 -192
- package/dist/lib/tui/writer.js +0 -1173
|
@@ -1,974 +0,0 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
-
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
-
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
-
for (let key of __getOwnPropNames(mod))
|
|
11
|
-
if (!__hasOwnProp.call(to, key))
|
|
12
|
-
__defProp(to, key, {
|
|
13
|
-
get: () => mod[key],
|
|
14
|
-
enumerable: true
|
|
15
|
-
});
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
19
|
-
var __export = (target, all) => {
|
|
20
|
-
for (var name in all)
|
|
21
|
-
__defProp(target, name, {
|
|
22
|
-
get: all[name],
|
|
23
|
-
enumerable: true,
|
|
24
|
-
configurable: true,
|
|
25
|
-
set: (newValue) => all[name] = () => newValue
|
|
26
|
-
});
|
|
27
|
-
};
|
|
28
|
-
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
29
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
30
|
-
|
|
31
|
-
// src/config.ts
|
|
32
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
33
|
-
import { homedir } from "node:os";
|
|
34
|
-
import { join } from "node:path";
|
|
35
|
-
var DEFAULT_API_URL = "https://app.gethmy.com/api";
|
|
36
|
-
var LOCAL_CONFIG_FILENAME = ".harmony-mcp.json";
|
|
37
|
-
function getConfigDir() {
|
|
38
|
-
return join(homedir(), ".harmony-mcp");
|
|
39
|
-
}
|
|
40
|
-
function getConfigPath() {
|
|
41
|
-
return join(getConfigDir(), "config.json");
|
|
42
|
-
}
|
|
43
|
-
function getLocalConfigPath(cwd) {
|
|
44
|
-
return join(cwd || process.cwd(), LOCAL_CONFIG_FILENAME);
|
|
45
|
-
}
|
|
46
|
-
function loadConfig() {
|
|
47
|
-
const configPath = getConfigPath();
|
|
48
|
-
if (!existsSync(configPath)) {
|
|
49
|
-
return {
|
|
50
|
-
apiKey: null,
|
|
51
|
-
apiUrl: DEFAULT_API_URL,
|
|
52
|
-
activeWorkspaceId: null,
|
|
53
|
-
activeProjectId: null,
|
|
54
|
-
userEmail: null,
|
|
55
|
-
memoryDir: null
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
try {
|
|
59
|
-
const data = readFileSync(configPath, "utf-8");
|
|
60
|
-
const config = JSON.parse(data);
|
|
61
|
-
return {
|
|
62
|
-
apiKey: config.apiKey || null,
|
|
63
|
-
apiUrl: config.apiUrl || DEFAULT_API_URL,
|
|
64
|
-
activeWorkspaceId: config.activeWorkspaceId || null,
|
|
65
|
-
activeProjectId: config.activeProjectId || null,
|
|
66
|
-
userEmail: config.userEmail || null,
|
|
67
|
-
memoryDir: config.memoryDir || null
|
|
68
|
-
};
|
|
69
|
-
} catch {
|
|
70
|
-
return {
|
|
71
|
-
apiKey: null,
|
|
72
|
-
apiUrl: DEFAULT_API_URL,
|
|
73
|
-
activeWorkspaceId: null,
|
|
74
|
-
activeProjectId: null,
|
|
75
|
-
userEmail: null,
|
|
76
|
-
memoryDir: null
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
function saveConfig(config) {
|
|
81
|
-
const configDir = getConfigDir();
|
|
82
|
-
const configPath = getConfigPath();
|
|
83
|
-
if (!existsSync(configDir)) {
|
|
84
|
-
mkdirSync(configDir, { recursive: true, mode: 448 });
|
|
85
|
-
}
|
|
86
|
-
const existingConfig = loadConfig();
|
|
87
|
-
const newConfig = { ...existingConfig, ...config };
|
|
88
|
-
writeFileSync(configPath, JSON.stringify(newConfig, null, 2), {
|
|
89
|
-
mode: 384
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
function loadLocalConfig(cwd) {
|
|
93
|
-
const localConfigPath = getLocalConfigPath(cwd);
|
|
94
|
-
if (!existsSync(localConfigPath)) {
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
try {
|
|
98
|
-
const data = readFileSync(localConfigPath, "utf-8");
|
|
99
|
-
const config = JSON.parse(data);
|
|
100
|
-
return {
|
|
101
|
-
workspaceId: config.workspaceId || null,
|
|
102
|
-
projectId: config.projectId || null
|
|
103
|
-
};
|
|
104
|
-
} catch {
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
function saveLocalConfig(config, cwd) {
|
|
109
|
-
const localConfigPath = getLocalConfigPath(cwd);
|
|
110
|
-
const existingConfig = loadLocalConfig(cwd) || {
|
|
111
|
-
workspaceId: null,
|
|
112
|
-
projectId: null
|
|
113
|
-
};
|
|
114
|
-
const newConfig = { ...existingConfig, ...config };
|
|
115
|
-
const cleanConfig = {};
|
|
116
|
-
if (newConfig.workspaceId)
|
|
117
|
-
cleanConfig.workspaceId = newConfig.workspaceId;
|
|
118
|
-
if (newConfig.projectId)
|
|
119
|
-
cleanConfig.projectId = newConfig.projectId;
|
|
120
|
-
writeFileSync(localConfigPath, JSON.stringify(cleanConfig, null, 2));
|
|
121
|
-
}
|
|
122
|
-
function hasLocalConfig(cwd) {
|
|
123
|
-
return existsSync(getLocalConfigPath(cwd));
|
|
124
|
-
}
|
|
125
|
-
function getApiKey() {
|
|
126
|
-
const config = loadConfig();
|
|
127
|
-
if (!config.apiKey) {
|
|
128
|
-
throw new Error(`Not configured. Run "npx @gethmy/mcp setup" to set your API key.
|
|
129
|
-
` + "You can generate an API key at https://gethmy.com → Settings → API Keys.");
|
|
130
|
-
}
|
|
131
|
-
return config.apiKey;
|
|
132
|
-
}
|
|
133
|
-
function getApiUrl() {
|
|
134
|
-
const config = loadConfig();
|
|
135
|
-
return config.apiUrl;
|
|
136
|
-
}
|
|
137
|
-
function getUserEmail() {
|
|
138
|
-
const config = loadConfig();
|
|
139
|
-
return config.userEmail;
|
|
140
|
-
}
|
|
141
|
-
function setUserEmail(email) {
|
|
142
|
-
saveConfig({ userEmail: email });
|
|
143
|
-
}
|
|
144
|
-
function setActiveWorkspace(workspaceId, options) {
|
|
145
|
-
if (options?.local) {
|
|
146
|
-
saveLocalConfig({ workspaceId }, options.cwd);
|
|
147
|
-
} else {
|
|
148
|
-
saveConfig({ activeWorkspaceId: workspaceId });
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
function setActiveProject(projectId, options) {
|
|
152
|
-
if (options?.local) {
|
|
153
|
-
saveLocalConfig({ projectId }, options.cwd);
|
|
154
|
-
} else {
|
|
155
|
-
saveConfig({ activeProjectId: projectId });
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
function getActiveWorkspaceId(cwd) {
|
|
159
|
-
const localConfig = loadLocalConfig(cwd);
|
|
160
|
-
if (localConfig?.workspaceId) {
|
|
161
|
-
return localConfig.workspaceId;
|
|
162
|
-
}
|
|
163
|
-
return loadConfig().activeWorkspaceId;
|
|
164
|
-
}
|
|
165
|
-
function getActiveProjectId(cwd) {
|
|
166
|
-
const localConfig = loadLocalConfig(cwd);
|
|
167
|
-
if (localConfig?.projectId) {
|
|
168
|
-
return localConfig.projectId;
|
|
169
|
-
}
|
|
170
|
-
return loadConfig().activeProjectId;
|
|
171
|
-
}
|
|
172
|
-
function isConfigured() {
|
|
173
|
-
const config = loadConfig();
|
|
174
|
-
return !!config.apiKey;
|
|
175
|
-
}
|
|
176
|
-
function areSkillsInstalled(cwd) {
|
|
177
|
-
const home = homedir();
|
|
178
|
-
const workingDir = cwd || process.cwd();
|
|
179
|
-
const foundPaths = [];
|
|
180
|
-
const globalSkillsDir = join(home, ".agents", "skills");
|
|
181
|
-
const globalSkillPath = join(globalSkillsDir, "hmy", "SKILL.md");
|
|
182
|
-
if (existsSync(globalSkillPath)) {
|
|
183
|
-
foundPaths.push(globalSkillPath);
|
|
184
|
-
return { installed: true, location: "global", paths: foundPaths };
|
|
185
|
-
}
|
|
186
|
-
const claudeGlobalSkill = join(home, ".claude", "skills", "hmy.md");
|
|
187
|
-
if (existsSync(claudeGlobalSkill)) {
|
|
188
|
-
foundPaths.push(claudeGlobalSkill);
|
|
189
|
-
return { installed: true, location: "global", paths: foundPaths };
|
|
190
|
-
}
|
|
191
|
-
const claudeGlobalSkillAlt = join(home, ".claude", "skills", "hmy", "SKILL.md");
|
|
192
|
-
if (existsSync(claudeGlobalSkillAlt)) {
|
|
193
|
-
foundPaths.push(claudeGlobalSkillAlt);
|
|
194
|
-
return { installed: true, location: "global", paths: foundPaths };
|
|
195
|
-
}
|
|
196
|
-
const localSkillPath = join(workingDir, ".claude", "skills", "hmy.md");
|
|
197
|
-
if (existsSync(localSkillPath)) {
|
|
198
|
-
foundPaths.push(localSkillPath);
|
|
199
|
-
return { installed: true, location: "local", paths: foundPaths };
|
|
200
|
-
}
|
|
201
|
-
const localSkillPathAlt = join(workingDir, ".claude", "skills", "hmy", "SKILL.md");
|
|
202
|
-
if (existsSync(localSkillPathAlt)) {
|
|
203
|
-
foundPaths.push(localSkillPathAlt);
|
|
204
|
-
return { installed: true, location: "local", paths: foundPaths };
|
|
205
|
-
}
|
|
206
|
-
return { installed: false, location: null, paths: [] };
|
|
207
|
-
}
|
|
208
|
-
function hasProjectContext(cwd) {
|
|
209
|
-
const localConfig = loadLocalConfig(cwd);
|
|
210
|
-
return !!(localConfig?.workspaceId || localConfig?.projectId);
|
|
211
|
-
}
|
|
212
|
-
function getMemoryDir() {
|
|
213
|
-
const config = loadConfig();
|
|
214
|
-
if (config.memoryDir)
|
|
215
|
-
return config.memoryDir;
|
|
216
|
-
return join(homedir(), ".harmony", "memory");
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// src/graph-expansion.ts
|
|
220
|
-
async function autoExpandGraph(client, entityId, title, content, _tags, workspaceId, projectId, maxRelations = 5) {
|
|
221
|
-
try {
|
|
222
|
-
const contentSnippet = content.slice(0, 200).trim();
|
|
223
|
-
const query = [title, contentSnippet].filter(Boolean).join(" ");
|
|
224
|
-
let candidates = [];
|
|
225
|
-
const { entities } = await client.searchMemoryEntities(workspaceId, query, {
|
|
226
|
-
project_id: projectId,
|
|
227
|
-
limit: 20
|
|
228
|
-
});
|
|
229
|
-
candidates = entities.filter((e) => e.id !== entityId).slice(0, maxRelations);
|
|
230
|
-
if (candidates.length === 0) {
|
|
231
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
232
|
-
const retry = await client.searchMemoryEntities(workspaceId, query, {
|
|
233
|
-
project_id: projectId,
|
|
234
|
-
limit: 20
|
|
235
|
-
});
|
|
236
|
-
candidates = retry.entities.filter((e) => e.id !== entityId).slice(0, maxRelations);
|
|
237
|
-
}
|
|
238
|
-
let relationsCreated = 0;
|
|
239
|
-
for (const candidate of candidates) {
|
|
240
|
-
try {
|
|
241
|
-
await client.createMemoryRelation({
|
|
242
|
-
source_id: entityId,
|
|
243
|
-
target_id: candidate.id,
|
|
244
|
-
relation_type: "relates_to",
|
|
245
|
-
confidence: 0.6
|
|
246
|
-
});
|
|
247
|
-
relationsCreated++;
|
|
248
|
-
} catch (err) {
|
|
249
|
-
const status = err?.status;
|
|
250
|
-
if (status !== 409) {}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
return { relationsCreated };
|
|
254
|
-
} catch {
|
|
255
|
-
return { relationsCreated: 0 };
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
async function findSimilarEntities(client, title, content, workspaceId, options) {
|
|
259
|
-
const contentSnippet = content.slice(0, 200).trim();
|
|
260
|
-
const query = [title, contentSnippet].filter(Boolean).join(" ");
|
|
261
|
-
try {
|
|
262
|
-
const { entities } = await client.searchMemoryEntities(workspaceId, query, {
|
|
263
|
-
project_id: options?.projectId,
|
|
264
|
-
limit: options?.limit ?? 20,
|
|
265
|
-
type: options?.type
|
|
266
|
-
});
|
|
267
|
-
const minScore = options?.minRrfScore ?? 0;
|
|
268
|
-
const excludeSet = new Set(options?.excludeIds || []);
|
|
269
|
-
return entities.filter((e) => {
|
|
270
|
-
if (excludeSet.has(e.id))
|
|
271
|
-
return false;
|
|
272
|
-
if (minScore > 0 && (e.rrf_score ?? 0) < minScore)
|
|
273
|
-
return false;
|
|
274
|
-
return true;
|
|
275
|
-
});
|
|
276
|
-
} catch {
|
|
277
|
-
return [];
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
var CAUSAL_LOOKUP = [
|
|
281
|
-
{
|
|
282
|
-
sourceType: "error",
|
|
283
|
-
targetType: "solution",
|
|
284
|
-
relation: "resolved_by",
|
|
285
|
-
direction: "forward"
|
|
286
|
-
},
|
|
287
|
-
{
|
|
288
|
-
sourceType: "solution",
|
|
289
|
-
targetType: "error",
|
|
290
|
-
relation: "resolved_by",
|
|
291
|
-
direction: "reverse"
|
|
292
|
-
},
|
|
293
|
-
{
|
|
294
|
-
sourceType: "lesson",
|
|
295
|
-
targetType: "error",
|
|
296
|
-
relation: "learned_from",
|
|
297
|
-
direction: "forward"
|
|
298
|
-
}
|
|
299
|
-
];
|
|
300
|
-
async function linkCrossTypeNeighbors(client, entityId, entityType, title, content, workspaceId, projectId) {
|
|
301
|
-
const rules = CAUSAL_LOOKUP.filter((r) => r.sourceType === entityType);
|
|
302
|
-
if (rules.length === 0)
|
|
303
|
-
return { relationsCreated: 0 };
|
|
304
|
-
let relationsCreated = 0;
|
|
305
|
-
for (const rule of rules) {
|
|
306
|
-
try {
|
|
307
|
-
const matches = await findSimilarEntities(client, title, content, workspaceId, {
|
|
308
|
-
projectId,
|
|
309
|
-
limit: 10,
|
|
310
|
-
minRrfScore: 0.04,
|
|
311
|
-
excludeIds: [entityId],
|
|
312
|
-
type: rule.targetType
|
|
313
|
-
});
|
|
314
|
-
for (const match of matches.slice(0, 3)) {
|
|
315
|
-
const sourceId = rule.direction === "forward" ? entityId : match.id;
|
|
316
|
-
const targetId = rule.direction === "forward" ? match.id : entityId;
|
|
317
|
-
try {
|
|
318
|
-
await client.createMemoryRelation({
|
|
319
|
-
source_id: sourceId,
|
|
320
|
-
target_id: targetId,
|
|
321
|
-
relation_type: rule.relation,
|
|
322
|
-
confidence: 0.65
|
|
323
|
-
});
|
|
324
|
-
relationsCreated++;
|
|
325
|
-
} catch {}
|
|
326
|
-
}
|
|
327
|
-
} catch {}
|
|
328
|
-
}
|
|
329
|
-
return { relationsCreated };
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// src/active-learning.ts
|
|
333
|
-
var CONTRADICTION_TYPES = new Set([
|
|
334
|
-
"decision",
|
|
335
|
-
"pattern",
|
|
336
|
-
"preference",
|
|
337
|
-
"lesson"
|
|
338
|
-
]);
|
|
339
|
-
var sessionTaskHistory = new Map;
|
|
340
|
-
var MID_SESSION_RATE_LIMIT_MS = 120000;
|
|
341
|
-
function levenshteinSimilarity(a, b) {
|
|
342
|
-
if (a === b)
|
|
343
|
-
return 1;
|
|
344
|
-
if (a.length === 0 || b.length === 0)
|
|
345
|
-
return 0;
|
|
346
|
-
const sa = a.slice(0, 200).toLowerCase();
|
|
347
|
-
const sb = b.slice(0, 200).toLowerCase();
|
|
348
|
-
const matrix = [];
|
|
349
|
-
for (let i = 0;i <= sa.length; i++) {
|
|
350
|
-
matrix[i] = [i];
|
|
351
|
-
}
|
|
352
|
-
for (let j = 0;j <= sb.length; j++) {
|
|
353
|
-
matrix[0][j] = j;
|
|
354
|
-
}
|
|
355
|
-
for (let i = 1;i <= sa.length; i++) {
|
|
356
|
-
for (let j = 1;j <= sb.length; j++) {
|
|
357
|
-
const cost = sa[i - 1] === sb[j - 1] ? 0 : 1;
|
|
358
|
-
matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
const maxLen = Math.max(sa.length, sb.length);
|
|
362
|
-
return 1 - matrix[sa.length][sb.length] / maxLen;
|
|
363
|
-
}
|
|
364
|
-
async function extractMidSessionLearnings(_client, ctx) {
|
|
365
|
-
const workspaceId = getActiveWorkspaceId();
|
|
366
|
-
if (!workspaceId)
|
|
367
|
-
return { count: 0, entityIds: [] };
|
|
368
|
-
const _projectId = getActiveProjectId() || undefined;
|
|
369
|
-
const now = Date.now();
|
|
370
|
-
const _entityIds = [];
|
|
371
|
-
const history = sessionTaskHistory.get(ctx.cardId);
|
|
372
|
-
if (ctx.currentTask) {
|
|
373
|
-
const previousTask = history?.lastTask || "";
|
|
374
|
-
const similarity = levenshteinSimilarity(previousTask, ctx.currentTask);
|
|
375
|
-
const existingSteps = history?.steps || [];
|
|
376
|
-
if (existingSteps.length === 0 || similarity < 0.6 && previousTask.length > 0) {
|
|
377
|
-
const newStep = {
|
|
378
|
-
task: ctx.currentTask,
|
|
379
|
-
progress: ctx.progressPercent ?? 0,
|
|
380
|
-
timestamp: now
|
|
381
|
-
};
|
|
382
|
-
if (existingSteps.length === 0 && previousTask.length > 0) {
|
|
383
|
-
existingSteps.push({
|
|
384
|
-
task: previousTask,
|
|
385
|
-
progress: 0,
|
|
386
|
-
timestamp: history?.lastExtractionAt ?? now
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
existingSteps.push(newStep);
|
|
390
|
-
sessionTaskHistory.set(ctx.cardId, {
|
|
391
|
-
lastTask: ctx.currentTask,
|
|
392
|
-
lastExtractionAt: history?.lastExtractionAt ?? 0,
|
|
393
|
-
steps: existingSteps
|
|
394
|
-
});
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
if (history && now - history.lastExtractionAt < MID_SESSION_RATE_LIMIT_MS) {
|
|
398
|
-
if (ctx.status !== "blocked" || !ctx.blockers?.length) {
|
|
399
|
-
return { count: 0, entityIds: [] };
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
if (ctx.status === "blocked" && ctx.blockers?.length) {
|
|
403
|
-
sessionTaskHistory.set(ctx.cardId, {
|
|
404
|
-
lastTask: ctx.currentTask || "",
|
|
405
|
-
lastExtractionAt: now,
|
|
406
|
-
steps: history?.steps || []
|
|
407
|
-
});
|
|
408
|
-
return { count: 0, entityIds: [] };
|
|
409
|
-
}
|
|
410
|
-
if (ctx.currentTask) {
|
|
411
|
-
const currentHistory = sessionTaskHistory.get(ctx.cardId);
|
|
412
|
-
sessionTaskHistory.set(ctx.cardId, {
|
|
413
|
-
lastTask: ctx.currentTask,
|
|
414
|
-
lastExtractionAt: currentHistory?.lastExtractionAt ?? 0,
|
|
415
|
-
steps: currentHistory?.steps || []
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
return { count: 0, entityIds: [] };
|
|
419
|
-
}
|
|
420
|
-
function clearMidSessionTracking(cardId) {
|
|
421
|
-
sessionTaskHistory.delete(cardId);
|
|
422
|
-
}
|
|
423
|
-
function enrichSteps(steps) {
|
|
424
|
-
return steps.map((step, i) => {
|
|
425
|
-
const prevProgress = i > 0 ? steps[i - 1].progress : 0;
|
|
426
|
-
const prevTimestamp = i > 0 ? steps[i - 1].timestamp : step.timestamp;
|
|
427
|
-
const progressDelta = step.progress - prevProgress;
|
|
428
|
-
return {
|
|
429
|
-
task: step.task,
|
|
430
|
-
progress: step.progress,
|
|
431
|
-
progressDelta,
|
|
432
|
-
durationMs: step.timestamp - prevTimestamp,
|
|
433
|
-
isKeyDecision: progressDelta >= 20
|
|
434
|
-
};
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
function buildProcedureContent(session, enrichedSteps, wikiLinksLine) {
|
|
438
|
-
const triggerLabels = session.cardLabels.length > 0 ? `Labels: ${session.cardLabels.join(", ")}` : "";
|
|
439
|
-
const stepsMarkdown = enrichedSteps.map((s, i) => {
|
|
440
|
-
const marker = s.isKeyDecision ? " **[key step]**" : "";
|
|
441
|
-
const duration = s.durationMs > 0 ? ` (~${Math.round(s.durationMs / 60000)}min)` : "";
|
|
442
|
-
return `${i + 1}. ${s.task} (${s.progress}%, +${s.progressDelta}%)${marker}${duration}`;
|
|
443
|
-
}).join(`
|
|
444
|
-
`);
|
|
445
|
-
const subtaskSection = session.cardSubtasks && session.cardSubtasks.length > 0 ? [
|
|
446
|
-
"",
|
|
447
|
-
"## Subtasks Completed",
|
|
448
|
-
...session.cardSubtasks.map((s) => `- [${s.done ? "x" : " "}] ${s.title}`)
|
|
449
|
-
].join(`
|
|
450
|
-
`) : "";
|
|
451
|
-
const durationInfo = session.sessionDurationMs ? `
|
|
452
|
-
Duration: ${Math.round(session.sessionDurationMs / 60000)} minutes` : "";
|
|
453
|
-
return [
|
|
454
|
-
"## Trigger",
|
|
455
|
-
`When working on: "${session.cardTitle}"`,
|
|
456
|
-
triggerLabels,
|
|
457
|
-
"",
|
|
458
|
-
"## Steps",
|
|
459
|
-
stepsMarkdown,
|
|
460
|
-
subtaskSection,
|
|
461
|
-
"",
|
|
462
|
-
"## Outcome",
|
|
463
|
-
`Completed at ${session.progressPercent ?? "unknown"}%`,
|
|
464
|
-
session.currentTask ? `Final state: ${session.currentTask}` : "",
|
|
465
|
-
`Agent: ${session.agentName}`,
|
|
466
|
-
durationInfo,
|
|
467
|
-
wikiLinksLine
|
|
468
|
-
].filter((line) => line !== undefined).join(`
|
|
469
|
-
`);
|
|
470
|
-
}
|
|
471
|
-
async function extractOrReinforceProcedure(client, session, steps, workspaceId, projectId, wikiLinksLine = "") {
|
|
472
|
-
const enrichedSteps = enrichSteps(steps);
|
|
473
|
-
const newContent = buildProcedureContent(session, enrichedSteps, wikiLinksLine);
|
|
474
|
-
const tags = [
|
|
475
|
-
"auto-extracted",
|
|
476
|
-
"procedure",
|
|
477
|
-
...session.cardLabels.slice(0, 3)
|
|
478
|
-
];
|
|
479
|
-
try {
|
|
480
|
-
const similar = await findSimilarEntities(client, `Procedure: ${session.cardTitle}`, newContent, workspaceId, { projectId, limit: 5, minRrfScore: 0.03 });
|
|
481
|
-
const matchingProcedure = similar.find((e) => e.type === "procedure" && (e.rrf_score ?? 0) >= 0.05);
|
|
482
|
-
if (matchingProcedure) {
|
|
483
|
-
const { entity: rawEntity } = await client.getMemoryEntity(matchingProcedure.id);
|
|
484
|
-
const fullEntity = rawEntity;
|
|
485
|
-
const currentMeta = fullEntity.metadata || {};
|
|
486
|
-
const sourceCards = (currentMeta.source_cards || []).slice();
|
|
487
|
-
if (!sourceCards.includes(session.cardId)) {
|
|
488
|
-
sourceCards.push(session.cardId);
|
|
489
|
-
}
|
|
490
|
-
const reuseCount = (currentMeta.reuse_count || 0) + 1;
|
|
491
|
-
const currentConfidence = fullEntity.confidence ?? 0.7;
|
|
492
|
-
const newConfidence = Math.min(0.95, currentConfidence + 0.05);
|
|
493
|
-
const stepsAppendix = enrichedSteps.map((s, i) => `${i + 1}. ${s.task} (${s.progress}%)`).join(`
|
|
494
|
-
`);
|
|
495
|
-
const appendix = `
|
|
496
|
-
|
|
497
|
-
---
|
|
498
|
-
### Execution ${reuseCount + 1}: ${session.cardTitle}
|
|
499
|
-
${stepsAppendix}
|
|
500
|
-
Agent: ${session.agentName} | ${new Date().toISOString().split("T")[0]}`;
|
|
501
|
-
const updatedMeta = {
|
|
502
|
-
...currentMeta,
|
|
503
|
-
reuse_count: reuseCount,
|
|
504
|
-
source_cards: sourceCards,
|
|
505
|
-
last_reinforced_at: new Date().toISOString(),
|
|
506
|
-
step_count: Math.max(currentMeta.step_count || 0, steps.length)
|
|
507
|
-
};
|
|
508
|
-
const shouldPromote = reuseCount >= 2 && fullEntity.memory_tier !== "reference";
|
|
509
|
-
await client.updateMemoryEntity(fullEntity.id, {
|
|
510
|
-
content: (fullEntity.content || "") + appendix,
|
|
511
|
-
confidence: newConfidence,
|
|
512
|
-
metadata: {
|
|
513
|
-
...updatedMeta,
|
|
514
|
-
...shouldPromote ? {
|
|
515
|
-
promoted_reason: `Reinforced by ${reuseCount + 1} successful sessions`,
|
|
516
|
-
promoted_at: new Date().toISOString()
|
|
517
|
-
} : {}
|
|
518
|
-
},
|
|
519
|
-
...shouldPromote ? { memory_tier: "reference" } : {}
|
|
520
|
-
});
|
|
521
|
-
return { mode: "reinforced", entityId: fullEntity.id };
|
|
522
|
-
}
|
|
523
|
-
} catch {}
|
|
524
|
-
return {
|
|
525
|
-
mode: "created",
|
|
526
|
-
learning: {
|
|
527
|
-
title: `Procedure: ${session.cardTitle}`,
|
|
528
|
-
content: newContent,
|
|
529
|
-
type: "procedure",
|
|
530
|
-
tier: "episode",
|
|
531
|
-
confidence: 0.7,
|
|
532
|
-
tags,
|
|
533
|
-
metadata: {
|
|
534
|
-
source: "active_learning",
|
|
535
|
-
card_id: session.cardId,
|
|
536
|
-
source_cards: [session.cardId],
|
|
537
|
-
step_count: steps.length,
|
|
538
|
-
key_step_count: enrichedSteps.filter((s) => s.isKeyDecision).length,
|
|
539
|
-
reuse_count: 0
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
};
|
|
543
|
-
}
|
|
544
|
-
var SESSION_CAUSAL_RULES = [
|
|
545
|
-
{
|
|
546
|
-
sourceType: "error",
|
|
547
|
-
targetType: "solution",
|
|
548
|
-
relation: "resolved_by",
|
|
549
|
-
confidence: 0.8
|
|
550
|
-
},
|
|
551
|
-
{
|
|
552
|
-
sourceType: "lesson",
|
|
553
|
-
targetType: "error",
|
|
554
|
-
relation: "learned_from",
|
|
555
|
-
confidence: 0.75
|
|
556
|
-
}
|
|
557
|
-
];
|
|
558
|
-
async function linkSessionEntities(client, createdPairs, _workspaceId, _projectId) {
|
|
559
|
-
let relationsCreated = 0;
|
|
560
|
-
for (const rule of SESSION_CAUSAL_RULES) {
|
|
561
|
-
const sources = createdPairs.filter((p) => p.learning.type === rule.sourceType);
|
|
562
|
-
const targets = createdPairs.filter((p) => p.learning.type === rule.targetType);
|
|
563
|
-
for (const source of sources) {
|
|
564
|
-
for (const target of targets) {
|
|
565
|
-
try {
|
|
566
|
-
await client.createMemoryRelation({
|
|
567
|
-
source_id: source.id,
|
|
568
|
-
target_id: target.id,
|
|
569
|
-
relation_type: rule.relation,
|
|
570
|
-
confidence: rule.confidence
|
|
571
|
-
});
|
|
572
|
-
relationsCreated++;
|
|
573
|
-
} catch {}
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
return { relationsCreated };
|
|
578
|
-
}
|
|
579
|
-
async function extractLearnings(client, session) {
|
|
580
|
-
const workspaceId = getActiveWorkspaceId();
|
|
581
|
-
if (!workspaceId) {
|
|
582
|
-
return { count: 0, entityIds: [] };
|
|
583
|
-
}
|
|
584
|
-
const projectId = getActiveProjectId() || undefined;
|
|
585
|
-
const learnings = [];
|
|
586
|
-
let relatedEntityTitles = [];
|
|
587
|
-
if (workspaceId) {
|
|
588
|
-
try {
|
|
589
|
-
const searchQuery = [
|
|
590
|
-
session.cardTitle,
|
|
591
|
-
session.currentTask || "",
|
|
592
|
-
...session.cardLabels
|
|
593
|
-
].filter(Boolean).join(" ");
|
|
594
|
-
const similar = await findSimilarEntities(client, searchQuery, session.currentTask || session.cardTitle, workspaceId, { projectId, limit: 5, minRrfScore: 0.01 });
|
|
595
|
-
relatedEntityTitles = similar.slice(0, 3).map((e) => e.title);
|
|
596
|
-
} catch {}
|
|
597
|
-
}
|
|
598
|
-
const wikiLinksLine = relatedEntityTitles.length > 0 ? `
|
|
599
|
-
Related: ${relatedEntityTitles.map((t) => `[[${t}]]`).join(", ")}` : "";
|
|
600
|
-
if (session.blockers && session.blockers.length > 0) {
|
|
601
|
-
for (const blocker of session.blockers) {
|
|
602
|
-
if (blocker.length < 80)
|
|
603
|
-
continue;
|
|
604
|
-
let isDuplicate = false;
|
|
605
|
-
try {
|
|
606
|
-
const similar = await findSimilarEntities(client, blocker.slice(0, 200), blocker, workspaceId, { projectId, limit: 3, minRrfScore: 0.05 });
|
|
607
|
-
isDuplicate = similar.some((e) => e.type === "error" && (e.rrf_score ?? 0) >= 0.06);
|
|
608
|
-
} catch {}
|
|
609
|
-
if (!isDuplicate) {
|
|
610
|
-
learnings.push({
|
|
611
|
-
title: `Blocker: ${blocker.slice(0, 100)}`,
|
|
612
|
-
content: `Encountered while working on "${session.cardTitle}":
|
|
613
|
-
|
|
614
|
-
${blocker}
|
|
615
|
-
|
|
616
|
-
Agent: ${session.agentName}
|
|
617
|
-
Session status: ${session.status}`,
|
|
618
|
-
type: "error",
|
|
619
|
-
tier: "episode",
|
|
620
|
-
confidence: 0.6,
|
|
621
|
-
tags: [
|
|
622
|
-
"auto-extracted",
|
|
623
|
-
"blocker",
|
|
624
|
-
...session.cardLabels.slice(0, 3)
|
|
625
|
-
],
|
|
626
|
-
metadata: {
|
|
627
|
-
source: "active_learning",
|
|
628
|
-
card_id: session.cardId
|
|
629
|
-
}
|
|
630
|
-
});
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
if (session.status === "paused" && (session.blockers?.length ?? 0) > 0) {
|
|
635
|
-
const durationInfo = session.sessionDurationMs ? `
|
|
636
|
-
Duration: ${Math.round(session.sessionDurationMs / 60000)} minutes` : "";
|
|
637
|
-
learnings.push({
|
|
638
|
-
title: `Paused: ${session.cardTitle}`,
|
|
639
|
-
content: [
|
|
640
|
-
`Paused work on "${session.cardTitle}".`,
|
|
641
|
-
session.currentTask ? `Last task: ${session.currentTask}` : "",
|
|
642
|
-
session.progressPercent !== undefined ? `Progress: ${session.progressPercent}%` : "",
|
|
643
|
-
durationInfo,
|
|
644
|
-
session.blockers?.length ? `Blockers: ${session.blockers.join("; ")}` : "",
|
|
645
|
-
`
|
|
646
|
-
Agent: ${session.agentName}`,
|
|
647
|
-
wikiLinksLine
|
|
648
|
-
].filter(Boolean).join(`
|
|
649
|
-
`),
|
|
650
|
-
type: "lesson",
|
|
651
|
-
tier: "draft",
|
|
652
|
-
confidence: 0.6,
|
|
653
|
-
tags: [
|
|
654
|
-
"auto-extracted",
|
|
655
|
-
"session-paused",
|
|
656
|
-
...session.cardLabels.slice(0, 3)
|
|
657
|
-
],
|
|
658
|
-
metadata: {
|
|
659
|
-
source: "active_learning",
|
|
660
|
-
card_id: session.cardId
|
|
661
|
-
}
|
|
662
|
-
});
|
|
663
|
-
}
|
|
664
|
-
const entityIds = [];
|
|
665
|
-
const stepHistory = sessionTaskHistory.get(session.cardId);
|
|
666
|
-
const MIN_PROCEDURE_STEPS = 5;
|
|
667
|
-
const MIN_PROCEDURE_DURATION_MS = 10 * 60 * 1000;
|
|
668
|
-
const hasEnoughSteps = stepHistory && stepHistory.steps.length >= MIN_PROCEDURE_STEPS;
|
|
669
|
-
const hasMinDuration = (session.sessionDurationMs ?? 0) >= MIN_PROCEDURE_DURATION_MS;
|
|
670
|
-
const isSuccessful = session.status === "completed" && (session.progressPercent === undefined || session.progressPercent >= 85) && !session.blockers?.length;
|
|
671
|
-
if (isSuccessful && hasEnoughSteps && hasMinDuration) {
|
|
672
|
-
const procedureResult = await extractOrReinforceProcedure(client, session, stepHistory.steps, workspaceId, projectId, wikiLinksLine);
|
|
673
|
-
if (procedureResult) {
|
|
674
|
-
if (procedureResult.mode === "created") {
|
|
675
|
-
learnings.push(procedureResult.learning);
|
|
676
|
-
} else {
|
|
677
|
-
entityIds.push(procedureResult.entityId);
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
const createdPairs = [];
|
|
682
|
-
for (const learning of learnings) {
|
|
683
|
-
try {
|
|
684
|
-
const metadata = {
|
|
685
|
-
...learning.metadata
|
|
686
|
-
};
|
|
687
|
-
const result = await client.createMemoryEntity({
|
|
688
|
-
workspace_id: workspaceId,
|
|
689
|
-
project_id: projectId,
|
|
690
|
-
type: learning.type,
|
|
691
|
-
scope: "project",
|
|
692
|
-
memory_tier: learning.tier,
|
|
693
|
-
title: learning.title,
|
|
694
|
-
content: learning.content,
|
|
695
|
-
confidence: learning.confidence,
|
|
696
|
-
tags: learning.tags,
|
|
697
|
-
metadata,
|
|
698
|
-
agent_identifier: session.agentIdentifier
|
|
699
|
-
});
|
|
700
|
-
const entity = result.entity;
|
|
701
|
-
if (entity?.id) {
|
|
702
|
-
entityIds.push(entity.id);
|
|
703
|
-
createdPairs.push({ id: entity.id, learning });
|
|
704
|
-
}
|
|
705
|
-
} catch {}
|
|
706
|
-
}
|
|
707
|
-
for (const { id, learning } of createdPairs) {
|
|
708
|
-
autoExpandGraph(client, id, learning.title, learning.content, learning.tags, workspaceId, projectId).catch(() => {});
|
|
709
|
-
detectContradictions(client, id, learning.type, learning.title, learning.content, learning.tags, workspaceId, projectId).catch(() => {});
|
|
710
|
-
linkCrossTypeNeighbors(client, id, learning.type, learning.title, learning.content, workspaceId, projectId).catch(() => {});
|
|
711
|
-
}
|
|
712
|
-
if (createdPairs.length >= 2) {
|
|
713
|
-
linkSessionEntities(client, createdPairs, workspaceId, projectId).catch(() => {});
|
|
714
|
-
}
|
|
715
|
-
clearMidSessionTracking(session.cardId);
|
|
716
|
-
return { count: entityIds.length, entityIds };
|
|
717
|
-
}
|
|
718
|
-
var PATTERN_THRESHOLD = 3;
|
|
719
|
-
async function detectAndCreatePatterns(client, newEntityIds, session, workspaceId, projectId) {
|
|
720
|
-
const patternEntityIds = [];
|
|
721
|
-
for (const newEntityId of newEntityIds) {
|
|
722
|
-
try {
|
|
723
|
-
const { entity: rawEntity } = await client.getMemoryEntity(newEntityId);
|
|
724
|
-
const entity = rawEntity;
|
|
725
|
-
if (!entity?.type)
|
|
726
|
-
continue;
|
|
727
|
-
const similar = await findSimilarEntities(client, entity.title, entity.content, workspaceId, { projectId, limit: 30, minRrfScore: 0.01 });
|
|
728
|
-
const existing = similar.filter((c) => !newEntityIds.includes(c.id) && c.type === entity.type);
|
|
729
|
-
if (existing.length < PATTERN_THRESHOLD)
|
|
730
|
-
continue;
|
|
731
|
-
const memberTitles = [
|
|
732
|
-
entity.title,
|
|
733
|
-
...existing.slice(0, 4).map((e) => e.title)
|
|
734
|
-
];
|
|
735
|
-
const patternTitle = `Pattern: recurring ${entity.type} (${existing.length + 1} instances)`;
|
|
736
|
-
const { entities: existingPatterns } = await client.listMemoryEntities({
|
|
737
|
-
workspace_id: workspaceId,
|
|
738
|
-
project_id: projectId,
|
|
739
|
-
type: "pattern",
|
|
740
|
-
limit: 10
|
|
741
|
-
});
|
|
742
|
-
const matchingPattern = existingPatterns.find((p) => p.metadata?.pattern_type === entity.type);
|
|
743
|
-
let patternId = null;
|
|
744
|
-
if (matchingPattern) {
|
|
745
|
-
patternId = matchingPattern.id;
|
|
746
|
-
await client.updateMemoryEntity(patternId, {
|
|
747
|
-
content: `Recurring pattern: ${entity.type} entities appearing ${existing.length + 1} times.
|
|
748
|
-
|
|
749
|
-
Members:
|
|
750
|
-
${memberTitles.map((t) => `- ${t}`).join(`
|
|
751
|
-
`)}
|
|
752
|
-
|
|
753
|
-
Last updated: ${new Date().toISOString()}`,
|
|
754
|
-
metadata: {
|
|
755
|
-
pattern_count: existing.length + 1,
|
|
756
|
-
pattern_type: entity.type,
|
|
757
|
-
last_updated: new Date().toISOString()
|
|
758
|
-
}
|
|
759
|
-
});
|
|
760
|
-
} else {
|
|
761
|
-
const result = await client.createMemoryEntity({
|
|
762
|
-
workspace_id: workspaceId,
|
|
763
|
-
project_id: projectId,
|
|
764
|
-
type: "pattern",
|
|
765
|
-
scope: "project",
|
|
766
|
-
memory_tier: "reference",
|
|
767
|
-
title: patternTitle,
|
|
768
|
-
content: `Recurring pattern: ${entity.type} entities detected ${existing.length + 1} times.
|
|
769
|
-
|
|
770
|
-
Members:
|
|
771
|
-
${memberTitles.map((t) => `- ${t}`).join(`
|
|
772
|
-
`)}`,
|
|
773
|
-
confidence: 0.75,
|
|
774
|
-
tags: ["auto-extracted", "pattern", entity.type],
|
|
775
|
-
metadata: {
|
|
776
|
-
source: "pattern_detection",
|
|
777
|
-
pattern_type: entity.type,
|
|
778
|
-
pattern_count: existing.length + 1
|
|
779
|
-
},
|
|
780
|
-
agent_identifier: session.agentIdentifier
|
|
781
|
-
});
|
|
782
|
-
const created = result.entity;
|
|
783
|
-
if (created?.id) {
|
|
784
|
-
patternId = created.id;
|
|
785
|
-
patternEntityIds.push(patternId);
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
if (!patternId)
|
|
789
|
-
continue;
|
|
790
|
-
const toLink = [newEntityId, ...existing.slice(0, 4).map((e) => e.id)];
|
|
791
|
-
for (const sourceId of toLink) {
|
|
792
|
-
try {
|
|
793
|
-
await client.createMemoryRelation({
|
|
794
|
-
source_id: sourceId,
|
|
795
|
-
target_id: patternId,
|
|
796
|
-
relation_type: "part_of",
|
|
797
|
-
confidence: 0.75
|
|
798
|
-
});
|
|
799
|
-
} catch {}
|
|
800
|
-
}
|
|
801
|
-
} catch {}
|
|
802
|
-
}
|
|
803
|
-
return patternEntityIds;
|
|
804
|
-
}
|
|
805
|
-
var CAUSAL_PATTERN_THRESHOLD = 3;
|
|
806
|
-
async function detectCausalPatterns(client, createdPairs, session, workspaceId, projectId) {
|
|
807
|
-
const patternIds = [];
|
|
808
|
-
const errors = createdPairs.filter((p) => p.learning.type === "error");
|
|
809
|
-
const solutions = createdPairs.filter((p) => p.learning.type === "solution");
|
|
810
|
-
if (errors.length === 0 || solutions.length === 0)
|
|
811
|
-
return patternIds;
|
|
812
|
-
for (const errorPair of errors) {
|
|
813
|
-
try {
|
|
814
|
-
const similarErrors = await findSimilarEntities(client, errorPair.learning.title, errorPair.learning.content, workspaceId, {
|
|
815
|
-
projectId,
|
|
816
|
-
limit: 20,
|
|
817
|
-
minRrfScore: 0.03,
|
|
818
|
-
excludeIds: createdPairs.map((p) => p.id),
|
|
819
|
-
type: "error"
|
|
820
|
-
});
|
|
821
|
-
const resolvedErrors = [];
|
|
822
|
-
for (const similar of similarErrors.slice(0, 10)) {
|
|
823
|
-
try {
|
|
824
|
-
const { outgoing } = await client.getRelatedEntities(similar.id);
|
|
825
|
-
const resolvedByRel = outgoing.find((r) => r.relation_type === "resolved_by");
|
|
826
|
-
if (resolvedByRel) {
|
|
827
|
-
resolvedErrors.push({
|
|
828
|
-
errorId: similar.id,
|
|
829
|
-
errorTitle: similar.title,
|
|
830
|
-
solutionTitle: resolvedByRel.target_title || "unknown"
|
|
831
|
-
});
|
|
832
|
-
}
|
|
833
|
-
} catch {}
|
|
834
|
-
}
|
|
835
|
-
if (resolvedErrors.length + 1 < CAUSAL_PATTERN_THRESHOLD)
|
|
836
|
-
continue;
|
|
837
|
-
const { entities: existingPatterns } = await client.listMemoryEntities({
|
|
838
|
-
workspace_id: workspaceId,
|
|
839
|
-
project_id: projectId,
|
|
840
|
-
type: "pattern",
|
|
841
|
-
limit: 10
|
|
842
|
-
});
|
|
843
|
-
const matchingPattern = existingPatterns.find((p) => p.metadata?.pattern_chain_type === "error_resolved_by_solution");
|
|
844
|
-
if (matchingPattern) {
|
|
845
|
-
await client.updateMemoryEntity(matchingPattern.id, {
|
|
846
|
-
content: [
|
|
847
|
-
`Recurring error→solution chain detected (${resolvedErrors.length + 1} instances).`,
|
|
848
|
-
"",
|
|
849
|
-
"## Error→Solution Pairs",
|
|
850
|
-
`- ${errorPair.learning.title} → ${solutions[0].learning.title}`,
|
|
851
|
-
...resolvedErrors.slice(0, 5).map((r) => `- ${r.errorTitle} → ${r.solutionTitle}`),
|
|
852
|
-
"",
|
|
853
|
-
`Last updated: ${new Date().toISOString()}`
|
|
854
|
-
].join(`
|
|
855
|
-
`),
|
|
856
|
-
metadata: {
|
|
857
|
-
pattern_chain_type: "error_resolved_by_solution",
|
|
858
|
-
pattern_count: resolvedErrors.length + 1,
|
|
859
|
-
last_updated: new Date().toISOString()
|
|
860
|
-
}
|
|
861
|
-
});
|
|
862
|
-
for (const pair of [errorPair, solutions[0]]) {
|
|
863
|
-
try {
|
|
864
|
-
await client.createMemoryRelation({
|
|
865
|
-
source_id: pair.id,
|
|
866
|
-
target_id: matchingPattern.id,
|
|
867
|
-
relation_type: "part_of",
|
|
868
|
-
confidence: 0.75
|
|
869
|
-
});
|
|
870
|
-
} catch {}
|
|
871
|
-
}
|
|
872
|
-
} else {
|
|
873
|
-
const result = await client.createMemoryEntity({
|
|
874
|
-
workspace_id: workspaceId,
|
|
875
|
-
project_id: projectId,
|
|
876
|
-
type: "pattern",
|
|
877
|
-
scope: "project",
|
|
878
|
-
memory_tier: "reference",
|
|
879
|
-
title: `Pattern: recurring error→solution chain (${resolvedErrors.length + 1} instances)`,
|
|
880
|
-
content: [
|
|
881
|
-
`Recurring error→solution chain detected across ${resolvedErrors.length + 1} sessions.`,
|
|
882
|
-
"",
|
|
883
|
-
"## Error→Solution Pairs",
|
|
884
|
-
`- ${errorPair.learning.title} → ${solutions[0].learning.title}`,
|
|
885
|
-
...resolvedErrors.slice(0, 5).map((r) => `- ${r.errorTitle} → ${r.solutionTitle}`)
|
|
886
|
-
].join(`
|
|
887
|
-
`),
|
|
888
|
-
confidence: 0.8,
|
|
889
|
-
tags: ["auto-extracted", "pattern", "causal-chain"],
|
|
890
|
-
metadata: {
|
|
891
|
-
source: "causal_pattern_detection",
|
|
892
|
-
pattern_chain_type: "error_resolved_by_solution",
|
|
893
|
-
pattern_count: resolvedErrors.length + 1
|
|
894
|
-
},
|
|
895
|
-
agent_identifier: session.agentIdentifier
|
|
896
|
-
});
|
|
897
|
-
const created = result.entity;
|
|
898
|
-
if (created?.id) {
|
|
899
|
-
patternIds.push(created.id);
|
|
900
|
-
const memberIds = [
|
|
901
|
-
errorPair.id,
|
|
902
|
-
solutions[0].id,
|
|
903
|
-
...resolvedErrors.slice(0, 4).map((r) => r.errorId)
|
|
904
|
-
];
|
|
905
|
-
for (const memberId of memberIds) {
|
|
906
|
-
try {
|
|
907
|
-
await client.createMemoryRelation({
|
|
908
|
-
source_id: memberId,
|
|
909
|
-
target_id: created.id,
|
|
910
|
-
relation_type: "part_of",
|
|
911
|
-
confidence: 0.75
|
|
912
|
-
});
|
|
913
|
-
} catch {}
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
} catch {}
|
|
918
|
-
}
|
|
919
|
-
return patternIds;
|
|
920
|
-
}
|
|
921
|
-
async function detectContradictions(client, entityId, entityType, title, content, tags, workspaceId, projectId) {
|
|
922
|
-
if (!CONTRADICTION_TYPES.has(entityType))
|
|
923
|
-
return [];
|
|
924
|
-
if (!title.trim())
|
|
925
|
-
return [];
|
|
926
|
-
try {
|
|
927
|
-
const similar = await findSimilarEntities(client, title, content, workspaceId, {
|
|
928
|
-
projectId,
|
|
929
|
-
limit: 10,
|
|
930
|
-
excludeIds: [entityId]
|
|
931
|
-
});
|
|
932
|
-
const candidates = similar.filter((e) => e.type === entityType && CONTRADICTION_TYPES.has(e.type));
|
|
933
|
-
if (candidates.length === 0)
|
|
934
|
-
return [];
|
|
935
|
-
const contradictions = [];
|
|
936
|
-
for (const candidate of candidates) {
|
|
937
|
-
if ((candidate.rrf_score ?? 0) > 0.8)
|
|
938
|
-
continue;
|
|
939
|
-
if ((candidate.rrf_score ?? 0) < 0.02)
|
|
940
|
-
continue;
|
|
941
|
-
const sharedTags = tags.filter((t) => candidate.tags?.includes(t));
|
|
942
|
-
if (tags.length > 0 && sharedTags.length === 0)
|
|
943
|
-
continue;
|
|
944
|
-
contradictions.push({
|
|
945
|
-
entityId: candidate.id,
|
|
946
|
-
title: candidate.title,
|
|
947
|
-
type: candidate.type,
|
|
948
|
-
tags: candidate.tags || []
|
|
949
|
-
});
|
|
950
|
-
}
|
|
951
|
-
for (const c of contradictions) {
|
|
952
|
-
try {
|
|
953
|
-
await client.createMemoryRelation({
|
|
954
|
-
source_id: entityId,
|
|
955
|
-
target_id: c.entityId,
|
|
956
|
-
relation_type: "contradicts",
|
|
957
|
-
confidence: 0.5
|
|
958
|
-
});
|
|
959
|
-
} catch {}
|
|
960
|
-
}
|
|
961
|
-
return contradictions;
|
|
962
|
-
} catch {
|
|
963
|
-
return [];
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
export {
|
|
967
|
-
linkSessionEntities,
|
|
968
|
-
extractMidSessionLearnings,
|
|
969
|
-
extractLearnings,
|
|
970
|
-
detectContradictions,
|
|
971
|
-
detectCausalPatterns,
|
|
972
|
-
detectAndCreatePatterns,
|
|
973
|
-
clearMidSessionTracking
|
|
974
|
-
};
|