@gethmy/mcp 2.4.7 → 2.5.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/README.md +34 -1
- package/dist/cli.js +20826 -18366
- package/dist/index.js +20924 -18464
- package/dist/lib/api-client.js +122 -925
- package/package.json +2 -2
- package/src/__tests__/mcp-integration.test.ts +141 -0
- package/src/__tests__/memory-floor.test.ts +126 -0
- package/src/__tests__/memory-park.test.ts +213 -0
- package/src/__tests__/memory-session.test.ts +77 -0
- package/src/__tests__/prompt-builder.test.ts +234 -0
- package/src/__tests__/skills.test.ts +111 -0
- package/src/__tests__/tool-dispatch.test.ts +260 -0
- package/src/api-client.ts +129 -96
- package/src/memory-floor.ts +264 -0
- package/src/memory-park.ts +252 -0
- package/src/memory-session.ts +61 -0
- package/src/prompt-builder.ts +93 -0
- package/src/server.ts +351 -1467
- package/src/__tests__/active-learning.test.ts +0 -483
- package/src/__tests__/agent-performance-profiles.test.ts +0 -468
- package/src/__tests__/context-assembly.test.ts +0 -506
- package/src/__tests__/lifecycle-maintenance.test.ts +0 -238
- package/src/__tests__/memory-audit.test.ts +0 -528
- package/src/__tests__/pattern-detection.test.ts +0 -438
- package/src/active-learning.ts +0 -1165
- package/src/consolidation.ts +0 -383
- package/src/context-assembly.ts +0 -1175
- package/src/lifecycle-maintenance.ts +0 -120
- package/src/memory-audit.ts +0 -578
- package/src/memory-cleanup.ts +0 -902
package/dist/lib/api-client.js
CHANGED
|
@@ -14,870 +14,22 @@ var __export = (target, all) => {
|
|
|
14
14
|
};
|
|
15
15
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
16
16
|
|
|
17
|
-
// ../memory/dist/schema.js
|
|
18
|
-
var init_schema = () => {};
|
|
19
|
-
|
|
20
|
-
// ../memory/dist/constraints.js
|
|
21
|
-
var init_constraints = __esm(() => {
|
|
22
|
-
init_schema();
|
|
23
|
-
});
|
|
24
|
-
// ../memory/dist/client.js
|
|
25
|
-
var init_client = __esm(() => {
|
|
26
|
-
init_constraints();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
// ../memory/dist/graph-walk.js
|
|
30
|
-
async function discoverRelatedContext(client, startIds, maxDepth = 2, maxEntities = 20, minConfidence = 0.5) {
|
|
31
|
-
const visited = new Set;
|
|
32
|
-
const collectedEntities = [];
|
|
33
|
-
const collectedRelations = [];
|
|
34
|
-
let truncated = false;
|
|
35
|
-
const queue = startIds.map((id) => [id, 0]);
|
|
36
|
-
for (const id of startIds) {
|
|
37
|
-
visited.add(id);
|
|
38
|
-
}
|
|
39
|
-
while (queue.length > 0) {
|
|
40
|
-
const [entityId, depth] = queue.shift();
|
|
41
|
-
if (collectedEntities.length >= maxEntities) {
|
|
42
|
-
truncated = true;
|
|
43
|
-
break;
|
|
44
|
-
}
|
|
45
|
-
if (depth > maxDepth)
|
|
46
|
-
continue;
|
|
47
|
-
try {
|
|
48
|
-
const entityResult = await client.getMemoryEntity(entityId);
|
|
49
|
-
const entity = entityResult.entity;
|
|
50
|
-
if (entity) {
|
|
51
|
-
collectedEntities.push({
|
|
52
|
-
id: entity.id,
|
|
53
|
-
type: entity.type,
|
|
54
|
-
title: entity.title,
|
|
55
|
-
confidence: entity.confidence ?? 1,
|
|
56
|
-
memory_tier: entity.memory_tier || "reference"
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
if (depth >= maxDepth)
|
|
60
|
-
continue;
|
|
61
|
-
const related = await client.getRelatedEntities(entityId);
|
|
62
|
-
for (const raw of related.outgoing || []) {
|
|
63
|
-
const rel = raw;
|
|
64
|
-
const relConfidence = rel.confidence ?? 1;
|
|
65
|
-
if (relConfidence < minConfidence)
|
|
66
|
-
continue;
|
|
67
|
-
const target = rel.target;
|
|
68
|
-
const targetId = target?.id ?? rel.target_id;
|
|
69
|
-
if (targetId && !visited.has(targetId)) {
|
|
70
|
-
visited.add(targetId);
|
|
71
|
-
queue.push([targetId, depth + 1]);
|
|
72
|
-
collectedRelations.push({
|
|
73
|
-
id: rel.id,
|
|
74
|
-
source_id: entityId,
|
|
75
|
-
target_id: targetId,
|
|
76
|
-
relation_type: rel.relation_type,
|
|
77
|
-
confidence: relConfidence
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
for (const raw of related.incoming || []) {
|
|
82
|
-
const rel = raw;
|
|
83
|
-
const relConfidence = rel.confidence ?? 1;
|
|
84
|
-
if (relConfidence < minConfidence)
|
|
85
|
-
continue;
|
|
86
|
-
const source = rel.source;
|
|
87
|
-
const sourceId = source?.id ?? rel.source_id;
|
|
88
|
-
if (sourceId && !visited.has(sourceId)) {
|
|
89
|
-
visited.add(sourceId);
|
|
90
|
-
queue.push([sourceId, depth + 1]);
|
|
91
|
-
collectedRelations.push({
|
|
92
|
-
id: rel.id,
|
|
93
|
-
source_id: sourceId,
|
|
94
|
-
target_id: entityId,
|
|
95
|
-
relation_type: rel.relation_type,
|
|
96
|
-
confidence: relConfidence
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
} catch {}
|
|
101
|
-
}
|
|
102
|
-
return {
|
|
103
|
-
entities: collectedEntities,
|
|
104
|
-
relations: collectedRelations,
|
|
105
|
-
depth: maxDepth,
|
|
106
|
-
truncated
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// ../memory/dist/lifecycle.js
|
|
111
|
-
function checkPromotion(currentTier, accessCount, confidence, createdAt) {
|
|
112
|
-
const ageDays = (Date.now() - new Date(createdAt).getTime()) / (1000 * 60 * 60 * 24);
|
|
113
|
-
const base = {
|
|
114
|
-
eligible: false,
|
|
115
|
-
targetTier: null,
|
|
116
|
-
reason: null,
|
|
117
|
-
currentTier,
|
|
118
|
-
accessCount,
|
|
119
|
-
confidence,
|
|
120
|
-
ageDays
|
|
121
|
-
};
|
|
122
|
-
if (currentTier === "draft") {
|
|
123
|
-
const rules = PROMOTION_RULES.draftToEpisode;
|
|
124
|
-
if (accessCount >= rules.minAccessCount && confidence >= rules.minConfidence && ageDays >= rules.minAgeDays) {
|
|
125
|
-
return {
|
|
126
|
-
...base,
|
|
127
|
-
eligible: true,
|
|
128
|
-
targetTier: "episode",
|
|
129
|
-
reason: `Accessed ${accessCount} times (≥${rules.minAccessCount}), confidence ${confidence} (≥${rules.minConfidence}), age ${Math.round(ageDays)}d (≥${rules.minAgeDays}d)`
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
if (currentTier === "episode") {
|
|
134
|
-
const rules = PROMOTION_RULES.episodeToReference;
|
|
135
|
-
if (accessCount >= rules.minAccessCount && confidence >= rules.minConfidence && ageDays >= rules.minAgeDays) {
|
|
136
|
-
return {
|
|
137
|
-
...base,
|
|
138
|
-
eligible: true,
|
|
139
|
-
targetTier: "reference",
|
|
140
|
-
reason: `Accessed ${accessCount} times (≥${rules.minAccessCount}), confidence ${confidence} (≥${rules.minConfidence}), age ${Math.round(ageDays)}d (≥${rules.minAgeDays}d)`
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
return base;
|
|
145
|
-
}
|
|
146
|
-
var PROMOTION_RULES;
|
|
147
|
-
var init_lifecycle = __esm(() => {
|
|
148
|
-
PROMOTION_RULES = {
|
|
149
|
-
draftToEpisode: {
|
|
150
|
-
minAccessCount: 5,
|
|
151
|
-
minConfidence: 0.8,
|
|
152
|
-
minAgeDays: 1
|
|
153
|
-
},
|
|
154
|
-
episodeToReference: {
|
|
155
|
-
minAccessCount: 10,
|
|
156
|
-
minConfidence: 0.9,
|
|
157
|
-
minAgeDays: 7
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
});
|
|
161
|
-
// ../memory/dist/sync.js
|
|
162
|
-
var init_sync = () => {};
|
|
163
|
-
|
|
164
|
-
// ../memory/dist/index.js
|
|
165
|
-
var init_dist = __esm(() => {
|
|
166
|
-
init_client();
|
|
167
|
-
init_constraints();
|
|
168
|
-
init_lifecycle();
|
|
169
|
-
init_schema();
|
|
170
|
-
init_sync();
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
// src/context-assembly.ts
|
|
174
|
-
var exports_context_assembly = {};
|
|
175
|
-
__export(exports_context_assembly, {
|
|
176
|
-
trackSessionAssembly: () => trackSessionAssembly,
|
|
177
|
-
recordContextFeedback: () => recordContextFeedback,
|
|
178
|
-
mapToContextEntity: () => mapToContextEntity,
|
|
179
|
-
getSessionAssemblyId: () => getSessionAssemblyId,
|
|
180
|
-
getCachedManifest: () => getCachedManifest,
|
|
181
|
-
expandQuery: () => expandQuery,
|
|
182
|
-
computeRelevanceScore: () => computeRelevanceScore,
|
|
183
|
-
cacheManifest: () => cacheManifest,
|
|
184
|
-
assembleContext: () => assembleContext
|
|
185
|
-
});
|
|
186
|
-
function estimateTokens(text) {
|
|
187
|
-
return Math.ceil(text.length / 4);
|
|
188
|
-
}
|
|
189
|
-
function passesQualityGate(entity) {
|
|
190
|
-
const content = entity.content.trim();
|
|
191
|
-
if (content.length < 50)
|
|
192
|
-
return false;
|
|
193
|
-
const normalizedTitle = entity.title.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim();
|
|
194
|
-
const normalizedContent = content.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim();
|
|
195
|
-
if (normalizedContent.length < normalizedTitle.length * 1.5) {
|
|
196
|
-
return false;
|
|
197
|
-
}
|
|
198
|
-
if (entity.type === "pattern" && /recurring .+ \(\d+ instances\)/i.test(entity.title)) {
|
|
199
|
-
const lines = content.split(`
|
|
200
|
-
`).filter((l) => l.trim().length > 0);
|
|
201
|
-
const bulletLines = lines.filter((l) => l.trim().startsWith("- "));
|
|
202
|
-
if (bulletLines.length > lines.length * 0.6)
|
|
203
|
-
return false;
|
|
204
|
-
}
|
|
205
|
-
if (entity.type === "procedure") {
|
|
206
|
-
const stepCount = (content.match(/^\d+\.\s/gm) || []).length;
|
|
207
|
-
if (stepCount < 3)
|
|
208
|
-
return false;
|
|
209
|
-
}
|
|
210
|
-
return true;
|
|
211
|
-
}
|
|
212
|
-
function generateAssemblyId() {
|
|
213
|
-
return `ctx_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
214
|
-
}
|
|
215
|
-
function truncateContent(content, maxTokens) {
|
|
216
|
-
const currentTokens = estimateTokens(content);
|
|
217
|
-
if (currentTokens <= maxTokens) {
|
|
218
|
-
return { text: content, truncated: false };
|
|
219
|
-
}
|
|
220
|
-
const paragraphs = content.split(/\n\n+/);
|
|
221
|
-
let result = paragraphs[0];
|
|
222
|
-
for (let i = 1;i < paragraphs.length; i++) {
|
|
223
|
-
const lines = paragraphs[i].split(`
|
|
224
|
-
`).filter((l) => l.startsWith("- ") || l.startsWith("* "));
|
|
225
|
-
if (lines.length > 0) {
|
|
226
|
-
const bulletSection = lines.join(`
|
|
227
|
-
`);
|
|
228
|
-
if (estimateTokens(result + `
|
|
229
|
-
|
|
230
|
-
` + bulletSection) <= maxTokens) {
|
|
231
|
-
result += `
|
|
232
|
-
|
|
233
|
-
` + bulletSection;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
if (estimateTokens(result) > maxTokens) {
|
|
238
|
-
const maxChars = maxTokens * 4;
|
|
239
|
-
result = result.slice(0, maxChars - 3) + "...";
|
|
240
|
-
}
|
|
241
|
-
return { text: result, truncated: true };
|
|
242
|
-
}
|
|
243
|
-
function escapeRegex(str) {
|
|
244
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
245
|
-
}
|
|
246
|
-
function expandQuery(taskContext) {
|
|
247
|
-
const queries = [taskContext];
|
|
248
|
-
const lowerQueries = [taskContext.toLowerCase()];
|
|
249
|
-
const words = taskContext.toLowerCase().split(/\W+/).filter((w) => w.length > 2);
|
|
250
|
-
const expandableWords = words.filter((w) => QUERY_SYNONYMS[w]);
|
|
251
|
-
for (const word of expandableWords) {
|
|
252
|
-
const synonyms = QUERY_SYNONYMS[word];
|
|
253
|
-
if (!synonyms)
|
|
254
|
-
continue;
|
|
255
|
-
const variation = taskContext.replace(new RegExp(`\\b${escapeRegex(word)}\\b`, "gi"), synonyms[0]);
|
|
256
|
-
const lowerVariation = variation.toLowerCase();
|
|
257
|
-
if (lowerVariation !== taskContext.toLowerCase() && !lowerQueries.includes(lowerVariation)) {
|
|
258
|
-
queries.push(variation);
|
|
259
|
-
lowerQueries.push(lowerVariation);
|
|
260
|
-
}
|
|
261
|
-
if (queries.length >= MAX_QUERY_VARIATIONS)
|
|
262
|
-
break;
|
|
263
|
-
}
|
|
264
|
-
if (words.length >= 3) {
|
|
265
|
-
const keyPhrases = words.filter((w) => ![
|
|
266
|
-
"the",
|
|
267
|
-
"and",
|
|
268
|
-
"for",
|
|
269
|
-
"with",
|
|
270
|
-
"this",
|
|
271
|
-
"that",
|
|
272
|
-
"from",
|
|
273
|
-
"into"
|
|
274
|
-
].includes(w)).slice(0, 4).join(" ");
|
|
275
|
-
if (!lowerQueries.includes(keyPhrases)) {
|
|
276
|
-
queries.push(keyPhrases);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
return queries.slice(0, MAX_QUERY_VARIATIONS);
|
|
280
|
-
}
|
|
281
|
-
function computeRelevanceScore(entity, taskContext, cardLabels, graphRelations) {
|
|
282
|
-
const reasons = [];
|
|
283
|
-
let score = 0;
|
|
284
|
-
const hasRrfScore = entity.rrf_score !== undefined && entity.rrf_score > 0;
|
|
285
|
-
if (hasRrfScore) {
|
|
286
|
-
const normalizedRrf = Math.min(entity.rrf_score / 0.04, 1);
|
|
287
|
-
const rrfContribution = normalizedRrf * 0.3;
|
|
288
|
-
score += rrfContribution;
|
|
289
|
-
reasons.push(`hybrid_search(rrf=${entity.rrf_score.toFixed(4)})`);
|
|
290
|
-
}
|
|
291
|
-
const textMatchWeight = hasRrfScore ? 0.15 : 0.4;
|
|
292
|
-
const taskWords = new Set(taskContext.toLowerCase().split(/\W+/).filter((w) => w.length > 2));
|
|
293
|
-
const entityWords = new Set(`${entity.title} ${entity.content}`.toLowerCase().split(/\W+/).filter((w) => w.length > 2));
|
|
294
|
-
const overlap = [...taskWords].filter((w) => entityWords.has(w));
|
|
295
|
-
if (overlap.length > 0) {
|
|
296
|
-
const textScore = Math.min(overlap.length / Math.max(taskWords.size, 1), 1) * textMatchWeight;
|
|
297
|
-
score += textScore;
|
|
298
|
-
reasons.push(`text_match(${overlap.length} words)`);
|
|
299
|
-
}
|
|
300
|
-
if (cardLabels.length > 0 && entity.tags.length > 0) {
|
|
301
|
-
const labelSet = new Set(cardLabels.map((l) => l.toLowerCase()));
|
|
302
|
-
const tagOverlap = entity.tags.filter((t) => labelSet.has(t.toLowerCase()));
|
|
303
|
-
if (tagOverlap.length > 0) {
|
|
304
|
-
const tagScore = tagOverlap.length / cardLabels.length * 0.3;
|
|
305
|
-
score += tagScore;
|
|
306
|
-
reasons.push(`tag_match(${tagOverlap.join(",")})`);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
score += entity.confidence * 0.15;
|
|
310
|
-
if (entity.confidence >= 0.9) {
|
|
311
|
-
reasons.push("high_confidence");
|
|
312
|
-
}
|
|
313
|
-
if (entity.last_accessed_at) {
|
|
314
|
-
const daysSinceAccess = (Date.now() - new Date(entity.last_accessed_at).getTime()) / (1000 * 60 * 60 * 24);
|
|
315
|
-
const halfLife = { draft: 7, episode: 30, reference: 180 }[entity.memory_tier];
|
|
316
|
-
const recencyScore = 0.5 ** (daysSinceAccess / halfLife) * 0.1;
|
|
317
|
-
score += recencyScore;
|
|
318
|
-
if (daysSinceAccess < 7)
|
|
319
|
-
reasons.push("recently_accessed");
|
|
320
|
-
}
|
|
321
|
-
if (entity.access_count > 0) {
|
|
322
|
-
const freqScore = Math.log10(entity.access_count + 1) * 0.05;
|
|
323
|
-
score += Math.min(freqScore, 0.1);
|
|
324
|
-
if (entity.access_count >= 5)
|
|
325
|
-
reasons.push(`frequently_used(${entity.access_count})`);
|
|
326
|
-
}
|
|
327
|
-
const usefulnessScore = entity.metadata?.usefulness_score ?? 0;
|
|
328
|
-
if (usefulnessScore >= 3) {
|
|
329
|
-
const usefulnessBoost = Math.min(usefulnessScore / 20, 0.15);
|
|
330
|
-
score += usefulnessBoost;
|
|
331
|
-
reasons.push(`useful(${usefulnessScore})`);
|
|
332
|
-
} else if (usefulnessScore === 0 && entity.access_count >= 5) {
|
|
333
|
-
score -= 0.02;
|
|
334
|
-
reasons.push("low_usefulness");
|
|
335
|
-
}
|
|
336
|
-
if (entity.type === "procedure") {
|
|
337
|
-
score += 0.1;
|
|
338
|
-
reasons.push("procedure_boost");
|
|
339
|
-
}
|
|
340
|
-
if (graphRelations && graphRelations.length > 0) {
|
|
341
|
-
const entityRelations = graphRelations.filter((r) => r.source_id === entity.id || r.target_id === entity.id);
|
|
342
|
-
if (entityRelations.length > 0) {
|
|
343
|
-
let bestBonus = 0;
|
|
344
|
-
let bestRelType = "";
|
|
345
|
-
for (const rel of entityRelations) {
|
|
346
|
-
const bonus = RELATION_BONUSES[rel.relation_type] ?? 0.1;
|
|
347
|
-
if (bonus > bestBonus) {
|
|
348
|
-
bestBonus = bonus;
|
|
349
|
-
bestRelType = rel.relation_type;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
score += bestBonus;
|
|
353
|
-
reasons.push(`graph_walk(${bestRelType})`);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
score = Math.max(0, Math.min(score, 1));
|
|
357
|
-
const tierWeight = TIER_WEIGHTS[entity.memory_tier];
|
|
358
|
-
score *= tierWeight;
|
|
359
|
-
return { score, reasons };
|
|
360
|
-
}
|
|
361
|
-
async function assembleContext(options) {
|
|
362
|
-
const {
|
|
363
|
-
workspaceId,
|
|
364
|
-
projectId,
|
|
365
|
-
taskContext,
|
|
366
|
-
cardLabels = [],
|
|
367
|
-
tokenBudget = DEFAULT_TOKEN_BUDGET,
|
|
368
|
-
client: client2,
|
|
369
|
-
graphWalkEnabled = true,
|
|
370
|
-
queryExpansionEnabled = true,
|
|
371
|
-
enableLlmReranking = false,
|
|
372
|
-
rerankFn
|
|
373
|
-
} = options;
|
|
374
|
-
const assemblyId = generateAssemblyId();
|
|
375
|
-
const manifest = {
|
|
376
|
-
assemblyId,
|
|
377
|
-
timestamp: new Date().toISOString(),
|
|
378
|
-
included: [],
|
|
379
|
-
excluded: [],
|
|
380
|
-
budgetUsed: 0,
|
|
381
|
-
budgetTotal: tokenBudget,
|
|
382
|
-
tierBreakdown: {
|
|
383
|
-
draft: { count: 0, tokens: 0 },
|
|
384
|
-
episode: { count: 0, tokens: 0 },
|
|
385
|
-
reference: { count: 0, tokens: 0 }
|
|
386
|
-
}
|
|
387
|
-
};
|
|
388
|
-
const candidates = [];
|
|
389
|
-
const queries = queryExpansionEnabled ? expandQuery(taskContext) : [taskContext];
|
|
390
|
-
const searchResults = await Promise.allSettled(queries.map((query) => client2.searchMemoryEntities(workspaceId, query, {
|
|
391
|
-
project_id: projectId,
|
|
392
|
-
limit: 30
|
|
393
|
-
})));
|
|
394
|
-
const candidateIds = new Set;
|
|
395
|
-
for (const result of searchResults) {
|
|
396
|
-
if (result.status !== "fulfilled")
|
|
397
|
-
continue;
|
|
398
|
-
if (result.value.entities?.length > 0) {
|
|
399
|
-
for (const raw of result.value.entities) {
|
|
400
|
-
const entity = mapToContextEntity(raw);
|
|
401
|
-
if (!candidateIds.has(entity.id)) {
|
|
402
|
-
candidateIds.add(entity.id);
|
|
403
|
-
candidates.push(entity);
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
if (candidates.length < 10 && projectId) {
|
|
409
|
-
try {
|
|
410
|
-
const listResult = await client2.listMemoryEntities({
|
|
411
|
-
workspace_id: workspaceId,
|
|
412
|
-
project_id: projectId,
|
|
413
|
-
limit: 30
|
|
414
|
-
});
|
|
415
|
-
if (listResult.entities?.length > 0) {
|
|
416
|
-
for (const raw of listResult.entities) {
|
|
417
|
-
const entity = mapToContextEntity(raw);
|
|
418
|
-
if (!candidateIds.has(entity.id)) {
|
|
419
|
-
candidateIds.add(entity.id);
|
|
420
|
-
candidates.push(entity);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
} catch {}
|
|
425
|
-
}
|
|
426
|
-
if (candidates.length < 20) {
|
|
427
|
-
try {
|
|
428
|
-
const wsResult = await client2.listMemoryEntities({
|
|
429
|
-
workspace_id: workspaceId,
|
|
430
|
-
scope: "workspace",
|
|
431
|
-
limit: 20
|
|
432
|
-
});
|
|
433
|
-
if (wsResult.entities?.length > 0) {
|
|
434
|
-
for (const raw of wsResult.entities) {
|
|
435
|
-
const entity = mapToContextEntity(raw);
|
|
436
|
-
if (!candidateIds.has(entity.id)) {
|
|
437
|
-
candidateIds.add(entity.id);
|
|
438
|
-
candidates.push(entity);
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
} catch {}
|
|
443
|
-
}
|
|
444
|
-
let graphRelations = [];
|
|
445
|
-
if (graphWalkEnabled && candidates.length > 0) {
|
|
446
|
-
try {
|
|
447
|
-
const seedCandidates = [...candidates].sort((a, b) => (b.rrf_score ?? 0) - (a.rrf_score ?? 0)).slice(0, GRAPH_WALK_SEED_COUNT);
|
|
448
|
-
const seedIds = seedCandidates.map((c) => c.id);
|
|
449
|
-
const walkResult = await discoverRelatedContext(client2, seedIds, GRAPH_WALK_MAX_DEPTH, GRAPH_WALK_MAX_ENTITIES, GRAPH_WALK_MIN_CONFIDENCE);
|
|
450
|
-
graphRelations = walkResult.relations;
|
|
451
|
-
const newEntityIds = walkResult.entities.filter((e) => !candidateIds.has(e.id)).map((e) => e.id);
|
|
452
|
-
if (newEntityIds.length > 0) {
|
|
453
|
-
const fetchResults = await Promise.allSettled(newEntityIds.map((id) => client2.getMemoryEntity(id)));
|
|
454
|
-
for (const result of fetchResults) {
|
|
455
|
-
if (result.status !== "fulfilled" || !result.value.entity)
|
|
456
|
-
continue;
|
|
457
|
-
const mapped = mapToContextEntity(result.value.entity);
|
|
458
|
-
candidateIds.add(mapped.id);
|
|
459
|
-
candidates.push(mapped);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
} catch {}
|
|
463
|
-
}
|
|
464
|
-
if (candidates.length === 0) {
|
|
465
|
-
return {
|
|
466
|
-
context: "",
|
|
467
|
-
manifest,
|
|
468
|
-
memories: []
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
const qualityCandidates = candidates.filter((entity) => {
|
|
472
|
-
if (passesQualityGate(entity))
|
|
473
|
-
return true;
|
|
474
|
-
manifest.excluded.push({
|
|
475
|
-
entityId: entity.id,
|
|
476
|
-
title: entity.title,
|
|
477
|
-
type: entity.type,
|
|
478
|
-
tier: entity.memory_tier,
|
|
479
|
-
relevanceScore: 0,
|
|
480
|
-
reason: "failed_quality_gate"
|
|
481
|
-
});
|
|
482
|
-
return false;
|
|
483
|
-
});
|
|
484
|
-
if (qualityCandidates.length === 0) {
|
|
485
|
-
return {
|
|
486
|
-
context: "",
|
|
487
|
-
manifest,
|
|
488
|
-
memories: []
|
|
489
|
-
};
|
|
490
|
-
}
|
|
491
|
-
const scored = qualityCandidates.map((entity) => {
|
|
492
|
-
const { score, reasons } = computeRelevanceScore(entity, taskContext, cardLabels, graphRelations.length > 0 ? graphRelations : undefined);
|
|
493
|
-
return { entity, score, reasons };
|
|
494
|
-
});
|
|
495
|
-
scored.sort((a, b) => b.score - a.score);
|
|
496
|
-
if (enableLlmReranking && rerankFn && scored.length >= RERANK_MIN_CANDIDATES) {
|
|
497
|
-
const topN = scored.slice(0, RERANK_TOP_N);
|
|
498
|
-
const scoreRange = topN[0].score - topN[topN.length - 1].score;
|
|
499
|
-
if (scoreRange <= RERANK_CLUSTER_THRESHOLD) {
|
|
500
|
-
try {
|
|
501
|
-
const rerankCandidates = topN.map((s) => ({
|
|
502
|
-
id: s.entity.id,
|
|
503
|
-
title: s.entity.title,
|
|
504
|
-
snippet: s.entity.content.slice(0, 200)
|
|
505
|
-
}));
|
|
506
|
-
const rerankedIds = await rerankFn(taskContext, rerankCandidates);
|
|
507
|
-
const idOrder = new Map(rerankedIds.map((id, i) => [id, i]));
|
|
508
|
-
topN.sort((a, b) => {
|
|
509
|
-
const aIdx = idOrder.get(a.entity.id) ?? 999;
|
|
510
|
-
const bIdx = idOrder.get(b.entity.id) ?? 999;
|
|
511
|
-
return aIdx - bIdx;
|
|
512
|
-
});
|
|
513
|
-
scored.splice(0, topN.length, ...topN);
|
|
514
|
-
} catch {}
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
const procedureBudget = Math.floor(tokenBudget * PROCEDURE_BUDGET_FRACTION);
|
|
518
|
-
const remainingBudget = tokenBudget - procedureBudget;
|
|
519
|
-
const tierBudgets = {
|
|
520
|
-
reference: Math.floor(remainingBudget * TIER_BUDGET_ALLOCATION.reference),
|
|
521
|
-
episode: Math.floor(remainingBudget * TIER_BUDGET_ALLOCATION.episode),
|
|
522
|
-
draft: Math.floor(remainingBudget * TIER_BUDGET_ALLOCATION.draft)
|
|
523
|
-
};
|
|
524
|
-
const tierUsed = {
|
|
525
|
-
reference: 0,
|
|
526
|
-
episode: 0,
|
|
527
|
-
draft: 0
|
|
528
|
-
};
|
|
529
|
-
let procedureUsed = 0;
|
|
530
|
-
const included = [];
|
|
531
|
-
let totalUsed = 0;
|
|
532
|
-
let referenceCount = 0;
|
|
533
|
-
for (const item of scored) {
|
|
534
|
-
if (item.entity.memory_tier === "reference" && item.entity.type !== "procedure" && referenceCount < MIN_REFERENCE_SLOTS) {
|
|
535
|
-
const { text, truncated } = truncateContent(item.entity.content, MAX_TOKENS_PER_ENTITY);
|
|
536
|
-
const tokens = estimateTokens(`### ${item.entity.title}
|
|
537
|
-
${text}`);
|
|
538
|
-
if (totalUsed + tokens <= tokenBudget) {
|
|
539
|
-
included.push({ ...item, tokens, truncated });
|
|
540
|
-
item.entity.content = text;
|
|
541
|
-
totalUsed += tokens;
|
|
542
|
-
tierUsed.reference += tokens;
|
|
543
|
-
referenceCount++;
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
const includedIds = new Set(included.map((i) => i.entity.id));
|
|
548
|
-
const procedureCandidates = scored.filter((item) => item.entity.type === "procedure" && !includedIds.has(item.entity.id));
|
|
549
|
-
for (const item of procedureCandidates) {
|
|
550
|
-
if (item.score < MIN_RELEVANCE_THRESHOLD) {
|
|
551
|
-
manifest.excluded.push({
|
|
552
|
-
entityId: item.entity.id,
|
|
553
|
-
title: item.entity.title,
|
|
554
|
-
type: item.entity.type,
|
|
555
|
-
tier: item.entity.memory_tier,
|
|
556
|
-
relevanceScore: item.score,
|
|
557
|
-
reason: "below_relevance_threshold"
|
|
558
|
-
});
|
|
559
|
-
continue;
|
|
560
|
-
}
|
|
561
|
-
const { text, truncated } = truncateContent(item.entity.content, MAX_TOKENS_PER_ENTITY);
|
|
562
|
-
const tokens = estimateTokens(`### ${item.entity.title}
|
|
563
|
-
${text}`);
|
|
564
|
-
if (procedureUsed + tokens > procedureBudget) {
|
|
565
|
-
const totalRemaining = tokenBudget - totalUsed;
|
|
566
|
-
if (tokens > totalRemaining) {
|
|
567
|
-
manifest.excluded.push({
|
|
568
|
-
entityId: item.entity.id,
|
|
569
|
-
title: item.entity.title,
|
|
570
|
-
type: item.entity.type,
|
|
571
|
-
tier: item.entity.memory_tier,
|
|
572
|
-
relevanceScore: item.score,
|
|
573
|
-
reason: "procedure_budget_exceeded"
|
|
574
|
-
});
|
|
575
|
-
continue;
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
if (totalUsed + tokens > tokenBudget) {
|
|
579
|
-
manifest.excluded.push({
|
|
580
|
-
entityId: item.entity.id,
|
|
581
|
-
title: item.entity.title,
|
|
582
|
-
type: item.entity.type,
|
|
583
|
-
tier: item.entity.memory_tier,
|
|
584
|
-
relevanceScore: item.score,
|
|
585
|
-
reason: "total_budget_exceeded"
|
|
586
|
-
});
|
|
587
|
-
continue;
|
|
588
|
-
}
|
|
589
|
-
included.push({ ...item, tokens, truncated });
|
|
590
|
-
item.entity.content = text;
|
|
591
|
-
totalUsed += tokens;
|
|
592
|
-
procedureUsed += tokens;
|
|
593
|
-
includedIds.add(item.entity.id);
|
|
594
|
-
}
|
|
595
|
-
for (const item of scored) {
|
|
596
|
-
if (includedIds.has(item.entity.id))
|
|
597
|
-
continue;
|
|
598
|
-
if (item.entity.type === "procedure")
|
|
599
|
-
continue;
|
|
600
|
-
if (item.score < MIN_RELEVANCE_THRESHOLD) {
|
|
601
|
-
manifest.excluded.push({
|
|
602
|
-
entityId: item.entity.id,
|
|
603
|
-
title: item.entity.title,
|
|
604
|
-
type: item.entity.type,
|
|
605
|
-
tier: item.entity.memory_tier,
|
|
606
|
-
relevanceScore: item.score,
|
|
607
|
-
reason: "below_relevance_threshold"
|
|
608
|
-
});
|
|
609
|
-
continue;
|
|
610
|
-
}
|
|
611
|
-
const tier = item.entity.memory_tier;
|
|
612
|
-
const { text, truncated } = truncateContent(item.entity.content, MAX_TOKENS_PER_ENTITY);
|
|
613
|
-
const tokens = estimateTokens(`### ${item.entity.title}
|
|
614
|
-
${text}`);
|
|
615
|
-
if (tierUsed[tier] + tokens > tierBudgets[tier]) {
|
|
616
|
-
const totalRemaining = tokenBudget - totalUsed;
|
|
617
|
-
if (tokens > totalRemaining) {
|
|
618
|
-
manifest.excluded.push({
|
|
619
|
-
entityId: item.entity.id,
|
|
620
|
-
title: item.entity.title,
|
|
621
|
-
type: item.entity.type,
|
|
622
|
-
tier,
|
|
623
|
-
relevanceScore: item.score,
|
|
624
|
-
reason: "budget_exceeded"
|
|
625
|
-
});
|
|
626
|
-
continue;
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
if (totalUsed + tokens > tokenBudget) {
|
|
630
|
-
manifest.excluded.push({
|
|
631
|
-
entityId: item.entity.id,
|
|
632
|
-
title: item.entity.title,
|
|
633
|
-
type: item.entity.type,
|
|
634
|
-
tier,
|
|
635
|
-
relevanceScore: item.score,
|
|
636
|
-
reason: "total_budget_exceeded"
|
|
637
|
-
});
|
|
638
|
-
continue;
|
|
639
|
-
}
|
|
640
|
-
included.push({ ...item, tokens, truncated });
|
|
641
|
-
item.entity.content = text;
|
|
642
|
-
totalUsed += tokens;
|
|
643
|
-
tierUsed[tier] += tokens;
|
|
644
|
-
includedIds.add(item.entity.id);
|
|
645
|
-
}
|
|
646
|
-
manifest.budgetUsed = totalUsed;
|
|
647
|
-
const procedureItems = included.filter((i) => i.entity.type === "procedure");
|
|
648
|
-
manifest.tierBreakdown = {
|
|
649
|
-
reference: {
|
|
650
|
-
count: included.filter((i) => i.entity.memory_tier === "reference" && i.entity.type !== "procedure").length,
|
|
651
|
-
tokens: tierUsed.reference
|
|
652
|
-
},
|
|
653
|
-
episode: {
|
|
654
|
-
count: included.filter((i) => i.entity.memory_tier === "episode" && i.entity.type !== "procedure").length,
|
|
655
|
-
tokens: tierUsed.episode
|
|
656
|
-
},
|
|
657
|
-
draft: {
|
|
658
|
-
count: included.filter((i) => i.entity.memory_tier === "draft" && i.entity.type !== "procedure").length,
|
|
659
|
-
tokens: tierUsed.draft
|
|
660
|
-
}
|
|
661
|
-
};
|
|
662
|
-
manifest.procedureBreakdown = {
|
|
663
|
-
count: procedureItems.length,
|
|
664
|
-
tokens: procedureUsed,
|
|
665
|
-
budget: procedureBudget
|
|
666
|
-
};
|
|
667
|
-
for (const item of included) {
|
|
668
|
-
manifest.included.push({
|
|
669
|
-
entityId: item.entity.id,
|
|
670
|
-
title: item.entity.title,
|
|
671
|
-
type: item.entity.type,
|
|
672
|
-
tier: item.entity.memory_tier,
|
|
673
|
-
relevanceScore: item.score,
|
|
674
|
-
reasons: item.reasons,
|
|
675
|
-
tokenCount: item.tokens,
|
|
676
|
-
truncated: item.truncated
|
|
677
|
-
});
|
|
678
|
-
}
|
|
679
|
-
const contextSections = [];
|
|
680
|
-
const nonProcedureItems = included.filter((i) => i.entity.type !== "procedure");
|
|
681
|
-
if (included.length > 0) {
|
|
682
|
-
if (procedureItems.length > 0) {
|
|
683
|
-
contextSections.push(`## Procedures (${procedureItems.length} loaded, ${procedureUsed}/${procedureBudget} tokens)`);
|
|
684
|
-
for (const item of procedureItems) {
|
|
685
|
-
const tags = item.entity.tags.length > 0 ? ` [${item.entity.tags.join(", ")}]` : "";
|
|
686
|
-
const tierLabel = item.entity.memory_tier !== "reference" ? ` (${item.entity.memory_tier})` : "";
|
|
687
|
-
contextSections.push(`
|
|
688
|
-
### ${item.entity.title} (confidence: ${item.entity.confidence})${tierLabel}${tags}`);
|
|
689
|
-
contextSections.push(item.entity.content);
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
if (nonProcedureItems.length > 0) {
|
|
693
|
-
contextSections.push(`
|
|
694
|
-
## Relevant Memories (${nonProcedureItems.length} loaded, ${manifest.excluded.length} excluded)`);
|
|
695
|
-
contextSections.push(`*Assembly: ${assemblyId} | Budget: ${totalUsed}/${tokenBudget} tokens*`);
|
|
696
|
-
for (const item of nonProcedureItems) {
|
|
697
|
-
const tags = item.entity.tags.length > 0 ? ` [${item.entity.tags.join(", ")}]` : "";
|
|
698
|
-
const tierLabel = item.entity.memory_tier !== "reference" ? ` (${item.entity.memory_tier})` : "";
|
|
699
|
-
contextSections.push(`
|
|
700
|
-
### ${item.entity.title} (${item.entity.type}, confidence: ${item.entity.confidence})${tierLabel}${tags}`);
|
|
701
|
-
contextSections.push(item.entity.content);
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
incrementAccessCounts(client2, included.map((i) => i.entity.id)).catch(() => {});
|
|
706
|
-
promoteEligibleEntities(client2, included.map((i) => i.entity)).catch(() => {});
|
|
707
|
-
return {
|
|
708
|
-
context: contextSections.join(`
|
|
709
|
-
`),
|
|
710
|
-
manifest,
|
|
711
|
-
memories: included.map((i) => i.entity)
|
|
712
|
-
};
|
|
713
|
-
}
|
|
714
|
-
function mapToContextEntity(raw) {
|
|
715
|
-
const e = raw;
|
|
716
|
-
return {
|
|
717
|
-
id: e.id,
|
|
718
|
-
type: e.type,
|
|
719
|
-
title: e.title,
|
|
720
|
-
content: e.content,
|
|
721
|
-
confidence: e.confidence ?? 1,
|
|
722
|
-
tags: e.tags || [],
|
|
723
|
-
memory_tier: e.memory_tier || "reference",
|
|
724
|
-
access_count: e.access_count || 0,
|
|
725
|
-
last_accessed_at: e.last_accessed_at || null,
|
|
726
|
-
created_at: e.created_at || "",
|
|
727
|
-
updated_at: e.updated_at || "",
|
|
728
|
-
metadata: e.metadata ?? undefined,
|
|
729
|
-
rrf_score: e.rrf_score ?? undefined,
|
|
730
|
-
fts_rank: e.fts_rank ?? undefined,
|
|
731
|
-
semantic_rank: e.semantic_rank ?? undefined
|
|
732
|
-
};
|
|
733
|
-
}
|
|
734
|
-
async function incrementAccessCounts(client2, entityIds) {
|
|
735
|
-
if (entityIds.length === 0)
|
|
736
|
-
return;
|
|
737
|
-
try {
|
|
738
|
-
await client2.batchTouchMemoryEntities(entityIds);
|
|
739
|
-
} catch {
|
|
740
|
-
await Promise.allSettled(entityIds.map((id) => client2.touchMemoryEntity(id)));
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
async function promoteEligibleEntities(client2, entities) {
|
|
744
|
-
for (const entity of entities) {
|
|
745
|
-
if (entity.memory_tier === "reference")
|
|
746
|
-
continue;
|
|
747
|
-
if (!entity.created_at)
|
|
748
|
-
continue;
|
|
749
|
-
const promotion = checkPromotion(entity.memory_tier, entity.access_count + 1, entity.confidence, entity.created_at);
|
|
750
|
-
if (promotion.eligible && promotion.targetTier) {
|
|
751
|
-
try {
|
|
752
|
-
await client2.updateMemoryEntity(entity.id, {
|
|
753
|
-
memory_tier: promotion.targetTier,
|
|
754
|
-
metadata: {
|
|
755
|
-
...entity.metadata || {},
|
|
756
|
-
promoted_at: new Date().toISOString(),
|
|
757
|
-
promotion_reason: promotion.reason,
|
|
758
|
-
promoted_from: entity.memory_tier
|
|
759
|
-
}
|
|
760
|
-
});
|
|
761
|
-
} catch {}
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
function cacheManifest(manifest) {
|
|
766
|
-
if (manifestCache.size >= MAX_CACHE_SIZE) {
|
|
767
|
-
const firstKey = manifestCache.keys().next().value;
|
|
768
|
-
if (firstKey)
|
|
769
|
-
manifestCache.delete(firstKey);
|
|
770
|
-
}
|
|
771
|
-
manifestCache.set(manifest.assemblyId, manifest);
|
|
772
|
-
}
|
|
773
|
-
function getCachedManifest(assemblyId) {
|
|
774
|
-
return manifestCache.get(assemblyId);
|
|
775
|
-
}
|
|
776
|
-
function trackSessionAssembly(cardId, assemblyId) {
|
|
777
|
-
if (sessionAssemblyMap.size >= MAX_SESSION_MAP_SIZE) {
|
|
778
|
-
const firstKey = sessionAssemblyMap.keys().next().value;
|
|
779
|
-
if (firstKey)
|
|
780
|
-
sessionAssemblyMap.delete(firstKey);
|
|
781
|
-
}
|
|
782
|
-
sessionAssemblyMap.set(cardId, assemblyId);
|
|
783
|
-
}
|
|
784
|
-
function getSessionAssemblyId(cardId) {
|
|
785
|
-
return sessionAssemblyMap.get(cardId);
|
|
786
|
-
}
|
|
787
|
-
async function recordContextFeedback(client2, cardId, sessionStatus, progressPercent, hadBlockers) {
|
|
788
|
-
const assemblyId = sessionAssemblyMap.get(cardId);
|
|
789
|
-
if (!assemblyId)
|
|
790
|
-
return { adjusted: 0 };
|
|
791
|
-
const manifest = manifestCache.get(assemblyId);
|
|
792
|
-
if (!manifest || manifest.included.length === 0)
|
|
793
|
-
return { adjusted: 0 };
|
|
794
|
-
let adjusted = 0;
|
|
795
|
-
const isSuccess = sessionStatus === "completed" && (progressPercent ?? 0) >= 100;
|
|
796
|
-
for (const entry of manifest.included) {
|
|
797
|
-
try {
|
|
798
|
-
if (isSuccess) {
|
|
799
|
-
const { entity } = await client2.getMemoryEntity(entry.entityId);
|
|
800
|
-
const e = entity;
|
|
801
|
-
const currentUsefulness = e.metadata?.usefulness_score ?? 0;
|
|
802
|
-
const newConfidence = Math.min((e.confidence ?? 0.5) + 0.05, 1);
|
|
803
|
-
await client2.updateMemoryEntity(entry.entityId, {
|
|
804
|
-
confidence: newConfidence,
|
|
805
|
-
metadata: {
|
|
806
|
-
usefulness_score: currentUsefulness + 1,
|
|
807
|
-
last_feedback_at: new Date().toISOString()
|
|
808
|
-
}
|
|
809
|
-
});
|
|
810
|
-
adjusted++;
|
|
811
|
-
} else if (hadBlockers) {
|
|
812
|
-
const { entity } = await client2.getMemoryEntity(entry.entityId);
|
|
813
|
-
const e = entity;
|
|
814
|
-
const newConfidence = Math.max((e.confidence ?? 0.5) - 0.02, 0.1);
|
|
815
|
-
await client2.updateMemoryEntity(entry.entityId, {
|
|
816
|
-
confidence: newConfidence,
|
|
817
|
-
metadata: {
|
|
818
|
-
last_feedback_at: new Date().toISOString()
|
|
819
|
-
}
|
|
820
|
-
});
|
|
821
|
-
adjusted++;
|
|
822
|
-
}
|
|
823
|
-
} catch {}
|
|
824
|
-
}
|
|
825
|
-
sessionAssemblyMap.delete(cardId);
|
|
826
|
-
return { adjusted };
|
|
827
|
-
}
|
|
828
|
-
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;
|
|
829
|
-
var init_context_assembly = __esm(() => {
|
|
830
|
-
init_dist();
|
|
831
|
-
TIER_WEIGHTS = {
|
|
832
|
-
reference: 1,
|
|
833
|
-
episode: 0.7,
|
|
834
|
-
draft: 0.4
|
|
835
|
-
};
|
|
836
|
-
TIER_BUDGET_ALLOCATION = {
|
|
837
|
-
reference: 0.6,
|
|
838
|
-
episode: 0.3,
|
|
839
|
-
draft: 0.1
|
|
840
|
-
};
|
|
841
|
-
RELATION_BONUSES = {
|
|
842
|
-
depends_on: 0.15,
|
|
843
|
-
resolved_by: 0.2,
|
|
844
|
-
relates_to: 0.1,
|
|
845
|
-
implements: 0.15,
|
|
846
|
-
blocks: 0.15,
|
|
847
|
-
references: 0.1,
|
|
848
|
-
extends: 0.1,
|
|
849
|
-
caused_by: 0.15
|
|
850
|
-
};
|
|
851
|
-
QUERY_SYNONYMS = {
|
|
852
|
-
auth: ["authentication", "authorization", "session"],
|
|
853
|
-
authentication: ["auth", "session", "sign-in"],
|
|
854
|
-
login: ["sign-in", "authentication", "session"],
|
|
855
|
-
bug: ["error", "issue", "defect", "problem"],
|
|
856
|
-
error: ["exception", "failure", "issue"],
|
|
857
|
-
fix: ["resolve", "patch", "repair", "correct"],
|
|
858
|
-
deploy: ["deployment", "release", "ship", "publish"],
|
|
859
|
-
test: ["testing", "spec", "assertion", "verify"],
|
|
860
|
-
config: ["configuration", "settings", "setup"],
|
|
861
|
-
db: ["database", "storage", "persistence"],
|
|
862
|
-
database: ["storage", "persistence", "data store"],
|
|
863
|
-
api: ["endpoint", "route", "service"],
|
|
864
|
-
ui: ["frontend", "component", "view"],
|
|
865
|
-
perf: ["performance", "speed", "latency"],
|
|
866
|
-
performance: ["speed", "latency", "optimization"]
|
|
867
|
-
};
|
|
868
|
-
manifestCache = new Map;
|
|
869
|
-
sessionAssemblyMap = new Map;
|
|
870
|
-
});
|
|
871
|
-
|
|
872
17
|
// src/prompt-builder.ts
|
|
873
18
|
var exports_prompt_builder = {};
|
|
874
19
|
__export(exports_prompt_builder, {
|
|
20
|
+
proposePromptVariant: () => proposePromptVariant,
|
|
875
21
|
inferCategoryFromLabels: () => inferCategoryFromLabels,
|
|
876
22
|
getRoleFraming: () => getRoleFraming,
|
|
877
23
|
getAvailableVariants: () => getAvailableVariants,
|
|
878
24
|
getAvailableCategories: () => getAvailableCategories,
|
|
879
|
-
generatePrompt: () => generatePrompt
|
|
25
|
+
generatePrompt: () => generatePrompt,
|
|
26
|
+
computeContentHash: () => computeContentHash,
|
|
27
|
+
PROMPT_TEMPLATE_VERSION: () => PROMPT_TEMPLATE_VERSION
|
|
880
28
|
});
|
|
29
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
30
|
+
function computeContentHash(prompt) {
|
|
31
|
+
return createHash("sha256").update(prompt).digest("hex");
|
|
32
|
+
}
|
|
881
33
|
function inferCategoryFromLabels(labels) {
|
|
882
34
|
for (const label of labels) {
|
|
883
35
|
const normalizedName = label.name.toLowerCase().trim();
|
|
@@ -895,7 +47,7 @@ function inferCategoryFromLabels(labels) {
|
|
|
895
47
|
function getRoleFraming(category) {
|
|
896
48
|
return DEFAULT_ROLE_FRAMINGS[category];
|
|
897
49
|
}
|
|
898
|
-
function
|
|
50
|
+
function estimateTokens(text) {
|
|
899
51
|
return Math.ceil(text.length / 4);
|
|
900
52
|
}
|
|
901
53
|
function formatSubtasks(subtasks) {
|
|
@@ -1058,8 +210,11 @@ ${customConstraints}`);
|
|
|
1058
210
|
linkedCardCount: links.length,
|
|
1059
211
|
memoryCount
|
|
1060
212
|
},
|
|
1061
|
-
tokenEstimate:
|
|
1062
|
-
...assemblyId && { assemblyId }
|
|
213
|
+
tokenEstimate: estimateTokens(prompt),
|
|
214
|
+
...assemblyId && { assemblyId },
|
|
215
|
+
promptId: randomUUID(),
|
|
216
|
+
contentHash: computeContentHash(prompt),
|
|
217
|
+
version: PROMPT_TEMPLATE_VERSION
|
|
1063
218
|
};
|
|
1064
219
|
}
|
|
1065
220
|
function extractSessionInsights(assembledContext) {
|
|
@@ -1141,7 +296,26 @@ function getAvailableCategories() {
|
|
|
1141
296
|
function getAvailableVariants() {
|
|
1142
297
|
return ["analysis", "draft", "execute"];
|
|
1143
298
|
}
|
|
1144
|
-
|
|
299
|
+
async function proposePromptVariant(contentHash, fetchCohort) {
|
|
300
|
+
if (!contentHash)
|
|
301
|
+
return null;
|
|
302
|
+
const cohort = await fetchCohort(contentHash);
|
|
303
|
+
if (!cohort || cohort.length < VARIANT_MIN_COHORT)
|
|
304
|
+
return null;
|
|
305
|
+
const completed = cohort.filter((r) => r.status === "completed" && (r.progressPercent ?? 0) >= 100 && !r.hadBlockers).length;
|
|
306
|
+
const completionRate = completed / cohort.length;
|
|
307
|
+
if (completionRate >= VARIANT_COMPLETION_THRESHOLD)
|
|
308
|
+
return null;
|
|
309
|
+
const blockerRate = cohort.filter((r) => r.hadBlockers).length / cohort.length;
|
|
310
|
+
const framingHint = blockerRate >= 0.4 ? "Cohort hits frequent blockers — try a more diagnostic framing (require root-cause + repro before any fix)." : "Cohort frequently stalls without finishing — try a more action-forcing framing (smaller subtasks, explicit DoD checklist).";
|
|
311
|
+
return {
|
|
312
|
+
contentHash,
|
|
313
|
+
cohortSize: cohort.length,
|
|
314
|
+
completionRate,
|
|
315
|
+
framingHint
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
var PROMPT_TEMPLATE_VERSION = 1, LABEL_CATEGORY_MAP, DEFAULT_ROLE_FRAMINGS, VARIANT_INSTRUCTIONS, VARIANT_MIN_COHORT = 10, VARIANT_COMPLETION_THRESHOLD = 0.4;
|
|
1145
319
|
var init_prompt_builder = __esm(() => {
|
|
1146
320
|
LABEL_CATEGORY_MAP = {
|
|
1147
321
|
bug: "bug",
|
|
@@ -1500,6 +674,26 @@ function getMemoryDir() {
|
|
|
1500
674
|
return join(homedir(), ".harmony", "memory");
|
|
1501
675
|
}
|
|
1502
676
|
|
|
677
|
+
// ../harmony-shared/dist/cardLinks.js
|
|
678
|
+
var LINK_TYPE_INVERSES = {
|
|
679
|
+
relates_to: "relates_to",
|
|
680
|
+
blocks: "is_blocked_by",
|
|
681
|
+
duplicates: "is_duplicated_by",
|
|
682
|
+
is_part_of: "has_part"
|
|
683
|
+
};
|
|
684
|
+
function getDisplayLinkType(linkType, direction) {
|
|
685
|
+
if (direction === "outgoing")
|
|
686
|
+
return linkType;
|
|
687
|
+
return LINK_TYPE_INVERSES[linkType];
|
|
688
|
+
}
|
|
689
|
+
// ../harmony-shared/dist/constants.js
|
|
690
|
+
var TIMINGS = {
|
|
691
|
+
SEARCH_DEBOUNCE: 300,
|
|
692
|
+
AUTOSAVE_DEBOUNCE: 1000,
|
|
693
|
+
TOAST_DURATION: 3000,
|
|
694
|
+
QUERY_STALE_TIME: 1000 * 60 * 5,
|
|
695
|
+
QUERY_GC_TIME: 1000 * 60 * 60 * 24
|
|
696
|
+
};
|
|
1503
697
|
// src/api-client.ts
|
|
1504
698
|
var RETRY_CONFIG = {
|
|
1505
699
|
maxRetries: 3,
|
|
@@ -1848,22 +1042,6 @@ class HarmonyApiClient {
|
|
|
1848
1042
|
const query = params.toString() ? `?${params.toString()}` : "";
|
|
1849
1043
|
return this.request("GET", `/cards/${cardId}/agent-context${query}`);
|
|
1850
1044
|
}
|
|
1851
|
-
async getAgentProfile(workspaceId, agentIdentifier) {
|
|
1852
|
-
const params = new URLSearchParams({
|
|
1853
|
-
workspace_id: workspaceId,
|
|
1854
|
-
agent_identifier: agentIdentifier
|
|
1855
|
-
});
|
|
1856
|
-
return this.request("GET", `/agent-profiles?${params.toString()}`);
|
|
1857
|
-
}
|
|
1858
|
-
async listAgentProfiles(workspaceId) {
|
|
1859
|
-
const params = new URLSearchParams({ workspace_id: workspaceId });
|
|
1860
|
-
return this.request("GET", `/agent-profiles?${params.toString()}`);
|
|
1861
|
-
}
|
|
1862
|
-
async refreshAgentProfiles(workspaceId) {
|
|
1863
|
-
return this.request("POST", "/agent-profiles/refresh", {
|
|
1864
|
-
workspace_id: workspaceId
|
|
1865
|
-
});
|
|
1866
|
-
}
|
|
1867
1045
|
async createMemoryEntity(data) {
|
|
1868
1046
|
return this.request("POST", "/memory/entities", data);
|
|
1869
1047
|
}
|
|
@@ -1888,6 +1066,8 @@ class HarmonyApiClient {
|
|
|
1888
1066
|
params.set("limit", String(options.limit));
|
|
1889
1067
|
if (options.offset !== undefined)
|
|
1890
1068
|
params.set("offset", String(options.offset));
|
|
1069
|
+
if (options.include_superseded)
|
|
1070
|
+
params.set("include_superseded", "true");
|
|
1891
1071
|
return this.request("GET", `/memory/entities?${params.toString()}`);
|
|
1892
1072
|
}
|
|
1893
1073
|
async getMemoryEntity(entityId) {
|
|
@@ -2053,10 +1233,35 @@ class HarmonyApiClient {
|
|
|
2053
1233
|
async generateApiKey(name) {
|
|
2054
1234
|
return this.request("POST", "/api-keys", { name });
|
|
2055
1235
|
}
|
|
1236
|
+
async recordPromptHistory(data) {
|
|
1237
|
+
return this.request("POST", "/prompt-history", data);
|
|
1238
|
+
}
|
|
1239
|
+
async recordPromptHistoryFeedback(sessionId, outcome) {
|
|
1240
|
+
return this.request("POST", "/prompt-history/feedback", {
|
|
1241
|
+
sessionId,
|
|
1242
|
+
outcome
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
async getPromptHistoryCohort(contentHash) {
|
|
1246
|
+
const params = new URLSearchParams({ content_hash: contentHash });
|
|
1247
|
+
return this.request("GET", `/prompt-history/cohort?${params.toString()}`);
|
|
1248
|
+
}
|
|
2056
1249
|
async generateCardPrompt(options) {
|
|
2057
|
-
const {
|
|
1250
|
+
const { generatePrompt: generatePrompt2 } = await loadPromptModules();
|
|
2058
1251
|
const cardResult = await this.getCard(options.cardId);
|
|
2059
1252
|
const cardData = cardResult.card;
|
|
1253
|
+
try {
|
|
1254
|
+
const linksResult = await this.getCardLinks(options.cardId);
|
|
1255
|
+
const rawLinks = linksResult.links || [];
|
|
1256
|
+
cardData.links = rawLinks.filter((l) => l.target_card).map((l) => ({
|
|
1257
|
+
target_card: l.target_card,
|
|
1258
|
+
direction: l.direction,
|
|
1259
|
+
display_type: getDisplayLinkType(l.link_type, l.direction)
|
|
1260
|
+
}));
|
|
1261
|
+
} catch (err) {
|
|
1262
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1263
|
+
console.debug(`[generateCardPrompt] getCardLinks failed: ${msg}`);
|
|
1264
|
+
}
|
|
2060
1265
|
let columnData = null;
|
|
2061
1266
|
const projectIdForBoard = options.projectId || cardData.project_id;
|
|
2062
1267
|
if (projectIdForBoard) {
|
|
@@ -2069,51 +1274,29 @@ class HarmonyApiClient {
|
|
|
2069
1274
|
} catch {}
|
|
2070
1275
|
}
|
|
2071
1276
|
const variant = options.variant || "execute";
|
|
2072
|
-
|
|
2073
|
-
|
|
1277
|
+
const assembledContextStr = undefined;
|
|
1278
|
+
const assemblyId = undefined;
|
|
2074
1279
|
let memories;
|
|
2075
1280
|
try {
|
|
2076
1281
|
if (options.workspaceId && cardData.title) {
|
|
2077
|
-
const
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
workspaceId: options.workspaceId,
|
|
2081
|
-
projectId: options.projectId,
|
|
2082
|
-
taskContext,
|
|
2083
|
-
cardLabels,
|
|
2084
|
-
cardId: cardData.id,
|
|
2085
|
-
client: this
|
|
1282
|
+
const memoryResult = await this.searchMemoryEntities(options.workspaceId, cardData.title, {
|
|
1283
|
+
project_id: options.projectId,
|
|
1284
|
+
limit: 5
|
|
2086
1285
|
});
|
|
2087
|
-
if (
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
1286
|
+
if (memoryResult.entities?.length > 0) {
|
|
1287
|
+
memories = memoryResult.entities.map((e) => ({
|
|
1288
|
+
id: e.id,
|
|
1289
|
+
type: e.type,
|
|
1290
|
+
title: e.title,
|
|
1291
|
+
content: e.content,
|
|
1292
|
+
confidence: e.confidence,
|
|
1293
|
+
tags: e.tags || []
|
|
1294
|
+
}));
|
|
2091
1295
|
}
|
|
2092
1296
|
}
|
|
2093
1297
|
} catch (err) {
|
|
2094
1298
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2095
|
-
console.debug(`[generateCardPrompt]
|
|
2096
|
-
try {
|
|
2097
|
-
if (options.workspaceId && cardData.title) {
|
|
2098
|
-
const memoryResult = await this.searchMemoryEntities(options.workspaceId, cardData.title, {
|
|
2099
|
-
project_id: options.projectId,
|
|
2100
|
-
limit: 5
|
|
2101
|
-
});
|
|
2102
|
-
if (memoryResult.entities?.length > 0) {
|
|
2103
|
-
memories = memoryResult.entities.map((e) => ({
|
|
2104
|
-
id: e.id,
|
|
2105
|
-
type: e.type,
|
|
2106
|
-
title: e.title,
|
|
2107
|
-
content: e.content,
|
|
2108
|
-
confidence: e.confidence,
|
|
2109
|
-
tags: e.tags || []
|
|
2110
|
-
}));
|
|
2111
|
-
}
|
|
2112
|
-
}
|
|
2113
|
-
} catch (fallbackErr) {
|
|
2114
|
-
const fallbackMsg = fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr);
|
|
2115
|
-
console.debug(`[generateCardPrompt] Memory fallback also failed: ${fallbackMsg}`);
|
|
2116
|
-
}
|
|
1299
|
+
console.debug(`[generateCardPrompt] Memory search failed: ${msg}`);
|
|
2117
1300
|
}
|
|
2118
1301
|
const result = generatePrompt2({
|
|
2119
1302
|
card: cardData,
|
|
@@ -2125,6 +1308,25 @@ class HarmonyApiClient {
|
|
|
2125
1308
|
assembledContext: assembledContextStr,
|
|
2126
1309
|
assemblyId
|
|
2127
1310
|
});
|
|
1311
|
+
try {
|
|
1312
|
+
await this.recordPromptHistory({
|
|
1313
|
+
cardId: cardData.id,
|
|
1314
|
+
generatedPrompt: result.prompt,
|
|
1315
|
+
variant,
|
|
1316
|
+
contextIncluded: {
|
|
1317
|
+
assemblyId: result.assemblyId ?? null,
|
|
1318
|
+
tokenEstimate: result.tokenEstimate,
|
|
1319
|
+
contextSummary: result.contextSummary
|
|
1320
|
+
},
|
|
1321
|
+
sessionId: options.sessionId ?? null,
|
|
1322
|
+
contentHash: result.contentHash,
|
|
1323
|
+
templateVersion: result.version,
|
|
1324
|
+
confidence: 0.5
|
|
1325
|
+
});
|
|
1326
|
+
} catch (err) {
|
|
1327
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1328
|
+
console.debug(`[generateCardPrompt] prompt_history persistence failed: ${msg}`);
|
|
1329
|
+
}
|
|
2128
1330
|
return {
|
|
2129
1331
|
...result,
|
|
2130
1332
|
cardId: cardData.id,
|
|
@@ -2136,27 +1338,22 @@ class HarmonyApiClient {
|
|
|
2136
1338
|
var _promptModules = null;
|
|
2137
1339
|
async function loadPromptModules() {
|
|
2138
1340
|
if (!_promptModules) {
|
|
2139
|
-
const
|
|
2140
|
-
Promise.resolve().then(() => (init_context_assembly(), exports_context_assembly)),
|
|
2141
|
-
Promise.resolve().then(() => (init_prompt_builder(), exports_prompt_builder))
|
|
2142
|
-
]);
|
|
1341
|
+
const pb = await Promise.resolve().then(() => (init_prompt_builder(), exports_prompt_builder));
|
|
2143
1342
|
_promptModules = {
|
|
2144
|
-
assembleContext: ca.assembleContext,
|
|
2145
|
-
cacheManifest: ca.cacheManifest,
|
|
2146
1343
|
generatePrompt: pb.generatePrompt
|
|
2147
1344
|
};
|
|
2148
1345
|
}
|
|
2149
1346
|
return _promptModules;
|
|
2150
1347
|
}
|
|
2151
|
-
var
|
|
1348
|
+
var client = null;
|
|
2152
1349
|
function getClient() {
|
|
2153
|
-
if (!
|
|
2154
|
-
|
|
1350
|
+
if (!client) {
|
|
1351
|
+
client = new HarmonyApiClient;
|
|
2155
1352
|
}
|
|
2156
|
-
return
|
|
1353
|
+
return client;
|
|
2157
1354
|
}
|
|
2158
1355
|
function resetClient() {
|
|
2159
|
-
|
|
1356
|
+
client = null;
|
|
2160
1357
|
}
|
|
2161
1358
|
export {
|
|
2162
1359
|
signupUser,
|