@gethmy/mcp 2.2.3 → 2.3.0
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/cli.js +780 -352
- package/dist/index.js +744 -351
- package/dist/lib/active-learning.js +73 -129
- package/dist/lib/consolidation.js +71 -11
- package/dist/lib/context-assembly.js +287 -30
- package/dist/lib/memory-cleanup.js +426 -0
- package/dist/lib/prompt-builder.js +5 -1
- package/dist/lib/server.js +63 -0
- package/dist/lib/skills.js +25 -1
- package/dist/lib/tui/setup.js +11 -0
- package/package.json +1 -1
- package/src/active-learning.ts +83 -145
- package/src/consolidation.ts +81 -12
- package/src/context-assembly.ts +342 -30
- package/src/memory-cleanup.ts +616 -0
- package/src/prompt-builder.ts +13 -1
- package/src/server.ts +74 -0
- package/src/skills.ts +25 -1
- package/src/tui/setup.ts +11 -0
package/dist/index.js
CHANGED
|
@@ -7070,6 +7070,7 @@ __export(exports_context_assembly, {
|
|
|
7070
7070
|
mapToContextEntity: () => mapToContextEntity,
|
|
7071
7071
|
getSessionAssemblyId: () => getSessionAssemblyId,
|
|
7072
7072
|
getCachedManifest: () => getCachedManifest,
|
|
7073
|
+
expandQuery: () => expandQuery,
|
|
7073
7074
|
computeRelevanceScore: () => computeRelevanceScore,
|
|
7074
7075
|
cacheManifest: () => cacheManifest,
|
|
7075
7076
|
assembleContext: () => assembleContext
|
|
@@ -7077,6 +7078,29 @@ __export(exports_context_assembly, {
|
|
|
7077
7078
|
function estimateTokens(text) {
|
|
7078
7079
|
return Math.ceil(text.length / 4);
|
|
7079
7080
|
}
|
|
7081
|
+
function passesQualityGate(entity) {
|
|
7082
|
+
const content = entity.content.trim();
|
|
7083
|
+
if (content.length < 50)
|
|
7084
|
+
return false;
|
|
7085
|
+
const normalizedTitle = entity.title.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim();
|
|
7086
|
+
const normalizedContent = content.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim();
|
|
7087
|
+
if (normalizedContent.length < normalizedTitle.length * 1.5) {
|
|
7088
|
+
return false;
|
|
7089
|
+
}
|
|
7090
|
+
if (entity.type === "pattern" && /recurring .+ \(\d+ instances\)/i.test(entity.title)) {
|
|
7091
|
+
const lines = content.split(`
|
|
7092
|
+
`).filter((l) => l.trim().length > 0);
|
|
7093
|
+
const bulletLines = lines.filter((l) => l.trim().startsWith("- "));
|
|
7094
|
+
if (bulletLines.length > lines.length * 0.6)
|
|
7095
|
+
return false;
|
|
7096
|
+
}
|
|
7097
|
+
if (entity.type === "procedure") {
|
|
7098
|
+
const stepCount = (content.match(/^\d+\.\s/gm) || []).length;
|
|
7099
|
+
if (stepCount < 3)
|
|
7100
|
+
return false;
|
|
7101
|
+
}
|
|
7102
|
+
return true;
|
|
7103
|
+
}
|
|
7080
7104
|
function generateAssemblyId() {
|
|
7081
7105
|
return `ctx_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
7082
7106
|
}
|
|
@@ -7108,7 +7132,45 @@ function truncateContent(content, maxTokens) {
|
|
|
7108
7132
|
}
|
|
7109
7133
|
return { text: result, truncated: true };
|
|
7110
7134
|
}
|
|
7111
|
-
function
|
|
7135
|
+
function escapeRegex2(str) {
|
|
7136
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7137
|
+
}
|
|
7138
|
+
function expandQuery(taskContext) {
|
|
7139
|
+
const queries = [taskContext];
|
|
7140
|
+
const lowerQueries = [taskContext.toLowerCase()];
|
|
7141
|
+
const words = taskContext.toLowerCase().split(/\W+/).filter((w) => w.length > 2);
|
|
7142
|
+
const expandableWords = words.filter((w) => QUERY_SYNONYMS[w]);
|
|
7143
|
+
for (const word of expandableWords) {
|
|
7144
|
+
const synonyms = QUERY_SYNONYMS[word];
|
|
7145
|
+
if (!synonyms)
|
|
7146
|
+
continue;
|
|
7147
|
+
const variation = taskContext.replace(new RegExp(`\\b${escapeRegex2(word)}\\b`, "gi"), synonyms[0]);
|
|
7148
|
+
const lowerVariation = variation.toLowerCase();
|
|
7149
|
+
if (lowerVariation !== taskContext.toLowerCase() && !lowerQueries.includes(lowerVariation)) {
|
|
7150
|
+
queries.push(variation);
|
|
7151
|
+
lowerQueries.push(lowerVariation);
|
|
7152
|
+
}
|
|
7153
|
+
if (queries.length >= MAX_QUERY_VARIATIONS)
|
|
7154
|
+
break;
|
|
7155
|
+
}
|
|
7156
|
+
if (words.length >= 3) {
|
|
7157
|
+
const keyPhrases = words.filter((w) => ![
|
|
7158
|
+
"the",
|
|
7159
|
+
"and",
|
|
7160
|
+
"for",
|
|
7161
|
+
"with",
|
|
7162
|
+
"this",
|
|
7163
|
+
"that",
|
|
7164
|
+
"from",
|
|
7165
|
+
"into"
|
|
7166
|
+
].includes(w)).slice(0, 4).join(" ");
|
|
7167
|
+
if (!lowerQueries.includes(keyPhrases)) {
|
|
7168
|
+
queries.push(keyPhrases);
|
|
7169
|
+
}
|
|
7170
|
+
}
|
|
7171
|
+
return queries.slice(0, MAX_QUERY_VARIATIONS);
|
|
7172
|
+
}
|
|
7173
|
+
function computeRelevanceScore(entity, taskContext, cardLabels, graphRelations) {
|
|
7112
7174
|
const reasons = [];
|
|
7113
7175
|
let score = 0;
|
|
7114
7176
|
const hasRrfScore = entity.rrf_score !== undefined && entity.rrf_score > 0;
|
|
@@ -7167,7 +7229,23 @@ function computeRelevanceScore(entity, taskContext, cardLabels) {
|
|
|
7167
7229
|
score += 0.1;
|
|
7168
7230
|
reasons.push("procedure_boost");
|
|
7169
7231
|
}
|
|
7170
|
-
|
|
7232
|
+
if (graphRelations && graphRelations.length > 0) {
|
|
7233
|
+
const entityRelations = graphRelations.filter((r) => r.source_id === entity.id || r.target_id === entity.id);
|
|
7234
|
+
if (entityRelations.length > 0) {
|
|
7235
|
+
let bestBonus = 0;
|
|
7236
|
+
let bestRelType = "";
|
|
7237
|
+
for (const rel of entityRelations) {
|
|
7238
|
+
const bonus = RELATION_BONUSES[rel.relation_type] ?? 0.1;
|
|
7239
|
+
if (bonus > bestBonus) {
|
|
7240
|
+
bestBonus = bonus;
|
|
7241
|
+
bestRelType = rel.relation_type;
|
|
7242
|
+
}
|
|
7243
|
+
}
|
|
7244
|
+
score += bestBonus;
|
|
7245
|
+
reasons.push(`graph_walk(${bestRelType})`);
|
|
7246
|
+
}
|
|
7247
|
+
}
|
|
7248
|
+
score = Math.max(0, Math.min(score, 1));
|
|
7171
7249
|
const tierWeight = TIER_WEIGHTS[entity.memory_tier];
|
|
7172
7250
|
score *= tierWeight;
|
|
7173
7251
|
return { score, reasons };
|
|
@@ -7179,7 +7257,11 @@ async function assembleContext(options) {
|
|
|
7179
7257
|
taskContext,
|
|
7180
7258
|
cardLabels = [],
|
|
7181
7259
|
tokenBudget = DEFAULT_TOKEN_BUDGET,
|
|
7182
|
-
client: client2
|
|
7260
|
+
client: client2,
|
|
7261
|
+
graphWalkEnabled = true,
|
|
7262
|
+
queryExpansionEnabled = true,
|
|
7263
|
+
enableLlmReranking = false,
|
|
7264
|
+
rerankFn
|
|
7183
7265
|
} = options;
|
|
7184
7266
|
const assemblyId = generateAssemblyId();
|
|
7185
7267
|
const manifest = {
|
|
@@ -7195,13 +7277,26 @@ async function assembleContext(options) {
|
|
|
7195
7277
|
reference: { count: 0, tokens: 0 }
|
|
7196
7278
|
}
|
|
7197
7279
|
};
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
7280
|
+
const candidates = [];
|
|
7281
|
+
const queries = queryExpansionEnabled ? expandQuery(taskContext) : [taskContext];
|
|
7282
|
+
const searchResults = await Promise.allSettled(queries.map((query) => client2.searchMemoryEntities(workspaceId, query, {
|
|
7283
|
+
project_id: projectId,
|
|
7284
|
+
limit: 30
|
|
7285
|
+
})));
|
|
7286
|
+
const candidateIds = new Set;
|
|
7287
|
+
for (const result of searchResults) {
|
|
7288
|
+
if (result.status !== "fulfilled")
|
|
7289
|
+
continue;
|
|
7290
|
+
if (result.value.entities?.length > 0) {
|
|
7291
|
+
for (const raw of result.value.entities) {
|
|
7292
|
+
const entity = mapToContextEntity(raw);
|
|
7293
|
+
if (!candidateIds.has(entity.id)) {
|
|
7294
|
+
candidateIds.add(entity.id);
|
|
7295
|
+
candidates.push(entity);
|
|
7296
|
+
}
|
|
7297
|
+
}
|
|
7203
7298
|
}
|
|
7204
|
-
}
|
|
7299
|
+
}
|
|
7205
7300
|
if (candidates.length < 10 && projectId) {
|
|
7206
7301
|
try {
|
|
7207
7302
|
const listResult = await client2.listMemoryEntities({
|
|
@@ -7210,9 +7305,13 @@ async function assembleContext(options) {
|
|
|
7210
7305
|
limit: 30
|
|
7211
7306
|
});
|
|
7212
7307
|
if (listResult.entities?.length > 0) {
|
|
7213
|
-
const
|
|
7214
|
-
|
|
7215
|
-
|
|
7308
|
+
for (const raw of listResult.entities) {
|
|
7309
|
+
const entity = mapToContextEntity(raw);
|
|
7310
|
+
if (!candidateIds.has(entity.id)) {
|
|
7311
|
+
candidateIds.add(entity.id);
|
|
7312
|
+
candidates.push(entity);
|
|
7313
|
+
}
|
|
7314
|
+
}
|
|
7216
7315
|
}
|
|
7217
7316
|
} catch {}
|
|
7218
7317
|
}
|
|
@@ -7224,9 +7323,33 @@ async function assembleContext(options) {
|
|
|
7224
7323
|
limit: 20
|
|
7225
7324
|
});
|
|
7226
7325
|
if (wsResult.entities?.length > 0) {
|
|
7227
|
-
const
|
|
7228
|
-
|
|
7229
|
-
|
|
7326
|
+
for (const raw of wsResult.entities) {
|
|
7327
|
+
const entity = mapToContextEntity(raw);
|
|
7328
|
+
if (!candidateIds.has(entity.id)) {
|
|
7329
|
+
candidateIds.add(entity.id);
|
|
7330
|
+
candidates.push(entity);
|
|
7331
|
+
}
|
|
7332
|
+
}
|
|
7333
|
+
}
|
|
7334
|
+
} catch {}
|
|
7335
|
+
}
|
|
7336
|
+
let graphRelations = [];
|
|
7337
|
+
if (graphWalkEnabled && candidates.length > 0) {
|
|
7338
|
+
try {
|
|
7339
|
+
const seedCandidates = [...candidates].sort((a, b) => (b.rrf_score ?? 0) - (a.rrf_score ?? 0)).slice(0, GRAPH_WALK_SEED_COUNT);
|
|
7340
|
+
const seedIds = seedCandidates.map((c) => c.id);
|
|
7341
|
+
const walkResult = await discoverRelatedContext(client2, seedIds, GRAPH_WALK_MAX_DEPTH, GRAPH_WALK_MAX_ENTITIES, GRAPH_WALK_MIN_CONFIDENCE);
|
|
7342
|
+
graphRelations = walkResult.relations;
|
|
7343
|
+
const newEntityIds = walkResult.entities.filter((e) => !candidateIds.has(e.id)).map((e) => e.id);
|
|
7344
|
+
if (newEntityIds.length > 0) {
|
|
7345
|
+
const fetchResults = await Promise.allSettled(newEntityIds.map((id) => client2.getMemoryEntity(id)));
|
|
7346
|
+
for (const result of fetchResults) {
|
|
7347
|
+
if (result.status !== "fulfilled" || !result.value.entity)
|
|
7348
|
+
continue;
|
|
7349
|
+
const mapped = mapToContextEntity(result.value.entity);
|
|
7350
|
+
candidateIds.add(mapped.id);
|
|
7351
|
+
candidates.push(mapped);
|
|
7352
|
+
}
|
|
7230
7353
|
}
|
|
7231
7354
|
} catch {}
|
|
7232
7355
|
}
|
|
@@ -7237,11 +7360,52 @@ async function assembleContext(options) {
|
|
|
7237
7360
|
memories: []
|
|
7238
7361
|
};
|
|
7239
7362
|
}
|
|
7240
|
-
const
|
|
7241
|
-
|
|
7363
|
+
const qualityCandidates = candidates.filter((entity) => {
|
|
7364
|
+
if (passesQualityGate(entity))
|
|
7365
|
+
return true;
|
|
7366
|
+
manifest.excluded.push({
|
|
7367
|
+
entityId: entity.id,
|
|
7368
|
+
title: entity.title,
|
|
7369
|
+
type: entity.type,
|
|
7370
|
+
tier: entity.memory_tier,
|
|
7371
|
+
relevanceScore: 0,
|
|
7372
|
+
reason: "failed_quality_gate"
|
|
7373
|
+
});
|
|
7374
|
+
return false;
|
|
7375
|
+
});
|
|
7376
|
+
if (qualityCandidates.length === 0) {
|
|
7377
|
+
return {
|
|
7378
|
+
context: "",
|
|
7379
|
+
manifest,
|
|
7380
|
+
memories: []
|
|
7381
|
+
};
|
|
7382
|
+
}
|
|
7383
|
+
const scored = qualityCandidates.map((entity) => {
|
|
7384
|
+
const { score, reasons } = computeRelevanceScore(entity, taskContext, cardLabels, graphRelations.length > 0 ? graphRelations : undefined);
|
|
7242
7385
|
return { entity, score, reasons };
|
|
7243
7386
|
});
|
|
7244
7387
|
scored.sort((a, b) => b.score - a.score);
|
|
7388
|
+
if (enableLlmReranking && rerankFn && scored.length >= RERANK_MIN_CANDIDATES) {
|
|
7389
|
+
const topN = scored.slice(0, RERANK_TOP_N);
|
|
7390
|
+
const scoreRange = topN[0].score - topN[topN.length - 1].score;
|
|
7391
|
+
if (scoreRange <= RERANK_CLUSTER_THRESHOLD) {
|
|
7392
|
+
try {
|
|
7393
|
+
const rerankCandidates = topN.map((s) => ({
|
|
7394
|
+
id: s.entity.id,
|
|
7395
|
+
title: s.entity.title,
|
|
7396
|
+
snippet: s.entity.content.slice(0, 200)
|
|
7397
|
+
}));
|
|
7398
|
+
const rerankedIds = await rerankFn(taskContext, rerankCandidates);
|
|
7399
|
+
const idOrder = new Map(rerankedIds.map((id, i) => [id, i]));
|
|
7400
|
+
topN.sort((a, b) => {
|
|
7401
|
+
const aIdx = idOrder.get(a.entity.id) ?? 999;
|
|
7402
|
+
const bIdx = idOrder.get(b.entity.id) ?? 999;
|
|
7403
|
+
return aIdx - bIdx;
|
|
7404
|
+
});
|
|
7405
|
+
scored.splice(0, topN.length, ...topN);
|
|
7406
|
+
} catch {}
|
|
7407
|
+
}
|
|
7408
|
+
}
|
|
7245
7409
|
const procedureBudget = Math.floor(tokenBudget * PROCEDURE_BUDGET_FRACTION);
|
|
7246
7410
|
const remainingBudget = tokenBudget - procedureBudget;
|
|
7247
7411
|
const tierBudgets = {
|
|
@@ -7553,7 +7717,7 @@ async function recordContextFeedback(client2, cardId, sessionStatus, progressPer
|
|
|
7553
7717
|
sessionAssemblyMap.delete(cardId);
|
|
7554
7718
|
return { adjusted };
|
|
7555
7719
|
}
|
|
7556
|
-
var DEFAULT_TOKEN_BUDGET = 4000, MAX_TOKENS_PER_ENTITY = 500, MIN_RELEVANCE_THRESHOLD = 0.
|
|
7720
|
+
var DEFAULT_TOKEN_BUDGET = 4000, MAX_TOKENS_PER_ENTITY = 500, MIN_RELEVANCE_THRESHOLD = 0.15, TIER_WEIGHTS, PROCEDURE_BUDGET_FRACTION = 0.15, TIER_BUDGET_ALLOCATION, MIN_REFERENCE_SLOTS = 1, GRAPH_WALK_MAX_DEPTH = 1, GRAPH_WALK_MAX_ENTITIES = 10, GRAPH_WALK_MIN_CONFIDENCE = 0.5, GRAPH_WALK_SEED_COUNT = 5, MAX_QUERY_VARIATIONS = 4, RERANK_CLUSTER_THRESHOLD = 0.05, RERANK_TOP_N = 10, RERANK_MIN_CANDIDATES = 5, RELATION_BONUSES, QUERY_SYNONYMS, manifestCache, MAX_CACHE_SIZE = 50, sessionAssemblyMap, MAX_SESSION_MAP_SIZE = 100;
|
|
7557
7721
|
var init_context_assembly = __esm(() => {
|
|
7558
7722
|
init_dist();
|
|
7559
7723
|
TIER_WEIGHTS = {
|
|
@@ -7566,6 +7730,33 @@ var init_context_assembly = __esm(() => {
|
|
|
7566
7730
|
episode: 0.3,
|
|
7567
7731
|
draft: 0.1
|
|
7568
7732
|
};
|
|
7733
|
+
RELATION_BONUSES = {
|
|
7734
|
+
depends_on: 0.15,
|
|
7735
|
+
resolved_by: 0.2,
|
|
7736
|
+
relates_to: 0.1,
|
|
7737
|
+
implements: 0.15,
|
|
7738
|
+
blocks: 0.15,
|
|
7739
|
+
references: 0.1,
|
|
7740
|
+
extends: 0.1,
|
|
7741
|
+
caused_by: 0.15
|
|
7742
|
+
};
|
|
7743
|
+
QUERY_SYNONYMS = {
|
|
7744
|
+
auth: ["authentication", "authorization", "session"],
|
|
7745
|
+
authentication: ["auth", "session", "sign-in"],
|
|
7746
|
+
login: ["sign-in", "authentication", "session"],
|
|
7747
|
+
bug: ["error", "issue", "defect", "problem"],
|
|
7748
|
+
error: ["exception", "failure", "issue"],
|
|
7749
|
+
fix: ["resolve", "patch", "repair", "correct"],
|
|
7750
|
+
deploy: ["deployment", "release", "ship", "publish"],
|
|
7751
|
+
test: ["testing", "spec", "assertion", "verify"],
|
|
7752
|
+
config: ["configuration", "settings", "setup"],
|
|
7753
|
+
db: ["database", "storage", "persistence"],
|
|
7754
|
+
database: ["storage", "persistence", "data store"],
|
|
7755
|
+
api: ["endpoint", "route", "service"],
|
|
7756
|
+
ui: ["frontend", "component", "view"],
|
|
7757
|
+
perf: ["performance", "speed", "latency"],
|
|
7758
|
+
performance: ["speed", "latency", "optimization"]
|
|
7759
|
+
};
|
|
7569
7760
|
manifestCache = new Map;
|
|
7570
7761
|
sessionAssemblyMap = new Map;
|
|
7571
7762
|
});
|
|
@@ -7694,7 +7885,11 @@ ${card.description}`);
|
|
|
7694
7885
|
roleFraming.focus.forEach((f) => {
|
|
7695
7886
|
sections.push(`- ${f}`);
|
|
7696
7887
|
});
|
|
7697
|
-
sections.push(`- **Memory:**
|
|
7888
|
+
sections.push(`- **Memory:** Store reusable knowledge via \`harmony_remember\`. Only store what a future agent couldn't easily discover from the code itself, applies beyond this specific card, and includes a "because" (not just what, but why).`);
|
|
7889
|
+
sections.push(` - GOOD: "BoardContext card state must use moveCard action, never direct setState — optimistic updates depend on action ordering"`);
|
|
7890
|
+
sections.push(` - GOOD: "Mobile bottom bar is 64px, overlaps fixed-position drawers — always add pb-16 to drawer content"`);
|
|
7891
|
+
sections.push(` - BAD: "Fixed the login button" (no reusable knowledge — the fix is in the code)`);
|
|
7892
|
+
sections.push(` - BAD: "Completed card #42" (ephemeral, auto-tracked by session)`);
|
|
7698
7893
|
sections.push(`
|
|
7699
7894
|
## Suggested Outputs`);
|
|
7700
7895
|
roleFraming.outputSuggestions.forEach((s) => {
|
|
@@ -24143,13 +24338,13 @@ function levenshteinSimilarity(a, b) {
|
|
|
24143
24338
|
const maxLen = Math.max(sa.length, sb.length);
|
|
24144
24339
|
return 1 - matrix[sa.length][sb.length] / maxLen;
|
|
24145
24340
|
}
|
|
24146
|
-
async function extractMidSessionLearnings(
|
|
24341
|
+
async function extractMidSessionLearnings(_client, ctx) {
|
|
24147
24342
|
const workspaceId = getActiveWorkspaceId();
|
|
24148
24343
|
if (!workspaceId)
|
|
24149
24344
|
return { count: 0, entityIds: [] };
|
|
24150
|
-
const
|
|
24345
|
+
const _projectId = getActiveProjectId() || undefined;
|
|
24151
24346
|
const now = Date.now();
|
|
24152
|
-
const
|
|
24347
|
+
const _entityIds = [];
|
|
24153
24348
|
const history = sessionTaskHistory.get(ctx.cardId);
|
|
24154
24349
|
if (ctx.currentTask) {
|
|
24155
24350
|
const previousTask = history?.lastTask || "";
|
|
@@ -24182,81 +24377,22 @@ async function extractMidSessionLearnings(client2, ctx) {
|
|
|
24182
24377
|
}
|
|
24183
24378
|
}
|
|
24184
24379
|
if (ctx.status === "blocked" && ctx.blockers?.length) {
|
|
24185
|
-
for (const blocker of ctx.blockers) {
|
|
24186
|
-
try {
|
|
24187
|
-
const result = await client2.createMemoryEntity({
|
|
24188
|
-
workspace_id: workspaceId,
|
|
24189
|
-
project_id: projectId,
|
|
24190
|
-
type: "error",
|
|
24191
|
-
scope: "project",
|
|
24192
|
-
memory_tier: "draft",
|
|
24193
|
-
title: `Blocker (mid-session): ${blocker.slice(0, 100)}`,
|
|
24194
|
-
content: `Encountered while working on "${ctx.cardTitle}":
|
|
24195
|
-
|
|
24196
|
-
${blocker}
|
|
24197
|
-
|
|
24198
|
-
Agent: ${ctx.agentName}
|
|
24199
|
-
Progress: ${ctx.progressPercent ?? "unknown"}%`,
|
|
24200
|
-
confidence: 0.5,
|
|
24201
|
-
tags: ["auto-extracted", "blocker", "mid-session"],
|
|
24202
|
-
metadata: {
|
|
24203
|
-
source: "mid_session",
|
|
24204
|
-
card_id: ctx.cardId
|
|
24205
|
-
},
|
|
24206
|
-
agent_identifier: ctx.agentIdentifier
|
|
24207
|
-
});
|
|
24208
|
-
const entity = result.entity;
|
|
24209
|
-
if (entity?.id)
|
|
24210
|
-
entityIds.push(entity.id);
|
|
24211
|
-
} catch {}
|
|
24212
|
-
}
|
|
24213
24380
|
sessionTaskHistory.set(ctx.cardId, {
|
|
24214
24381
|
lastTask: ctx.currentTask || "",
|
|
24215
24382
|
lastExtractionAt: now,
|
|
24216
24383
|
steps: history?.steps || []
|
|
24217
24384
|
});
|
|
24218
|
-
return { count:
|
|
24385
|
+
return { count: 0, entityIds: [] };
|
|
24219
24386
|
}
|
|
24220
24387
|
if (ctx.currentTask) {
|
|
24221
|
-
const previousTask = history?.lastTask || "";
|
|
24222
|
-
const similarity = levenshteinSimilarity(previousTask, ctx.currentTask);
|
|
24223
|
-
if (similarity < 0.6 && previousTask.length > 0) {
|
|
24224
|
-
try {
|
|
24225
|
-
const result = await client2.createMemoryEntity({
|
|
24226
|
-
workspace_id: workspaceId,
|
|
24227
|
-
project_id: projectId,
|
|
24228
|
-
type: "context",
|
|
24229
|
-
scope: "project",
|
|
24230
|
-
memory_tier: "draft",
|
|
24231
|
-
title: `Task transition: ${ctx.cardTitle}`,
|
|
24232
|
-
content: `Agent transitioned tasks on "${ctx.cardTitle}".
|
|
24233
|
-
|
|
24234
|
-
Previous: ${previousTask}
|
|
24235
|
-
Current: ${ctx.currentTask}
|
|
24236
|
-
Progress: ${ctx.progressPercent ?? "unknown"}%`,
|
|
24237
|
-
confidence: 0.5,
|
|
24238
|
-
tags: ["auto-extracted", "task-transition", "mid-session"],
|
|
24239
|
-
metadata: {
|
|
24240
|
-
source: "mid_session",
|
|
24241
|
-
card_id: ctx.cardId,
|
|
24242
|
-
previous_task: previousTask,
|
|
24243
|
-
current_task: ctx.currentTask
|
|
24244
|
-
},
|
|
24245
|
-
agent_identifier: ctx.agentIdentifier
|
|
24246
|
-
});
|
|
24247
|
-
const entity = result.entity;
|
|
24248
|
-
if (entity?.id)
|
|
24249
|
-
entityIds.push(entity.id);
|
|
24250
|
-
} catch {}
|
|
24251
|
-
}
|
|
24252
24388
|
const currentHistory = sessionTaskHistory.get(ctx.cardId);
|
|
24253
24389
|
sessionTaskHistory.set(ctx.cardId, {
|
|
24254
24390
|
lastTask: ctx.currentTask,
|
|
24255
|
-
lastExtractionAt:
|
|
24391
|
+
lastExtractionAt: currentHistory?.lastExtractionAt ?? 0,
|
|
24256
24392
|
steps: currentHistory?.steps || []
|
|
24257
24393
|
});
|
|
24258
24394
|
}
|
|
24259
|
-
return { count:
|
|
24395
|
+
return { count: 0, entityIds: [] };
|
|
24260
24396
|
}
|
|
24261
24397
|
function clearMidSessionTracking(cardId) {
|
|
24262
24398
|
sessionTaskHistory.delete(cardId);
|
|
@@ -24440,49 +24576,60 @@ async function extractLearnings(client2, session) {
|
|
|
24440
24576
|
Related: ${relatedEntityTitles.map((t) => `[[${t}]]`).join(", ")}` : "";
|
|
24441
24577
|
if (session.blockers && session.blockers.length > 0) {
|
|
24442
24578
|
for (const blocker of session.blockers) {
|
|
24443
|
-
|
|
24444
|
-
|
|
24445
|
-
|
|
24579
|
+
if (blocker.length < 80)
|
|
24580
|
+
continue;
|
|
24581
|
+
let isDuplicate = false;
|
|
24582
|
+
try {
|
|
24583
|
+
const similar = await findSimilarEntities(client2, blocker.slice(0, 200), blocker, workspaceId, { projectId, limit: 3, minRrfScore: 0.05 });
|
|
24584
|
+
isDuplicate = similar.some((e) => e.type === "error" && (e.rrf_score ?? 0) >= 0.06);
|
|
24585
|
+
} catch {}
|
|
24586
|
+
if (!isDuplicate) {
|
|
24587
|
+
learnings.push({
|
|
24588
|
+
title: `Blocker: ${blocker.slice(0, 100)}`,
|
|
24589
|
+
content: `Encountered while working on "${session.cardTitle}":
|
|
24446
24590
|
|
|
24447
24591
|
${blocker}
|
|
24448
24592
|
|
|
24449
24593
|
Agent: ${session.agentName}
|
|
24450
24594
|
Session status: ${session.status}`,
|
|
24451
|
-
|
|
24452
|
-
|
|
24453
|
-
|
|
24454
|
-
|
|
24455
|
-
|
|
24456
|
-
|
|
24457
|
-
|
|
24458
|
-
|
|
24459
|
-
|
|
24595
|
+
type: "error",
|
|
24596
|
+
tier: "episode",
|
|
24597
|
+
confidence: 0.6,
|
|
24598
|
+
tags: [
|
|
24599
|
+
"auto-extracted",
|
|
24600
|
+
"blocker",
|
|
24601
|
+
...session.cardLabels.slice(0, 3)
|
|
24602
|
+
],
|
|
24603
|
+
metadata: {
|
|
24604
|
+
source: "active_learning",
|
|
24605
|
+
card_id: session.cardId
|
|
24606
|
+
}
|
|
24607
|
+
});
|
|
24608
|
+
}
|
|
24460
24609
|
}
|
|
24461
24610
|
}
|
|
24462
|
-
|
|
24463
|
-
if (session.status === "completed" && hasMeaningfulContent) {
|
|
24611
|
+
if (session.status === "paused" && (session.blockers?.length ?? 0) > 0) {
|
|
24464
24612
|
const durationInfo = session.sessionDurationMs ? `
|
|
24465
24613
|
Duration: ${Math.round(session.sessionDurationMs / 60000)} minutes` : "";
|
|
24466
24614
|
learnings.push({
|
|
24467
|
-
title: `
|
|
24615
|
+
title: `Paused: ${session.cardTitle}`,
|
|
24468
24616
|
content: [
|
|
24469
|
-
`
|
|
24470
|
-
session.currentTask ? `
|
|
24617
|
+
`Paused work on "${session.cardTitle}".`,
|
|
24618
|
+
session.currentTask ? `Last task: ${session.currentTask}` : "",
|
|
24471
24619
|
session.progressPercent !== undefined ? `Progress: ${session.progressPercent}%` : "",
|
|
24472
24620
|
durationInfo,
|
|
24473
|
-
session.
|
|
24474
|
-
session.blockers?.length ? `Blockers encountered: ${session.blockers.join("; ")}` : "",
|
|
24621
|
+
session.blockers?.length ? `Blockers: ${session.blockers.join("; ")}` : "",
|
|
24475
24622
|
`
|
|
24476
24623
|
Agent: ${session.agentName}`,
|
|
24477
24624
|
wikiLinksLine
|
|
24478
24625
|
].filter(Boolean).join(`
|
|
24479
24626
|
`),
|
|
24480
24627
|
type: "lesson",
|
|
24481
|
-
tier: "
|
|
24482
|
-
confidence: 0.
|
|
24628
|
+
tier: "draft",
|
|
24629
|
+
confidence: 0.6,
|
|
24483
24630
|
tags: [
|
|
24484
24631
|
"auto-extracted",
|
|
24485
|
-
"session-
|
|
24632
|
+
"session-paused",
|
|
24486
24633
|
...session.cardLabels.slice(0, 3)
|
|
24487
24634
|
],
|
|
24488
24635
|
metadata: {
|
|
@@ -24491,35 +24638,14 @@ Agent: ${session.agentName}`,
|
|
|
24491
24638
|
}
|
|
24492
24639
|
});
|
|
24493
24640
|
}
|
|
24494
|
-
const hasBugLabel = session.cardLabels.some((l) => ["bug", "fix", "hotfix", "defect", "error"].includes(l.toLowerCase()));
|
|
24495
|
-
if (hasBugLabel && session.status === "completed") {
|
|
24496
|
-
learnings.push({
|
|
24497
|
-
title: `Solution: ${session.cardTitle}`,
|
|
24498
|
-
content: [
|
|
24499
|
-
`Resolved bug: "${session.cardTitle}"`,
|
|
24500
|
-
session.currentTask ? `
|
|
24501
|
-
Approach: ${session.currentTask}` : "",
|
|
24502
|
-
`
|
|
24503
|
-
Agent: ${session.agentName}`,
|
|
24504
|
-
wikiLinksLine
|
|
24505
|
-
].filter(Boolean).join(`
|
|
24506
|
-
`),
|
|
24507
|
-
type: "solution",
|
|
24508
|
-
tier: "reference",
|
|
24509
|
-
confidence: 0.8,
|
|
24510
|
-
tags: ["auto-extracted", "bug-fix", ...session.cardLabels.slice(0, 3)],
|
|
24511
|
-
metadata: {
|
|
24512
|
-
source: "active_learning",
|
|
24513
|
-
card_id: session.cardId,
|
|
24514
|
-
auto_confidence: true
|
|
24515
|
-
}
|
|
24516
|
-
});
|
|
24517
|
-
}
|
|
24518
24641
|
const entityIds = [];
|
|
24519
24642
|
const stepHistory = sessionTaskHistory.get(session.cardId);
|
|
24520
|
-
const
|
|
24643
|
+
const MIN_PROCEDURE_STEPS = 5;
|
|
24644
|
+
const MIN_PROCEDURE_DURATION_MS = 10 * 60 * 1000;
|
|
24645
|
+
const hasEnoughSteps = stepHistory && stepHistory.steps.length >= MIN_PROCEDURE_STEPS;
|
|
24646
|
+
const hasMinDuration = (session.sessionDurationMs ?? 0) >= MIN_PROCEDURE_DURATION_MS;
|
|
24521
24647
|
const isSuccessful = session.status === "completed" && (session.progressPercent === undefined || session.progressPercent >= 85) && !session.blockers?.length;
|
|
24522
|
-
if (isSuccessful && hasEnoughSteps) {
|
|
24648
|
+
if (isSuccessful && hasEnoughSteps && hasMinDuration) {
|
|
24523
24649
|
const procedureResult = await extractOrReinforceProcedure(client2, session, stepHistory.steps, workspaceId, projectId, wikiLinksLine);
|
|
24524
24650
|
if (procedureResult) {
|
|
24525
24651
|
if (procedureResult.mode === "created") {
|
|
@@ -24563,218 +24689,9 @@ Agent: ${session.agentName}`,
|
|
|
24563
24689
|
if (createdPairs.length >= 2) {
|
|
24564
24690
|
linkSessionEntities(client2, createdPairs, workspaceId, projectId).catch(() => {});
|
|
24565
24691
|
}
|
|
24566
|
-
if (entityIds.length > 0) {
|
|
24567
|
-
detectAndCreatePatterns(client2, entityIds, session, workspaceId, projectId).catch(() => {});
|
|
24568
|
-
}
|
|
24569
|
-
if (createdPairs.length > 0) {
|
|
24570
|
-
detectCausalPatterns(client2, createdPairs, session, workspaceId, projectId).catch(() => {});
|
|
24571
|
-
}
|
|
24572
24692
|
clearMidSessionTracking(session.cardId);
|
|
24573
24693
|
return { count: entityIds.length, entityIds };
|
|
24574
24694
|
}
|
|
24575
|
-
var PATTERN_THRESHOLD = 3;
|
|
24576
|
-
async function detectAndCreatePatterns(client2, newEntityIds, session, workspaceId, projectId) {
|
|
24577
|
-
const patternEntityIds = [];
|
|
24578
|
-
for (const newEntityId of newEntityIds) {
|
|
24579
|
-
try {
|
|
24580
|
-
const { entity: rawEntity } = await client2.getMemoryEntity(newEntityId);
|
|
24581
|
-
const entity = rawEntity;
|
|
24582
|
-
if (!entity?.type)
|
|
24583
|
-
continue;
|
|
24584
|
-
const similar = await findSimilarEntities(client2, entity.title, entity.content, workspaceId, { projectId, limit: 30, minRrfScore: 0.01 });
|
|
24585
|
-
const existing = similar.filter((c) => !newEntityIds.includes(c.id) && c.type === entity.type);
|
|
24586
|
-
if (existing.length < PATTERN_THRESHOLD)
|
|
24587
|
-
continue;
|
|
24588
|
-
const memberTitles = [
|
|
24589
|
-
entity.title,
|
|
24590
|
-
...existing.slice(0, 4).map((e) => e.title)
|
|
24591
|
-
];
|
|
24592
|
-
const patternTitle = `Pattern: recurring ${entity.type} (${existing.length + 1} instances)`;
|
|
24593
|
-
const { entities: existingPatterns } = await client2.listMemoryEntities({
|
|
24594
|
-
workspace_id: workspaceId,
|
|
24595
|
-
project_id: projectId,
|
|
24596
|
-
type: "pattern",
|
|
24597
|
-
limit: 10
|
|
24598
|
-
});
|
|
24599
|
-
const matchingPattern = existingPatterns.find((p) => p.metadata?.pattern_type === entity.type);
|
|
24600
|
-
let patternId = null;
|
|
24601
|
-
if (matchingPattern) {
|
|
24602
|
-
patternId = matchingPattern.id;
|
|
24603
|
-
await client2.updateMemoryEntity(patternId, {
|
|
24604
|
-
content: `Recurring pattern: ${entity.type} entities appearing ${existing.length + 1} times.
|
|
24605
|
-
|
|
24606
|
-
Members:
|
|
24607
|
-
${memberTitles.map((t) => `- ${t}`).join(`
|
|
24608
|
-
`)}
|
|
24609
|
-
|
|
24610
|
-
Last updated: ${new Date().toISOString()}`,
|
|
24611
|
-
metadata: {
|
|
24612
|
-
pattern_count: existing.length + 1,
|
|
24613
|
-
pattern_type: entity.type,
|
|
24614
|
-
last_updated: new Date().toISOString()
|
|
24615
|
-
}
|
|
24616
|
-
});
|
|
24617
|
-
} else {
|
|
24618
|
-
const result = await client2.createMemoryEntity({
|
|
24619
|
-
workspace_id: workspaceId,
|
|
24620
|
-
project_id: projectId,
|
|
24621
|
-
type: "pattern",
|
|
24622
|
-
scope: "project",
|
|
24623
|
-
memory_tier: "reference",
|
|
24624
|
-
title: patternTitle,
|
|
24625
|
-
content: `Recurring pattern: ${entity.type} entities detected ${existing.length + 1} times.
|
|
24626
|
-
|
|
24627
|
-
Members:
|
|
24628
|
-
${memberTitles.map((t) => `- ${t}`).join(`
|
|
24629
|
-
`)}`,
|
|
24630
|
-
confidence: 0.75,
|
|
24631
|
-
tags: ["auto-extracted", "pattern", entity.type],
|
|
24632
|
-
metadata: {
|
|
24633
|
-
source: "pattern_detection",
|
|
24634
|
-
pattern_type: entity.type,
|
|
24635
|
-
pattern_count: existing.length + 1
|
|
24636
|
-
},
|
|
24637
|
-
agent_identifier: session.agentIdentifier
|
|
24638
|
-
});
|
|
24639
|
-
const created = result.entity;
|
|
24640
|
-
if (created?.id) {
|
|
24641
|
-
patternId = created.id;
|
|
24642
|
-
patternEntityIds.push(patternId);
|
|
24643
|
-
}
|
|
24644
|
-
}
|
|
24645
|
-
if (!patternId)
|
|
24646
|
-
continue;
|
|
24647
|
-
const toLink = [newEntityId, ...existing.slice(0, 4).map((e) => e.id)];
|
|
24648
|
-
for (const sourceId of toLink) {
|
|
24649
|
-
try {
|
|
24650
|
-
await client2.createMemoryRelation({
|
|
24651
|
-
source_id: sourceId,
|
|
24652
|
-
target_id: patternId,
|
|
24653
|
-
relation_type: "part_of",
|
|
24654
|
-
confidence: 0.75
|
|
24655
|
-
});
|
|
24656
|
-
} catch {}
|
|
24657
|
-
}
|
|
24658
|
-
} catch {}
|
|
24659
|
-
}
|
|
24660
|
-
return patternEntityIds;
|
|
24661
|
-
}
|
|
24662
|
-
var CAUSAL_PATTERN_THRESHOLD = 3;
|
|
24663
|
-
async function detectCausalPatterns(client2, createdPairs, session, workspaceId, projectId) {
|
|
24664
|
-
const patternIds = [];
|
|
24665
|
-
const errors3 = createdPairs.filter((p) => p.learning.type === "error");
|
|
24666
|
-
const solutions = createdPairs.filter((p) => p.learning.type === "solution");
|
|
24667
|
-
if (errors3.length === 0 || solutions.length === 0)
|
|
24668
|
-
return patternIds;
|
|
24669
|
-
for (const errorPair of errors3) {
|
|
24670
|
-
try {
|
|
24671
|
-
const similarErrors = await findSimilarEntities(client2, errorPair.learning.title, errorPair.learning.content, workspaceId, {
|
|
24672
|
-
projectId,
|
|
24673
|
-
limit: 20,
|
|
24674
|
-
minRrfScore: 0.03,
|
|
24675
|
-
excludeIds: createdPairs.map((p) => p.id),
|
|
24676
|
-
type: "error"
|
|
24677
|
-
});
|
|
24678
|
-
const resolvedErrors = [];
|
|
24679
|
-
for (const similar of similarErrors.slice(0, 10)) {
|
|
24680
|
-
try {
|
|
24681
|
-
const { outgoing } = await client2.getRelatedEntities(similar.id);
|
|
24682
|
-
const resolvedByRel = outgoing.find((r) => r.relation_type === "resolved_by");
|
|
24683
|
-
if (resolvedByRel) {
|
|
24684
|
-
resolvedErrors.push({
|
|
24685
|
-
errorId: similar.id,
|
|
24686
|
-
errorTitle: similar.title,
|
|
24687
|
-
solutionTitle: resolvedByRel.target_title || "unknown"
|
|
24688
|
-
});
|
|
24689
|
-
}
|
|
24690
|
-
} catch {}
|
|
24691
|
-
}
|
|
24692
|
-
if (resolvedErrors.length + 1 < CAUSAL_PATTERN_THRESHOLD)
|
|
24693
|
-
continue;
|
|
24694
|
-
const { entities: existingPatterns } = await client2.listMemoryEntities({
|
|
24695
|
-
workspace_id: workspaceId,
|
|
24696
|
-
project_id: projectId,
|
|
24697
|
-
type: "pattern",
|
|
24698
|
-
limit: 10
|
|
24699
|
-
});
|
|
24700
|
-
const matchingPattern = existingPatterns.find((p) => p.metadata?.pattern_chain_type === "error_resolved_by_solution");
|
|
24701
|
-
if (matchingPattern) {
|
|
24702
|
-
await client2.updateMemoryEntity(matchingPattern.id, {
|
|
24703
|
-
content: [
|
|
24704
|
-
`Recurring error→solution chain detected (${resolvedErrors.length + 1} instances).`,
|
|
24705
|
-
"",
|
|
24706
|
-
"## Error→Solution Pairs",
|
|
24707
|
-
`- ${errorPair.learning.title} → ${solutions[0].learning.title}`,
|
|
24708
|
-
...resolvedErrors.slice(0, 5).map((r) => `- ${r.errorTitle} → ${r.solutionTitle}`),
|
|
24709
|
-
"",
|
|
24710
|
-
`Last updated: ${new Date().toISOString()}`
|
|
24711
|
-
].join(`
|
|
24712
|
-
`),
|
|
24713
|
-
metadata: {
|
|
24714
|
-
pattern_chain_type: "error_resolved_by_solution",
|
|
24715
|
-
pattern_count: resolvedErrors.length + 1,
|
|
24716
|
-
last_updated: new Date().toISOString()
|
|
24717
|
-
}
|
|
24718
|
-
});
|
|
24719
|
-
for (const pair of [errorPair, solutions[0]]) {
|
|
24720
|
-
try {
|
|
24721
|
-
await client2.createMemoryRelation({
|
|
24722
|
-
source_id: pair.id,
|
|
24723
|
-
target_id: matchingPattern.id,
|
|
24724
|
-
relation_type: "part_of",
|
|
24725
|
-
confidence: 0.75
|
|
24726
|
-
});
|
|
24727
|
-
} catch {}
|
|
24728
|
-
}
|
|
24729
|
-
} else {
|
|
24730
|
-
const result = await client2.createMemoryEntity({
|
|
24731
|
-
workspace_id: workspaceId,
|
|
24732
|
-
project_id: projectId,
|
|
24733
|
-
type: "pattern",
|
|
24734
|
-
scope: "project",
|
|
24735
|
-
memory_tier: "reference",
|
|
24736
|
-
title: `Pattern: recurring error→solution chain (${resolvedErrors.length + 1} instances)`,
|
|
24737
|
-
content: [
|
|
24738
|
-
`Recurring error→solution chain detected across ${resolvedErrors.length + 1} sessions.`,
|
|
24739
|
-
"",
|
|
24740
|
-
"## Error→Solution Pairs",
|
|
24741
|
-
`- ${errorPair.learning.title} → ${solutions[0].learning.title}`,
|
|
24742
|
-
...resolvedErrors.slice(0, 5).map((r) => `- ${r.errorTitle} → ${r.solutionTitle}`)
|
|
24743
|
-
].join(`
|
|
24744
|
-
`),
|
|
24745
|
-
confidence: 0.8,
|
|
24746
|
-
tags: ["auto-extracted", "pattern", "causal-chain"],
|
|
24747
|
-
metadata: {
|
|
24748
|
-
source: "causal_pattern_detection",
|
|
24749
|
-
pattern_chain_type: "error_resolved_by_solution",
|
|
24750
|
-
pattern_count: resolvedErrors.length + 1
|
|
24751
|
-
},
|
|
24752
|
-
agent_identifier: session.agentIdentifier
|
|
24753
|
-
});
|
|
24754
|
-
const created = result.entity;
|
|
24755
|
-
if (created?.id) {
|
|
24756
|
-
patternIds.push(created.id);
|
|
24757
|
-
const memberIds = [
|
|
24758
|
-
errorPair.id,
|
|
24759
|
-
solutions[0].id,
|
|
24760
|
-
...resolvedErrors.slice(0, 4).map((r) => r.errorId)
|
|
24761
|
-
];
|
|
24762
|
-
for (const memberId of memberIds) {
|
|
24763
|
-
try {
|
|
24764
|
-
await client2.createMemoryRelation({
|
|
24765
|
-
source_id: memberId,
|
|
24766
|
-
target_id: created.id,
|
|
24767
|
-
relation_type: "part_of",
|
|
24768
|
-
confidence: 0.75
|
|
24769
|
-
});
|
|
24770
|
-
} catch {}
|
|
24771
|
-
}
|
|
24772
|
-
}
|
|
24773
|
-
}
|
|
24774
|
-
} catch {}
|
|
24775
|
-
}
|
|
24776
|
-
return patternIds;
|
|
24777
|
-
}
|
|
24778
24695
|
async function detectContradictions(client2, entityId, entityType, title, content, tags, workspaceId, projectId) {
|
|
24779
24696
|
if (!CONTRADICTION_TYPES.has(entityType))
|
|
24780
24697
|
return [];
|
|
@@ -25587,7 +25504,7 @@ async function autoEndSession(client3, cardId, status) {
|
|
|
25587
25504
|
// src/consolidation.ts
|
|
25588
25505
|
async function consolidateMemories(client3, workspaceId, projectId, options) {
|
|
25589
25506
|
const dryRun = options?.dryRun !== false;
|
|
25590
|
-
const minClusterSize = options?.minClusterSize ??
|
|
25507
|
+
const minClusterSize = options?.minClusterSize ?? 3;
|
|
25591
25508
|
const result = {
|
|
25592
25509
|
consolidated: 0,
|
|
25593
25510
|
clustersFound: 0,
|
|
@@ -25652,12 +25569,7 @@ async function consolidateMemories(client3, workspaceId, projectId, options) {
|
|
|
25652
25569
|
result.clustersFound++;
|
|
25653
25570
|
const mergedTitle = deriveClusterTitle(cluster, type);
|
|
25654
25571
|
const memberTitles = cluster.map((e) => e.title);
|
|
25655
|
-
const mergedContent =
|
|
25656
|
-
`Consolidated from ${cluster.length} ${type} memories:
|
|
25657
|
-
`,
|
|
25658
|
-
...cluster.map((e) => `- **${e.title}**: ${e.content.slice(0, 200)}`)
|
|
25659
|
-
].join(`
|
|
25660
|
-
`);
|
|
25572
|
+
const mergedContent = synthesizeClusterContent(cluster, type);
|
|
25661
25573
|
const maxConfidence = Math.max(...cluster.map((e) => e.confidence));
|
|
25662
25574
|
const allTags = [...new Set(cluster.flatMap((e) => e.tags || []))];
|
|
25663
25575
|
const detail = {
|
|
@@ -25719,6 +25631,60 @@ async function consolidateMemories(client3, workspaceId, projectId, options) {
|
|
|
25719
25631
|
}
|
|
25720
25632
|
return result;
|
|
25721
25633
|
}
|
|
25634
|
+
function synthesizeClusterContent(cluster, type) {
|
|
25635
|
+
const SKIP_PATTERNS = [
|
|
25636
|
+
/^##\s/,
|
|
25637
|
+
/^Agent:/,
|
|
25638
|
+
/^Duration:/,
|
|
25639
|
+
/^Labels:/,
|
|
25640
|
+
/^Progress:/,
|
|
25641
|
+
/^Session status:/,
|
|
25642
|
+
/^Completed at/,
|
|
25643
|
+
/^Final state:/,
|
|
25644
|
+
/^Related:/,
|
|
25645
|
+
/^When working on:/,
|
|
25646
|
+
/^\d+\.\s+.+\(\d+%,\s*\+\d+%\)/,
|
|
25647
|
+
/^Last updated:/,
|
|
25648
|
+
/^Recurring pattern:/,
|
|
25649
|
+
/^Consolidated from/
|
|
25650
|
+
];
|
|
25651
|
+
const seenLines = new Set;
|
|
25652
|
+
const knowledgeLines = [];
|
|
25653
|
+
for (const entity of cluster) {
|
|
25654
|
+
const lines = entity.content.split(`
|
|
25655
|
+
`).map((l) => l.trim());
|
|
25656
|
+
for (const line of lines) {
|
|
25657
|
+
if (!line || line.length < 20)
|
|
25658
|
+
continue;
|
|
25659
|
+
if (SKIP_PATTERNS.some((p) => p.test(line)))
|
|
25660
|
+
continue;
|
|
25661
|
+
const normalized = line.toLowerCase().replace(/[*_`#[\]]/g, "").trim();
|
|
25662
|
+
if (seenLines.has(normalized))
|
|
25663
|
+
continue;
|
|
25664
|
+
seenLines.add(normalized);
|
|
25665
|
+
knowledgeLines.push(line);
|
|
25666
|
+
}
|
|
25667
|
+
}
|
|
25668
|
+
if (knowledgeLines.length === 0) {
|
|
25669
|
+
return `${cluster.length} related ${type} entities consolidated. Original titles:
|
|
25670
|
+
${cluster.map((e) => `- ${e.title}`).join(`
|
|
25671
|
+
`)}`;
|
|
25672
|
+
}
|
|
25673
|
+
const MAX_CHARS = 1600;
|
|
25674
|
+
const result = [
|
|
25675
|
+
`Consolidated knowledge from ${cluster.length} ${type} entities:
|
|
25676
|
+
`
|
|
25677
|
+
];
|
|
25678
|
+
let charCount = result[0].length;
|
|
25679
|
+
for (const line of knowledgeLines) {
|
|
25680
|
+
if (charCount + line.length + 3 > MAX_CHARS)
|
|
25681
|
+
break;
|
|
25682
|
+
result.push(`- ${line}`);
|
|
25683
|
+
charCount += line.length + 3;
|
|
25684
|
+
}
|
|
25685
|
+
return result.join(`
|
|
25686
|
+
`);
|
|
25687
|
+
}
|
|
25722
25688
|
function deriveClusterTitle(cluster, type) {
|
|
25723
25689
|
const stopWords = new Set([
|
|
25724
25690
|
"the",
|
|
@@ -25773,9 +25739,9 @@ function deriveClusterTitle(cluster, type) {
|
|
|
25773
25739
|
wordCounts.set(word, (wordCounts.get(word) || 0) + 1);
|
|
25774
25740
|
}
|
|
25775
25741
|
}
|
|
25776
|
-
const topWords = [...wordCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0,
|
|
25777
|
-
const suffix = topWords.length > 0 ? topWords.join("
|
|
25778
|
-
return
|
|
25742
|
+
const topWords = [...wordCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 4).map(([word]) => word[0].toUpperCase() + word.slice(1));
|
|
25743
|
+
const suffix = topWords.length > 0 ? topWords.join(" / ") : "Various";
|
|
25744
|
+
return `${type[0].toUpperCase() + type.slice(1)}: ${suffix}`;
|
|
25779
25745
|
}
|
|
25780
25746
|
|
|
25781
25747
|
// src/server.ts
|
|
@@ -25851,6 +25817,371 @@ async function runLifecycleMaintenance(client3, workspaceId, projectId) {
|
|
|
25851
25817
|
return result;
|
|
25852
25818
|
}
|
|
25853
25819
|
|
|
25820
|
+
// src/memory-cleanup.ts
|
|
25821
|
+
init_dist();
|
|
25822
|
+
var ALL_STEPS = [
|
|
25823
|
+
"prune",
|
|
25824
|
+
"consolidate",
|
|
25825
|
+
"orphans",
|
|
25826
|
+
"duplicates",
|
|
25827
|
+
"backfill"
|
|
25828
|
+
];
|
|
25829
|
+
async function runMemoryCleanup(client3, workspaceId, projectId, options) {
|
|
25830
|
+
const dryRun = options?.dryRun !== false;
|
|
25831
|
+
const steps = options?.steps ?? ALL_STEPS;
|
|
25832
|
+
const maxAgeDays = options?.maxAgeDays ?? 30;
|
|
25833
|
+
const minClusterSize = options?.minClusterSize ?? 3;
|
|
25834
|
+
const orphanAgeDays = options?.orphanAgeDays ?? 14;
|
|
25835
|
+
const report = {
|
|
25836
|
+
success: true,
|
|
25837
|
+
dryRun,
|
|
25838
|
+
timestamp: new Date().toISOString(),
|
|
25839
|
+
workspace: { id: workspaceId, projectId },
|
|
25840
|
+
summary: { totalEntities: 0, issuesFound: 0, actionsTaken: 0 },
|
|
25841
|
+
steps: {},
|
|
25842
|
+
errors: [],
|
|
25843
|
+
healthReport: ""
|
|
25844
|
+
};
|
|
25845
|
+
let entities = [];
|
|
25846
|
+
try {
|
|
25847
|
+
const listResult = await client3.listMemoryEntities({
|
|
25848
|
+
workspace_id: workspaceId,
|
|
25849
|
+
project_id: projectId,
|
|
25850
|
+
limit: 200
|
|
25851
|
+
});
|
|
25852
|
+
entities = listResult.entities || [];
|
|
25853
|
+
report.summary.totalEntities = entities.length;
|
|
25854
|
+
} catch (err) {
|
|
25855
|
+
report.errors.push({
|
|
25856
|
+
step: "init",
|
|
25857
|
+
message: `Failed to fetch entities: ${err.message}`
|
|
25858
|
+
});
|
|
25859
|
+
report.success = false;
|
|
25860
|
+
report.healthReport = generateHealthReport(report);
|
|
25861
|
+
return report;
|
|
25862
|
+
}
|
|
25863
|
+
if (steps.includes("prune")) {
|
|
25864
|
+
try {
|
|
25865
|
+
report.steps.prune = runPruneStep(entities, maxAgeDays);
|
|
25866
|
+
if (!dryRun) {
|
|
25867
|
+
for (const item of report.steps.prune.items) {
|
|
25868
|
+
try {
|
|
25869
|
+
await client3.deleteMemoryEntity(item.id);
|
|
25870
|
+
report.steps.prune.pruned++;
|
|
25871
|
+
} catch {}
|
|
25872
|
+
}
|
|
25873
|
+
report.summary.actionsTaken += report.steps.prune.pruned;
|
|
25874
|
+
}
|
|
25875
|
+
report.summary.issuesFound += report.steps.prune.staleDraftsFound;
|
|
25876
|
+
} catch (err) {
|
|
25877
|
+
report.errors.push({
|
|
25878
|
+
step: "prune",
|
|
25879
|
+
message: err.message
|
|
25880
|
+
});
|
|
25881
|
+
}
|
|
25882
|
+
}
|
|
25883
|
+
if (steps.includes("consolidate")) {
|
|
25884
|
+
try {
|
|
25885
|
+
const result = await consolidateMemories(client3, workspaceId, projectId, {
|
|
25886
|
+
dryRun,
|
|
25887
|
+
minClusterSize
|
|
25888
|
+
});
|
|
25889
|
+
report.steps.consolidate = {
|
|
25890
|
+
clustersFound: result.clustersFound,
|
|
25891
|
+
entitiesProcessed: result.entitiesProcessed,
|
|
25892
|
+
consolidated: result.consolidated,
|
|
25893
|
+
details: result.details
|
|
25894
|
+
};
|
|
25895
|
+
report.summary.issuesFound += result.clustersFound;
|
|
25896
|
+
if (!dryRun)
|
|
25897
|
+
report.summary.actionsTaken += result.consolidated;
|
|
25898
|
+
} catch (err) {
|
|
25899
|
+
report.errors.push({
|
|
25900
|
+
step: "consolidate",
|
|
25901
|
+
message: err.message
|
|
25902
|
+
});
|
|
25903
|
+
}
|
|
25904
|
+
}
|
|
25905
|
+
if (steps.includes("orphans")) {
|
|
25906
|
+
try {
|
|
25907
|
+
report.steps.orphans = await runOrphanStep(client3, entities, orphanAgeDays);
|
|
25908
|
+
if (!dryRun) {
|
|
25909
|
+
for (const item of report.steps.orphans.items) {
|
|
25910
|
+
try {
|
|
25911
|
+
await client3.deleteMemoryEntity(item.id);
|
|
25912
|
+
report.steps.orphans.removed++;
|
|
25913
|
+
} catch {}
|
|
25914
|
+
}
|
|
25915
|
+
report.summary.actionsTaken += report.steps.orphans.removed;
|
|
25916
|
+
}
|
|
25917
|
+
report.summary.issuesFound += report.steps.orphans.orphansFound;
|
|
25918
|
+
} catch (err) {
|
|
25919
|
+
report.errors.push({
|
|
25920
|
+
step: "orphans",
|
|
25921
|
+
message: err.message
|
|
25922
|
+
});
|
|
25923
|
+
}
|
|
25924
|
+
}
|
|
25925
|
+
if (steps.includes("duplicates")) {
|
|
25926
|
+
try {
|
|
25927
|
+
report.steps.duplicates = await runDuplicateStep(client3, entities, workspaceId, projectId);
|
|
25928
|
+
if (!dryRun) {
|
|
25929
|
+
for (const pair of report.steps.duplicates.pairs) {
|
|
25930
|
+
try {
|
|
25931
|
+
await client3.deleteMemoryEntity(pair.removeId);
|
|
25932
|
+
report.steps.duplicates.resolved++;
|
|
25933
|
+
} catch {}
|
|
25934
|
+
}
|
|
25935
|
+
report.summary.actionsTaken += report.steps.duplicates.resolved;
|
|
25936
|
+
}
|
|
25937
|
+
report.summary.issuesFound += report.steps.duplicates.duplicatePairsFound;
|
|
25938
|
+
} catch (err) {
|
|
25939
|
+
report.errors.push({
|
|
25940
|
+
step: "duplicates",
|
|
25941
|
+
message: err.message
|
|
25942
|
+
});
|
|
25943
|
+
}
|
|
25944
|
+
}
|
|
25945
|
+
if (steps.includes("backfill")) {
|
|
25946
|
+
try {
|
|
25947
|
+
if (dryRun) {
|
|
25948
|
+
report.steps.backfill = {
|
|
25949
|
+
processed: 0,
|
|
25950
|
+
remaining: -1,
|
|
25951
|
+
errors: []
|
|
25952
|
+
};
|
|
25953
|
+
} else {
|
|
25954
|
+
const result = await client3.backfillEmbeddings(workspaceId);
|
|
25955
|
+
report.steps.backfill = {
|
|
25956
|
+
processed: result.processed,
|
|
25957
|
+
remaining: result.remaining,
|
|
25958
|
+
errors: result.errors || []
|
|
25959
|
+
};
|
|
25960
|
+
report.summary.actionsTaken += result.processed;
|
|
25961
|
+
}
|
|
25962
|
+
} catch (err) {
|
|
25963
|
+
report.errors.push({
|
|
25964
|
+
step: "backfill",
|
|
25965
|
+
message: err.message
|
|
25966
|
+
});
|
|
25967
|
+
}
|
|
25968
|
+
}
|
|
25969
|
+
report.healthReport = generateHealthReport(report);
|
|
25970
|
+
return report;
|
|
25971
|
+
}
|
|
25972
|
+
function runPruneStep(entities, maxAgeDays) {
|
|
25973
|
+
const now = Date.now();
|
|
25974
|
+
const drafts = entities.filter((e) => e.memory_tier === "draft");
|
|
25975
|
+
const stale = [];
|
|
25976
|
+
for (const entity of drafts) {
|
|
25977
|
+
const ageDays = (now - new Date(entity.created_at).getTime()) / (1000 * 60 * 60 * 24);
|
|
25978
|
+
if (ageDays < maxAgeDays)
|
|
25979
|
+
continue;
|
|
25980
|
+
const lifecycle2 = evaluateLifecycle(entity);
|
|
25981
|
+
stale.push({
|
|
25982
|
+
id: entity.id,
|
|
25983
|
+
title: entity.title,
|
|
25984
|
+
ageDays: Math.round(ageDays),
|
|
25985
|
+
decayScore: Math.round(lifecycle2.decay.score * 100) / 100
|
|
25986
|
+
});
|
|
25987
|
+
}
|
|
25988
|
+
return { staleDraftsFound: stale.length, pruned: 0, items: stale };
|
|
25989
|
+
}
|
|
25990
|
+
async function runOrphanStep(client3, entities, orphanAgeDays) {
|
|
25991
|
+
const now = Date.now();
|
|
25992
|
+
const result = { orphansFound: 0, removed: 0, items: [] };
|
|
25993
|
+
const candidates = entities.filter((e) => {
|
|
25994
|
+
if (e.memory_tier === "reference")
|
|
25995
|
+
return false;
|
|
25996
|
+
if (e.access_count >= 2)
|
|
25997
|
+
return false;
|
|
25998
|
+
const ageDays = (now - new Date(e.created_at).getTime()) / (1000 * 60 * 60 * 24);
|
|
25999
|
+
return ageDays >= orphanAgeDays;
|
|
26000
|
+
});
|
|
26001
|
+
for (const entity of candidates) {
|
|
26002
|
+
try {
|
|
26003
|
+
const related = await client3.getRelatedEntities(entity.id);
|
|
26004
|
+
const totalRelations = (related.outgoing?.length || 0) + (related.incoming?.length || 0);
|
|
26005
|
+
if (totalRelations > 0)
|
|
26006
|
+
continue;
|
|
26007
|
+
const ageDays = (now - new Date(entity.created_at).getTime()) / (1000 * 60 * 60 * 24);
|
|
26008
|
+
result.items.push({
|
|
26009
|
+
id: entity.id,
|
|
26010
|
+
title: entity.title,
|
|
26011
|
+
type: entity.type,
|
|
26012
|
+
tier: entity.memory_tier,
|
|
26013
|
+
ageDays: Math.round(ageDays),
|
|
26014
|
+
accessCount: entity.access_count
|
|
26015
|
+
});
|
|
26016
|
+
result.orphansFound++;
|
|
26017
|
+
} catch {}
|
|
26018
|
+
}
|
|
26019
|
+
return result;
|
|
26020
|
+
}
|
|
26021
|
+
async function runDuplicateStep(client3, entities, workspaceId, projectId) {
|
|
26022
|
+
const result = {
|
|
26023
|
+
duplicatePairsFound: 0,
|
|
26024
|
+
resolved: 0,
|
|
26025
|
+
pairs: []
|
|
26026
|
+
};
|
|
26027
|
+
const seenPairs = new Set;
|
|
26028
|
+
const flaggedForRemoval = new Set;
|
|
26029
|
+
for (const entity of entities) {
|
|
26030
|
+
if (flaggedForRemoval.has(entity.id))
|
|
26031
|
+
continue;
|
|
26032
|
+
let similar;
|
|
26033
|
+
try {
|
|
26034
|
+
similar = await findSimilarEntities(client3, entity.title, entity.content, workspaceId, { projectId, limit: 5, minRrfScore: 0.05, excludeIds: [entity.id] });
|
|
26035
|
+
} catch {
|
|
26036
|
+
continue;
|
|
26037
|
+
}
|
|
26038
|
+
for (const match of similar) {
|
|
26039
|
+
if (flaggedForRemoval.has(match.id))
|
|
26040
|
+
continue;
|
|
26041
|
+
const pairKey = [entity.id, match.id].sort().join(":");
|
|
26042
|
+
if (seenPairs.has(pairKey))
|
|
26043
|
+
continue;
|
|
26044
|
+
seenPairs.add(pairKey);
|
|
26045
|
+
const sim = titleSimilarity(entity.title, match.title);
|
|
26046
|
+
if (sim < 0.85)
|
|
26047
|
+
continue;
|
|
26048
|
+
const entityScore = entityQualityScore(entity);
|
|
26049
|
+
const matchEntity = entities.find((e) => e.id === match.id);
|
|
26050
|
+
const matchScore = matchEntity ? entityQualityScore(matchEntity) : match.confidence;
|
|
26051
|
+
const [keep, remove] = entityScore >= matchScore ? [entity, { id: match.id, title: match.title }] : [{ id: match.id, title: match.title }, entity];
|
|
26052
|
+
flaggedForRemoval.add(remove.id);
|
|
26053
|
+
result.pairs.push({
|
|
26054
|
+
keepId: keep.id,
|
|
26055
|
+
keepTitle: keep.title,
|
|
26056
|
+
removeId: remove.id,
|
|
26057
|
+
removeTitle: remove.title,
|
|
26058
|
+
similarity: Math.round(sim * 100) / 100
|
|
26059
|
+
});
|
|
26060
|
+
result.duplicatePairsFound++;
|
|
26061
|
+
}
|
|
26062
|
+
}
|
|
26063
|
+
return result;
|
|
26064
|
+
}
|
|
26065
|
+
var TIER_WEIGHTS2 = {
|
|
26066
|
+
reference: 3,
|
|
26067
|
+
episode: 2,
|
|
26068
|
+
draft: 1
|
|
26069
|
+
};
|
|
26070
|
+
function entityQualityScore(entity) {
|
|
26071
|
+
return entity.confidence + (TIER_WEIGHTS2[entity.memory_tier] || 0) + Math.min(entity.access_count, 10) * 0.1;
|
|
26072
|
+
}
|
|
26073
|
+
function titleSimilarity(a, b) {
|
|
26074
|
+
const na = a.toLowerCase().trim();
|
|
26075
|
+
const nb = b.toLowerCase().trim();
|
|
26076
|
+
if (na === nb)
|
|
26077
|
+
return 1;
|
|
26078
|
+
const wordsA = new Set(na.split(/\W+/).filter(Boolean));
|
|
26079
|
+
const wordsB = new Set(nb.split(/\W+/).filter(Boolean));
|
|
26080
|
+
if (wordsA.size === 0 || wordsB.size === 0)
|
|
26081
|
+
return 0;
|
|
26082
|
+
let intersection3 = 0;
|
|
26083
|
+
for (const w of wordsA) {
|
|
26084
|
+
if (wordsB.has(w))
|
|
26085
|
+
intersection3++;
|
|
26086
|
+
}
|
|
26087
|
+
const union3 = wordsA.size + wordsB.size - intersection3;
|
|
26088
|
+
return union3 > 0 ? intersection3 / union3 : 0;
|
|
26089
|
+
}
|
|
26090
|
+
function generateHealthReport(report) {
|
|
26091
|
+
const mode = report.dryRun ? "Dry Run (preview)" : "Executed";
|
|
26092
|
+
const lines = [
|
|
26093
|
+
`# Memory Health Report
|
|
26094
|
+
`,
|
|
26095
|
+
`**Mode:** ${mode} | **Entities:** ${report.summary.totalEntities} | **Issues:** ${report.summary.issuesFound} | **Actions:** ${report.summary.actionsTaken}`,
|
|
26096
|
+
""
|
|
26097
|
+
];
|
|
26098
|
+
if (report.steps.prune) {
|
|
26099
|
+
const p = report.steps.prune;
|
|
26100
|
+
lines.push("## Stale Drafts");
|
|
26101
|
+
if (p.staleDraftsFound === 0) {
|
|
26102
|
+
lines.push(`No stale drafts found.
|
|
26103
|
+
`);
|
|
26104
|
+
} else {
|
|
26105
|
+
lines.push(`Found **${p.staleDraftsFound}** stale drafts${!report.dryRun ? ` (pruned ${p.pruned})` : ""}:`);
|
|
26106
|
+
lines.push("| Title | Age | Decay |");
|
|
26107
|
+
lines.push("|-------|-----|-------|");
|
|
26108
|
+
for (const item of p.items.slice(0, 20)) {
|
|
26109
|
+
lines.push(`| ${item.title} | ${item.ageDays}d | ${item.decayScore} |`);
|
|
26110
|
+
}
|
|
26111
|
+
lines.push("");
|
|
26112
|
+
}
|
|
26113
|
+
}
|
|
26114
|
+
if (report.steps.consolidate) {
|
|
26115
|
+
const c = report.steps.consolidate;
|
|
26116
|
+
lines.push("## Consolidation");
|
|
26117
|
+
if (c.clustersFound === 0) {
|
|
26118
|
+
lines.push(`Scanned ${c.entitiesProcessed} draft/episode entities — no clusters found.
|
|
26119
|
+
`);
|
|
26120
|
+
} else {
|
|
26121
|
+
lines.push(`Found **${c.clustersFound}** clusters across ${c.entitiesProcessed} entities:`);
|
|
26122
|
+
for (const d of c.details.slice(0, 10)) {
|
|
26123
|
+
lines.push(`- **${d.mergedTitle}** — ${d.clusterSize} entities`);
|
|
26124
|
+
}
|
|
26125
|
+
lines.push("");
|
|
26126
|
+
}
|
|
26127
|
+
}
|
|
26128
|
+
if (report.steps.orphans) {
|
|
26129
|
+
const o = report.steps.orphans;
|
|
26130
|
+
lines.push("## Orphaned Entities");
|
|
26131
|
+
if (o.orphansFound === 0) {
|
|
26132
|
+
lines.push(`No orphans found.
|
|
26133
|
+
`);
|
|
26134
|
+
} else {
|
|
26135
|
+
lines.push(`Found **${o.orphansFound}** orphans${!report.dryRun ? ` (removed ${o.removed})` : ""}:`);
|
|
26136
|
+
lines.push("| Title | Type | Tier | Age | Accesses |");
|
|
26137
|
+
lines.push("|-------|------|------|-----|----------|");
|
|
26138
|
+
for (const item of o.items.slice(0, 20)) {
|
|
26139
|
+
lines.push(`| ${item.title} | ${item.type} | ${item.tier} | ${item.ageDays}d | ${item.accessCount} |`);
|
|
26140
|
+
}
|
|
26141
|
+
lines.push("");
|
|
26142
|
+
}
|
|
26143
|
+
}
|
|
26144
|
+
if (report.steps.duplicates) {
|
|
26145
|
+
const d = report.steps.duplicates;
|
|
26146
|
+
lines.push("## Near-Duplicates");
|
|
26147
|
+
if (d.duplicatePairsFound === 0) {
|
|
26148
|
+
lines.push(`No duplicates found.
|
|
26149
|
+
`);
|
|
26150
|
+
} else {
|
|
26151
|
+
lines.push(`Found **${d.duplicatePairsFound}** duplicate pairs${!report.dryRun ? ` (resolved ${d.resolved})` : ""}:`);
|
|
26152
|
+
for (const pair of d.pairs.slice(0, 20)) {
|
|
26153
|
+
lines.push(`- "${pair.keepTitle}" ~ "${pair.removeTitle}" (${Math.round(pair.similarity * 100)}% similar, keep first)`);
|
|
26154
|
+
}
|
|
26155
|
+
lines.push("");
|
|
26156
|
+
}
|
|
26157
|
+
}
|
|
26158
|
+
if (report.steps.backfill) {
|
|
26159
|
+
const b = report.steps.backfill;
|
|
26160
|
+
lines.push("## Embedding Coverage");
|
|
26161
|
+
if (report.dryRun) {
|
|
26162
|
+
lines.push("Backfill will run when executed with `dryRun: false`.\n");
|
|
26163
|
+
} else if (b.remaining === 0) {
|
|
26164
|
+
lines.push(`All embeddings up to date (processed ${b.processed}).
|
|
26165
|
+
`);
|
|
26166
|
+
} else {
|
|
26167
|
+
lines.push(`Processed ${b.processed} entities. ${b.remaining} still need embeddings.
|
|
26168
|
+
`);
|
|
26169
|
+
}
|
|
26170
|
+
}
|
|
26171
|
+
if (report.errors.length > 0) {
|
|
26172
|
+
lines.push("## Errors");
|
|
26173
|
+
for (const e of report.errors) {
|
|
26174
|
+
lines.push(`- **${e.step}:** ${e.message}`);
|
|
26175
|
+
}
|
|
26176
|
+
lines.push("");
|
|
26177
|
+
}
|
|
26178
|
+
if (report.dryRun) {
|
|
26179
|
+
lines.push("---\n*Run with `dryRun: false` to execute cleanup.*");
|
|
26180
|
+
}
|
|
26181
|
+
return lines.join(`
|
|
26182
|
+
`);
|
|
26183
|
+
}
|
|
26184
|
+
|
|
25854
26185
|
// src/onboard.ts
|
|
25855
26186
|
async function onboardNewUser(params) {
|
|
25856
26187
|
const {
|
|
@@ -27285,6 +27616,47 @@ var TOOLS = {
|
|
|
27285
27616
|
},
|
|
27286
27617
|
required: []
|
|
27287
27618
|
}
|
|
27619
|
+
},
|
|
27620
|
+
harmony_cleanup_memories: {
|
|
27621
|
+
description: "Run a unified memory cleanup: prune stale drafts, consolidate similar memories, detect orphans and duplicates, and backfill embeddings. Returns a health report. Dry-run by default — run with dryRun=false to execute.",
|
|
27622
|
+
inputSchema: {
|
|
27623
|
+
type: "object",
|
|
27624
|
+
properties: {
|
|
27625
|
+
workspaceId: {
|
|
27626
|
+
type: "string",
|
|
27627
|
+
description: "Workspace ID (optional if context set)"
|
|
27628
|
+
},
|
|
27629
|
+
projectId: {
|
|
27630
|
+
type: "string",
|
|
27631
|
+
description: "Project ID (optional)"
|
|
27632
|
+
},
|
|
27633
|
+
dryRun: {
|
|
27634
|
+
type: "boolean",
|
|
27635
|
+
description: "Preview cleanup without executing changes (default: true)"
|
|
27636
|
+
},
|
|
27637
|
+
steps: {
|
|
27638
|
+
type: "array",
|
|
27639
|
+
items: {
|
|
27640
|
+
type: "string",
|
|
27641
|
+
enum: ["prune", "consolidate", "orphans", "duplicates", "backfill"]
|
|
27642
|
+
},
|
|
27643
|
+
description: "Which cleanup steps to run (default: all). Options: prune, consolidate, orphans, duplicates, backfill."
|
|
27644
|
+
},
|
|
27645
|
+
maxAgeDays: {
|
|
27646
|
+
type: "number",
|
|
27647
|
+
description: "Max age in days for stale draft pruning (default: 30)"
|
|
27648
|
+
},
|
|
27649
|
+
minClusterSize: {
|
|
27650
|
+
type: "number",
|
|
27651
|
+
description: "Min cluster size for consolidation (default: 3)"
|
|
27652
|
+
},
|
|
27653
|
+
orphanAgeDays: {
|
|
27654
|
+
type: "number",
|
|
27655
|
+
description: "Min age in days for orphan detection (default: 14)"
|
|
27656
|
+
}
|
|
27657
|
+
},
|
|
27658
|
+
required: []
|
|
27659
|
+
}
|
|
27288
27660
|
}
|
|
27289
27661
|
};
|
|
27290
27662
|
var RESOURCES = [
|
|
@@ -28795,6 +29167,27 @@ async function handleToolCall(name, args, deps) {
|
|
|
28795
29167
|
message: `Processed ${entitiesProcessed} entities, created ${relationsCreated} new relations.`
|
|
28796
29168
|
};
|
|
28797
29169
|
}
|
|
29170
|
+
case "harmony_cleanup_memories": {
|
|
29171
|
+
const workspaceId = args.workspaceId || deps.getActiveWorkspaceId();
|
|
29172
|
+
if (!workspaceId) {
|
|
29173
|
+
throw new Error("No workspace specified. Use harmony_set_workspace_context or provide workspaceId.");
|
|
29174
|
+
}
|
|
29175
|
+
const projectId = args.projectId || deps.getActiveProjectId() || undefined;
|
|
29176
|
+
const report = await runMemoryCleanup(client3, workspaceId, projectId, {
|
|
29177
|
+
dryRun: args.dryRun,
|
|
29178
|
+
steps: args.steps,
|
|
29179
|
+
maxAgeDays: args.maxAgeDays,
|
|
29180
|
+
minClusterSize: args.minClusterSize,
|
|
29181
|
+
orphanAgeDays: args.orphanAgeDays
|
|
29182
|
+
});
|
|
29183
|
+
return {
|
|
29184
|
+
success: report.success,
|
|
29185
|
+
dryRun: report.dryRun,
|
|
29186
|
+
summary: report.summary,
|
|
29187
|
+
errors: report.errors,
|
|
29188
|
+
healthReport: report.healthReport
|
|
29189
|
+
};
|
|
29190
|
+
}
|
|
28798
29191
|
default:
|
|
28799
29192
|
throw new Error(`Unknown tool: ${name}`);
|
|
28800
29193
|
}
|