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